Module progbg.graphing

Graphing Primitives Module

Expand source code
"""Graphing Primitives Module"""

from ._bargraph import BarGraph, Bar, BarFactory, BarGroup
from ._custom import CustomGraph
from ._linegraph import Line, LineGraph, ConstLine

__all__ = [
    "BarGraph",
    "Bar",
    "BarFactory",
    "BarGroup",
    "CustomGraph",
    "Line",
    "LineGraph",
    "ConstLine",
]

Classes

class Bar (wl, composed_of, label=None)

Bar object used within BarGraph

This represent a bar within a bar graph. Its construction used an execution object. Once an execution is done, metrics objects are pulled and summarized into means and standard deviations.

The keys within the core.Metrics are used to compose bars. You may select just one. But optionally you may compose bars of many metrics (See matplotlibs stacked bar).

Arguments

wl (Execution, List): Execution object or list for constant values composed_of (List): A key for the data to use, or optionally a list of keys label (str): Label of the bar

Example

>>> e1 = plan_execution(...)
>>> e2 = plan_execution(...)
>>> e3 = plan_execution(...)
>>>
>>> b1 = Bar(e1, ["data1", "data2"])
>>>
>>> # Below Bar: data1 = 10, data2 = 22
>>> b1 = Bar([10, 22], ["data1", "data2"])
Expand source code
class Bar(GraphObject):
    """Bar object used within `BarGraph`

    This represent a bar within a bar graph.  Its construction used an execution object.
    Once an execution is done, metrics objects are pulled and summarized into means and standard
    deviations.

    The keys within the `core.Metrics` are used to compose bars.  You may select just one.
    But optionally you may compose bars of many metrics (See matplotlibs stacked bar).

    Arguments:
        wl (Execution, List):  Execution object or list for constant values
        composed_of (List): A key for the data to use, or optionally a list of keys
        label (str): Label of the bar

    Example:
        >>> e1 = plan_execution(...)
        >>> e2 = plan_execution(...)
        >>> e3 = plan_execution(...)
        >>>
        >>> b1 = Bar(e1, ["data1", "data2"])
        >>>
        >>> # Below Bar: data1 = 10, data2 = 22
        >>> b1 = Bar([10, 22], ["data1", "data2"])
    """

    def __init__(self, wl, composed_of, label=None):

        self.composed = composed_of
        if isinstance(wl, list):
            d = dict()
            for i, x in enumerate(self.composed):
                d[x] = wl[i]
            wl = ExecutionStub(**d)
        self.workload = wl
        self.label = label
        if isinstance(label, str):
            self.label = [label]
        if label is None:
            self.label = [""]

    def get_data(self, restrict_on):
        d = filter(self.workload._cached, restrict_on)[0].get_stats()
        composed = []
        for c in self.composed:
            composed.append(c)
            composed.append(c + "_std")
        label = self.label
        return pd.DataFrame({c: d[c] for c in composed}, index=self.label).T

Ancestors

  • progbg.graphing._graph.GraphObject
  • abc.ABC

Methods

def get_data(self, restrict_on)
Expand source code
def get_data(self, restrict_on):
    d = filter(self.workload._cached, restrict_on)[0].get_stats()
    composed = []
    for c in self.composed:
        composed.append(c)
        composed.append(c + "_std")
    label = self.label
    return pd.DataFrame({c: d[c] for c in composed}, index=self.label).T
class BarFactory (wl)

Ease of use Factory Class

Used to quickly be able to make many bars from one Execution object

Expand source code
class BarFactory:
    """Ease of use Factory Class

    Used to quickly be able to make many bars from one Execution object
    """

    def __init__(self, wl):
        self.workload = wl

    def __call__(self, composed_of, label=None):
        if not label:
            label = self.workload.name
        return Bar(self.workload, composed_of, label)
class BarGraph (workloads: List, **kwargs)

Bar Graph

Args

workloads : List
A list of list of bars. Each list is a grouping of bars to be graphs.
group_labels : List
Labels associated to each grouped list in workloads.
formatter : Function, optional
Function object for post customization of graphs.
width : float
Width of each bar
out : Path
Output file for this single graph to be saved to
style : str
Style of the graph (default: color_a)
kwargs : optional
Passed to matplotlib Axes.plot function or optional named params below.

Progbg optional kwargs: title (str): Title of the graph or figure

Examples

Suppose we have some previously defined execution called exec.

>>> exec = plan_execution(...)
>>> bar1 = Bar(exec, "stat-one", label="Custom Stat")
>>> bar2 = Bar(exec, "stat-two", label="Custom Stat Two")
>>> plan_graph(
>>>     BarGraph([[bar1, bar2]],
>>>         group_labels=["These a grouped!"],
>>>         out="custom.svg"
>>>     )

The above example would create a graph grouping both bar1, and bar2 next to each other. The below example would seperate bar1 and bar2. "stat-one", and "stat-two", are both values that would have been added to the associated core.Metrics object which is passed through the parser functions provided by the user.

>>> plan_graph(
>>>     BarGraph([[bar1], [bar2]],
>>>         group_labels=["Group 1!", "Group 2!"],
>>>         out="custom.svg"
>>>     )
Expand source code
class BarGraph(Graph):
    """Bar Graph

    Args:
        workloads (List): A list of list of bars.  Each list is a grouping of bars to be graphs.
        group_labels (List): Labels associated to each grouped list in workloads.
        formatter (Function, optional): Function object for post customization of graphs.
        width (float): Width of each bar
        out (Path): Output file for this single graph to be saved to
        style (str): Style of the graph (default: color_a)
        kwargs (optional): Passed to matplotlib `Axes.plot` function or optional named params below.

    Progbg optional kwargs:
        title (str): Title of the graph or figure

    Examples:
        Suppose we have some previously defined execution called `exec`.

        >>> exec = plan_execution(...)
        >>> bar1 = Bar(exec, "stat-one", label="Custom Stat")
        >>> bar2 = Bar(exec, "stat-two", label="Custom Stat Two")
        >>> plan_graph(
        >>>     BarGraph([[bar1, bar2]],
        >>>         group_labels=["These a grouped!"],
        >>>         out="custom.svg"
        >>>     )

        The above example would create a graph grouping both bar1, and bar2 next to each other. The below example
        would seperate bar1 and bar2. "stat-one", and "stat-two", are both values that would have been added to the
        associated `core.Metrics` object which is passed through the parser functions provided by the user.

        >>> plan_graph(
        >>>     BarGraph([[bar1], [bar2]],
        >>>         group_labels=["Group 1!", "Group 2!"],
        >>>         out="custom.svg"
        >>>     )
    """

    def __init__(self, workloads: List, **kwargs):
        super().__init__(**kwargs)

        default_options = dict(
            std=True,
            group_labels=[],
            log=False,
            width=0.5,
        )

        for prop, default in default_options.items():
            setattr(self, prop, kwargs.get(prop, default))

        self.workloads = workloads
        self.html_out = ".".join(self.out.split(".")[:-1]) + ".svg"

    def _graph(self, ax, data):
        if isinstance(self.workloads[0], BarGroup):
            # Retrieve top level labels
            data = [pd.concat(x) for x in data]
            data = pd.concat(data, axis=1)
            cols = [c for c in data.columns if c[-4:] != "_std"]
            cols_std = [c for c in data.columns if len(c) > 4 and c[-4:] == "_std"]
            df = data[cols].T
            std = data[cols_std]
            std.columns = [x[:-4] for x in std.columns]
            std = std.T
            df.plot.bar(rot=-90, ax=ax, yerr=std, capsize=4, width=self.width)
            if self.log:
                ax.set_yscale("log")
        else:
            data = pd.concat(data, axis=1).T
            cols = [c for c in data.columns if c[-4:] != "_std"]
            cols_std = [c for c in data.columns if len(c) > 4 and c[-4:] == "_std"]
            df = data[cols]
            std = data[cols_std].T
            df.plot(rot=-90, kind="bar", stacked=True, ax=ax)

Ancestors

  • progbg.graphing._graph.Graph
  • abc.ABC
class BarGroup (executions, cat, label)

Groups Data as bars on single value

Arguments

executions (List): List of executions to include in group cat (str): category to compare the workloads label (List[str]): Labels for the elements in the group

Examples

>>> e1 = plan_execution(...)
>>> e2 = plan_execution(...)
>>> e3 = plan_execution(...)
>>>
>>> b1 = BarGroup([e1, e2, e3], "data1", ["exec1", "exec2", "exec3"])
>>> b2 = BarGroup([e1, 20, e3], "data1", ["exec1", "normal", "exec3"])
Expand source code
class BarGroup(GraphObject):
    """Groups Data as bars on single value

    Arguments:
        executions (List): List of executions to include in group
        cat (str): category to compare the workloads
        label (List[str]): Labels for the elements in the group

    Examples:
        >>> e1 = plan_execution(...)
        >>> e2 = plan_execution(...)
        >>> e3 = plan_execution(...)
        >>>
        >>> b1 = BarGroup([e1, e2, e3], "data1", ["exec1", "exec2", "exec3"])
        >>> b2 = BarGroup([e1, 20, e3], "data1", ["exec1", "normal", "exec3"])
    """

    def __init__(self, executions, cat, label):
        self.wls = executions
        self.cat = cat
        self.label = label

    def get_data(self, restrict_on):
        bars = []
        for i, w in enumerate(self.wls):
            bars.append(Bar(w, [self.cat], [self.label[i]]))
        dfs = [b.get_data(restrict_on).T for b in bars]
        return dfs

    def bars(self):
        bars = []
        half = (len(self.wls) - 1 / 2) - 1
        for i, w in enumerate(self.wls):
            if i == half:
                bars.append(Bar(w, self.cat, self.label[i]))
            else:
                bars.append(Bar(w, self.cat, None))
        return bars

Ancestors

  • progbg.graphing._graph.GraphObject
  • abc.ABC

Methods

def bars(self)
Expand source code
def bars(self):
    bars = []
    half = (len(self.wls) - 1 / 2) - 1
    for i, w in enumerate(self.wls):
        if i == half:
            bars.append(Bar(w, self.cat, self.label[i]))
        else:
            bars.append(Bar(w, self.cat, None))
    return bars
def get_data(self, restrict_on)
Expand source code
def get_data(self, restrict_on):
    bars = []
    for i, w in enumerate(self.wls):
        bars.append(Bar(w, [self.cat], [self.label[i]]))
    dfs = [b.get_data(restrict_on).T for b in bars]
    return dfs
class ConstLine (value, label, index, style=':')

Const Line Object Will plot a horizontal line

Arguments

value: Value for constant line, either a workload or straight number label (str): Label for the line index: Label used for the value's index. Used when comparing against other lines

Expand source code
class ConstLine(GraphObject):
    """Const Line Object
    Will plot a horizontal line

    Arguments:
        value: Value for constant line, either a workload or straight number
        label (str): Label for the line
        index: Label used for the value's index. Used when comparing against other lines
    """

    def __init__(self, value, label, index, style=":"):
        self.label = label
        self.value = value
        self.index = index
        self.style = style

    def get_data(self, restrict_on):
        val = self.value._cached[0].get_stats()[self.index]
        return (self.label, val)

Ancestors

  • progbg.graphing._graph.GraphObject
  • abc.ABC

Methods

def get_data(self, restrict_on)
Expand source code
def get_data(self, restrict_on):
    val = self.value._cached[0].get_stats()[self.index]
    return (self.label, val)
class CustomGraph (workloads, func, out, formatter=[], style='color_a')

Helper class that provides a standard way to create an ABC using inheritance.

Expand source code
class CustomGraph(Graph):
    def __init__(self, workloads, func, out, formatter=[], style="color_a"):
        self.workloads = workloads
        self.formatter = formatter
        self.formatters = formatter
        self.style = style
        self.out = out
        self._opts = dict(
            std=True,
        )
        self.html_out = ".".join(out.split(".")[:-1]) + ".svg"
        self._restrict_on = {}
        self.func = func

    def _graph(self, ax, data):
        self.func(ax, data)

Ancestors

  • progbg.graphing._graph.Graph
  • abc.ABC
class Line (execution, value: str, x=None, label: str = None, style='--')

Line Object Used to specify a line within a graph.

Arguments

workload (Execution, list[Execution]): Specifies an Execution or list to produce a series. value (str): Data label to capture in the series x (str, list): When one execution is specified, represent string label to specify x axis.

Optional

label (str): Label for the line style (str): Style for the line

Examples

>>> e1 = plan_execution(...)
>>> e2 = plan_execution(...)
>>> e3 = plan_execution(...)
>>>
>>> l1 = Line(e1, "data1", x="x_axis_data")
>>> # In below line object, line at point 0 will specify e1["data1"],
>>> # 1 will specify e2["data1"], and 2 will specify e3["data1"]
>>> l2 = Line([e1, e2, e3], "data1", x=[0, 1, 2])
Expand source code
class Line(GraphObject):
    """Line Object
    Used to specify a line within a graph.

    Arguments:
        workload (Execution, list[Execution]): Specifies an Execution or list to produce a series.
        value (str): Data label to capture in the series
        x (str, list): When one execution is specified, represent string label to specify x axis.

    Optional:
        label (str): Label for the line
        style (str): Style for the line

    Examples:
        >>> e1 = plan_execution(...)
        >>> e2 = plan_execution(...)
        >>> e3 = plan_execution(...)
        >>>
        >>> l1 = Line(e1, "data1", x="x_axis_data")
        >>> # In below line object, line at point 0 will specify e1["data1"],
        >>> # 1 will specify e2["data1"], and 2 will specify e3["data1"]
        >>> l2 = Line([e1, e2, e3], "data1", x=[0, 1, 2])
    """

    def __init__(self, execution, value: str, x=None, label: str = None, style="--"):
        if label:
            self.label = label
        else:
            self.label = value
        self.value = value
        self.workload = execution
        self.x = x
        self.style = style
        assert x is not None, "x must be specified"

        if isinstance(x, str):
            assert not isinstance(
                x, list
            ), "When x is data ID, only one execution can be specified"

        if isinstance(x, list):
            assert len(x) == len(
                workload
            ), "When x is a list, workload list length must equal x list length"

    def get_data(self, restrict_on, iter=None):
        d = {self.label: [], self.label + "_std": []}
        if isinstance(self.workload, list):
            for x in self.workload:
                d[self.label].append(x._cached[0].get_stats()[self.value])
                d[self.label + "_std"].append(
                    x._cached[0].get_stats()[self.value + "_std"]
                )

            return pd.DataFrame(d, index=self.x)
        else:
            metrics = filter(self.workload._cached, restrict_on)
            dicts = [d.get_stats() for d in metrics]
            df = pd.DataFrame(dicts)
            df = df.groupby([self.x])

            return pd.DataFrame(df.mean()[[self.value, self.value + "_std"]])

Ancestors

  • progbg.graphing._graph.GraphObject
  • abc.ABC

Methods

def get_data(self, restrict_on, iter=None)
Expand source code
def get_data(self, restrict_on, iter=None):
    d = {self.label: [], self.label + "_std": []}
    if isinstance(self.workload, list):
        for x in self.workload:
            d[self.label].append(x._cached[0].get_stats()[self.value])
            d[self.label + "_std"].append(
                x._cached[0].get_stats()[self.value + "_std"]
            )

        return pd.DataFrame(d, index=self.x)
    else:
        metrics = filter(self.workload._cached, restrict_on)
        dicts = [d.get_stats() for d in metrics]
        df = pd.DataFrame(dicts)
        df = df.groupby([self.x])

        return pd.DataFrame(df.mean()[[self.value, self.value + "_std"]])
class LineGraph (lines, **kwargs)

progbg Line Graph

Args

lines : List[Line]
Workloads that the line graph will use in the WRK:BCK1/BCK2 format
type (str) (Function, optional): Type of line graph (default, cdf)
style : str, cycler
Style string for progbg styles or a cycler object to dictate custom style of lines
formatter : Function, optional
Formatter to be used on the graph once the graph is complete
out : str, optional
Optional name for file the user wishes to save the graph too.
kwargs : optional
Passed to matplotlib Axes.plot function or optional named params below.

Types of Line Graphs: default: This is just the standard line graph cdf: Creates a CDF line graph

Examples

Suppose we have some previously defined backend composed_backend and workloads Wrk:

>>> exec = sb.plan_execution(
>>>     Wrk({}, [("x", range(0, 5))], iterations = 5),
>>>     out = "out",
>>>     backends = [composed_backend({},
>>>         [("pass_me_in", range(0, 10, 2))])],
>>>     parser = file_func,
>>> )

Note: We are executing the benchmark over a ranging value called "x". Say we want to see how our stat changes over this value using a line graph. The following would be done:

>>> line1 = Line(exec, "stat-one", x="x", label="Custom Stat")
>>> line2 = Line(exec, "stat-two", x="x", label="Custom Stat Two")
>>> plan_graph(
>>>     LineGraph([line1, line2],
>>>         restrict_on = {
>>>             "pass_me_in", 0,
>>>         },
>>>         out="custom.svg"
>>>     )

We restrict on pass_me_in = 0 as in the above execution we are executing over this as well so we need to isolate on one changing value for the line graph.

Expand source code
class LineGraph(Graph):
    """progbg Line Graph

    Args:
        lines (List[Line]): Workloads that the line graph will use in the WRK:BCK1/BCK2 format
        type (str) (Function, optional): Type of line graph (default, cdf)
        style (str, cycler): Style string for progbg styles or a cycler object to dictate custom style of lines
        formatter (Function, optional): Formatter to be used on the graph once the graph is complete
        out (str, optional): Optional name for file the user wishes to save the graph too.
        kwargs (optional): Passed to matplotlib `Axes.plot` function or optional named params below.

    Types of Line Graphs:
        default: This is just the standard line graph
        cdf: Creates a CDF line graph

    Examples:
        Suppose we have some previously defined backend `composed_backend` and workloads `Wrk`:

        >>> exec = sb.plan_execution(
        >>>     Wrk({}, [("x", range(0, 5))], iterations = 5),
        >>>     out = "out",
        >>>     backends = [composed_backend({},
        >>>         [("pass_me_in", range(0, 10, 2))])],
        >>>     parser = file_func,
        >>> )

        Note: We are executing the benchmark over a ranging value called "x". Say we want to see how
        our stat changes over this value using a line graph. The following would be done:

        >>> line1 = Line(exec, "stat-one", x="x", label="Custom Stat")
        >>> line2 = Line(exec, "stat-two", x="x", label="Custom Stat Two")
        >>> plan_graph(
        >>>     LineGraph([line1, line2],
        >>>         restrict_on = {
        >>>             "pass_me_in", 0,
        >>>         },
        >>>         out="custom.svg"
        >>>     )

        We restrict on `pass_me_in = 0` as in the above execution we are executing over this as well so
        we need to isolate on one changing value for the line graph.
    """

    def __init__(self, lines, **kwargs):
        super().__init__(**kwargs)

        default_options = dict(
            std=False,
            group_labels=[],
            type="default",
            log=False,
            width=0.5,
        )

        for prop, default in default_options.items():
            setattr(self, prop, kwargs.get(prop, default))

        self.consts = []
        self.workloads = []
        for c in lines:
            if isinstance(c, ConstLine):
                self.consts.append(c)
            else:
                self.workloads.append(c)

        self.html_out = ".".join(self.out.split(".")[:-1]) + ".svg"

    def _graph(self, ax, data):
        # Hack for dealing with const lines.
        consts = [x.get_data(self._restrict_on) for x in self.consts]
        vals = [x for x in data[0].T.columns]
        styles = [x.style for x in self.workloads]
        styles_consts = [x.style for x in self.consts]

        # Combine data
        data = pd.concat(data, axis=1)
        consts = [pd.DataFrame({c[0]: [c[1]] * len(vals)}, index=vals) for c in consts]
        if len(consts):
            consts = pd.concat(consts, axis=1)

        # Pull out the standard deviation and such
        cols = [c for c in data.columns if c[-4:] != "_std"]
        cols_std = [c for c in data.columns if len(c) > 4 and c[-4:] == "_std"]
        d = data[cols]
        std = data[cols_std]
        std.columns = [x[:-4] for x in std.columns]
        y = [x for x in d.T.columns]

        # It seems like styles is not respected setting them so we will manually do them
        style = iter(get_style_cycler())
        for i, x in enumerate(d.columns):
            tmp = next(style)
            if self.std:
                ax.errorbar(y, d[x].tolist(), yerr=std[x], **tmp)
            else:
                ax.plot(y, d[x].tolist(), styles[i], **tmp)
        if len(consts):
            for i, x in enumerate(consts.columns):
                tmp = next(style)
                ax.plot(y, consts[x].tolist(), **tmp)

Ancestors

  • progbg.graphing._graph.Graph
  • abc.ABC