diff options
author | Richard Samuels <richard.l.samuels@gmail.com> | 2022-08-02 18:23:03 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2022-08-02 18:54:21 +0000 |
commit | f1290949e7cbe560c95d024b37e5e6fa490c09be (patch) | |
tree | fb0eec8e248d0a709ccbfac1e455dbb1cbe13aa7 /site_scons | |
parent | 10d1fe34d902d72df318e58b8c0e3b27a2d4e3e6 (diff) | |
download | mongo-f1290949e7cbe560c95d024b37e5e6fa490c09be.tar.gz |
SERVER-67055 Create scons builtin metrics
Diffstat (limited to 'site_scons')
-rw-r--r-- | site_scons/site_tools/build_metrics/__init__.py | 4 | ||||
-rw-r--r-- | site_scons/site_tools/build_metrics/build_metrics_format.schema | 41 | ||||
-rw-r--r-- | site_scons/site_tools/build_metrics/scons.py | 146 |
3 files changed, 190 insertions, 1 deletions
diff --git a/site_scons/site_tools/build_metrics/__init__.py b/site_scons/site_tools/build_metrics/__init__.py index aa51a0c4413..07be60782e9 100644 --- a/site_scons/site_tools/build_metrics/__init__.py +++ b/site_scons/site_tools/build_metrics/__init__.py @@ -35,6 +35,7 @@ from .util import add_meta_data, get_build_metric_dict, CaptureAtexits from .memory import MemoryMonitor from .per_action_metrics import PerActionMetrics from .artifacts import CollectArtifacts +from .scons import SConsStats _SEC_TO_NANOSEC_FACTOR = 1000000000.0 _METRICS_COLLECTORS = [] @@ -86,7 +87,8 @@ def generate(env, **kwargs): _METRICS_COLLECTORS = [ MemoryMonitor(psutil.Process().memory_info().vms), PerActionMetrics(), - CollectArtifacts(env) + CollectArtifacts(env), + SConsStats() ] diff --git a/site_scons/site_tools/build_metrics/build_metrics_format.schema b/site_scons/site_tools/build_metrics/build_metrics_format.schema index 559a1ee90a3..9a2544b5624 100644 --- a/site_scons/site_tools/build_metrics/build_metrics_format.schema +++ b/site_scons/site_tools/build_metrics/build_metrics_format.schema @@ -13,6 +13,10 @@ "description": "Size in bytes", "minimum": 0 }, + "seconds": { + "type": "integer", + "description": "Number of seconds" + }, "binsize": { "type": "object", "properties": { @@ -104,5 +108,42 @@ } } } + }, + "scons_metrics": { + "type": "object", + "properties": { + "memory": { + "type": "object", + "properties": { + "pre_read": {"$ref": "#/$defs/bytes"}, + "post_read": {"$ref": "#/$defs/bytes"}, + "pre_build": {"$ref": "#/$defs/bytes"}, + "post_build": {"$ref": "#/$defs/bytes"} + } + }, + "time": { + "type": "object", + "properties": { + "total": {"type": "number"}, + "sconscript_exec": {"type": "number"}, + "scons_exec": {"type": "number"}, + "command_exec": {"type": "number"} + } + }, + "counts": { + "type": "array", + "items": { + "type": "object", + "properties": { + "array_index": {"type": "integer"}, + "item_name": {"type": "str"}, + "pre_read": {"$ref": "#/$defs/bytes"}, + "post_read": {"$ref": "#/$defs/bytes"}, + "pre_build": {"$ref": "#/$defs/bytes"}, + "post_build": {"$ref": "#/$defs/bytes"} + } + } + } + } } } diff --git a/site_scons/site_tools/build_metrics/scons.py b/site_scons/site_tools/build_metrics/scons.py new file mode 100644 index 00000000000..34e8b2f8747 --- /dev/null +++ b/site_scons/site_tools/build_metrics/scons.py @@ -0,0 +1,146 @@ +import time +from typing import Tuple, List, Any, Optional + +from typing_extensions import Protocol, TypedDict +from .protocol import BuildMetricsCollector +import SCons.Script + + +class _HookedStartTime(float): + def __init__(self, val) -> None: + float.__init__(val) + self.hooked_end_time = None + + def __rsub__(self, other): + self.hooked_end_time = other + return other - float(self) + + +def _safe_list_get(list_, i, default=None): + try: + return list_[i] + except IndexError: + return default + + +class MemoryMetrics(TypedDict): + pre_read: int + post_read: int + pre_build: int + post_build: int + + +class TimeMetrics(TypedDict): + total: int + sconscript_exec: int + scons_exec: int + command_exec: int + + +class CountsMetrics(TypedDict): + array_index: int + item_name: str + pre_read: int + post_read: int + pre_build: int + post_build: int + + +class SConsStats(BuildMetricsCollector): + def __init__(self): + # hook start_time so we can also capture the end time + if not isinstance(SCons.Script.start_time, _HookedStartTime): + SCons.Script.start_time = _HookedStartTime(SCons.Script.start_time) + + def get_name(self) -> str: + return "SConsStats" + + def finalize(self) -> Tuple[str, Any]: + out = {} + memory = self._finalize_memory() + if memory is not None: + out["memory"] = memory + time = self._finalize_time() + if time is not None: + out["time"] = time + counts = self._finalize_counts() + if counts is not None: + out["counts"] = counts + return "scons_metrics", out + + def _finalize_memory(self) -> Optional[MemoryMetrics]: + memory_stats = SCons.Script.Main.memory_stats.stats + pre_read = _safe_list_get(memory_stats, 0, 0) + post_read = _safe_list_get(memory_stats, 1, 0) + pre_build = _safe_list_get(memory_stats, 2, 0) + post_build = _safe_list_get(memory_stats, 3, 0) + if pre_read == 0 and post_read == 0 and pre_build == 0 and post_build == 0: + print( + "WARNING: SConsStats read all memory statistics as 0. Did you pass --debug=memory?") + return None + return MemoryMetrics(pre_read=pre_read, post_read=post_read, pre_build=pre_build, + post_build=post_build) + + def _finalize_counts(self) -> Optional[List[CountsMetrics]]: + count_stats = SCons.Script.Main.count_stats.stats + if len(count_stats) != 4: + print( + f"WARNING: SConsStats expected 4 counts, found {len(count_stats)}. Did you pass --debug=count?" + ) + return None + + # This incomprehensible block taken from SCons produces stats_table, + # a mapping of class name to a list of counts with the same order as + # count_stats.labels + # From SCons/Script/Main.py:517 + stats_table = {} + for s in count_stats: + for n in [t[0] for t in s]: + stats_table[n] = [0, 0, 0, 0] + i = 0 + for s in count_stats: + for n, c in s: + stats_table[n][i] = c + i = i + 1 + # End section copied from SCons + + out = [] + for key, value in stats_table.items(): + out.append( + CountsMetrics( + array_index=len(out), item_name=key, pre_read=value[0], post_read=value[1], + pre_build=value[2], post_build=value[3])) + + return out + + def _finalize_time(self) -> Optional[TimeMetrics]: + # unfortunately, much of the SCons time keeping is encased in the + # main() function with local variables, so we're stuck copying + # a bit of logic from SCons.Script.Main + + end_time = SCons.Script.start_time.hooked_end_time + try: + total_time = end_time - SCons.Script.start_time + except TypeError as e: + if str(e) == "unsupported operand type(s) for -: 'NoneType' and 'float'": + print( + "WARNING: SConsStats failed to calculate SCons total time. Did you pass --debug=time?" + ) + return None + raise e + + sconscript_time = SCons.Script.Main.sconscript_time + + # From SCons/Script/Main.py:1428 + if SCons.Script.Main.num_jobs == 1: + ct = SCons.Script.Main.cumulative_command_time + else: + if SCons.Script.Main.last_command_end is None or SCons.Script.Main.first_command_start is None: + ct = 0.0 + else: + ct = SCons.Script.Main.last_command_end - SCons.Script.Main.first_command_start + scons_time = total_time - sconscript_time - ct + # End section copied from SCons + + return TimeMetrics(total=total_time, sconscript_exec=sconscript_time, scons_exec=scons_time, + command_exec=ct) |