Skip to content

Instantly share code, notes, and snippets.

@nh2
Created April 19, 2022 14:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nh2/19e92530b51338959615af19d9759c5b to your computer and use it in GitHub Desktop.
Save nh2/19e92530b51338959615af19d9759c5b to your computer and use it in GitHub Desktop.
Process `ghc -v` output, summarising where time is spent.
# ./ghc-performance-categorizer.py ghci-v-output-1.txt --sort-by time
TASK time ms mem MB
------------------------------------------------
systool:cpp 4 2
systool:merge-objects 11 3
Simplify 38 68
ByteCodeGen 70 164
initializing 72 119
systool:as 175 68
Chasing 705 1453
Parser 775 1964
systool:linker 812 357
CoreTidy 1149 1405
systool:cc 1489 58
CorePrep 1821 2203
Simplifier 8040 11801
Renamer/typechecker 20184 26724
CodeGen 43917 81220
Desugar 58124 117565
------------------------------------------------
TOTAL 137387 245175
#! /usr/bin/env python3
import argparse
import fileinput
import re
from collections import defaultdict
from operator import itemgetter
def main():
parser = argparse.ArgumentParser(description='Process `ghc -v` output, summarising where time is spent.')
parser.add_argument('files', metavar='FILE', nargs='*', help='files to read, if empty, stdin is used')
parser.add_argument('--sort-by', dest='sort_by', choices=['time', 'memory'], help='Sort table by this column')
args = parser.parse_args()
# Example lines:
# !!! ByteCodeGen [Ghci1]: finished in 0.02 milliseconds, allocated 0.037 megabytes
# !!! Desugar [Test.MallocInfo]: finished in 1.46 milliseconds, allocated 3.290 megabytes
# !!! Simplify [expr]: finished in 0.72 milliseconds, allocated 2.717 megabytes
# !!! systool:as: finished in 0.37 milliseconds, allocated 0.219 megabytes
r = re.compile(r'!!! (?P<task>\S+).* finished in (?P<time_milliseconds>\S+) milliseconds, allocated (?P<memory_megabytes>\S+) megabytes')
times = defaultdict(float)
memories = defaultdict(float)
# If you would call fileinput.input() without files it would try to process all arguments.
# We pass '-' as only file when argparse got no files which will cause fileinput to read from stdin
# From: https://gist.github.com/martinth/ed991fb8cdcac3dfadf7
for line in fileinput.input(files=args.files if len(args.files) > 0 else ('-', )):
l = line.strip('\n')
res = r.match(l)
if res is not None:
d = res.groupdict()
task = d['task'].rstrip(':')
time_milliseconds = float(d['time_milliseconds'])
memory_megabytes = float(d['memory_megabytes'])
times[task] += time_milliseconds
memories[task] += memory_megabytes
total_time = sum([t for _, t in times.items()])
total_mem = sum([t for _, t in memories.items()])
tasks = times.keys()
if args.sort_by == 'time':
tasks = sorted(times.keys(), key=lambda t: times[t])
elif args.sort_by == 'memory':
tasks = sorted(times.keys(), key=lambda t: memories[t])
def format_line(task: str, time_milliseconds: float, memory_megabytes: float):
time = f"{time_milliseconds:4.0f}"
mem = f"{memory_megabytes:4.0f}"
return f"{task.ljust(30)} {time.rjust(8)} {mem.rjust(8)}"
print("TASK time ms mem MB")
print("------------------------------------------------")
for task in tasks:
print(format_line(task, times[task], memories[task]))
print("------------------------------------------------")
print(format_line("TOTAL", total_time, total_mem))
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment