1 module tagion.tools.tprofview;
2 
3 import std.algorithm;
4 import std.conv : to;
5 import std.exception : assumeWontThrow;
6 import std.file : exists;
7 import std.format;
8 import std.getopt;
9 import std.range;
10 import std.stdio;
11 import std.traits;
12 import std.typecons;
13 import std.utf;
14 import tagion.tools.Basic;
15 import tagion.tools.revision;
16 
17 mixin Main!(_main);
18 
19 struct ProfLine {
20     uint num_calls;
21     uint tree_time;
22     uint func_time;
23     double per_calls;
24     string func_name;
25     this(const uint num_calls, const uint tree_time, const uint func_time, string func_name) pure @nogc {
26         this.num_calls = num_calls;
27         this.tree_time = tree_time;
28         this.func_time = func_time;
29         per_calls = double(tree_time) / double(num_calls);
30         this.func_name = func_name;
31     }
32 
33     string toString() const {
34         return format("%8d   %8d   %8d   %8.3f    %s", num_calls, tree_time, func_time,
35                 per_calls,
36                 func_name);
37     }
38 
39 }
40 
41 enum ProfSort {
42     calls,
43     tree,
44     func,
45     percalls,
46 }
47 
48 struct ProfInfo {
49     string[] head;
50     ProfLine[] proflines;
51     void display(ProfSort sort_option)(const uint num) const {
52         scope list_sort = proflines.dup
53             .sort!((a, b) => a.tupleof[sort_option] > b.tupleof[sort_option]);
54         head.each!writeln;
55         list_sort
56             .take(num)
57             .each!writeln;
58     }
59 
60     void display(const ProfSort sort_option, const uint num) const {
61     SortOption:
62         final switch (sort_option) {
63             static foreach (E; EnumMembers!ProfSort) {
64         case E:
65                 display!E(num);
66                 break SortOption;
67             }
68         }
69     }
70 }
71 
72 ProfInfo loadProf(ref File fin, const bool verbose) {
73     ProfInfo result;
74     bool validUTF(Line)(Line line) nothrow {
75         try {
76             line.value.validate;
77         }
78         catch (Exception e) {
79             if (verbose) {
80                 assumeWontThrow({ writeln(e.msg); writefln("%d:%s", line.index, line.value); }());
81             }
82             return false;
83         }
84         return true;
85     }
86 
87     auto prof_file = fin.byLine // .map!(l => l.filter!(c => c.isValidDchar))
88         .enumerate(1)
89         .filter!(l => validUTF(l))
90         .map!(l => l.value.toUTF8);
91     prof_file
92         .find!(l => l.startsWith("========")); //, "==")); //, "======"));
93     result.head = prof_file.take(4).map!(l => l.idup).array;
94     result.proflines = prof_file
95         .map!(l => tuple(
96                 l.split.take(3).map!(n => n.to!uint).array,
97                 l.split.drop(4).join(" ")))
98         .filter!(prof => prof[1].length > 0)
99         .map!(prof => ProfLine(prof[0][0], prof[0][1], prof[0][2], prof[1].idup))
100         .array;
101     return result;
102 }
103 
104 int _main(string[] args) {
105     immutable program = args[0];
106 
107     bool version_switch;
108     uint number_tobe_listed = 10;
109     string trace_file = "trace.log";
110     ProfSort prof_sort = ProfSort.tree;
111     bool verbose_switch;
112     auto main_args = getopt(args,
113             std.getopt.config.caseSensitive,
114             std.getopt.config.bundling,
115             "version", "display the version", &version_switch,
116             "l", format("Number to be listed (default %d)", number_tobe_listed), &number_tobe_listed,
117             "s", format("Sort flag %s (default %s)", [EnumMembers!ProfSort], prof_sort), &prof_sort,
118     "v|verbose", "verbose switch", &verbose_switch,
119     );
120 
121     if (version_switch) {
122         revision_text.writeln;
123         return 0;
124     }
125 
126     if (main_args.helpWanted) {
127         defaultGetoptPrinter(
128                 [
129                 revision_text,
130                 "Documentation: https://tagion.org/",
131                 "",
132                 "Usage:",
133                 format("%s [<option>...] <trace-file> ", program),
134                 "",
135                 "<option>:",
136 
137                 ].join("\n"),
138                 main_args.options);
139         return 0;
140     }
141     if (args.length > 1) {
142         trace_file = args[1];
143         writefln("trace file %s", trace_file);
144     }
145     try {
146         auto fin = File(trace_file, "r");
147         scope (exit) {
148             fin.close;
149         }
150         const prof_list = loadProf(fin, verbose_switch);
151         prof_list.display(prof_sort, number_tobe_listed);
152     }
153     catch (Exception e) {
154         stderr.writeln(e.msg);
155         return 1;
156     }
157     return 0;
158 }