]> gitweb.factorcode.org Git - factor.git/blob - basis/tools/profiler/sampling/sampling.factor
4e02eea05e0c6ffa27e7c3d4a1f28061e8ebb277
[factor.git] / basis / tools / profiler / sampling / sampling.factor
1 ! (c)2011 Joe Groff bsd license
2 USING: accessors assocs combinators combinators.short-circuit
3 continuations formatting fry generalizations hashtables.identity
4 io kernel kernel.private layouts locals math math.parser
5 math.vectors memory namespaces prettyprint sequences
6 sequences.generalizations sets sorting ;
7 IN: tools.profiler.sampling
8
9 <PRIVATE
10 PRIMITIVE: (get-samples) ( -- samples/f )
11 PRIMITIVE: profiling ( n -- )
12 PRIVATE>
13
14 SYMBOL: samples-per-second
15
16 samples-per-second [ 1,000 ] initialize
17
18 <PRIVATE
19 SYMBOL: raw-profile-data
20 CONSTANT: ignore-words
21     { signal-handler leaf-signal-handler profiling minor-gc }
22
23 : ignore-word? ( word -- ? ) ignore-words member? ; inline
24 PRIVATE>
25
26 : most-recent-profile-data ( -- profile-data )
27     raw-profile-data get-global [ "No profile data" throw ] unless* ;
28
29 : profile ( quot -- )
30     samples-per-second get-global profiling
31     [ 0 profiling (get-samples) raw-profile-data set-global ]
32     [ ] cleanup ; inline
33
34 : total-sample-count ( sample -- count ) 0 swap nth ;
35 : gc-sample-count ( sample -- count ) 1 swap nth ;
36 : jit-sample-count ( sample -- count ) 2 swap nth ;
37 : foreign-sample-count ( sample -- count ) 3 swap nth ;
38 : foreign-thread-sample-count ( sample -- count ) 4 swap nth ;
39 : sample-counts-slice ( sample -- counts ) 5 head-slice ;
40
41 : sample-thread ( sample -- thread ) 5 swap nth ;
42 : sample-callstack ( sample -- array ) 6 swap nth ;
43 : unclip-callstack ( sample -- sample' callstack-top )
44     clone 6 over [ unclip-last swap ] change-nth ;
45
46 : samples>time ( samples -- seconds )
47     samples-per-second get-global / ;
48
49 : total-time* ( profile-data -- n )
50     [ total-sample-count ] map-sum samples>time ;
51
52 : gc-time* ( profile-data -- n )
53     [ gc-sample-count ] map-sum samples>time ;
54
55 : foreign-time* ( profile-data -- n )
56     [ foreign-sample-count ] map-sum samples>time ;
57
58 : foreign-thread-time* ( profile-data -- n )
59     [ foreign-thread-sample-count ] map-sum samples>time ;
60
61 : total-time ( -- n )
62     most-recent-profile-data total-time* ;
63
64 : gc-time ( -- n )
65     most-recent-profile-data gc-time* ;
66
67 : foreign-time ( -- n )
68     most-recent-profile-data foreign-time* ;
69
70 : foreign-thread-time ( -- n )
71     most-recent-profile-data foreign-thread-time* ;
72
73 TUPLE: profile-node
74     total-time gc-time jit-time foreign-time foreign-thread-time children
75     depth ;
76
77 <PRIVATE
78
79 : collect-threads ( samples -- by-thread )
80     [ sample-thread ] collect-by ;
81
82 : time-per-thread ( -- n )
83     most-recent-profile-data collect-threads [ total-time* ] assoc-map ;
84
85 : leaf-callstack? ( callstack -- ? )
86     [ ignore-word? ] all? ;
87
88 CONSTANT: zero-counts { 0 0 0 0 0 }
89
90 : sum-counts ( samples -- times )
91     zero-counts [ sample-counts-slice v+ ] reduce ;
92
93 : <profile-node> ( times children depth -- node )
94     [ 5 firstn [ samples>time ] 5 napply ] 2dip profile-node boa ;
95
96 : <profile-root-node> ( samples collector-quot -- node )
97     [ sum-counts ] swap bi 0 <profile-node> ; inline
98
99 :: (collect-subtrees) ( samples max-depth depth child-quot: ( samples -- child ) -- children )
100     max-depth depth > [
101         samples [ sample-callstack leaf-callstack? ] reject
102         [ f ] [ child-quot call ] if-empty
103     ] [ f ] if ; inline
104
105 :: collect-tops ( samples max-depth depth -- node )
106     samples H{ } clone [
107         '[ unclip-callstack _ push-at ] each
108     ] keep [
109         [ sum-counts ]
110         [ max-depth depth [ max-depth depth 1 + collect-tops ] (collect-subtrees) ] bi
111         depth <profile-node>
112     ] assoc-map ;
113
114 : redundant-root-node? ( assoc -- ? )
115     {
116         [ children>> assoc-size 1 = ]
117         [ children>> values first children>> ]
118         [ [ total-time>> ] [ children>> values first total-time>> ] bi = ]
119     } 1&& ;
120
121 : trim-root ( root -- root' )
122     dup redundant-root-node? [ children>> values first trim-root ] when ;
123
124 :: (top-down) ( max-depth profile-data depth -- tree )
125     profile-data collect-threads
126     [ [ max-depth depth collect-tops ] <profile-root-node> trim-root ] assoc-map ;
127
128 PRIVATE>
129
130 : top-down-max-depth* ( max-depth profile-data -- tree )
131     0 (top-down) ;
132
133 : top-down-max-depth ( max-depth -- tree )
134     most-recent-profile-data top-down-max-depth* ;
135
136 : top-down* ( profile-data -- tree )
137     [ most-positive-fixnum ] dip top-down-max-depth* ;
138
139 : top-down ( -- tree )
140     most-positive-fixnum top-down-max-depth ;
141
142 <PRIVATE
143
144 :: counts+at ( key assoc sample -- )
145     key assoc [ zero-counts or sample sample-counts-slice v+ ] change-at ;
146
147 :: collect-flat ( samples -- flat )
148     IH{ } clone :> per-word-samples
149     samples [| sample |
150         sample sample-callstack members [ ignore-word? ] reject [
151             per-word-samples sample counts+at
152         ] each
153     ] each
154     per-word-samples [ f 0 <profile-node> ] assoc-map ;
155
156 : redundant-flat-node? ( child-node root-node -- ? )
157     [ total-time>> ] same? ;
158
159 : trim-flat ( root-node -- root-node' )
160     dup '[ [ nip _ redundant-flat-node? ] assoc-reject ] change-children ;
161
162 PRIVATE>
163
164 : flat* ( profile-data -- flat )
165     collect-threads
166     [ [ collect-flat ] <profile-root-node> trim-flat ] assoc-map ;
167
168 : flat ( -- flat )
169     most-recent-profile-data flat* ;
170
171 <PRIVATE
172
173 : nth-or-last ( n seq -- elt )
174     [ drop f ] [
175         2dup bounds-check? [ nth ] [ nip last ] if
176     ] if-empty ;
177
178 :: collect-cross-section ( samples depth -- cross-section )
179     IH{ } clone :> per-word-samples
180     samples [| sample |
181         depth sample sample-callstack [ ignore-word? ] trim-tail nth-or-last :> word
182         word [
183             word per-word-samples sample counts+at
184         ] when
185     ] each
186     per-word-samples [ f depth <profile-node> ] assoc-map ;
187
188 PRIVATE>
189
190 :: cross-section* ( depth profile-data -- tree )
191     profile-data collect-threads
192     [ [ depth collect-cross-section ] <profile-root-node> ] assoc-map ;
193
194 : cross-section ( depth -- tree )
195     most-recent-profile-data cross-section* ;
196
197 <PRIVATE
198
199 : depth. ( depth -- )
200     [ "  " write ] times ;
201
202 : by-total-time ( nodes -- nodes' )
203     >alist [ second total-time>> ] inv-sort-with ;
204
205 : duration. ( duration -- )
206     1000 * "%9.1f" printf ;
207
208 : percentage. ( num denom -- )
209     [ 100 * ] dip /f "%6.2f" printf ;
210
211 DEFER: (profile.)
212
213 :: times. ( node -- )
214     node {
215         [ depth>> number>string 4 CHAR: \s pad-head write bl ]
216         [ total-time>> duration. bl ]
217         [ [ gc-time>> ] [ total-time>> ] bi percentage. bl ]
218         [ [ jit-time>> ] [ total-time>> ] bi percentage. bl ]
219         [ [ foreign-time>> ] [ total-time>> ] bi percentage. bl ]
220         [ [ foreign-thread-time>> ] [ total-time>> ] bi percentage. bl ]
221     } cleave ;
222
223 :: (profile-node.) ( word node depth -- )
224     node times.
225     depth depth.
226     word pprint-short nl
227     node children>> depth 1 + (profile.) ;
228
229 : (profile.) ( nodes depth -- )
230     [ by-total-time ] dip '[ _ (profile-node.) ] assoc-each ;
231
232 : profile-heading. ( -- )
233     "depth   time ms  GC %  JIT %  FFI %   FT %" print ;
234    ! NNNN XXXXXXX.X XXXX.X XXXX.X XXXX.X XXXX.X | | foo
235
236 PRIVATE>
237
238 : profile. ( tree -- )
239     profile-heading.
240     [ 0 (profile-node.) ] assoc-each ;