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 } 432