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