summaryrefslogtreecommitdiff
path: root/site_scons
diff options
context:
space:
mode:
authorRichard Samuels <richard.l.samuels@gmail.com>2022-08-02 18:23:03 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2022-08-02 18:54:21 +0000
commitf1290949e7cbe560c95d024b37e5e6fa490c09be (patch)
treefb0eec8e248d0a709ccbfac1e455dbb1cbe13aa7 /site_scons
parent10d1fe34d902d72df318e58b8c0e3b27a2d4e3e6 (diff)
downloadmongo-f1290949e7cbe560c95d024b37e5e6fa490c09be.tar.gz
SERVER-67055 Create scons builtin metrics
Diffstat (limited to 'site_scons')
-rw-r--r--site_scons/site_tools/build_metrics/__init__.py4
-rw-r--r--site_scons/site_tools/build_metrics/build_metrics_format.schema41
-rw-r--r--site_scons/site_tools/build_metrics/scons.py146
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)