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