From c27ae96751ad246a73912a526bd7c220cbf768c5 Mon Sep 17 00:00:00 2001 From: AndrewVu23 Date: Wed, 15 Jul 2026 18:45:34 +0700 Subject: [PATCH 1/2] Add offline HTML dashboards for sim stats and HW correlation plots Replace the many-per-stat HTML workflow with a searchable single-page dashboard by default; keep --individual/--all-html for the old correlator per-file output. --- README.md | 6 +- util/plotting/README.md | 34 +- util/plotting/correl_dashboard.py | 455 ++++++++++++++++++++ util/plotting/plot-correlation.py | 81 ++-- util/plotting/plot-get-stats-dashboard.py | 494 ++++++++++++++++++++++ 5 files changed, 1032 insertions(+), 38 deletions(-) create mode 100644 util/plotting/correl_dashboard.py create mode 100644 util/plotting/plot-get-stats-dashboard.py diff --git a/README.md b/README.md index 4edfa0da89..de1e0b0151 100644 --- a/README.md +++ b/README.md @@ -230,11 +230,11 @@ To run the correlator - do the following: ``` The script may take a few minutes to run (primarily because it is parsing a large amount of hardware data for >150 apps). -Stdout will print the summary of counters error, correlation, etc. and a set of correlation plots will be generated -in: +Stdout will print the summary of counters error, correlation, etc. By default it writes one offline dashboard: ``` -./util/plotting/correl-html/ +./util/plotting/correl-html/dashboard.html ``` +Use `--individual` for the old per-stat HTML files, or `--all-html` for both. Here you will find interactive HTML plots, csvs and textual summaries of how well the simulator correlated against hardware on both a per-kernel and per-app basis. Note that the simple tests we ran in this tutorial are short running and not generally representative of scaled GPU apps and are just meant to quickly validate you can get Accel-Sim working. diff --git a/util/plotting/README.md b/util/plotting/README.md index 8a8c133c20..5423da0b5f 100644 --- a/util/plotting/README.md +++ b/util/plotting/README.md @@ -23,6 +23,31 @@ All stats collected by the `get_stats.py` file will be plotted and placed in ./htmls/. [An example for the IPC is here](https://engineering.purdue.edu/tgrogers/accel-sim/example-plots/example.plot.rodinia_2.0-ft.html). +# Single-page dashboard (all stats in one HTML) + +`./plot-get-stats.py` writes one HTML file per stat (~25 files). If you'd rather +review everything in one place, use `./plot-get-stats-dashboard.py`, which reads +the exact same CSV and produces a single, self-contained `dashboard.html` with a +searchable sidebar. Only one stat's chart is shown at a time; click a stat in the +sidebar to switch. Stat names are cleaned up from the collection regexes (e.g. +`gpgpu_simulation_time\s*=.*\(([0-9]+) sec\).*` becomes `gpgpu_simulation_time (sec)`), +and Plotly is embedded so the file opens in any browser with no internet. + +```bash +# Collect the stats (same as above): +../job_launching/get_stats.py -R -C QV100-SASS,QV100-PTX -B rodinia_2.0-ft | tee per-app-stats.csv + +# Build the dashboard: +./plot-get-stats-dashboard.py -c per-app-stats.csv +# open ./htmls/dashboard.html +``` + +Options: +* `-c/--csv_file` the get_stats.py CSV to plot (required) +* `-o/--output` output HTML path (default `./htmls/dashboard.html`) +* `-n/--basename` dashboard title +* `-s/--stats_yml` stats yml used to group the sidebar into categories (defaults to `../job_launching/stats/example_stats.yml`) + # Instructions on plotting correlation graphs * Make sure CUDA\_INSTALL\_PATH is set and bin/lib directories are in PATH and LD\_LIBRARY\_PATH @@ -40,11 +65,18 @@ All stats collected by the `get_stats.py` file will be plotted and placed in ./h ../job_launching/get_stats.py -R -K -k -C -B > correl.stats.csv # An example: ../job_launching/get_stats.py -R -K -k -C QV100-SASS,QV100-PTX -B rodinia_2.0-ft > correl.stats.csv ./plot-correlation.py -c correl.stats.csv - # stdout will print summary statistics and html files will be generated in ./correl-html/ + # Default: one offline dashboard at ./correl-html/dashboard.html + # (sidebar + Per-app/Per-kernel toggle; no per-stat HTML spam) + # Per-stat HTML files only (old behavior, no dashboard): + # ./plot-correlation.py -c correl.stats.csv -H ... --individual + # Both dashboard and per-stat HTML: + # ./plot-correlation.py -c correl.stats.csv -H ... --all-html # You can generate pdf files instead using ./plot-correlation.py -c correl.stats.csv -H ../../hw_run/QUADRO-V100/9.1/ # You can also generate pdf files for the correaltions using "-i pdf" ``` +For day-to-day review, prefer the default `./correl-html/dashboard.html`. Use `--individual` +only when you need the separate per-app / per-kernel HTML files for archival or sharing. [Here is an example correlation plot for the simple rodinia tests aggregated per-app](https://engineering.purdue.edu/tgrogers/accel-sim/example-plots/gv100-cycles.QV100-PTX.QV100-SASS.per-app.html). [And per-kernel](https://engineering.purdue.edu/tgrogers/accel-sim/example-plots/gv100-cycles.QV100-PTX.QV100-SASS.per-kernel.html). Note again - that these short-running tests are not representative of longer running GPU apps and the correlation on these applications should diff --git a/util/plotting/correl_dashboard.py b/util/plotting/correl_dashboard.py new file mode 100644 index 0000000000..8079261b59 --- /dev/null +++ b/util/plotting/correl_dashboard.py @@ -0,0 +1,455 @@ +#!/usr/bin/env python3 +"""Self-contained correlation dashboard (sidebar + per-app/per-kernel toggle). + +Consumed by plot-correlation.py after it builds the figs = {kernel, app} dicts. +""" + +from __future__ import print_function + +import html +import json +import os +import re + +import plotly + + +def figure_to_payload(fig): + """Convert a Plotly Figure to a JSON-serializable payload for the dashboard. + + Pulls the long gray summary annotations out of the Plotly layout into a + plain `summary` string so the dashboard can wrap them in HTML (Plotly + annotations clip at the plot edge and truncate). + """ + raw = fig.to_plotly_json() + layout = raw.get("layout", {}) or {} + summaries = [] + for ann in layout.get("annotations") or []: + text = ann.get("text") if isinstance(ann, dict) else None + if text: + summaries.append(re.sub(r"<[^>]+>", "", str(text)).strip()) + # Drop in-plot annotations; shown in the HTML banner instead. + layout["annotations"] = [] + # Less top margin needed without the overlay banner. + margin = layout.get("margin") + if not isinstance(margin, dict): + margin = {} + else: + margin = dict(margin) + margin["t"] = max(int(margin.get("t") or 0), 40) + layout["margin"] = margin + return { + "data": raw.get("data", []), + "layout": layout, + "summary": "\n".join(summaries), + } + + +def _basename_key(fig_key): + """Strip path and _app/_kernel suffix from a figs dict key. + + e.g. '.../correl-html/gpc_cycles.RTX3070-SASS_app' -> 'gpc_cycles.RTX3070-SASS' + """ + base = os.path.basename(fig_key) + if base.endswith("_app"): + return base[: -len("_app")] + if base.endswith("_kernel"): + return base[: -len("_kernel")] + return base + + +def _pretty_label(stat_key, fig): + """Human label: prefer axis/layout titles, else clean up plotfile.CFG.""" + + def _title_text(obj): + if obj is None: + return None + if isinstance(obj, str): + return obj + return getattr(obj, "text", None) + + layout = getattr(fig, "layout", None) + candidates = [] + if layout is not None: + candidates.append(_title_text(getattr(layout, "title", None))) + for axis in ("xaxis", "yaxis"): + ax = getattr(layout, axis, None) + if ax is not None: + candidates.append(_title_text(getattr(ax, "title", None))) + + for raw in candidates: + if not raw: + continue + text = re.sub(r"", "", str(raw)).strip() + text = re.sub(r"\s*\[Correl=.*$", "", text).strip() + # "Hardware GPC Cycles" / "Simulation GPC Cycles" / "Per App GPC Cycles" + text = re.sub( + r"^(Hardware|Simulation|Per App|Per-App|Per Kernel|Per-Kernel)\s+", + "", + text, + flags=re.IGNORECASE, + ).strip() + if text: + return text + + # Fallback: gpc_cycles.RTX3070-SASS -> Gpc Cycles (RTX3070-SASS) + parts = stat_key.rsplit(".", 1) + stem = parts[0].replace("-", " ").replace("_", " ") + stem = " ".join(w.capitalize() for w in stem.split()) + if len(parts) == 2: + return "{0} ({1})".format(stem, parts[1]) + return stem + + +def _stable_id(stat_key): + sid = re.sub(r"[^0-9a-zA-Z]+", "_", stat_key).strip("_") + return sid or "stat" + + +def write_correl_dashboard(outdir, figs, title="Correlation"): + """Write correl-html/dashboard.html from figs={'kernel':..., 'app':...}. + + Returns the absolute path of the written file. + """ + if not os.path.isdir(outdir): + os.makedirs(outdir) + + # Pair app/kernel figures by shared plotname stem. + app_figs = figs.get("app", {}) or {} + kernel_figs = figs.get("kernel", {}) or {} + + by_stat = {} # stat_key -> {app, kernel, label} + for key, fig in app_figs.items(): + sk = _basename_key(key) + by_stat.setdefault(sk, {})["app"] = fig + for key, fig in kernel_figs.items(): + sk = _basename_key(key) + by_stat.setdefault(sk, {})["kernel"] = fig + + order = [] + labels = {} + payload_app = {} + payload_kernel = {} + + for sk in sorted(by_stat.keys()): + entry = by_stat[sk] + sid = _stable_id(sk) + # Prefer app fig for the label (same chart_name). + label_fig = entry.get("app") or entry.get("kernel") + label = _pretty_label(sk, label_fig) + order.append(sid) + labels[sid] = label + if "app" in entry: + p = figure_to_payload(entry["app"]) + p["label"] = label + payload_app[sid] = p + if "kernel" in entry: + p = figure_to_payload(entry["kernel"]) + p["label"] = label + payload_kernel[sid] = p + + if not order: + print("No correlation figures to put in the dashboard.") + return None + + out_path = os.path.join(outdir, "dashboard.html") + html_str = _render_html( + title=title, + order=order, + labels=labels, + figures_app=payload_app, + figures_kernel=payload_kernel, + ) + with open(out_path, "w") as f: + f.write(html_str) + return os.path.abspath(out_path) + + +def _render_html(title, order, labels, figures_app, figures_kernel): + plotly_js = plotly.offline.get_plotlyjs() + + sidebar_items = [] + for sid in order: + label = labels[sid] + sidebar_items.append( + ''.format( + html.escape(sid, quote=True), + html.escape(label.lower(), quote=True), + html.escape(label), + ) + ) + sidebar_html = "\n".join(sidebar_items) + + return TEMPLATE.format( + title=html.escape(title), + plotly_js=plotly_js, + sidebar_html=sidebar_html, + order_json=json.dumps(order), + labels_json=json.dumps(labels), + figures_app_json=json.dumps(figures_app), + figures_kernel_json=json.dumps(figures_kernel), + ) + + +TEMPLATE = """ + + + + +{title} - Correlation Dashboard + + + + + +
+
+
+
+
+
+ + + + +""" diff --git a/util/plotting/plot-correlation.py b/util/plotting/plot-correlation.py index 00d22d3831..4893300df0 100755 --- a/util/plotting/plot-correlation.py +++ b/util/plotting/plot-correlation.py @@ -32,6 +32,8 @@ import time import math +from correl_dashboard import write_correl_dashboard + def getAppData(kernels, x, y, xaxis_title, correlmap): count = 0 @@ -439,22 +441,24 @@ def make_submission_quality_image(image_type, traces, hw_cfg, figs=None): # Create the figures kernel_fig = Figure(data=kernel_data, layout=png_layout) app_fig = Figure(data=app_data, layout=app_layout) - - # Always generate individual HTML files - plotly.offline.plot( - kernel_fig, - filename=plotname + ".per-kernel.html", - auto_open=False, - ) - plotly.offline.plot( - app_fig, - filename=plotname + ".per-app.html", - auto_open=False, - ) - + # Per-stat HTML files only when --individual / --all-html is set. + # Default path writes the single offline dashboard instead. + if getattr(options, "write_individual_html", False): + plotly.offline.plot( + kernel_fig, + filename=plotname + ".per-kernel.html", + auto_open=False, + ) + + plotly.offline.plot( + app_fig, + filename=plotname + ".per-app.html", + auto_open=False, + ) + if figs is not None: - # Store figures for combining later + # Store figures for the dashboard figs["kernel"][f"{plotname}_kernel"] = kernel_fig figs["app"][f"{plotname}_app"] = app_fig @@ -800,7 +804,24 @@ def summarize_hw_data(hw_data, logger): help="A serialized version of the hw_data dictionary. If used - it will skip -H arguments.", default=None, ) -parser.add_option("-c", "--csv_file", dest="csv_file", help="File to parse", default="") +parser.add_option( + "-c", "--csv_file", dest="csv_file", help="File to parse", default="" +) +parser.add_option( + "-I", + "--individual", + dest="individual", + action="store_true", + default=False, + help="Write per-stat per-app/per-kernel HTML files only (no dashboard).", +) +parser.add_option( + "--all-html", + dest="all_html", + action="store_true", + default=False, + help="Write both the single dashboard and individual per-stat HTML files.", +) parser.add_option( "-d", "--data_mappings", @@ -912,6 +933,9 @@ def summarize_hw_data(hw_data, logger): (options, args) = parser.parse_args() +# Default: dashboard only. --individual = per-stat HTML only. --all-html = both. +options.write_individual_html = bool(options.individual or options.all_html) +options.write_dashboard = bool(options.all_html or not options.individual) common.load_defined_yamls() benchmarks = [] @@ -1262,22 +1286,11 @@ def summarize_hw_data(hw_data, logger): for (plotfile, hw_cfg), traces in fig_data.items(): make_submission_quality_image(options.image_type, traces, hw_cfg, figs=figs) -# Write combined HTML files -# Combined per-kernel plots -kernel_out_path = os.path.join(correl_outdir, "combined_per_kernel.html") -with open(kernel_out_path, 'w') as f: - f.write('') -with open(kernel_out_path, 'a') as f: - for fig in figs["kernel"].values(): - f.write(fig.to_html(full_html=False, include_plotlyjs='cdn')) - -# Combined per-app plots -app_out_path = os.path.join(correl_outdir, "combined_per_app.html") -with open(app_out_path, 'w') as f: - f.write('') -with open(app_out_path, 'a') as f: - for fig in figs["app"].values(): - f.write(fig.to_html(full_html=False, include_plotlyjs='cdn')) - -print("Combined per-kernel output available at: file://{0}".format(kernel_out_path)) -print("Combined per-app output available at: file://{0}".format(app_out_path)) +if options.write_dashboard: + dash_title = options.plotname if options.plotname else "Correlation" + dash_path = write_correl_dashboard(correl_outdir, figs, title=dash_title) + if dash_path: + print("Dashboard written to: {0}".format(dash_path)) + print("Open it in any browser (no internet required).") +elif options.write_individual_html: + print("Individual per-stat HTML files written under: {0}".format(correl_outdir)) diff --git a/util/plotting/plot-get-stats-dashboard.py b/util/plotting/plot-get-stats-dashboard.py new file mode 100644 index 0000000000..1595721594 --- /dev/null +++ b/util/plotting/plot-get-stats-dashboard.py @@ -0,0 +1,494 @@ +#!/usr/bin/env python3 + +"""Build a single, self-contained HTML dashboard from a get_stats.py CSV. + +Unlike plot-get-stats.py (which writes one HTML file per stat), this script +emits a single dashboard.html with a searchable sidebar. Only one stat's chart +is shown at a time; clicking a different stat in the sidebar switches the view. +The Plotly library is embedded so the resulting file works fully offline. +""" + +from optparse import OptionParser +import plotly +import os +import re +import json +import html + +this_directory = os.path.dirname(os.path.realpath(__file__)) + "/" + +import sys + +sys.path.insert(0, os.path.join(this_directory, "..", "job_launching")) +import common + +import numpy as np +import csv + + +def get_csv_data(filepath): + all_stats = {} + apps = [] + data = {} + any_data = False + with open(filepath, "r") as data_file: + reader = csv.reader(data_file) # define reader object + state = "start" + for row in reader: # loop through rows in csv file + if len(row) != 0 and row[0].startswith("----"): + state = "find-stat" + continue + if state == "find-stat": + current_stat = row[0] + state = "find-apps" + continue + if state == "find-apps": + apps = [item.upper() for item in row[1:]] + state = "process-cfgs" + continue + if state == "process-cfgs": + if len(row) == 0: + if any_data: + all_stats[current_stat] = apps, data + apps = [] + data = {} + state = "start" + any_data = False + continue + temp = [] + for x in row[1:]: + try: + temp.append(float(x)) + any_data = True + except ValueError: + temp.append(0) + data[row[0]] = np.array(temp) + + return all_stats + + +def _detect_unit(s): + """Infer a human-readable unit from an (unescaped) collection regex.""" + if re.search(r"inst/sec", s): + return "inst/sec" + if re.search(r"cycle/sec", s): + return "cycle/sec" + if re.search(r"GB/Sec", s, re.IGNORECASE): + return "GB/Sec" + if re.search(r"\bsec\b", s): + return "sec" + if "%" in s: + return "%" + if re.search(r"\(\.\*\)x\b", s): + return "x" + return "" + + +def pretty_label(raw): + """Turn a collection regex into a readable stat name. + + e.g. 'gpgpu_simulation_time\\s*=.*\\(([0-9]+) sec\\).*' -> 'gpgpu_simulation_time (sec)' + '\\s+L2_cache_stats_breakdown\\[GLOBAL_ACC_R\\]\\[HIT\\]\\s*=\\s*(.*)' + -> 'L2_cache_stats_breakdown[GLOBAL_ACC_R][HIT]' + 'gpgpu_simulation_rate\\s+=\\s+(.*)\\s+\\(inst\\/sec\\)' + -> 'gpgpu_simulation_rate (inst/sec)' + """ + # Unescape common regex escapes so unit detection and names read cleanly. + s = raw + for esc, plain in ( + ("\\/", "/"), + ("\\[", "["), + ("\\]", "]"), + ("\\(", "("), + ("\\)", ")"), + ): + s = s.replace(esc, plain) + + unit = _detect_unit(s) + + # Keep only the part before the '=' assignment in the regex. + name = re.split(r"=", s, maxsplit=1)[0] + + # Strip regex whitespace tokens and any leftover escapes/quantifiers. + name = name.replace("\\s+", "").replace("\\s*", "").replace("\\s", "") + name = name.replace("\\", "") + name = name.strip(" +*") + + if unit and unit not in name: + name = "{0} ({1})".format(name, unit) + return name + + +def load_categories(stats_yml): + """Map each raw stat regex to a sidebar category using the stats yml. + + Returns a dict {raw_stat: category_label}. Best-effort: if the yml is + missing or unreadable, an empty dict is returned and everything falls + back to the 'Other' group. + """ + labels = { + "collect_aggregate": "Aggregate", + "collect_abs": "Per-kernel (absolute)", + "collect_rates": "Rates", + } + mapping = {} + if not stats_yml or not os.path.exists(stats_yml): + return mapping + try: + import yaml + + parsed = yaml.load(open(stats_yml), Loader=yaml.FullLoader) + except Exception: + return mapping + for key, label in labels.items(): + for raw in parsed.get(key, []) or []: + mapping[raw] = label + return mapping + + +colors = [ + "#0F8C79", + "#BD2D28", + "#E3BA22", + "#E6842A", + "#137B80", + "#8E6C8A", + "#9A3E25", + "#3B7DD8", +] + + +def build_figure(stat, apps, data): + """Build a Plotly grouped-bar figure dict for one stat.""" + label = pretty_label(stat) + traces = [] + cfg_count = 0 + for cfg, values in data.items(): + traces.append( + { + "type": "bar", + "x": apps, + "y": [float(v) for v in values], + "name": cfg, + "marker": {"color": colors[cfg_count % len(colors)]}, + } + ) + cfg_count += 1 + + layout = { + "barmode": "group", + "bargap": 0.25, + "bargroupgap": 0.05, + "showlegend": True, + "legend": {"orientation": "h", "y": -0.35, "x": 0}, + "margin": {"l": 80, "r": 40, "t": 20, "b": 160}, + "yaxis": { + "title": {"text": label}, + "gridcolor": "#e9edf2", + "zerolinecolor": "#d0d7de", + }, + "xaxis": { + "automargin": True, + "tickangle": -35, + }, + "paper_bgcolor": "rgba(0,0,0,0)", + "plot_bgcolor": "rgba(0,0,0,0)", + "font": { + "family": "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif", + "color": "#24292f", + }, + } + return {"data": traces, "layout": layout, "label": label} + + +def render_html(basename, figures, order, categories): + """Assemble the self-contained dashboard HTML string. + + figures: {stat_id: {data, layout, label}} + order: list of stat_ids in display order + categories: {stat_id: category_label} + """ + plotly_js = plotly.offline.get_plotlyjs() + + # Group stat ids by category, preserving encounter order within each group. + grouped = {} + for sid in order: + cat = categories.get(sid, "Other") + grouped.setdefault(cat, []).append(sid) + + category_order = ["Aggregate", "Per-kernel (absolute)", "Rates", "Other"] + ordered_cats = [c for c in category_order if c in grouped] + ordered_cats += [c for c in grouped if c not in ordered_cats] + + sidebar_items = [] + for cat in ordered_cats: + sidebar_items.append( + '
{0}
'.format(html.escape(cat)) + ) + for sid in grouped[cat]: + label = figures[sid]["label"] + sidebar_items.append( + ''.format( + html.escape(sid, quote=True), + html.escape(label.lower(), quote=True), + html.escape(label), + ) + ) + sidebar_html = "\n".join(sidebar_items) + + figures_payload = { + sid: {"data": figures[sid]["data"], "layout": figures[sid]["layout"], "label": figures[sid]["label"]} + for sid in order + } + figures_json = json.dumps(figures_payload) + order_json = json.dumps(order) + title = html.escape(basename) + + return TEMPLATE.format( + title=title, + plotly_js=plotly_js, + sidebar_html=sidebar_html, + figures_json=figures_json, + order_json=order_json, + ) + + +TEMPLATE = """ + + + + +{title} - Stats Dashboard + + + + + +
+
+
+
+ + + + +""" + + +def main(): + parser = OptionParser() + parser.add_option( + "-c", "--csv_file", dest="csv_file", help="File to parse", default="" + ) + parser.add_option( + "-o", + "--output", + dest="output", + help="Output HTML file path.", + default=os.path.join(this_directory, "htmls", "dashboard.html"), + ) + parser.add_option( + "-n", + "--basename", + dest="basename", + help="Dashboard title.", + default="gpgpu-sim", + ) + parser.add_option( + "-s", + "--stats_yml", + dest="stats_yml", + help="Stats yml used to group stats into sidebar categories.", + default=os.path.join( + this_directory, "..", "job_launching", "stats", "example_stats.yml" + ), + ) + (options, args) = parser.parse_args() + options.csv_file = common.file_option_test(options.csv_file, "", this_directory) + if options.csv_file == "": + parser.error("Please supply a csv file with -c/--csv_file") + + all_stats = get_csv_data(options.csv_file) + if not all_stats: + print("No stats found in {0}".format(options.csv_file)) + return + + categories_raw = load_categories(options.stats_yml) + + figures = {} + order = [] + categories = {} + seen = set() + for stat, (apps, data) in all_stats.items(): + sid = re.sub("[^0-9a-zA-Z]+", "_", stat).strip("_") + base_sid = sid or "stat" + n = 1 + while sid in seen: + n += 1 + sid = "{0}_{1}".format(base_sid, n) + seen.add(sid) + + figures[sid] = build_figure(stat, apps, data) + order.append(sid) + categories[sid] = categories_raw.get(stat, "Other") + print("added: " + figures[sid]["label"]) + + out_html = render_html(options.basename, figures, order, categories) + + outdir = os.path.dirname(os.path.abspath(options.output)) + if not os.path.exists(outdir): + os.makedirs(outdir) + with open(options.output, "w") as f: + f.write(out_html) + + print("\nDashboard written to: {0}".format(os.path.abspath(options.output))) + print("Open it in any browser (no internet required).") + + +if __name__ == "__main__": + main() From 3838001a29d2b7eac73a2ecc13c19d091a4af165 Mon Sep 17 00:00:00 2001 From: AndrewVu23 Date: Wed, 15 Jul 2026 19:26:30 +0700 Subject: [PATCH 2/2] Polish dashboards + wsl run fix + add run script in readme --- README.md | 11 +++++++++++ util/plotting/correl_dashboard.py | 0 util/plotting/plot-get-stats-dashboard.py | 0 util/plotting/plot-get-stats.py | 14 +++++++++++--- 4 files changed, 22 insertions(+), 3 deletions(-) mode change 100644 => 100755 util/plotting/correl_dashboard.py mode change 100644 => 100755 util/plotting/plot-get-stats-dashboard.py diff --git a/README.md b/README.md index de1e0b0151..8a165ea2bb 100644 --- a/README.md +++ b/README.md @@ -182,6 +182,17 @@ After the jobs finish - you can collect all the stats using: ./util/job_launching/get_stats.py -N myTest | tee stats.csv ``` +To plot them, either write one HTML file per stat: +```bash +./util/plotting/plot-get-stats.py -c stats.csv +# outputs under ./util/plotting/htmls/ +``` +or build a single offline dashboard (searchable sidebar, one chart at a time): +```bash +./util/plotting/plot-get-stats-dashboard.py -c stats.csv +# open ./util/plotting/htmls/dashboard.html +``` + If you want to run the accel-sim.out executable command itself for specific workload, you can use: ```bash /gpu-simulator/bin/release/accel-sim.out -trace ./hw_run/rodinia_2.0-ft/9.1/backprop-rodinia-2.0-ft/4096___data_result_4096_txt/traces/kernelslist.g -config ./gpu-simulator/gpgpu-sim/configs/tested-cfgs/SM7_QV100/gpgpusim.config -config ./gpu-simulator/configs/tested-cfgs/SM7_QV100/trace.config diff --git a/util/plotting/correl_dashboard.py b/util/plotting/correl_dashboard.py old mode 100644 new mode 100755 diff --git a/util/plotting/plot-get-stats-dashboard.py b/util/plotting/plot-get-stats-dashboard.py old mode 100644 new mode 100755 diff --git a/util/plotting/plot-get-stats.py b/util/plotting/plot-get-stats.py index 1988d7e427..6b20252089 100755 --- a/util/plotting/plot-get-stats.py +++ b/util/plotting/plot-get-stats.py @@ -100,6 +100,17 @@ def get_csv_data(filepath): all_stats = get_csv_data(options.csv_file) +outdir = os.path.join(this_directory, "htmls") +if not os.path.exists(outdir): + os.makedirs(outdir) + +if not all_stats: + print( + "No plottable stats found in {0} " + "(empty file, or all values were non-numeric like NA).".format(options.csv_file) + ) + sys.exit(1) + colors = [ "#0F8C79", "#BD2D28", @@ -141,9 +152,6 @@ def get_csv_data(filepath): fig = Figure(data=data, layout=layout) figure_name = re.sub("[^0-9a-zA-Z]+", "_", stat) + "_" + options.plotname print("plotting: " + figure_name) - outdir = os.path.join(this_directory, "htmls") - if not os.path.exists(outdir): - os.makedirs(outdir) plotly.offline.plot( fig, filename=os.path.join(outdir, figure_name + ".html"), auto_open=False )