@cryptotaxi247 / netdata / commits / b41df70d8

Windows Network Viewer (#22470)

thiagoftsm committed May 28, 2026 at 23:04 UTC b41df70d8a25103e7c7a615d1100348a26495160
11 files changed +701 -8
CMakeLists.txt
+13 -5
@@ -201,7 +201,7 @@ cmake_dependent_option(ENABLE_PLUGIN_EBPF "Enable Linux eBPF metric collection"
201 cmake_dependent_option(ENABLE_LEGACY_EBPF_PROGRAMS "Enable eBPF programs for kernels without BTF support" True "ENABLE_PLUGIN_EBPF" False)
202 mark_as_advanced(ENABLE_LEGACY_EBPF_PROGRAMS)
203 cmake_dependent_option(ENABLE_PLUGIN_LOCAL_LISTENERS "Enable local listening socket tracking (including service auto-discovery support)" ${DEFAULT_FEATURE_STATE} "OS_LINUX" False)
204 -cmake_dependent_option(ENABLE_PLUGIN_NETWORK_VIEWER "Enable network viewer functionality" ${DEFAULT_FEATURE_STATE} "OS_LINUX" False)
204 +cmake_dependent_option(ENABLE_PLUGIN_NETWORK_VIEWER "Enable network viewer functionality" ${DEFAULT_FEATURE_STATE} "OS_LINUX OR OS_WINDOWS" False)
205 cmake_dependent_option(ENABLE_PLUGIN_NFACCT "Enable Linux NFACCT metric collection" ${DEFAULT_FEATURE_STATE} "OS_LINUX" False)
206 cmake_dependent_option(ENABLE_PLUGIN_PERF "Enable Linux performance counter monitoring" ${DEFAULT_FEATURE_STATE} "OS_LINUX" False)
207 cmake_dependent_option(ENABLE_PLUGIN_SLABINFO "Enable Linux kernel SLAB allocator monitoring" ${DEFAULT_FEATURE_STATE} "OS_LINUX" False)
@@ -3133,10 +3133,18 @@ if(ENABLE_PLUGIN_NETWORK_VIEWER)
3133 endif()
3134 endif()
3135
3136 - set(NETWORK_VIEWER_FILES
3137 - src/libnetdata/local-sockets/local-sockets.h
3138 - src/collectors/network-viewer.plugin/network-viewer.c
3139 - )
3136 + set(NETWORK_VIEWER_FILES)
3137 + if(OS_LINUX)
3138 + list(APPEND NETWORK_VIEWER_FILES
3139 + src/libnetdata/local-sockets/local-sockets.h
3140 + src/collectors/network-viewer.plugin/network-viewer.c
3141 + )
3142 + endif()
3143 + if(OS_WINDOWS)
3144 + list(APPEND NETWORK_VIEWER_FILES
3145 + src/collectors/network-viewer.plugin/perflib-tcp_udp.c
3146 + )
3147 + endif()
3148
3149 add_executable(network-viewer.plugin ${NETWORK_VIEWER_FILES})
3150 if(USE_LTO AND CMAKE_C_COMPILER_ID STREQUAL "GNU" AND CMAKE_SIZEOF_VOID_P EQUAL 4)
src/collectors/COLLECTORS.md
+2
@@ -305,9 +305,11 @@ Need a dedicated integration? [Submit a feature request](https://github.com/netd
305 | [Starlink (SpaceX)](https://github.com/netdata/netdata/blob/master/src/go/plugin/go.d/collector/prometheus/integrations/starlink_spacex.md) | Monitor SpaceX Starlink satellite internet metrics for efficient internet service management and performance. |
306 | [strongSwan](https://github.com/netdata/netdata/blob/master/src/go/plugin/go.d/collector/prometheus/integrations/strongswan.md) | Track strongSwan VPN and IPSec metrics using the vici interface for efficient virtual private network (VPN) management and performance. |
307 | [Synproxy](https://github.com/netdata/netdata/blob/master/src/collectors/proc.plugin/integrations/synproxy.md) | This integration provides statistics about the Synproxy netfilter module. |
308 +| TCP Stack | Monitor TCP stack counters and connection states on Windows systems. |
309 | [tc QoS classes](https://github.com/netdata/netdata/blob/master/src/collectors/tc.plugin/integrations/tc_qos_classes.md) | Examine tc metrics to gain insights into Linux traffic control operations. |
310 | [Timex](https://github.com/netdata/netdata/blob/master/src/collectors/timex.plugin/integrations/timex.md) | Examine Timex metrics to gain insights into system clock operations. |
311 | [Tor](https://github.com/netdata/netdata/blob/master/src/go/plugin/go.d/collector/tor/integrations/tor.md) | Tracks Tor's download and upload traffic, as well as its uptime. |
312 +| UDP Stack | Monitor UDP datagram counters on Windows systems. |
313 | [Ubiquiti UFiber OLT](https://github.com/netdata/netdata/blob/master/src/go/plugin/go.d/collector/prometheus/integrations/ubiquiti_ufiber_olt.md) | Track Ubiquiti UFiber GPON (Gigabit Passive Optical Network) device metrics for efficient fiber-optic network management and performance. |
314 | [Unbound](https://github.com/netdata/netdata/blob/master/src/go/plugin/go.d/collector/unbound/integrations/unbound.md) | This collector monitors Unbound servers. |
315 | [WireGuard](https://github.com/netdata/netdata/blob/master/src/go/plugin/go.d/collector/wireguard/integrations/wireguard.md) | This collector monitors WireGuard VPN devices and peers traffic. |
src/collectors/network-viewer.plugin/README.md deleted
-1
@@ -1 +0,0 @@
1 -integrations/network_connections.md
\ No newline at end of file
src/collectors/network-viewer.plugin/README.md new
+16
@@ -0,0 +1,16 @@
1 +# network-viewer.plugin
2 +
3 +This collector directory contains three integrations that share the same plugin target:
4 +
5 +- [Network Connections](integrations/network_connections.md) on Linux
6 +- [TCP Stack](integrations/tcp_stack.md) on Windows
7 +- [UDP Stack](integrations/udp_stack.md) on Windows
8 +
9 +## Overview
10 +
11 +`network-viewer.plugin` builds the Linux socket-table viewer on Linux and the TCP/UDP perflib collectors on Windows.
12 +
13 +## Default Configuration
14 +
15 +The Linux `Network Connections` integration has no collector-specific options.
16 +The Windows `TCP Stack` and `UDP Stack` integrations have no collector-specific options.
src/collectors/network-viewer.plugin/metadata.yaml
+96 -2
@@ -58,8 +58,8 @@ modules:
58 list: []
59 examples:
60 folding:
61 - title: ""
61 enabled: false
62 + title: ""
63 list: []
64 troubleshooting:
65 problems:
@@ -82,7 +82,7 @@ modules:
82 Shows active network connections with protocol details, states, addresses, ports, and performance metrics.
83
84 Provides both aggregated and detailed views of TCP and UDP connections for IPv4 and IPv6,
85 - including connection direction (listen, inbound, outbound), process information,
85 + including connection direction (listen, inbound, outbound, local), process information,
86 and TCP performance metrics (RTT, retransmissions).
87
88 Connections are classified as system or container based on network namespace.
@@ -93,3 +93,97 @@ modules:
93 performance: ""
94 security: ""
95 availability: ""
96 + - meta:
97 + plugin_name: network-viewer.plugin
98 + module_name: PerflibNetworkProtocols
99 + monitored_instance:
100 + name: Windows Network Protocols
101 + link: "https://learn.microsoft.com/en-us/windows/win32/networking"
102 + categories:
103 + - data-collection.networking
104 + icon_filename: windows.svg
105 + alternative_monitored_instances: []
106 + keywords:
107 + - network
108 + - tcp
109 + - udp
110 + - windows
111 + - perflib
112 + - segments
113 + - datagrams
114 + related_resources:
115 + integrations:
116 + list: []
117 + info_provided_to_referring_integrations:
118 + description: ""
119 + overview:
120 + data_collection:
121 + metrics_description: |
122 + This collector exposes TCP and UDP stack statistics on Windows systems via a real-time table function.
123 + method_description: |
124 + It queries the TCPv4, TCPv6, UDPv4, and UDPv6 objects from Windows Perflib,
125 + combining the results into a single table grouped by transport protocol and IP family.
126 + supported_platforms:
127 + include:
128 + - windows
129 + exclude: []
130 + multi_instance: false
131 + additional_permissions:
132 + description: ""
133 + default_behavior:
134 + auto_detection:
135 + description: |
136 + The collector automatically detects all of the metrics, no further configuration is required.
137 + limits:
138 + description: ""
139 + performance_impact:
140 + description: ""
141 + setup:
142 + prerequisites:
143 + list: []
144 + configuration:
145 + file:
146 + name: ""
147 + options:
148 + description: ""
149 + folding:
150 + title: ""
151 + enabled: false
152 + list: []
153 + examples:
154 + folding:
155 + title: ""
156 + enabled: false
157 + list: []
158 + troubleshooting:
159 + problems:
160 + list: []
161 + alerts: []
162 + metrics:
163 + folding:
164 + title: Metrics
165 + enabled: false
166 + description: ""
167 + availability: []
168 + scopes: []
169 + functions:
170 + description: |
171 + This collector exposes a real-time function for viewing Windows TCP and UDP stack statistics.
172 + list:
173 + - id: network-protocols
174 + name: Network Protocols
175 + description: |
176 + Shows Windows TCP and UDP stack counters grouped by transport protocol (TCP/UDP)
177 + and IP family (IPv4/IPv6).
178 +
179 + Each row covers one transport+family combination (TCPv4, TCPv6, UDPv4, UDPv6) and includes
180 + traffic counters (received, sent, errors), TCP-specific connection counters
181 + (active opens, established, passive opens, resets, total segments, retransmitted segments),
182 + and the UDP-specific datagrams-with-no-port counter.
183 + parameters: []
184 + returns:
185 + description: ""
186 + columns: []
187 + performance: ""
188 + security: ""
189 + availability: ""
src/collectors/network-viewer.plugin/perflib-tcp_udp.c new
+489
@@ -0,0 +1,489 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "libnetdata/libnetdata.h"
4 +#include "libnetdata/os/windows-perflib/perflib.h"
5 +
6 +#define PLUGIN_NETWORK_VIEWER_NAME "network-viewer.plugin"
7 +#define NV_WIN_FUNCTION_PROTO "network-protocols"
8 +#define NV_WIN_FUNCTION_PROTO_HELP "Windows TCP and UDP statistics by transport and IP family"
9 +#define NV_WIN_FUNCTION_UPDATE_EVERY 5
10 +#define NV_WIN_FUNCTION_PRIORITY 100
11 +
12 +netdata_mutex_t stdout_mutex;
13 +static bool plugin_should_exit = false;
14 +
15 +// ============================================================
16 +// Shared helpers
17 +// ============================================================
18 +
19 +// Resolve a perflib object by name; returns true and sets both out-pointers on success.
20 +static bool perflib_get_object(const char *object_name,
21 + PERF_DATA_BLOCK **pDataBlock_out,
22 + PERF_OBJECT_TYPE **pObjectType_out)
23 +{
24 + DWORD id = RegistryFindIDByName(object_name);
25 + if (id == PERFLIB_REGISTRY_NAME_NOT_FOUND)
26 + return false;
27 +
28 + *pDataBlock_out = perflibGetPerformanceData(id);
29 + if (!*pDataBlock_out)
30 + return false;
31 +
32 + *pObjectType_out = perflibFindObjectTypeByName(*pDataBlock_out, object_name);
33 + return *pObjectType_out != NULL;
34 +}
35 +
36 +// Write the common JSON table response header fields into an already-created buffer.
37 +static void nv_table_begin(BUFFER *wb, const char *help)
38 +{
39 + buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_MINIFY);
40 + buffer_json_member_add_uint64(wb, "status", HTTP_RESP_OK);
41 + buffer_json_member_add_string(wb, "type", "table");
42 + buffer_json_member_add_time_t(wb, "update_every", NV_WIN_FUNCTION_UPDATE_EVERY);
43 + buffer_json_member_add_boolean(wb, "has_history", false);
44 + buffer_json_member_add_string(wb, "help", help);
45 +}
46 +
47 +// Finalize the JSON, then send it to pluginsd under the stdout mutex.
48 +static void nv_send_result(const char *transaction, BUFFER *wb, time_t now_s)
49 +{
50 + buffer_json_member_add_time_t(wb, "expires", now_s + NV_WIN_FUNCTION_UPDATE_EVERY);
51 + buffer_json_finalize(wb);
52 + netdata_mutex_lock(&stdout_mutex);
53 + wb->response_code = HTTP_RESP_OK;
54 + wb->content_type = CT_APPLICATION_JSON;
55 + wb->expires = now_s + NV_WIN_FUNCTION_UPDATE_EVERY;
56 + pluginsd_function_result_to_stdout(transaction, wb);
57 + netdata_mutex_unlock(&stdout_mutex);
58 +}
59 +
60 +// Serialize pluginsd JSON errors the same way as success responses.
61 +static void nv_send_error(const char *transaction, int code, const char *message)
62 +{
63 + netdata_mutex_lock(&stdout_mutex);
64 + pluginsd_function_json_error_to_stdout(transaction, code, message);
65 + netdata_mutex_unlock(&stdout_mutex);
66 +}
67 +
68 +// Add a sticky string key column (Transport, Family, etc.).
69 +static void nv_add_key_field(BUFFER *wb, size_t *field_id, const char *id, const char *label)
70 +{
71 + buffer_rrdf_table_add_field(wb, (*field_id)++, id, label,
72 + RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
73 + 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL, RRDF_FIELD_SUMMARY_COUNT,
74 + RRDF_FIELD_FILTER_MULTISELECT,
75 + RRDF_FIELD_OPTS_UNIQUE_KEY | RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_STICKY, NULL);
76 +}
77 +
78 +// Add a standard integer counter column (all counter columns share the same display/filter flags).
79 +static void nv_add_int_field(BUFFER *wb, size_t *field_id,
80 + const char *id, const char *label, const char *unit)
81 +{
82 + buffer_rrdf_table_add_field(wb, (*field_id)++, id, label,
83 + RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NUMBER,
84 + 0, unit, NAN, RRDF_FIELD_SORT_DESCENDING, NULL, RRDF_FIELD_SUMMARY_SUM,
85 + RRDF_FIELD_FILTER_RANGE, RRDF_FIELD_OPTS_VISIBLE, NULL);
86 +}
87 +
88 +// Add one entry to the default_charts array: [chart_key, groupby_column].
89 +static void nv_add_default_chart(BUFFER *wb, const char *chart_key, const char *groupby)
90 +{
91 + buffer_json_add_array_item_array(wb);
92 + buffer_json_add_array_item_string(wb, chart_key);
93 + buffer_json_add_array_item_string(wb, groupby);
94 + buffer_json_array_close(wb);
95 +}
96 +
97 +// Add one entry to the group_by object: a single-column grouping keyed by name.
98 +static void nv_add_group_by(BUFFER *wb, const char *name)
99 +{
100 + buffer_json_member_add_object(wb, name);
101 + {
102 + buffer_json_member_add_string(wb, "name", name);
103 + buffer_json_member_add_array(wb, "columns");
104 + buffer_json_add_array_item_string(wb, name);
105 + buffer_json_array_close(wb);
106 + }
107 + buffer_json_object_close(wb);
108 +}
109 +
110 +// Convert Perflib rate counters to per-second values and keep raw gauges unchanged.
111 +static uint64_t nv_perflib_value(const COUNTER_DATA *cd)
112 +{
113 + if(unlikely(!cd->updated))
114 + return 0;
115 +
116 + switch(cd->current.CounterType) {
117 + case PERF_COUNTER_COUNTER:
118 + case PERF_SAMPLE_COUNTER:
119 + case PERF_COUNTER_BULK_COUNT: {
120 + if(unlikely(!cd->previous.Time || !cd->current.Frequency))
121 + return 0;
122 +
123 + ULONGLONG data1 = cd->current.Data;
124 + ULONGLONG data0 = cd->previous.Data;
125 + LONGLONG time1 = cd->current.Time;
126 + LONGLONG time0 = cd->previous.Time;
127 + LONGLONG dt = time1 - time0;
128 +
129 + if(unlikely(dt <= 0 || data1 < data0))
130 + return 0;
131 +
132 + return (uint64_t)(((double)(data1 - data0) * (double)cd->current.Frequency) / (double)dt);
133 + }
134 +
135 + default:
136 + return (uint64_t)cd->current.Data;
137 + }
138 +}
139 +
140 +// ============================================================
141 +// TCP
142 +// ============================================================
143 +
144 +typedef struct {
145 + const char *af;
146 + const char *object_name;
147 +
148 + COUNTER_DATA connection_failures;
149 + COUNTER_DATA connections_active;
150 + COUNTER_DATA connections_established;
151 + COUNTER_DATA connections_passive;
152 + COUNTER_DATA connections_reset;
153 + COUNTER_DATA segments_total;
154 + COUNTER_DATA segments_received;
155 + COUNTER_DATA segments_retransmitted;
156 + COUNTER_DATA segments_sent;
157 +} TCP_FAMILY;
158 +
159 +static TCP_FAMILY tcp_ipv4 = {
160 + .af = "IPv4",
161 + .object_name = "TCPv4",
162 +};
163 +
164 +static TCP_FAMILY tcp_ipv6 = {
165 + .af = "IPv6",
166 + .object_name = "TCPv6",
167 +};
168 +
169 +static void initialize_tcp_keys(TCP_FAMILY *tcp)
170 +{
171 + tcp->connection_failures.key = "Connection Failures";
172 + tcp->connections_active.key = "Connections Active";
173 + tcp->connections_established.key = "Connections Established";
174 + tcp->connections_passive.key = "Connections Passive";
175 + tcp->connections_reset.key = "Connections Reset";
176 + tcp->segments_total.key = "Segments/sec";
177 + tcp->segments_received.key = "Segments Received/sec";
178 + tcp->segments_retransmitted.key = "Segments Retransmitted/sec";
179 + tcp->segments_sent.key = "Segments Sent/sec";
180 +}
181 +
182 +
183 +static bool tcp_collect_family(TCP_FAMILY *tcp)
184 +{
185 + PERF_DATA_BLOCK *pDataBlock;
186 + PERF_OBJECT_TYPE *pObjectType;
187 + if (!perflib_get_object(tcp->object_name, &pDataBlock, &pObjectType))
188 + return false;
189 +
190 + bool have_any = false;
191 + have_any |= perflibGetObjectCounter(pDataBlock, pObjectType, &tcp->connection_failures);
192 + have_any |= perflibGetObjectCounter(pDataBlock, pObjectType, &tcp->connections_active);
193 + have_any |= perflibGetObjectCounter(pDataBlock, pObjectType, &tcp->connections_established);
194 + have_any |= perflibGetObjectCounter(pDataBlock, pObjectType, &tcp->connections_passive);
195 + have_any |= perflibGetObjectCounter(pDataBlock, pObjectType, &tcp->connections_reset);
196 + have_any |= perflibGetObjectCounter(pDataBlock, pObjectType, &tcp->segments_total);
197 + have_any |= perflibGetObjectCounter(pDataBlock, pObjectType, &tcp->segments_received);
198 + have_any |= perflibGetObjectCounter(pDataBlock, pObjectType, &tcp->segments_retransmitted);
199 + have_any |= perflibGetObjectCounter(pDataBlock, pObjectType, &tcp->segments_sent);
200 +
201 + return have_any;
202 +}
203 +
204 +// ============================================================
205 +// UDP
206 +// ============================================================
207 +
208 +typedef struct {
209 + const char *af;
210 + const char *object_name;
211 +
212 + COUNTER_DATA datagrams_no_port;
213 + COUNTER_DATA datagrams_received_errors;
214 + COUNTER_DATA datagrams_received;
215 + COUNTER_DATA datagrams_sent;
216 +} UDP_FAMILY;
217 +
218 +static UDP_FAMILY udp_ipv4 = {
219 + .af = "IPv4",
220 + .object_name = "UDPv4",
221 +};
222 +
223 +static UDP_FAMILY udp_ipv6 = {
224 + .af = "IPv6",
225 + .object_name = "UDPv6",
226 +};
227 +
228 +static netdata_mutex_t nv_collect_mutex;
229 +
230 +static void initialize_udp_keys(UDP_FAMILY *udp)
231 +{
232 + udp->datagrams_no_port.key = "Datagrams No Port/sec";
233 + udp->datagrams_received_errors.key = "Datagrams Received Errors";
234 + udp->datagrams_received.key = "Datagrams Received/sec";
235 + udp->datagrams_sent.key = "Datagrams Sent/sec";
236 +}
237 +
238 +
239 +static bool udp_collect_family(UDP_FAMILY *udp)
240 +{
241 + PERF_DATA_BLOCK *pDataBlock;
242 + PERF_OBJECT_TYPE *pObjectType;
243 + if (!perflib_get_object(udp->object_name, &pDataBlock, &pObjectType))
244 + return false;
245 +
246 + bool have_any = false;
247 + have_any |= perflibGetObjectCounter(pDataBlock, pObjectType, &udp->datagrams_no_port);
248 + have_any |= perflibGetObjectCounter(pDataBlock, pObjectType, &udp->datagrams_received_errors);
249 + have_any |= perflibGetObjectCounter(pDataBlock, pObjectType, &udp->datagrams_received);
250 + have_any |= perflibGetObjectCounter(pDataBlock, pObjectType, &udp->datagrams_sent);
251 +
252 + return have_any;
253 +}
254 +
255 +// ============================================================
256 +// Network Protocols (combined TCP + UDP function)
257 +// ============================================================
258 +
259 +// Column order for all rows: Transport, Family, Received, Sent, Errors,
260 +// ConnActive, ConnEstablished, ConnPassive, ConnReset, SegsTotal, SegsRetransmitted,
261 +// DatagramsNoPort.
262 +
263 +static void proto_emit_tcp_row(BUFFER *wb, const TCP_FAMILY *tcp)
264 +{
265 + buffer_json_add_array_item_array(wb);
266 + {
267 + buffer_json_add_array_item_string(wb, "TCP");
268 + buffer_json_add_array_item_string(wb, tcp->af);
269 + buffer_json_add_array_item_uint64(wb, nv_perflib_value(&tcp->segments_received));
270 + buffer_json_add_array_item_uint64(wb, nv_perflib_value(&tcp->segments_sent));
271 + buffer_json_add_array_item_uint64(wb, nv_perflib_value(&tcp->connection_failures));
272 + buffer_json_add_array_item_uint64(wb, nv_perflib_value(&tcp->connections_active));
273 + buffer_json_add_array_item_uint64(wb, nv_perflib_value(&tcp->connections_established));
274 + buffer_json_add_array_item_uint64(wb, nv_perflib_value(&tcp->connections_passive));
275 + buffer_json_add_array_item_uint64(wb, nv_perflib_value(&tcp->connections_reset));
276 + buffer_json_add_array_item_uint64(wb, nv_perflib_value(&tcp->segments_total));
277 + buffer_json_add_array_item_uint64(wb, nv_perflib_value(&tcp->segments_retransmitted));
278 + buffer_json_add_array_item_uint64(wb, 0); // DatagramsNoPort — UDP only
279 + }
280 + buffer_json_array_close(wb);
281 +}
282 +
283 +static void proto_emit_udp_row(BUFFER *wb, const UDP_FAMILY *udp)
284 +{
285 + buffer_json_add_array_item_array(wb);
286 + {
287 + buffer_json_add_array_item_string(wb, "UDP");
288 + buffer_json_add_array_item_string(wb, udp->af);
289 + buffer_json_add_array_item_uint64(wb, nv_perflib_value(&udp->datagrams_received));
290 + buffer_json_add_array_item_uint64(wb, nv_perflib_value(&udp->datagrams_sent));
291 + buffer_json_add_array_item_uint64(wb, nv_perflib_value(&udp->datagrams_received_errors));
292 + buffer_json_add_array_item_uint64(wb, 0); // ConnActive — TCP only
293 + buffer_json_add_array_item_uint64(wb, 0); // ConnEstablished — TCP only
294 + buffer_json_add_array_item_uint64(wb, 0); // ConnPassive — TCP only
295 + buffer_json_add_array_item_uint64(wb, 0); // ConnReset — TCP only
296 + buffer_json_add_array_item_uint64(wb, 0); // SegsTotal — TCP only
297 + buffer_json_add_array_item_uint64(wb, 0); // SegsRetransmitted — TCP only
298 + buffer_json_add_array_item_uint64(wb, nv_perflib_value(&udp->datagrams_no_port));
299 + }
300 + buffer_json_array_close(wb);
301 +}
302 +
303 +void function_network_protocols(
304 + const char *transaction, char *function __maybe_unused,
305 + usec_t *stop_monotonic_ut __maybe_unused, bool *cancelled,
306 + BUFFER *payload __maybe_unused, HTTP_ACCESS access __maybe_unused,
307 + const char *source __maybe_unused, void *data __maybe_unused)
308 +{
309 + bool have_tcp_ipv4 = false;
310 + bool have_tcp_ipv6 = false;
311 + bool have_udp_ipv4 = false;
312 + bool have_udp_ipv6 = false;
313 +
314 + if(unlikely(cancelled && __atomic_load_n(cancelled, __ATOMIC_RELAXED))) {
315 + nv_send_error(transaction, HTTP_RESP_CLIENT_CLOSED_REQUEST, "Request cancelled.");
316 + goto cleanup;
317 + }
318 +
319 + // Serialize access to the shared COUNTER_DATA state so previous/current
320 + // deltas are consistent regardless of which worker thread handles the request.
321 + // Hold the mutex across collection AND data-row emission: proto_emit_*_row
322 + // reads previous/current fields from the same shared structs that another
323 + // worker would overwrite during its own collection pass.
324 + netdata_mutex_lock(&nv_collect_mutex);
325 + have_tcp_ipv4 = tcp_collect_family(&tcp_ipv4);
326 + have_tcp_ipv6 = tcp_collect_family(&tcp_ipv6);
327 + have_udp_ipv4 = udp_collect_family(&udp_ipv4);
328 + have_udp_ipv6 = udp_collect_family(&udp_ipv6);
329 +
330 + if(unlikely(cancelled && __atomic_load_n(cancelled, __ATOMIC_RELAXED))) {
331 + netdata_mutex_unlock(&nv_collect_mutex);
332 + nv_send_error(transaction, HTTP_RESP_CLIENT_CLOSED_REQUEST, "Request cancelled.");
333 + goto cleanup;
334 + }
335 +
336 + if(unlikely(!have_tcp_ipv4 && !have_tcp_ipv6 && !have_udp_ipv4 && !have_udp_ipv6)) {
337 + netdata_mutex_unlock(&nv_collect_mutex);
338 + nv_send_error(transaction, HTTP_RESP_INTERNAL_SERVER_ERROR,
339 + "failed to collect Windows TCP/UDP stack statistics");
340 + goto cleanup;
341 + }
342 +
343 + time_t now_s = now_realtime_sec();
344 + CLEAN_BUFFER *wb = buffer_create(0, NULL);
345 + nv_table_begin(wb, NV_WIN_FUNCTION_PROTO_HELP);
346 +
347 + buffer_json_member_add_array(wb, "data");
348 + {
349 + if(have_tcp_ipv4)
350 + proto_emit_tcp_row(wb, &tcp_ipv4);
351 + if(have_tcp_ipv6)
352 + proto_emit_tcp_row(wb, &tcp_ipv6);
353 + if(have_udp_ipv4)
354 + proto_emit_udp_row(wb, &udp_ipv4);
355 + if(have_udp_ipv6)
356 + proto_emit_udp_row(wb, &udp_ipv6);
357 + }
358 + buffer_json_array_close(wb); // data
359 + netdata_mutex_unlock(&nv_collect_mutex);
360 +
361 + size_t field_id = 0;
362 + buffer_json_member_add_object(wb, "columns");
363 + {
364 + nv_add_key_field(wb, &field_id, "Transport", "Transport Protocol");
365 + nv_add_key_field(wb, &field_id, "Family", "IP Protocol Family");
366 +
367 + // Normalized columns — TCP: segments, UDP: datagrams
368 + nv_add_int_field(wb, &field_id, "Received", "Received (Segments/Datagrams)", "segments/datagrams/s");
369 + nv_add_int_field(wb, &field_id, "Sent", "Sent (Segments/Datagrams)", "segments/datagrams/s");
370 + nv_add_int_field(wb, &field_id, "Errors", "Errors (Failures/Rx Errors)", "errors");
371 +
372 + // TCP-only columns (UDP rows carry 0)
373 + // ConnActive/ConnPassive/ConnReset are PERF_COUNTER_RAWCOUNT cumulative totals,
374 + // not per-second rates, so units have no /s suffix.
375 + nv_add_int_field(wb, &field_id, "ConnActive", "Active Connections Opened", "opens");
376 + nv_add_int_field(wb, &field_id, "ConnEstablished", "Currently Established Connections", "connections");
377 + nv_add_int_field(wb, &field_id, "ConnPassive", "Passive Connections Opened", "opens");
378 + nv_add_int_field(wb, &field_id, "ConnReset", "Reset Connections", "resets");
379 + nv_add_int_field(wb, &field_id, "SegsTotal", "Total Segments", "segments/s");
380 + nv_add_int_field(wb, &field_id, "SegsRetransmitted", "Retransmitted Segments", "segments/s");
381 +
382 + // UDP-only column (TCP rows carry 0)
383 + nv_add_int_field(wb, &field_id, "DatagramsNoPort", "Datagrams with No Port", "datagrams/s");
384 + }
385 + buffer_json_object_close(wb); // columns
386 + buffer_json_member_add_string(wb, "default_sort_column", "Received");
387 +
388 + // charts.columns = metric columns for the Y axis (NOT the groupby column)
389 + buffer_json_member_add_object(wb, "charts");
390 + {
391 + buffer_json_member_add_object(wb, "Traffic");
392 + {
393 + buffer_json_member_add_string(wb, "name", "Traffic");
394 + buffer_json_member_add_string(wb, "type", "stacked-bar");
395 + buffer_json_member_add_array(wb, "columns");
396 + {
397 + buffer_json_add_array_item_string(wb, "Received");
398 + buffer_json_add_array_item_string(wb, "Sent");
399 + }
400 + buffer_json_array_close(wb);
401 + }
402 + buffer_json_object_close(wb);
403 + }
404 + buffer_json_object_close(wb); // charts
405 +
406 + // default_charts: [chart_key, groupby_column] — same chart, two grouping axes
407 + buffer_json_member_add_array(wb, "default_charts");
408 + {
409 + nv_add_default_chart(wb, "Traffic", "Transport");
410 + nv_add_default_chart(wb, "Traffic", "Family");
411 + }
412 + buffer_json_array_close(wb); // default_charts
413 +
414 + buffer_json_member_add_object(wb, "group_by");
415 + {
416 + nv_add_group_by(wb, "Transport");
417 + nv_add_group_by(wb, "Family");
418 + }
419 + buffer_json_object_close(wb); // group_by
420 +
421 + nv_send_result(transaction, wb, now_s);
422 +
423 +cleanup:
424 + // Release the thread-local perflib buffer once per request to avoid retaining
425 + // the largest query size for the lifetime of the worker thread.
426 + perflibFreePerformanceData();
427 +}
428 +
429 +// ============================================================
430 +// main
431 +// ============================================================
432 +
433 +int main(int argc, char **argv)
434 +{
435 + netdata_mutex_init(&stdout_mutex);
436 + nd_log_initialize_for_external_plugins("network-viewer.plugin");
437 + netdata_threads_init_for_external_plugins(0);
438 +
439 + PerflibNamesRegistryInitialize();
440 + netdata_mutex_init(&nv_collect_mutex);
441 +
442 + // Prime each family's COUNTER_DATA so the first real request has a valid
443 + // previous baseline and rate counters return non-zero values immediately.
444 + initialize_tcp_keys(&tcp_ipv4);
445 + initialize_tcp_keys(&tcp_ipv6);
446 + initialize_udp_keys(&udp_ipv4);
447 + initialize_udp_keys(&udp_ipv6);
448 + tcp_collect_family(&tcp_ipv4);
449 + tcp_collect_family(&tcp_ipv6);
450 + udp_collect_family(&udp_ipv4);
451 + udp_collect_family(&udp_ipv6);
452 + perflibFreePerformanceData();
453 +
454 + fprintf(stdout,
455 + PLUGINSD_KEYWORD_FUNCTION " GLOBAL \"%s\" %d \"%s\" \"top\" " HTTP_ACCESS_FORMAT " %d\n",
456 + NV_WIN_FUNCTION_PROTO, PLUGINS_FUNCTIONS_TIMEOUT_DEFAULT, NV_WIN_FUNCTION_PROTO_HELP,
457 + (HTTP_ACCESS_FORMAT_CAST)(HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE),
458 + NV_WIN_FUNCTION_PRIORITY);
459 + fflush(stdout);
460 +
461 + struct functions_evloop_globals *wg =
462 + functions_evloop_init(5, "NV-WIN", &stdout_mutex, &plugin_should_exit, NULL);
463 +
464 + functions_evloop_add_function(wg, NV_WIN_FUNCTION_PROTO, function_network_protocols,
465 + PLUGINS_FUNCTIONS_TIMEOUT_DEFAULT, NULL);
466 +
467 + usec_t send_newline_ut = 0;
468 + const bool tty = isatty(fileno(stdout)) == 1;
469 +
470 + heartbeat_t hb;
471 + heartbeat_init(&hb, USEC_PER_SEC);
472 +
473 + while (!__atomic_load_n(&plugin_should_exit, __ATOMIC_ACQUIRE)) {
474 + usec_t dt_ut = heartbeat_next(&hb);
475 + send_newline_ut += dt_ut;
476 +
477 + if (!tty && send_newline_ut > USEC_PER_SEC) {
478 + send_newline_and_flush(&stdout_mutex);
479 + send_newline_ut = 0;
480 + }
481 +
482 + PerflibNamesRegistryUpdate();
483 + }
484 +
485 + functions_evloop_cancel_threads(wg);
486 + PerflibNamesRegistryCleanup();
487 +
488 + return 0;
489 +}
src/collectors/network-viewer.plugin/taxonomy.yaml new
+5
@@ -0,0 +1,5 @@
1 +taxonomy_version: 1
2 +plugin_name: network-viewer.plugin
3 +module_name: network-viewer.plugin
4 +taxonomy_optout:
5 + reason: This collector exposes live function views only and does not provide static metric contexts for dashboard taxonomy placement.
src/go/plugin/go.d/collector/prometheus/taxonomy.yaml new
+5
@@ -0,0 +1,5 @@
1 +taxonomy_version: 1
2 +plugin_name: go.d.plugin
3 +module_name: prometheus
4 +taxonomy_optout:
5 + reason: Generic Prometheus jobs define dynamic, endpoint-specific chart contexts at runtime and do not expose stable collector-level metric scopes for dashboard taxonomy placement.
src/libnetdata/os/windows-api/windows_api.c
+3
@@ -5,8 +5,11 @@
5 #include <winsock2.h>
6 #include <ws2tcpip.h>
7 #include <iphlpapi.h>
8 +#include <stdlib.h>
9 +#include <string.h>
10 #include <stdbool.h>
11
12 +
13 struct netdata_windows_ip_labels {
14 char *local_iface;
15 char *ipaddr;
src/libnetdata/os/windows-api/windows_api.h
+2
@@ -5,6 +5,8 @@
5
6 #if defined(OS_WINDOWS)
7
8 +#include <stdbool.h>
9 +
10 char *netdata_win_local_interface();
11 char *netdata_win_local_ip();
12
src/libnetdata/socket/socket.c
+70
@@ -361,6 +361,76 @@ int sock_set_tcp_defer_accept(int fd __maybe_unused, bool defer __maybe_unused)
361 inline int wait_on_socket_or_cancel_with_timeout(
362 NETDATA_SSL *ssl,
363 int fd, int timeout_ms, short int poll_events, short int *revents) {
364 +
365 +#if defined(OS_WINDOWS)
366 + // WSAPoll() (used internally by MinGW poll()) only works for sockets.
367 + // For pipe file descriptors (e.g. stdin when launched as a subprocess),
368 + // poll() fails and kills the reader thread. Use PeekNamedPipe instead.
369 + if(poll_events & POLLIN) {
370 + HANDLE h = (HANDLE)_get_osfhandle(fd);
371 + if(h != INVALID_HANDLE_VALUE && GetFileType(h) == FILE_TYPE_PIPE) {
372 + bool forever = (timeout_ms <= 0);
373 + if(revents)
374 + *revents = 0;
375 + while(timeout_ms > 0 || forever) {
376 + if(nd_thread_signaled_to_cancel()) {
377 + errno = ECANCELED;
378 + if(revents)
379 + *revents = 0;
380 + return -1;
381 + }
382 +
383 + DWORD available = 0;
384 + if(!PeekNamedPipe(h, NULL, 0, NULL, &available, NULL)) {
385 + DWORD winerr = GetLastError();
386 + short pipe_revents = POLLERR;
387 + switch(winerr) {
388 + case ERROR_BROKEN_PIPE:
389 + case ERROR_PIPE_NOT_CONNECTED:
390 + case ERROR_NO_DATA:
391 + errno = EPIPE;
392 + pipe_revents = POLLHUP;
393 + break;
394 + case ERROR_INVALID_HANDLE:
395 + errno = EBADF;
396 + pipe_revents = POLLNVAL;
397 + break;
398 + case ERROR_OPERATION_ABORTED:
399 + errno = ECANCELED;
400 + if(revents)
401 + *revents = 0;
402 + return -1;
403 + default:
404 + errno = EIO;
405 + pipe_revents = POLLERR;
406 + break;
407 + }
408 + if(revents) *revents = pipe_revents;
409 + return 2;
410 + }
411 +
412 + if(available > 0) {
413 + if(revents) *revents = POLLIN;
414 + return 0;
415 + }
416 +
417 + // Waiting on the pipe HANDLE itself is not a reliable readiness
418 + // indicator for named pipes. Use a time-based loop around
419 + // PeekNamedPipe() and sleep only for the cancellability window.
420 + const DWORD wait_ms = (DWORD)((timeout_ms >= ND_CHECK_CANCELLABILITY_WHILE_WAITING_EVERY_MS || forever) ?
421 + ND_CHECK_CANCELLABILITY_WHILE_WAITING_EVERY_MS : timeout_ms);
422 + Sleep(wait_ms);
423 + if(!forever)
424 + timeout_ms -= (int)wait_ms;
425 + }
426 + errno = ETIMEDOUT;
427 + if(revents)
428 + *revents = 0;
429 + return 1;
430 + }
431 + }
432 +#endif
433 +
434 struct pollfd pfd = {
435 .fd = fd,
436 .events = poll_events,