Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_processor_tbl.c
105655 views
1
/*-
2
* Copyright (c) 2005-2006 The FreeBSD Project
3
* All rights reserved.
4
*
5
* Author: Victor Cruceru <[email protected]>
6
*
7
* Redistribution of this software and documentation and use in source and
8
* binary forms, with or without modification, are permitted provided that
9
* the following conditions are met:
10
*
11
* 1. Redistributions of source code or documentation must retain the above
12
* copyright notice, this list of conditions and the following disclaimer.
13
* 2. Redistributions in binary form must reproduce the above copyright
14
* notice, this list of conditions and the following disclaimer in the
15
* documentation and/or other materials provided with the distribution.
16
*
17
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27
* SUCH DAMAGE.
28
*/
29
30
/*
31
* Host Resources MIB for SNMPd. Implementation for hrProcessorTable
32
*/
33
34
#include <sys/param.h>
35
#include <sys/sysctl.h>
36
#include <sys/user.h>
37
38
#include <assert.h>
39
#include <math.h>
40
#include <stdlib.h>
41
#include <string.h>
42
#include <syslog.h>
43
44
#include "hostres_snmp.h"
45
#include "hostres_oid.h"
46
#include "hostres_tree.h"
47
48
/*
49
* This structure is used to hold a SNMP table entry
50
* for HOST-RESOURCES-MIB's hrProcessorTable.
51
* Note that index is external being allocated & maintained
52
* by the hrDeviceTable code..
53
*/
54
struct processor_entry {
55
int32_t index;
56
const struct asn_oid *frwId;
57
int32_t load; /* average cpu usage */
58
int32_t sample_cnt; /* number of usage samples */
59
int32_t cur_sample_idx; /* current valid sample */
60
TAILQ_ENTRY(processor_entry) link;
61
u_char cpu_no; /* which cpu, counted from 0 */
62
63
/* the samples from the last minute, as required by MIB */
64
double samples[MAX_CPU_SAMPLES];
65
long states[MAX_CPU_SAMPLES][CPUSTATES];
66
};
67
TAILQ_HEAD(processor_tbl, processor_entry);
68
69
/* the head of the list with hrDeviceTable's entries */
70
static struct processor_tbl processor_tbl =
71
TAILQ_HEAD_INITIALIZER(processor_tbl);
72
73
/* number of processors in dev tbl */
74
static int32_t detected_processor_count;
75
76
/* sysctlbyname(hw.ncpu) */
77
static int hw_ncpu;
78
79
/* sysctlbyname(kern.cp_times) */
80
static int cpmib[2];
81
static size_t cplen;
82
83
/* periodic timer used to get cpu load stats */
84
static void *cpus_load_timer;
85
86
/**
87
* Returns the CPU usage of a given processor entry.
88
*
89
* It needs at least two cp_times "tick" samples to calculate a delta and
90
* thus, the usage over the sampling period.
91
*/
92
static int
93
get_avg_load(struct processor_entry *e)
94
{
95
u_int i, oldest;
96
long delta = 0;
97
double usage = 0.0;
98
99
assert(e != NULL);
100
101
/* Need two samples to perform delta calculation. */
102
if (e->sample_cnt <= 1)
103
return (0);
104
105
/* Oldest usable index, we wrap around. */
106
if (e->sample_cnt == MAX_CPU_SAMPLES)
107
oldest = (e->cur_sample_idx + 1) % MAX_CPU_SAMPLES;
108
else
109
oldest = 0;
110
111
/* Sum delta for all states. */
112
for (i = 0; i < CPUSTATES; i++) {
113
delta += e->states[e->cur_sample_idx][i];
114
delta -= e->states[oldest][i];
115
}
116
if (delta == 0)
117
return 0;
118
119
/* Take idle time from the last element and convert to
120
* percent usage by contrasting with total ticks delta. */
121
usage = (double)(e->states[e->cur_sample_idx][CPUSTATES-1] -
122
e->states[oldest][CPUSTATES-1]) / delta;
123
usage = 100 - (usage * 100);
124
HRDBG("CPU no. %d, delta ticks %ld, pct usage %.2f", e->cpu_no,
125
delta, usage);
126
127
return ((int)(usage));
128
}
129
130
/**
131
* Save a new sample to proc entry and get the average usage.
132
*
133
* Samples are stored in a ringbuffer from 0..(MAX_CPU_SAMPLES-1)
134
*/
135
static void
136
save_sample(struct processor_entry *e, long *cp_times)
137
{
138
int i;
139
140
e->cur_sample_idx = (e->cur_sample_idx + 1) % MAX_CPU_SAMPLES;
141
for (i = 0; cp_times != NULL && i < CPUSTATES; i++)
142
e->states[e->cur_sample_idx][i] = cp_times[i];
143
144
e->sample_cnt++;
145
if (e->sample_cnt > MAX_CPU_SAMPLES)
146
e->sample_cnt = MAX_CPU_SAMPLES;
147
148
HRDBG("sample count for CPU no. %d went to %d", e->cpu_no, e->sample_cnt);
149
e->load = get_avg_load(e);
150
151
}
152
153
/**
154
* Create a new entry into the processor table.
155
*/
156
static struct processor_entry *
157
proc_create_entry(u_int cpu_no, struct device_map_entry *map)
158
{
159
struct device_entry *dev;
160
struct processor_entry *entry;
161
char name[128];
162
163
/*
164
* If there is no map entry create one by creating a device table
165
* entry.
166
*/
167
if (map == NULL) {
168
snprintf(name, sizeof(name), "cpu%u", cpu_no);
169
if ((dev = device_entry_create(name, "", "")) == NULL)
170
return (NULL);
171
dev->flags |= HR_DEVICE_IMMUTABLE;
172
STAILQ_FOREACH(map, &device_map, link)
173
if (strcmp(map->name_key, name) == 0)
174
break;
175
if (map == NULL)
176
abort();
177
}
178
179
if ((entry = malloc(sizeof(*entry))) == NULL) {
180
syslog(LOG_ERR, "hrProcessorTable: %s malloc "
181
"failed: %m", __func__);
182
return (NULL);
183
}
184
memset(entry, 0, sizeof(*entry));
185
186
entry->index = map->hrIndex;
187
entry->load = 0;
188
entry->sample_cnt = 0;
189
entry->cur_sample_idx = -1;
190
entry->cpu_no = (u_char)cpu_no;
191
entry->frwId = &oid_zeroDotZero; /* unknown id FIXME */
192
193
INSERT_OBJECT_INT(entry, &processor_tbl);
194
195
HRDBG("CPU %d added with SNMP index=%d",
196
entry->cpu_no, entry->index);
197
198
return (entry);
199
}
200
201
/**
202
* Scan the device map table for CPUs and create an entry into the
203
* processor table for each CPU.
204
*
205
* Make sure that the number of processors announced by the kernel hw.ncpu
206
* is equal to the number of processors we have found in the device table.
207
*/
208
static void
209
create_proc_table(void)
210
{
211
struct device_map_entry *map;
212
struct processor_entry *entry;
213
int cpu_no;
214
size_t len;
215
216
detected_processor_count = 0;
217
218
/*
219
* Because hrProcessorTable depends on hrDeviceTable,
220
* the device detection must be performed at this point.
221
* If not, no entries will be present in the hrProcessor Table.
222
*
223
* For non-ACPI system the processors are not in the device table,
224
* therefore insert them after checking hw.ncpu.
225
*/
226
STAILQ_FOREACH(map, &device_map, link)
227
if (strncmp(map->name_key, "cpu", strlen("cpu")) == 0 &&
228
strstr(map->location_key, ".CPU") != NULL) {
229
if (sscanf(map->name_key,"cpu%d", &cpu_no) != 1) {
230
syslog(LOG_ERR, "hrProcessorTable: Failed to "
231
"get cpu no. from device named '%s'",
232
map->name_key);
233
continue;
234
}
235
236
if ((entry = proc_create_entry(cpu_no, map)) == NULL)
237
continue;
238
239
detected_processor_count++;
240
}
241
242
len = sizeof(hw_ncpu);
243
if (sysctlbyname("hw.ncpu", &hw_ncpu, &len, NULL, 0) == -1 ||
244
len != sizeof(hw_ncpu)) {
245
syslog(LOG_ERR, "hrProcessorTable: sysctl(hw.ncpu) failed");
246
hw_ncpu = 0;
247
}
248
249
HRDBG("%d CPUs detected via device table; hw.ncpu is %d",
250
detected_processor_count, hw_ncpu);
251
252
/* XXX Can happen on non-ACPI systems? Create entries by hand. */
253
for (; detected_processor_count < hw_ncpu; detected_processor_count++)
254
proc_create_entry(detected_processor_count, NULL);
255
256
len = 2;
257
if (sysctlnametomib("kern.cp_times", cpmib, &len)) {
258
syslog(LOG_ERR, "hrProcessorTable: sysctlnametomib(kern.cp_times) failed");
259
cpmib[0] = 0;
260
cpmib[1] = 0;
261
cplen = 0;
262
} else if (sysctl(cpmib, 2, NULL, &len, NULL, 0)) {
263
syslog(LOG_ERR, "hrProcessorTable: sysctl(kern.cp_times) length query failed");
264
cplen = 0;
265
} else {
266
cplen = len / sizeof(long);
267
}
268
HRDBG("%zu entries for kern.cp_times", cplen);
269
270
}
271
272
/**
273
* Free the processor table
274
*/
275
static void
276
free_proc_table(void)
277
{
278
struct processor_entry *n1;
279
280
while ((n1 = TAILQ_FIRST(&processor_tbl)) != NULL) {
281
TAILQ_REMOVE(&processor_tbl, n1, link);
282
free(n1);
283
detected_processor_count--;
284
}
285
286
assert(detected_processor_count == 0);
287
detected_processor_count = 0;
288
}
289
290
/**
291
* Refresh all values in the processor table. We call this once for
292
* every PDU that accesses the table.
293
*/
294
static void
295
refresh_processor_tbl(void)
296
{
297
struct processor_entry *entry;
298
size_t size;
299
300
long pcpu_cp_times[cplen];
301
memset(pcpu_cp_times, 0, sizeof(pcpu_cp_times));
302
303
size = cplen * sizeof(long);
304
if (sysctl(cpmib, 2, pcpu_cp_times, &size, NULL, 0) == -1 &&
305
!(errno == ENOMEM && size >= cplen * sizeof(long))) {
306
syslog(LOG_ERR, "hrProcessorTable: sysctl(kern.cp_times) failed");
307
return;
308
}
309
310
TAILQ_FOREACH(entry, &processor_tbl, link) {
311
assert(hr_kd != NULL);
312
save_sample(entry, &pcpu_cp_times[entry->cpu_no * CPUSTATES]);
313
}
314
315
}
316
317
/**
318
* This function is called MAX_CPU_SAMPLES times per minute to collect the
319
* CPU load.
320
*/
321
static void
322
get_cpus_samples(void *arg __unused)
323
{
324
325
HRDBG("[%llu] ENTER", (unsigned long long)get_ticks());
326
refresh_processor_tbl();
327
HRDBG("[%llu] EXIT", (unsigned long long)get_ticks());
328
}
329
330
/**
331
* Called to start this table. We need to start the periodic idle
332
* time collection.
333
*/
334
void
335
start_processor_tbl(struct lmodule *mod)
336
{
337
338
/*
339
* Start the cpu stats collector
340
* The semantics of timer_start parameters is in "SNMP ticks";
341
* we have 100 "SNMP ticks" per second, thus we are trying below
342
* to get MAX_CPU_SAMPLES per minute
343
*/
344
cpus_load_timer = timer_start_repeat(100, 100 * 60 / MAX_CPU_SAMPLES,
345
get_cpus_samples, NULL, mod);
346
}
347
348
/**
349
* Init the things for hrProcessorTable.
350
* Scan the device table for processor entries.
351
*/
352
void
353
init_processor_tbl(void)
354
{
355
356
/* create the initial processor table */
357
create_proc_table();
358
/* and get first samples */
359
refresh_processor_tbl();
360
}
361
362
/**
363
* Finalization routine for hrProcessorTable.
364
* It destroys the lists and frees any allocated heap memory.
365
*/
366
void
367
fini_processor_tbl(void)
368
{
369
370
if (cpus_load_timer != NULL) {
371
timer_stop(cpus_load_timer);
372
cpus_load_timer = NULL;
373
}
374
375
free_proc_table();
376
}
377
378
/**
379
* Access routine for the processor table.
380
*/
381
int
382
op_hrProcessorTable(struct snmp_context *ctx __unused,
383
struct snmp_value *value, u_int sub, u_int iidx __unused,
384
enum snmp_op curr_op)
385
{
386
struct processor_entry *entry;
387
388
switch (curr_op) {
389
390
case SNMP_OP_GETNEXT:
391
if ((entry = NEXT_OBJECT_INT(&processor_tbl,
392
&value->var, sub)) == NULL)
393
return (SNMP_ERR_NOSUCHNAME);
394
value->var.len = sub + 1;
395
value->var.subs[sub] = entry->index;
396
goto get;
397
398
case SNMP_OP_GET:
399
if ((entry = FIND_OBJECT_INT(&processor_tbl,
400
&value->var, sub)) == NULL)
401
return (SNMP_ERR_NOSUCHNAME);
402
goto get;
403
404
case SNMP_OP_SET:
405
if ((entry = FIND_OBJECT_INT(&processor_tbl,
406
&value->var, sub)) == NULL)
407
return (SNMP_ERR_NO_CREATION);
408
return (SNMP_ERR_NOT_WRITEABLE);
409
410
case SNMP_OP_ROLLBACK:
411
case SNMP_OP_COMMIT:
412
abort();
413
}
414
abort();
415
416
get:
417
switch (value->var.subs[sub - 1]) {
418
419
case LEAF_hrProcessorFrwID:
420
assert(entry->frwId != NULL);
421
value->v.oid = *entry->frwId;
422
return (SNMP_ERR_NOERROR);
423
424
case LEAF_hrProcessorLoad:
425
value->v.integer = entry->load;
426
return (SNMP_ERR_NOERROR);
427
}
428
abort();
429
}
430
431