From 3b0793589b2e12c4444ccd99d3ff28aaf95f9677 Mon Sep 17 00:00:00 2001 From: 72004 Date: Thu, 22 Oct 2026 01:07:51 +0500 Subject: [PATCH] Pass jfr, gc and jit telemetry devices to the provisioned pipelines The from-distribution and from-sources pipelines created SolrProvisioner without telemetry_devices, so --telemetry jfr/gc/jit added nothing to SOLR_OPTS. Build the enabled devices from telemetry.devices and telemetry.params and pass them in. Output goes to the test run's telemetry directory. Docker is left out on purpose: the flags point at host paths, which the JVM inside the container cannot open. That part needs the log storage change discussed in #138. Part of #138 --- docs/reference/telemetry.md | 14 ++-- solrorbit/test_run_orchestrator.py | 27 +++++++- tests/test_execution_orchestrator_test.py | 79 +++++++++++++++++++++++ 3 files changed, 110 insertions(+), 10 deletions(-) diff --git a/docs/reference/telemetry.md b/docs/reference/telemetry.md index 8674a043..5de6de3e 100644 --- a/docs/reference/telemetry.md +++ b/docs/reference/telemetry.md @@ -214,10 +214,10 @@ Records Solr version, JVM version, and hardware info as run metadata. Called onc Enables Java Flight Recorder. Injects `-XX:StartFlightRecording=...` into `SOLR_OPTS`. -**Pipeline:** `docker` or `from-distribution` only +**Pipeline:** `from-distribution` or `from-sources` (Docker support is tracked in [#138](https://github.com/apache/solr-orbit/issues/138)) **Enable:** `--telemetry jfr` -The flight recording is written to `profile.jfr` in the benchmark log directory. Requires OpenJDK 11 or later. To use a custom recording template, pass `--telemetry-params recording-template:/path/to/template.jfc`. +The flight recording is written to `profile.jfr` in the `telemetry` directory of the test run (`~/.solr-orbit/benchmarks/test-runs//telemetry/`). Requires OpenJDK 11 or later. To use a custom recording template, pass `--telemetry-params recording-template:/path/to/template.jfc`. --- @@ -225,10 +225,10 @@ The flight recording is written to `profile.jfr` in the benchmark log directory. Enables GC logging (Java 9+ `-Xlog:` format). Injects the GC log flags into `SOLR_OPTS`. -**Pipeline:** `docker` or `from-distribution` only +**Pipeline:** `from-distribution` or `from-sources` (Docker support is tracked in [#138](https://github.com/apache/solr-orbit/issues/138)) **Enable:** `--telemetry gc` -GC output is written to `gc.log` in the benchmark log directory. The default log configuration is `gc*=info,safepoint=info,age*=trace`. Override with `--telemetry-params gc-log-config:gc*=debug`. +GC output is written to `gc.log` in the `telemetry` directory of the test run (`~/.solr-orbit/benchmarks/test-runs//telemetry/`). The default log configuration is `gc*=info,safepoint=info,age*=trace`. Override with `--telemetry-params gc-log-config:gc*=debug`. --- @@ -236,10 +236,10 @@ GC output is written to `gc.log` in the benchmark log directory. The default log Enables JIT compiler logging. Injects `-XX:+LogCompilation` and related flags into `SOLR_OPTS`. -**Pipeline:** `docker` or `from-distribution` only +**Pipeline:** `from-distribution` or `from-sources` (Docker support is tracked in [#138](https://github.com/apache/solr-orbit/issues/138)) **Enable:** `--telemetry jit` -JIT output is written to `jit.log` in the benchmark log directory. +JIT output is written to `jit.log` in the `telemetry` directory of the test run (`~/.solr-orbit/benchmarks/test-runs//telemetry/`). --- @@ -280,7 +280,7 @@ Add JVM profiling devices on provisioned pipelines: ```bash solr-orbit run \ - --pipeline docker \ + --pipeline from-distribution \ --distribution-version 9.10.1 \ --workload nyc_taxis \ --telemetry gc,jfr diff --git a/solrorbit/test_run_orchestrator.py b/solrorbit/test_run_orchestrator.py index 891e53e7..251a46b9 100644 --- a/solrorbit/test_run_orchestrator.py +++ b/solrorbit/test_run_orchestrator.py @@ -38,7 +38,7 @@ from solrorbit import actor, config, doc_link, \ worker_coordinator, exceptions, builder, metrics, \ - publisher, workload, version, PROGRAM_NAME + publisher, workload, version, paths, telemetry, PROGRAM_NAME from solrorbit.builder import cluster_config as cc_module from solrorbit.builder.supplier import SourceRepository, Builder from solrorbit.builder.solr_provisioner import SolrProvisioner, SolrDockerLauncher @@ -462,6 +462,25 @@ def _load_cluster_config(cfg): return instance +def _create_jvm_telemetry_devices(cfg): + """ + Build the JVM telemetry devices (``jfr``, ``gc``, ``jit``) enabled with ``--telemetry``. + + ``SolrProvisioner`` adds the ``instrument_java_opts()`` of every device it is given to + ``SOLR_OPTS``, so only the enabled ones are returned. Their output goes to a + ``telemetry`` directory inside the test run directory. + """ + enabled_devices = cfg.opts("telemetry", "devices", mandatory=False, default_value=[]) or [] + telemetry_params = cfg.opts("telemetry", "params", mandatory=False, default_value={}) or {} + log_root = os.path.join(paths.test_run_root(cfg), "telemetry") + devices = [ + telemetry.FlightRecorder(telemetry_params, log_root, java_major_version=None), + telemetry.Gc(telemetry_params, log_root, java_major_version=None), + telemetry.JitCompiler(log_root, telemetry_params), + ] + return [device for device in devices if device.command in enabled_devices] + + def solr_from_sources(cfg): """ Clone/update the Solr source tree, build a distribution with Gradle, provision @@ -517,7 +536,8 @@ def solr_from_sources(cfg): cc_instance = _load_cluster_config(cfg) solr_modules = cfg.opts("solr", "modules", mandatory=False, default_value="") provisioner = SolrProvisioner(cache_dir=os.path.join(base_dir, "cache"), port=port, - cluster_config=cc_instance, solr_modules=solr_modules) + cluster_config=cc_instance, solr_modules=solr_modules, + telemetry_devices=_create_jvm_telemetry_devices(cfg)) try: provisioner.start(solr_root, mode="cloud") set_default_hosts(cfg, host="127.0.0.1", port=port) @@ -562,7 +582,8 @@ def solr_from_distribution(cfg): cc_instance = _load_cluster_config(cfg) solr_modules = cfg.opts("solr", "modules", mandatory=False, default_value="") provisioner = SolrProvisioner(cache_dir=cache_dir, port=port, cluster_config=cc_instance, - solr_modules=solr_modules) + solr_modules=solr_modules, + telemetry_devices=_create_jvm_telemetry_devices(cfg)) _tarball = provisioner.download(version_str) solr_root = provisioner.install(version_str, install_dir) diff --git a/tests/test_execution_orchestrator_test.py b/tests/test_execution_orchestrator_test.py index 452d0d81..464645c6 100644 --- a/tests/test_execution_orchestrator_test.py +++ b/tests/test_execution_orchestrator_test.py @@ -32,6 +32,7 @@ import pytest from solrorbit import config, exceptions, test_run_orchestrator +from solrorbit.builder.solr_provisioner import SolrProvisioner from solrorbit.utils import opts @@ -151,3 +152,81 @@ def test_check_cloud_mode_raises_for_standalone_solr(): admin_client.return_value.is_cloud_mode.return_value = False with pytest.raises(exceptions.SystemSetupError, match="not running in SolrCloud"): test_run_orchestrator._check_cloud_mode(cfg) + + +TEST_RUN_ID = "28a032d1-0b03-4579-ad2a-c65316f126e9" + + +def _provisioned_cfg(root_dir, devices, params=None): + cfg = config.Config() + cfg.add(config.Scope.benchmark, "system", "test_run.id", TEST_RUN_ID) + cfg.add(config.Scope.benchmark, "node", "root.dir", str(root_dir)) + cfg.add(config.Scope.benchmark, "telemetry", "devices", devices) + cfg.add(config.Scope.benchmark, "telemetry", "params", params or {}) + cfg.add(config.Scope.benchmark, "builder", "distribution.version", "9.10.1") + cfg.add(config.Scope.benchmark, "solr", "install_dir", str(root_dir / "install")) + cfg.add(config.Scope.benchmark, "solr", "cache_dir", str(root_dir / "cache")) + return cfg + + +def test_create_jvm_telemetry_devices_returns_only_enabled_devices(tmp_path): + cfg = _provisioned_cfg(tmp_path, ["gc", "jfr", "segment-stats"]) + + devices = test_run_orchestrator._create_jvm_telemetry_devices(cfg) + + assert sorted(device.command for device in devices) == ["gc", "jfr"] + expected_log_root = os.path.join(str(tmp_path), "test-runs", TEST_RUN_ID, "telemetry") + assert all(device.log_root == expected_log_root for device in devices) + + +def test_create_jvm_telemetry_devices_returns_nothing_without_telemetry(tmp_path): + cfg = _provisioned_cfg(tmp_path, []) + + assert test_run_orchestrator._create_jvm_telemetry_devices(cfg) == [] + + +def test_jvm_telemetry_devices_add_their_flags_to_solr_opts(tmp_path): + cfg = _provisioned_cfg(tmp_path, ["gc", "jfr", "jit"], params={"gc-log-config": "gc*=debug"}) + devices = test_run_orchestrator._create_jvm_telemetry_devices(cfg) + + provisioner = SolrProvisioner(cache_dir=str(tmp_path / "cache"), telemetry_devices=devices) + solr_opts = provisioner._build_env()["SOLR_OPTS"] + + log_root = os.path.join(str(tmp_path), "test-runs", TEST_RUN_ID, "telemetry") + assert f"-Xlog:gc*=debug:file={os.path.join(log_root, 'gc.log')}" in solr_opts + assert f"filename={os.path.join(log_root, 'profile.jfr')}" in solr_opts + assert f"-XX:LogFile={os.path.join(log_root, 'jit.log')}" in solr_opts + + +@mock.patch("solrorbit.test_run_orchestrator.run_test") +@mock.patch("solrorbit.test_run_orchestrator.set_default_hosts") +@mock.patch("solrorbit.test_run_orchestrator._load_cluster_config", return_value=None) +@mock.patch("solrorbit.test_run_orchestrator.SolrProvisioner") +def test_from_distribution_passes_jvm_telemetry_devices(provisioner_class, load_cluster_config, set_default_hosts, run_test, tmp_path): + cfg = _provisioned_cfg(tmp_path, ["gc", "jit"]) + + test_run_orchestrator.solr_from_distribution(cfg) + + devices = provisioner_class.call_args.kwargs["telemetry_devices"] + assert sorted(device.command for device in devices) == ["gc", "jit"] + run_test.assert_called_once_with(cfg, external=True) + + +@mock.patch("solrorbit.test_run_orchestrator.run_test") +@mock.patch("solrorbit.test_run_orchestrator.set_default_hosts") +@mock.patch("solrorbit.test_run_orchestrator._load_cluster_config", return_value=None) +@mock.patch("solrorbit.test_run_orchestrator.SolrProvisioner") +@mock.patch("solrorbit.test_run_orchestrator.tarfile.open") +@mock.patch("solrorbit.test_run_orchestrator.glob.glob", return_value=["/src/solr-10.0.0-SNAPSHOT.tgz"]) +@mock.patch("solrorbit.test_run_orchestrator.Builder") +@mock.patch("solrorbit.test_run_orchestrator.SourceRepository") +def test_from_sources_passes_jvm_telemetry_devices(source_repository, bldr, glob_glob, tarfile_open, provisioner_class, + load_cluster_config, set_default_hosts, run_test, tmp_path): + tarfile_open.return_value.__enter__.return_value.getnames.return_value = ["solr-10.0.0-SNAPSHOT/bin/solr"] + cfg = _provisioned_cfg(tmp_path, ["jfr"]) + + test_run_orchestrator.solr_from_sources(cfg) + + devices = provisioner_class.call_args.kwargs["telemetry_devices"] + assert [device.command for device in devices] == ["jfr"] + run_test.assert_called_once_with(cfg, external=True)