added release.txt blurb. Fixed spelling typo in Defaults.xml
[scons.git] / SCons / Util / stats.py
blobce820e66c65f2db5161cc59b63ce8a3698096063
1 # MIT License
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.
24 """
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.
43 """
45 from abc import ABC
47 import platform
48 import json
49 import sys
50 from datetime import datetime
52 import SCons.Debug
54 all_stats = {}
55 ENABLE_JSON = False
56 JSON_OUTPUT_FILE = 'scons_stats.json'
58 def add_stat_type(name, stat_object):
59 """Add a statistic type to the global collection"""
60 if name in all_stats:
61 raise UserWarning(f'Stat type {name} already exists')
62 all_stats[name] = stat_object
65 class Stats(ABC):
66 def __init__(self):
67 self.stats = []
68 self.labels = []
69 self.append = self.do_nothing
70 self.print_stats = self.do_nothing
71 self.enabled = False
73 def do_append(self, label):
74 raise NotImplementedError
76 def do_print(self):
77 raise NotImplementedError
79 def enable(self, outfp):
80 self.outfp = outfp
81 self.append = self.do_append
82 self.print_stats = self.do_print
83 self.enabled = True
85 def do_nothing(self, *args, **kw):
86 pass
89 class CountStats(Stats):
91 def __init__(self):
92 super().__init__()
93 self.stats_table = {}
95 def do_append(self, label):
96 self.labels.append(label)
97 self.stats.append(SCons.Debug.fetchLoggedInstances())
99 def do_print(self):
100 self.stats_table = {}
101 for s in self.stats:
102 for n in [t[0] for t in s]:
103 self.stats_table[n] = [0, 0, 0, 0]
104 i = 0
105 for s in self.stats:
106 for n, c in s:
107 self.stats_table[n][i] = c
108 i = i + 1
109 self.outfp.write("Object counts:\n")
110 pre = [" "]
111 post = [" %s\n"]
112 l = len(self.stats)
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())
129 def do_print(self):
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):
136 def __init__(self):
137 super().__init__()
138 self.totals = {}
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):
142 self.totals = {
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,
153 'end' : finish_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
170 BUILD_TARGETS,
171 COMMAND_LINE_TARGETS,
172 ARGUMENTS,
173 ARGLIST,
175 # print(f"DUMPING JSON FILE: {JSON_OUTPUT_FILE}")
176 json_structure = {}
177 if count_stats.enabled:
178 json_structure['Object counts'] = {}
180 oc = json_structure['Object counts']
181 for c in count_stats.stats_table:
182 oc[c] = {}
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):
191 m[label] = 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],
203 'ARGV' : sys.argv,
204 'TIME' : datetime.now().isoformat(),
205 'HOST' : platform.node(),
206 'PYTHON_VERSION' : {
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))
220 # Local Variables:
221 # tab-width:4
222 # indent-tabs-mode:nil
223 # End:
224 # vim: set expandtab tabstop=4 shiftwidth=4: