#!/usr/bin/python3
# This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details

# Given a trace event file, this tool generates a flame graph based on the event scopes present in the file
# The result of analysis is a .svg file which can be viewed in a browser

import sys
import svg
import json

class Node(svg.Node):
    def __init__(self):
        svg.Node.__init__(self)
        self.caption = ""
        self.description = ""
        self.ticks = 0

    def text(self):
        return self.caption

    def title(self):
        return self.caption

    def details(self, root):
        return "{} ({:,} usec, {:.1%}); self: {:,} usec".format(self.description, self.width, self.width / root.width, self.ticks)

with open(sys.argv[1]) as f:
    dump = f.read()

root = Node()

# Finish the file
if not dump.endswith("]"):
    dump += "{}]"

data = json.loads(dump)

stacks = {}

for l in data:
    if len(l) == 0:
        continue

    # Track stack of each thread, but aggregate values together
    tid = l["tid"]

    if not tid in stacks:
        stacks[tid] = []
    stack = stacks[tid]

    if l["ph"] == 'B':
        stack.append(l)
    elif l["ph"] == 'E':
        node = root

        for e in stack:
            caption = e["name"]
            description = ''

            if "args" in e:
                for arg in e["args"]:
                    if len(description) != 0:
                        description += ", "

                    description += "{}: {}".format(arg, e["args"][arg])

            child = node.child(caption + description)
            child.caption = caption
            child.description = description

            node = child

        begin = stack[-1]

        ticks = l["ts"] - begin["ts"]
        rawticks = ticks

        # Flame graph requires ticks without children duration
        if "childts" in begin:
            ticks -= begin["childts"]

        node.ticks += int(ticks)

        stack.pop()

        if len(stack):
            parent = stack[-1]

            if "childts" in parent:
                parent["childts"] += rawticks
            else:
                parent["childts"] = rawticks

svg.layout(root, lambda n: n.ticks)
svg.display(root, "Flame Graph", "hot", flip = True)
