3 # Copyright The SCons Foundation
5 # Permission is hereby granted, free of charge, to any person obtaining
6 # a copy of this software and associated documentation files (the
7 # "Software"), to deal in the Software without restriction, including
8 # without limitation the rights to use, copy, modify, merge, publish,
9 # distribute, sublicense, and/or sell copies of the Software, and to
10 # permit persons to whom the Software is furnished to do so, subject to
11 # the following conditions:
13 # The above copyright notice and this permission notice shall be included
14 # in all copies or substantial portions of the Software.
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
17 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
18 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25 SCons statistics routines.
27 This package provides a way to gather various statistics during an SCons
28 run and dump that info in several formats
30 Additionally, it probably makes sense to do stderr/stdout output of
31 those statistics here as well
33 There are basically two types of stats:
35 1. Timer (start/stop/time) for specific event. These events can be
36 hierarchical. So you can record the children events of some parent.
37 Think program compile could contain the total Program builder time,
38 which could include linking, and stripping the executable
40 2. Counter. Counting the number of events and/or objects created. This
41 would likely only be reported at the end of a given SCons run,
42 though it might be useful to query during a run.
50 from datetime
import datetime
56 JSON_OUTPUT_FILE
= 'scons_stats.json'
58 def add_stat_type(name
, stat_object
):
59 """Add a statistic type to the global collection"""
61 raise UserWarning(f
'Stat type {name} already exists')
62 all_stats
[name
] = stat_object
69 self
.append
= self
.do_nothing
70 self
.print_stats
= self
.do_nothing
73 def do_append(self
, label
):
74 raise NotImplementedError
77 raise NotImplementedError
79 def enable(self
, outfp
):
81 self
.append
= self
.do_append
82 self
.print_stats
= self
.do_print
85 def do_nothing(self
, *args
, **kw
):
89 class CountStats(Stats
):
95 def do_append(self
, label
):
96 self
.labels
.append(label
)
97 self
.stats
.append(SCons
.Debug
.fetchLoggedInstances())
100 self
.stats_table
= {}
102 for n
in [t
[0] for t
in s
]:
103 self
.stats_table
[n
] = [0, 0, 0, 0]
107 self
.stats_table
[n
][i
] = c
109 self
.outfp
.write("Object counts:\n")
113 fmt1
= ''.join(pre
+ [' %7s'] * l
+ post
)
114 fmt2
= ''.join(pre
+ [' %7d'] * l
+ post
)
115 labels
= self
.labels
[:l
]
116 labels
.append(("", "Class"))
117 self
.outfp
.write(fmt1
% tuple(x
[0] for x
in labels
))
118 self
.outfp
.write(fmt1
% tuple(x
[1] for x
in labels
))
119 for k
in sorted(self
.stats_table
.keys()):
120 r
= self
.stats_table
[k
][:l
] + [k
]
121 self
.outfp
.write(fmt2
% tuple(r
))
124 class MemStats(Stats
):
125 def do_append(self
, label
):
126 self
.labels
.append(label
)
127 self
.stats
.append(SCons
.Debug
.memory())
130 fmt
= 'Memory %-32s %12d\n'
131 for label
, stats
in zip(self
.labels
, self
.stats
):
132 self
.outfp
.write(fmt
% (label
, stats
))
135 class TimeStats(Stats
):
139 self
.commands
= {} # we get order from insertion order, and can address individual via dict
141 def total_times(self
, build_time
, sconscript_time
, scons_exec_time
, command_exec_time
):
143 'build_time': build_time
,
144 'sconscript_time': sconscript_time
,
145 'scons_exec_time': scons_exec_time
,
146 'command_exec_time': command_exec_time
149 def add_command(self
, command
, start_time
, finish_time
):
150 if command
in self
.commands
:
151 print("Duplicate command %s" % command
)
152 self
.commands
[command
] = {'start': start_time
,
154 'duration': finish_time
- start_time
}
157 count_stats
= CountStats()
158 memory_stats
= MemStats()
159 time_stats
= TimeStats()
162 def write_scons_stats_file():
164 Actually write the JSON file with debug information.
165 Depending which of : count, time, action-timestamps,memory their information will be written.
168 # Have to import where used to avoid import loop
169 from SCons
.Script
import ( # pylint: disable=import-outside-toplevel
171 COMMAND_LINE_TARGETS
,
175 # print(f"DUMPING JSON FILE: {JSON_OUTPUT_FILE}")
177 if count_stats
.enabled
:
178 json_structure
['Object counts'] = {}
180 oc
= json_structure
['Object counts']
181 for c
in count_stats
.stats_table
:
183 for l
, v
in zip(count_stats
.labels
, count_stats
.stats_table
[c
]):
184 oc
[c
][''.join(l
)] = v
186 if memory_stats
.enabled
:
187 json_structure
['Memory'] = {}
189 m
= json_structure
['Memory']
190 for label
, stats
in zip(memory_stats
.labels
, memory_stats
.stats
):
193 if time_stats
.enabled
:
194 json_structure
['Time'] = {'Commands': time_stats
.commands
,
195 'Totals': time_stats
.totals
}
197 # Now add information about this build to the JSON file
198 json_structure
['Build_Info'] = {
199 'BUILD_TARGETS' : [str(t
) for t
in BUILD_TARGETS
],
200 'ARGUMENTS' : [str(a
) for a
in ARGUMENTS
],
201 'ARGLIST' : [ str(al
) for al
in ARGLIST
],
202 'COMMAND_LINE_TARGETS' : [ str(clt
) for clt
in COMMAND_LINE_TARGETS
],
204 'TIME' : datetime
.now().isoformat(),
205 'HOST' : platform
.node(),
207 'major' : sys
.version_info
.major
,
208 'minor' : sys
.version_info
.minor
,
209 'micro' : sys
.version_info
.micro
,
210 'releaselevel' : sys
.version_info
.releaselevel
,
211 'serial' : sys
.version_info
.serial
,
216 with
open(JSON_OUTPUT_FILE
, 'w') as sf
:
217 sf
.write(json
.dumps(json_structure
, indent
=4))
222 # indent-tabs-mode:nil
224 # vim: set expandtab tabstop=4 shiftwidth=4: