aboutsummaryrefslogtreecommitdiff
path: root/src/plots.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/plots.py')
-rw-r--r--src/plots.py771
1 files changed, 0 insertions, 771 deletions
diff --git a/src/plots.py b/src/plots.py
deleted file mode 100644
index fb39578..0000000
--- a/src/plots.py
+++ /dev/null
@@ -1,771 +0,0 @@
-# Copyright 2018-2020 Florian Fischer <florian.fl.fischer@fau.de>
-#
-# This file is part of allocbench.
-#
-# allocbench is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# allocbench is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with allocbench. If not, see <http://www.gnu.org/licenses/>.
-"""Plot different graphs from allocbench results"""
-
-import copy
-import itertools
-import matplotlib
-import matplotlib.pyplot as plt
-import numpy as np
-import os
-import traceback
-
-import src.globalvars
-from src.util import print_debug, print_warn
-
-# This is useful when evaluating strings in the plot functions. str(np.NaN) == "nan"
-nan = np.NaN
-
-DEFAULT_PLOT_OPTIONS = {
- 'plot': {
- 'marker': '.',
- 'linestyle': '-',
- },
- 'errorbar': {
- 'marker': '.',
- 'linestyle': '-',
- 'yerr': True,
- },
- 'bar': {
- 'yerr': True,
- }
-}
-
-DEFAULT_FIG_OPTIONS = {
- 'plot': {
- 'legend': True,
- 'legend_pos': 'best',
- 'autoticks': True,
- },
- 'errorbar': {
- 'legend': True,
- 'legend_pos': 'best',
- 'autoticks': True,
- },
- 'bar': {
- 'legend': True,
- 'legend_pos': 'lower center',
- 'autoticks': False,
- }
-}
-
-FIGURES = {}
-
-def _get_alloc_color(bench, alloc):
- """Populate all not set allocator colors with matplotlib 'C' colors"""
- if isinstance(alloc, str):
- alloc = bench.results["allocators"][alloc]
- if alloc["color"] is None:
- allocs = bench.results["allocators"]
- explicit_colors = [
- v["color"] for v in allocs.values() if v["color"] is not None
- ]
- matplotlib_c_colors = ["C" + str(i) for i in range(0, 10)]
- avail_colors = [
- c for c in matplotlib_c_colors if c not in explicit_colors
- ]
-
- for alloc in allocs.values():
- if alloc["color"] is None:
- alloc["color"] = avail_colors.pop()
-
- return alloc["color"]
-
-
-def _eval_with_stat(bench, evaluation, alloc, perm, stat):
- """Helper to evaluate a datapoint description string"""
- try:
- res = evaluation.format(**bench.results["stats"][alloc][perm][stat])
- except KeyError:
- print_debug(traceback.format_exc())
- print_warn(
- f"KeyError while expanding {evaluation} for {alloc} and {perm}")
- return nan
- return eval(res)
-
-
-def _get_y_data(bench, expression, allocator, perms, stat="mean", scale=None):
- """Helper to get the y data of an allocator for given permutations"""
- y_data = []
- for perm in perms:
- if scale:
- if scale == allocator:
- y_data.append(1)
- else:
- val = _eval_with_stat(bench, expression, allocator, perm, stat)
- norm_val = _eval_with_stat(bench, expression, scale, perm,
- stat)
- y_data.append(val / norm_val)
- else:
- y_data.append(
- _eval_with_stat(bench, expression, allocator, perm, stat))
-
- return y_data
-
-def _create_plot_options(plot_type, **kwargs):
- """
- Create a plot options dictionary.
-
- Parameters
- ----------
- plot_type : str
- The plot type for which the options should be created.
- Possible values: {'bar', 'errorbar', 'plot'}
-
- **kwargs : plot properties, optional
- *kwargs* are used to specify properties like a line label (for
- auto legends), linewidth, antialiasing, marker face color.
-
- Returns
- -------
- options : dict
- Dict holding the specified options and all default values for plot type
- """
-
- options = copy.deepcopy(DEFAULT_PLOT_OPTIONS[plot_type])
- for key, value in kwargs.items():
- options[key] = value
-
- return options
-
-def _create_figure_options(plot_type, fig_label, **kwargs):
- """
- Create a figure options dictionary
-
- Parameters
- ----------
- plot_type : str
- The plot type for which the options should be created.
- Possible values: {'bar', 'errorbar', 'plot'}
-
- **kwargs : figure properties, optional
- *kwargs* are used to specify properties like legends, legend position,
- x-/ and ylabel, and title.
-
- Returns
- -------
- options : dict
- Dict holding the specified options and all default values for plot type
- """
-
- options = copy.deepcopy(DEFAULT_FIG_OPTIONS[plot_type])
-
- options['fig_label'] = fig_label
-
- for key, value in kwargs.items():
- options[key] = value
-
- return options
-
-def _plot(bench,
- allocators,
- y_expression,
- x_data,
- perms,
- plot_type,
- plot_options,
- fig_options,
- scale=None,
- file_postfix="",
- sumdir="",
- file_ext=src.globalvars.summary_file_ext):
- """
- Create a plot for a given expression
-
- Parameters
- ----------
-
- Returns
- -------
- figure : :rc:`~matplotlib.figure.Figure`
- The new :rc:`.Figure` instance wrapping our plot.
-
- Notes
- -----
- If you are creating many figures, make sure you explicitly call
- :rc:`.pyplot.close` on the figures you are not using, because this will
- enable pyplot to properly clean up the memory.
- """
- fig = plt.figure(fig_options['fig_label'])
- FIGURES[fig_options['fig_label']] = fig
- if plot_type == 'bar' and 'width' not in plot_options:
- n_allocators = len(allocators)
- width = 1 / (n_allocators + 1)
- plot_options['width'] = width
- for i, allocator in enumerate(allocators):
- y_data = _get_y_data(bench,
- y_expression,
- allocator,
- perms,
- stat='mean',
- scale=scale)
-
- if plot_options.get('yerr', False):
- plot_options['yerr'] = _get_y_data(bench,
- y_expression,
- allocator,
- perms,
- stat='std')
- try:
- plot_func = getattr(plt, plot_type)
- except AttributeError:
- print_debug(f'Unknown plot type: {plot_type}')
- raise
-
- _x_data = x_data
- if not fig_options['autoticks']:
- _x_data = np.arange(1, len(x_data) + 1)
- if plot_type == 'bar':
- _x_data = _x_data + width / 2 + (i * plot_options['width'])
-
- plot_func(_x_data,
- y_data,
- label=allocator,
- color=_get_alloc_color(bench, allocator),
- **plot_options)
-
- if fig_options['legend']:
- plt.legend(loc=fig_options['legend_pos'])
-
- if not fig_options['autoticks']:
- plt.xticks(_x_data - (i / 2 * plot_options['width']), x_data)
-
- plt.xlabel(fig_options['xlabel'])
- plt.ylabel(fig_options['ylabel'])
- plt.title(fig_options['title'])
-
- fig_path = os.path.join(sumdir, f'{fig_options["fig_label"]}.{file_ext}')
- if file_ext == 'tex':
- import tikzplotlib
- tikzplotlib.save(fig_path)
- else:
- fig.savefig(fig_path)
-
- return fig
-
-def plot(bench,
- y_expression,
- plot_type='errorbar',
- x_args=None,
- scale=None,
- plot_options=None,
- fig_options=None,
- file_postfix="",
- sumdir="",
- file_ext=src.globalvars.summary_file_ext):
- """
- Create plots for a given expression for the y axis.
-
- Parameters
- ----------
-
- y_expression : str
-
- plot_type : str, optional, default='errorbar'
- The plot type for which the options should be created.
- Possible values: {'bar', 'errorbar', 'plot'}
-
- x_args : [str], optional, default=None
- The benchmark arguments for which a plot should be created.
- If not provided, defaults to :rc:`bench.arguments.keys()`
-
- scale : str, optional, default=None
- Name of the allocator which should be used to normalize the results.
-
- plot_options : dict, optional, default None
- Dictionary containing plot options which should be passed to the plot
- type function. If not provided the default plot type options are used.
- Possible options:
- * yerr: bool - Plot the standard deviation as errorbars
- * marker: str - Style of the used markers
- * line: str - Style of the drawn lines
-
- fig_options : dict, optional, default None
- Dictionary containing figure options.
- If not provided the default plot type options are used.
- Possible options:
- * ylabel : str - The label of the y axis.
- * xlabel : str - The label of the x axis.
- * title : str - The title of the plot.
- * legend : bool - Should the plot have a legend.
- * legend_pos : str - Location of the legend.
- For possible values see :rc:`help(matplotlib.pyploy.legend)`.
- * autoticks : bool - Let matplotlib set the xticks automatically.
-
- file_postfix: str, optional, default=""
- Postfix which is appended to the plot's file name.
-
- sumdir : path or str, optional, default=""
- Directory where the plot should be saved. If not provided defaults
- to the current working directory.
-
- file_ext : str, optional, default=:rc:`src.globalvars.summary_file_ext`
- File extension of the saved plot. If not provided defaults to the
- value of :rc:`src.globalvars.summary_file_ext`
-
- """
-
- args = bench.results["args"]
- allocators = bench.results["allocators"]
-
- x_args = x_args or args
-
- for loose_arg in x_args:
- x_data = args[loose_arg]
-
- fixed_args = [[(k, v) for v in args[k]] for k in args if k != loose_arg]
- for fixed_part in itertools.product(*fixed_args):
- fixed_part = {k:v for k, v in fixed_part}
-
- fixed_part_str = ".".join([f'{k}={v}' for k, v in fixed_part.items()])
- fig_label = f'{bench.name}.{fixed_part_str}.{file_postfix}'
-
- cur_plot_options = _create_plot_options(plot_type, **plot_options or {})
-
- cur_fig_options = {}
-
- substitutions = vars()
- substitutions.update(vars(bench))
- for option, value in (fig_options or {}).items():
- if isinstance(value, str):
- cur_fig_options[option] = value.format(**substitutions)
-
- cur_fig_options = _create_figure_options(plot_type, fig_label, **cur_fig_options)
-
- # plot specific defaults
- cur_fig_options.setdefault("ylabel", y_expression)
- cur_fig_options.setdefault("xlabel", loose_arg)
- cur_fig_options.setdefault("titel", fig_label)
-
- _plot(bench,
- allocators,
- y_expression,
- x_data,
- list(bench.iterate_args(args=args, fixed=fixed_part)),
- plot_type,
- cur_plot_options,
- cur_fig_options)
-
-def print_common_facts(comment_symbol="", file=None):
- print(comment_symbol, "Common facts:", file=file)
- for fact, value in src.facter.FACTS.items():
- print(f"{comment_symbol} {fact}: {value}", file=file)
- print(file=file)
-
-def print_facts(bench, comment_symbol="", print_common=True, print_allocators=False, file=None):
- """Write collected facts about used system and benchmark to file"""
- print(comment_symbol, bench.name, file=file)
- print(file=file)
-
- if print_common:
- print_common_facts(comment_symbol=comment_symbol, file=file)
-
- print(comment_symbol, "Benchmark facts:", file=file)
- for fact, value in bench.results["facts"].items():
- print(comment_symbol, f"{fact}: {value}", file=file)
-
- if print_allocators:
- print(comment_symbol, f'allocators: {" ".join(bench.results["allocators"])}', file=file)
-
- print(file=file)
-
-
-def export_stats_to_csv(bench, datapoint, path=None):
- """Write descriptive statistics about datapoint to csv file"""
- allocators = bench.results["allocators"]
- args = bench.results["args"]
- stats = bench.results["stats"]
-
- if path is None:
- path = datapoint
-
- path = path + ".csv"
-
- stats_fields = list(stats[list(allocators)[0]][list(
- bench.iterate_args(args=args))[0]])
- fieldnames = ["allocator", *args, *stats_fields]
- widths = []
- for fieldname in fieldnames:
- widths.append(len(fieldname) + 2)
-
- # collect rows
- rows = {}
- for alloc in allocators:
- rows[alloc] = {}
- for perm in bench.iterate_args(args=args):
- row = []
- row.append(alloc)
- row += list(perm._asdict().values())
- row += [
- stats[alloc][perm][stat][datapoint]
- for stat in stats[alloc][perm]
- ]
- row[-1] = (",".join([str(x) for x in row[-1]]))
- rows[alloc][perm] = row
-
- # calc widths
- for i in range(0, len(fieldnames)):
- for alloc in allocators:
- for perm in bench.iterate_args(args=args):
- field_len = len(str(rows[alloc][perm][i])) + 2
- if field_len > widths[i]:
- widths[i] = field_len
-
- with open(path, "w") as csv_file:
- headerline = ""
- for i, name in enumerate(fieldnames):
- headerline += name.capitalize().ljust(widths[i]).replace("_", "-")
- print(headerline, file=csv_file)
-
- for alloc in allocators:
- for perm in bench.iterate_args(args=args):
- line = ""
- for i, row in enumerate(rows[alloc][perm]):
- line += str(row).ljust(widths[i])
- print(line.replace("_", "-"), file=csv_file)
-
-
-def export_stats_to_dataref(bench, datapoint, path=None):
- """Write descriptive statistics about datapoint to dataref file"""
- stats = bench.results["stats"]
-
- if path is None:
- path = datapoint
-
- path = path + ".dataref"
-
- # Example: \drefset{/mysql/glibc/40/Lower-whisker}{71552.0}
- line = "\\drefset{{/{}/{}/{}/{}}}{{{}}}"
-
- with open(path, "w") as dataref_file:
- # Write facts to file
- print_facts(bench, comment_symbol="%", file=dataref_file)
-
- for alloc in bench.results["allocators"]:
- for perm in bench.iterate_args(args=bench.results["args"]):
- for statistic, values in stats[alloc][perm].items():
- cur_line = line.format(
- bench.name, alloc,
- "/".join([str(p) for p in list(perm)]), statistic,
- values.get(datapoint, nan))
- # Replace empty outliers
- cur_line.replace("[]", "")
- # Replace underscores
- cur_line.replace("_", "-")
- print(cur_line, file=dataref_file)
-
-
-def write_best_doublearg_tex_table(bench,
- expr,
- sort=">",
- file_postfix="",
- sumdir=""):
- args = bench.results["args"]
- keys = list(args.keys())
- allocators = bench.results["allocators"]
-
- header_arg = keys[0] if len(args[keys[0]]) < len(
- args[keys[1]]) else keys[1]
- row_arg = [arg for arg in args if arg != header_arg][0]
-
- headers = args[header_arg]
- rows = args[row_arg]
-
- cell_text = []
- for arg_value in rows:
- row = []
- for perm in bench.iterate_args(args=args, fixed={row_arg: arg_value}):
- best = []
- best_val = None
- for allocator in allocators:
- mean = _eval_with_stat(bench, expr, allocator, perm, "mean")
-
- if not best_val:
- best = [allocator]
- best_val = mean
- elif ((sort == ">" and mean > best_val)
- or (sort == "<" and mean < best_val)):
- best = [allocator]
- best_val = mean
- elif mean == best_val:
- best.append(allocator)
-
- row.append(f"{best[0]}: {best_val:.3f}")
- cell_text.append(" & ".join(row))
-
- table_layout = " l |" * len(headers)
- header_line = " & ".join([str(x) for x in headers])
- cell_text = "\\\\\n".join(cell_text)
-
- tex =\
-f"""\\documentclass{{standalone}}
-\\begin{{document}}
-\\begin{{tabular}}{{|{table_layout}}}
-{header_arg}/{row_arg} & {header_line} \\\\
-{cell_text}
-\\end{{tabular}}
-\\end{{document}}
-"""
-
- fname = os.path.join(sumdir, f"{bench.name}.{file_postfix}.tex")
- with open(fname, "w") as tex_file:
- print(tex, file=tex_file)
-
-
-def write_tex_table(bench, entries, file_postfix="", sumdir=""):
- """generate a latex standalone table from an list of entries dictionaries
-
- Entries must have at least the two keys: "label" and "expression".
- The optional "sort" key specifies the direction of the order:
- ">" : bigger is better.
- "<" : smaller is better.
-
- Table layout:
-
- | alloc1 | alloc2 | ....
- ---------------------------------------
- | name1 name2 | ...
- ---------------------------------------
- perm1 | eavl1 eval2 | ...
- perm2 | eval1 eval2 | ...
- """
- args = bench.results["args"]
- allocators = bench.results["allocators"]
- nallocators = len(allocators)
- nentries = len(entries)
- perm_fields = bench.Perm._fields
- nperm_fields = len(perm_fields)
-
- alloc_header_line = f"\\multicolumn{{{nperm_fields}}}{{c|}}{{}} &"
- for alloc in allocators:
- alloc_header_line += f"\\multicolumn{{{nentries}}}{{c|}}{{{alloc}}} &"
- alloc_header_line = alloc_header_line[:-1] + "\\\\"
-
- perm_fields_header = ""
- for field in bench.Perm._fields:
- perm_fields_header += f'{field} &'
- entry_header_line = ""
- for entry in entries:
- entry_header_line += f'{entry["label"]} &'
- entry_header_line = perm_fields_header + entry_header_line * nallocators
- entry_header_line = entry_header_line[:-1] + "\\\\"
-
- fname = os.path.join(sumdir, ".".join([bench.name, file_postfix, "tex"]))
- with open(fname, "w") as tex_file:
- print("\\documentclass{standalone}", file=tex_file)
- print("\\usepackage{booktabs}", file=tex_file)
- print("\\usepackage{xcolor}", file=tex_file)
- print("\\begin{document}", file=tex_file)
- print("\\begin{tabular}{|",
- f"{'c|'*nperm_fields}",
- f"{'c'*nentries}|" * nallocators,
- "}",
- file=tex_file)
- print("\\toprule", file=tex_file)
-
- print(alloc_header_line, file=tex_file)
- print("\\hline", file=tex_file)
- print(entry_header_line, file=tex_file)
- print("\\hline", file=tex_file)
-
- for perm in bench.iterate_args(args=args):
- values = [[] for _ in entries]
- maxs = [None for _ in entries]
- mins = [None for _ in entries]
- for allocator in allocators:
- for i, entry in enumerate(entries):
- expr = entry["expression"]
- values[i].append(
- _eval_with_stat(bench, expr, allocator, perm, "mean"))
-
- # get max and min for each entry
- for i, entry in enumerate(entries):
- if not "sort" in entry:
- continue
- # bigger is better
- if entry["sort"] == ">":
- maxs[i] = max(values[i])
- mins[i] = min(values[i])
- # smaller is better
- elif entry["sort"] == "<":
- mins[i] = max(values[i])
- maxs[i] = min(values[i])
-
- # build row
- row = ""
- perm_dict = perm._asdict()
- for field in perm_fields:
- row += str(perm_dict[field]) + "&"
-
- for i, _ in enumerate(allocators):
- for j, entry_vals in enumerate(values):
- val = entry_vals[i]
-
- # format
- val_str = str(val)
- if isinstance(val, float):
- val_str = f"{val:.2f}"
-
- # colorize
- if val == maxs[j]:
- val_str = f"\\textcolor{{green}}{{{val_str}}}"
- elif val == mins[j]:
- val_str = f"\\textcolor{{red}}{{{val_str}}}"
- row += f"{val_str} &"
- #escape _ for latex
- row = row.replace("_", "\\_")
- print(row[:-1], "\\\\", file=tex_file)
-
- print("\\end{tabular}", file=tex_file)
- print("\\end{document}", file=tex_file)
-
-
-def pgfplot_legend(bench,
- sumdir="",
- file_name="pgfplot_legend",
- colors=True,
- columns=3):
- """create a standalone pgfplot legend"""
-
- allocators = bench.results["allocators"]
- color_definitions = ""
- legend_entries = ""
- for alloc_name, alloc_dict in allocators.items():
- if colors:
- # define color
- rgb = matplotlib.colors.to_rgb(_get_alloc_color(bench, alloc_dict))
- color_definitions += f"\\providecolor{{{alloc_name}-color}}{{rgb}}{{{rgb[0]},{rgb[1]},{rgb[2]}}}\n"
- color_definitions += f"\\pgfplotsset{{{alloc_name}/.style={{color={alloc_name}-color}}}}\n\n"
-
- alloc_color = ""
- if colors:
- alloc_color = f"{alloc_name}-color"
- legend_entries += f"\t\\addplot+ [{alloc_color}] coordinates {{(0,0)}};\n"
- legend_entries += f"\t\\addlegendentry{{{alloc_name}}}\n\n"
-
- tex =\
-f"""
-\\documentclass{{standalone}}
-\\usepackage{{pgfplots}}
-
-\\usepackage{{xcolor}}
-
-{color_definitions}
-{src.globalvars.latex_custom_preamble}
-\\begin{{document}}
-\\begin{{tikzpicture}}
-\\begin{{axis}} [
-\tlegend columns={columns},
-\thide axis,
-\tscale only axis, width=5mm, % make axis really small (smaller than legend)
-]
-
-{legend_entries}
-\\end{{axis}}
-\\end{{tikzpicture}}
-\\end{{document}}"""
-
- with open(os.path.join(sumdir, f"{file_name}.tex"), "w") as legend_file:
- print(tex, file=legend_file)
-
-
-def pgfplot(bench,
- perms,
- xexpr,
- yexpr,
- axis_attr="",
- bar=False,
- ylabel="y-label",
- xlabel="x-label",
- title="default title",
- postfix="",
- sumdir="",
- scale=None,
- error_bars=True,
- colors=True):
-
- allocators = bench.results["allocators"]
- perms = list(perms)
-
- label_substitutions = vars()
- label_substitutions.update(vars(bench))
- xlabel = xlabel.format(**label_substitutions)
- ylabel = ylabel.format(**label_substitutions)
- title = title.format(**label_substitutions)
-
- if bar:
- axis_attr = f"\tybar,\n{axis_attr}"
-
- color_definitions = ""
- style_definitions = ""
- plots = ""
- for alloc_name, alloc_dict in allocators.items():
- if colors:
- # define color
- rgb = matplotlib.colors.to_rgb(_get_alloc_color(bench, alloc_dict))
- color_definitions += f"\\providecolor{{{alloc_name}-color}}{{rgb}}{{{rgb[0]},{rgb[1]},{rgb[2]}}}\n"
- style_definitions += f"\\pgfplotsset{{{alloc_name}/.style={{color={alloc_name}-color}}}}\n\n"
-
- eb = ""
- ebt = ""
- edp = ""
- if error_bars:
- eb = ",\n\terror bars/.cd, y dir=both, y explicit,\n"
- ebt += "[y error=error]"
- edp = " error"
- alloc_color = ""
- if colors:
- alloc_color = f"{alloc_name}"
- plots += f"\\addplot+[{alloc_color}{eb}] table {ebt}"
-
- plots += f" {{\n\tx y{edp}\n"
-
- for perm in perms:
- xval = _eval_with_stat(bench, xexpr, alloc_name, perm, "mean")
- yval = _eval_with_stat(bench, yexpr, alloc_name, perm, "mean")
- error = ""
- if error_bars:
- error = f" {_eval_with_stat(bench, yexpr, alloc_name, perm, 'std')}"
- plots += f"\t{xval} {yval}{error}\n"
-
- plots += "};\n"
-
- tex =\
-f"""\\documentclass{{standalone}}
-\\usepackage{{pgfplots}}
-\\usepackage{{xcolor}}
-{style_definitions}
-% include commont.tex if found to override styles
-% see https://tex.stackexchange.com/questions/377295/how-to-prevent-input-from-failing-if-the-file-is-missing/377312#377312
-\\InputIfFileExists{{common.tex}}{{}}{{}}
-{color_definitions}
-\\begin{{document}}
-\\begin{{tikzpicture}}
-\\begin{{axis}}[
-\ttitle={{{title}}},
-\txlabel={{{xlabel}}},
-\tylabel={{{ylabel}}},
-{axis_attr}]
-
-{plots}
-\\end{{axis}}
-\\end{{tikzpicture}}
-\\end{{document}}"""
-
- with open(os.path.join(sumdir, f"{bench.name}.{postfix}.tex"),
- "w") as plot_file:
- print(tex, file=plot_file)