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