-
Notifications
You must be signed in to change notification settings - Fork 121
Expand file tree
/
Copy pathmlprof
More file actions
executable file
·237 lines (204 loc) · 7.29 KB
/
Copy pathmlprof
File metadata and controls
executable file
·237 lines (204 loc) · 7.29 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
#!/usr/bin/env tclsh
#
# MLPROF, profile a given modulecmd execution
# Copyright (C) 2019-2025 Xavier Delaruelle
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
##########################################################################
proc reportUsage {} {
puts "Usage: $::argv0 \[option\] mode versiontag args...
Profile a given modulecmd execution
Modes:
reportX Report profiling of top X called procedures
print Print full profiling report
Define modulecmd.tcl run:
versiontag Select modulecmd.tcl version to profile
args Set modulecmd.tcl arguments to define execution to profile
Options:
-h, --help Show this help message and exit
Examples:
$::argv0 report10 v5.0.1 avail
$::argv0 report5 v4.5.3 load mymod/3.0
$::argv0 print v5.1.1 list"
}
proc sgr {sgrcode str} {
return "\033\[${sgrcode}m$str\033\[0m"
}
proc reportError {str} {
puts "[sgr {1;31} ERROR]: $str"
}
# parse arguments
set hintmsg "\n Try '$argv0 --help' for more information."
# treat options
set firstarg [lindex $argv 0]
switch -glob -- $firstarg {
-h - --help {
reportUsage
exit 0
}
-* {
reportError "Invalid option '$firstarg'$hintmsg"
exit 1
}
}
if {$argc < 2} {
reportError "Unexpected number of arguments$hintmsg"
exit 1
}
# how to report collected data
set mode $firstarg
# get modulecmd version to run
set tag [lindex $argv 1]
# fix arguments to run modulecmd.tcl via source command
set argv [lrange [lreplace $argv 1 1 sh] 1 end]
# clean argument management code prior profiling
rename reportUsage {}
rename sgr {}
rename reportError {}
unset hintmsg
# inhibit exit command to get through the whole modulecmd.tcl script and
# and finish here to compute profiling result
rename ::exit ::__exit
proc exit {{returnCode 0}} {}
# also inhibit puts to ensure nothing is printed during execution
rename ::puts ::__puts
##nagelfar syntax puts r 0
proc puts {args} {}
# initialize profiling
package require profiler
# Rewrite profProc procedure of profiler Tcllib module as original procedure
# reset procedure it profiles each time it is defined, which does not suit
# our needs for modulecmd.tcl as several procedures are rewritten along the
# process. So here pre-existing statistic counters are not reset during proc
# profiling initialization.
rename ::profiler::profProc ::profiler::__profProc
proc ::profiler::profProc {name arglist body} {
# save pre-existing counters for proc name
variable callCount
variable compileTime
variable totalRuntime
variable descendantTime
variable statTime
variable enabled
variable paused
# Get the fully qualified name of the proc
set ns [uplevel [list namespace current]]
# If the proc call did not happen at the global context and it did not
# have an absolute namespace qualifier, we have to prepend the current
# namespace to the command name
if { ![string equal $ns "::"] } {
if { ![string match "::*" $name] } {
set name "${ns}::${name}"
}
}
if { ![string match "::*" $name] } {
set name "::$name"
}
# Set up accounting for this procedure
# Change for mlprof: keep existing stats if any
if {![info exists callCount($name)]} {
set callCount($name) 0
set compileTime($name) 0
set totalRuntime($name) 0
set descendantTime($name) 0
set statTime($name) {}
}
set enabled($name) [expr {!$paused}]
if {[package vsatisfies [package provide Tcl] 8.4]} {
uplevel 1 [list ::_oldProc $name $arglist $body]
##nagelfar subcmd+ {trace add execution} {enter leave}
trace add execution $name {enter leave} \
[list ::profiler::TraceHandler $name]
} else {
uplevel 1 [list ::_oldProc ${name}ORIG $arglist $body]
uplevel 1 [list interp alias {} $name {} ::profiler::Handler $name]
}
return
}
# Rewrite enterHandler procedure of profiler Tcllib module to correctly track
# call timing that gets mixed in the original version with the different level
# calls made in modulecmd.tcl
rename ::profiler::enterHandler ::profiler::__enterHandler
proc ::profiler::enterHandler {name caller} {
variable enabled
if { !$enabled($name) } {
return
}
if { [catch {incr ::profiler::callers($name,$caller)}] } {
set ::profiler::callers($name,$caller) 1
}
::profiler::tZero [info level].$name.$caller
}
# Rewrite leaveHandler procedure of profiler Tcllib module to correctly track
# call timing that gets mixed in the original version with the different level
# calls made in modulecmd.tcl
rename ::profiler::leaveHandler ::profiler::__leaveHandler
proc ::profiler::leaveHandler {name caller} {
variable enabled
# Tkt [0dd4b31bb8] Note that the result is pulled from the
# caller's context as it is not passed into leaveHandler
if { !$enabled($name) } {
return [uplevel 1 {lindex $args 1}] ;# RETURN RESULT!
}
set t [::profiler::tMark [info level].$name.$caller]
lappend ::profiler::statTime($name) $t
if { [incr ::profiler::callCount($name)] == 1 } {
set ::profiler::compileTime($name) $t
}
incr ::profiler::totalRuntime($name) $t
if { [catch {incr ::profiler::descendantTime($caller) $t}] } {
set ::profiler::descendantTime($caller) $t
}
if { [catch {incr ::profiler::descendants($caller,$name)}] } {
set ::profiler::descendants($caller,$name) 1
}
return [uplevel 1 {lindex $args 1}] ;# RETURN RESULT!
}
profiler::init
# run modulecmd with profiling enabled
source modulecmd.$tag
# restore puts command to output profiling result
rename ::puts {}
rename ::__puts ::puts
if {[string equal -length 6 $mode report]} {
# number of proc timing to return
set nbproc [string range $mode 6 end]
if {![string is integer $nbproc]} {
set nbproc 10
}
# post-process profiling data
set totalruntime 0
set totalcall 0
foreach {procname profdata} [::profiler::dump] {
array unset procprof
array set procprof $profdata
set procname [string trimleft $procname :]
# compute proc inner runtime (subtracting descendent proc runtime)
set runtime [expr {$procprof(totalRuntime) - $procprof(descendantTime)}]
if {$runtime < 0} {
set runtime 0
}
set totalruntime [expr {$totalruntime + $runtime}]
set totalcall [expr {$totalcall + $procprof(callCount)}]
lappend profres [list $procname $procprof(callCount) $runtime]
}
# record total runtime
lappend profres [list Total $totalcall $totalruntime]
# output total time and timing of the 10 biggest procs
puts [eval concat [lrange [lsort -integer -decreasing -index 2 $profres] 0\
[expr {$nbproc + 1}]]]
} else {
puts [profiler::print]
}
# vim:set tabstop=3 shiftwidth=3 expandtab autoindent syntax=tcl: