hostres_storage_tbl.c revision 154137
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: head/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_storage_tbl.c 154133 2006-01-09 12:33:45Z harti $
30 */
31
32/*
33 * Host Resources MIB for SNMPd. Implementation for hrStorageTable
34 */
35
36#include <sys/types.h>
37#include <sys/param.h>
38#include <sys/sysctl.h>
39#include <sys/vmmeter.h>
40#include <sys/mount.h>
41
42#include <vm/vm_param.h>
43
44#include <assert.h>
45#include <err.h>
46#include <limits.h>
47#include <memstat.h>
48#include <paths.h>
49#include <stdlib.h>
50#include <string.h>
51#include <syslog.h>
52#include <unistd.h> /*for getpagesize()*/
53
54#include "hostres_snmp.h"
55#include "hostres_oid.h"
56#include "hostres_tree.h"
57
58/*
59 * This structure is used to hold a SNMP table entry
60 * for HOST-RESOURCES-MIB's hrStorageTable
61 */
62struct storage_entry {
63	int32_t		index;
64	struct asn_oid	type;
65	u_char		descr[255 + 1];
66	int32_t		allocationUnits;
67	int32_t		size;
68	int32_t		used;
69	uint32_t	allocationFailures;
70#define HR_STORAGE_FOUND 0x001
71	uint32_t	flags;	/* to be used internally*/
72	TAILQ_ENTRY(storage_entry) link;
73};
74TAILQ_HEAD(storage_tbl, storage_entry);
75
76/*
77 * Next structure is used to keep o list of mappings from a specific name
78 * (a_name) to an entry in the hrStorageTblEntry. We are trying to keep the
79 * same index for a specific name at least for the duration of one SNMP agent
80 * run.
81 */
82struct storage_map_entry {
83	int32_t		hrIndex; /* used for hrStorageTblEntry::index */
84
85	/* map key, also used for hrStorageTblEntry::descr */
86	u_char		a_name[255 + 1];
87
88	/*
89	 * next may be NULL if the respective hrStorageTblEntry
90	 * is (temporally) gone
91	 */
92	struct storage_entry *entry;
93	STAILQ_ENTRY(storage_map_entry) link;
94};
95STAILQ_HEAD(storage_map, storage_map_entry);
96
97/* the head of the list with table's entries */
98static struct storage_tbl storage_tbl = TAILQ_HEAD_INITIALIZER(storage_tbl);
99
100/*for consistent table indexing*/
101static struct storage_map storage_map =
102    STAILQ_HEAD_INITIALIZER(storage_map);
103
104/* last (agent) tick when hrStorageTable was updated */
105static uint64_t storage_tick;
106
107/* maximum number of ticks between two refreshs */
108uint32_t storage_tbl_refresh = HR_STORAGE_TBL_REFRESH * 100;
109
110/* for kvm_getswapinfo, malloc'd */
111static struct kvm_swap *swap_devs;
112static size_t swap_devs_len;		/* item count for swap_devs */
113
114/* for getfsstat, malloc'd */
115static struct statfs *fs_buf;
116static size_t fs_buf_count;		/* item count for fs_buf */
117
118static struct vmtotal mem_stats;
119
120/* next int available for indexing the hrStorageTable */
121static uint32_t next_storage_index = 1;
122
123/* start of list for memory detailed stats */
124static struct memory_type_list *mt_list;
125
126/* Constants */
127static const struct asn_oid OIDX_hrStorageRam_c = OIDX_hrStorageRam;
128static const struct asn_oid OIDX_hrStorageVirtualMemory_c =
129    OIDX_hrStorageVirtualMemory;
130
131/**
132 * Create a new entry into the storage table and, if neccessary, an
133 * entry into the storage map.
134 */
135static struct storage_entry *
136storage_entry_create(const char *name)
137{
138	struct storage_entry *entry;
139	struct storage_map_entry *map;
140
141	if ((entry = malloc(sizeof(*entry))) == NULL) {
142		syslog(LOG_WARNING, "%s: %m", __func__);
143		return (NULL);
144	}
145
146	strlcpy(entry->descr, name, sizeof(entry->descr));
147
148	STAILQ_FOREACH(map, &storage_map, link)
149		if (strcmp(map->a_name, entry->descr) == 0) {
150			entry->index = map->hrIndex;
151			map->entry = entry;
152			break;
153		}
154
155	if (map == NULL) {
156		/* new object - get a new index */
157		if (next_storage_index > INT_MAX) {
158		        syslog(LOG_ERR,
159			    "%s: hrStorageTable index wrap", __func__);
160			free(entry);
161			return (NULL);
162		}
163
164		if ((map = malloc(sizeof(*map))) == NULL) {
165			syslog(LOG_ERR, "hrStorageTable: %s: %m", __func__ );
166			free(entry);
167			return (NULL);
168		}
169
170		map->hrIndex = next_storage_index ++;
171		strlcpy(map->a_name, entry->descr, sizeof(map->a_name));
172		map->entry = entry;
173
174		STAILQ_INSERT_TAIL(&storage_map, map, link);
175
176		HRDBG("%s added into hrStorageMap at index=%d",
177		    name, map->hrIndex);
178	} else {
179		HRDBG("%s exists in hrStorageMap index=%d\n",
180		    name, map->hrIndex);
181	}
182
183	entry->index = map->hrIndex;
184
185	INSERT_OBJECT_INT(entry, &storage_tbl);
186
187	return (entry);
188}
189
190/**
191 * Delete an entry from the storage table.
192 */
193static void
194storage_entry_delete(struct storage_entry *entry)
195{
196	struct storage_map_entry *map;
197
198	assert(entry != NULL);
199
200	TAILQ_REMOVE(&storage_tbl, entry, link);
201	STAILQ_FOREACH(map, &storage_map, link)
202		if (map->entry == entry) {
203			map->entry = NULL;
204			break;
205		}
206
207	free(entry);
208}
209
210/**
211 * Find a table entry by its name.
212 */
213static struct storage_entry *
214storage_find_by_name(const char *name)
215{
216	struct storage_entry *entry;
217
218	TAILQ_FOREACH(entry, &storage_tbl, link)
219		if (strncmp(entry->descr, name,
220		    sizeof(entry->descr) - 1) == 0)
221			return (entry);
222
223	return (NULL);
224}
225
226/*
227 * VM info.
228 */
229static void
230storage_OS_get_vm(void)
231{
232	int mib[2] = { CTL_VM, VM_TOTAL };
233	size_t len = sizeof(mem_stats);
234	int page_size_bytes;
235	struct storage_entry *entry;
236
237	if (sysctl(mib, 2, &mem_stats, &len, NULL, 0) < 0) {
238		syslog(LOG_ERR,
239		    "hrStoragetable: %s: sysctl({CTL_VM, VM_METER}) "
240		    "failed: %m", __func__);
241		assert(0);
242		return;
243	}
244
245	page_size_bytes = getpagesize();
246
247	/* Real Memory Metrics */
248	if ((entry = storage_find_by_name("Real Memory Metrics")) == NULL &&
249	    (entry = storage_entry_create("Real Memory Metrics")) == NULL)
250		return; /* I'm out of luck now, maybe next time */
251
252	entry->flags |= HR_STORAGE_FOUND;
253	entry->type = OIDX_hrStorageRam_c;
254	entry->allocationUnits = page_size_bytes;
255	entry->size = mem_stats.t_rm;
256	entry->used = mem_stats.t_arm; /* ACTIVE is not USED - FIXME */
257	entry->allocationFailures = 0;
258
259	/* Shared Real Memory Metrics */
260	if ((entry = storage_find_by_name("Shared Real Memory Metrics")) ==
261	    NULL &&
262	    (entry = storage_entry_create("Shared Real Memory Metrics")) ==
263	    NULL)
264		return;
265
266	entry->flags |= HR_STORAGE_FOUND;
267	entry->type = OIDX_hrStorageRam_c;
268	entry->allocationUnits = page_size_bytes;
269	entry->size = mem_stats.t_rmshr;
270	/* ACTIVE is not USED - FIXME */
271	entry->used = mem_stats.t_armshr;
272	entry->allocationFailures = 0;
273}
274
275static void
276storage_OS_get_memstat(void)
277{
278	struct memory_type *mt_item;
279	struct storage_entry *entry;
280
281	if (mt_list == NULL) {
282		if ((mt_list = memstat_mtl_alloc()) == NULL)
283			/* again? we have a serious problem */
284		return;
285	}
286
287	if (memstat_sysctl_all(mt_list, 0) < 0) {
288		syslog(LOG_ERR, "memstat_sysctl_all failed: %s",
289		    memstat_strerror(memstat_mtl_geterror(mt_list)) );
290		return;
291	}
292
293	if ((mt_item = memstat_mtl_first(mt_list)) == NULL) {
294		/* usually this is not an error, no errno for this failure*/
295		HRDBG("memstat_mtl_first failed");
296		return;
297	}
298
299	do {
300		const char *memstat_name;
301		uint64_t tmp_size;
302		int allocator;
303		char alloc_descr[255 + 1];
304
305		memstat_name = memstat_get_name(mt_item);
306
307		if (memstat_name == NULL || strlen(memstat_name) == 0)
308			continue;
309
310		switch (allocator = memstat_get_allocator(mt_item)) {
311
312		  case ALLOCATOR_MALLOC:
313			snprintf(alloc_descr, sizeof(alloc_descr),
314			    "MALLOC: %s", memstat_name);
315			break;
316
317		  case ALLOCATOR_UMA:
318			snprintf(alloc_descr, sizeof(alloc_descr),
319			    "UMA: %s", memstat_name);
320			break;
321
322		  default:
323			snprintf(alloc_descr, sizeof(alloc_descr),
324			    "UNKNOWN%d: %s", allocator, memstat_name);
325			break;
326		}
327
328		if ((entry = storage_find_by_name(alloc_descr)) == NULL &&
329		    (entry = storage_entry_create(alloc_descr)) == NULL)
330			return;
331
332		entry->flags |= HR_STORAGE_FOUND;
333		entry->type = OIDX_hrStorageRam_c;
334
335		if ((tmp_size = memstat_get_size(mt_item)) == 0)
336			tmp_size = memstat_get_sizemask(mt_item);
337		entry->allocationUnits =
338		    (tmp_size  > INT_MAX ? INT_MAX : (int32_t)tmp_size);
339
340		tmp_size  = memstat_get_countlimit(mt_item);
341		entry->size =
342		    (tmp_size  > INT_MAX ? INT_MAX : (int32_t)tmp_size);
343
344		tmp_size = memstat_get_count(mt_item);
345		entry->used =
346		    (tmp_size  > INT_MAX ? INT_MAX : (int32_t)tmp_size);
347
348		tmp_size = memstat_get_failures(mt_item);
349		entry->allocationFailures =
350		    (tmp_size  > INT_MAX ? INT_MAX : (int32_t)tmp_size);
351
352	} while((mt_item = memstat_mtl_next(mt_item)) != NULL);
353}
354
355/**
356 * Get swap info
357 */
358static void
359storage_OS_get_swap(void)
360{
361        int nswapdev = 0;
362	size_t len = sizeof(nswapdev);
363	struct storage_entry *entry;
364	char swap_w_prefix[255 + 1];
365
366	if (sysctlbyname("vm.nswapdev", &nswapdev, &len, NULL,0 ) < 0) {
367		syslog(LOG_ERR,
368		    "hrStorageTable: sysctlbyname(\"vm.nswapdev\") "
369		    "failed. %m");
370		assert(0);
371		return;
372	}
373
374	if (nswapdev <= 0) {
375		HRDBG("vm.nswapdev is %d", nswapdev);
376		return;
377	}
378
379	if (nswapdev + 1 != (int)swap_devs_len || swap_devs == NULL) {
380		swap_devs_len = nswapdev + 1;
381		swap_devs = reallocf(swap_devs,
382		    swap_devs_len * sizeof(struct kvm_swap));
383
384		assert(swap_devs != NULL);
385		if (swap_devs == NULL) {
386			swap_devs_len = 0;
387			return;
388		}
389	}
390
391	nswapdev = kvm_getswapinfo(hr_kd, swap_devs, swap_devs_len, 0);
392	if (nswapdev < 0) {
393		syslog(LOG_ERR,
394		    "hrStorageTable: kvm_getswapinfo failed. %m\n");
395		assert(0);
396		return;
397	}
398
399	for (len = 0; len < (size_t)nswapdev; len++) {
400		memset(&swap_w_prefix[0], '\0', sizeof(swap_w_prefix));
401		snprintf(swap_w_prefix, sizeof(swap_w_prefix) - 1,
402		    "Swap:%s%s", _PATH_DEV, swap_devs[len].ksw_devname);
403
404		entry = storage_find_by_name(swap_w_prefix);
405		if (entry == NULL)
406			entry = storage_entry_create(swap_w_prefix);
407
408		assert (entry != NULL);
409		if (entry == NULL)
410			return; /* Out of luck */
411
412		entry->flags |= HR_STORAGE_FOUND;
413		entry->type = OIDX_hrStorageVirtualMemory_c;
414		entry->allocationUnits = getpagesize();
415		entry->size = swap_devs[len].ksw_total;
416		entry->used = swap_devs[len].ksw_used;
417		entry->allocationFailures = 0;
418	}
419}
420
421/**
422 * Query the underlaying OS for the mounted file systems
423 * anf fill in the respective lists (for hrStorageTable and for hrFSTable)
424 */
425static void
426storage_OS_get_fs(void)
427{
428	struct storage_entry *entry;
429	uint64_t used_blocks_count = 0;
430	char fs_string[255+1];
431	int mounted_fs_count;
432	int i = 0;
433
434	if ((mounted_fs_count = getfsstat(NULL, 0, MNT_NOWAIT)) < 0) {
435		syslog(LOG_ERR, "hrStorageTable: getfsstat() failed: %m");
436		return; /* out of luck this time */
437	}
438
439	if (mounted_fs_count != (int)fs_buf_count || fs_buf == NULL) {
440		fs_buf_count = mounted_fs_count;
441		fs_buf = reallocf(fs_buf, fs_buf_count * sizeof(struct statfs));
442		if (fs_buf == NULL) {
443			fs_buf_count = 0;
444			assert(0);
445			return;
446		}
447	}
448
449	if ((mounted_fs_count = getfsstat(fs_buf,
450	    fs_buf_count * sizeof(struct statfs), MNT_NOWAIT)) < 0) {
451		syslog(LOG_ERR, "hrStorageTable: getfsstat() failed: %m");
452		return; /* out of luck this time */
453	}
454
455	HRDBG("got %d mounted FS", mounted_fs_count);
456
457	fs_tbl_pre_refresh();
458
459	for (i = 0; i < mounted_fs_count; i++) {
460		snprintf(fs_string, sizeof(fs_string),
461		    "%s, type: %s, dev: %s", fs_buf[i].f_mntonname,
462		    fs_buf[i].f_fstypename, fs_buf[i].f_mntfromname);
463
464		entry = storage_find_by_name(fs_string);
465		if (entry == NULL)
466			entry = storage_entry_create(fs_string);
467
468		assert (entry != NULL);
469		if (entry == NULL)
470			return; /* Out of luck */
471
472		entry->flags |= HR_STORAGE_FOUND;
473		entry->type = *fs_get_type(&fs_buf[i]);
474
475		if (fs_buf[i].f_bsize > INT_MAX)
476			entry->allocationUnits = INT_MAX;
477		else
478			entry->allocationUnits = fs_buf[i].f_bsize;
479
480		if (fs_buf[i].f_blocks > INT_MAX)
481			entry->size = INT_MAX;
482		else
483			entry->size = fs_buf[i].f_blocks;
484
485		used_blocks_count = fs_buf[i].f_blocks - fs_buf[i].f_bfree;
486
487		if (used_blocks_count > INT_MAX)
488			entry->used = INT_MAX;
489		else
490			entry->used = used_blocks_count;
491
492		entry->allocationFailures = 0;
493
494		/* take care of hrFSTable */
495		fs_tbl_process_statfs_entry(&fs_buf[i], entry->index);
496	}
497
498	fs_tbl_post_refresh();
499}
500
501/**
502 * Initialize storage table and populate it.
503 */
504void
505init_storage_tbl(void)
506{
507	if ((mt_list = memstat_mtl_alloc()) == NULL)
508		syslog(LOG_ERR,
509		    "hrStorageTable: memstat_mtl_alloc() failed: %m");
510
511	refresh_storage_tbl(1);
512}
513
514void
515fini_storage_tbl(void)
516{
517	struct storage_map_entry *n1;
518
519	if (swap_devs != NULL) {
520		free(swap_devs);
521		swap_devs = NULL;
522	}
523	swap_devs_len = 0;
524
525	if (fs_buf != NULL) {
526		free(fs_buf);
527		fs_buf = NULL;
528	}
529	fs_buf_count = 0;
530
531	while ((n1 = STAILQ_FIRST(&storage_map)) != NULL) {
532		STAILQ_REMOVE_HEAD(&storage_map, link);
533		if (n1->entry != NULL) {
534			TAILQ_REMOVE(&storage_tbl, n1->entry, link);
535			free(n1->entry);
536		}
537		free(n1);
538	}
539	assert(TAILQ_EMPTY(&storage_tbl));
540}
541
542void
543refresh_storage_tbl(int force)
544{
545	struct storage_entry *entry, *entry_tmp;
546
547	if (!force && storage_tick != 0 &&
548	    this_tick - storage_tick < storage_tbl_refresh) {
549		HRDBG("no refresh needed");
550		return;
551	}
552
553	/* mark each entry as missing */
554	TAILQ_FOREACH(entry, &storage_tbl, link)
555		entry->flags &= ~HR_STORAGE_FOUND;
556
557	storage_OS_get_vm();
558	storage_OS_get_swap();
559	storage_OS_get_fs();
560	storage_OS_get_memstat();
561
562	/*
563	 * Purge items that disappeared
564	 */
565	TAILQ_FOREACH_SAFE(entry, &storage_tbl, link, entry_tmp)
566		if (!(entry->flags & HR_STORAGE_FOUND))
567			storage_entry_delete(entry);
568
569	storage_tick = this_tick;
570
571	HRDBG("refresh DONE");
572}
573
574/*
575 * This is the implementation for a generated (by our SNMP tool)
576 * function prototype, see hostres_tree.h
577 * It handles the SNMP operations for hrStorageTable
578 */
579int
580op_hrStorageTable(struct snmp_context *ctx __unused, struct snmp_value *value,
581    u_int sub, u_int iidx __unused, enum snmp_op curr_op)
582{
583	struct storage_entry *entry;
584
585	refresh_storage_tbl(0);
586
587	switch (curr_op) {
588
589	case SNMP_OP_GETNEXT:
590		if ((entry = NEXT_OBJECT_INT(&storage_tbl,
591		    &value->var, sub)) == NULL)
592			return (SNMP_ERR_NOSUCHNAME);
593
594		value->var.len = sub + 1;
595		value->var.subs[sub] = entry->index;
596		goto get;
597
598	case SNMP_OP_GET:
599		if ((entry = FIND_OBJECT_INT(&storage_tbl,
600		    &value->var, sub)) == NULL)
601			return (SNMP_ERR_NOSUCHNAME);
602		goto get;
603
604	case SNMP_OP_SET:
605		if ((entry = FIND_OBJECT_INT(&storage_tbl,
606		    &value->var, sub)) == NULL)
607			return (SNMP_ERR_NO_CREATION);
608		return (SNMP_ERR_NOT_WRITEABLE);
609
610	case SNMP_OP_ROLLBACK:
611	case SNMP_OP_COMMIT:
612		abort();
613	}
614	abort();
615
616  get:
617	switch (value->var.subs[sub - 1]) {
618
619	case LEAF_hrStorageIndex:
620		value->v.integer = entry->index;
621		return (SNMP_ERR_NOERROR);
622
623	case LEAF_hrStorageType:
624		value->v.oid = entry->type;
625		return (SNMP_ERR_NOERROR);
626
627	case LEAF_hrStorageDescr:
628		return (string_get(value, entry->descr, -1));
629		break;
630
631	case LEAF_hrStorageAllocationUnits:
632		value->v.integer = entry->allocationUnits;
633		return (SNMP_ERR_NOERROR);
634
635	case LEAF_hrStorageSize:
636		value->v.integer = entry->size;
637		return (SNMP_ERR_NOERROR);
638
639	case LEAF_hrStorageUsed:
640		value->v.integer = entry->used;
641		return (SNMP_ERR_NOERROR);
642
643	case LEAF_hrStorageAllocationFailures:
644		value->v.uint32 = entry->allocationFailures;
645		return (SNMP_ERR_NOERROR);
646	}
647	abort();
648}
649