vmm/devices/virtio/net/metrics.rs
1// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Defines the metrics system for Network devices.
5//!
6//! # Metrics format
7//! The metrics are flushed in JSON when requested by vmm::logger::metrics::METRICS.write().
8//!
9//! ## JSON example with metrics:
10//! ```json
11//! {
12//! "net_eth0": {
13//! "activate_fails": "SharedIncMetric",
14//! "cfg_fails": "SharedIncMetric",
15//! "mac_address_updates": "SharedIncMetric",
16//! "no_rx_avail_buffer": "SharedIncMetric",
17//! "no_tx_avail_buffer": "SharedIncMetric",
18//! ...
19//! }
20//! "net_eth1": {
21//! "activate_fails": "SharedIncMetric",
22//! "cfg_fails": "SharedIncMetric",
23//! "mac_address_updates": "SharedIncMetric",
24//! "no_rx_avail_buffer": "SharedIncMetric",
25//! "no_tx_avail_buffer": "SharedIncMetric",
26//! ...
27//! }
28//! ...
29//! "net_iface_id": {
30//! "activate_fails": "SharedIncMetric",
31//! "cfg_fails": "SharedIncMetric",
32//! "mac_address_updates": "SharedIncMetric",
33//! "no_rx_avail_buffer": "SharedIncMetric",
34//! "no_tx_avail_buffer": "SharedIncMetric",
35//! ...
36//! }
37//! "net": {
38//! "activate_fails": "SharedIncMetric",
39//! "cfg_fails": "SharedIncMetric",
40//! "mac_address_updates": "SharedIncMetric",
41//! "no_rx_avail_buffer": "SharedIncMetric",
42//! "no_tx_avail_buffer": "SharedIncMetric",
43//! ...
44//! }
45//! }
46//! ```
47//! Each `net` field in the example above is a serializable `NetDeviceMetrics` structure
48//! collecting metrics such as `activate_fails`, `cfg_fails`, etc. for the network device.
49//! `net_eth0` represent metrics for the endpoint "/network-interfaces/eth0",
50//! `net_eth1` represent metrics for the endpoint "/network-interfaces/eth1", and
51//! `net_iface_id` represent metrics for the endpoint "/network-interfaces/{iface_id}"
52//! network device respectively and `net` is the aggregate of all the per device metrics.
53//!
54//! # Limitations
55//! Network device currently do not have `vmm::logger::metrics::StoreMetrics` so aggregate
56//! doesn't consider them.
57//!
58//! # Design
59//! The main design goals of this system are:
60//! * To improve network device metrics by logging them at per device granularity.
61//! * Continue to provide aggregate net metrics to maintain backward compatibility.
62//! * Move NetDeviceMetrics out of from logger and decouple it.
63//! * Use lockless operations, preferably ones that don't require anything other than simple
64//! reads/writes being atomic.
65//! * Rely on `serde` to provide the actual serialization for writing the metrics.
66//! * Since all metrics start at 0, we implement the `Default` trait via derive for all of them, to
67//! avoid having to initialize everything by hand.
68//!
69//! * Devices could be created in any order i.e. the first device created could either be eth0 or
70//! eth1 so if we use a vector for NetDeviceMetrics and call 1st device as net0, then net0 could
71//! sometimes point to eth0 and sometimes to eth1 which doesn't help with analysing the metrics.
72//! So, use Map instead of Vec to help understand which interface the metrics actually belongs to.
73//! * We use "net_$iface_id" for the metrics name instead of "net_$tap_name" to be consistent with
74//! the net endpoint "/network-interfaces/{iface_id}".
75//!
76//! The system implements 1 types of metrics:
77//! * Shared Incremental Metrics (SharedIncMetrics) - dedicated for the metrics which need a counter
78//! (i.e the number of times an API request failed). These metrics are reset upon flush.
79//!
80//! We use net::metrics::METRICS instead of adding an entry of NetDeviceMetrics
81//! in Net so that metrics are accessible to be flushed even from signal handlers.
82
83use std::collections::BTreeMap;
84use std::sync::{Arc, RwLock};
85
86use serde::ser::SerializeMap;
87use serde::{Serialize, Serializer};
88
89use crate::logger::{IncMetric, LatencyAggregateMetrics, SharedIncMetric};
90
91/// map of network interface id and metrics
92/// this should be protected by a lock before accessing.
93#[derive(Debug)]
94pub struct NetMetricsPerDevice {
95 /// used to access per net device metrics
96 pub metrics: BTreeMap<String, Arc<NetDeviceMetrics>>,
97}
98
99impl NetMetricsPerDevice {
100 /// Allocate `NetDeviceMetrics` for net device having
101 /// id `iface_id`. Also, allocate only if it doesn't
102 /// exist to avoid overwriting previously allocated data.
103 /// lock is always initialized so it is safe the unwrap
104 /// the lock without a check.
105 pub fn alloc(iface_id: String) -> Arc<NetDeviceMetrics> {
106 Arc::clone(
107 METRICS
108 .write()
109 .unwrap()
110 .metrics
111 .entry(iface_id)
112 .or_insert_with(|| Arc::new(NetDeviceMetrics::default())),
113 )
114 }
115}
116
117/// Pool of Network-related metrics per device behind a lock to
118/// keep things thread safe. Since the lock is initialized here
119/// it is safe to unwrap it without any check.
120static METRICS: RwLock<NetMetricsPerDevice> = RwLock::new(NetMetricsPerDevice {
121 metrics: BTreeMap::new(),
122});
123
124/// This function facilitates aggregation and serialization of
125/// per net device metrics.
126pub fn flush_metrics<S: Serializer>(serializer: S) -> Result<S::Ok, S::Error> {
127 let net_metrics = METRICS.read().unwrap();
128 let metrics_len = net_metrics.metrics.len();
129 // +1 to accomodate aggregate net metrics
130 let mut seq = serializer.serialize_map(Some(1 + metrics_len))?;
131
132 let mut net_aggregated: NetDeviceMetrics = NetDeviceMetrics::default();
133
134 for (name, metrics) in net_metrics.metrics.iter() {
135 let devn = format!("net_{}", name);
136 // serialization will flush the metrics so aggregate before it.
137 let m: &NetDeviceMetrics = metrics;
138 net_aggregated.aggregate(m);
139 seq.serialize_entry(&devn, m)?;
140 }
141 seq.serialize_entry("net", &net_aggregated)?;
142 seq.end()
143}
144
145/// Network-related metrics.
146#[derive(Default, Debug, Serialize)]
147pub struct NetDeviceMetrics {
148 /// Number of times when activate failed on a network device.
149 pub activate_fails: SharedIncMetric,
150 /// Number of times when interacting with the space config of a network device failed.
151 pub cfg_fails: SharedIncMetric,
152 /// Number of times the mac address was updated through the config space.
153 pub mac_address_updates: SharedIncMetric,
154 /// No available buffer for the net device rx queue.
155 pub no_rx_avail_buffer: SharedIncMetric,
156 /// No available buffer for the net device tx queue.
157 pub no_tx_avail_buffer: SharedIncMetric,
158 /// Number of times when handling events on a network device failed.
159 pub event_fails: SharedIncMetric,
160 /// Number of events associated with the receiving queue.
161 pub rx_queue_event_count: SharedIncMetric,
162 /// Number of events associated with the rate limiter installed on the receiving path.
163 pub rx_event_rate_limiter_count: SharedIncMetric,
164 /// Number of RX rate limiter throttling events.
165 pub rx_rate_limiter_throttled: SharedIncMetric,
166 /// Number of events received on the associated tap.
167 pub rx_tap_event_count: SharedIncMetric,
168 /// Number of bytes received.
169 pub rx_bytes_count: SharedIncMetric,
170 /// Number of packets received.
171 pub rx_packets_count: SharedIncMetric,
172 /// Number of errors while receiving data.
173 pub rx_fails: SharedIncMetric,
174 /// Number of successful read operations while receiving data.
175 pub rx_count: SharedIncMetric,
176 /// Number of times reading from TAP failed.
177 pub tap_read_fails: SharedIncMetric,
178 /// Number of times writing to TAP failed.
179 pub tap_write_fails: SharedIncMetric,
180 /// Duration of all tap write operations.
181 pub tap_write_agg: LatencyAggregateMetrics,
182 /// Number of transmitted bytes.
183 pub tx_bytes_count: SharedIncMetric,
184 /// Number of malformed TX frames.
185 pub tx_malformed_frames: SharedIncMetric,
186 /// Number of errors while transmitting data.
187 pub tx_fails: SharedIncMetric,
188 /// Number of successful write operations while transmitting data.
189 pub tx_count: SharedIncMetric,
190 /// Number of transmitted packets.
191 pub tx_packets_count: SharedIncMetric,
192 /// Number of events associated with the transmitting queue.
193 pub tx_queue_event_count: SharedIncMetric,
194 /// Number of events associated with the rate limiter installed on the transmitting path.
195 pub tx_rate_limiter_event_count: SharedIncMetric,
196 /// Number of RX rate limiter throttling events.
197 pub tx_rate_limiter_throttled: SharedIncMetric,
198 /// Number of packets with a spoofed mac, sent by the guest.
199 pub tx_spoofed_mac_count: SharedIncMetric,
200 /// Number of remaining requests in the TX queue.
201 pub tx_remaining_reqs_count: SharedIncMetric,
202}
203
204impl NetDeviceMetrics {
205 /// Const default construction.
206 pub fn new() -> Self {
207 Self {
208 tap_write_agg: LatencyAggregateMetrics::new(),
209 ..Default::default()
210 }
211 }
212
213 /// Net metrics are SharedIncMetric where the diff of current vs
214 /// old is serialized i.e. serialize_u64(current-old).
215 /// So to have the aggregate serialized in same way we need to
216 /// fetch the diff of current vs old metrics and add it to the
217 /// aggregate.
218 pub fn aggregate(&mut self, other: &Self) {
219 self.activate_fails.add(other.activate_fails.fetch_diff());
220 self.cfg_fails.add(other.cfg_fails.fetch_diff());
221 self.mac_address_updates
222 .add(other.mac_address_updates.fetch_diff());
223 self.no_rx_avail_buffer
224 .add(other.no_rx_avail_buffer.fetch_diff());
225 self.no_tx_avail_buffer
226 .add(other.no_tx_avail_buffer.fetch_diff());
227 self.event_fails.add(other.event_fails.fetch_diff());
228 self.rx_queue_event_count
229 .add(other.rx_queue_event_count.fetch_diff());
230 self.rx_event_rate_limiter_count
231 .add(other.rx_event_rate_limiter_count.fetch_diff());
232 self.rx_rate_limiter_throttled
233 .add(other.rx_rate_limiter_throttled.fetch_diff());
234 self.rx_tap_event_count
235 .add(other.rx_tap_event_count.fetch_diff());
236 self.rx_bytes_count.add(other.rx_bytes_count.fetch_diff());
237 self.rx_packets_count
238 .add(other.rx_packets_count.fetch_diff());
239 self.rx_fails.add(other.rx_fails.fetch_diff());
240 self.rx_count.add(other.rx_count.fetch_diff());
241 self.tap_read_fails.add(other.tap_read_fails.fetch_diff());
242 self.tap_write_fails.add(other.tap_write_fails.fetch_diff());
243 self.tap_write_agg
244 .sum_us
245 .add(other.tap_write_agg.sum_us.fetch_diff());
246 self.tx_bytes_count.add(other.tx_bytes_count.fetch_diff());
247 self.tx_malformed_frames
248 .add(other.tx_malformed_frames.fetch_diff());
249 self.tx_fails.add(other.tx_fails.fetch_diff());
250 self.tx_count.add(other.tx_count.fetch_diff());
251 self.tx_packets_count
252 .add(other.tx_packets_count.fetch_diff());
253 self.tx_queue_event_count
254 .add(other.tx_queue_event_count.fetch_diff());
255 self.tx_rate_limiter_event_count
256 .add(other.tx_rate_limiter_event_count.fetch_diff());
257 self.tx_rate_limiter_throttled
258 .add(other.tx_rate_limiter_throttled.fetch_diff());
259 self.tx_spoofed_mac_count
260 .add(other.tx_spoofed_mac_count.fetch_diff());
261 self.tx_remaining_reqs_count
262 .add(other.tx_remaining_reqs_count.fetch_diff());
263 }
264}
265
266#[cfg(test)]
267pub mod tests {
268 use super::*;
269
270 #[test]
271 fn test_max_net_dev_metrics() {
272 // Note: this test has nothing to do with
273 // Net structure or IRQs, this is just to allocate
274 // metrics for max number of devices that system can have.
275 // we have 5-23 irq for net devices so max 19 net devices.
276 const MAX_NET_DEVICES: usize = 19;
277
278 drop(METRICS.read().unwrap());
279 drop(METRICS.write().unwrap());
280
281 for i in 0..MAX_NET_DEVICES {
282 let devn: String = format!("eth{}", i);
283 NetMetricsPerDevice::alloc(devn.clone());
284 METRICS
285 .read()
286 .unwrap()
287 .metrics
288 .get(&devn)
289 .unwrap()
290 .activate_fails
291 .inc();
292 METRICS
293 .read()
294 .unwrap()
295 .metrics
296 .get(&devn)
297 .unwrap()
298 .rx_bytes_count
299 .add(10);
300 METRICS
301 .read()
302 .unwrap()
303 .metrics
304 .get(&devn)
305 .unwrap()
306 .tx_bytes_count
307 .add(5);
308 }
309
310 for i in 0..MAX_NET_DEVICES {
311 let devn: String = format!("eth{}", i);
312 assert!(
313 METRICS
314 .read()
315 .unwrap()
316 .metrics
317 .get(&devn)
318 .unwrap()
319 .activate_fails
320 .count()
321 >= 1
322 );
323 assert!(
324 METRICS
325 .read()
326 .unwrap()
327 .metrics
328 .get(&devn)
329 .unwrap()
330 .rx_bytes_count
331 .count()
332 >= 10
333 );
334 assert_eq!(
335 METRICS
336 .read()
337 .unwrap()
338 .metrics
339 .get(&devn)
340 .unwrap()
341 .tx_bytes_count
342 .count(),
343 5
344 );
345 }
346 }
347 #[test]
348 fn test_signle_net_dev_metrics() {
349 // Use eth0 so that we can check thread safety with the
350 // `test_net_dev_metrics` which also uses the same name.
351 let devn = "eth0";
352
353 drop(METRICS.read().unwrap());
354 drop(METRICS.write().unwrap());
355
356 NetMetricsPerDevice::alloc(String::from(devn));
357 METRICS.read().unwrap().metrics.get(devn).unwrap();
358
359 METRICS
360 .read()
361 .unwrap()
362 .metrics
363 .get(devn)
364 .unwrap()
365 .activate_fails
366 .inc();
367 assert!(
368 METRICS
369 .read()
370 .unwrap()
371 .metrics
372 .get(devn)
373 .unwrap()
374 .activate_fails
375 .count()
376 > 0,
377 "{}",
378 METRICS
379 .read()
380 .unwrap()
381 .metrics
382 .get(devn)
383 .unwrap()
384 .activate_fails
385 .count()
386 );
387 // we expect only 2 tests (this and test_max_net_dev_metrics)
388 // to update activate_fails count for eth0.
389 assert!(
390 METRICS
391 .read()
392 .unwrap()
393 .metrics
394 .get(devn)
395 .unwrap()
396 .activate_fails
397 .count()
398 <= 2,
399 "{}",
400 METRICS
401 .read()
402 .unwrap()
403 .metrics
404 .get(devn)
405 .unwrap()
406 .activate_fails
407 .count()
408 );
409
410 METRICS
411 .read()
412 .unwrap()
413 .metrics
414 .get(devn)
415 .unwrap()
416 .activate_fails
417 .inc();
418 METRICS
419 .read()
420 .unwrap()
421 .metrics
422 .get(devn)
423 .unwrap()
424 .rx_bytes_count
425 .add(5);
426 assert!(
427 METRICS
428 .read()
429 .unwrap()
430 .metrics
431 .get(devn)
432 .unwrap()
433 .rx_bytes_count
434 .count()
435 >= 5
436 );
437 }
438}