Filename | /var/www/foswikidev/core/lib/Monitor.pm |
Statements | Executed 38 statements in 2.81ms |
Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
---|---|---|---|---|---|
1 | 1 | 1 | 20µs | 20µs | BEGIN@113 | Monitor::
1 | 1 | 1 | 20µs | 42µs | BEGIN@40 | Monitor::
1 | 1 | 1 | 18µs | 36µs | BEGIN@212 | Monitor::
7 | 7 | 2 | 17µs | 17µs | __ANON__[:119] | Monitor::
1 | 1 | 1 | 17µs | 33µs | BEGIN@97 | Monitor::
1 | 1 | 1 | 16µs | 32µs | BEGIN@231 | Monitor::
1 | 1 | 1 | 16µs | 31µs | BEGIN@280 | Monitor::
1 | 1 | 1 | 13µs | 20µs | BEGIN@103 | Monitor::
1 | 1 | 1 | 13µs | 20µs | BEGIN@41 | Monitor::
1 | 1 | 1 | 12µs | 29µs | BEGIN@104 | Monitor::
1 | 1 | 1 | 11µs | 27µs | BEGIN@98 | Monitor::
1 | 1 | 1 | 11µs | 26µs | BEGIN@232 | Monitor::
1 | 1 | 1 | 11µs | 26µs | BEGIN@281 | Monitor::
1 | 1 | 1 | 6µs | 6µ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 | 41µs | 2 | 64µs | # spent 42µs (20+22) within Monitor::BEGIN@40 which was called:
# once (20µs+22µs) by Foswiki::BEGIN@50 at line 40 # spent 42µs making 1 call to Monitor::BEGIN@40
# spent 22µs making 1 call to strict::import |
41 | 2 | 479µs | 2 | 27µs | # spent 20µs (13+7) within Monitor::BEGIN@41 which was called:
# once (13µs+7µs) by Foswiki::BEGIN@50 at line 41 # spent 20µs making 1 call to Monitor::BEGIN@41
# spent 7µs making 1 call to warnings::import |
42 | |||||
43 | 1 | 400ns | our @times; | ||
44 | 1 | 100ns | our @methodStats; | ||
45 | 1 | 200ns | 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 =~ m/([\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 | 43µs | 2 | 50µs | # spent 33µs (17+17) within Monitor::BEGIN@97 which was called:
# once (17µs+17µs) by Foswiki::BEGIN@50 at line 97 # spent 33µs making 1 call to Monitor::BEGIN@97
# spent 17µs making 1 call to warnings::unimport |
98 | 2 | 68µs | 2 | 42µs | # spent 27µs (11+15) within Monitor::BEGIN@98 which was called:
# once (11µs+15µs) by Foswiki::BEGIN@50 at line 98 # spent 27µs making 1 call to Monitor::BEGIN@98
# spent 15µs making 1 call to strict::unimport |
99 | |||||
100 | *MARK = \&_mark; | ||||
101 | *MonitorMethod = \&_monitorMethod; | ||||
102 | |||||
103 | 2 | 35µs | 2 | 26µs | # spent 20µs (13+6) within Monitor::BEGIN@103 which was called:
# once (13µs+6µs) by Foswiki::BEGIN@50 at line 103 # spent 20µs making 1 call to Monitor::BEGIN@103
# spent 6µs making 1 call to warnings::import |
104 | 2 | 195µs | 2 | 46µs | # spent 29µs (12+17) within Monitor::BEGIN@104 which was called:
# once (12µs+17µs) by Foswiki::BEGIN@50 at line 104 # spent 29µs making 1 call to Monitor::BEGIN@104
# spent 17µ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 20µs within Monitor::BEGIN@113 which was called:
# once (20µs+0s) by Foswiki::BEGIN@50 at line 122 | ||||
114 | 1 | 3µs | my $caller = caller; | ||
115 | 1 | 12µs | if ( $ENV{FOSWIKI_MONITOR} ) { | ||
116 | startMonitoring(); | ||||
117 | } | ||||
118 | else { | ||||
119 | 8 | 31µs | # spent 17µs within Monitor::__ANON__[/var/www/foswikidev/core/lib/Monitor.pm:119] which was called 7 times, avg 2µs/call:
# once (5µs+0s) by Foswiki::UI::View::view at line 463 of /var/www/foswikidev/core/lib/Foswiki/UI/View.pm
# once (3µs+0s) by Foswiki::UI::View::view at line 441 of /var/www/foswikidev/core/lib/Foswiki/UI/View.pm
# once (3µs+0s) by Foswiki::UI::View::view at line 467 of /var/www/foswikidev/core/lib/Foswiki/UI/View.pm
# once (2µs+0s) by Foswiki::new at line 2269 of /var/www/foswikidev/core/lib/Foswiki.pm
# once (2µs+0s) by Foswiki::new at line 1953 of /var/www/foswikidev/core/lib/Foswiki.pm
# once (2µs+0s) by Foswiki::UI::View::view at line 473 of /var/www/foswikidev/core/lib/Foswiki/UI/View.pm
# once (1µs+0s) by Foswiki::UI::View::view at line 426 of /var/www/foswikidev/core/lib/Foswiki/UI/View.pm | ||
120 | 1 | 2µs | *MonitorMethod = sub { }; | ||
121 | } | ||||
122 | 1 | 820µs | 1 | 20µs | } # spent 20µ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 6µs within Monitor::END which was called:
# once (6µs+0s) by main::RUNTIME at line 0 of view | ||||
131 | 1 | 6µ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 =~ m/([\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 | 239µs | 2 | 54µs | # spent 36µs (18+18) within Monitor::BEGIN@212 which was called:
# once (18µs+18µs) by Foswiki::BEGIN@50 at line 212 # spent 36µs making 1 call to Monitor::BEGIN@212
# spent 18µs making 1 call to strict::unimport |
213 | foreach my $symname ( sort keys %{"${package}::"} ) { | ||||
214 | next if ( $symname =~ m/^ASSERT/ ); | ||||
215 | next if ( $symname =~ m/^DEBUG/ ); | ||||
216 | next if ( $symname =~ m/^UNTAINTED/ ); | ||||
217 | next if ( $symname =~ m/^except/ ); | ||||
218 | next if ( $symname =~ m/^otherwise/ ); | ||||
219 | next if ( $symname =~ m/^finally/ ); | ||||
220 | next if ( $symname =~ m/^try/ ); | ||||
221 | next if ( $symname =~ m/^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 | 43µs | 2 | 48µs | # spent 32µs (16+16) within Monitor::BEGIN@231 which was called:
# once (16µs+16µs) by Foswiki::BEGIN@50 at line 231 # spent 32µs making 1 call to Monitor::BEGIN@231
# spent 16µs making 1 call to warnings::unimport |
232 | 2 | 353µs | 2 | 41µs | # spent 26µs (11+15) within Monitor::BEGIN@232 which was called:
# once (11µs+15µs) by Foswiki::BEGIN@50 at line 232 # spent 26µs making 1 call to Monitor::BEGIN@232
# spent 15µ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 | 42µs | 2 | 47µs | # spent 31µs (16+16) within Monitor::BEGIN@280 which was called:
# once (16µs+16µs) by Foswiki::BEGIN@50 at line 280 # spent 31µs making 1 call to Monitor::BEGIN@280
# spent 16µs making 1 call to warnings::unimport |
281 | 2 | 396µs | 2 | 40µs | # spent 26µs (11+15) within Monitor::BEGIN@281 which was called:
# once (11µs+15µs) by Foswiki::BEGIN@50 at line 281 # spent 26µs making 1 call to Monitor::BEGIN@281
# spent 15µ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 | 5µs | 1; | ||
327 | __END__ |