xref: /freebsd/usr.bin/netstat/netisr.c (revision 95968ea7ec8f1cd3c1d03cc56c704cb2392d0d25)
1  /*-
2   * SPDX-License-Identifier: BSD-2-Clause
3   *
4   * Copyright (c) 2010-2011 Juniper Networks, Inc.
5   * All rights reserved.
6   *
7   * This software was developed by Robert N. M. Watson under contract
8   * to Juniper Networks, Inc.
9   *
10   * Redistribution and use in source and binary forms, with or without
11   * modification, are permitted provided that the following conditions
12   * are met:
13   * 1. Redistributions of source code must retain the above copyright
14   *    notice, this list of conditions and the following disclaimer.
15   * 2. Redistributions in binary form must reproduce the above copyright
16   *    notice, this list of conditions and the following disclaimer in the
17   *    documentation and/or other materials provided with the distribution.
18   *
19   * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22   * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
23   * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24   * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25   * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27   * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28   * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29   * SUCH DAMAGE.
30   */
31  
32  
33  #include <sys/param.h>
34  #include <sys/sysctl.h>
35  
36  #include <sys/_lock.h>
37  #include <sys/_mutex.h>
38  
39  #define	_WANT_NETISR_INTERNAL
40  #include <net/netisr.h>
41  #include <net/netisr_internal.h>
42  
43  #include <stdint.h>
44  #include <stdio.h>
45  #include <stdlib.h>
46  #include <stdbool.h>
47  #include <string.h>
48  #include <sysexits.h>
49  #include <libxo/xo.h>
50  #include "netstat.h"
51  #include "nl_defs.h"
52  
53  /*
54   * Print statistics for the kernel netisr subsystem.
55   */
56  static u_int				 bindthreads;
57  static u_int				 maxthreads;
58  static u_int				 numthreads;
59  
60  static u_int				 defaultqlimit;
61  static u_int				 maxqlimit;
62  
63  static char				 dispatch_policy[20];
64  
65  static struct sysctl_netisr_proto	*proto_array;
66  static u_int				 proto_array_len;
67  
68  static struct sysctl_netisr_workstream	*workstream_array;
69  static u_int				 workstream_array_len;
70  
71  static struct sysctl_netisr_work	*work_array;
72  static u_int				 work_array_len;
73  
74  static u_int				*nws_array;
75  
76  static u_int				 maxprot;
77  
78  static void
netisr_dispatch_policy_to_string(u_int policy,char * buf,size_t buflen)79  netisr_dispatch_policy_to_string(u_int policy, char *buf,
80      size_t buflen)
81  {
82  	const char *str;
83  
84  	switch (policy) {
85  	case NETISR_DISPATCH_DEFAULT:
86  		str = "default";
87  		break;
88  	case NETISR_DISPATCH_DEFERRED:
89  		str = "deferred";
90  		break;
91  	case NETISR_DISPATCH_HYBRID:
92  		str = "hybrid";
93  		break;
94  	case NETISR_DISPATCH_DIRECT:
95  		str = "direct";
96  		break;
97  	default:
98  		str = "unknown";
99  		break;
100  	}
101  	snprintf(buf, buflen, "%s", str);
102  }
103  
104  /*
105   * Load a nul-terminated string from KVM up to 'limit', guarantee that the
106   * string in local memory is nul-terminated.
107   */
108  static void
netisr_load_kvm_string(uintptr_t addr,char * dest,u_int limit)109  netisr_load_kvm_string(uintptr_t addr, char *dest, u_int limit)
110  {
111  	u_int i;
112  
113  	for (i = 0; i < limit; i++) {
114  		if (kread(addr + i, &dest[i], sizeof(dest[i])) != 0)
115  			xo_errx(EX_OSERR, "%s: kread()", __func__);
116  		if (dest[i] == '\0')
117  			break;
118  	}
119  	dest[limit - 1] = '\0';
120  }
121  
122  static const char *
netisr_proto2name(u_int proto)123  netisr_proto2name(u_int proto)
124  {
125  	u_int i;
126  
127  	for (i = 0; i < proto_array_len; i++) {
128  		if (proto_array[i].snp_proto == proto)
129  			return (proto_array[i].snp_name);
130  	}
131  	return ("unknown");
132  }
133  
134  static int
netisr_protoispresent(u_int proto)135  netisr_protoispresent(u_int proto)
136  {
137  	u_int i;
138  
139  	for (i = 0; i < proto_array_len; i++) {
140  		if (proto_array[i].snp_proto == proto)
141  			return (1);
142  	}
143  	return (0);
144  }
145  
146  static void
netisr_load_kvm_config(void)147  netisr_load_kvm_config(void)
148  {
149  	u_int tmp;
150  
151  	kread(nl[N_NETISR_BINDTHREADS].n_value, &bindthreads, sizeof(u_int));
152  	kread(nl[N_NETISR_MAXTHREADS].n_value, &maxthreads, sizeof(u_int));
153  	kread(nl[N_NWS_COUNT].n_value, &numthreads, sizeof(u_int));
154  	kread(nl[N_NETISR_DEFAULTQLIMIT].n_value, &defaultqlimit,
155  	    sizeof(u_int));
156  	kread(nl[N_NETISR_MAXQLIMIT].n_value, &maxqlimit, sizeof(u_int));
157  	kread(nl[N_NETISR_DISPATCH_POLICY].n_value, &tmp, sizeof(u_int));
158  
159  	netisr_dispatch_policy_to_string(tmp, dispatch_policy,
160  	    sizeof(dispatch_policy));
161  }
162  
163  static void
netisr_load_sysctl_uint(const char * name,u_int * p)164  netisr_load_sysctl_uint(const char *name, u_int *p)
165  {
166  	size_t retlen;
167  
168  	retlen = sizeof(u_int);
169  	if (sysctlbyname(name, p, &retlen, NULL, 0) < 0)
170  		xo_err(EX_OSERR, "%s", name);
171  	if (retlen != sizeof(u_int))
172  		xo_errx(EX_DATAERR, "%s: invalid len %ju", name, (uintmax_t)retlen);
173  }
174  
175  static void
netisr_load_sysctl_string(const char * name,char * p,size_t len)176  netisr_load_sysctl_string(const char *name, char *p, size_t len)
177  {
178  	size_t retlen;
179  
180  	retlen = len;
181  	if (sysctlbyname(name, p, &retlen, NULL, 0) < 0)
182  		xo_err(EX_OSERR, "%s", name);
183  	p[len - 1] = '\0';
184  }
185  
186  static void
netisr_load_sysctl_config(void)187  netisr_load_sysctl_config(void)
188  {
189  
190  	netisr_load_sysctl_uint("net.isr.bindthreads", &bindthreads);
191  	netisr_load_sysctl_uint("net.isr.maxthreads", &maxthreads);
192  	netisr_load_sysctl_uint("net.isr.numthreads", &numthreads);
193  
194  	netisr_load_sysctl_uint("net.isr.defaultqlimit", &defaultqlimit);
195  	netisr_load_sysctl_uint("net.isr.maxqlimit", &maxqlimit);
196  
197  	netisr_load_sysctl_string("net.isr.dispatch", dispatch_policy,
198  	    sizeof(dispatch_policy));
199  }
200  
201  static void
netisr_load_kvm_proto(void)202  netisr_load_kvm_proto(void)
203  {
204  	struct netisr_proto *np_array, *npp;
205  	u_int i, protocount;
206  	struct sysctl_netisr_proto *snpp;
207  	size_t len;
208  
209  	/*
210  	 * Kernel compile-time and user compile-time definitions of
211  	 * NETISR_MAXPROT must match, as we use that to size work arrays.
212  	 */
213  	kread(nl[N_NETISR_MAXPROT].n_value, &maxprot, sizeof(u_int));
214  	if (maxprot != NETISR_MAXPROT)
215  		xo_errx(EX_DATAERR, "%s: NETISR_MAXPROT mismatch", __func__);
216  	len = maxprot * sizeof(*np_array);
217  	np_array = malloc(len);
218  	if (np_array == NULL)
219  		xo_err(EX_OSERR, "%s: malloc", __func__);
220  	if (kread(nl[N_NETISR_PROTO].n_value, np_array, len) != 0)
221  		xo_errx(EX_DATAERR, "%s: kread(_netisr_proto)", __func__);
222  
223  	/*
224  	 * Size and allocate memory to hold only live protocols.
225  	 */
226  	protocount = 0;
227  	for (i = 0; i < maxprot; i++) {
228  		if (np_array[i].np_name == NULL)
229  			continue;
230  		protocount++;
231  	}
232  	proto_array = calloc(protocount, sizeof(*proto_array));
233  	if (proto_array == NULL)
234  		xo_err(EX_OSERR, "malloc");
235  	protocount = 0;
236  	for (i = 0; i < maxprot; i++) {
237  		npp = &np_array[i];
238  		if (npp->np_name == NULL)
239  			continue;
240  		snpp = &proto_array[protocount];
241  		snpp->snp_version = sizeof(*snpp);
242  		netisr_load_kvm_string((uintptr_t)npp->np_name,
243  		    snpp->snp_name, sizeof(snpp->snp_name));
244  		snpp->snp_proto = i;
245  		snpp->snp_qlimit = npp->np_qlimit;
246  		snpp->snp_policy = npp->np_policy;
247  		snpp->snp_dispatch = npp->np_dispatch;
248  		if (npp->np_m2flow != NULL)
249  			snpp->snp_flags |= NETISR_SNP_FLAGS_M2FLOW;
250  		if (npp->np_m2cpuid != NULL)
251  			snpp->snp_flags |= NETISR_SNP_FLAGS_M2CPUID;
252  		if (npp->np_drainedcpu != NULL)
253  			snpp->snp_flags |= NETISR_SNP_FLAGS_DRAINEDCPU;
254  		protocount++;
255  	}
256  	proto_array_len = protocount;
257  	free(np_array);
258  }
259  
260  static void
netisr_load_sysctl_proto(void)261  netisr_load_sysctl_proto(void)
262  {
263  	size_t len;
264  
265  	if (sysctlbyname("net.isr.proto", NULL, &len, NULL, 0) < 0)
266  		xo_err(EX_OSERR, "net.isr.proto: query len");
267  	if (len % sizeof(*proto_array) != 0)
268  		xo_errx(EX_DATAERR, "net.isr.proto: invalid len");
269  	proto_array = malloc(len);
270  	if (proto_array == NULL)
271  		xo_err(EX_OSERR, "malloc");
272  	if (sysctlbyname("net.isr.proto", proto_array, &len, NULL, 0) < 0)
273  		xo_err(EX_OSERR, "net.isr.proto: query data");
274  	if (len % sizeof(*proto_array) != 0)
275  		xo_errx(EX_DATAERR, "net.isr.proto: invalid len");
276  	proto_array_len = len / sizeof(*proto_array);
277  	if (proto_array_len < 1)
278  		xo_errx(EX_DATAERR, "net.isr.proto: no data");
279  	if (proto_array[0].snp_version != sizeof(proto_array[0]))
280  		xo_errx(EX_DATAERR, "net.isr.proto: invalid version");
281  }
282  
283  static void
netisr_load_kvm_workstream(void)284  netisr_load_kvm_workstream(void)
285  {
286  	struct netisr_workstream nws;
287  	struct sysctl_netisr_workstream *snwsp;
288  	struct sysctl_netisr_work *snwp;
289  	struct netisr_work *nwp;
290  	u_int counter, cpuid, proto, wsid;
291  	size_t len;
292  
293  	len = numthreads * sizeof(*nws_array);
294  	nws_array = malloc(len);
295  	if (nws_array == NULL)
296  		xo_err(EX_OSERR, "malloc");
297  	if (kread(nl[N_NWS_ARRAY].n_value, nws_array, len) != 0)
298  		xo_errx(EX_OSERR, "%s: kread(_nws_array)", __func__);
299  	workstream_array = calloc(numthreads, sizeof(*workstream_array));
300  	if (workstream_array == NULL)
301  		xo_err(EX_OSERR, "calloc");
302  	workstream_array_len = numthreads;
303  	work_array = calloc(numthreads * proto_array_len, sizeof(*work_array));
304  	if (work_array == NULL)
305  		xo_err(EX_OSERR, "calloc");
306  	counter = 0;
307  	for (wsid = 0; wsid < numthreads; wsid++) {
308  		cpuid = nws_array[wsid];
309  		kset_dpcpu(cpuid);
310  		if (kread(nl[N_NWS].n_value, &nws, sizeof(nws)) != 0)
311  			xo_errx(EX_OSERR, "%s: kread(nw)", __func__);
312  		snwsp = &workstream_array[wsid];
313  		snwsp->snws_version = sizeof(*snwsp);
314  		snwsp->snws_wsid = cpuid;
315  		snwsp->snws_cpu = cpuid;
316  		if (nws.nws_intr_event != NULL)
317  			snwsp->snws_flags |= NETISR_SNWS_FLAGS_INTR;
318  
319  		/*
320  		 * Extract the CPU's per-protocol work information.
321  		 */
322  		xo_emit("counting to maxprot: {:maxprot/%u}\n", maxprot);
323  		for (proto = 0; proto < maxprot; proto++) {
324  			if (!netisr_protoispresent(proto))
325  				continue;
326  			nwp = &nws.nws_work[proto];
327  			snwp = &work_array[counter];
328  			snwp->snw_version = sizeof(*snwp);
329  			snwp->snw_wsid = cpuid;
330  			snwp->snw_proto = proto;
331  			snwp->snw_len = nwp->nw_len;
332  			snwp->snw_watermark = nwp->nw_watermark;
333  			snwp->snw_dispatched = nwp->nw_dispatched;
334  			snwp->snw_hybrid_dispatched =
335  			    nwp->nw_hybrid_dispatched;
336  			snwp->snw_qdrops = nwp->nw_qdrops;
337  			snwp->snw_queued = nwp->nw_queued;
338  			snwp->snw_handled = nwp->nw_handled;
339  			counter++;
340  		}
341  	}
342  	work_array_len = counter;
343  }
344  
345  static void
netisr_load_sysctl_workstream(void)346  netisr_load_sysctl_workstream(void)
347  {
348  	size_t len;
349  
350  	if (sysctlbyname("net.isr.workstream", NULL, &len, NULL, 0) < 0)
351  		xo_err(EX_OSERR, "net.isr.workstream: query len");
352  	if (len % sizeof(*workstream_array) != 0)
353  		xo_errx(EX_DATAERR, "net.isr.workstream: invalid len");
354  	workstream_array = malloc(len);
355  	if (workstream_array == NULL)
356  		xo_err(EX_OSERR, "malloc");
357  	if (sysctlbyname("net.isr.workstream", workstream_array, &len, NULL,
358  	    0) < 0)
359  		xo_err(EX_OSERR, "net.isr.workstream: query data");
360  	if (len % sizeof(*workstream_array) != 0)
361  		xo_errx(EX_DATAERR, "net.isr.workstream: invalid len");
362  	workstream_array_len = len / sizeof(*workstream_array);
363  	if (workstream_array_len < 1)
364  		xo_errx(EX_DATAERR, "net.isr.workstream: no data");
365  	if (workstream_array[0].snws_version != sizeof(workstream_array[0]))
366  		xo_errx(EX_DATAERR, "net.isr.workstream: invalid version");
367  }
368  
369  static void
netisr_load_sysctl_work(void)370  netisr_load_sysctl_work(void)
371  {
372  	size_t len;
373  
374  	if (sysctlbyname("net.isr.work", NULL, &len, NULL, 0) < 0)
375  		xo_err(EX_OSERR, "net.isr.work: query len");
376  	if (len % sizeof(*work_array) != 0)
377  		xo_errx(EX_DATAERR, "net.isr.work: invalid len");
378  	work_array = malloc(len);
379  	if (work_array == NULL)
380  		xo_err(EX_OSERR, "malloc");
381  	if (sysctlbyname("net.isr.work", work_array, &len, NULL, 0) < 0)
382  		xo_err(EX_OSERR, "net.isr.work: query data");
383  	if (len % sizeof(*work_array) != 0)
384  		xo_errx(EX_DATAERR, "net.isr.work: invalid len");
385  	work_array_len = len / sizeof(*work_array);
386  	if (work_array_len < 1)
387  		xo_errx(EX_DATAERR, "net.isr.work: no data");
388  	if (work_array[0].snw_version != sizeof(work_array[0]))
389  		xo_errx(EX_DATAERR, "net.isr.work: invalid version");
390  }
391  
392  static void
netisr_print_proto(struct sysctl_netisr_proto * snpp)393  netisr_print_proto(struct sysctl_netisr_proto *snpp)
394  {
395  	char tmp[20];
396  
397  	xo_emit("{[:-6}{k:name/%s}{]:}", snpp->snp_name);
398  	xo_emit(" {:protocol/%5u}", snpp->snp_proto);
399  	xo_emit(" {:queue-limit/%6u}", snpp->snp_qlimit);
400  	xo_emit(" {:policy-type/%6s}",
401  	    (snpp->snp_policy == NETISR_POLICY_SOURCE) ?  "source" :
402  	    (snpp->snp_policy == NETISR_POLICY_FLOW) ? "flow" :
403  	    (snpp->snp_policy == NETISR_POLICY_CPU) ? "cpu" : "-");
404  	netisr_dispatch_policy_to_string(snpp->snp_dispatch, tmp,
405  	    sizeof(tmp));
406  	xo_emit(" {:policy/%8s}", tmp);
407  	xo_emit("   {:flags/%s%s%s}\n",
408  	    (snpp->snp_flags & NETISR_SNP_FLAGS_M2CPUID) ?  "C" : "-",
409  	    (snpp->snp_flags & NETISR_SNP_FLAGS_DRAINEDCPU) ?  "D" : "-",
410  	    (snpp->snp_flags & NETISR_SNP_FLAGS_M2FLOW) ? "F" : "-");
411  }
412  
413  static void
netisr_print_workstream(struct sysctl_netisr_workstream * snwsp)414  netisr_print_workstream(struct sysctl_netisr_workstream *snwsp)
415  {
416  	struct sysctl_netisr_work *snwp;
417  	u_int i;
418  
419  	xo_open_list("work");
420  	for (i = 0; i < work_array_len; i++) {
421  		snwp = &work_array[i];
422  		if (snwp->snw_wsid != snwsp->snws_wsid)
423  			continue;
424  		xo_open_instance("work");
425  		xo_emit("{t:workstream/%4u} ", snwsp->snws_wsid);
426  		xo_emit("{t:cpu/%3u} ", snwsp->snws_cpu);
427  		xo_emit("{P:  }");
428  		xo_emit("{t:name/%-6s}", netisr_proto2name(snwp->snw_proto));
429  		xo_emit(" {t:length/%5u}", snwp->snw_len);
430  		xo_emit(" {t:watermark/%5u}", snwp->snw_watermark);
431  		xo_emit(" {t:dispatched/%8ju}", snwp->snw_dispatched);
432  		xo_emit(" {t:hybrid-dispatched/%8ju}",
433  		    snwp->snw_hybrid_dispatched);
434  		xo_emit(" {t:queue-drops/%8ju}", snwp->snw_qdrops);
435  		xo_emit(" {t:queued/%8ju}", snwp->snw_queued);
436  		xo_emit(" {t:handled/%8ju}", snwp->snw_handled);
437  		xo_emit("\n");
438  		xo_close_instance("work");
439  	}
440  	xo_close_list("work");
441  }
442  
443  void
netisr_stats(void)444  netisr_stats(void)
445  {
446  	struct sysctl_netisr_workstream *snwsp;
447  	struct sysctl_netisr_proto *snpp;
448  	u_int i;
449  
450  	if (live) {
451  		netisr_load_sysctl_config();
452  		netisr_load_sysctl_proto();
453  		netisr_load_sysctl_workstream();
454  		netisr_load_sysctl_work();
455  	} else {
456  		netisr_load_kvm_config();
457  		netisr_load_kvm_proto();
458  		netisr_load_kvm_workstream();		/* Also does work. */
459  	}
460  
461  	xo_open_container("netisr");
462  
463  	xo_emit("{T:Configuration}:\n");
464  	xo_emit("{T:/%-25s} {T:/%12s} {T:/%12s}\n",
465  	    "Setting", "Current", "Limit");
466  	xo_emit("{T:/%-25s} {T:/%12u} {T:/%12u}\n",
467  	    "Thread count", numthreads, maxthreads);
468  	xo_emit("{T:/%-25s} {T:/%12u} {T:/%12u}\n",
469  	    "Default queue limit", defaultqlimit, maxqlimit);
470  	xo_emit("{T:/%-25s} {T:/%12s} {T:/%12s}\n",
471  	    "Dispatch policy", dispatch_policy, "n/a");
472  	xo_emit("{T:/%-25s} {T:/%12s} {T:/%12s}\n",
473  	    "Threads bound to CPUs", bindthreads ? "enabled" : "disabled",
474  	    "n/a");
475  	xo_emit("\n");
476  
477  	xo_emit("{T:Protocols}:\n");
478  	xo_emit("{T:/%-6s} {T:/%5s} {T:/%6s} {T:/%-6s} {T:/%-8s} {T:/%-5s}\n",
479  	    "Name", "Proto", "QLimit", "Policy", "Dispatch", "Flags");
480  	xo_open_list("protocol");
481  	for (i = 0; i < proto_array_len; i++) {
482  		xo_open_instance("protocol");
483  		snpp = &proto_array[i];
484  		netisr_print_proto(snpp);
485  		xo_close_instance("protocol");
486  	}
487  	xo_close_list("protocol");
488  	xo_emit("\n");
489  
490  	xo_emit("{T:Workstreams}:\n");
491  	xo_emit("{T:/%4s} {T:/%3s} ", "WSID", "CPU");
492  	xo_emit("{P:/%2s}", "");
493  	xo_emit("{T:/%-6s} {T:/%5s} {T:/%5s} {T:/%8s} {T:/%8s} {T:/%8s} "
494  	    "{T:/%8s} {T:/%8s}\n",
495  	    "Name", "Len", "WMark", "Disp'd", "HDisp'd", "QDrops", "Queued",
496  	    "Handled");
497  	xo_open_list("workstream");
498  	for (i = 0; i < workstream_array_len; i++) {
499  		xo_open_instance("workstream");
500  		snwsp = &workstream_array[i];
501  		netisr_print_workstream(snwsp);
502  		xo_close_instance("workstream");
503  	}
504  	xo_close_list("workstream");
505  	xo_close_container("netisr");
506  }
507