Index: tools/perf/png.py
===================================================================
--- tools/perf/png.py	(revision 1cc76897009fde80a172b634a4b8c0add28d4749)
+++ tools/perf/png.py	(revision 1cc76897009fde80a172b634a4b8c0add28d4749)
@@ -0,0 +1,123 @@
+#!/usr/bin/python3
+
+import argparse, json, math, sys, time
+import multiprocessing
+from PIL import Image
+import numpy as np
+
+class Timed:
+	def __init__(self, text):
+		print(text, end='', flush=True)
+
+	def pretty(self, durr):
+		seconds = int(durr)
+		days, seconds = divmod(seconds, 86400)
+		hours, seconds = divmod(seconds, 3600)
+		minutes, seconds = divmod(seconds, 60)
+		if days > 0:
+			return '%dd%dh%dm%ds' % (days, hours, minutes, seconds)
+		elif hours > 0:
+			return '%dh%dm%ds' % (hours, minutes, seconds)
+		elif minutes > 0:
+			return '%dm%ds' % (minutes, seconds)
+		else:
+			return '%ds' % (seconds,)
+
+	def __enter__(self):
+		self.start = time.time()
+		return self
+
+	def __exit__(self, *args):
+		self.end = time.time()
+		print(self.pretty(self.end - self.start))
+
+
+
+parser = argparse.ArgumentParser()
+parser.add_argument('--infile', type=argparse.FileType('r'), default=sys.stdin)
+parser.add_argument('--outfile', type=str, default='out.png')
+
+args = parser.parse_args()
+
+pool = multiprocessing.Pool()
+
+with Timed("Loading json..."):
+	obj = json.load(args.infile)
+
+min_tsc = int(obj['min-tsc'])
+max_tsc = int(obj['max-tsc'])
+
+def tsc_to_s(tsc):
+	return float(tsc - min_tsc)  / 2500000000.0
+
+max_sec = tsc_to_s(max_tsc)
+print([min_tsc, max_tsc, max_sec])
+
+min_cpu = int(obj['min-cpu'])
+max_cpu = int(obj['max-cpu'])
+cnt_cpu = max_cpu - min_cpu + 1
+nbins = math.ceil(max_sec * 10)
+
+class Bar:
+	def __init__(self):
+		pass
+
+with Timed("Creating bins..."):
+	bins = []
+	for _ in range(0, int(nbins)):
+		bar = Bar()
+		bins.append([bar, *[*([0] * cnt_cpu), bar] * cnt_cpu])
+		# bins.append([0] * cnt_cpu)
+
+	bins = np.array(bins)
+
+
+
+def flatten(val):
+	secs = tsc_to_s(val[1])
+	ratio = secs / max_sec
+	b = int(ratio * (nbins - 1))
+	## from/to
+	from_ = val[2] - min_cpu
+	to_   = val[3] - min_cpu
+	idx = int(1 + ((cnt_cpu + 1) * to_) + from_)
+	return [b, idx, 1]
+	## val per cpu
+	# cnt = val[2]
+	# idx = val[3] - min_cpu
+	# # idx = from_
+	# return [b, idx, cnt]
+
+
+with Timed("Compressing data..."):
+	compress = map(flatten, obj['values'])
+
+highest  = 1
+with Timed("Grouping data..."):
+	for x in compress:
+		bins[x[0]][x[1]] += x[2]
+		highest = max(highest, bins[x[0]][x[1]])
+
+print(highest)
+# highest  = 10000000000
+
+with Timed("Normalizing data..."):
+	def normalize(v):
+		if type(v) is Bar:
+			return np.uint32(0xff008000)
+		v = v * 255 / float(highest)
+		if v > 256:
+			v = 255
+		u8 = np.uint8(v)
+		u32 = np.uint32(u8)
+
+		return (0xff << 24) | (u32 << 16) | (u32 << 8) | (u32 << 0)
+	normalizef = np.vectorize(normalize, [np.uint32])
+
+	bins = normalizef(bins)
+
+print(bins.shape)
+with Timed("Saving image..."):
+	im = Image.fromarray(bins, mode='RGBA')
+	im.show()
+	im.save(args.outfile)
