1154133Sharti/*- 2154133Sharti * Copyright (c) 2005-2006 The FreeBSD Project 3154133Sharti * All rights reserved. 4154133Sharti * 5154133Sharti * Author: Victor Cruceru <soc-victor@freebsd.org> 6154133Sharti * 7154133Sharti * Redistribution of this software and documentation and use in source and 8154133Sharti * binary forms, with or without modification, are permitted provided that 9154133Sharti * the following conditions are met: 10154133Sharti * 11154133Sharti * 1. Redistributions of source code or documentation must retain the above 12154133Sharti * copyright notice, this list of conditions and the following disclaimer. 13154133Sharti * 2. Redistributions in binary form must reproduce the above copyright 14154133Sharti * notice, this list of conditions and the following disclaimer in the 15154133Sharti * documentation and/or other materials provided with the distribution. 16154133Sharti * 17154133Sharti * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18154133Sharti * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19154133Sharti * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20154133Sharti * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21154133Sharti * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22154133Sharti * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23154133Sharti * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24154133Sharti * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25154133Sharti * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26154133Sharti * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27154133Sharti * SUCH DAMAGE. 28154133Sharti * 29154133Sharti * $FreeBSD$ 30154133Sharti */ 31154133Sharti 32154133Sharti/* 33154133Sharti * Host Resources MIB for SNMPd. Implementation for hrProcessorTable 34154133Sharti */ 35154133Sharti 36154133Sharti#include <sys/param.h> 37154133Sharti#include <sys/sysctl.h> 38154133Sharti#include <sys/user.h> 39154133Sharti 40154133Sharti#include <assert.h> 41154133Sharti#include <math.h> 42154133Sharti#include <stdlib.h> 43154133Sharti#include <string.h> 44154133Sharti#include <syslog.h> 45154133Sharti 46154133Sharti#include "hostres_snmp.h" 47154133Sharti#include "hostres_oid.h" 48154133Sharti#include "hostres_tree.h" 49154133Sharti 50154133Sharti/* 51154133Sharti * This structure is used to hold a SNMP table entry 52154133Sharti * for HOST-RESOURCES-MIB's hrProcessorTable. 53154133Sharti * Note that index is external being allocated & maintained 54154133Sharti * by the hrDeviceTable code.. 55154133Sharti */ 56154133Shartistruct processor_entry { 57154133Sharti int32_t index; 58160341Sharti const struct asn_oid *frwId; 59214489Suqs int32_t load; /* average cpu usage */ 60214489Suqs int32_t sample_cnt; /* number of usage samples */ 61214489Suqs int32_t cur_sample_idx; /* current valid sample */ 62154133Sharti TAILQ_ENTRY(processor_entry) link; 63160341Sharti u_char cpu_no; /* which cpu, counted from 0 */ 64154133Sharti 65154133Sharti /* the samples from the last minute, as required by MIB */ 66154133Sharti double samples[MAX_CPU_SAMPLES]; 67214489Suqs long states[MAX_CPU_SAMPLES][CPUSTATES]; 68154133Sharti}; 69154133ShartiTAILQ_HEAD(processor_tbl, processor_entry); 70154133Sharti 71154133Sharti/* the head of the list with hrDeviceTable's entries */ 72154133Shartistatic struct processor_tbl processor_tbl = 73154133Sharti TAILQ_HEAD_INITIALIZER(processor_tbl); 74154133Sharti 75154133Sharti/* number of processors in dev tbl */ 76154133Shartistatic int32_t detected_processor_count; 77154133Sharti 78154133Sharti/* sysctlbyname(hw.ncpu) */ 79154133Shartistatic int hw_ncpu; 80154133Sharti 81214489Suqs/* sysctlbyname(kern.cp_times) */ 82214489Suqsstatic int cpmib[2]; 83214489Suqsstatic size_t cplen; 84154133Sharti 85154133Sharti/* periodic timer used to get cpu load stats */ 86154133Shartistatic void *cpus_load_timer; 87154133Sharti 88214489Suqs/** 89214489Suqs * Returns the CPU usage of a given processor entry. 90214489Suqs * 91214489Suqs * It needs at least two cp_times "tick" samples to calculate a delta and 92214489Suqs * thus, the usage over the sampling period. 93154133Sharti */ 94154133Shartistatic int 95154133Shartiget_avg_load(struct processor_entry *e) 96154133Sharti{ 97214489Suqs u_int i, oldest; 98214489Suqs long delta = 0; 99214489Suqs double usage = 0.0; 100154133Sharti 101154133Sharti assert(e != NULL); 102154133Sharti 103214489Suqs /* Need two samples to perform delta calculation. */ 104214489Suqs if (e->sample_cnt <= 1) 105154133Sharti return (0); 106154133Sharti 107214489Suqs /* Oldest usable index, we wrap around. */ 108214489Suqs if (e->sample_cnt == MAX_CPU_SAMPLES) 109214489Suqs oldest = (e->cur_sample_idx + 1) % MAX_CPU_SAMPLES; 110214489Suqs else 111214489Suqs oldest = 0; 112154133Sharti 113214489Suqs /* Sum delta for all states. */ 114214489Suqs for (i = 0; i < CPUSTATES; i++) { 115214489Suqs delta += e->states[e->cur_sample_idx][i]; 116214489Suqs delta -= e->states[oldest][i]; 117214489Suqs } 118214489Suqs if (delta == 0) 119214489Suqs return 0; 120154133Sharti 121214489Suqs /* Take idle time from the last element and convert to 122214489Suqs * percent usage by contrasting with total ticks delta. */ 123214489Suqs usage = (double)(e->states[e->cur_sample_idx][CPUSTATES-1] - 124214489Suqs e->states[oldest][CPUSTATES-1]) / delta; 125214489Suqs usage = 100 - (usage * 100); 126214489Suqs HRDBG("CPU no. %d, delta ticks %ld, pct usage %.2f", e->cpu_no, 127214489Suqs delta, usage); 128154133Sharti 129214489Suqs return ((int)(usage)); 130154133Sharti} 131154133Sharti 132154133Sharti/** 133214489Suqs * Save a new sample to proc entry and get the average usage. 134214489Suqs * 135214489Suqs * Samples are stored in a ringbuffer from 0..(MAX_CPU_SAMPLES-1) 136154133Sharti */ 137154133Shartistatic void 138214489Suqssave_sample(struct processor_entry *e, long *cp_times) 139154133Sharti{ 140214489Suqs int i; 141154133Sharti 142154133Sharti e->cur_sample_idx = (e->cur_sample_idx + 1) % MAX_CPU_SAMPLES; 143214489Suqs for (i = 0; cp_times != NULL && i < CPUSTATES; i++) 144214489Suqs e->states[e->cur_sample_idx][i] = cp_times[i]; 145154133Sharti 146214489Suqs e->sample_cnt++; 147214489Suqs if (e->sample_cnt > MAX_CPU_SAMPLES) 148154133Sharti e->sample_cnt = MAX_CPU_SAMPLES; 149214489Suqs 150214489Suqs HRDBG("sample count for CPU no. %d went to %d", e->cpu_no, e->sample_cnt); 151214489Suqs e->load = get_avg_load(e); 152214489Suqs 153154133Sharti} 154154133Sharti 155154133Sharti/** 156154133Sharti * Create a new entry into the processor table. 157154133Sharti */ 158154133Shartistatic struct processor_entry * 159154133Shartiproc_create_entry(u_int cpu_no, struct device_map_entry *map) 160154133Sharti{ 161154133Sharti struct device_entry *dev; 162154133Sharti struct processor_entry *entry; 163154133Sharti char name[128]; 164154133Sharti 165154133Sharti /* 166154133Sharti * If there is no map entry create one by creating a device table 167154133Sharti * entry. 168154133Sharti */ 169154133Sharti if (map == NULL) { 170154133Sharti snprintf(name, sizeof(name), "cpu%u", cpu_no); 171154133Sharti if ((dev = device_entry_create(name, "", "")) == NULL) 172154133Sharti return (NULL); 173154133Sharti dev->flags |= HR_DEVICE_IMMUTABLE; 174154133Sharti STAILQ_FOREACH(map, &device_map, link) 175154133Sharti if (strcmp(map->name_key, name) == 0) 176154133Sharti break; 177154133Sharti if (map == NULL) 178154133Sharti abort(); 179154133Sharti } 180160341Sharti 181154133Sharti if ((entry = malloc(sizeof(*entry))) == NULL) { 182154133Sharti syslog(LOG_ERR, "hrProcessorTable: %s malloc " 183154133Sharti "failed: %m", __func__); 184154133Sharti return (NULL); 185154133Sharti } 186154133Sharti memset(entry, 0, sizeof(*entry)); 187154133Sharti 188154133Sharti entry->index = map->hrIndex; 189154133Sharti entry->load = 0; 190214489Suqs entry->sample_cnt = 0; 191214489Suqs entry->cur_sample_idx = -1; 192154133Sharti entry->cpu_no = (u_char)cpu_no; 193160341Sharti entry->frwId = &oid_zeroDotZero; /* unknown id FIXME */ 194154133Sharti 195154133Sharti INSERT_OBJECT_INT(entry, &processor_tbl); 196154133Sharti 197154133Sharti HRDBG("CPU %d added with SNMP index=%d", 198154133Sharti entry->cpu_no, entry->index); 199154133Sharti 200154133Sharti return (entry); 201154133Sharti} 202154133Sharti 203154133Sharti/** 204154133Sharti * Scan the device map table for CPUs and create an entry into the 205214489Suqs * processor table for each CPU. 206214489Suqs * 207214489Suqs * Make sure that the number of processors announced by the kernel hw.ncpu 208214489Suqs * is equal to the number of processors we have found in the device table. 209154133Sharti */ 210154133Shartistatic void 211154133Sharticreate_proc_table(void) 212154133Sharti{ 213154133Sharti struct device_map_entry *map; 214154133Sharti struct processor_entry *entry; 215154133Sharti int cpu_no; 216214489Suqs size_t len; 217154133Sharti 218154133Sharti detected_processor_count = 0; 219154133Sharti 220154133Sharti /* 221154133Sharti * Because hrProcessorTable depends on hrDeviceTable, 222154133Sharti * the device detection must be performed at this point. 223154133Sharti * If not, no entries will be present in the hrProcessor Table. 224154133Sharti * 225154133Sharti * For non-ACPI system the processors are not in the device table, 226214489Suqs * therefore insert them after checking hw.ncpu. 227154133Sharti */ 228154133Sharti STAILQ_FOREACH(map, &device_map, link) 229154133Sharti if (strncmp(map->name_key, "cpu", strlen("cpu")) == 0 && 230154133Sharti strstr(map->location_key, ".CPU") != NULL) { 231154133Sharti if (sscanf(map->name_key,"cpu%d", &cpu_no) != 1) { 232154133Sharti syslog(LOG_ERR, "hrProcessorTable: Failed to " 233154133Sharti "get cpu no. from device named '%s'", 234154133Sharti map->name_key); 235154133Sharti continue; 236154133Sharti } 237154133Sharti 238154133Sharti if ((entry = proc_create_entry(cpu_no, map)) == NULL) 239154133Sharti continue; 240154133Sharti 241154133Sharti detected_processor_count++; 242154133Sharti } 243154133Sharti 244214489Suqs len = sizeof(hw_ncpu); 245214489Suqs if (sysctlbyname("hw.ncpu", &hw_ncpu, &len, NULL, 0) == -1 || 246214489Suqs len != sizeof(hw_ncpu)) { 247214489Suqs syslog(LOG_ERR, "hrProcessorTable: sysctl(hw.ncpu) failed"); 248214489Suqs hw_ncpu = 0; 249214489Suqs } 250154133Sharti 251214489Suqs HRDBG("%d CPUs detected via device table; hw.ncpu is %d", 252214489Suqs detected_processor_count, hw_ncpu); 253214489Suqs 254214489Suqs /* XXX Can happen on non-ACPI systems? Create entries by hand. */ 255214489Suqs for (; detected_processor_count < hw_ncpu; detected_processor_count++) 256214489Suqs proc_create_entry(detected_processor_count, NULL); 257214489Suqs 258214489Suqs len = 2; 259214489Suqs if (sysctlnametomib("kern.cp_times", cpmib, &len)) { 260214489Suqs syslog(LOG_ERR, "hrProcessorTable: sysctlnametomib(kern.cp_times) failed"); 261214489Suqs cpmib[0] = 0; 262214489Suqs cpmib[1] = 0; 263214489Suqs cplen = 0; 264214489Suqs } else if (sysctl(cpmib, 2, NULL, &len, NULL, 0)) { 265214489Suqs syslog(LOG_ERR, "hrProcessorTable: sysctl(kern.cp_times) length query failed"); 266214489Suqs cplen = 0; 267214489Suqs } else { 268214489Suqs cplen = len / sizeof(long); 269214489Suqs } 270214489Suqs HRDBG("%zu entries for kern.cp_times", cplen); 271214489Suqs 272154133Sharti} 273154133Sharti 274154133Sharti/** 275154133Sharti * Free the processor table 276154133Sharti */ 277154133Shartistatic void 278154133Shartifree_proc_table(void) 279154133Sharti{ 280154133Sharti struct processor_entry *n1; 281154133Sharti 282154133Sharti while ((n1 = TAILQ_FIRST(&processor_tbl)) != NULL) { 283154133Sharti TAILQ_REMOVE(&processor_tbl, n1, link); 284154133Sharti free(n1); 285154133Sharti detected_processor_count--; 286154133Sharti } 287154133Sharti 288154133Sharti assert(detected_processor_count == 0); 289154133Sharti detected_processor_count = 0; 290154133Sharti} 291154133Sharti 292154133Sharti/** 293154133Sharti * Refresh all values in the processor table. We call this once for 294154133Sharti * every PDU that accesses the table. 295154133Sharti */ 296154133Shartistatic void 297154133Shartirefresh_processor_tbl(void) 298154133Sharti{ 299154133Sharti struct processor_entry *entry; 300214489Suqs size_t size; 301154133Sharti 302214489Suqs long pcpu_cp_times[cplen]; 303214489Suqs memset(pcpu_cp_times, 0, sizeof(pcpu_cp_times)); 304154133Sharti 305214489Suqs size = cplen * sizeof(long); 306214489Suqs if (sysctl(cpmib, 2, pcpu_cp_times, &size, NULL, 0) == -1 && 307214489Suqs !(errno == ENOMEM && size >= cplen * sizeof(long))) { 308214489Suqs syslog(LOG_ERR, "hrProcessorTable: sysctl(kern.cp_times) failed"); 309214489Suqs return; 310214489Suqs } 311214489Suqs 312154133Sharti TAILQ_FOREACH(entry, &processor_tbl, link) { 313154249Sharti assert(hr_kd != NULL); 314214489Suqs save_sample(entry, &pcpu_cp_times[entry->cpu_no * CPUSTATES]); 315154133Sharti } 316154133Sharti 317154133Sharti} 318154133Sharti 319154133Sharti/** 320154133Sharti * This function is called MAX_CPU_SAMPLES times per minute to collect the 321154133Sharti * CPU load. 322154133Sharti */ 323154133Shartistatic void 324154133Shartiget_cpus_samples(void *arg __unused) 325154133Sharti{ 326154133Sharti 327154859Sharti HRDBG("[%llu] ENTER", (unsigned long long)get_ticks()); 328154133Sharti refresh_processor_tbl(); 329154859Sharti HRDBG("[%llu] EXIT", (unsigned long long)get_ticks()); 330154133Sharti} 331154133Sharti 332154133Sharti/** 333154133Sharti * Called to start this table. We need to start the periodic idle 334154133Sharti * time collection. 335154133Sharti */ 336154133Shartivoid 337154133Shartistart_processor_tbl(struct lmodule *mod) 338154133Sharti{ 339154133Sharti 340154133Sharti /* 341154133Sharti * Start the cpu stats collector 342154133Sharti * The semantics of timer_start parameters is in "SNMP ticks"; 343154133Sharti * we have 100 "SNMP ticks" per second, thus we are trying below 344154133Sharti * to get MAX_CPU_SAMPLES per minute 345154133Sharti */ 346154133Sharti cpus_load_timer = timer_start_repeat(100, 100 * 60 / MAX_CPU_SAMPLES, 347154133Sharti get_cpus_samples, NULL, mod); 348154133Sharti} 349154133Sharti 350154133Sharti/** 351214489Suqs * Init the things for hrProcessorTable. 352214489Suqs * Scan the device table for processor entries. 353214489Suqs */ 354214489Suqsvoid 355214489Suqsinit_processor_tbl(void) 356214489Suqs{ 357214489Suqs 358214489Suqs /* create the initial processor table */ 359214489Suqs create_proc_table(); 360214489Suqs /* and get first samples */ 361214489Suqs refresh_processor_tbl(); 362214489Suqs} 363214489Suqs 364214489Suqs/** 365214489Suqs * Finalization routine for hrProcessorTable. 366214489Suqs * It destroys the lists and frees any allocated heap memory. 367214489Suqs */ 368214489Suqsvoid 369214489Suqsfini_processor_tbl(void) 370214489Suqs{ 371214489Suqs 372214489Suqs if (cpus_load_timer != NULL) { 373214489Suqs timer_stop(cpus_load_timer); 374214489Suqs cpus_load_timer = NULL; 375214489Suqs } 376214489Suqs 377214489Suqs free_proc_table(); 378214489Suqs} 379214489Suqs 380214489Suqs/** 381154133Sharti * Access routine for the processor table. 382154133Sharti */ 383154133Shartiint 384154133Shartiop_hrProcessorTable(struct snmp_context *ctx __unused, 385154133Sharti struct snmp_value *value, u_int sub, u_int iidx __unused, 386154133Sharti enum snmp_op curr_op) 387154133Sharti{ 388154133Sharti struct processor_entry *entry; 389154133Sharti 390154133Sharti switch (curr_op) { 391154133Sharti 392154133Sharti case SNMP_OP_GETNEXT: 393154133Sharti if ((entry = NEXT_OBJECT_INT(&processor_tbl, 394154133Sharti &value->var, sub)) == NULL) 395154133Sharti return (SNMP_ERR_NOSUCHNAME); 396154133Sharti value->var.len = sub + 1; 397154133Sharti value->var.subs[sub] = entry->index; 398154133Sharti goto get; 399154133Sharti 400154133Sharti case SNMP_OP_GET: 401154133Sharti if ((entry = FIND_OBJECT_INT(&processor_tbl, 402154133Sharti &value->var, sub)) == NULL) 403154133Sharti return (SNMP_ERR_NOSUCHNAME); 404154133Sharti goto get; 405154133Sharti 406154133Sharti case SNMP_OP_SET: 407154133Sharti if ((entry = FIND_OBJECT_INT(&processor_tbl, 408154133Sharti &value->var, sub)) == NULL) 409154133Sharti return (SNMP_ERR_NO_CREATION); 410154133Sharti return (SNMP_ERR_NOT_WRITEABLE); 411154133Sharti 412154133Sharti case SNMP_OP_ROLLBACK: 413154133Sharti case SNMP_OP_COMMIT: 414154133Sharti abort(); 415154133Sharti } 416154133Sharti abort(); 417154133Sharti 418154133Sharti get: 419154133Sharti switch (value->var.subs[sub - 1]) { 420154133Sharti 421154133Sharti case LEAF_hrProcessorFrwID: 422160341Sharti assert(entry->frwId != NULL); 423160341Sharti value->v.oid = *entry->frwId; 424154133Sharti return (SNMP_ERR_NOERROR); 425154133Sharti 426154133Sharti case LEAF_hrProcessorLoad: 427154133Sharti value->v.integer = entry->load; 428154133Sharti return (SNMP_ERR_NOERROR); 429154133Sharti } 430154133Sharti abort(); 431154133Sharti} 432