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 descritpion 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 neccessary, 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 int nswapdev = 0; 378 size_t len = sizeof(nswapdev); 379 struct storage_entry *entry; 380 char swap_w_prefix[SE_DESC_MLEN]; 381 382 if (sysctlbyname("vm.nswapdev", &nswapdev, &len, NULL,0 ) < 0) { 383 syslog(LOG_ERR, 384 "hrStorageTable: sysctlbyname(\"vm.nswapdev\") " 385 "failed. %m"); 386 assert(0); 387 return; 388 } 389 390 if (nswapdev <= 0) { 391 HRDBG("vm.nswapdev is %d", nswapdev); 392 return; 393 } 394 395 if (nswapdev + 1 != (int)swap_devs_len || swap_devs == NULL) { 396 swap_devs_len = nswapdev + 1; 397 swap_devs = reallocf(swap_devs, 398 swap_devs_len * sizeof(struct kvm_swap)); 399 400 assert(swap_devs != NULL); 401 if (swap_devs == NULL) { 402 swap_devs_len = 0; 403 return; 404 } 405 } 406 407 nswapdev = kvm_getswapinfo(hr_kd, swap_devs, swap_devs_len, 0); 408 if (nswapdev < 0) { 409 syslog(LOG_ERR, 410 "hrStorageTable: kvm_getswapinfo failed. %m\n"); 411 assert(0); 412 return; 413 } 414 415 for (len = 0; len < (size_t)nswapdev; len++) { 416 memset(&swap_w_prefix[0], '\0', sizeof(swap_w_prefix)); 417 snprintf(swap_w_prefix, sizeof(swap_w_prefix) - 1, 418 "Swap:%s%s", _PATH_DEV, swap_devs[len].ksw_devname); 419 420 entry = storage_find_by_name(swap_w_prefix); 421 if (entry == NULL) 422 entry = storage_entry_create(swap_w_prefix); 423 424 assert (entry != NULL); 425 if (entry == NULL) 426 return; /* Out of luck */ 427 428 entry->flags |= HR_STORAGE_FOUND; 429 entry->type = &OIDX_hrStorageVirtualMemory_c; 430 entry->allocationUnits = getpagesize(); 431 entry->size = swap_devs[len].ksw_total; 432 entry->used = swap_devs[len].ksw_used; 433 entry->allocationFailures = 0; 434 } 435 } 436 437 /** 438 * Query the underlaying OS for the mounted file systems 439 * anf fill in the respective lists (for hrStorageTable and for hrFSTable) 440 */ 441 static void 442 storage_OS_get_fs(void) 443 { 444 struct storage_entry *entry; 445 uint64_t used_blocks_count = 0; 446 char fs_string[SE_DESC_MLEN]; 447 int mounted_fs_count; 448 int i = 0; 449 450 if ((mounted_fs_count = getfsstat(NULL, 0, MNT_NOWAIT)) < 0) { 451 syslog(LOG_ERR, "hrStorageTable: getfsstat() failed: %m"); 452 return; /* out of luck this time */ 453 } 454 455 if (mounted_fs_count != (int)fs_buf_count || fs_buf == NULL) { 456 fs_buf_count = mounted_fs_count; 457 fs_buf = reallocf(fs_buf, fs_buf_count * sizeof(struct statfs)); 458 if (fs_buf == NULL) { 459 fs_buf_count = 0; 460 assert(0); 461 return; 462 } 463 } 464 465 if ((mounted_fs_count = getfsstat(fs_buf, 466 fs_buf_count * sizeof(struct statfs), MNT_NOWAIT)) < 0) { 467 syslog(LOG_ERR, "hrStorageTable: getfsstat() failed: %m"); 468 return; /* out of luck this time */ 469 } 470 471 HRDBG("got %d mounted FS", mounted_fs_count); 472 473 fs_tbl_pre_refresh(); 474 475 for (i = 0; i < mounted_fs_count; i++) { 476 snprintf(fs_string, sizeof(fs_string), 477 "%s, type: %s, dev: %s", fs_buf[i].f_mntonname, 478 fs_buf[i].f_fstypename, fs_buf[i].f_mntfromname); 479 480 entry = storage_find_by_name(fs_string); 481 if (entry == NULL) 482 entry = storage_entry_create(fs_string); 483 484 assert (entry != NULL); 485 if (entry == NULL) 486 return; /* Out of luck */ 487 488 entry->flags |= HR_STORAGE_FOUND; 489 entry->type = fs_get_type(&fs_buf[i]); /*XXX - This is wrong*/ 490 491 if (fs_buf[i].f_bsize > INT_MAX) 492 entry->allocationUnits = INT_MAX; 493 else 494 entry->allocationUnits = fs_buf[i].f_bsize; 495 496 if (fs_buf[i].f_blocks > INT_MAX) 497 entry->size = INT_MAX; 498 else 499 entry->size = fs_buf[i].f_blocks; 500 501 used_blocks_count = fs_buf[i].f_blocks - fs_buf[i].f_bfree; 502 503 if (used_blocks_count > INT_MAX) 504 entry->used = INT_MAX; 505 else 506 entry->used = used_blocks_count; 507 508 entry->allocationFailures = 0; 509 510 /* take care of hrFSTable */ 511 fs_tbl_process_statfs_entry(&fs_buf[i], entry->index); 512 } 513 514 fs_tbl_post_refresh(); 515 } 516 517 /** 518 * Initialize storage table and populate it. 519 */ 520 void 521 init_storage_tbl(void) 522 { 523 if ((mt_list = memstat_mtl_alloc()) == NULL) 524 syslog(LOG_ERR, 525 "hrStorageTable: memstat_mtl_alloc() failed: %m"); 526 527 refresh_storage_tbl(1); 528 } 529 530 void 531 fini_storage_tbl(void) 532 { 533 struct storage_map_entry *n1; 534 535 if (swap_devs != NULL) { 536 free(swap_devs); 537 swap_devs = NULL; 538 } 539 swap_devs_len = 0; 540 541 if (fs_buf != NULL) { 542 free(fs_buf); 543 fs_buf = NULL; 544 } 545 fs_buf_count = 0; 546 547 while ((n1 = STAILQ_FIRST(&storage_map)) != NULL) { 548 STAILQ_REMOVE_HEAD(&storage_map, link); 549 if (n1->entry != NULL) { 550 TAILQ_REMOVE(&storage_tbl, n1->entry, link); 551 free(n1->entry->descr); 552 free(n1->entry); 553 } 554 free(n1->a_name); 555 free(n1); 556 } 557 assert(TAILQ_EMPTY(&storage_tbl)); 558 } 559 560 void 561 refresh_storage_tbl(int force) 562 { 563 struct storage_entry *entry, *entry_tmp; 564 565 if (!force && storage_tick != 0 && 566 this_tick - storage_tick < storage_tbl_refresh) { 567 HRDBG("no refresh needed"); 568 return; 569 } 570 571 /* mark each entry as missing */ 572 TAILQ_FOREACH(entry, &storage_tbl, link) 573 entry->flags &= ~HR_STORAGE_FOUND; 574 575 storage_OS_get_vm(); 576 storage_OS_get_swap(); 577 storage_OS_get_fs(); 578 storage_OS_get_memstat(); 579 580 /* 581 * Purge items that disappeared 582 */ 583 TAILQ_FOREACH_SAFE(entry, &storage_tbl, link, entry_tmp) 584 if (!(entry->flags & HR_STORAGE_FOUND)) 585 storage_entry_delete(entry); 586 587 storage_tick = this_tick; 588 589 HRDBG("refresh DONE"); 590 } 591 592 /* 593 * This is the implementation for a generated (by our SNMP tool) 594 * function prototype, see hostres_tree.h 595 * It handles the SNMP operations for hrStorageTable 596 */ 597 int 598 op_hrStorageTable(struct snmp_context *ctx __unused, struct snmp_value *value, 599 u_int sub, u_int iidx __unused, enum snmp_op curr_op) 600 { 601 struct storage_entry *entry; 602 603 refresh_storage_tbl(0); 604 605 switch (curr_op) { 606 607 case SNMP_OP_GETNEXT: 608 if ((entry = NEXT_OBJECT_INT(&storage_tbl, 609 &value->var, sub)) == NULL) 610 return (SNMP_ERR_NOSUCHNAME); 611 612 value->var.len = sub + 1; 613 value->var.subs[sub] = entry->index; 614 goto get; 615 616 case SNMP_OP_GET: 617 if ((entry = FIND_OBJECT_INT(&storage_tbl, 618 &value->var, sub)) == NULL) 619 return (SNMP_ERR_NOSUCHNAME); 620 goto get; 621 622 case SNMP_OP_SET: 623 if ((entry = FIND_OBJECT_INT(&storage_tbl, 624 &value->var, sub)) == NULL) 625 return (SNMP_ERR_NO_CREATION); 626 return (SNMP_ERR_NOT_WRITEABLE); 627 628 case SNMP_OP_ROLLBACK: 629 case SNMP_OP_COMMIT: 630 abort(); 631 } 632 abort(); 633 634 get: 635 switch (value->var.subs[sub - 1]) { 636 637 case LEAF_hrStorageIndex: 638 value->v.integer = entry->index; 639 return (SNMP_ERR_NOERROR); 640 641 case LEAF_hrStorageType: 642 assert(entry->type != NULL); 643 value->v.oid = *entry->type; 644 return (SNMP_ERR_NOERROR); 645 646 case LEAF_hrStorageDescr: 647 assert(entry->descr != NULL); 648 return (string_get(value, entry->descr, -1)); 649 break; 650 651 case LEAF_hrStorageAllocationUnits: 652 value->v.integer = entry->allocationUnits; 653 return (SNMP_ERR_NOERROR); 654 655 case LEAF_hrStorageSize: 656 value->v.integer = entry->size; 657 return (SNMP_ERR_NOERROR); 658 659 case LEAF_hrStorageUsed: 660 value->v.integer = entry->used; 661 return (SNMP_ERR_NOERROR); 662 663 case LEAF_hrStorageAllocationFailures: 664 value->v.uint32 = entry->allocationFailures; 665 return (SNMP_ERR_NOERROR); 666 } 667 abort(); 668 } 669