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