Filename | /usr/local/src/github.com/foswiki/core/lib/Monitor.pm |
Statements | Executed 38 statements in 3.05ms |
Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
---|---|---|---|---|---|
7 | 7 | 2 | 62µs | 62µs | __ANON__[:119] | Monitor::
1 | 1 | 1 | 23µs | 31µs | BEGIN@40 | Monitor::
1 | 1 | 1 | 21µs | 34µs | BEGIN@103 | Monitor::
1 | 1 | 1 | 21µs | 21µs | BEGIN@113 | Monitor::
1 | 1 | 1 | 18µs | 49µs | BEGIN@280 | Monitor::
1 | 1 | 1 | 17µs | 50µs | BEGIN@97 | Monitor::
1 | 1 | 1 | 17µs | 49µs | BEGIN@231 | Monitor::
1 | 1 | 1 | 16µs | 36µs | BEGIN@41 | Monitor::
1 | 1 | 1 | 16µs | 47µs | BEGIN@212 | Monitor::
1 | 1 | 1 | 16µs | 47µs | BEGIN@98 | Monitor::
1 | 1 | 1 | 16µs | 22µs | BEGIN@104 | Monitor::
1 | 1 | 1 | 15µs | 48µs | BEGIN@281 | Monitor::
1 | 1 | 1 | 15µs | 43µs | BEGIN@232 | Monitor::
1 | 1 | 1 | 9µs | 9µs | END | Monitor::
0 | 0 | 0 | 0s | 0s | __ANON__[:120] | Monitor::
0 | 0 | 0 | 0s | 0s | __ANON__[:255] | Monitor::
0 | 0 | 0 | 0s | 0s | __ANON__[:322] | Monitor::
0 | 0 | 0 | 0s | 0s | _get_stat_info | Monitor::
0 | 0 | 0 | 0s | 0s | _mark | Monitor::
0 | 0 | 0 | 0s | 0s | _monitorMethod | Monitor::
0 | 0 | 0 | 0s | 0s | getRunTimeSoFar | Monitor::
0 | 0 | 0 | 0s | 0s | monitorMACRO | Monitor::
0 | 0 | 0 | 0s | 0s | startMonitoring | Monitor::
0 | 0 | 0 | 0s | 0s | tidytime | Monitor::
Line | State ments |
Time on line |
Calls | Time in subs |
Code |
---|---|---|---|---|---|
1 | # See bottom of file for license and copyright information | ||||
2 | |||||
3 | =begin TML | ||||
4 | |||||
5 | Monitoring package. Instrument the code like this: | ||||
6 | |||||
7 | use Monitor (); | ||||
8 | Monitor::MARK("Description of event"); | ||||
9 | Monitor::MARK("Another event"); | ||||
10 | |||||
11 | or, to monitor all the calls to a module | ||||
12 | |||||
13 | use Monitor (); | ||||
14 | Monitor::MonitorMethod('Foswiki::Users'); | ||||
15 | |||||
16 | or a function | ||||
17 | |||||
18 | use Monitor (); | ||||
19 | Monitor::MonitorMethod('Foswiki::Users', 'getCanonicalUserID'); | ||||
20 | |||||
21 | Then set the environment variable FOSWIKI_MONITOR to a perl true value, and | ||||
22 | run the script from the command line e.g: | ||||
23 | $ cd bin | ||||
24 | $ ./view -topic Myweb/MyTestTopic | ||||
25 | |||||
26 | The results will be printed to STDERR at the end of the run. Two times are | ||||
27 | shown, a time relative to the last MARK and a time relative to the first MARK | ||||
28 | (which is always set the first time this package is used). The final column | ||||
29 | is total memory. | ||||
30 | |||||
31 | NOTE: it uses /proc - so its linux specific... | ||||
32 | |||||
33 | TODO: replace FOSWIKI_MONITOR with LocalSite.cfg setting that can turn on per module instrumentation. | ||||
34 | TODO: rewrite to use Foswiki::Loggers | ||||
35 | |||||
36 | =cut | ||||
37 | |||||
38 | package Monitor; | ||||
39 | |||||
40 | 2 | 44µs | 2 | 38µs | # spent 31µs (23+8) within Monitor::BEGIN@40 which was called:
# once (23µs+8µs) by Foswiki::BEGIN@48 at line 40 # spent 31µs making 1 call to Monitor::BEGIN@40
# spent 8µs making 1 call to strict::import |
41 | 2 | 489µs | 2 | 56µs | # spent 36µs (16+20) within Monitor::BEGIN@41 which was called:
# once (16µs+20µs) by Foswiki::BEGIN@48 at line 41 # spent 36µs making 1 call to Monitor::BEGIN@41
# spent 20µs making 1 call to warnings::import |
42 | |||||
43 | 1 | 2µs | our @times; | ||
44 | 1 | 1µs | our @methodStats; | ||
45 | 1 | 1µs | our $show_percent; | ||
46 | |||||
47 | sub _get_stat_info { | ||||
48 | |||||
49 | # open and read the main stat file | ||||
50 | my $_INFO; | ||||
51 | if ( !open( $_INFO, '<', "/proc/$_[0]/stat" ) ) { | ||||
52 | |||||
53 | # Failed | ||||
54 | return { vsize => 0, rss => 0 }; | ||||
55 | } | ||||
56 | my @info = split( /\s+/, <$_INFO> ); | ||||
57 | close($_INFO); | ||||
58 | |||||
59 | # these are all the props (skip some) | ||||
60 | # pid(0) comm(1) state ppid pgrp session tty | ||||
61 | # tpgid(7) flags minflt cminflt majflt cmajflt | ||||
62 | # utime(13) stime cutime cstime counter | ||||
63 | # priority(18) timeout itrealvalue starttime vsize rss | ||||
64 | # rlim(24) startcode endcode startstack kstkesp kstkeip | ||||
65 | # signal(30) blocked sigignore sigcatch wchan | ||||
66 | |||||
67 | # get the important ones | ||||
68 | return { | ||||
69 | vsize => $info[22], | ||||
70 | rss => $info[23] * 4 | ||||
71 | }; | ||||
72 | } | ||||
73 | |||||
74 | sub _mark { | ||||
75 | my $event = shift; | ||||
76 | push( @times, [ $event, new Benchmark(), _get_stat_info($$) ] ); | ||||
77 | } | ||||
78 | |||||
79 | sub tidytime { | ||||
80 | my ( $a, $b ) = @_; | ||||
81 | my $s = timestr( timediff( $a, $b ) ); | ||||
82 | $s =~ /([\d.]+) wallclock secs.*([\d.]+) CPU/; | ||||
83 | my ( $w, $c ) = ( $1, $2 ); | ||||
84 | if ( defined $show_percent ) { | ||||
85 | $w = $w * 100.0 / $show_percent; | ||||
86 | return "$w%"; | ||||
87 | } | ||||
88 | return "wall $w CPU $c"; | ||||
89 | } | ||||
90 | |||||
91 | sub startMonitoring { | ||||
92 | require Benchmark; | ||||
93 | import Benchmark ':hireswallclock'; | ||||
94 | die $@ if $@; | ||||
95 | |||||
96 | { | ||||
97 | 2 | 46µs | 2 | 83µs | # spent 50µs (17+33) within Monitor::BEGIN@97 which was called:
# once (17µs+33µs) by Foswiki::BEGIN@48 at line 97 # spent 50µs making 1 call to Monitor::BEGIN@97
# spent 33µs making 1 call to warnings::unimport |
98 | 2 | 75µs | 2 | 79µs | # spent 47µs (16+31) within Monitor::BEGIN@98 which was called:
# once (16µs+31µs) by Foswiki::BEGIN@48 at line 98 # spent 47µs making 1 call to Monitor::BEGIN@98
# spent 31µs making 1 call to strict::unimport |
99 | |||||
100 | *MARK = \&_mark; | ||||
101 | *MonitorMethod = \&_monitorMethod; | ||||
102 | |||||
103 | 2 | 44µs | 2 | 46µs | # spent 34µs (21+12) within Monitor::BEGIN@103 which was called:
# once (21µs+12µs) by Foswiki::BEGIN@48 at line 103 # spent 34µs making 1 call to Monitor::BEGIN@103
# spent 12µs making 1 call to warnings::import |
104 | 2 | 186µs | 2 | 28µs | # spent 22µs (16+6) within Monitor::BEGIN@104 which was called:
# once (16µs+6µs) by Foswiki::BEGIN@48 at line 104 # spent 22µs making 1 call to Monitor::BEGIN@104
# spent 6µs making 1 call to strict::import |
105 | |||||
106 | #reset the loged time | ||||
107 | @times = (); | ||||
108 | @methodStats = (); | ||||
109 | } | ||||
110 | MARK('START'); | ||||
111 | } | ||||
112 | |||||
113 | # spent 21µs within Monitor::BEGIN@113 which was called:
# once (21µs+0s) by Foswiki::BEGIN@48 at line 122 | ||||
114 | 1 | 2µs | my $caller = caller; | ||
115 | 1 | 10µs | if ( $ENV{FOSWIKI_MONITOR} ) { | ||
116 | startMonitoring(); | ||||
117 | } | ||||
118 | else { | ||||
119 | 8 | 77µs | # spent 62µs within Monitor::__ANON__[/usr/local/src/github.com/foswiki/core/lib/Monitor.pm:119] which was called 7 times, avg 9µs/call:
# once (12µs+0s) by Foswiki::new at line 1898 of /usr/local/src/github.com/foswiki/core/lib/Foswiki.pm
# once (11µs+0s) by Foswiki::UI::View::view at line 356 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/UI/View.pm
# once (10µs+0s) by Foswiki::UI::View::view at line 397 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/UI/View.pm
# once (10µs+0s) by Foswiki::UI::View::view at line 393 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/UI/View.pm
# once (10µs+0s) by Foswiki::UI::View::view at line 371 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/UI/View.pm
# once (5µs+0s) by Foswiki::UI::View::view at line 403 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/UI/View.pm
# once (4µs+0s) by Foswiki::new at line 1633 of /usr/local/src/github.com/foswiki/core/lib/Foswiki.pm | ||
120 | 1 | 4µs | *MonitorMethod = sub { }; | ||
121 | } | ||||
122 | 1 | 921µs | 1 | 21µs | } # spent 21µs making 1 call to Monitor::BEGIN@113 |
123 | |||||
124 | #a bit of a hack to allow us to display the time it took to render | ||||
125 | sub getRunTimeSoFar { | ||||
126 | my $ibm = timestr( timediff( $times[$#times]->[1], $times[0]->[1] ) ); | ||||
127 | return $ibm; | ||||
128 | } | ||||
129 | |||||
130 | # spent 9µs within Monitor::END which was called:
# once (9µs+0s) by main::RUNTIME at line 0 of view | ||||
131 | 1 | 12µs | return unless ( $ENV{FOSWIKI_MONITOR} ); | ||
132 | MARK('END'); | ||||
133 | my $lastbm; | ||||
134 | my $firstbm; | ||||
135 | my %mash; | ||||
136 | |||||
137 | if ( scalar(@times) > 1 ) { | ||||
138 | my $ibm = timestr( timediff( $times[$#times]->[1], $times[0]->[1] ) ); | ||||
139 | if ( $ibm =~ /([\d.]+) wallclock/ ) { | ||||
140 | $show_percent = $1; | ||||
141 | } | ||||
142 | print STDERR "\n\n| Event | Delta | Abs | Mem |"; | ||||
143 | foreach my $bm (@times) { | ||||
144 | $firstbm = $bm unless $firstbm; | ||||
145 | if ($lastbm) { | ||||
146 | my $s = tidytime( $bm->[1], $lastbm->[1] ); | ||||
147 | my $t = tidytime( $bm->[1], $firstbm->[1] ); | ||||
148 | $s = "\n| $bm->[0] | $s | $t | $bm->[2]->{vsize} |"; | ||||
149 | print STDERR $s; | ||||
150 | } | ||||
151 | $lastbm = $bm; | ||||
152 | } | ||||
153 | print STDERR "\nTotal time: $ibm"; | ||||
154 | } | ||||
155 | |||||
156 | my %methods; | ||||
157 | foreach my $call (@methodStats) { | ||||
158 | $methods{ $call->{method} } = { | ||||
159 | count => 0, | ||||
160 | min => 99999999, | ||||
161 | max => 0, | ||||
162 | mem_min => 99999999, | ||||
163 | mem_max => 0 | ||||
164 | } | ||||
165 | unless defined( $methods{ $call->{method} } ); | ||||
166 | $methods{ $call->{method} }{count} += 1; | ||||
167 | my $diff = timediff( $call->{out}, $call->{in} ); | ||||
168 | |||||
169 | $methods{ $call->{method} }{min} = ${$diff}[0] | ||||
170 | if ( $methods{ $call->{method} }{min} > ${$diff}[0] ); | ||||
171 | $methods{ $call->{method} }{max} = ${$diff}[0] | ||||
172 | if ( $methods{ $call->{method} }{max} < ${$diff}[0] ); | ||||
173 | if ( defined( $methods{ $call->{method} }{total} ) ) { | ||||
174 | $methods{ $call->{method} }{total} = | ||||
175 | Benchmark::timesum( $methods{ $call->{method} }{total}, $diff ); | ||||
176 | } | ||||
177 | else { | ||||
178 | $methods{ $call->{method} }{total} = $diff; | ||||
179 | } | ||||
180 | my $memdiff = $call->{out_stat}{rss} - $call->{in_stat}{rss}; | ||||
181 | $methods{ $call->{method} }{mem_min} = $memdiff | ||||
182 | if ( $methods{ $call->{method} }{mem_min} > $memdiff ); | ||||
183 | $methods{ $call->{method} }{mem_max} = $memdiff | ||||
184 | if ( $methods{ $call->{method} }{mem_max} < $memdiff ); | ||||
185 | } | ||||
186 | print STDERR | ||||
187 | "\n\n| Count | Time (Min/Max) | Memory(Min/Max) | Total | Method |"; | ||||
188 | foreach my $method ( sort keys %methods ) { | ||||
189 | print STDERR "\n| " | ||||
190 | . sprintf( '%6u', $methods{$method}{count} ) . ' | ' | ||||
191 | . sprintf( '%6.3f / %6.3f', | ||||
192 | $methods{$method}{min}, | ||||
193 | $methods{$method}{max} ) | ||||
194 | . ' | ' | ||||
195 | . sprintf( '%6u / %6u', | ||||
196 | $methods{$method}{mem_min}, | ||||
197 | $methods{$method}{mem_max} ) | ||||
198 | . ' | ' | ||||
199 | . timestr( $methods{$method}{total} ) | ||||
200 | . " | $method |"; | ||||
201 | } | ||||
202 | print STDERR "\n"; | ||||
203 | } | ||||
204 | |||||
205 | #BEWARE - though this is extremely useful to show whats fast / slow in a Class, its also a potentially | ||||
206 | #deadly hack | ||||
207 | #method wrapper - http://chainsawblues.vox.com/library/posts/page/1/ | ||||
208 | sub _monitorMethod { | ||||
209 | my ( $package, $method ) = @_; | ||||
210 | |||||
211 | if ( !defined($method) ) { | ||||
212 | 2 | 266µs | 2 | 77µs | # spent 47µs (16+30) within Monitor::BEGIN@212 which was called:
# once (16µs+30µs) by Foswiki::BEGIN@48 at line 212 # spent 47µs making 1 call to Monitor::BEGIN@212
# spent 30µs making 1 call to strict::unimport |
213 | foreach my $symname ( sort keys %{"${package}::"} ) { | ||||
214 | next if ( $symname =~ /^ASSERT/ ); | ||||
215 | next if ( $symname =~ /^DEBUG/ ); | ||||
216 | next if ( $symname =~ /^UNTAINTED/ ); | ||||
217 | next if ( $symname =~ /^except/ ); | ||||
218 | next if ( $symname =~ /^otherwise/ ); | ||||
219 | next if ( $symname =~ /^finally/ ); | ||||
220 | next if ( $symname =~ /^try/ ); | ||||
221 | next if ( $symname =~ /^with/ ); | ||||
222 | _monitorMethod( $package, $symname ); | ||||
223 | } | ||||
224 | } | ||||
225 | else { | ||||
226 | my $old = ($package)->can($method); # look up along MRO | ||||
227 | return if ( !defined($old) ); | ||||
228 | |||||
229 | #print STDERR "monitoring $package :: $method)"; | ||||
230 | { | ||||
231 | 2 | 46µs | 2 | 80µs | # spent 49µs (17+32) within Monitor::BEGIN@231 which was called:
# once (17µs+32µs) by Foswiki::BEGIN@48 at line 231 # spent 49µs making 1 call to Monitor::BEGIN@231
# spent 32µs making 1 call to warnings::unimport |
232 | 2 | 366µs | 2 | 72µs | # spent 43µs (15+29) within Monitor::BEGIN@232 which was called:
# once (15µs+29µs) by Foswiki::BEGIN@48 at line 232 # spent 43µs making 1 call to Monitor::BEGIN@232
# spent 29µs making 1 call to strict::unimport |
233 | *{"${package}::$method"} = sub { | ||||
234 | |||||
235 | #Monitor::MARK("begin $package $method"); | ||||
236 | my $in_stat = _get_stat_info($$); | ||||
237 | my $in_bench = new Benchmark(); | ||||
238 | my $self = shift; | ||||
239 | my @result = $self->$old(@_); | ||||
240 | my $out_bench = new Benchmark(); | ||||
241 | |||||
242 | #Monitor::MARK("end $package $method => ".($result||'undef')); | ||||
243 | my $out_stat = _get_stat_info($$); | ||||
244 | push( | ||||
245 | @methodStats, | ||||
246 | { | ||||
247 | method => "${package}::$method", | ||||
248 | in => $in_bench, | ||||
249 | in_stat => $in_stat, | ||||
250 | out => $out_bench, | ||||
251 | out_stat => $out_stat | ||||
252 | } | ||||
253 | ); | ||||
254 | return wantarray ? @result : $result[0]; | ||||
255 | } | ||||
256 | } | ||||
257 | } | ||||
258 | } | ||||
259 | |||||
260 | #BEWARE - as above | ||||
261 | #provide more detailed information about a specific MACRO handler | ||||
262 | #this Presumes that the macro function is defined as 'sub Foswiki::MACRO' and can be loaded from 'Foswiki::Macros::MACRO' | ||||
263 | # | ||||
264 | # logs, session GET and POST params, MACRO and MACRO params and timing stats | ||||
265 | # | ||||
266 | # the $logFunction is an optional reference to a writeLog($name, hash_ref_of_values_to_log) (see DebugLogPlugin for an example) | ||||
267 | sub monitorMACRO { | ||||
268 | my $package = 'Foswiki'; | ||||
269 | my $method = shift; | ||||
270 | my $logLevel = shift; | ||||
271 | my $logFunction = shift; | ||||
272 | |||||
273 | eval "require Foswiki::Macros::$method"; | ||||
274 | return if ($@); | ||||
275 | my $old = ($package)->can($method); # look up along MRO | ||||
276 | return if ( !defined($old) ); | ||||
277 | |||||
278 | #print STDERR "monitoring $package :: $method)"; | ||||
279 | { | ||||
280 | 2 | 51µs | 2 | 80µs | # spent 49µs (18+31) within Monitor::BEGIN@280 which was called:
# once (18µs+31µs) by Foswiki::BEGIN@48 at line 280 # spent 49µs making 1 call to Monitor::BEGIN@280
# spent 31µs making 1 call to warnings::unimport |
281 | 2 | 405µs | 2 | 81µs | # spent 48µs (15+33) within Monitor::BEGIN@281 which was called:
# once (15µs+33µs) by Foswiki::BEGIN@48 at line 281 # spent 48µs making 1 call to Monitor::BEGIN@281
# spent 33µs making 1 call to strict::unimport |
282 | *{"${package}::$method"} = sub { | ||||
283 | my ( $session, $params, $topicObject ) = @_; | ||||
284 | |||||
285 | #Monitor::MARK("begin $package $method"); | ||||
286 | my $in_stat = _get_stat_info($$); | ||||
287 | my $in_bench = new Benchmark(); | ||||
288 | my @result = $session->$old( $params, $topicObject, @_ ); | ||||
289 | my $out_bench = new Benchmark(); | ||||
290 | |||||
291 | #Monitor::MARK("end $package $method => ".($result||'undef')); | ||||
292 | my $out_stat = _get_stat_info($$); | ||||
293 | |||||
294 | my $stat_hash = { | ||||
295 | method => "${package}::$method", | ||||
296 | in => $in_bench, | ||||
297 | in_stat => $in_stat, | ||||
298 | out => $out_bench, | ||||
299 | out_stat => $out_stat | ||||
300 | }; | ||||
301 | push( @methodStats, $stat_hash ); | ||||
302 | |||||
303 | if ( defined($logFunction) ) | ||||
304 | { #this is effectivly the same as $logLevel>0 | ||||
305 | #lets not make the %stat_hash huge, as its kept in memory | ||||
306 | my %hashToLog = %$stat_hash; | ||||
307 | $hashToLog{params} = $params; | ||||
308 | |||||
309 | #if we're logging this detail of information, we're less worried about performance. | ||||
310 | #numbers _will be off_ if there are nested MACRO's being logged | ||||
311 | $hashToLog{macroTime} = | ||||
312 | timestr( timediff( $stat_hash->{out}, $stat_hash->{in} ) ); | ||||
313 | $hashToLog{macroMemory} = | ||||
314 | $stat_hash->{out_stat}{rss} - $stat_hash->{in_stat}{rss}; | ||||
315 | |||||
316 | if ( $logLevel > 1 ) { | ||||
317 | $hashToLog{result} = wantarray ? @result : $result[0]; | ||||
318 | } | ||||
319 | &$logFunction( $method, \%hashToLog ); | ||||
320 | } | ||||
321 | return wantarray ? @result : $result[0]; | ||||
322 | } | ||||
323 | } | ||||
324 | } | ||||
325 | |||||
326 | 1 | 6µs | 1; | ||
327 | __END__ |