2 * Copyright (c) 2005-2006 The FreeBSD Project
5 * Author: Victor Cruceru <soc-victor@freebsd.org>
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:
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.
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
33 * Host Resources MIB for SNMPd. Implementation for hrProcessorTable
36 #include <sys/param.h>
37 #include <sys/sysctl.h>
46 #include "hostres_snmp.h"
47 #include "hostres_oid.h"
48 #include "hostres_tree.h"
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..
56 struct processor_entry {
58 const struct asn_oid *frwId;
60 TAILQ_ENTRY(processor_entry) link;
61 u_char cpu_no; /* which cpu, counted from 0 */
62 pid_t idle_pid; /* PID of idle process for this CPU */
64 /* the samples from the last minute, as required by MIB */
65 double samples[MAX_CPU_SAMPLES];
67 /* current sample to fill in next time, must be < MAX_CPU_SAMPLES */
68 uint32_t cur_sample_idx;
70 /* number of useful samples */
73 TAILQ_HEAD(processor_tbl, processor_entry);
75 /* the head of the list with hrDeviceTable's entries */
76 static struct processor_tbl processor_tbl =
77 TAILQ_HEAD_INITIALIZER(processor_tbl);
79 /* number of processors in dev tbl */
80 static int32_t detected_processor_count;
82 /* sysctlbyname(hw.ncpu) */
85 /* sysctlbyname(kern.{ccpu,fscale}) */
89 /* tick of PDU where we have refreshed the processor table last */
90 static uint64_t proctbl_tick;
92 /* periodic timer used to get cpu load stats */
93 static void *cpus_load_timer;
96 * Average the samples. The entire algorithm seems to be wrong XXX.
99 get_avg_load(struct processor_entry *e)
106 if (e->sample_cnt == 0)
109 for (i = 0; i < e->sample_cnt; i++)
110 sum += e->samples[i];
112 return ((int)floor((double)sum/(double)e->sample_cnt));
116 * Stolen from /usr/src/bin/ps/print.c. The idle process should never
120 processor_getpcpu(struct kinfo_proc *ki_p)
123 if (ccpu == 0 || fscale == 0)
126 #define fxtofl(fixpt) ((double)(fixpt) / fscale)
127 return (100.0 * fxtofl(ki_p->ki_pctcpu) /
128 (1.0 - exp(ki_p->ki_swtime * log(fxtofl(ccpu)))));
135 save_sample(struct processor_entry *e, struct kinfo_proc *kp)
138 e->samples[e->cur_sample_idx] = 100.0 - processor_getpcpu(kp);
139 e->load = get_avg_load(e);
140 e->cur_sample_idx = (e->cur_sample_idx + 1) % MAX_CPU_SAMPLES;
142 if (++e->sample_cnt > MAX_CPU_SAMPLES)
143 e->sample_cnt = MAX_CPU_SAMPLES;
147 * Create a new entry into the processor table.
149 static struct processor_entry *
150 proc_create_entry(u_int cpu_no, struct device_map_entry *map)
152 struct device_entry *dev;
153 struct processor_entry *entry;
157 * If there is no map entry create one by creating a device table
161 snprintf(name, sizeof(name), "cpu%u", cpu_no);
162 if ((dev = device_entry_create(name, "", "")) == NULL)
164 dev->flags |= HR_DEVICE_IMMUTABLE;
165 STAILQ_FOREACH(map, &device_map, link)
166 if (strcmp(map->name_key, name) == 0)
172 if ((entry = malloc(sizeof(*entry))) == NULL) {
173 syslog(LOG_ERR, "hrProcessorTable: %s malloc "
174 "failed: %m", __func__);
177 memset(entry, 0, sizeof(*entry));
179 entry->index = map->hrIndex;
181 entry->cpu_no = (u_char)cpu_no;
183 entry->frwId = &oid_zeroDotZero; /* unknown id FIXME */
185 INSERT_OBJECT_INT(entry, &processor_tbl);
187 HRDBG("CPU %d added with SNMP index=%d",
188 entry->cpu_no, entry->index);
194 * Get the PIDs for the idle processes of the CPUs.
197 processor_get_pids(void)
199 struct kinfo_proc *plist, *kp;
204 struct processor_entry *entry;
206 plist = kvm_getprocs(hr_kd, KERN_PROC_ALL, 0, &nproc);
207 if (plist == NULL || nproc < 0) {
208 syslog(LOG_ERR, "hrProcessor: kvm_getprocs() failed: %m");
212 for (i = 0, kp = plist; i < nproc; i++, kp++) {
213 if (!IS_KERNPROC(kp))
216 if (strcmp(kp->ki_comm, "idle") == 0) {
217 /* single processor system */
219 } else if (sscanf(kp->ki_comm, "idle: cpu%d%n", &cpu, &nchars)
220 == 1 && (u_int)nchars == strlen(kp->ki_comm)) {
223 /* not an idle process */
226 HRDBG("'%s' proc with pid %d is on CPU #%d (last on #%d)",
227 kp->ki_comm, kp->ki_pid, kp->ki_oncpu, kp->ki_lastcpu);
229 TAILQ_FOREACH(entry, &processor_tbl, link)
230 if (entry->cpu_no == kp->ki_lastcpu)
234 /* create entry on non-ACPI systems */
235 if ((entry = proc_create_entry(cpu, NULL)) == NULL)
238 detected_processor_count++;
241 entry->idle_pid = kp->ki_pid;
242 HRDBG("CPU no. %d with SNMP index=%d has idle PID %d",
243 entry->cpu_no, entry->index, entry->idle_pid);
245 save_sample(entry, kp);
250 * Scan the device map table for CPUs and create an entry into the
251 * processor table for each CPU. Then fetch the idle PIDs for all CPUs.
254 create_proc_table(void)
256 struct device_map_entry *map;
257 struct processor_entry *entry;
260 detected_processor_count = 0;
263 * Because hrProcessorTable depends on hrDeviceTable,
264 * the device detection must be performed at this point.
265 * If not, no entries will be present in the hrProcessor Table.
267 * For non-ACPI system the processors are not in the device table,
268 * therefor insert them when getting the idle pids. XXX
270 STAILQ_FOREACH(map, &device_map, link)
271 if (strncmp(map->name_key, "cpu", strlen("cpu")) == 0 &&
272 strstr(map->location_key, ".CPU") != NULL) {
273 if (sscanf(map->name_key,"cpu%d", &cpu_no) != 1) {
274 syslog(LOG_ERR, "hrProcessorTable: Failed to "
275 "get cpu no. from device named '%s'",
280 if ((entry = proc_create_entry(cpu_no, map)) == NULL)
283 detected_processor_count++;
286 HRDBG("%d CPUs detected", detected_processor_count);
288 processor_get_pids();
292 * Free the processor table
295 free_proc_table(void)
297 struct processor_entry *n1;
299 while ((n1 = TAILQ_FIRST(&processor_tbl)) != NULL) {
300 TAILQ_REMOVE(&processor_tbl, n1, link);
302 detected_processor_count--;
305 assert(detected_processor_count == 0);
306 detected_processor_count = 0;
310 * Init the things for hrProcessorTable.
311 * Scan the device table for processor entries.
314 init_processor_tbl(void)
318 /* get various parameters from the kernel */
320 if (sysctlbyname("kern.ccpu", &ccpu, &len, NULL, 0) == -1) {
321 syslog(LOG_ERR, "hrProcessorTable: sysctl(kern.ccpu) failed");
325 len = sizeof(fscale);
326 if (sysctlbyname("kern.fscale", &fscale, &len, NULL, 0) == -1) {
327 syslog(LOG_ERR, "hrProcessorTable: sysctl(kern.fscale) failed");
331 /* create the initial processor table */
336 * Finalization routine for hrProcessorTable.
337 * It destroys the lists and frees any allocated heap memory.
340 fini_processor_tbl(void)
343 if (cpus_load_timer != NULL) {
344 timer_stop(cpus_load_timer);
345 cpus_load_timer = NULL;
352 * Make sure that the number of processors announced by the kernel hw.ncpu
353 * is equal to the number of processors we have found in the device table.
354 * If they differ rescan the device table.
357 processor_refill_tbl(void)
360 HRDBG("hw_ncpu=%d detected_processor_count=%d", hw_ncpu,
361 detected_processor_count);
364 size_t size = sizeof(hw_ncpu);
366 if (sysctlbyname("hw.ncpu", &hw_ncpu, &size, NULL, 0) == -1 ||
367 size != sizeof(hw_ncpu)) {
368 syslog(LOG_ERR, "hrProcessorTable: "
369 "sysctl(hw.ncpu) failed: %m");
375 if (hw_ncpu != detected_processor_count) {
382 * Refresh all values in the processor table. We call this once for
383 * every PDU that accesses the table.
386 refresh_processor_tbl(void)
388 struct processor_entry *entry;
390 struct kinfo_proc *plist;
393 processor_refill_tbl();
396 TAILQ_FOREACH(entry, &processor_tbl, link) {
397 if (entry->idle_pid <= 0) {
402 assert(hr_kd != NULL);
404 plist = kvm_getprocs(hr_kd, KERN_PROC_PID,
405 entry->idle_pid, &nproc);
406 if (plist == NULL || nproc != 1) {
407 syslog(LOG_ERR, "%s: missing item with "
408 "PID = %d for CPU #%d\n ", __func__,
409 entry->idle_pid, entry->cpu_no);
413 save_sample(entry, plist);
417 processor_get_pids();
419 proctbl_tick = this_tick;
423 * This function is called MAX_CPU_SAMPLES times per minute to collect the
427 get_cpus_samples(void *arg __unused)
430 HRDBG("[%llu] ENTER", (unsigned long long)get_ticks());
431 refresh_processor_tbl();
432 HRDBG("[%llu] EXIT", (unsigned long long)get_ticks());
436 * Called to start this table. We need to start the periodic idle
440 start_processor_tbl(struct lmodule *mod)
444 * Start the cpu stats collector
445 * The semantics of timer_start parameters is in "SNMP ticks";
446 * we have 100 "SNMP ticks" per second, thus we are trying below
447 * to get MAX_CPU_SAMPLES per minute
449 cpus_load_timer = timer_start_repeat(100, 100 * 60 / MAX_CPU_SAMPLES,
450 get_cpus_samples, NULL, mod);
454 * Access routine for the processor table.
457 op_hrProcessorTable(struct snmp_context *ctx __unused,
458 struct snmp_value *value, u_int sub, u_int iidx __unused,
459 enum snmp_op curr_op)
461 struct processor_entry *entry;
463 if (this_tick != proctbl_tick)
464 refresh_processor_tbl();
468 case SNMP_OP_GETNEXT:
469 if ((entry = NEXT_OBJECT_INT(&processor_tbl,
470 &value->var, sub)) == NULL)
471 return (SNMP_ERR_NOSUCHNAME);
472 value->var.len = sub + 1;
473 value->var.subs[sub] = entry->index;
477 if ((entry = FIND_OBJECT_INT(&processor_tbl,
478 &value->var, sub)) == NULL)
479 return (SNMP_ERR_NOSUCHNAME);
483 if ((entry = FIND_OBJECT_INT(&processor_tbl,
484 &value->var, sub)) == NULL)
485 return (SNMP_ERR_NO_CREATION);
486 return (SNMP_ERR_NOT_WRITEABLE);
488 case SNMP_OP_ROLLBACK:
495 switch (value->var.subs[sub - 1]) {
497 case LEAF_hrProcessorFrwID:
498 assert(entry->frwId != NULL);
499 value->v.oid = *entry->frwId;
500 return (SNMP_ERR_NOERROR);
502 case LEAF_hrProcessorLoad:
503 value->v.integer = entry->load;
504 return (SNMP_ERR_NOERROR);