xref: /openbsd-src/usr.bin/systat/cpu.c (revision d1df930ffab53da22f3324c32bed7ac5709915e6)
1 /*	$OpenBSD: cpu.c,v 1.7 2018/09/26 17:23:13 cheloha Exp $	*/
2 
3 /*
4  * Copyright (c) 2013 Reyk Floeter <reyk@openbsd.org>
5  * Copyright (c) 2001, 2007 Can Erkin Acar <canacar@openbsd.org>
6  *
7  * Permission to use, copy, modify, and distribute this software for any
8  * purpose with or without fee is hereby granted, provided that the above
9  * copyright notice and this permission notice appear in all copies.
10  *
11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18  */
19 
20 /* CPU percentages() function from usr.bin/top/util.c:
21  *
22  *  Top users/processes display for Unix
23  *  Version 3
24  *
25  * Copyright (c) 1984, 1989, William LeFebvre, Rice University
26  * Copyright (c) 1989, 1990, 1992, William LeFebvre, Northwestern University
27  *
28  * Redistribution and use in source and binary forms, with or without
29  * modification, are permitted provided that the following conditions
30  * are met:
31  * 1. Redistributions of source code must retain the above copyright
32  *    notice, this list of conditions and the following disclaimer.
33  * 2. Redistributions in binary form must reproduce the above copyright
34  *    notice, this list of conditions and the following disclaimer in the
35  *    documentation and/or other materials provided with the distribution.
36  *
37  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
38  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
39  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
40  * IN NO EVENT SHALL THE AUTHOR OR HIS EMPLOYER BE LIABLE FOR ANY DIRECT, INDIRECT,
41  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
42  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
43  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
44  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
45  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
46  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
47  */
48 
49 #include <sys/signal.h>
50 #include <sys/sched.h>
51 #include <sys/sysctl.h>
52 
53 #include <errno.h>
54 #include <stdlib.h>
55 #include <stdint.h>
56 #include <string.h>
57 #include <unistd.h>
58 #include "systat.h"
59 
60 void		 print_cpu(void);
61 int		 read_cpu(void);
62 int		 select_cpu(void);
63 static void	 cpu_info(void);
64 static void	 print_fld_percentage(field_def *, double);
65 static int	 percentages(int, int64_t *, int64_t *, int64_t *, int64_t *);
66 
67 field_def fields_cpu[] = {
68 	{ "CPU", 4, 8, 1, FLD_ALIGN_LEFT, -1, 0, 0, 0 },
69 	{ "User", 10, 20, 1, FLD_ALIGN_RIGHT, -1, 0, 0, 0 },
70 	{ "Nice", 10, 20, 1, FLD_ALIGN_RIGHT, -1, 0, 0, 0 },
71 	{ "System", 10, 20, 1, FLD_ALIGN_RIGHT, -1, 0, 0, 0 },
72 	{ "Spin", 10, 20, 1, FLD_ALIGN_RIGHT, -1, 0, 0, 0 },
73 	{ "Interrupt", 10, 20, 1, FLD_ALIGN_RIGHT, -1, 0, 0, 0 },
74 	{ "Idle", 10, 20, 1, FLD_ALIGN_RIGHT, -1, 0, 0, 0 },
75 };
76 
77 #define FLD_CPU_CPU	FIELD_ADDR(fields_cpu, 0)
78 #define FLD_CPU_USR	FIELD_ADDR(fields_cpu, 1)
79 #define FLD_CPU_NIC	FIELD_ADDR(fields_cpu, 2)
80 #define FLD_CPU_SYS	FIELD_ADDR(fields_cpu, 3)
81 #define FLD_CPU_SPIN	FIELD_ADDR(fields_cpu, 4)
82 #define FLD_CPU_INT	FIELD_ADDR(fields_cpu, 5)
83 #define FLD_CPU_IDLE	FIELD_ADDR(fields_cpu, 6)
84 
85 /* Define views */
86 field_def *view_cpu_0[] = {
87 	FLD_CPU_CPU, FLD_CPU_USR, FLD_CPU_NIC, FLD_CPU_SYS, FLD_CPU_SPIN,
88 	FLD_CPU_INT, FLD_CPU_IDLE, NULL
89 };
90 
91 /* Define view managers */
92 struct view_manager cpu_mgr = {
93 	"cpu", select_cpu, read_cpu, NULL, print_header,
94 	print_cpu, keyboard_callback, NULL, NULL
95 };
96 
97 field_view views_cpu[] = {
98 	{ view_cpu_0, "cpu", 'C', &cpu_mgr },
99 	{ NULL, NULL, 0, NULL }
100 };
101 
102 int	  cpu_count;
103 int	 *cpu_online;
104 int64_t	 *cpu_states;
105 int64_t	**cpu_tm;
106 int64_t	**cpu_old;
107 int64_t	**cpu_diff;
108 
109 /*
110  * percentages(cnt, out, new, old, diffs) - calculate percentage change
111  * between array "old" and "new", putting the percentages in "out".
112  * "cnt" is size of each array and "diffs" is used for scratch space.
113  * The array "old" is updated on each call.
114  * The routine assumes modulo arithmetic.  This function is especially
115  * useful on BSD machines for calculating cpu state percentages.
116  */
117 static int
118 percentages(int cnt, int64_t *out, int64_t *new, int64_t *old, int64_t *diffs)
119 {
120 	int64_t change, total_change, *dp, half_total;
121 	int i;
122 
123 	/* initialization */
124 	total_change = 0;
125 	dp = diffs;
126 
127 	/* calculate changes for each state and the overall change */
128 	for (i = 0; i < cnt; i++) {
129 		if ((change = *new - *old) < 0) {
130 			/* this only happens when the counter wraps */
131 			change = INT64_MAX - *old + *new;
132 		}
133 		total_change += (*dp++ = change);
134 		*old++ = *new++;
135 	}
136 
137 	/* avoid divide by zero potential */
138 	if (total_change == 0)
139 		total_change = 1;
140 
141 	/* calculate percentages based on overall change, rounding up */
142 	half_total = total_change / 2l;
143 	for (i = 0; i < cnt; i++)
144 		*out++ = ((*diffs++ * 1000 + half_total) / total_change);
145 
146 	/* return the total in case the caller wants to use it */
147 	return (total_change);
148 }
149 
150 static void
151 cpu_info(void)
152 {
153 	int	 cpu_time_mib[] = { CTL_KERN, KERN_CPTIME2, 0 }, i;
154 	int64_t *tmpstate;
155 	size_t	 size;
156 
157 	size = CPUSTATES * sizeof(int64_t);
158 	for (i = 0; i < cpu_count; i++) {
159 		cpu_time_mib[2] = i;
160 		tmpstate = cpu_states + (CPUSTATES * i);
161 		if (sysctl(cpu_time_mib, 3, cpu_tm[i], &size, NULL, 0) < 0) {
162 			if (errno != ENODEV)
163 				error("sysctl KERN_CPTIME2");
164 			cpu_online[i] = 0;
165 			continue;
166 		}
167 		cpu_online[i] = 1;
168 		percentages(CPUSTATES, tmpstate, cpu_tm[i],
169 		    cpu_old[i], cpu_diff[i]);
170 	}
171 }
172 
173 static void
174 print_fld_percentage(field_def *fld, double val)
175 {
176 	if (fld == NULL)
177 		return;
178 
179 	tb_start();
180 	tbprintf(val >= 1000 ? "%4.0f%%" : "%4.1f%%", val / 10.);
181 	print_fld_tb(fld);
182 	tb_end();
183 }
184 
185 int
186 select_cpu(void)
187 {
188 	return (0);
189 }
190 
191 int
192 read_cpu(void)
193 {
194 	cpu_info();
195 	num_disp = cpu_count;
196 	return (0);
197 }
198 
199 int
200 initcpu(void)
201 {
202 	field_view	*v;
203 	size_t		 size = sizeof(cpu_count);
204 	int		 mib[2], i;
205 
206 	mib[0] = CTL_HW;
207 	mib[1] = HW_NCPU;
208 	if (sysctl(mib, 2, &cpu_count, &size, NULL, 0) == -1)
209 		return (-1);
210 	if ((cpu_online = calloc(cpu_count, sizeof(*cpu_online))) == NULL)
211 		return (-1);
212 	if ((cpu_states = calloc(cpu_count,
213 	    CPUSTATES * sizeof(int64_t))) == NULL)
214 		return (-1);
215 	if ((cpu_tm = calloc(cpu_count, sizeof(int64_t *))) == NULL ||
216 	    (cpu_old = calloc(cpu_count, sizeof(int64_t *))) == NULL ||
217 	    (cpu_diff = calloc(cpu_count, sizeof(int64_t *))) == NULL)
218 		return (-1);
219 	for (i = 0; i < cpu_count; i++) {
220 		if ((cpu_tm[i] = calloc(CPUSTATES, sizeof(int64_t))) == NULL ||
221 		    (cpu_old[i] = calloc(CPUSTATES, sizeof(int64_t))) == NULL ||
222 		    (cpu_diff[i] = calloc(CPUSTATES, sizeof(int64_t))) == NULL)
223 			return (-1);
224 	}
225 
226 	for (v = views_cpu; v->name != NULL; v++)
227 		add_view(v);
228 
229 	read_cpu();
230 
231 	return(1);
232 }
233 
234 #define ADD_EMPTY_LINE \
235 	do {								\
236 		if (cur >= dispstart && cur < end) 			\
237 			end_line();					\
238 		if (++cur >= end)					\
239 			return;						\
240 	} while (0)
241 
242 #define ADD_LINE_CPU(v, cs) \
243 	do {								\
244 		if (cur >= dispstart && cur < end) { 			\
245 			print_fld_size(FLD_CPU_CPU, (v));		\
246 			print_fld_percentage(FLD_CPU_USR, (cs[CP_USER]));\
247 			print_fld_percentage(FLD_CPU_NIC, (cs[CP_NICE]));\
248 			print_fld_percentage(FLD_CPU_SYS, (cs[CP_SYS]));\
249 			print_fld_percentage(FLD_CPU_SPIN, (cs[CP_SPIN]));\
250 			print_fld_percentage(FLD_CPU_INT, (cs[CP_INTR]));\
251 			print_fld_percentage(FLD_CPU_IDLE, (cs[CP_IDLE]));	\
252 			end_line();					\
253 		}							\
254 		if (++cur >= end)					\
255 			return;						\
256 	} while (0)
257 
258 #define ADD_OFFLINE_CPU(v) do {						\
259 	if (cur >= dispstart && cur < end) { 				\
260 		print_fld_size(FLD_CPU_CPU, (v));			\
261 		print_fld_str(FLD_CPU_USR, "-");			\
262 		print_fld_str(FLD_CPU_NIC, "-");			\
263 		print_fld_str(FLD_CPU_SYS, "-");			\
264 		print_fld_str(FLD_CPU_SPIN, "-");			\
265 		print_fld_str(FLD_CPU_INT, "-");			\
266 		print_fld_str(FLD_CPU_IDLE, "-");			\
267 		end_line();						\
268 	}								\
269 	if (++cur >= end)						\
270 		return;							\
271 } while (0)
272 
273 void
274 print_cpu(void)
275 {
276 	int		cur = 0, c, i;
277 	int		end = dispstart + maxprint;
278 	int64_t		*states;
279 	double		value[CPUSTATES];
280 
281 	if (end > num_disp)
282 		end = num_disp;
283 
284 	for (c = 0; c < cpu_count; c++) {
285 		if (!cpu_online[c]) {
286 			ADD_OFFLINE_CPU(c);
287 			continue;
288 		}
289 		states = cpu_states + (CPUSTATES * c);
290 
291 		for (i = 0; i < CPUSTATES; i++)
292 			value[i] = *states++;
293 
294 		ADD_LINE_CPU(c, value);
295 	}
296 
297 	ADD_EMPTY_LINE;
298 }
299