<commit_before>#!/usr/bin/env python3

import configparser
from functools import lru_cache
import glob
import io
import os

import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import pandas as pd
# import numpy as np


TEST_RESULT_ROOT = "/Users/esteele/tmp/wifi-test/root/wifi-test-results"
METADATA_FILENAME = "metadata.ini"
TEST_RESULT_GLOB = "*.csv"
TEST_RESULT_DIR_PATTERN = os.path.join(TEST_RESULT_ROOT, "test-run-{0}")
TEST_RESULT_METADATA_FILE = os.path.join(TEST_RESULT_DIR_PATTERN,
                                         METADATA_FILENAME)
TEST_RESULT_FILES_PATTERN = os.path.join(TEST_RESULT_DIR_PATTERN,
                                         TEST_RESULT_GLOB)
BW_DESC = {
    150000: "360p bitrate",
    250000: "480p bitrate",
}


def get_test_run_results(test_run_id):
    test_result_files_glob = TEST_RESULT_FILES_PATTERN.format(test_run_id)
    result_sio = io.StringIO()
    for csv in glob.iglob(test_result_files_glob):
        with open(csv) as f:
            result_sio.write(f.read())

    # Prime for reading
    result_sio.seek(0)
    return result_sio


def get_dataframe_from_test_run(test_run_id, downscale_factor=1):
    raw_run_data = get_test_run_results(test_run_id)
    summary_data = pd.read_csv(raw_run_data,
                               comment="#",
                               names=["client_id",
                                      "timestamp",
                                      "bytes_per_sec"])
    # Create a time_offset column
    summary_data["time_offset"] = summary_data['timestamp'] - \
        min(summary_data['timestamp'])
    summary_data["time_offset"] = \
        (summary_data["time_offset"] // downscale_factor) * downscale_factor
    summary_data["time_offset"] = \
        pd.to_timedelta(summary_data['time_offset'], unit="s")
    summary_data.set_index("time_offset", inplace=True)
    return summary_data


def get_graph_title_for_run(test_run_id):
    config = configparser.ConfigParser()
    config.read(TEST_RESULT_METADATA_FILE.format(test_run_id))
    # Don't count the global section
    client_count = len(config.sections()) - 1
    stream_count = sum([int(config[s]["parallel_run_count"])
                        for s in config.sections()
                        if s != "global"])
    bandwidth_desc = "{0} bytes/sec".format(
        config["global"]["test_bandwidth_bps"]
    )
    title = "Run {0} against {1}{2}.\n" \
            "{3} streams between {4} clients. " \
            "Each stream attempting {5}" \
            .format(
                test_run_id,
                config["global"]["test_server_hostname"],
                config["global"]["extra_run_description"],
                stream_count,
                client_count,
                bandwidth_desc
            )
    return title


def get_graph_title_for_group(test_group_id):
    run_ids = get_test_run_ids_for_group_id(test_group_id)
    config = configparser.ConfigParser()
    config.read(TEST_RESULT_METADATA_FILE.format(run_ids[0]))
    # Don't count the global section
    client_count = len(config.sections()) - 1
    stream_count = sum([int(config[s]["parallel_run_count"])
                        for s in config.sections()
                        if s != "global"])
    bandwidth_desc = "{0} bytes/sec".format(
        config["global"]["test_bandwidth_bps"]
    )
    title = "Group run {0} against {1}{2} with {3} repeat runs.\n" \
            "{4} streams between {5} clients. " \
            "Each stream attempting {6}" \
            .format(
                test_group_id,
                config["global"]["test_server_hostname"],
                config["global"]["extra_run_description"],
                len(run_ids),
                stream_count,
                client_count,
                bandwidth_desc,
            )
    return title


def get_bw_annotation_detail(run_id):
    config = configparser.ConfigParser()
    config.read(TEST_RESULT_METADATA_FILE.format(run_id))
    bandwidth_bps = int(config["global"]["test_bandwidth_bps"])

    return bandwidth_bps, BW_DESC.get(bandwidth_bps, "")


def show_run_df_as_line_graph(df, title, ax):
    pivot_df = df.pivot(columns="client_id",
                        values="bytes_per_sec")
    ax = pivot_df.plot(figsize=(20, 10), ax=ax)
    ax.set_xlabel("Elapsed time (sec)")
    ax.set_xbound(lower=0)
    ax.set_ylabel("Throughput (bytes/sec)")
    ax.axhline(y=250000,
               color='0.75',
               linestyle="--")
    # Could also do va=bottom, ha=right to put the annotation in the graph
    ax.annotate("480p bitrate",
                xy=(1.0, 250000),
                xycoords=("axes fraction", "data"),
                va="center", ha="left")
    ax.set_title(title)


def show_multiple_run_ids_as_line_graph(run_ids):
    _, axes = plt.subplots(1, len(run_ids),
                           sharex=True,
                           sharey=True,
                           squeeze=False)

    for idx, run_id in enumerate(run_ids):
        summary_data = get_dataframe_from_test_run(run_id)
        show_run_df_as_line_graph(
            summary_data,
            get_graph_title_for_run(run_id),
            ax=axes[0][idx]
        )


def show_run_df_as_boxplot(df, title, annotation_xpoint, annotation_str):
    ax = df.boxplot(column="bytes_per_sec",
                    by="time_offset",
                    figsize=(10, 5),
                    whis=[5, 95],
                    showfliers=False)
    ax.set_xlabel("Elapsed time (sec)")
    major_loc = ticker.AutoLocator()
    major_fmt = ticker.FormatStrFormatter('%d')
    ax.xaxis.set_major_locator(major_loc)
    ax.xaxis.set_major_formatter(major_fmt)
    ax.grid()<commit_msg>Show reference line for common bitrates<commit_after>#!/usr/bin/env python3

import configparser
from functools import lru_cache
import glob
import io
import os

import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import pandas as pd
# import numpy as np


TEST_RESULT_ROOT = "/Users/esteele/tmp/wifi-test/root/wifi-test-results"
METADATA_FILENAME = "metadata.ini"
TEST_RESULT_GLOB = "*.csv"
TEST_RESULT_DIR_PATTERN = os.path.join(TEST_RESULT_ROOT, "test-run-{0}")
TEST_RESULT_METADATA_FILE = os.path.join(TEST_RESULT_DIR_PATTERN,
                                         METADATA_FILENAME)
TEST_RESULT_FILES_PATTERN = os.path.join(TEST_RESULT_DIR_PATTERN,
                                         TEST_RESULT_GLOB)
DEFAULT_REFERENCE_LINE_Y_VALUE = 250000
BW_DESC = {
    125000: "360p bitrate",
    250000: "480p bitrate",
    500000: "720p bitrate",
}


def get_test_run_results(test_run_id):
    test_result_files_glob = TEST_RESULT_FILES_PATTERN.format(test_run_id)
    result_sio = io.StringIO()
    for csv in glob.iglob(test_result_files_glob):
        with open(csv) as f:
            result_sio.write(f.read())

    # Prime for reading
    result_sio.seek(0)
    return result_sio


def get_dataframe_from_test_run(test_run_id, downscale_factor=1):
    raw_run_data = get_test_run_results(test_run_id)
    summary_data = pd.read_csv(raw_run_data,
                               comment="#",
                               names=["client_id",
                                      "timestamp",
                                      "bytes_per_sec"])
    # Create a time_offset column
    summary_data["time_offset"] = summary_data['timestamp'] - \
        min(summary_data['timestamp'])
    summary_data["time_offset"] = \
        (summary_data["time_offset"] // downscale_factor) * downscale_factor
    summary_data["time_offset"] = \
        pd.to_timedelta(summary_data['time_offset'], unit="s")
    summary_data.set_index("time_offset", inplace=True)
    return summary_data


def get_graph_title_for_run(test_run_id):
    config = configparser.ConfigParser()
    config.read(TEST_RESULT_METADATA_FILE.format(test_run_id))
    # Don't count the global section
    client_count = len(config.sections()) - 1
    stream_count = sum([int(config[s]["parallel_run_count"])
                        for s in config.sections()
                        if s != "global"])
    bandwidth_desc = "{0} bytes/sec".format(
        config["global"]["test_bandwidth_bps"]
    )
    title = "Run {0} against {1} {2}.\n" \
            "{3} streams between {4} clients. " \
            "Each stream attempting {5}" \
            .format(
                test_run_id,
                config["global"]["test_server_hostname"],
                config["global"]["extra_run_description"],
                stream_count,
                client_count,
                bandwidth_desc
            )
    return title


def get_graph_title_for_group(test_group_id):
    run_ids = get_test_run_ids_for_group_id(test_group_id)
    config = configparser.ConfigParser()
    config.read(TEST_RESULT_METADATA_FILE.format(run_ids[0]))
    # Don't count the global section
    client_count = len(config.sections()) - 1
    stream_count = sum([int(config[s]["parallel_run_count"])
                        for s in config.sections()
                        if s != "global"])
    bandwidth_desc = "{0} bytes/sec".format(
        config["global"]["test_bandwidth_bps"]
    )
    title = "Group run {0} against {1} {2} with {3} repeat runs.\n" \
            "{4} streams between {5} clients. " \
            "Each stream attempting {6}" \
            .format(
                test_group_id,
                config["global"]["test_server_hostname"],
                config["global"]["extra_run_description"],
                len(run_ids),
                stream_count,
                client_count,
                bandwidth_desc,
            )
    return title


def get_bw_annotation_detail(run_id):
    config = configparser.ConfigParser()
    config.read(TEST_RESULT_METADATA_FILE.format(run_id))
    bandwidth_bps = int(config["global"]["test_bandwidth_bps"])

    return bandwidth_bps, BW_DESC.get(bandwidth_bps, "")


def show_run_df_as_line_graph(run_id, ax):
    config = configparser.ConfigParser()
    config.read(TEST_RESULT_METADATA_FILE.format(run_id))
    test_bandwidth_bps = int(config["global"]["test_bandwidth_bps"])
    if test_bandwidth_bps in BW_DESC:
        reference_line_y = test_bandwidth_bps
    else:
        reference_line_y = DEFAULT_REFERENCE_LINE_Y_VALUE
    df = get_dataframe_from_test_run(run_id)
    pivot_df = df.pivot(columns="client_id",
                        values="bytes_per_sec")
    ax = pivot_df.plot(figsize=(20, 10), ax=ax)
    ax.set_xlabel("Elapsed time (sec)")
    ax.set_xbound(lower=0)
    ax.set_ylabel("Throughput (bytes/sec)")
    ax.axhline(y=reference_line_y,
               color='0.75',
               linestyle="--")
    # Could also do va=bottom, ha=right to put the annotation in the graph
    ax.annotate(BW_DESC[reference_line_y],
                xy=(1.0, reference_line_y),
                xycoords=("axes fraction", "data"),
                va="center", ha="left")
    ax.set_title(get_graph_title_for_run(run_id))


def show_multiple_run_ids_as_line_graph(run_ids):
    _, axes = plt.subplots(1, len(run_ids),
                           sharex=True,
                           sharey=True,
                           squeeze=False)

    for idx, run_id in enumerate(run_ids):
        show_run_df_as_line_graph(
            run_id,
            ax=axes[0][idx]
        )


def show_run_df_as_boxplot(df, title, annotation_xpoint, annotation_str):
    ax = df.boxplot(column="bytes_per_sec",
                    by="time_offset",
                    figsize=(10, 5),
                    whis=[5, 95],
                    showfliers=False)
    ax.set_xlabel("Elapsed time (sec)")
    major_loc = ticker.AutoLocator()
    major_fmt = ticker.FormatStrFormatter('%d')
    ax.xaxis.set_major_locator(major_loc)
    ax.xaxis.set_major_formatter(major_fmt)
    ax.grid()
{'fraction_non_alphanumeric': 0.10850613626745663, 'fraction_numerical': 0.012357173085061363, 'mean_word_length': 2.383734249713631, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 12, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '96741', 'n_tokens_mistral': 3917, 'n_tokens_neox': 3720, 'n_words': 626}