Skip to content

Commit e62ab2d

Browse files
committed
[GR-69781] Track USS memory size for heap benchmarks and micro-small.
PullRequest: graalpython/4014
2 parents eccba94 + 05b6f91 commit e62ab2d

File tree

8 files changed

+98
-41
lines changed

8 files changed

+98
-41
lines changed

ci.jsonnet

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -391,9 +391,13 @@
391391
for bench in ["warmup"]
392392
} + {
393393
[bench]: bench_task(bench) + platform_spec(no_jobs) + bench_variants({
394-
"vm_name:graalpython_enterprise_interpreter" : {"linux:amd64:jdk-latest" : post_merge + t("00:30:00")},
394+
"vm_name:graalvm_ee_default_interpreter" : {"linux:amd64:jdk-latest" : post_merge + t("01:00:00")},
395+
"vm_name:graalvm_ee_default_interpreter_bc_dsl" : {"linux:amd64:jdk-latest" : post_merge + t("01:00:00")},
396+
"vm_name:graalpython_enterprise_interpreter" : {"linux:amd64:jdk-latest" : daily + t("01:00:00")},
397+
"vm_name:graalpython_enterprise_interpreter_bc_dsl" : {"linux:amd64:jdk-latest" : daily + t("01:00:00")},
398+
"vm_name:cpython" : {"linux:amd64:jdk-latest" : weekly + t("00:30:00")},
395399
}),
396-
for bench in ["heap"]
400+
for bench in ["heap", "micro_small_heap"]
397401
} + {
398402
// interop benchmarks only for graalpython, weekly is enough
399403
[bench]: bench_task(bench) + platform_spec(no_jobs) + bench_variants({

ci/python-bench.libsonnet

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
local platform_spec = run_spec.platform_spec,
1414
local evaluate_late = run_spec.evaluate_late,
1515
local downloads = self.downloads,
16-
local graalpy_gate = self.graalpy_gate,
1716
local os_arch_jdk_mixin = self.os_arch_jdk_mixin,
1817
local all_jobs = self.all_jobs,
1918
local no_jobs = self.no_jobs,
@@ -37,6 +36,7 @@
3736
java_embedding_meso_small: "java-embedding-meso-small:*",
3837
jmh: "python-jmh:GRAALPYTHON_BENCH",
3938
heap: "heap-graalpython:*",
39+
micro_small_heap: "micro-small-heap-graalpython:*",
4040
},
4141

4242
PY_BENCHMARKS:: {

ci/python-gate.libsonnet

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,9 @@
467467
// logging
468468
["mx"] + self.mx_parameters + self.dy + ["sversions"],
469469
],
470+
on_success+: [
471+
["rm", "-rf", "graal_dumps"],
472+
],
470473
}),
471474

472475
graalpy_ee_gate:: $.graalpy_gate + task_spec({

mx.graalpython/live_heap_tracker.py

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,42 @@
3737
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
3838
# SOFTWARE.
3939

40-
import sys
41-
import time
42-
40+
from pathlib import Path
4341
import re
4442
import subprocess
43+
import sys
44+
import time
4545

4646
TOTAL_RE = re.compile(r'^Total +\d+ +(\d+)', re.MULTILINE)
47+
PRIVATE_RE = re.compile(r'Private_(?:Clean|Dirty):\s+(\d+) kB')
48+
49+
50+
def jmap(jmap_binary, ppid):
51+
if not jmap_binary:
52+
return 0
53+
try:
54+
jmap_output = subprocess.check_output(
55+
[jmap_binary, '-histo:live', str(ppid)],
56+
universal_newlines=True,
57+
stderr=subprocess.DEVNULL,
58+
)
59+
if match := TOTAL_RE.search(jmap_output):
60+
heap_bytes = int(match.group(1))
61+
return heap_bytes
62+
except (subprocess.CalledProcessError, subprocess.TimeoutExpired):
63+
pass
64+
return 0
65+
66+
67+
def uss(ppid):
68+
smap = Path(f"/proc/{ppid}/smaps")
69+
try:
70+
memory_map = smap.read_text()
71+
total_bytes = sum(int(val) * 1024 for val in PRIVATE_RE.findall(memory_map))
72+
return total_bytes
73+
except FileNotFoundError:
74+
pass
75+
return 0
4776

4877

4978
def main():
@@ -54,19 +83,12 @@ def main():
5483
with open(output_file, 'w') as f:
5584
for _ in range(iterations):
5685
proc = subprocess.Popen(benchmark)
86+
ppid = proc.pid
5787
while proc.poll() is None:
5888
time.sleep(0.3)
59-
try:
60-
jmap_output = subprocess.check_output(
61-
[jmap_binary, '-histo:live', str(proc.pid)],
62-
universal_newlines=True,
63-
stderr=subprocess.DEVNULL,
64-
)
65-
if match := TOTAL_RE.search(jmap_output):
66-
heap_bytes = int(match.group(1))
67-
f.write(f'{heap_bytes}\n')
68-
except subprocess.CalledProcessError:
69-
pass
89+
uss_bytes = uss(ppid)
90+
heap_bytes = jmap(jmap_binary, ppid)
91+
f.write(f"{heap_bytes} {uss_bytes}\n")
7092
if proc.returncode != 0:
7193
sys.exit(proc.returncode)
7294

mx.graalpython/mx_graalpython.py

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -161,15 +161,6 @@ def wants_debug_build(flags=os.environ.get("CFLAGS", "")):
161161

162162

163163
if WIN32:
164-
# we need the .lib for pythonjni
165-
original_DefaultNativeProject_getArchivableResults = mx_native.DefaultNativeProject.getArchivableResults
166-
def getArchivableResultsWithLib(self, *args, **kwargs):
167-
for result in original_DefaultNativeProject_getArchivableResults(self, *args, **kwargs):
168-
if any(r.endswith("pythonjni.dll") for r in result):
169-
yield tuple(r.replace(".dll", ".lib") for r in result)
170-
yield result
171-
mx_native.DefaultNativeProject.getArchivableResults = getArchivableResultsWithLib
172-
173164
# let's check if VS compilers are on the PATH
174165
if not os.environ.get("LIB"):
175166
mx.log("LIB not in environment, not a VS shell")
@@ -2415,7 +2406,7 @@ def __init__(self, suite, name, subDir, srcDirs, deps, workingSets, d, theLicens
24152406
mx.Project.__init__(self, suite, name, subDir, srcDirs, deps, workingSets, d, theLicense, **kwargs)
24162407

24172408
def getOutput(self, replaceVar=mx_subst.results_substitutions):
2418-
return self.get_output_root()
2409+
return replaceVar.substitute(self.get_output_root())
24192410

24202411
def output_dir(self):
24212412
return self.getOutput()

mx.graalpython/mx_graalpython_bench_param.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,5 +340,6 @@ def _pickling_benchmarks(module='pickle'):
340340
"post-startup": [],
341341
"import-a-lot": [],
342342
"allocate-objects": [],
343-
}]
343+
}],
344+
"micro-small-heap": [PATH_MICRO, MICRO_BENCHMARKS_SMALL],
344345
}

mx.graalpython/mx_graalpython_benchmark.py

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -949,9 +949,15 @@ def map_command(self, cmd):
949949
if self.bmSuite:
950950
bench_name = f"{self.bmSuite.name()}-{bench_name}"
951951
ts = datetime.now().strftime("%Y%m%d-%H%M%S")
952-
jmap_command = mx.get_jdk().exe_path('jmap')
952+
vm = self.bmSuite.execution_context.virtual_machine
953+
if isinstance(vm, GraalPythonVm) and vm.launcher_type == "jvm":
954+
jmap_command = mx.get_jdk().exe_path('jmap')
955+
else:
956+
jmap_command = ""
953957
self.out_file = os.path.join(os.getcwd(), f"heap_tracker_{bench_name}_{ts}.txt")
954958
iterations = 3
959+
if "-i" in cmd:
960+
cmd[cmd.index("-i") + 1] = "1"
955961
return [sys.executable, str(DIR / 'live_heap_tracker.py'), self.out_file, str(iterations), jmap_command, *cmd]
956962

957963
def get_rules(self, bmSuiteArgs):
@@ -964,18 +970,41 @@ def __init__(self, tracker, bmSuiteArgs):
964970

965971
def parse(self, text):
966972
with open(self.tracker.out_file) as f:
967-
heap_mb = [int(line.strip()) / (1024 ** 2) for line in f if line]
973+
heap_mb, uss_mb = zip(*(map(lambda i: int(i) / (1024 ** 2), line.split()) for line in f if line))
968974
os.unlink(self.tracker.out_file)
969-
self.tracker.out_file = None
970-
deciles = statistics.quantiles(heap_mb, n=10)
971-
print(f"Heap size deciles (MiB): {deciles}")
975+
heap_deciles = statistics.quantiles(heap_mb, n=10)
976+
uss_deciles = statistics.quantiles(uss_mb, n=10)
977+
print(f"Heap size deciles (MiB): {heap_deciles}")
978+
print(f"USS size deciles (MiB): {uss_deciles}")
979+
# The heap benchmarks are a separate suite, because they are run
980+
# very differently, but we want to be able to conveniently query
981+
# all data about the same suites that we have. So, if this suite
982+
# name ends with "-heap", we drop that so it gets attributed to the
983+
# base suite.
984+
suite = self.tracker.bmSuite.benchSuiteName(self.bmSuiteArgs)
985+
if suite.endswith("-heap"):
986+
suite = suite[:-len("-heap")]
987+
benchmark = f"{suite}.{self.tracker.bmSuite.currently_running_benchmark()}"
988+
vm_flags = ' '.join(self.tracker.bmSuite.vmArgs(self.bmSuiteArgs))
972989
return [
973990
PythonBaseBenchmarkSuite.with_branch_and_commit_dict({
974-
"benchmark": self.tracker.bmSuite.currently_running_benchmark(),
975-
"bench-suite": self.tracker.bmSuite.benchSuiteName(self.bmSuiteArgs),
976-
"config.vm-flags": ' '.join(self.tracker.bmSuite.vmArgs(self.bmSuiteArgs)),
991+
"benchmark": benchmark,
992+
"bench-suite": suite,
993+
"config.vm-flags": vm_flags,
977994
"metric.name": "allocated-memory",
978-
"metric.value": deciles[-1],
995+
"metric.value": heap_deciles[-1],
996+
"metric.unit": "MB",
997+
"metric.type": "numeric",
998+
"metric.score-function": "id",
999+
"metric.better": "lower",
1000+
"metric.iteration": 0
1001+
}),
1002+
PythonBaseBenchmarkSuite.with_branch_and_commit_dict({
1003+
"benchmark": benchmark,
1004+
"bench-suite": suite,
1005+
"config.vm-flags": vm_flags,
1006+
"metric.name": "memory",
1007+
"metric.value": uss_deciles[-1],
9791008
"metric.unit": "MB",
9801009
"metric.type": "numeric",
9811010
"metric.score-function": "id",
@@ -1004,7 +1033,17 @@ def register_tracker(self, name, tracker_type):
10041033
def createCommandLineArgs(self, benchmarks, bmSuiteArgs):
10051034
benchmark = benchmarks[0]
10061035
bench_path = os.path.join(self._bench_path, f'{benchmark}.py')
1007-
return [*self.vmArgs(bmSuiteArgs), bench_path, *self.runArgs(bmSuiteArgs)]
1036+
bench_args = self._benchmarks[benchmark]
1037+
run_args = self.runArgs(bmSuiteArgs)
1038+
cmd_args = []
1039+
if "-i" in bench_args:
1040+
# Need to use the harness to parse
1041+
cmd_args.append(HARNESS_PATH)
1042+
if "-i" not in run_args:
1043+
# Explicit iteration count overrides default
1044+
run_args += bench_args
1045+
cmd_args.append(bench_path)
1046+
return [*self.vmArgs(bmSuiteArgs), *cmd_args, *run_args]
10081047

10091048
def successPatterns(self):
10101049
return []

mx.graalpython/suite.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1053,9 +1053,6 @@
10531053
"BOUNCYCASTLE-PKIX",
10541054
"BOUNCYCASTLE-UTIL",
10551055
],
1056-
"javaProperties": {
1057-
# "python.jni.library": "<lib:pythonjni>"
1058-
},
10591056
"description": "GraalPy, a high-performance embeddable Python 3 runtime for Java. This artifact includes the core language runtime without standard libraries. It is not recommended to depend on the artifact directly. Instead, use \'org.graalvm.polyglot:python\' or \'org.graalvm.polyglot:python-community\' to ensure all dependencies are pulled in correctly.",
10601057
"maven": {
10611058
"artifactId": "python-language",

0 commit comments

Comments
 (0)