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}