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;
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 */
65 /* the samples from the last minute, as required by MIB */
66 double samples[MAX_CPU_SAMPLES];
67 long states[MAX_CPU_SAMPLES][CPUSTATES];
69 TAILQ_HEAD(processor_tbl, processor_entry);
71 /* the head of the list with hrDeviceTable's entries */
72 static struct processor_tbl processor_tbl =
73 TAILQ_HEAD_INITIALIZER(processor_tbl);
75 /* number of processors in dev tbl */
76 static int32_t detected_processor_count;
78 /* sysctlbyname(hw.ncpu) */
81 /* sysctlbyname(kern.cp_times) */
85 /* periodic timer used to get cpu load stats */
86 static void *cpus_load_timer;
89 * Returns the CPU usage of a given processor entry.
91 * It needs at least two cp_times "tick" samples to calculate a delta and
92 * thus, the usage over the sampling period.
95 get_avg_load(struct processor_entry *e)
103 /* Need two samples to perform delta calculation. */
104 if (e->sample_cnt <= 1)
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;
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];
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,
129 return ((int)(usage));
133 * Save a new sample to proc entry and get the average usage.
135 * Samples are stored in a ringbuffer from 0..(MAX_CPU_SAMPLES-1)
138 save_sample(struct processor_entry *e, long *cp_times)
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];
147 if (e->sample_cnt > MAX_CPU_SAMPLES)
148 e->sample_cnt = MAX_CPU_SAMPLES;
150 HRDBG("sample count for CPU no. %d went to %d", e->cpu_no, e->sample_cnt);
151 e->load = get_avg_load(e);
156 * Create a new entry into the processor table.
158 static struct processor_entry *
159 proc_create_entry(u_int cpu_no, struct device_map_entry *map)
161 struct device_entry *dev;
162 struct processor_entry *entry;
166 * If there is no map entry create one by creating a device table
170 snprintf(name, sizeof(name), "cpu%u", cpu_no);
171 if ((dev = device_entry_create(name, "", "")) == NULL)
173 dev->flags |= HR_DEVICE_IMMUTABLE;
174 STAILQ_FOREACH(map, &device_map, link)
175 if (strcmp(map->name_key, name) == 0)
181 if ((entry = malloc(sizeof(*entry))) == NULL) {
182 syslog(LOG_ERR, "hrProcessorTable: %s malloc "
183 "failed: %m", __func__);
186 memset(entry, 0, sizeof(*entry));
188 entry->index = map->hrIndex;
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 */
195 INSERT_OBJECT_INT(entry, &processor_tbl);
197 HRDBG("CPU %d added with SNMP index=%d",
198 entry->cpu_no, entry->index);
204 * Scan the device map table for CPUs and create an entry into the
205 * processor table for each CPU.
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.
211 create_proc_table(void)
213 struct device_map_entry *map;
214 struct processor_entry *entry;
218 detected_processor_count = 0;
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.
225 * For non-ACPI system the processors are not in the device table,
226 * therefore insert them after checking hw.ncpu.
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'",
238 if ((entry = proc_create_entry(cpu_no, map)) == NULL)
241 detected_processor_count++;
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");
251 HRDBG("%d CPUs detected via device table; hw.ncpu is %d",
252 detected_processor_count, hw_ncpu);
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);
259 if (sysctlnametomib("kern.cp_times", cpmib, &len)) {
260 syslog(LOG_ERR, "hrProcessorTable: sysctlnametomib(kern.cp_times) failed");
264 } else if (sysctl(cpmib, 2, NULL, &len, NULL, 0)) {
265 syslog(LOG_ERR, "hrProcessorTable: sysctl(kern.cp_times) length query failed");
268 cplen = len / sizeof(long);
270 HRDBG("%zu entries for kern.cp_times", cplen);
275 * Free the processor table
278 free_proc_table(void)
280 struct processor_entry *n1;
282 while ((n1 = TAILQ_FIRST(&processor_tbl)) != NULL) {
283 TAILQ_REMOVE(&processor_tbl, n1, link);
285 detected_processor_count--;
288 assert(detected_processor_count == 0);
289 detected_processor_count = 0;
293 * Refresh all values in the processor table. We call this once for
294 * every PDU that accesses the table.
297 refresh_processor_tbl(void)
299 struct processor_entry *entry;
302 long pcpu_cp_times[cplen];
303 memset(pcpu_cp_times, 0, sizeof(pcpu_cp_times));
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");
312 TAILQ_FOREACH(entry, &processor_tbl, link) {
313 assert(hr_kd != NULL);
314 save_sample(entry, &pcpu_cp_times[entry->cpu_no * CPUSTATES]);
320 * This function is called MAX_CPU_SAMPLES times per minute to collect the
324 get_cpus_samples(void *arg __unused)
327 HRDBG("[%llu] ENTER", (unsigned long long)get_ticks());
328 refresh_processor_tbl();
329 HRDBG("[%llu] EXIT", (unsigned long long)get_ticks());
333 * Called to start this table. We need to start the periodic idle
337 start_processor_tbl(struct lmodule *mod)
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
346 cpus_load_timer = timer_start_repeat(100, 100 * 60 / MAX_CPU_SAMPLES,
347 get_cpus_samples, NULL, mod);
351 * Init the things for hrProcessorTable.
352 * Scan the device table for processor entries.
355 init_processor_tbl(void)
358 /* create the initial processor table */
360 /* and get first samples */
361 refresh_processor_tbl();
365 * Finalization routine for hrProcessorTable.
366 * It destroys the lists and frees any allocated heap memory.
369 fini_processor_tbl(void)
372 if (cpus_load_timer != NULL) {
373 timer_stop(cpus_load_timer);
374 cpus_load_timer = NULL;
381 * Access routine for the processor table.
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)
388 struct processor_entry *entry;
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;
401 if ((entry = FIND_OBJECT_INT(&processor_tbl,
402 &value->var, sub)) == NULL)
403 return (SNMP_ERR_NOSUCHNAME);
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);
412 case SNMP_OP_ROLLBACK:
419 switch (value->var.subs[sub - 1]) {
421 case LEAF_hrProcessorFrwID:
422 assert(entry->frwId != NULL);
423 value->v.oid = *entry->frwId;
424 return (SNMP_ERR_NOERROR);
426 case LEAF_hrProcessorLoad:
427 value->v.integer = entry->load;
428 return (SNMP_ERR_NOERROR);