Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions docs/reference/telemetry.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,32 +214,32 @@ 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/<test-run-id>/telemetry/`). Requires OpenJDK 11 or later. To use a custom recording template, pass `--telemetry-params recording-template:/path/to/template.jfc`.

---

### gc

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/<test-run-id>/telemetry/`). The default log configuration is `gc*=info,safepoint=info,age*=trace`. Override with `--telemetry-params gc-log-config:gc*=debug`.

---

### jit

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/<test-run-id>/telemetry/`).

---

Expand Down Expand Up @@ -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
Expand Down
27 changes: 24 additions & 3 deletions solrorbit/test_run_orchestrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)

Expand Down
79 changes: 79 additions & 0 deletions tests/test_execution_orchestrator_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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)