proc netstat: add network interface statistics function (#16334)
* add network interface stats function * move Total to 3rd pos * fix drops tx
Ilya Mashchenko committed
Nov 4, 2023 at 14:12 UTC
9bedfb4d9521e73edff0fe8e3b1de127392b5219
1 file changed
+338
collectors/proc.plugin/proc_net_dev.c
+338
@@ -5,6 +5,8 @@
5
#define PLUGIN_PROC_MODULE_NETDEV_NAME "/proc/net/dev"
6
#define CONFIG_SECTION_PLUGIN_PROC_NETDEV "plugin:" PLUGIN_PROC_CONFIG_NAME ":" PLUGIN_PROC_MODULE_NETDEV_NAME
7
8
+#define RRDFUNCTIONS_NETDEV_HELP "View network interface statistics"
9
+
10
#define STATE_LENGTH_MAX 32
11
12
#define READ_RETRY_PERIOD 60 // seconds
@@ -20,6 +22,18 @@ enum {
22
NETDEV_DUPLEX_FULL
23
};
24
25
+static const char *get_duplex_string(int duplex)
26
+{
27
+ switch (duplex) {
28
+ case NETDEV_DUPLEX_FULL:
29
+ return "full";
30
+ case NETDEV_DUPLEX_HALF:
31
+ return "half";
32
+ default:
33
+ return "unknown";
34
+ }
35
+}
36
+
37
enum {
38
NETDEV_OPERSTATE_UNKNOWN,
39
NETDEV_OPERSTATE_NOTPRESENT,
@@ -49,6 +63,26 @@ static inline int get_operstate(char *operstate)
63
return NETDEV_OPERSTATE_UNKNOWN;
64
}
65
66
+static const char *get_operstate_string(int operstate)
67
+{
68
+ switch (operstate) {
69
+ case NETDEV_OPERSTATE_UP:
70
+ return "up";
71
+ case NETDEV_OPERSTATE_DOWN:
72
+ return "down";
73
+ case NETDEV_OPERSTATE_NOTPRESENT:
74
+ return "notpresent";
75
+ case NETDEV_OPERSTATE_LOWERLAYERDOWN:
76
+ return "lowerlayerdown";
77
+ case NETDEV_OPERSTATE_TESTING:
78
+ return "testing";
79
+ case NETDEV_OPERSTATE_DORMANT:
80
+ return "dormant";
81
+ default:
82
+ return "unknown";
83
+ }
84
+}
85
+
86
// ----------------------------------------------------------------------------
87
// netdev list
88
@@ -63,6 +97,8 @@ static struct netdev {
97
int enabled;
98
int updated;
99
100
+ bool function_ready;
101
+
102
time_t discover_time;
103
104
int carrier_file_exists;
@@ -367,6 +403,7 @@ static struct netdev_rename {
403
404
static int netdev_pending_renames = 0;
405
static netdata_mutex_t netdev_rename_mutex = NETDATA_MUTEX_INITIALIZER;
406
+static netdata_mutex_t netdev_dev_mutex = NETDATA_MUTEX_INITIALIZER;
407
408
static struct netdev_rename *netdev_rename_find(const char *host_device, uint32_t hash) {
409
struct netdev_rename *r;
@@ -572,6 +609,297 @@ static inline void netdev_rename_all_lock(void) {
609
}
610
611
// ----------------------------------------------------------------------------
612
+
613
+int netdev_function_net_interfaces(BUFFER *wb, int timeout __maybe_unused, const char *function __maybe_unused,
614
+ void *collector_data __maybe_unused,
615
+ rrd_function_result_callback_t result_cb, void *result_cb_data,
616
+ rrd_function_is_cancelled_cb_t is_cancelled_cb, void *is_cancelled_cb_data,
617
+ rrd_function_register_canceller_cb_t register_canceller_cb __maybe_unused,
618
+ void *register_canceller_cb_data __maybe_unused) {
619
+
620
+ buffer_flush(wb);
621
+ wb->content_type = CT_APPLICATION_JSON;
622
+ buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT);
623
+
624
+ buffer_json_member_add_string(wb, "hostname", rrdhost_hostname(localhost));
625
+ buffer_json_member_add_uint64(wb, "status", HTTP_RESP_OK);
626
+ buffer_json_member_add_string(wb, "type", "table");
627
+ buffer_json_member_add_time_t(wb, "update_every", 1);
628
+ buffer_json_member_add_string(wb, "help", RRDFUNCTIONS_NETDEV_HELP);
629
+ buffer_json_member_add_array(wb, "data");
630
+
631
+ double max_traffic_rx = 0.0;
632
+ double max_traffic_tx = 0.0;
633
+ double max_traffic = 0.0;
634
+ double max_packets_rx = 0.0;
635
+ double max_packets_tx = 0.0;
636
+ double max_mcast_rx = 0.0;
637
+ double max_drops_rx = 0.0;
638
+ double max_drops_tx = 0.0;
639
+
640
+ netdata_mutex_lock(&netdev_dev_mutex);
641
+
642
+ RRDDIM *rd = NULL;
643
+
644
+ for (struct netdev *d = netdev_root; d != netdev_last_used; d = d->next) {
645
+ if (unlikely(!d->function_ready))
646
+ continue;
647
+
648
+ buffer_json_add_array_item_array(wb);
649
+
650
+ buffer_json_add_array_item_string(wb, d->name);
651
+
652
+ buffer_json_add_array_item_string(wb, d->virtual ? "virtual" : "physical");
653
+ buffer_json_add_array_item_string(wb, d->carrier == 1 ? "up" : "down");
654
+ buffer_json_add_array_item_string(wb, get_operstate_string(d->operstate));
655
+ buffer_json_add_array_item_string(wb, get_duplex_string(d->duplex));
656
+ buffer_json_add_array_item_double(wb, d->speed > 0 ? d->speed : NAN);
657
+ buffer_json_add_array_item_double(wb, d->mtu > 0 ? d->mtu : NAN);
658
+
659
+ rd = d->flipped ? d->rd_tbytes : d->rd_rbytes;
660
+ double traffic_rx = NAN;
661
+ if (rd) {
662
+ traffic_rx = rd->collector.last_stored_value / 1000.0;
663
+ max_traffic_rx = MAX(max_traffic_rx, traffic_rx);
664
+ }
665
+
666
+ rd = d->flipped ? d->rd_rbytes : d->rd_tbytes;
667
+ double traffic_tx = NAN;
668
+ if (rd) {
669
+ traffic_tx = ABS(rd->collector.last_stored_value / 1000.0);
670
+ max_traffic_tx = MAX(max_traffic_tx, traffic_tx);
671
+ }
672
+
673
+ // FIXME: "traffic" (total) is needed only for default_sorting
674
+ // can be removed when default_sorting will accept multiple columns (sum)
675
+ double traffic = NAN;
676
+ if (!isnan(traffic_rx) && !isnan(traffic_tx)) {
677
+ traffic = traffic_rx + traffic_tx;
678
+ max_traffic = MAX(max_traffic, traffic);
679
+ }
680
+
681
+ buffer_json_add_array_item_double(wb, traffic_rx);
682
+ buffer_json_add_array_item_double(wb, traffic_tx);
683
+ buffer_json_add_array_item_double(wb, traffic);
684
+
685
+ rd = d->flipped ? d->rd_tpackets : d->rd_rpackets;
686
+ double packets_rx = NAN;
687
+ if (rd) {
688
+ packets_rx = rd->collector.last_stored_value / 1000.0;
689
+ max_packets_rx = MAX(max_packets_rx, packets_rx);
690
+ }
691
+ buffer_json_add_array_item_double(wb, packets_rx);
692
+
693
+ rd = d->flipped ? d->rd_rpackets : d->rd_tpackets;
694
+ double packets_tx = NAN;
695
+ if (rd) {
696
+ packets_tx = ABS(rd->collector.last_stored_value / 1000.0);
697
+ max_packets_tx = MAX(max_packets_tx, packets_tx);
698
+ }
699
+ buffer_json_add_array_item_double(wb, packets_tx);
700
+
701
+ double mcast_rx = NAN;
702
+ if (d->rd_rmulticast) {
703
+ mcast_rx = ABS(d->rd_rmulticast->collector.last_stored_value / 1000.0);
704
+ max_mcast_rx = MAX(max_mcast_rx, mcast_rx);
705
+ }
706
+ buffer_json_add_array_item_double(wb, mcast_rx);
707
+
708
+
709
+ rd = d->flipped ? d->rd_tdrops : d->rd_rdrops;
710
+ double drops_rx = NAN;
711
+ if (rd) {
712
+ drops_rx = rd->collector.last_stored_value;
713
+ max_drops_rx = MAX(max_drops_rx, drops_rx);
714
+ }
715
+ buffer_json_add_array_item_double(wb, drops_rx);
716
+
717
+ rd = d->flipped ? d->rd_rdrops : d->rd_tdrops;
718
+ double drops_tx = NAN;
719
+ if (rd) {
720
+ drops_tx = ABS(rd->collector.last_stored_value);
721
+ max_drops_tx = MAX(max_drops_tx, drops_tx);
722
+ }
723
+ buffer_json_add_array_item_double(wb, drops_tx);
724
+
725
+ buffer_json_array_close(wb);
726
+ }
727
+
728
+ netdata_mutex_unlock(&netdev_dev_mutex);
729
+
730
+ buffer_json_array_close(wb); // data
731
+ buffer_json_member_add_object(wb, "columns");
732
+ {
733
+ size_t field_id = 0;
734
+
735
+ buffer_rrdf_table_add_field(wb, field_id++, "Name", "Network Interface Name",
736
+ RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
737
+ 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
738
+ RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT,
739
+ RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_UNIQUE_KEY | RRDF_FIELD_OPTS_STICKY,
740
+ NULL);
741
+
742
+ buffer_rrdf_table_add_field(wb, field_id++, "Type", "Network Interface Type",
743
+ RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
744
+ 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
745
+ RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT,
746
+ RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_UNIQUE_KEY,
747
+ NULL);
748
+
749
+ buffer_rrdf_table_add_field(wb, field_id++, "PhState", "Current Physical State",
750
+ RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
751
+ 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
752
+ RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT,
753
+ RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_UNIQUE_KEY,
754
+ NULL);
755
+
756
+ buffer_rrdf_table_add_field(wb, field_id++, "OpState", "Current Operational State",
757
+ RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
758
+ 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
759
+ RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT,
760
+ RRDF_FIELD_OPTS_UNIQUE_KEY,
761
+ NULL);
762
+
763
+ buffer_rrdf_table_add_field(wb, field_id++, "Duplex", "Current Duplex Mode",
764
+ RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
765
+ 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
766
+ RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT,
767
+ RRDF_FIELD_OPTS_UNIQUE_KEY,
768
+ NULL);
769
+
770
+ buffer_rrdf_table_add_field(wb, field_id++, "Speed", "Current Link Speed",
771
+ RRDF_FIELD_TYPE_BAR_WITH_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NUMBER,
772
+ 0, "Mbit", NAN, RRDF_FIELD_SORT_DESCENDING, NULL,
773
+ RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT,
774
+ RRDF_FIELD_OPTS_UNIQUE_KEY,
775
+ NULL);
776
+
777
+ buffer_rrdf_table_add_field(wb, field_id++, "MTU", "Maximum Transmission Unit",
778
+ RRDF_FIELD_TYPE_BAR_WITH_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NUMBER,
779
+ 0, "Octets", NAN, RRDF_FIELD_SORT_DESCENDING, NULL,
780
+ RRDF_FIELD_SUMMARY_COUNT, RRDF_FIELD_FILTER_MULTISELECT,
781
+ RRDF_FIELD_OPTS_UNIQUE_KEY,
782
+ NULL);
783
+
784
+ buffer_rrdf_table_add_field(wb, field_id++, "In", "Traffic Received",
785
+ RRDF_FIELD_TYPE_BAR_WITH_INTEGER, RRDF_FIELD_VISUAL_BAR, RRDF_FIELD_TRANSFORM_NUMBER,
786
+ 2, "MiB", max_traffic_rx, RRDF_FIELD_SORT_DESCENDING, NULL,
787
+ RRDF_FIELD_SUMMARY_SUM, RRDF_FIELD_FILTER_NONE,
788
+ RRDF_FIELD_OPTS_VISIBLE,
789
+ NULL);
790
+
791
+ buffer_rrdf_table_add_field(wb, field_id++, "Out", "Traffic Sent",
792
+ RRDF_FIELD_TYPE_BAR_WITH_INTEGER, RRDF_FIELD_VISUAL_BAR, RRDF_FIELD_TRANSFORM_NUMBER,
793
+ 2, "MiB", max_traffic_tx, RRDF_FIELD_SORT_DESCENDING, NULL,
794
+ RRDF_FIELD_SUMMARY_SUM, RRDF_FIELD_FILTER_NONE,
795
+ RRDF_FIELD_OPTS_VISIBLE,
796
+ NULL);
797
+
798
+ buffer_rrdf_table_add_field(wb, field_id++, "Total", "Traffic Received and Sent",
799
+ RRDF_FIELD_TYPE_BAR_WITH_INTEGER, RRDF_FIELD_VISUAL_BAR, RRDF_FIELD_TRANSFORM_NUMBER,
800
+ 2, "MiB", max_traffic, RRDF_FIELD_SORT_DESCENDING, NULL,
801
+ RRDF_FIELD_SUMMARY_SUM, RRDF_FIELD_FILTER_NONE,
802
+ RRDF_FIELD_OPTS_VISIBLE,
803
+ NULL);
804
+
805
+ buffer_rrdf_table_add_field(wb, field_id++, "PktsIn", "Received Packets",
806
+ RRDF_FIELD_TYPE_BAR_WITH_INTEGER, RRDF_FIELD_VISUAL_BAR, RRDF_FIELD_TRANSFORM_NUMBER,
807
+ 2, "Kpps", max_packets_rx, RRDF_FIELD_SORT_DESCENDING, NULL,
808
+ RRDF_FIELD_SUMMARY_SUM, RRDF_FIELD_FILTER_NONE,
809
+ RRDF_FIELD_OPTS_VISIBLE,
810
+ NULL);
811
+
812
+ buffer_rrdf_table_add_field(wb, field_id++, "PktsOut", "Sent Packets",
813
+ RRDF_FIELD_TYPE_BAR_WITH_INTEGER, RRDF_FIELD_VISUAL_BAR, RRDF_FIELD_TRANSFORM_NUMBER,
814
+ 2, "Kpps", max_packets_tx, RRDF_FIELD_SORT_DESCENDING, NULL,
815
+ RRDF_FIELD_SUMMARY_SUM, RRDF_FIELD_FILTER_NONE,
816
+ RRDF_FIELD_OPTS_VISIBLE,
817
+ NULL);
818
+
819
+ buffer_rrdf_table_add_field(wb, field_id++, "McastIn", "Multicast Received Packets",
820
+ RRDF_FIELD_TYPE_BAR_WITH_INTEGER, RRDF_FIELD_VISUAL_BAR, RRDF_FIELD_TRANSFORM_NUMBER,
821
+ 2, "Kpps", max_mcast_rx, RRDF_FIELD_SORT_DESCENDING, NULL,
822
+ RRDF_FIELD_SUMMARY_SUM, RRDF_FIELD_FILTER_NONE,
823
+ RRDF_FIELD_OPTS_NONE,
824
+ NULL);
825
+
826
+ buffer_rrdf_table_add_field(wb, field_id++, "DropsIn", "Dropped Inbound Packets",
827
+ RRDF_FIELD_TYPE_BAR_WITH_INTEGER, RRDF_FIELD_VISUAL_BAR, RRDF_FIELD_TRANSFORM_NUMBER,
828
+ 2, "Drops", max_drops_rx, RRDF_FIELD_SORT_DESCENDING, NULL,
829
+ RRDF_FIELD_SUMMARY_SUM, RRDF_FIELD_FILTER_NONE,
830
+ RRDF_FIELD_OPTS_VISIBLE,
831
+ NULL);
832
+
833
+ buffer_rrdf_table_add_field(wb, field_id++, "DropsOut", "Dropped Outbound Packets",
834
+ RRDF_FIELD_TYPE_BAR_WITH_INTEGER, RRDF_FIELD_VISUAL_BAR, RRDF_FIELD_TRANSFORM_NUMBER,
835
+ 2, "Drops", max_drops_tx, RRDF_FIELD_SORT_DESCENDING, NULL,
836
+ RRDF_FIELD_SUMMARY_SUM, RRDF_FIELD_FILTER_NONE,
837
+ RRDF_FIELD_OPTS_VISIBLE,
838
+ NULL);
839
+ }
840
+
841
+ buffer_json_object_close(wb); // columns
842
+ buffer_json_member_add_string(wb, "default_sort_column", "Total");
843
+
844
+ buffer_json_member_add_object(wb, "charts");
845
+ {
846
+ buffer_json_member_add_object(wb, "Traffic");
847
+ {
848
+ buffer_json_member_add_string(wb, "name", "Traffic");
849
+ buffer_json_member_add_string(wb, "type", "stacked-bar");
850
+ buffer_json_member_add_array(wb, "columns");
851
+ {
852
+ buffer_json_add_array_item_string(wb, "In");
853
+ buffer_json_add_array_item_string(wb, "Out");
854
+ }
855
+ buffer_json_array_close(wb);
856
+ }
857
+ buffer_json_object_close(wb);
858
+
859
+ buffer_json_member_add_object(wb, "Packets");
860
+ {
861
+ buffer_json_member_add_string(wb, "name", "Packets");
862
+ buffer_json_member_add_string(wb, "type", "stacked-bar");
863
+ buffer_json_member_add_array(wb, "columns");
864
+ {
865
+ buffer_json_add_array_item_string(wb, "PktsIn");
866
+ buffer_json_add_array_item_string(wb, "PktsOut");
867
+ }
868
+ buffer_json_array_close(wb);
869
+ }
870
+ buffer_json_object_close(wb);
871
+ }
872
+ buffer_json_object_close(wb); // charts
873
+
874
+ buffer_json_member_add_array(wb, "default_charts");
875
+ {
876
+ buffer_json_add_array_item_array(wb);
877
+ buffer_json_add_array_item_string(wb, "Traffic");
878
+ buffer_json_add_array_item_string(wb, "Name");
879
+ buffer_json_array_close(wb);
880
+
881
+ buffer_json_add_array_item_array(wb);
882
+ buffer_json_add_array_item_string(wb, "Traffic");
883
+ buffer_json_add_array_item_string(wb, "Type");
884
+ buffer_json_array_close(wb);
885
+ }
886
+ buffer_json_array_close(wb);
887
+
888
+ buffer_json_member_add_time_t(wb, "expires", now_realtime_sec() + 1);
889
+ buffer_json_finalize(wb);
890
+
891
+ int response = HTTP_RESP_OK;
892
+ if(is_cancelled_cb && is_cancelled_cb(is_cancelled_cb_data)) {
893
+ buffer_flush(wb);
894
+ response = HTTP_RESP_CLIENT_CLOSED_REQUEST;
895
+ }
896
+
897
+ if(result_cb)
898
+ result_cb(wb, response, result_cb_data);
899
+
900
+ return response;
901
+}
902
+
903
// netdev data collection
904
905
static void netdev_cleanup() {
@@ -633,6 +961,7 @@ static struct netdev *get_netdev(const char *name) {
961
d->hash = simple_hash(d->name);
962
d->len = strlen(d->name);
963
d->chart_labels = rrdlabels_create();
964
+ d->function_ready = false;
965
966
d->chart_type_net_bytes = strdupz("net");
967
d->chart_type_net_compressed = strdupz("net_compressed");
@@ -1485,6 +1814,8 @@ int do_proc_net_dev(int update_every, usec_t dt) {
1814
rrddim_set_by_pointer(d->st_events, d->rd_tcarrier, (collected_number)d->tcarrier);
1815
rrdset_done(d->st_events);
1816
}
1817
+
1818
+ d->function_ready = true;
1819
}
1820
1821
if(do_bandwidth == CONFIG_BOOLEAN_YES || (do_bandwidth == CONFIG_BOOLEAN_AUTO &&
@@ -1541,6 +1872,9 @@ void *netdev_main(void *ptr)
1872
1873
netdata_thread_cleanup_push(netdev_main_cleanup, ptr);
1874
1875
+ rrd_collector_started();
1876
+ rrd_function_add(localhost, NULL, "network-interfaces", 10, RRDFUNCTIONS_NETDEV_HELP, true, netdev_function_net_interfaces, NULL);
1877
+
1878
usec_t step = localhost->rrd_update_every * USEC_PER_SEC;
1879
heartbeat_t hb;
1880
heartbeat_init(&hb);
@@ -1555,10 +1889,14 @@ void *netdev_main(void *ptr)
1889
cgroup_netdev_reset_all();
1890
1891
worker_is_busy(0);
1892
+
1893
+ netdata_mutex_lock(&netdev_dev_mutex);
1894
if(do_proc_net_dev(localhost->rrd_update_every, hb_dt))
1895
break;
1896
+ netdata_mutex_unlock(&netdev_dev_mutex);
1897
}
1898
1899
netdata_thread_cleanup_pop(1);
1900
+
1901
return NULL;
1902
}