Skip to content

Commit 70bc59b

Browse files
committed
[GR-70638] Add time tracker for benchmarking.
PullRequest: mx/1989
2 parents 51edf7d + 99b8977 commit 70bc59b

File tree

4 files changed

+149
-72
lines changed

4 files changed

+149
-72
lines changed

common.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"Jsonnet files should not include this file directly but use ci/common.jsonnet instead."
55
],
66

7-
"mx_version": "7.68.2",
7+
"mx_version": "7.68.4",
88

99
"COMMENT.jdks": "When adding or removing JDKs keep in sync with JDKs in ci/common.jsonnet",
1010
"jdks": {
@@ -49,12 +49,12 @@
4949
"graalvm-ee-25-ea": {"name": "graalvm-jdk", "version": "25.0.0", "ea": "36", "platformspecific": true },
5050

5151
"oraclejdk-latest": {"name": "jpg-jdk", "version": "25", "build_id": "jdk-25.0.1+8", "platformspecific": true, "extrabundles": ["static-libs"]},
52-
"labsjdk-ce-latest": {"name": "labsjdk", "version": "ce-25.0.1+8-jvmci-25.1-b10", "platformspecific": true },
53-
"labsjdk-ce-latestDebug": {"name": "labsjdk", "version": "ce-25.0.1+8-jvmci-25.1-b10-debug", "platformspecific": true },
54-
"labsjdk-ce-latest-llvm": {"name": "labsjdk", "version": "ce-25.0.1+8-jvmci-25.1-b10-sulong", "platformspecific": true },
55-
"labsjdk-ee-latest": {"name": "labsjdk", "version": "ee-25.0.1+8-jvmci-25.1-b10", "platformspecific": true },
56-
"labsjdk-ee-latestDebug": {"name": "labsjdk", "version": "ee-25.0.1+8-jvmci-25.1-b10-debug", "platformspecific": true },
57-
"labsjdk-ee-latest-llvm": {"name": "labsjdk", "version": "ee-25.0.1+8-jvmci-25.1-b10-sulong", "platformspecific": true }
52+
"labsjdk-ce-latest": {"name": "labsjdk", "version": "ce-25.0.1+8-jvmci-25.1-b11", "platformspecific": true },
53+
"labsjdk-ce-latestDebug": {"name": "labsjdk", "version": "ce-25.0.1+8-jvmci-25.1-b11-debug", "platformspecific": true },
54+
"labsjdk-ce-latest-llvm": {"name": "labsjdk", "version": "ce-25.0.1+8-jvmci-25.1-b11-sulong", "platformspecific": true },
55+
"labsjdk-ee-latest": {"name": "labsjdk", "version": "ee-25.0.1+8-jvmci-25.1-b11", "platformspecific": true },
56+
"labsjdk-ee-latestDebug": {"name": "labsjdk", "version": "ee-25.0.1+8-jvmci-25.1-b11-debug", "platformspecific": true },
57+
"labsjdk-ee-latest-llvm": {"name": "labsjdk", "version": "ee-25.0.1+8-jvmci-25.1-b11-sulong", "platformspecific": true }
5858
},
5959

6060
"eclipse": {

src/mx/_impl/mx_benchmark.py

Lines changed: 75 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -102,12 +102,13 @@
102102
"enable_tracker",
103103
"disable_tracker",
104104
"Tracker",
105-
"RssTracker",
105+
"TimeTracker",
106106
"PsrecordTracker",
107107
"PsrecordMaxrssTracker",
108108
"RssPercentilesTracker",
109-
"RssPercentilesAndMaxTracker",
109+
"RssPercentilesAndTimeTracker",
110110
"EnergyConsumptionTracker",
111+
"get_tracker_class",
111112
"BenchmarkExecutor",
112113
"make_hwloc_bind",
113114
"init_benchmark_suites",
@@ -766,14 +767,36 @@ def register_command_mapper_hook(self, name: str, hook: Callable | MapperHook):
766767
else:
767768
raise ValueError(f"Hook must be MapperHook or callable, got {type(hook)}")
768769

769-
770770
def register_tracker(self, name, tracker_type):
771771
tracker = tracker_type(self)
772772
self._tracker = tracker
773773
hook = tracker.get_hook()
774774

775775
self.register_command_mapper_hook(name, hook)
776776

777+
def unregister_tracker(self):
778+
if self._tracker is None:
779+
mx.warn("No tracker is currently registered to unregister")
780+
return
781+
782+
tracker_type = type(self._tracker)
783+
hook_name_to_remove = None
784+
785+
for name, registered_type in _available_trackers.items():
786+
if registered_type == tracker_type:
787+
hook_name_to_remove = name
788+
break
789+
790+
if hook_name_to_remove and hook_name_to_remove in self._command_mapper_hooks:
791+
del self._command_mapper_hooks[hook_name_to_remove]
792+
else:
793+
mx.warn("Could not find tracker's hook in registered command mapper hooks")
794+
795+
self._tracker = None
796+
797+
def tracker(self):
798+
return self._tracker
799+
777800
def version(self):
778801
"""The suite version selected for execution which is either the :defaultSuiteVerion:
779802
or the :desiredVersion: if any.
@@ -3575,77 +3598,61 @@ def get_rules(self, bmSuiteArgs):
35753598
def get_hook(self) -> MapperHook:
35763599
return DefaultTrackerHook(self)
35773600

3578-
class RssTracker(Tracker):
3601+
class TimeTracker(Tracker):
35793602
def __init__(self, bmSuite):
35803603
super().__init__(bmSuite)
3581-
log_deprecation("The 'rss' tracker is deprecated, use 'rsspercentiles' instead!")
3604+
self._timing_wrapper = None
3605+
3606+
if mx.get_os() != "linux":
3607+
script_dir = os.path.dirname(os.path.abspath(__file__))
3608+
self._timing_wrapper = os.path.join(script_dir, "timing_wrapper.py")
3609+
if not os.path.exists(self._timing_wrapper):
3610+
mx.warn(f"Timing wrapper not found at {self._timing_wrapper}")
3611+
self._timing_wrapper = None
35823612

35833613
def map_command(self, cmd):
35843614
"""
3585-
Tracks the max resident memory size used by the process using the 'time' command.
3615+
Tracks wall-clock time using platform-appropriate timing commands.
35863616
35873617
:param list[str] cmd: the input command to modify
3588-
:return:
3618+
:return: list[str] modified command with timing prefix
35893619
"""
35903620
if not _use_tracker:
35913621
return cmd
3622+
35923623
if mx.get_os() == "linux":
3593-
prefix = ["time", "-v"]
3594-
elif mx.get_os() == "darwin":
3595-
prefix = ["time", "-l"]
3624+
# Forces GNU time with '/usr/bin/time' instead of shell builtin with 'time'
3625+
prefix = ["/usr/bin/time", "-f", "Wall-clock time: %e sec"]
35963626
else:
3597-
mx.log(f"Ignoring the 'rss' tracker since it is not supported on {mx.get_os()}")
3598-
prefix = []
3627+
if self._timing_wrapper and os.path.exists(self._timing_wrapper):
3628+
prefix = [self._timing_wrapper]
3629+
else:
3630+
raise ValueError(f"Timing wrapper not found at {self._timing_wrapper}")
3631+
35993632
return prefix + cmd
36003633

36013634
def get_rules(self, bmSuiteArgs):
36023635
if mx_benchmark_compatibility().bench_suite_needs_suite_args():
36033636
suite_name = self.bmSuite.benchSuiteName(bmSuiteArgs)
36043637
else:
36053638
suite_name = self.bmSuite.benchSuiteName()
3606-
if mx.get_os() == "linux":
3607-
# Output of 'time -v' on linux contains:
3608-
# Maximum resident set size (kbytes): 511336
3609-
rules = [
3610-
StdOutRule(
3611-
r"Maximum resident set size \(kbytes\): (?P<rss>[0-9]+)",
3612-
{
3613-
"benchmark": self.bmSuite.currently_running_benchmark(),
3614-
"bench-suite": suite_name,
3615-
"config.vm-flags": ' '.join(self.bmSuite.vmArgs(bmSuiteArgs)),
3616-
"metric.name": "max-rss",
3617-
"metric.value": ("<rss>", lambda x: int(float(x)/(1024))),
3618-
"metric.unit": "MB",
3619-
"metric.type": "numeric",
3620-
"metric.score-function": "id",
3621-
"metric.better": "lower",
3622-
"metric.iteration": 0
3623-
}
3624-
)
3625-
]
3626-
elif mx.get_os() == "darwin":
3627-
# Output of 'time -l' on linux contains (size in bytes):
3628-
# 523608064 maximum resident set size
3629-
rules = [
3630-
StdOutRule(
3631-
r"(?P<rss>[0-9]+)\s+maximum resident set size",
3632-
{
3633-
"benchmark": self.bmSuite.currently_running_benchmark(),
3634-
"bench-suite": suite_name,
3635-
"config.vm-flags": ' '.join(self.bmSuite.vmArgs(bmSuiteArgs)),
3636-
"metric.name": "max-rss",
3637-
"metric.value": ("<rss>", lambda x: int(float(x)/(1024*1024))),
3638-
"metric.unit": "MB",
3639-
"metric.type": "numeric",
3640-
"metric.score-function": "id",
3641-
"metric.better": "lower",
3642-
"metric.iteration": 0
3643-
}
3644-
)
3645-
]
3646-
else:
3647-
rules = []
3648-
return rules
3639+
return [
3640+
StdOutRule(
3641+
r"Wall-clock time:\s+(?P<time_sec>[0-9]+\.[0-9]+) sec",
3642+
{
3643+
"benchmark": self.bmSuite.currently_running_benchmark(),
3644+
"bench-suite": suite_name,
3645+
"config.vm-flags": ' '.join(self.bmSuite.vmArgs(bmSuiteArgs)),
3646+
"metric.name": "time",
3647+
"metric.unit": "ms",
3648+
"metric.value": ("<time_sec>", lambda ms: float(ms) * 1000),
3649+
"metric.type": "numeric",
3650+
"metric.score-function": "id",
3651+
"metric.better": "lower",
3652+
"metric.iteration": 0
3653+
}
3654+
)
3655+
]
36493656

36503657

36513658
class PsrecordTracker(Tracker):
@@ -3727,7 +3734,7 @@ class PsrecordMaxrssTracker(Tracker):
37273734

37283735
def __init__(self, bmSuite):
37293736
super().__init__(bmSuite)
3730-
self.rss = RssTracker(bmSuite)
3737+
self.rss = TimeTracker(bmSuite)
37313738
self.psrecord = PsrecordTracker(bmSuite)
37323739

37333740
def map_command(self, cmd):
@@ -3911,22 +3918,21 @@ def parseResults(self, text):
39113918
return []
39123919

39133920

3914-
class RssPercentilesAndMaxTracker(Tracker):
3921+
class RssPercentilesAndTimeTracker(Tracker):
39153922
def __init__(self, bmSuite):
39163923
super().__init__(bmSuite)
3917-
self.rss_max_tracker = RssTracker(bmSuite)
3924+
self.time_tracker = TimeTracker(bmSuite)
39183925
self.rss_percentiles_tracker = RssPercentilesTracker(
39193926
bmSuite,
39203927
skip=1, # skip RSS of the 'time' command
3921-
copy_into_max_rss=False # don't copy a percentile into max-rss, since the rss_max_tracker will generate the metric
3928+
copy_into_max_rss=True
39223929
)
3923-
log_deprecation("The 'rsspercentiles+maxrss' tracker is deprecated, use 'rsspercentiles' instead!")
39243930

39253931
def map_command(self, cmd):
3926-
return self.rss_percentiles_tracker.map_command(self.rss_max_tracker.map_command(cmd))
3932+
return self.rss_percentiles_tracker.map_command(self.time_tracker.map_command(cmd))
39273933

39283934
def get_rules(self, bmSuiteArgs):
3929-
return self.rss_max_tracker.get_rules(bmSuiteArgs) + self.rss_percentiles_tracker.get_rules(bmSuiteArgs)
3935+
return self.time_tracker.get_rules(bmSuiteArgs) + self.rss_percentiles_tracker.get_rules(bmSuiteArgs)
39303936

39313937
class EnergyConsumptionTracker(Tracker):
39323938
"""
@@ -4060,14 +4066,19 @@ def should_apply(self, stage: Optional[Stage]) -> bool:
40604066
return stage.is_final() if stage else False
40614067

40624068
_available_trackers = {
4063-
"rss": RssTracker,
4069+
"time": TimeTracker,
40644070
"psrecord": PsrecordTracker,
40654071
"psrecord+maxrss": PsrecordMaxrssTracker,
40664072
"rsspercentiles": RssPercentilesTracker,
4067-
"rsspercentiles+maxrss": RssPercentilesAndMaxTracker,
4073+
"rsspercentiles+time": RssPercentilesAndTimeTracker,
40684074
"energy": EnergyConsumptionTracker
40694075
}
40704076

4077+
def get_tracker_class(tracker_name):
4078+
if tracker_name not in _available_trackers:
4079+
raise ValueError(f"Tracker '{tracker_name}' doesn't exist! Use one of: [{', '.join(_available_trackers.keys())}]")
4080+
return _available_trackers[tracker_name]
4081+
40714082

40724083
class BenchmarkExecutor(object):
40734084
def uid(self):

src/mx/_impl/timing_wrapper.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
#!/usr/bin/env python3
2+
#
3+
# ----------------------------------------------------------------------------------------------------
4+
#
5+
# Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved.
6+
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
7+
#
8+
# This code is free software; you can redistribute it and/or modify it
9+
# under the terms of the GNU General Public License version 2 only, as
10+
# published by the Free Software Foundation.
11+
#
12+
# This code is distributed in the hope that it will be useful, but WITHOUT
13+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15+
# version 2 for more details (a copy is included in the LICENSE file that
16+
# accompanied this code).
17+
#
18+
# You should have received a copy of the GNU General Public License version
19+
# 2 along with this work; if not, write to the Free Software Foundation,
20+
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
21+
#
22+
# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
23+
# or visit www.oracle.com if you need additional information or have any
24+
# questions.
25+
#
26+
# ----------------------------------------------------------------------------------------------------
27+
28+
"""
29+
Simple timing wrapper that measures wall-clock time with high precision.
30+
Usage: python timing_wrapper.py <command> [args...]
31+
"""
32+
import sys
33+
import subprocess
34+
import time
35+
36+
37+
def main():
38+
if len(sys.argv) < 2:
39+
print("Usage: python timing_wrapper.py <command> [args...]", file=sys.stderr)
40+
sys.exit(1)
41+
42+
cmd = sys.argv[1:]
43+
44+
start_time = time.perf_counter()
45+
46+
try:
47+
result = subprocess.run(cmd, check=False)
48+
elapsed = time.perf_counter() - start_time
49+
50+
print(f"Wall-clock time: {elapsed:.6f} sec", file=sys.stderr)
51+
sys.exit(result.returncode)
52+
53+
except KeyboardInterrupt:
54+
elapsed = time.perf_counter() - start_time
55+
print(f"\nWall-clock time (interrupted): {elapsed:.6f} sec", file=sys.stderr)
56+
sys.exit(1)
57+
58+
except OSError as e:
59+
elapsed = time.perf_counter() - start_time
60+
print(f"Error running command: {e}", file=sys.stderr)
61+
print(f"Wall-clock time (error): {elapsed:.6f} sec", file=sys.stderr)
62+
sys.exit(1)
63+
64+
65+
if __name__ == "__main__":
66+
main()

src/mx/mx_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
# The version must be updated for every PR (checked in CI) and the comment should reflect the PR's issue
2-
version = "7.68.4" # GR-71712 SpotBugs suite-level attribute must not override project level config
2+
version = "7.68.5" # GR-70638 - benchmarking time tracker

0 commit comments

Comments
 (0)