-
Notifications
You must be signed in to change notification settings - Fork 317
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: initial draft of custom metric tool and systemd timer
- Loading branch information
1 parent
b545f6b
commit f8849ba
Showing
9 changed files
with
335 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
33 changes: 33 additions & 0 deletions
33
ic-os/components/monitoring/custom-metrics/metrics_tool.service
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
[Unit] | ||
Description=Report custom metrics once per minute | ||
|
||
[Service] | ||
Type=oneshot | ||
ExecStart=/opt/ic/bin/metrics_tool --metrics /run/node_exporter/collector_textfile/custom_metrics.prom | ||
DeviceAllow=/dev/vda | ||
IPAddressDeny=any | ||
LockPersonality=yes | ||
MemoryDenyWriteExecute=yes | ||
NoNewPrivileges=yes | ||
PrivateDevices=no | ||
PrivateNetwork=yes | ||
PrivateTmp=yes | ||
PrivateUsers=no | ||
ProtectClock=yes | ||
ProtectControlGroups=yes | ||
ProtectHome=yes | ||
ProtectHostname=yes | ||
ProtectKernelModules=yes | ||
ProtectKernelTunables=yes | ||
ProtectSystem=strict | ||
ReadOnlyPaths=/proc/interrupts | ||
ReadWritePaths=/run/node_exporter/collector_textfile | ||
RestrictAddressFamilies=AF_UNIX | ||
RestrictAddressFamilies=~AF_UNIX | ||
RestrictNamespaces=yes | ||
RestrictRealtime=yes | ||
RestrictSUIDSGID=yes | ||
SystemCallArchitectures=native | ||
SystemCallErrorNumber=EPERM | ||
SystemCallFilter=@system-service | ||
UMask=022 |
10 changes: 10 additions & 0 deletions
10
ic-os/components/monitoring/custom-metrics/metrics_tool.timer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
[Unit] | ||
Description=Collect custom metrics every minute | ||
|
||
[Timer] | ||
OnBootSec=60s | ||
OnUnitActiveSec=60s | ||
Unit=metrics_tool.service | ||
|
||
[Install] | ||
WantedBy=timers.target |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_library", "rust_test", "rust_test_suite") | ||
|
||
package(default_visibility = ["//rs:ic-os-pkg"]) | ||
|
||
DEPENDENCIES = [ | ||
# Keep sorted. | ||
"//rs/sys", | ||
"@crate_index//:anyhow", | ||
"@crate_index//:clap", | ||
] | ||
|
||
DEV_DEPENDENCIES = [ | ||
# Keep sorted. | ||
] | ||
|
||
MACRO_DEPENDENCIES = [] | ||
|
||
ALIASES = {} | ||
|
||
rust_library( | ||
name = "metrics_tool", | ||
srcs = glob( | ||
["src/**/*.rs"], | ||
exclude = ["src/main.rs"], | ||
), | ||
aliases = ALIASES, | ||
crate_name = "ic_metrics_tool", | ||
proc_macro_deps = MACRO_DEPENDENCIES, | ||
visibility = ["//rs:system-tests-pkg"], | ||
deps = DEPENDENCIES, | ||
) | ||
|
||
rust_binary( | ||
name = "metrics_tool_bin", | ||
srcs = ["src/main.rs"], | ||
aliases = ALIASES, | ||
proc_macro_deps = MACRO_DEPENDENCIES, | ||
deps = DEPENDENCIES + [":metrics_tool"], | ||
) | ||
|
||
rust_test( | ||
name = "metrics_tool_test", | ||
crate = ":metrics_tool", | ||
deps = DEPENDENCIES + DEV_DEPENDENCIES, | ||
) | ||
|
||
rust_test_suite( | ||
name = "metrics_tool_integration", | ||
srcs = glob(["tests/**/*.rs"]), | ||
target_compatible_with = [ | ||
"@platforms//os:linux", | ||
], | ||
deps = [":metrics_tool_bin"] + DEPENDENCIES + DEV_DEPENDENCIES, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
[package] | ||
name = "ic-metrics-tool" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
[[bin]] | ||
name = "metrics_tool" | ||
path = "src/main.rs" | ||
|
||
[dependencies] | ||
anyhow = { workspace = true } | ||
clap = { workspace = true } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
// TODO: refactor/merge this with fstrim_tool and guestos_tool metrics functionality | ||
use std::fs::File; | ||
use std::io::{self, Write}; | ||
use std::path::Path; | ||
|
||
// TODO: everything is floating point for now | ||
pub struct Metric { | ||
name: String, | ||
value: f64, | ||
annotation: String, | ||
labels: Vec<(String, String)>, | ||
} | ||
|
||
impl Metric { | ||
pub fn new(name: &str, value: f64) -> Self { | ||
Self { | ||
name: name.to_string(), | ||
value, | ||
annotation: "Custom metric".to_string(), | ||
labels: Vec::new(), | ||
} | ||
} | ||
pub fn with_annotation(name: &str, value: f64, annotation: &str) -> Self { | ||
Self { | ||
name: name.to_string(), | ||
value, | ||
annotation: annotation.to_string(), | ||
labels: Vec::new(), | ||
} | ||
} | ||
|
||
pub fn add_annotation(mut self, annotation: &str) -> Self { | ||
self.annotation = annotation.to_string(); | ||
self | ||
} | ||
|
||
pub fn add_label(mut self, key: &str, value: &str) -> Self { | ||
self.labels.push((key.to_string(), value.to_string())); | ||
self | ||
} | ||
|
||
// TODO: formatting of floats | ||
pub fn to_string(&self) -> String { | ||
let labels_str = if self.labels.is_empty() { | ||
String::new() | ||
} else { | ||
let labels: Vec<String> = self.labels.iter() | ||
.map(|(k, v)| format!("{}=\"{}\"", k, v)) | ||
.collect(); | ||
format!("{{{}}}", labels.join(",")) | ||
}; | ||
format!("# HELP {} {}\n\ | ||
# TYPE {} counter\n\ | ||
{}{} {}", self.name, self.annotation, self.name, self.name, labels_str, self.value) | ||
} | ||
} | ||
|
||
pub struct MetricsWriter { | ||
file_path: String, | ||
} | ||
|
||
impl MetricsWriter { | ||
pub fn new(file_path: &str) -> Self { | ||
Self { | ||
file_path: file_path.to_string(), | ||
} | ||
} | ||
|
||
pub fn write_metrics(&self, metrics: &[Metric]) -> io::Result<()> { | ||
let path = Path::new(&self.file_path); | ||
let mut file = File::create(&path)?; | ||
for metric in metrics { | ||
writeln!(file, "{}", metric.to_string())?; | ||
} | ||
Ok(()) | ||
} | ||
} | ||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn test_metric_to_string() { | ||
let metric = Metric::new("test_metric", 123.45) | ||
.add_label("label1", "value1") | ||
.add_label("label2", "value2"); | ||
assert_eq!(metric.to_string(), "# HELP test_metric Custom metric\n\ | ||
# TYPE test_metric counter\n\ | ||
test_metric{label1=\"value1\",label2=\"value2\"} 123.45"); | ||
} | ||
|
||
#[test] | ||
fn test_write_metrics() { | ||
let metrics = vec![ | ||
Metric::new("metric1", 1.0), | ||
Metric::new("metric2", 2.0).add_label("label", "value"), | ||
]; | ||
let writer = MetricsWriter::new("/tmp/test_metrics.prom"); | ||
writer.write_metrics(&metrics).unwrap(); | ||
let content = std::fs::read_to_string("/tmp/test_metrics.prom").unwrap(); | ||
assert!(content.contains("# HELP metric1 Custom metric\n\ | ||
# TYPE metric1 counter\n\ | ||
metric1 1")); | ||
assert!(content.contains("# HELP metric2 Custom metric\n\ | ||
# TYPE metric2 counter\n\ | ||
metric2{label=\"value\"} 2")); | ||
} | ||
|
||
#[test] | ||
fn test_metric_large_value() { | ||
let metric = Metric::new("large_value_metric", 1.0e64); | ||
assert_eq!(metric.to_string(), "# HELP large_value_metric Custom metric\n\ | ||
# TYPE large_value_metric counter\n\ | ||
large_value_metric 10000000000000000000000000000000000000000000000000000000000000000"); | ||
} | ||
|
||
|
||
#[test] | ||
fn test_metric_without_labels() { | ||
let metric = Metric::new("no_label_metric", 42.0); | ||
assert_eq!(metric.to_string(), "# HELP no_label_metric Custom metric\n\ | ||
# TYPE no_label_metric counter\n\ | ||
no_label_metric 42"); | ||
} | ||
|
||
#[test] | ||
fn test_metric_with_annotation() { | ||
let metric = Metric::with_annotation("annotated_metric", 99.9, "This is a test metric"); | ||
assert_eq!(metric.to_string(), "# HELP annotated_metric This is a test metric\n\ | ||
# TYPE annotated_metric counter\n\ | ||
annotated_metric 99.9"); | ||
} | ||
|
||
#[test] | ||
fn test_write_empty_metrics() { | ||
let metrics: Vec<Metric> = Vec::new(); | ||
let writer = MetricsWriter::new("/tmp/test_empty_metrics.prom"); | ||
writer.write_metrics(&metrics).unwrap(); | ||
let content = std::fs::read_to_string("/tmp/test_empty_metrics.prom").unwrap(); | ||
assert!(content.is_empty()); | ||
} | ||
|
||
#[test] | ||
fn test_metric_with_multiple_labels() { | ||
let metric = Metric::new("multi_label_metric", 10.0) | ||
.add_label("foo", "bar") | ||
.add_label("version", "1.0.0"); | ||
assert_eq!(metric.to_string(), "# HELP multi_label_metric Custom metric\n\ | ||
# TYPE multi_label_metric counter\n\ | ||
multi_label_metric{foo=\"bar\",version=\"1.0.0\"} 10"); | ||
} | ||
|
||
#[test] | ||
fn test_metric_with_empty_annotation() { | ||
let metric = Metric::with_annotation("empty_annotation_metric", 5.5, ""); | ||
assert_eq!(metric.to_string(), "# HELP empty_annotation_metric \n\ | ||
# TYPE empty_annotation_metric counter\n\ | ||
empty_annotation_metric 5.5"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
use anyhow::Result; | ||
use clap::Parser; | ||
|
||
use std::path::Path; | ||
use std::fs::File; | ||
use std::io::{self, BufRead}; | ||
|
||
use ic_metrics_tool::{Metric, MetricsWriter}; | ||
|
||
const INTERRUPT_FILTER: &str = "TLB shootdowns"; | ||
const INTERRUPT_SOURCE: &str = "/proc/interrupts"; | ||
const CUSTOM_METRICS_PROM: &str = "/run/node_exporter/collector_textfile/custom_metrics.prom"; | ||
const TLB_SHOOTDOWN_METRIC_NAME: &str = "sum_tlb_shootdowns"; | ||
|
||
#[derive(Parser)] | ||
struct MetricToolArgs { | ||
#[arg( | ||
short = 'm', | ||
long = "metrics", | ||
default_value = CUSTOM_METRICS_PROM | ||
)] | ||
/// Filename to write the prometheus metrics for node_exporter generation. | ||
/// Fails badly if the directory doesn't exist. | ||
metrics_filename: String, | ||
} | ||
|
||
fn get_sum_tlb_shootdowns() -> Result<u64> { | ||
let path = Path::new(INTERRUPT_SOURCE); | ||
let file = File::open(&path)?; | ||
let reader = io::BufReader::new(file); | ||
|
||
let mut total_tlb_shootdowns = 0; | ||
|
||
for line in reader.lines() { | ||
let line = line?; | ||
if line.contains(INTERRUPT_FILTER) { | ||
let parts: Vec<&str> = line.split_whitespace().collect(); | ||
for part in parts.iter().skip(1) { | ||
if let Ok(value) = part.parse::<u64>() { | ||
total_tlb_shootdowns += value; | ||
} | ||
} | ||
} | ||
} | ||
|
||
Ok(total_tlb_shootdowns) | ||
} | ||
|
||
pub fn main() -> Result<()> { | ||
let opts = MetricToolArgs::parse(); | ||
let mpath = Path::new(&opts.metrics_filename); | ||
let tlb_shootdowns = get_sum_tlb_shootdowns()?; | ||
|
||
let metrics = vec![ | ||
Metric::new(TLB_SHOOTDOWN_METRIC_NAME, tlb_shootdowns as f64).add_annotation("Total TLB shootdowns"), | ||
]; | ||
let writer = MetricsWriter::new(mpath.to_str().unwrap()); | ||
writer.write_metrics(&metrics).unwrap(); | ||
|
||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters