Skip to content

Commit e09a1fe

Browse files
pb8odianpopa
authored andcommitted
Add --metrics-path option
Signed-off-by: Pablo Barbáchano <[email protected]>
1 parent 00c868b commit e09a1fe

File tree

8 files changed

+155
-9
lines changed

8 files changed

+155
-9
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
making it safe to snapshot uVMs running on a newer host CPU (Cascade Lake)
1212
and restore on a host that has a Skylake CPU.
1313

14+
- Added a new CLI option `--metrics-path PATH`. It accepts a file parameter
15+
where metrics will be sent to.
16+
1417
### Fixed
1518

1619
- Make the `T2` template more robust by explicitly disabling additional

docs/metrics.md

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
# Firecracker Metrics Configuration
22

3-
For the metrics capability, Firecracker uses a single Metrics system.
4-
This system can be configured by sending a `PUT` API Request to the
5-
`/metrics` path.
3+
For the metrics capability, Firecracker uses a single Metrics system. This
4+
system can be configured either by: a) sending a `PUT` API Request to the
5+
`/metrics` path: or b) using the `--metrics-path` CLI option.
6+
7+
Note the metrics configuration is **not** part of the guest configuration and is
8+
not restored from a snapshot.
69

710
## Prerequisites
811

@@ -17,7 +20,15 @@ mkfifo metrics.fifo
1720
touch metrics.file
1821
```
1922

20-
## Configuring the system
23+
## Configuring the system via CLI
24+
25+
When launching Firecracker, use the CLI option to set the metrics file.
26+
27+
```bash
28+
./firecracker --metrics-path metrics.fifo
29+
```
30+
31+
## Configuring the system via API
2132

2233
You can configure the Metrics system by sending the following API command:
2334

src/api_server/swagger/firecracker.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ paths:
366366
204:
367367
description: Metrics system created.
368368
400:
369-
description: Metrics system cannot be initialized due to bad input.
369+
description: Metrics system cannot be initialized due to bad input request or metrics system already initialized.
370370
schema:
371371
$ref: "#/definitions/Error"
372372
default:

src/firecracker/src/main.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use vmm::signal_handler::register_signal_handlers;
2121
use vmm::version_map::{FC_VERSION_TO_SNAP_VERSION, VERSION_MAP};
2222
use vmm::vmm_config::instance_info::{InstanceInfo, VmState};
2323
use vmm::vmm_config::logger::{init_logger, LoggerConfig, LoggerLevel};
24+
use vmm::vmm_config::metrics::{init_metrics, MetricsConfig};
2425
use vmm::{EventManager, FcExitCode, HTTP_MAX_PAYLOAD_SIZE};
2526

2627
// The reason we place default API socket under /run is that API socket is a
@@ -193,6 +194,11 @@ fn main_exitable() -> FcExitCode {
193194
"Whether or not to include the file path and line number of the log's origin.",
194195
),
195196
)
197+
.arg(
198+
Argument::new("metrics-path")
199+
.takes_value(true)
200+
.help("Path to a fifo or a file used for configuring the metrics on startup."),
201+
)
196202
.arg(Argument::new("boot-timer").takes_value(false).help(
197203
"Whether or not to load boot timer device for logging elapsed time since \
198204
InstanceStart command.",
@@ -289,7 +295,16 @@ fn main_exitable() -> FcExitCode {
289295
show_log_origin,
290296
);
291297
if let Err(err) = init_logger(logger_config, &instance_info) {
292-
return generic_error_exit(&format!("Could not initialize logger:: {}", err));
298+
return generic_error_exit(&format!("Could not initialize logger: {}", err));
299+
};
300+
}
301+
302+
if let Some(metrics_path) = arguments.single_value("metrics-path") {
303+
let metrics_config = MetricsConfig {
304+
metrics_path: PathBuf::from(metrics_path),
305+
};
306+
if let Err(err) = init_metrics(metrics_config) {
307+
return generic_error_exit(&format!("Could not initialize metrics: {}", err));
293308
};
294309
}
295310

src/logger/src/metrics.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
//! which we are capturing specific metrics.
1010
//!
1111
//! ## JSON example with metrics:
12-
//! ```bash
12+
//! ```json
1313
//! {
1414
//! "utc_timestamp_ms": 1541591155180,
1515
//! "api_server": {

tests/framework/microvm.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import shutil
1919
import time
2020
import weakref
21+
from pathlib import Path
2122

2223
from threading import Lock
2324
from retry import retry
@@ -508,6 +509,7 @@ def spawn(
508509
log_file="log_fifo",
509510
log_level="Info",
510511
use_ramdisk=False,
512+
metrics_path=None,
511513
):
512514
"""Start a microVM as a daemon or in a screen session."""
513515
# pylint: disable=subprocess-run-check
@@ -547,6 +549,11 @@ def spawn(
547549
self.jailer.extra_args.update({"log-path": log_file, "level": log_level})
548550
self.start_console_logger(log_fifo)
549551

552+
if metrics_path is not None:
553+
self.create_jailed_resource(metrics_path, create_jail=True)
554+
metrics_path = Path(metrics_path)
555+
self.jailer.extra_args.update({"metrics-path": metrics_path.name})
556+
550557
if self.metadata_file:
551558
if os.path.exists(self.metadata_file):
552559
LOG.debug("metadata file exists, adding as a jailed resource")

tests/integration_tests/build/test_coverage.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@
2929
# Checkout the cpuid crate. In the future other
3030
# differences may appear.
3131
if utils.is_io_uring_supported():
32-
COVERAGE_DICT = {"Intel": 84.77, "AMD": 84.11, "ARM": 83.86}
32+
COVERAGE_DICT = {"Intel": 84.77, "AMD": 84.11, "ARM": 83.78}
3333
else:
34-
COVERAGE_DICT = {"Intel": 81.89, "AMD": 81.25, "ARM": 80.95}
34+
COVERAGE_DICT = {"Intel": 81.89, "AMD": 81.25, "ARM": 80.86}
3535

3636
PROC_MODEL = proc.proc_type()
3737

tests/integration_tests/functional/test_cmd_line_parameters.py

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44

55
import logging
66
import platform
7+
from pathlib import Path
78

9+
import host_tools.logging as log_tools
810
from host_tools.cargo_build import get_firecracker_binaries
911
from conftest import _test_images_s3_bucket
1012
from framework.artifacts import ArtifactCollection
@@ -80,3 +82,111 @@ def test_describe_snapshot_all_versions(bin_cloner_path):
8082
assert code == 0
8183
assert stderr == ""
8284
assert target_version in stdout
85+
86+
87+
def test_cli_metrics_path(test_microvm_with_api):
88+
"""
89+
Test --metrics-path parameter
90+
91+
@type: functional
92+
"""
93+
microvm = test_microvm_with_api
94+
metrics_fifo_path = Path(microvm.path) / "metrics_ndjson.fifo"
95+
metrics_fifo = log_tools.Fifo(metrics_fifo_path)
96+
microvm.spawn(metrics_path=metrics_fifo_path)
97+
microvm.basic_config()
98+
microvm.start()
99+
100+
metrics = microvm.flush_metrics(metrics_fifo)
101+
102+
exp_keys = [
103+
"utc_timestamp_ms",
104+
"api_server",
105+
"balloon",
106+
"block",
107+
"deprecated_api",
108+
"get_api_requests",
109+
"i8042",
110+
"latencies_us",
111+
"logger",
112+
"mmds",
113+
"net",
114+
"patch_api_requests",
115+
"put_api_requests",
116+
"seccomp",
117+
"vcpu",
118+
"vmm",
119+
"uart",
120+
"signals",
121+
"vsock",
122+
]
123+
124+
if platform.machine() == "aarch64":
125+
exp_keys.append("rtc")
126+
127+
assert set(metrics.keys()) == set(exp_keys)
128+
129+
130+
def test_cli_metrics_path_if_metrics_initialized_twice_fail(test_microvm_with_api):
131+
"""
132+
Given: a running firecracker with metrics configured with the CLI option
133+
When: Configure metrics via API
134+
Then: API returns an error
135+
136+
@type: functional
137+
"""
138+
microvm = test_microvm_with_api
139+
140+
# First configure the µvm metrics with --metrics-path
141+
metrics_path = Path(microvm.path) / "metrics.ndjson"
142+
metrics_path.touch()
143+
microvm.spawn(metrics_path=metrics_path)
144+
145+
# Then try to configure it with PUT /metrics
146+
metrics2_path = Path(microvm.path) / "metrics2.ndjson"
147+
metrics2_path.touch()
148+
response = microvm.metrics.put(
149+
metrics_path=microvm.create_jailed_resource(metrics2_path)
150+
)
151+
152+
# It should fail with HTTP 400 because it's already configured
153+
assert response.status_code == 400
154+
assert response.json() == {
155+
"fault_message": "Reinitialization of metrics not allowed."
156+
}
157+
158+
159+
def test_cli_metrics_if_resume_no_metrics(test_microvm_with_api, microvm_factory):
160+
"""
161+
Check that metrics configuration is not part of the snapshot
162+
163+
@type: functional
164+
"""
165+
# Given: a snapshot of a FC with metrics configured with the CLI option
166+
uvm1 = test_microvm_with_api
167+
metrics_path = Path(uvm1.path) / "metrics.ndjson"
168+
metrics_path.touch()
169+
uvm1.spawn(metrics_path=metrics_path)
170+
uvm1.basic_config()
171+
uvm1.start()
172+
173+
mem_path = Path(uvm1.jailer.chroot_path()) / "test.mem"
174+
snapshot_path = Path(uvm1.jailer.chroot_path()) / "test.snap"
175+
uvm1.pause_to_snapshot(
176+
mem_file_path=mem_path.name,
177+
snapshot_path=snapshot_path.name,
178+
)
179+
assert mem_path.exists()
180+
181+
# When: restoring from the snapshot
182+
uvm2 = microvm_factory.build()
183+
uvm2.spawn()
184+
uvm2.restore_from_snapshot(
185+
snapshot_vmstate=snapshot_path,
186+
snapshot_mem=mem_path,
187+
snapshot_disks=[uvm1.rootfs_file],
188+
)
189+
190+
# Then: the old metrics configuration does not exist
191+
metrics2 = Path(uvm2.jailer.chroot_path()) / metrics_path.name
192+
assert not metrics2.exists()

0 commit comments

Comments
 (0)