below_model/
network.rs

1// Copyright (c) Facebook, Inc. and its affiliates.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use super::*;
16
17#[::below_derive::queriable_derives]
18pub struct NetworkModel {
19    #[queriable(subquery)]
20    pub interfaces: BTreeMap<String, SingleNetModel>,
21    #[queriable(subquery)]
22    pub tcp: TcpModel,
23    #[queriable(subquery)]
24    pub ip: IpModel,
25    #[queriable(subquery)]
26    pub ip6: Ip6Model,
27    #[queriable(subquery)]
28    pub icmp: IcmpModel,
29    #[queriable(subquery)]
30    pub icmp6: Icmp6Model,
31    #[queriable(subquery)]
32    pub udp: UdpModel,
33    #[queriable(subquery)]
34    pub udp6: Udp6Model,
35}
36
37impl NetworkModel {
38    pub fn new(sample: &NetworkStats, last: Option<(&NetworkStats, Duration)>) -> Self {
39        let mut interfaces: BTreeMap<String, SingleNetModel> = BTreeMap::new();
40
41        let net_stats = sample.net;
42        let ethtool_stats = sample.ethtool;
43
44        let mut iface_names = BTreeSet::new();
45        if let Some(ifaces) = net_stats.interfaces.as_ref() {
46            for (interface, _) in ifaces.iter() {
47                iface_names.insert(interface.to_string());
48            }
49        }
50        if let Some(ethtool_stats) = ethtool_stats {
51            for key in ethtool_stats.nic.keys() {
52                iface_names.insert(key.to_string());
53            }
54        }
55
56        for interface in iface_names {
57            let iface_stat = net_stats
58                .interfaces
59                .as_ref()
60                .and_then(|ifaces| ifaces.get(&interface));
61            let ethtool_stat = ethtool_stats
62                .as_ref()
63                .and_then(|stat| stat.nic.get(&interface));
64
65            let s_iface = SingleNetworkStat {
66                iface: iface_stat,
67                nic: ethtool_stat,
68            };
69
70            let mut l_network_stat = SingleNetworkStat {
71                iface: None,
72                nic: None,
73            };
74            let l_iface = last.map(|(l, d)| {
75                let l_iface_stat = l
76                    .net
77                    .interfaces
78                    .as_ref()
79                    .and_then(|ifaces| ifaces.get(&interface));
80                let l_ethtool_stat = l.ethtool.as_ref().and_then(|stat| stat.nic.get(&interface));
81                l_network_stat = SingleNetworkStat {
82                    iface: l_iface_stat,
83                    nic: l_ethtool_stat,
84                };
85                (&l_network_stat, d)
86            });
87
88            let net_model = SingleNetModel::new(&interface, &s_iface, l_iface);
89            interfaces.insert(interface, net_model);
90        }
91
92        NetworkModel {
93            interfaces,
94            tcp: TcpModel::new(
95                sample.net.tcp.as_ref().unwrap_or(&Default::default()),
96                last.and_then(|(l, d)| {
97                    let n = l.net;
98                    n.tcp.as_ref().map(|n| (n, d))
99                }),
100            ),
101            ip: IpModel::new(
102                sample.net.ip.as_ref().unwrap_or(&Default::default()),
103                last.and_then(|(l, d)| {
104                    let n = l.net;
105                    n.ip.as_ref().map(|n| (n, d))
106                }),
107                sample.net.ip_ext.as_ref().unwrap_or(&Default::default()),
108                last.and_then(|(l, d)| {
109                    let n = l.net;
110                    n.ip_ext.as_ref().map(|n| (n, d))
111                }),
112            ),
113            ip6: Ip6Model::new(
114                sample.net.ip6.as_ref().unwrap_or(&Default::default()),
115                last.and_then(|(l, d)| {
116                    let n = l.net;
117                    n.ip6.as_ref().map(|n| (n, d))
118                }),
119            ),
120            icmp: IcmpModel::new(
121                sample.net.icmp.as_ref().unwrap_or(&Default::default()),
122                last.and_then(|(l, d)| {
123                    let n = l.net;
124                    n.icmp.as_ref().map(|n| (n, d))
125                }),
126            ),
127            icmp6: Icmp6Model::new(
128                sample.net.icmp6.as_ref().unwrap_or(&Default::default()),
129                last.and_then(|(l, d)| {
130                    let n = l.net;
131                    n.icmp6.as_ref().map(|n| (n, d))
132                }),
133            ),
134            udp: UdpModel::new(
135                sample.net.udp.as_ref().unwrap_or(&Default::default()),
136                last.and_then(|(l, d)| {
137                    let n = l.net;
138                    n.udp.as_ref().map(|n| (n, d))
139                }),
140            ),
141            udp6: Udp6Model::new(
142                sample.net.udp6.as_ref().unwrap_or(&Default::default()),
143                last.and_then(|(l, d)| {
144                    let n = l.net;
145                    n.udp6.as_ref().map(|n| (n, d))
146                }),
147            ),
148        }
149    }
150}
151
152impl Nameable for NetworkModel {
153    fn name() -> &'static str {
154        "network"
155    }
156}
157
158#[::below_derive::queriable_derives]
159pub struct TcpModel {
160    pub active_opens_per_sec: Option<u64>,
161    pub passive_opens_per_sec: Option<u64>,
162    pub attempt_fails_per_sec: Option<u64>,
163    pub estab_resets_per_sec: Option<u64>,
164    pub curr_estab_conn: Option<u64>,
165    pub in_segs_per_sec: Option<u64>,
166    pub out_segs_per_sec: Option<u64>,
167    pub retrans_segs_per_sec: Option<u64>,
168    pub retrans_segs: Option<u64>,
169    pub in_errs: Option<u64>,
170    pub out_rsts_per_sec: Option<u64>,
171    pub in_csum_errors: Option<u64>,
172    // Collected TcpExt stats, but not going to display. If we got feedback that user do need
173    // those stats, we can add those here.
174}
175
176impl TcpModel {
177    pub fn new(sample: &procfs::TcpStat, last: Option<(&procfs::TcpStat, Duration)>) -> TcpModel {
178        TcpModel {
179            active_opens_per_sec: get_option_rate!(active_opens, sample, last),
180            passive_opens_per_sec: get_option_rate!(passive_opens, sample, last),
181            attempt_fails_per_sec: get_option_rate!(attempt_fails, sample, last),
182            estab_resets_per_sec: get_option_rate!(estab_resets, sample, last),
183            curr_estab_conn: sample.curr_estab,
184            in_segs_per_sec: get_option_rate!(in_segs, sample, last),
185            out_segs_per_sec: get_option_rate!(out_segs, sample, last),
186            retrans_segs_per_sec: get_option_rate!(retrans_segs, sample, last),
187            retrans_segs: sample.retrans_segs,
188            in_errs: sample.in_errs,
189            out_rsts_per_sec: get_option_rate!(out_rsts, sample, last),
190            in_csum_errors: sample.in_csum_errors,
191        }
192    }
193}
194
195#[::below_derive::queriable_derives]
196pub struct IpModel {
197    pub forwarding_pkts_per_sec: Option<u64>,
198    pub in_receives_pkts_per_sec: Option<u64>,
199    pub forw_datagrams_per_sec: Option<u64>,
200    pub in_discards_pkts_per_sec: Option<u64>,
201    pub in_delivers_pkts_per_sec: Option<u64>,
202    pub out_requests_per_sec: Option<u64>,
203    pub out_discards_pkts_per_sec: Option<u64>,
204    pub out_no_routes_pkts_per_sec: Option<u64>,
205    // IpExt stats below
206    pub in_mcast_pkts_per_sec: Option<u64>,
207    pub out_mcast_pkts_per_sec: Option<u64>,
208    pub in_bcast_pkts_per_sec: Option<u64>,
209    pub out_bcast_pkts_per_sec: Option<u64>,
210    pub in_octets_per_sec: Option<u64>,
211    pub out_octets_per_sec: Option<u64>,
212    pub in_mcast_octets_per_sec: Option<u64>,
213    pub out_mcast_octets_per_sec: Option<u64>,
214    pub in_bcast_octets_per_sec: Option<u64>,
215    pub out_bcast_octets_per_sec: Option<u64>,
216    pub in_no_ect_pkts_per_sec: Option<u64>,
217}
218
219impl IpModel {
220    pub fn new(
221        sample: &procfs::IpStat,
222        last: Option<(&procfs::IpStat, Duration)>,
223        sample_ext: &procfs::IpExtStat,
224        last_ext: Option<(&procfs::IpExtStat, Duration)>,
225    ) -> IpModel {
226        IpModel {
227            forwarding_pkts_per_sec: get_option_rate!(forwarding, sample, last),
228            in_receives_pkts_per_sec: get_option_rate!(in_receives, sample, last),
229            forw_datagrams_per_sec: get_option_rate!(forw_datagrams, sample, last),
230            in_discards_pkts_per_sec: get_option_rate!(in_discards, sample, last),
231            in_delivers_pkts_per_sec: get_option_rate!(in_delivers, sample, last),
232            out_requests_per_sec: get_option_rate!(out_requests, sample, last),
233            out_discards_pkts_per_sec: get_option_rate!(out_discards, sample, last),
234            out_no_routes_pkts_per_sec: get_option_rate!(out_no_routes, sample, last),
235            // IpExt
236            in_mcast_pkts_per_sec: get_option_rate!(in_mcast_pkts, sample_ext, last_ext),
237            out_mcast_pkts_per_sec: get_option_rate!(out_mcast_pkts, sample_ext, last_ext),
238            in_bcast_pkts_per_sec: get_option_rate!(in_bcast_pkts, sample_ext, last_ext),
239            out_bcast_pkts_per_sec: get_option_rate!(out_bcast_pkts, sample_ext, last_ext),
240            in_octets_per_sec: get_option_rate!(in_octets, sample_ext, last_ext),
241            out_octets_per_sec: get_option_rate!(out_octets, sample_ext, last_ext),
242            in_mcast_octets_per_sec: get_option_rate!(in_mcast_octets, sample_ext, last_ext),
243            out_mcast_octets_per_sec: get_option_rate!(out_mcast_octets, sample_ext, last_ext),
244            in_bcast_octets_per_sec: get_option_rate!(in_bcast_octets, sample_ext, last_ext),
245            out_bcast_octets_per_sec: get_option_rate!(out_bcast_octets, sample_ext, last_ext),
246            in_no_ect_pkts_per_sec: get_option_rate!(in_no_ect_pkts, sample_ext, last_ext),
247        }
248    }
249}
250
251#[::below_derive::queriable_derives]
252pub struct Ip6Model {
253    pub in_receives_pkts_per_sec: Option<u64>,
254    pub in_hdr_errors: Option<u64>,
255    pub in_no_routes_pkts_per_sec: Option<u64>,
256    pub in_addr_errors: Option<u64>,
257    pub in_discards_pkts_per_sec: Option<u64>,
258    pub in_delivers_pkts_per_sec: Option<u64>,
259    pub out_forw_datagrams_per_sec: Option<u64>,
260    pub out_requests_per_sec: Option<u64>,
261    pub out_no_routes_pkts_per_sec: Option<u64>,
262    pub in_mcast_pkts_per_sec: Option<u64>,
263    pub out_mcast_pkts_per_sec: Option<u64>,
264    pub in_octets_per_sec: Option<u64>,
265    pub out_octets_per_sec: Option<u64>,
266    pub in_mcast_octets_per_sec: Option<u64>,
267    pub out_mcast_octets_per_sec: Option<u64>,
268    pub in_bcast_octets_per_sec: Option<u64>,
269    pub out_bcast_octets_per_sec: Option<u64>,
270}
271
272impl Ip6Model {
273    pub fn new(sample: &procfs::Ip6Stat, last: Option<(&procfs::Ip6Stat, Duration)>) -> Ip6Model {
274        Ip6Model {
275            in_receives_pkts_per_sec: get_option_rate!(in_receives, sample, last),
276            in_hdr_errors: sample.in_hdr_errors,
277            in_no_routes_pkts_per_sec: get_option_rate!(in_no_routes, sample, last),
278            in_addr_errors: sample.in_addr_errors,
279            in_discards_pkts_per_sec: get_option_rate!(in_discards, sample, last),
280            in_delivers_pkts_per_sec: get_option_rate!(in_delivers, sample, last),
281            out_forw_datagrams_per_sec: get_option_rate!(out_forw_datagrams, sample, last),
282            out_requests_per_sec: get_option_rate!(out_requests, sample, last),
283            out_no_routes_pkts_per_sec: get_option_rate!(out_no_routes, sample, last),
284            in_mcast_pkts_per_sec: get_option_rate!(in_mcast_pkts, sample, last),
285            out_mcast_pkts_per_sec: get_option_rate!(out_mcast_pkts, sample, last),
286            in_octets_per_sec: get_option_rate!(in_octets, sample, last),
287            out_octets_per_sec: get_option_rate!(out_octets, sample, last),
288            in_mcast_octets_per_sec: get_option_rate!(in_mcast_octets, sample, last),
289            out_mcast_octets_per_sec: get_option_rate!(out_mcast_octets, sample, last),
290            in_bcast_octets_per_sec: get_option_rate!(in_bcast_octets, sample, last),
291            out_bcast_octets_per_sec: get_option_rate!(out_bcast_octets, sample, last),
292        }
293    }
294}
295
296#[::below_derive::queriable_derives]
297pub struct IcmpModel {
298    pub in_msgs_per_sec: Option<u64>,
299    pub in_errors: Option<u64>,
300    pub in_dest_unreachs: Option<u64>,
301    pub out_msgs_per_sec: Option<u64>,
302    pub out_errors: Option<u64>,
303    pub out_dest_unreachs: Option<u64>,
304}
305
306impl IcmpModel {
307    pub fn new(
308        sample: &procfs::IcmpStat,
309        last: Option<(&procfs::IcmpStat, Duration)>,
310    ) -> IcmpModel {
311        IcmpModel {
312            in_msgs_per_sec: get_option_rate!(in_msgs, sample, last),
313            in_errors: sample.in_errors,
314            in_dest_unreachs: sample.in_dest_unreachs,
315            out_msgs_per_sec: get_option_rate!(out_msgs, sample, last),
316            out_errors: sample.out_errors,
317            out_dest_unreachs: sample.out_dest_unreachs,
318        }
319    }
320}
321
322#[::below_derive::queriable_derives]
323pub struct Icmp6Model {
324    pub in_msgs_per_sec: Option<u64>,
325    pub in_errors: Option<u64>,
326    pub in_dest_unreachs: Option<u64>,
327    pub out_msgs_per_sec: Option<u64>,
328    pub out_errors: Option<u64>,
329    pub out_dest_unreachs: Option<u64>,
330}
331
332impl Icmp6Model {
333    pub fn new(
334        sample: &procfs::Icmp6Stat,
335        last: Option<(&procfs::Icmp6Stat, Duration)>,
336    ) -> Icmp6Model {
337        Icmp6Model {
338            in_msgs_per_sec: get_option_rate!(in_msgs, sample, last),
339            in_errors: sample.in_errors,
340            in_dest_unreachs: sample.in_dest_unreachs,
341            out_msgs_per_sec: get_option_rate!(out_msgs, sample, last),
342            out_errors: sample.out_errors,
343            out_dest_unreachs: sample.out_dest_unreachs,
344        }
345    }
346}
347
348#[::below_derive::queriable_derives]
349pub struct UdpModel {
350    pub in_datagrams_pkts_per_sec: Option<u64>,
351    pub no_ports: Option<u64>,
352    pub in_errors: Option<u64>,
353    pub out_datagrams_pkts_per_sec: Option<u64>,
354    pub rcvbuf_errors: Option<u64>,
355    pub sndbuf_errors: Option<u64>,
356    pub ignored_multi: Option<u64>,
357}
358
359impl UdpModel {
360    pub fn new(sample: &procfs::UdpStat, last: Option<(&procfs::UdpStat, Duration)>) -> UdpModel {
361        UdpModel {
362            in_datagrams_pkts_per_sec: get_option_rate!(in_datagrams, sample, last),
363            no_ports: sample.no_ports,
364            in_errors: sample.in_errors,
365            out_datagrams_pkts_per_sec: get_option_rate!(out_datagrams, sample, last),
366            rcvbuf_errors: sample.rcvbuf_errors,
367            sndbuf_errors: sample.sndbuf_errors,
368            ignored_multi: sample.ignored_multi,
369        }
370    }
371}
372
373#[::below_derive::queriable_derives]
374pub struct Udp6Model {
375    pub in_datagrams_pkts_per_sec: Option<u64>,
376    pub no_ports: Option<u64>,
377    pub in_errors: Option<u64>,
378    pub out_datagrams_pkts_per_sec: Option<u64>,
379    pub rcvbuf_errors: Option<u64>,
380    pub sndbuf_errors: Option<u64>,
381    pub in_csum_errors: Option<u64>,
382    pub ignored_multi: Option<u64>,
383}
384
385impl Udp6Model {
386    pub fn new(
387        sample: &procfs::Udp6Stat,
388        last: Option<(&procfs::Udp6Stat, Duration)>,
389    ) -> Udp6Model {
390        Udp6Model {
391            in_datagrams_pkts_per_sec: get_option_rate!(in_datagrams, sample, last),
392            no_ports: sample.no_ports,
393            in_errors: sample.in_errors,
394            out_datagrams_pkts_per_sec: get_option_rate!(out_datagrams, sample, last),
395            rcvbuf_errors: sample.rcvbuf_errors,
396            sndbuf_errors: sample.sndbuf_errors,
397            in_csum_errors: sample.in_csum_errors,
398            ignored_multi: sample.ignored_multi,
399        }
400    }
401}
402
403#[::below_derive::queriable_derives]
404pub struct SingleNetModel {
405    pub interface: String,
406    pub rx_bytes_per_sec: Option<f64>,
407    pub tx_bytes_per_sec: Option<f64>,
408    pub throughput_per_sec: Option<f64>,
409    pub rx_packets_per_sec: Option<u64>,
410    pub tx_packets_per_sec: Option<u64>,
411    pub collisions: Option<u64>,
412    pub multicast: Option<u64>,
413    pub rx_bytes: Option<u64>,
414    pub rx_compressed: Option<u64>,
415    pub rx_crc_errors: Option<u64>,
416    pub rx_dropped: Option<u64>,
417    pub rx_errors: Option<u64>,
418    pub rx_fifo_errors: Option<u64>,
419    pub rx_frame_errors: Option<u64>,
420    pub rx_length_errors: Option<u64>,
421    pub rx_missed_errors: Option<u64>,
422    pub rx_nohandler: Option<u64>,
423    pub rx_over_errors: Option<u64>,
424    pub rx_packets: Option<u64>,
425    pub tx_aborted_errors: Option<u64>,
426    pub tx_bytes: Option<u64>,
427    pub tx_carrier_errors: Option<u64>,
428    pub tx_compressed: Option<u64>,
429    pub tx_dropped: Option<u64>,
430    pub tx_errors: Option<u64>,
431    pub tx_fifo_errors: Option<u64>,
432    pub tx_heartbeat_errors: Option<u64>,
433    pub tx_packets: Option<u64>,
434    pub tx_window_errors: Option<u64>,
435    pub tx_timeout_per_sec: Option<u64>,
436    pub raw_stats: BTreeMap<String, u64>,
437
438    #[queriable(subquery)]
439    pub queues: Vec<SingleQueueModel>,
440}
441
442pub struct SingleNetworkStat<'a> {
443    iface: Option<&'a procfs::InterfaceStat>,
444    nic: Option<&'a ethtool::NicStats>,
445}
446
447impl SingleNetModel {
448    fn add_iface_stats(
449        net_model: &mut SingleNetModel,
450        sample: &procfs::InterfaceStat,
451        last: Option<(&procfs::InterfaceStat, Duration)>,
452    ) {
453        let rx_bytes_per_sec = last
454            .map(|(l, d)| count_per_sec!(l.rx_bytes, sample.rx_bytes, d))
455            .unwrap_or_default();
456        let tx_bytes_per_sec = last
457            .map(|(l, d)| count_per_sec!(l.tx_bytes, sample.tx_bytes, d))
458            .unwrap_or_default();
459        let throughput_per_sec =
460            Some(rx_bytes_per_sec.unwrap_or_default() + tx_bytes_per_sec.unwrap_or_default());
461
462        net_model.rx_bytes_per_sec = rx_bytes_per_sec;
463        net_model.tx_bytes_per_sec = tx_bytes_per_sec;
464        net_model.throughput_per_sec = throughput_per_sec;
465        net_model.rx_packets_per_sec = last
466            .map(|(l, d)| count_per_sec!(l.rx_packets, sample.rx_packets, d))
467            .unwrap_or_default()
468            .map(|s| s as u64);
469        net_model.tx_packets_per_sec = last
470            .map(|(l, d)| count_per_sec!(l.tx_packets, sample.tx_packets, d))
471            .unwrap_or_default()
472            .map(|s| s as u64);
473        net_model.collisions = sample.collisions;
474        net_model.multicast = sample.multicast;
475        net_model.rx_bytes = sample.rx_bytes;
476        net_model.rx_compressed = sample.rx_compressed;
477        net_model.rx_crc_errors = sample.rx_crc_errors;
478        net_model.rx_dropped = sample.rx_dropped;
479        net_model.rx_errors = sample.rx_errors;
480        net_model.rx_fifo_errors = sample.rx_fifo_errors;
481        net_model.rx_frame_errors = sample.rx_frame_errors;
482        net_model.rx_length_errors = sample.rx_length_errors;
483        net_model.rx_missed_errors = sample.rx_missed_errors;
484        net_model.rx_nohandler = sample.rx_nohandler;
485        net_model.rx_over_errors = sample.rx_over_errors;
486        net_model.rx_packets = sample.rx_packets;
487        net_model.tx_aborted_errors = sample.tx_aborted_errors;
488        net_model.tx_bytes = sample.tx_bytes;
489        net_model.tx_carrier_errors = sample.tx_carrier_errors;
490        net_model.tx_compressed = sample.tx_compressed;
491        net_model.tx_dropped = sample.tx_dropped;
492        net_model.tx_errors = sample.tx_errors;
493        net_model.tx_fifo_errors = sample.tx_fifo_errors;
494        net_model.tx_heartbeat_errors = sample.tx_heartbeat_errors;
495        net_model.tx_packets = sample.tx_packets;
496        net_model.tx_window_errors = sample.tx_window_errors;
497    }
498
499    fn add_ethtool_stats(
500        net_model: &mut SingleNetModel,
501        sample: &ethtool::NicStats,
502        last: Option<(&ethtool::NicStats, Duration)>,
503    ) {
504        net_model.tx_timeout_per_sec = get_option_rate!(tx_timeout, sample, last);
505        net_model.raw_stats = sample.raw_stats.clone();
506
507        // set ethtool queue stats
508        let s_queue_stats = &sample.queue;
509        // Vec<QueueStats> are always sorted on the queue id
510        for (queue_id, s_queue_stats) in s_queue_stats.iter().enumerate() {
511            let idx = queue_id as u32;
512            let last = last.and_then(|(l, d)| {
513                let queue_stats = &l.queue;
514                queue_stats.get(queue_id).map(|n| (n, d))
515            });
516            let queue_model = SingleQueueModel::new(&net_model.interface, idx, s_queue_stats, last);
517            net_model.queues.push(queue_model);
518        }
519    }
520
521    fn new(
522        interface: &str,
523        sample: &SingleNetworkStat,
524        last: Option<(&SingleNetworkStat, Duration)>,
525    ) -> SingleNetModel {
526        let iface_stat = sample.iface;
527        let ethtool_stat = sample.nic;
528
529        let mut net_model = SingleNetModel {
530            interface: interface.to_string(),
531            ..Default::default()
532        };
533
534        // set procfs iface stats
535        if let Some(iface_stat) = iface_stat {
536            Self::add_iface_stats(
537                &mut net_model,
538                iface_stat,
539                last.and_then(|(l, d)| l.iface.map(|l| (l, d))),
540            );
541        }
542
543        // set ethtool stats
544        if let Some(nic_stat) = ethtool_stat {
545            let sample = nic_stat;
546            let last = last.and_then(|(l, d)| l.nic.map(|l| (l, d)));
547
548            Self::add_ethtool_stats(&mut net_model, sample, last);
549        }
550
551        net_model
552    }
553}
554
555impl Nameable for SingleNetModel {
556    fn name() -> &'static str {
557        "network"
558    }
559}
560
561#[::below_derive::queriable_derives]
562pub struct SingleQueueModel {
563    pub interface: String,
564    pub queue_id: u32,
565    pub rx_bytes_per_sec: Option<u64>,
566    pub tx_bytes_per_sec: Option<u64>,
567    pub rx_count_per_sec: Option<u64>,
568    pub tx_count_per_sec: Option<u64>,
569    pub tx_missed_tx: Option<u64>,
570    pub tx_unmask_interrupt: Option<u64>,
571    pub raw_stats: BTreeMap<String, u64>,
572}
573
574impl SingleQueueModel {
575    fn new(
576        interface: &str,
577        queue_id: u32,
578        sample: &ethtool::QueueStats,
579        last: Option<(&ethtool::QueueStats, Duration)>,
580    ) -> Self {
581        SingleQueueModel {
582            interface: interface.to_string(),
583            queue_id,
584            rx_bytes_per_sec: get_option_rate!(rx_bytes, sample, last),
585            tx_bytes_per_sec: get_option_rate!(tx_bytes, sample, last),
586            rx_count_per_sec: get_option_rate!(rx_count, sample, last),
587            tx_count_per_sec: get_option_rate!(tx_count, sample, last),
588            tx_missed_tx: sample.tx_missed_tx,
589            tx_unmask_interrupt: sample.tx_unmask_interrupt,
590            raw_stats: sample.raw_stats.clone(),
591        }
592    }
593}
594
595impl Nameable for SingleQueueModel {
596    fn name() -> &'static str {
597        "ethtool_queue"
598    }
599}
600
601#[cfg(test)]
602mod test {
603    use super::*;
604
605    #[test]
606    fn query_model() {
607        let model_json = r#"
608        {
609            "interfaces": {
610                "eth0": {
611                    "interface": "eth0",
612                    "rx_bytes_per_sec": 42,
613                    "tx_timeout_per_sec": 10,
614                    "raw_stats": {
615                        "stat0": 0
616                    },
617                    "queues": [
618                        {
619                            "interface": "eth0",
620                            "queue_id": 0,
621                            "rx_bytes_per_sec": 42,
622                            "tx_bytes_per_sec": 1337,
623                            "rx_count_per_sec": 10,
624                            "tx_count_per_sec": 20,
625                            "tx_missed_tx": 100,
626                            "tx_unmask_interrupt": 200,
627                            "raw_stats": {
628                                "stat1": 1,
629                                "stat2": 2
630                            }
631                        },
632                        {
633                            "interface": "eth0",
634                            "queue_id": 1,
635                            "rx_bytes_per_sec": 1337,
636                            "tx_bytes_per_sec": 42,
637                            "rx_count_per_sec": 20,
638                            "tx_count_per_sec": 10,
639                            "tx_missed_tx": 200,
640                            "tx_unmask_interrupt": 100,
641                            "raw_stats": {
642                                "stat3": 3,
643                                "stat4": 4
644                            }
645                        }
646                    ]
647                }
648            },
649            "tcp": {},
650            "ip": {},
651            "ip6": {},
652            "icmp": {},
653            "icmp6": {},
654            "udp": {},
655            "udp6": {}
656        }
657        "#;
658        let model: NetworkModel = serde_json::from_str(model_json).unwrap();
659        assert_eq!(
660            model
661                .query(&NetworkModelFieldId::from_str("interfaces.eth0.rx_bytes_per_sec").unwrap()),
662            Some(Field::F64(42.0))
663        );
664
665        assert_eq!(
666            model.query(
667                &NetworkModelFieldId::from_str("interfaces.eth0.tx_timeout_per_sec").unwrap()
668            ),
669            Some(Field::U64(10))
670        );
671
672        assert_eq!(
673            model.query(
674                &NetworkModelFieldId::from_str("interfaces.eth0.queues.0.queue_id").unwrap()
675            ),
676            Some(Field::U32(0))
677        );
678        assert_eq!(
679            model.query(
680                &NetworkModelFieldId::from_str("interfaces.eth0.queues.0.rx_bytes_per_sec")
681                    .unwrap()
682            ),
683            Some(Field::U64(42))
684        );
685
686        assert_eq!(
687            model.query(
688                &NetworkModelFieldId::from_str("interfaces.eth0.queues.1.queue_id").unwrap()
689            ),
690            Some(Field::U32(1))
691        );
692    }
693
694    #[test]
695    fn test_parse_ethtool_stats() {
696        let l_net_stats = procfs::NetStat::default();
697        let s_net_stats = procfs::NetStat::default();
698
699        let l_ethtool_stats = ethtool::EthtoolStats {
700            nic: BTreeMap::from([(
701                "eth0".to_string(),
702                ethtool::NicStats {
703                    tx_timeout: Some(10),
704                    raw_stats: BTreeMap::from([("stat0".to_string(), 0)]),
705                    queue: vec![
706                        ethtool::QueueStats {
707                            rx_bytes: Some(42),
708                            tx_bytes: Some(1337),
709                            rx_count: Some(10),
710                            tx_count: Some(20),
711                            tx_missed_tx: Some(100),
712                            tx_unmask_interrupt: Some(200),
713                            raw_stats: vec![("stat1".to_string(), 1), ("stat2".to_string(), 2)]
714                                .into_iter()
715                                .collect(),
716                        },
717                        ethtool::QueueStats {
718                            rx_bytes: Some(1337),
719                            tx_bytes: Some(42),
720                            rx_count: Some(20),
721                            tx_count: Some(10),
722                            tx_missed_tx: Some(200),
723                            tx_unmask_interrupt: Some(100),
724                            raw_stats: vec![("stat3".to_string(), 3), ("stat4".to_string(), 4)]
725                                .into_iter()
726                                .collect(),
727                        },
728                    ],
729                },
730            )]),
731        };
732
733        let s_ethtool_stats = ethtool::EthtoolStats {
734            nic: BTreeMap::from([(
735                "eth0".to_string(),
736                ethtool::NicStats {
737                    tx_timeout: Some(20),
738                    raw_stats: BTreeMap::from([("stat0".to_string(), 10)]),
739                    queue: vec![
740                        ethtool::QueueStats {
741                            rx_bytes: Some(52),
742                            tx_bytes: Some(1347),
743                            rx_count: Some(20),
744                            tx_count: Some(30),
745                            tx_missed_tx: Some(110),
746                            tx_unmask_interrupt: Some(210),
747                            raw_stats: vec![("stat1".to_string(), 11), ("stat2".to_string(), 12)]
748                                .into_iter()
749                                .collect(),
750                        },
751                        ethtool::QueueStats {
752                            rx_bytes: Some(1347),
753                            tx_bytes: Some(52),
754                            rx_count: Some(30),
755                            tx_count: Some(20),
756                            tx_missed_tx: Some(210),
757                            tx_unmask_interrupt: Some(110),
758                            raw_stats: vec![("stat3".to_string(), 13), ("stat4".to_string(), 14)]
759                                .into_iter()
760                                .collect(),
761                        },
762                    ],
763                },
764            )]),
765        };
766
767        let prev_sample = NetworkStats {
768            net: &l_net_stats,
769            ethtool: &Some(l_ethtool_stats),
770        };
771        let sample = NetworkStats {
772            net: &s_net_stats,
773            ethtool: &Some(s_ethtool_stats),
774        };
775        let last = Some((&prev_sample, Duration::from_secs(1)));
776
777        let model = NetworkModel::new(&sample, last);
778
779        let iface_model = model.interfaces.get("eth0").unwrap();
780        assert_eq!(iface_model.tx_timeout_per_sec, Some(10));
781        let nic_raw_stat = iface_model.raw_stats.get("stat0").unwrap();
782        assert_eq!(*nic_raw_stat, 10);
783
784        let queue_model = iface_model.queues.first().unwrap();
785        assert_eq!(queue_model.rx_bytes_per_sec, Some(10));
786        // for raw stats, we just take the latest value (not the difference)
787        let queue_raw_stat = queue_model.raw_stats.get("stat1").unwrap();
788        assert_eq!(*queue_raw_stat, 11);
789
790        let queue_model = iface_model.queues.get(1).unwrap();
791        assert_eq!(queue_model.rx_bytes_per_sec, Some(10));
792        let queue_raw_stat = queue_model.raw_stats.get("stat3").unwrap();
793        assert_eq!(*queue_raw_stat, 13);
794    }
795}