Skip to content

Instantly share code, notes, and snippets.

@JohanEngelen
Created February 24, 2023 20:37
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 JohanEngelen/907c7681e4740f82d37fd2f2244ba7bf to your computer and use it in GitHub Desktop.
Save JohanEngelen/907c7681e4740f82d37fd2f2244ba7bf to your computer and use it in GitHub Desktop.
ASCII output of --ftime-trace
// Run using: ldc2 --run format_timetrace.d <your .time-trace file>
// Outputs timetrace.txt in current dir
import std.stdio;
import std.file;
import std.json;
import std.range;
import std.conv;
import std.algorithm;
int main(string[] args) {
if (args.length < 1)
return 1;
auto input_json = read(args[1]).to!string;
auto output_file = File("timetrace.txt", "w");
auto json = parseJSON(input_json);
auto beginningOfTime = json["beginningOfTime"].get!ulong;
auto traceEvents = json["traceEvents"].get!(JSONValue[]);
// Read meta data
JSONValue[] metadata; // "M"
JSONValue[] counterdata; // "C"
JSONValue[] processes; // "X"
foreach (value; traceEvents) {
switch (value["ph"].get!string) {
case "M": metadata ~= value; break;
case "C": counterdata ~= value; break;
case "X": processes ~= value; break;
default: //drop
}
}
output_file.writeln("Timetrace: ", args[1]);
output_file.writeln("Metadata:");
foreach (node; metadata) {
output_file.write(" ");
output_file.writeln(node);
}
// process node = {"ph":"X","name": "Sema1: Module object","ts":26825,"dur":1477,"loc":"<no file>","args":{"detail": "","loc":"<no file>"},"pid":101,"tid":101},
// Sort time processes
multiSort!( q{a["ts"].get!ulong < b["ts"].get!ulong},
q{a["dur"].get!ulong > b["dur"].get!ulong})
(processes);
// Build tree (to get nicer looking structure lines)
static struct Node {
Node[] children;
const JSONValue* json;
ulong last_ts; // represents the last timestamp of this node (i.e. ts + dur)
this(const JSONValue* json, ulong last_ts) {
this.json = json;
this.last_ts = last_ts;
}
}
Node tree = Node(new JSONValue("Tree root"), ulong.max);
Node*[] parent_stack = [&tree]; // each stack item represents the first uncompleted note of that level in the tree
foreach (const ref process; processes) {
auto last_ts = process["ts"].get!ulong + process["dur"].get!ulong;
size_t parent_idx = 0; // index in parent_stack to which this item should be added.
for (size_t i = 0; i < parent_stack.length; i++) {
if (last_ts > parent_stack[i].last_ts) {
// The current process outlasts stack item i. Stop traversing, parent is i-1;
parent_idx = i-1;
parent_stack.length = i;
break;
}
parent_idx = i;
}
parent_stack[parent_idx].children ~= Node(&process, last_ts);
parent_stack ~= &parent_stack[parent_idx].children[$-1];
}
string duration_format_string = "%13.3f ";
output_file.writeln("Duration (ms)");
// Print the tree
void print_node(const Node* node, wchar[] indentstring, bool last_child) {
output_file.writef(duration_format_string, cast(double)(*node.json)["dur"].get!ulong / 1000);
if (last_child)
indentstring[$-1] = '└';
output_file.write(indentstring);
output_file.write("- ", (*node.json)["name"].get!string);
output_file.write(", ", (*node.json)["args"]["detail"].get!string);
output_file.writeln(", ", (*node.json)["args"]["loc"].get!string);
if (last_child)
indentstring[$-1] = ' ';
wchar[] child_indentstring = indentstring ~ " |";
foreach (i, ref child; node.children) {
print_node(&child, child_indentstring, i == node.children.length-1);
}
}
wchar[] indentstring;
//indentstring ~= ""w;
foreach (i, ref child; tree.children) {
print_node(&child, indentstring, false);
}
return 0;
}
@rikkimax
Copy link

My version:

// Run using: rdmd timeTraceTree.d <your .time-trace file>
// Outputs timetrace.txt in current dir
module timeTraceTree;
import std.stdio;
import std.file;
import std.json;
import std.range;
import std.conv;
import std.algorithm;

File outputTextFile, outputTSVFile;
static string duration_format_string = "%13.3f ";

JSONValue sourceFile;
JSONValue[] metadata; // "M"
JSONValue[] counterdata; // "C"
JSONValue[] processes; // "X"

ulong lineNumberCounter = 1;

int main(string[] args)
{
    if (args.length < 1)
        return 1;

    auto input_json = read(args[1]).to!string;
    outputTextFile = File("timetrace.txt", "w");
    outputTSVFile = File("timetrace.tsv", "w");

    {
        sourceFile = parseJSON(input_json);
        readMetaData;
        constructTree;
        constructList;
    }

    {
        outputTextFile.writeln("Timetrace: ", args[1]);
        lineNumberCounter++;

        outputTextFile.writeln("Metadata:");
        lineNumberCounter++;

        foreach (node; metadata)
        {
            outputTextFile.write("  ");
            outputTextFile.writeln(node);
            lineNumberCounter++;
        }

        outputTextFile.writeln("Duration (ms)");
        lineNumberCounter++;
    }

    foreach (i, ref child; Node.root.children)
        child.print(0, false);

    outputTSVFile.writeln("Duration\tText Line Number\tName\tLocation\tDetail");
    foreach (node; Node.all)
        outputTSVFile.writeln(node.duration, "\t", node.lineNumber, "\t",
                node.name, "\t", node.location, "\t", node.detail);

    return 0;
}

void readMetaData()
{
    auto beginningOfTime = sourceFile["beginningOfTime"].get!ulong;
    auto traceEvents = sourceFile["traceEvents"].get!(JSONValue[]);

    // Read meta data
    foreach (value; traceEvents)
    {
        switch (value["ph"].get!string)
        {
        case "M":
            metadata ~= value;
            break;
        case "C":
            counterdata ~= value;
            break;
        case "X":
            processes ~= value;
            break;
        default: //drop
        }
    }

    // process node = {"ph":"X","name": "Sema1: Module object","ts":26825,"dur":1477,"loc":"<no file>","args":{"detail": "","loc":"<no file>"},"pid":101,"tid":101},
    // Sort time processes
    multiSort!(q{a["ts"].get!ulong < b["ts"].get!ulong}, q{a["dur"].get!ulong > b["dur"].get!ulong})(
            processes);
}

void constructTree()
{
    // Build tree (to get nicer looking structure lines)
    Node*[] parent_stack = [&Node.root]; // each stack item represents the first uncompleted note of that level in the tree

    foreach (ref process; processes)
    {
        auto last_ts = process["ts"].get!ulong + process["dur"].get!ulong;
        size_t parent_idx = 0; // index in parent_stack to which this item should be added.

        foreach (i; 0 .. parent_stack.length)
        {
            if (last_ts > parent_stack[i].last_ts)
            {
                // The current process outlasts stack item i. Stop traversing, parent is i-1;
                parent_idx = i - 1;
                parent_stack.length = i;
                break;
            }

            parent_idx = i;
        }

        parent_stack[parent_idx].children ~= Node(&process, last_ts);
        parent_stack ~= &parent_stack[parent_idx].children[$ - 1];
        Node.count++;
    }
}

void constructList()
{
    size_t offset;

    Node.all.length = Node.count - 1;

    void handle(Node* root)
    {
        Node.all[offset++] = root;

        foreach (ref child; root.children)
            handle(&child);
    }

    foreach (ref child; Node.root.children)
        handle(&child);

    Node.all.sort!((a, b) => a.duration > b.duration);
}

struct Node
{
    Node[] children;
    JSONValue* json;
    ulong last_ts; // represents the last timestamp of this node (i.e. ts + dur)
    ulong lineNumber;

    string name;
    ulong duration;
    string location;
    string detail;

    this(JSONValue* json, ulong last_ts)
    {
        this.json = json;
        this.last_ts = last_ts;

        if ((*json).type == JSONType.object && "dur" in (*json))
        {
            this.duration = (*json)["dur"].get!ulong;
            this.name = (*json)["name"].get!string;
            this.location = (*json)["args"]["loc"].get!string;
            this.detail = (*json)["args"]["detail"].get!string;
        }
    }

    void print(uint indentLevel, bool last_child)
    {
        char[] identPrefix = getIdentPrefix(indentLevel, last_child);

        import std.stdio;

        if (last_child)
        {
            identPrefix[$ - 4] = ' ';
            identPrefix[$ - 3 .. $] = "\u2514";
        }
        else
            identPrefix[$ - 2 .. $] = " |";

        outputTextFile.writef(duration_format_string,
                cast(double)(*this.json)["dur"].get!ulong / 1000);

        outputTextFile.write(identPrefix);
        outputTextFile.write("- ", this.name);
        outputTextFile.write(", ", this.detail);
        outputTextFile.writeln(", ", this.location);

        this.lineNumber = lineNumberCounter;
        lineNumberCounter++;

        if (last_child)
            identPrefix[$ - 4 .. $] = ' ';

        foreach (i, ref child; this.children)
            child.print(indentLevel + 1, i == this.children.length - 1);
    }

    static Node root = Node(new JSONValue("Tree root"), ulong.max);
    static Node*[] all;
    static size_t count = 1;
}

char[] getIdentPrefix(uint indentLevel, bool last_child)
{
    static char[] buffer;

    size_t needed = ((indentLevel + 1) * 2) + (last_child * 2);

    if (buffer.length < needed)
        buffer.length = needed;

    return buffer;
}

@JohanEngelen
Copy link
Author

Thanks @rikkimax . Added to LDC here: ldc-developers/ldc#4335
(the text output indentation string is incorrect in your version, so I used my earlier un-optimized version)

@rikkimax
Copy link

Cheers!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment