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