forked from OSchip/llvm-project
195 lines
5.6 KiB
Python
195 lines
5.6 KiB
Python
"""Reads JSON files produced by the benchmarking framework and renders them.
|
|
|
|
Installation:
|
|
> apt-get install python3-pip
|
|
> pip3 install matplotlib scipy numpy
|
|
|
|
Run:
|
|
> python3 render.py3 <files>
|
|
|
|
Rendering can occur on disk by specifying the --output option or on screen if
|
|
the --headless flag is not set.
|
|
"""
|
|
|
|
import argparse
|
|
import collections
|
|
import json
|
|
import math
|
|
import pprint
|
|
import sys
|
|
import matplotlib.pyplot as plt
|
|
from matplotlib.ticker import EngFormatter
|
|
import numpy as np
|
|
import scipy.stats
|
|
|
|
|
|
def format_freq(number):
|
|
"""Returns a human readable frequency."""
|
|
magnitude = 0
|
|
while math.fabs(number) >= 1000:
|
|
number /= 1000.0
|
|
magnitude += 1
|
|
return "%g%sHz" % (number, ["", "k", "M", "G"][magnitude])
|
|
|
|
|
|
def format_size(number):
|
|
"""Returns number in human readable form."""
|
|
magnitude = 0
|
|
while number >= 1000 and number % 1000 == 0:
|
|
number /= 1000
|
|
magnitude += 1
|
|
return "%g%s" % (number, ["", "K", "M", "G"][magnitude])
|
|
|
|
|
|
def mean_confidence_interval(dataset, confidence=0.95):
|
|
"""Returns the mean and half confidence interval for the dataset."""
|
|
a = 1.0 * np.array(dataset)
|
|
n = len(a)
|
|
m, se = np.mean(a), scipy.stats.sem(a)
|
|
h = se * scipy.stats.t.ppf((1 + confidence) / 2., n - 1)
|
|
return m, h
|
|
|
|
|
|
def add_plot(function_name, points):
|
|
"""Plots measurements for a function."""
|
|
n = len(points.keys())
|
|
x = np.zeros(n)
|
|
y = np.zeros(n)
|
|
yerr = np.zeros(n)
|
|
|
|
for i, key in enumerate(sorted(points.keys())):
|
|
values = points[key]
|
|
m, e = mean_confidence_interval(values)
|
|
x[i] = key
|
|
y[i] = m
|
|
yerr[i] = e
|
|
|
|
plt.plot(x, y, linewidth=1, label=function_name)
|
|
plt.fill_between(x, y - yerr, y + yerr, alpha=0.5)
|
|
|
|
|
|
def get_title(host):
|
|
"""Formats the Host object into a title for the plot."""
|
|
cpu_name = host["CpuName"]
|
|
cpu_freq = format_freq(host["CpuFrequency"])
|
|
cache_strings = []
|
|
for cache in host["Caches"]:
|
|
prefix = {
|
|
"Instruction": "i",
|
|
"Data": "d",
|
|
"Unified": "u",
|
|
}.get(cache["Type"])
|
|
cache_strings.append(r"%sL_%d %s_{/%d}" %
|
|
(prefix, cache["Level"], format_size(
|
|
cache["Size"]), cache["NumSharing"]))
|
|
title = "%s (%s)" % (cpu_name, cpu_freq)
|
|
subtitle = r"$" + ", ".join(sorted(cache_strings)) + r"$"
|
|
return title + "\n" + subtitle
|
|
|
|
|
|
def get_host(jsons):
|
|
"""Returns the host of the different json objects iff they are all the same.
|
|
"""
|
|
host = None
|
|
for root in jsons:
|
|
if host and host != root["Host"]:
|
|
sys.exit("The datasets are not coming from the same Host")
|
|
if not host:
|
|
host = root["Host"]
|
|
return host
|
|
|
|
|
|
def get_configuration(jsons):
|
|
"""Returns the configuration of the different json objects iff they are all
|
|
the same.
|
|
"""
|
|
config = None
|
|
for root in jsons:
|
|
if config and config != root["Configuration"]:
|
|
return None
|
|
if not config:
|
|
config = root["Configuration"]
|
|
return config
|
|
|
|
|
|
def setup_graphs(files, display):
|
|
"""Setups the graphs to render from the json files."""
|
|
jsons = []
|
|
for file in files:
|
|
with open(file) as json_file:
|
|
jsons.append(json.load(json_file))
|
|
if not jsons:
|
|
sys.exit("Nothing to process")
|
|
|
|
for root in jsons:
|
|
frequency = root["Host"]["CpuFrequency"]
|
|
for function in root["Functions"]:
|
|
function_name = function["Name"]
|
|
sizes = function["Sizes"]
|
|
runtimes = function["Runtimes"]
|
|
assert len(sizes) == len(runtimes)
|
|
values = collections.defaultdict(lambda: [])
|
|
for i in range(len(sizes)):
|
|
value = runtimes[i]
|
|
if display == "cycles":
|
|
value = value * frequency
|
|
if display == "bytespercycle":
|
|
value = value * frequency
|
|
value = sizes[i] / value
|
|
values[sizes[i]].append(value)
|
|
add_plot(function_name, values)
|
|
|
|
config = get_configuration(jsons)
|
|
if config:
|
|
plt.figtext(
|
|
0.95,
|
|
0.15,
|
|
pprint.pformat(config),
|
|
verticalalignment="bottom",
|
|
horizontalalignment="right",
|
|
multialignment="left",
|
|
fontsize="small",
|
|
bbox=dict(boxstyle="round", facecolor="wheat", alpha=0.5))
|
|
|
|
axes = plt.gca()
|
|
axes.set_title(get_title(get_host(jsons)))
|
|
axes.set_ylim(bottom=0)
|
|
axes.set_xlabel("Size")
|
|
axes.xaxis.set_major_formatter(EngFormatter(unit="B"))
|
|
if display == "cycles":
|
|
axes.set_ylabel("Cycles")
|
|
if display == "time":
|
|
axes.set_ylabel("Time")
|
|
axes.yaxis.set_major_formatter(EngFormatter(unit="s"))
|
|
if display == "bytespercycle":
|
|
axes.set_ylabel("bytes/cycle")
|
|
|
|
plt.legend()
|
|
plt.grid()
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="Process benchmark json files.")
|
|
parser.add_argument("files", nargs="+", help="The json files to read from.")
|
|
parser.add_argument("--output", help="The output file to write the graph.")
|
|
parser.add_argument(
|
|
"--headless",
|
|
help="If set do not display the graph.",
|
|
action="store_true")
|
|
parser.add_argument(
|
|
"--display",
|
|
choices= ["time", "cycles", "bytespercycle"],
|
|
default="time",
|
|
help="Use to display either 'time', 'cycles' or 'bytes/cycle'.")
|
|
|
|
args = parser.parse_args()
|
|
setup_graphs(args.files, args.display)
|
|
if args.output:
|
|
plt.savefig(args.output)
|
|
if not args.headless:
|
|
plt.show()
|
|
|
|
if __name__ == "__main__":
|
|
main()
|