Path: blob/main/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_processor_tbl.c
105655 views
/*-1* Copyright (c) 2005-2006 The FreeBSD Project2* All rights reserved.3*4* Author: Victor Cruceru <[email protected]>5*6* Redistribution of this software and documentation and use in source and7* binary forms, with or without modification, are permitted provided that8* the following conditions are met:9*10* 1. Redistributions of source code or documentation must retain the above11* copyright notice, this list of conditions and the following disclaimer.12* 2. Redistributions in binary form must reproduce the above copyright13* notice, this list of conditions and the following disclaimer in the14* documentation and/or other materials provided with the distribution.15*16* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND17* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE18* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE19* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE20* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL21* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS22* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)23* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT24* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY25* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF26* SUCH DAMAGE.27*/2829/*30* Host Resources MIB for SNMPd. Implementation for hrProcessorTable31*/3233#include <sys/param.h>34#include <sys/sysctl.h>35#include <sys/user.h>3637#include <assert.h>38#include <math.h>39#include <stdlib.h>40#include <string.h>41#include <syslog.h>4243#include "hostres_snmp.h"44#include "hostres_oid.h"45#include "hostres_tree.h"4647/*48* This structure is used to hold a SNMP table entry49* for HOST-RESOURCES-MIB's hrProcessorTable.50* Note that index is external being allocated & maintained51* by the hrDeviceTable code..52*/53struct processor_entry {54int32_t index;55const struct asn_oid *frwId;56int32_t load; /* average cpu usage */57int32_t sample_cnt; /* number of usage samples */58int32_t cur_sample_idx; /* current valid sample */59TAILQ_ENTRY(processor_entry) link;60u_char cpu_no; /* which cpu, counted from 0 */6162/* the samples from the last minute, as required by MIB */63double samples[MAX_CPU_SAMPLES];64long states[MAX_CPU_SAMPLES][CPUSTATES];65};66TAILQ_HEAD(processor_tbl, processor_entry);6768/* the head of the list with hrDeviceTable's entries */69static struct processor_tbl processor_tbl =70TAILQ_HEAD_INITIALIZER(processor_tbl);7172/* number of processors in dev tbl */73static int32_t detected_processor_count;7475/* sysctlbyname(hw.ncpu) */76static int hw_ncpu;7778/* sysctlbyname(kern.cp_times) */79static int cpmib[2];80static size_t cplen;8182/* periodic timer used to get cpu load stats */83static void *cpus_load_timer;8485/**86* Returns the CPU usage of a given processor entry.87*88* It needs at least two cp_times "tick" samples to calculate a delta and89* thus, the usage over the sampling period.90*/91static int92get_avg_load(struct processor_entry *e)93{94u_int i, oldest;95long delta = 0;96double usage = 0.0;9798assert(e != NULL);99100/* Need two samples to perform delta calculation. */101if (e->sample_cnt <= 1)102return (0);103104/* Oldest usable index, we wrap around. */105if (e->sample_cnt == MAX_CPU_SAMPLES)106oldest = (e->cur_sample_idx + 1) % MAX_CPU_SAMPLES;107else108oldest = 0;109110/* Sum delta for all states. */111for (i = 0; i < CPUSTATES; i++) {112delta += e->states[e->cur_sample_idx][i];113delta -= e->states[oldest][i];114}115if (delta == 0)116return 0;117118/* Take idle time from the last element and convert to119* percent usage by contrasting with total ticks delta. */120usage = (double)(e->states[e->cur_sample_idx][CPUSTATES-1] -121e->states[oldest][CPUSTATES-1]) / delta;122usage = 100 - (usage * 100);123HRDBG("CPU no. %d, delta ticks %ld, pct usage %.2f", e->cpu_no,124delta, usage);125126return ((int)(usage));127}128129/**130* Save a new sample to proc entry and get the average usage.131*132* Samples are stored in a ringbuffer from 0..(MAX_CPU_SAMPLES-1)133*/134static void135save_sample(struct processor_entry *e, long *cp_times)136{137int i;138139e->cur_sample_idx = (e->cur_sample_idx + 1) % MAX_CPU_SAMPLES;140for (i = 0; cp_times != NULL && i < CPUSTATES; i++)141e->states[e->cur_sample_idx][i] = cp_times[i];142143e->sample_cnt++;144if (e->sample_cnt > MAX_CPU_SAMPLES)145e->sample_cnt = MAX_CPU_SAMPLES;146147HRDBG("sample count for CPU no. %d went to %d", e->cpu_no, e->sample_cnt);148e->load = get_avg_load(e);149150}151152/**153* Create a new entry into the processor table.154*/155static struct processor_entry *156proc_create_entry(u_int cpu_no, struct device_map_entry *map)157{158struct device_entry *dev;159struct processor_entry *entry;160char name[128];161162/*163* If there is no map entry create one by creating a device table164* entry.165*/166if (map == NULL) {167snprintf(name, sizeof(name), "cpu%u", cpu_no);168if ((dev = device_entry_create(name, "", "")) == NULL)169return (NULL);170dev->flags |= HR_DEVICE_IMMUTABLE;171STAILQ_FOREACH(map, &device_map, link)172if (strcmp(map->name_key, name) == 0)173break;174if (map == NULL)175abort();176}177178if ((entry = malloc(sizeof(*entry))) == NULL) {179syslog(LOG_ERR, "hrProcessorTable: %s malloc "180"failed: %m", __func__);181return (NULL);182}183memset(entry, 0, sizeof(*entry));184185entry->index = map->hrIndex;186entry->load = 0;187entry->sample_cnt = 0;188entry->cur_sample_idx = -1;189entry->cpu_no = (u_char)cpu_no;190entry->frwId = &oid_zeroDotZero; /* unknown id FIXME */191192INSERT_OBJECT_INT(entry, &processor_tbl);193194HRDBG("CPU %d added with SNMP index=%d",195entry->cpu_no, entry->index);196197return (entry);198}199200/**201* Scan the device map table for CPUs and create an entry into the202* processor table for each CPU.203*204* Make sure that the number of processors announced by the kernel hw.ncpu205* is equal to the number of processors we have found in the device table.206*/207static void208create_proc_table(void)209{210struct device_map_entry *map;211struct processor_entry *entry;212int cpu_no;213size_t len;214215detected_processor_count = 0;216217/*218* Because hrProcessorTable depends on hrDeviceTable,219* the device detection must be performed at this point.220* If not, no entries will be present in the hrProcessor Table.221*222* For non-ACPI system the processors are not in the device table,223* therefore insert them after checking hw.ncpu.224*/225STAILQ_FOREACH(map, &device_map, link)226if (strncmp(map->name_key, "cpu", strlen("cpu")) == 0 &&227strstr(map->location_key, ".CPU") != NULL) {228if (sscanf(map->name_key,"cpu%d", &cpu_no) != 1) {229syslog(LOG_ERR, "hrProcessorTable: Failed to "230"get cpu no. from device named '%s'",231map->name_key);232continue;233}234235if ((entry = proc_create_entry(cpu_no, map)) == NULL)236continue;237238detected_processor_count++;239}240241len = sizeof(hw_ncpu);242if (sysctlbyname("hw.ncpu", &hw_ncpu, &len, NULL, 0) == -1 ||243len != sizeof(hw_ncpu)) {244syslog(LOG_ERR, "hrProcessorTable: sysctl(hw.ncpu) failed");245hw_ncpu = 0;246}247248HRDBG("%d CPUs detected via device table; hw.ncpu is %d",249detected_processor_count, hw_ncpu);250251/* XXX Can happen on non-ACPI systems? Create entries by hand. */252for (; detected_processor_count < hw_ncpu; detected_processor_count++)253proc_create_entry(detected_processor_count, NULL);254255len = 2;256if (sysctlnametomib("kern.cp_times", cpmib, &len)) {257syslog(LOG_ERR, "hrProcessorTable: sysctlnametomib(kern.cp_times) failed");258cpmib[0] = 0;259cpmib[1] = 0;260cplen = 0;261} else if (sysctl(cpmib, 2, NULL, &len, NULL, 0)) {262syslog(LOG_ERR, "hrProcessorTable: sysctl(kern.cp_times) length query failed");263cplen = 0;264} else {265cplen = len / sizeof(long);266}267HRDBG("%zu entries for kern.cp_times", cplen);268269}270271/**272* Free the processor table273*/274static void275free_proc_table(void)276{277struct processor_entry *n1;278279while ((n1 = TAILQ_FIRST(&processor_tbl)) != NULL) {280TAILQ_REMOVE(&processor_tbl, n1, link);281free(n1);282detected_processor_count--;283}284285assert(detected_processor_count == 0);286detected_processor_count = 0;287}288289/**290* Refresh all values in the processor table. We call this once for291* every PDU that accesses the table.292*/293static void294refresh_processor_tbl(void)295{296struct processor_entry *entry;297size_t size;298299long pcpu_cp_times[cplen];300memset(pcpu_cp_times, 0, sizeof(pcpu_cp_times));301302size = cplen * sizeof(long);303if (sysctl(cpmib, 2, pcpu_cp_times, &size, NULL, 0) == -1 &&304!(errno == ENOMEM && size >= cplen * sizeof(long))) {305syslog(LOG_ERR, "hrProcessorTable: sysctl(kern.cp_times) failed");306return;307}308309TAILQ_FOREACH(entry, &processor_tbl, link) {310assert(hr_kd != NULL);311save_sample(entry, &pcpu_cp_times[entry->cpu_no * CPUSTATES]);312}313314}315316/**317* This function is called MAX_CPU_SAMPLES times per minute to collect the318* CPU load.319*/320static void321get_cpus_samples(void *arg __unused)322{323324HRDBG("[%llu] ENTER", (unsigned long long)get_ticks());325refresh_processor_tbl();326HRDBG("[%llu] EXIT", (unsigned long long)get_ticks());327}328329/**330* Called to start this table. We need to start the periodic idle331* time collection.332*/333void334start_processor_tbl(struct lmodule *mod)335{336337/*338* Start the cpu stats collector339* The semantics of timer_start parameters is in "SNMP ticks";340* we have 100 "SNMP ticks" per second, thus we are trying below341* to get MAX_CPU_SAMPLES per minute342*/343cpus_load_timer = timer_start_repeat(100, 100 * 60 / MAX_CPU_SAMPLES,344get_cpus_samples, NULL, mod);345}346347/**348* Init the things for hrProcessorTable.349* Scan the device table for processor entries.350*/351void352init_processor_tbl(void)353{354355/* create the initial processor table */356create_proc_table();357/* and get first samples */358refresh_processor_tbl();359}360361/**362* Finalization routine for hrProcessorTable.363* It destroys the lists and frees any allocated heap memory.364*/365void366fini_processor_tbl(void)367{368369if (cpus_load_timer != NULL) {370timer_stop(cpus_load_timer);371cpus_load_timer = NULL;372}373374free_proc_table();375}376377/**378* Access routine for the processor table.379*/380int381op_hrProcessorTable(struct snmp_context *ctx __unused,382struct snmp_value *value, u_int sub, u_int iidx __unused,383enum snmp_op curr_op)384{385struct processor_entry *entry;386387switch (curr_op) {388389case SNMP_OP_GETNEXT:390if ((entry = NEXT_OBJECT_INT(&processor_tbl,391&value->var, sub)) == NULL)392return (SNMP_ERR_NOSUCHNAME);393value->var.len = sub + 1;394value->var.subs[sub] = entry->index;395goto get;396397case SNMP_OP_GET:398if ((entry = FIND_OBJECT_INT(&processor_tbl,399&value->var, sub)) == NULL)400return (SNMP_ERR_NOSUCHNAME);401goto get;402403case SNMP_OP_SET:404if ((entry = FIND_OBJECT_INT(&processor_tbl,405&value->var, sub)) == NULL)406return (SNMP_ERR_NO_CREATION);407return (SNMP_ERR_NOT_WRITEABLE);408409case SNMP_OP_ROLLBACK:410case SNMP_OP_COMMIT:411abort();412}413abort();414415get:416switch (value->var.subs[sub - 1]) {417418case LEAF_hrProcessorFrwID:419assert(entry->frwId != NULL);420value->v.oid = *entry->frwId;421return (SNMP_ERR_NOERROR);422423case LEAF_hrProcessorLoad:424value->v.integer = entry->load;425return (SNMP_ERR_NOERROR);426}427abort();428}429430431