subsystem benchmarks: add cpu profiling (#2734)

Ready-to-merge version of
https://github.com/paritytech/polkadot-sdk/pull/2601

- Added optional CPU profiling
- Updated instructions how to set up Prometheus, Pyroscope and Graphana
- Added a flamegraph dashboard
<img width="1470" alt="image"
src="https://github.com/paritytech/polkadot-sdk/assets/27277055/c8f3b33d-3c01-4ec0-ac34-72d52325b6e6">

---------

Co-authored-by: ordian <write@reusable.software>
This commit is contained in:
Andrei Eres
2023-12-19 12:14:22 +01:00
committed by GitHub
parent f93f461acc
commit 526c81b138
7 changed files with 206 additions and 30 deletions
Generated
+2
View File
@@ -13322,6 +13322,8 @@ dependencies = [
"polkadot-primitives", "polkadot-primitives",
"polkadot-primitives-test-helpers", "polkadot-primitives-test-helpers",
"prometheus", "prometheus",
"pyroscope",
"pyroscope_pprofrs",
"rand 0.8.5", "rand 0.8.5",
"sc-keystore", "sc-keystore",
"sc-network", "sc-network",
+2
View File
@@ -56,6 +56,8 @@ serde = "1.0.192"
serde_yaml = "0.9" serde_yaml = "0.9"
paste = "1.0.14" paste = "1.0.14"
orchestra = { version = "0.3.3", default-features = false, features = ["futures_channel"] } orchestra = { version = "0.3.3", default-features = false, features = ["futures_channel"] }
pyroscope = "0.5.7"
pyroscope_pprofrs = "0.2.7"
[features] [features]
default = [] default = []
+49 -22
View File
@@ -26,17 +26,26 @@ The output binary will be placed in `target/testnet/subsystem-bench`.
### Test metrics ### Test metrics
Subsystem, CPU usage and network metrics are exposed via a prometheus endpoint during the test execution. Subsystem, CPU usage and network metrics are exposed via a prometheus endpoint during the test execution.
A small subset of these collected metrics are displayed in the CLI, but for an in depth analysys of the test results, A small subset of these collected metrics are displayed in the CLI, but for an in depth analysys of the test results,
a local Grafana/Prometheus stack is needed. a local Grafana/Prometheus stack is needed.
### Run Prometheus, Pyroscope and Graphana in Docker
If docker is not usable, then follow the next sections to manually install Prometheus, Pyroscope and Graphana on your machine.
```bash
cd polkadot/node/subsystem-bench/docker
docker compose up
```
### Install Prometheus ### Install Prometheus
Please follow the [official installation guide](https://prometheus.io/docs/prometheus/latest/installation/) for your Please follow the [official installation guide](https://prometheus.io/docs/prometheus/latest/installation/) for your
platform/OS. platform/OS.
After succesfully installing and starting up Prometheus, we need to alter it's configuration such that it After succesfully installing and starting up Prometheus, we need to alter it's configuration such that it
will scrape the benchmark prometheus endpoint `127.0.0.1:9999`. Please check the prometheus official documentation will scrape the benchmark prometheus endpoint `127.0.0.1:9999`. Please check the prometheus official documentation
regarding the location of `prometheus.yml`. On MacOS for example the full path `/opt/homebrew/etc/prometheus.yml` regarding the location of `prometheus.yml`. On MacOS for example the full path `/opt/homebrew/etc/prometheus.yml`
prometheus.yml: prometheus.yml:
@@ -57,13 +66,29 @@ scrape_configs:
To complete this step restart Prometheus server such that it picks up the new configuration. To complete this step restart Prometheus server such that it picks up the new configuration.
### Install and setup Grafana ### Install Pyroscope
To collect CPU profiling data, you must be running the Pyroscope server.
Follow the [installation guide](https://grafana.com/docs/pyroscope/latest/get-started/)
relevant to your operating system.
### Install Grafana
Follow the [installation guide](https://grafana.com/docs/grafana/latest/setup-grafana/installation/) relevant Follow the [installation guide](https://grafana.com/docs/grafana/latest/setup-grafana/installation/) relevant
to your operating system. to your operating system.
Once you have the installation up and running, configure the local Prometheus as a data source by following ### Setup Grafana
[this guide](https://grafana.com/docs/grafana/latest/datasources/prometheus/configure-prometheus-data-source/)
Once you have the installation up and running, configure the local Prometheus and Pyroscope (if needed)
as data sources by following these guides:
- [Prometheus](https://grafana.com/docs/grafana/latest/datasources/prometheus/configure-prometheus-data-source/)
- [Pyroscope](https://grafana.com/docs/grafana/latest/datasources/grafana-pyroscope/)
If you are running the servers in Docker, use the following URLs:
- Prometheus `http://prometheus:9090/`
- Pyroscope `http://pyroscope:4040/`
#### Import dashboards #### Import dashboards
@@ -86,26 +111,29 @@ Commands:
``` ```
Note: `test-sequence` is a special test objective that wraps up an arbitrary number of test objectives. It is tipically Note: `test-sequence` is a special test objective that wraps up an arbitrary number of test objectives. It is tipically
used to run a suite of tests defined in a `yaml` file like in this [example](examples/availability_read.yaml). used to run a suite of tests defined in a `yaml` file like in this [example](examples/availability_read.yaml).
### Standard test options ### Standard test options
``` ```
Options: Options:
--network <NETWORK> The type of network to be emulated [default: ideal] [possible values: --network <NETWORK> The type of network to be emulated [default: ideal] [possible values:
ideal, healthy, degraded] ideal, healthy, degraded]
--n-cores <N_CORES> Number of cores to fetch availability for [default: 100] --n-cores <N_CORES> Number of cores to fetch availability for [default: 100]
--n-validators <N_VALIDATORS> Number of validators to fetch chunks from [default: 500] --n-validators <N_VALIDATORS> Number of validators to fetch chunks from [default: 500]
--min-pov-size <MIN_POV_SIZE> The minimum pov size in KiB [default: 5120] --min-pov-size <MIN_POV_SIZE> The minimum pov size in KiB [default: 5120]
--max-pov-size <MAX_POV_SIZE> The maximum pov size bytes [default: 5120] --max-pov-size <MAX_POV_SIZE> The maximum pov size bytes [default: 5120]
-n, --num-blocks <NUM_BLOCKS> The number of blocks the test is going to run [default: 1] -n, --num-blocks <NUM_BLOCKS> The number of blocks the test is going to run [default: 1]
-p, --peer-bandwidth <PEER_BANDWIDTH> The bandwidth of simulated remote peers in KiB -p, --peer-bandwidth <PEER_BANDWIDTH> The bandwidth of simulated remote peers in KiB
-b, --bandwidth <BANDWIDTH> The bandwidth of our simulated node in KiB -b, --bandwidth <BANDWIDTH> The bandwidth of our simulated node in KiB
--peer-error <PEER_ERROR> Simulated conection error ratio [0-100] --peer-error <PEER_ERROR> Simulated conection error ratio [0-100]
--peer-min-latency <PEER_MIN_LATENCY> Minimum remote peer latency in milliseconds [0-5000] --peer-min-latency <PEER_MIN_LATENCY> Minimum remote peer latency in milliseconds [0-5000]
--peer-max-latency <PEER_MAX_LATENCY> Maximum remote peer latency in milliseconds [0-5000] --peer-max-latency <PEER_MAX_LATENCY> Maximum remote peer latency in milliseconds [0-5000]
-h, --help Print help --profile Enable CPU Profiling with Pyroscope
-V, --version Print version --pyroscope-url <PYROSCOPE_URL> Pyroscope Server URL [default: http://localhost:4040]
--pyroscope-sample-rate <PYROSCOPE_SAMPLE_RATE> Pyroscope Sample Rate [default: 113]
-h, --help Print help
-V, --version Print version
``` ```
These apply to all test objectives, except `test-sequence` which relies on the values being specified in a file. These apply to all test objectives, except `test-sequence` which relies on the values being specified in a file.
@@ -192,8 +220,7 @@ view the test progress in real time by accessing [this link](http://localhost:30
Now run Now run
`target/testnet/subsystem-bench test-sequence --path polkadot/node/subsystem-bench/examples/availability_read.yaml` `target/testnet/subsystem-bench test-sequence --path polkadot/node/subsystem-bench/examples/availability_read.yaml`
and view the metrics in real time and spot differences between different `n_valiator` values. and view the metrics in real time and spot differences between different `n_validators` values.
## Create new test objectives ## Create new test objectives
This tool is intended to make it easy to write new test objectives that focus individual subsystems, This tool is intended to make it easy to write new test objectives that focus individual subsystems,
@@ -0,0 +1,35 @@
services:
grafana:
image: grafana/grafana-enterprise:latest
container_name: grafana
restart: always
networks:
- subsystem-bench
ports:
- "3000:3000"
prometheus:
image: prom/prometheus:latest
container_name: prometheus
restart: always
networks:
- subsystem-bench
volumes:
- ./prometheus:/etc/prometheus
extra_hosts:
- "host.docker.internal:host-gateway"
ports:
- "9090:9090"
- "9999:9999"
pyroscope:
container_name: pyroscope
image: grafana/pyroscope:latest
restart: always
networks:
- subsystem-bench
ports:
- "4040:4040"
networks:
subsystem-bench:
@@ -0,0 +1,11 @@
global:
scrape_interval: 5s
scrape_configs:
- job_name: "prometheus"
static_configs:
- targets: ["localhost:9090"]
- job_name: "subsystem-bench"
scrape_interval: 0s500ms
static_configs:
- targets: ['host.docker.internal:9999']
@@ -0,0 +1,70 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "grafana",
"uid": "-- Grafana --"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": 1,
"links": [],
"liveNow": false,
"panels": [
{
"datasource": {
"type": "grafana-pyroscope-datasource",
"uid": "bc3bc04f-85f9-464b-8ae3-fbe0949063f6"
},
"gridPos": {
"h": 18,
"w": 24,
"x": 0,
"y": 0
},
"id": 1,
"targets": [
{
"datasource": {
"type": "grafana-pyroscope-datasource",
"uid": "bc3bc04f-85f9-464b-8ae3-fbe0949063f6"
},
"groupBy": [],
"labelSelector": "{service_name=\"subsystem-bench\"}",
"profileTypeId": "process_cpu:cpu:nanoseconds:cpu:nanoseconds",
"queryType": "profile",
"refId": "A"
}
],
"title": "CPU Profiling",
"type": "flamegraph"
}
],
"refresh": "",
"schemaVersion": 38,
"tags": [],
"templating": {
"list": []
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {},
"timezone": "",
"title": "CPU Profiling",
"uid": "c31191d5-fe2b-49e2-8b1c-1451f31d1628",
"version": 1,
"weekStart": ""
}
@@ -18,6 +18,8 @@
//! CI regression testing. //! CI regression testing.
use clap::Parser; use clap::Parser;
use color_eyre::eyre; use color_eyre::eyre;
use pyroscope::PyroscopeAgent;
use pyroscope_pprofrs::{pprof_backend, PprofConfig};
use colored::Colorize; use colored::Colorize;
use std::{path::Path, time::Duration}; use std::{path::Path, time::Duration};
@@ -76,12 +78,34 @@ struct BenchCli {
/// Maximum remote peer latency in milliseconds [0-5000]. /// Maximum remote peer latency in milliseconds [0-5000].
pub peer_max_latency: Option<u64>, pub peer_max_latency: Option<u64>,
#[clap(long, default_value_t = false)]
/// Enable CPU Profiling with Pyroscope
pub profile: bool,
#[clap(long, requires = "profile", default_value_t = String::from("http://localhost:4040"))]
/// Pyroscope Server URL
pub pyroscope_url: String,
#[clap(long, requires = "profile", default_value_t = 113)]
/// Pyroscope Sample Rate
pub pyroscope_sample_rate: u32,
#[command(subcommand)] #[command(subcommand)]
pub objective: cli::TestObjective, pub objective: cli::TestObjective,
} }
impl BenchCli { impl BenchCli {
fn launch(self) -> eyre::Result<()> { fn launch(self) -> eyre::Result<()> {
let agent_running = if self.profile {
let agent = PyroscopeAgent::builder(self.pyroscope_url.as_str(), "subsystem-bench")
.backend(pprof_backend(PprofConfig::new().sample_rate(self.pyroscope_sample_rate)))
.build()?;
Some(agent.start()?)
} else {
None
};
let configuration = self.standard_configuration; let configuration = self.standard_configuration;
let mut test_config = match self.objective { let mut test_config = match self.objective {
TestObjective::TestSequence(options) => { TestObjective::TestSequence(options) => {
@@ -165,6 +189,11 @@ impl BenchCli {
env.runtime() env.runtime()
.block_on(availability::benchmark_availability_read(&mut env, state)); .block_on(availability::benchmark_availability_read(&mut env, state));
if let Some(agent_running) = agent_running {
let agent_ready = agent_running.stop()?;
agent_ready.shutdown();
}
Ok(()) Ok(())
} }
} }