1+ import FlameGraphs
2+
3+ using Base. StackTraces: StackFrame
4+ using FlameGraphs. LeftChildRightSiblingTrees: Node, addchild
5+ using Core. Compiler. Timings: Timing
6+
7+ """
8+ flatten_times(timing::Core.Compiler.Timings.Timing; tmin_secs = 0.0)
9+
10+ Flatten the execution graph of Timings returned from `@snoopi_deep` into a Vector of pairs,
11+ with the exclusive time for each invocation of type inference, skipping any frames that
12+ took less than `tmin_secs` seconds. Results are sorted by time.
13+ """
14+ function flatten_times(timing:: Core.Compiler.Timings.Timing ; tmin_secs = 0.0 )
15+ out = Pair{Float64,Core. Compiler. Timings. InferenceFrameInfo}[]
16+ frontier = [timing]
17+ while ! isempty(frontier)
18+ t = popfirst!(frontier)
19+ exclusive_time = (t. time / 1e9 )
20+ if exclusive_time >= tmin_secs
21+ push!(out, exclusive_time => t. mi_info)
22+ end
23+ append!(frontier, t. children)
24+ end
25+ return sort(out; by= tl-> tl[1 ])
26+ end
27+
28+ struct InclusiveTiming
29+ mi_info:: Core.Compiler.Timings.InferenceFrameInfo
30+ inclusive_time:: UInt64
31+ start_time:: UInt64
32+ children:: Vector{InclusiveTiming}
33+ end
34+
35+ inclusive_time(t:: InclusiveTiming ) = t. inclusive_time
36+
37+ function build_inclusive_times(t:: Timing )
38+ child_times = InclusiveTiming[
39+ build_inclusive_times(child)
40+ for child in t. children
41+ ]
42+ incl_time = t. time + sum(inclusive_time, child_times; init= UInt64(0 ))
43+ return InclusiveTiming(t. mi_info, incl_time, t. start_time, child_times)
44+ end
45+
46+ """
47+ to_flamegraph(t::Core.Compiler.Timings.Timing; tmin_secs=0.0)
48+ to_flamegraph(t::SnoopCompile.InclusiveTiming; tmin_secs=0.0)
49+
50+ Convert the call tree of inference timings returned from `@snoopi_deep` into a FlameGraph.
51+ Returns a FlameGraphs.FlameGraph structure that represents the timing trace recorded for
52+ type inference.
53+
54+ Frames that take less than `tmin_secs` seconds of _inclusive time_ will not be included
55+ in the resultant FlameGraph (meaning total time including it and all of its children).
56+ This can be helpful if you have a very big profile, to save on processing time.
57+
58+ # Examples
59+ ```julia
60+ julia> timing = SnoopCompileCore.@snoopi_deep begin
61+ @eval sort(rand(100)) # Evaluate some code and profile julia's type inference
62+ end;
63+
64+ julia> fg = SnoopCompile.to_flamegraph(timing)
65+ Node(FlameGraphs.NodeData(ROOT() at typeinfer.jl:70, 0x00, 0:15355670))
66+
67+ julia> ProfileView.view(fg); # Display the FlameGraph in a package that supports it
68+
69+ julia> fg = SnoopCompile.to_flamegraph(timing; tmin_secs=0.0001) # Skip very tiny frames
70+ Node(FlameGraphs.NodeData(ROOT() at typeinfer.jl:70, 0x00, 0:15355670))
71+ ```
72+
73+ NOTE: This function must touch every frame in the provided `Timing` to build inclusive
74+ timing information (`InclusiveTiming`). If you have a very large profile, and you plan to
75+ call this function multiple times (say with different values for `tmin_secs`), you can save
76+ some intermediate time by first calling [`build_inclusive_times(t)`](@ref), only once,
77+ and then passing in the `InclusiveTiming` object for all subsequent calls.
78+ """
79+ function to_flamegraph(t:: Timing ; tmin_secs = 0.0 )
80+ it = build_inclusive_times(t)
81+ to_flamegraph(it; tmin_secs= tmin_secs)
82+ end
83+
84+ function to_flamegraph(to:: InclusiveTiming ; tmin_secs = 0.0 )
85+ tmin_ns = UInt64(round(tmin_secs * 1e9 ))
86+
87+ # Compute a "root" frame for the top-level node, to cover the whole profile
88+ node_data = _flamegraph_frame(to, to. start_time; toplevel= true )
89+ root = Node(node_data)
90+ return _build_flamegraph!(root, to, to. start_time, tmin_ns)
91+ end
92+ function _build_flamegraph!(root, to:: InclusiveTiming , start_ns, tmin_ns)
93+ for child in to. children
94+ if child. inclusive_time > tmin_ns
95+ node_data = _flamegraph_frame(child, start_ns; toplevel= false )
96+ node = addchild(root, node_data)
97+ _build_flamegraph!(node, child, start_ns, tmin_ns)
98+ end
99+ end
100+ return root
101+ end
102+
103+ function frame_name(mi_info:: Core.Compiler.Timings.InferenceFrameInfo )
104+ frame_name(mi_info. mi:: Core.Compiler.MethodInstance )
105+ end
106+ function frame_name(mi:: Core.Compiler.MethodInstance )
107+ frame_name(mi. def. name, mi. specTypes)
108+ end
109+ # Special printing for Type Tuples so they're less ugly in the FlameGraph
110+ function frame_name(name, :: Type{TT} ) where TT<: Tuple
111+ io = IOBuffer()
112+ Base. show_tuple_as_call(io, name, TT)
113+ v = String(take!(io))
114+ return v
115+ end
116+
117+ # NOTE: The "root" node doesn't cover the whole profile, because it's only the _complement_
118+ # of the inference times (so it's missing the _overhead_ from the measurement).
119+ # SO we need to manually create a root node that covers the whole thing.
120+ function max_end_time(t:: InclusiveTiming )
121+ return maximum(child. start_time + child. inclusive_time for child in t. children;
122+ init = UInt64(t. start_time + t. inclusive_time))
123+ end
124+
125+ # Make a flat frame for this Timing
126+ function _flamegraph_frame(to:: InclusiveTiming , start_ns; toplevel)
127+ # TODO : Use a better conversion to a StackFrame so this contains the right kind of data
128+ mi = to. mi_info. mi
129+ tt = Symbol(frame_name(to. mi_info))
130+ sf = StackFrame(tt, mi. def. file, mi. def. line, mi, false , false , UInt64(0x0 ))
131+ status = 0x0 # "default" status -- See FlameGraphs.jl
132+ start = to. start_time - start_ns
133+ if toplevel
134+ # Compute a range over the whole profile for the top node.
135+ range = Int(start) : Int(max_end_time(to) - start_ns)
136+ else
137+ range = Int(start) : Int(start + to. inclusive_time)
138+ end
139+ return FlameGraphs. NodeData(sf, status, range)
140+ end
0 commit comments