]> git.ipfire.org Git - thirdparty/collectd.git/blob - src/memory.c
22b7446b0c58a0e93330df4c9bbb28de88631146
[thirdparty/collectd.git] / src / memory.c
1 /**
2 * collectd - src/memory.c
3 * Copyright (C) 2005-2014 Florian octo Forster
4 * Copyright (C) 2009 Simon Kuhnle
5 * Copyright (C) 2009 Manuel Sanmartin
6 *
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation; only version 2 of the License is applicable.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 *
20 * Authors:
21 * Florian octo Forster <octo at collectd.org>
22 * Simon Kuhnle <simon at blarzwurst.de>
23 * Manuel Sanmartin
24 **/
25
26 #include "collectd.h"
27
28 #include "plugin.h"
29 #include "utils/common/common.h"
30
31 #if (defined(HAVE_SYS_SYSCTL_H) && defined(HAVE_SYSCTLBYNAME)) || \
32 defined(__OpenBSD__)
33 /* Implies BSD variant */
34 #include <sys/sysctl.h>
35 #endif
36 #ifdef HAVE_SYS_VMMETER_H
37 #include <sys/vmmeter.h>
38 #endif
39
40 #ifdef HAVE_MACH_KERN_RETURN_H
41 #include <mach/kern_return.h>
42 #endif
43 #ifdef HAVE_MACH_MACH_INIT_H
44 #include <mach/mach_init.h>
45 #endif
46 #ifdef HAVE_MACH_MACH_HOST_H
47 #include <mach/mach_host.h>
48 #endif
49 #ifdef HAVE_MACH_HOST_PRIV_H
50 #include <mach/host_priv.h>
51 #endif
52 #ifdef HAVE_MACH_VM_STATISTICS_H
53 #include <mach/vm_statistics.h>
54 #endif
55
56 #if HAVE_STATGRAB_H
57 #include <statgrab.h>
58 #endif
59
60 #if HAVE_PERFSTAT
61 #include <libperfstat.h>
62 #include <sys/protosw.h>
63 #endif /* HAVE_PERFSTAT */
64
65 /* vm_statistics_data_t */
66 #if HAVE_HOST_STATISTICS
67 static mach_port_t port_host;
68 static vm_size_t pagesize;
69 /* #endif HAVE_HOST_STATISTICS */
70
71 #elif HAVE_SYSCTLBYNAME
72 #if HAVE_SYSCTL && defined(KERNEL_NETBSD)
73 static int pagesize;
74 #include <unistd.h> /* getpagesize() */
75 #else
76 /* no global variables */
77 #endif
78 /* #endif HAVE_SYSCTLBYNAME */
79
80 #elif KERNEL_LINUX
81 /* no global variables */
82 /* #endif KERNEL_LINUX */
83
84 #elif HAVE_LIBKSTAT
85 static int pagesize;
86 static kstat_t *ksp;
87 static kstat_t *ksz;
88 /* #endif HAVE_LIBKSTAT */
89
90 #elif HAVE_SYSCTL && __OpenBSD__
91 /* OpenBSD variant does not have sysctlbyname */
92 static int pagesize;
93 /* #endif HAVE_SYSCTL && __OpenBSD__ */
94
95 #elif HAVE_LIBSTATGRAB
96 /* no global variables */
97 /* endif HAVE_LIBSTATGRAB */
98 #elif HAVE_PERFSTAT
99 static int pagesize;
100 /* endif HAVE_PERFSTAT */
101 #else
102 #error "No applicable input method."
103 #endif
104
105 #if KERNEL_NETBSD
106 #include <uvm/uvm_extern.h>
107 #endif
108
109 static bool values_absolute = true;
110 static bool values_percentage;
111
112 static int memory_config(oconfig_item_t *ci) /* {{{ */
113 {
114 for (int i = 0; i < ci->children_num; i++) {
115 oconfig_item_t *child = ci->children + i;
116 if (strcasecmp("ValuesAbsolute", child->key) == 0)
117 cf_util_get_boolean(child, &values_absolute);
118 else if (strcasecmp("ValuesPercentage", child->key) == 0)
119 cf_util_get_boolean(child, &values_percentage);
120 else
121 ERROR("memory plugin: Invalid configuration option: "
122 "\"%s\".",
123 child->key);
124 }
125
126 return 0;
127 } /* }}} int memory_config */
128
129 static int memory_init(void) {
130 #if HAVE_HOST_STATISTICS
131 port_host = mach_host_self();
132 host_page_size(port_host, &pagesize);
133 /* #endif HAVE_HOST_STATISTICS */
134
135 #elif HAVE_SYSCTLBYNAME
136 #if HAVE_SYSCTL && defined(KERNEL_NETBSD)
137 pagesize = getpagesize();
138 #else
139 /* no init stuff */
140 #endif /* HAVE_SYSCTL && defined(KERNEL_NETBSD) */
141 /* #endif HAVE_SYSCTLBYNAME */
142
143 #elif defined(KERNEL_LINUX)
144 /* no init stuff */
145 /* #endif KERNEL_LINUX */
146
147 #elif defined(HAVE_LIBKSTAT)
148 /* getpagesize(3C) tells me this does not fail.. */
149 pagesize = getpagesize();
150 if (get_kstat(&ksp, "unix", 0, "system_pages") != 0) {
151 ksp = NULL;
152 return -1;
153 }
154 if (get_kstat(&ksz, "zfs", 0, "arcstats") != 0) {
155 ksz = NULL;
156 return -1;
157 }
158
159 /* #endif HAVE_LIBKSTAT */
160
161 #elif HAVE_SYSCTL && __OpenBSD__
162 /* OpenBSD variant does not have sysctlbyname */
163 pagesize = getpagesize();
164 if (pagesize <= 0) {
165 ERROR("memory plugin: Invalid pagesize: %i", pagesize);
166 return -1;
167 }
168 /* #endif HAVE_SYSCTL && __OpenBSD__ */
169
170 #elif HAVE_LIBSTATGRAB
171 /* no init stuff */
172 /* #endif HAVE_LIBSTATGRAB */
173
174 #elif HAVE_PERFSTAT
175 pagesize = getpagesize();
176 #endif /* HAVE_PERFSTAT */
177 return 0;
178 } /* int memory_init */
179
180 #define MEMORY_SUBMIT(...) \
181 do { \
182 if (values_absolute) \
183 plugin_dispatch_multivalue(vl, false, DS_TYPE_GAUGE, __VA_ARGS__, NULL); \
184 if (values_percentage) \
185 plugin_dispatch_multivalue(vl, true, DS_TYPE_GAUGE, __VA_ARGS__, NULL); \
186 } while (0)
187
188 #if KERNEL_LINUX
189 static void memory_submit_available(gauge_t value) {
190 value_list_t vl = VALUE_LIST_INIT;
191
192 vl.values = &(value_t){.gauge = value};
193 vl.values_len = 1;
194
195 sstrncpy(vl.plugin, "memory", sizeof(vl.plugin));
196 sstrncpy(vl.type, "memory", sizeof(vl.type));
197 sstrncpy(vl.type_instance, "available", sizeof(vl.type_instance));
198
199 plugin_dispatch_values(&vl);
200 }
201 #endif
202
203 static int memory_read_internal(value_list_t *vl) {
204 #if HAVE_HOST_STATISTICS
205 kern_return_t status;
206 vm_statistics_data_t vm_data;
207 mach_msg_type_number_t vm_data_len;
208
209 gauge_t wired;
210 gauge_t active;
211 gauge_t inactive;
212 gauge_t free;
213
214 if (!port_host || !pagesize)
215 return -1;
216
217 vm_data_len = sizeof(vm_data) / sizeof(natural_t);
218 if ((status = host_statistics(port_host, HOST_VM_INFO, (host_info_t)&vm_data,
219 &vm_data_len)) != KERN_SUCCESS) {
220 ERROR("memory-plugin: host_statistics failed and returned the value %i",
221 (int)status);
222 return -1;
223 }
224
225 /*
226 * From <http://docs.info.apple.com/article.html?artnum=107918>:
227 *
228 * Wired memory
229 * This information can't be cached to disk, so it must stay in RAM.
230 * The amount depends on what applications you are using.
231 *
232 * Active memory
233 * This information is currently in RAM and actively being used.
234 *
235 * Inactive memory
236 * This information is no longer being used and has been cached to
237 * disk, but it will remain in RAM until another application needs
238 * the space. Leaving this information in RAM is to your advantage if
239 * you (or a client of your computer) come back to it later.
240 *
241 * Free memory
242 * This memory is not being used.
243 */
244
245 wired = (gauge_t)(((uint64_t)vm_data.wire_count) * ((uint64_t)pagesize));
246 active = (gauge_t)(((uint64_t)vm_data.active_count) * ((uint64_t)pagesize));
247 inactive =
248 (gauge_t)(((uint64_t)vm_data.inactive_count) * ((uint64_t)pagesize));
249 free = (gauge_t)(((uint64_t)vm_data.free_count) * ((uint64_t)pagesize));
250
251 MEMORY_SUBMIT("wired", wired, "active", active, "inactive", inactive, "free",
252 free);
253 /* #endif HAVE_HOST_STATISTICS */
254
255 #elif HAVE_SYSCTLBYNAME
256
257 #if HAVE_SYSCTL && defined(KERNEL_NETBSD)
258 int mib[] = {CTL_VM, VM_UVMEXP2};
259 struct uvmexp_sysctl uvmexp;
260 gauge_t mem_active;
261 gauge_t mem_inactive;
262 gauge_t mem_free;
263 gauge_t mem_wired;
264 gauge_t mem_kernel;
265 size_t size;
266
267 memset(&uvmexp, 0, sizeof(uvmexp));
268 size = sizeof(uvmexp);
269
270 if (sysctl(mib, 2, &uvmexp, &size, NULL, 0) < 0) {
271 char errbuf[1024];
272 WARNING("memory plugin: sysctl failed: %s",
273 sstrerror(errno, errbuf, sizeof(errbuf)));
274 return (-1);
275 }
276
277 assert(pagesize > 0);
278 mem_active = (gauge_t)(uvmexp.active * pagesize);
279 mem_inactive = (gauge_t)(uvmexp.inactive * pagesize);
280 mem_free = (gauge_t)(uvmexp.free * pagesize);
281 mem_wired = (gauge_t)(uvmexp.wired * pagesize);
282 mem_kernel = (gauge_t)((uvmexp.npages - (uvmexp.active + uvmexp.inactive +
283 uvmexp.free + uvmexp.wired)) *
284 pagesize);
285
286 MEMORY_SUBMIT("active", mem_active, "inactive", mem_inactive, "free",
287 mem_free, "wired", mem_wired, "kernel", mem_kernel);
288 /* #endif HAVE_SYSCTL && defined(KERNEL_NETBSD) */
289
290 #else /* Other HAVE_SYSCTLBYNAME providers */
291 /*
292 * vm.stats.vm.v_page_size: 4096
293 * vm.stats.vm.v_page_count: 246178
294 * vm.stats.vm.v_free_count: 28760
295 * vm.stats.vm.v_wire_count: 37526
296 * vm.stats.vm.v_active_count: 55239
297 * vm.stats.vm.v_inactive_count: 113730
298 * vm.stats.vm.v_cache_count: 10809
299 * vm.stats.vm.v_user_wire_count: 0
300 * vm.stats.vm.v_laundry_count: 40394
301 */
302 const char *sysctl_keys[10] = {
303 "vm.stats.vm.v_page_size", "vm.stats.vm.v_page_count",
304 "vm.stats.vm.v_free_count", "vm.stats.vm.v_wire_count",
305 "vm.stats.vm.v_active_count", "vm.stats.vm.v_inactive_count",
306 "vm.stats.vm.v_cache_count", "vm.stats.vm.v_user_wire_count",
307 "vm.stats.vm.v_laundry_count", NULL};
308 double sysctl_vals[10];
309
310 for (int i = 0; sysctl_keys[i] != NULL; i++) {
311 int value;
312 size_t value_len = sizeof(value);
313
314 if (sysctlbyname(sysctl_keys[i], (void *)&value, &value_len, NULL, 0) ==
315 0) {
316 sysctl_vals[i] = value;
317 DEBUG("memory plugin: %26s: %g", sysctl_keys[i], sysctl_vals[i]);
318 } else {
319 sysctl_vals[i] = NAN;
320 }
321 } /* for (sysctl_keys) */
322
323 /* multiply all all page counts with the pagesize */
324 for (int i = 1; sysctl_keys[i] != NULL; i++)
325 if (!isnan(sysctl_vals[i]))
326 sysctl_vals[i] *= sysctl_vals[0];
327
328 MEMORY_SUBMIT("free", (gauge_t)sysctl_vals[2], "wired",
329 (gauge_t)sysctl_vals[3], "active", (gauge_t)sysctl_vals[4],
330 "inactive", (gauge_t)sysctl_vals[5], "cache",
331 (gauge_t)sysctl_vals[6], "user_wire", (gauge_t)sysctl_vals[7],
332 "laundry", (gauge_t)sysctl_vals[8]);
333
334 #endif /* HAVE_SYSCTL && KERNEL_NETBSD */
335 /* #endif HAVE_SYSCTLBYNAME */
336
337 #elif KERNEL_LINUX
338 FILE *fh;
339 char buffer[1024];
340
341 char *fields[8];
342 int numfields;
343
344 bool mem_available_info = false;
345 bool detailed_slab_info = false;
346
347 gauge_t mem_total = 0;
348 gauge_t mem_used = 0;
349 gauge_t mem_buffered = 0;
350 gauge_t mem_cached = 0;
351 gauge_t mem_free = 0;
352 gauge_t mem_available = 0;
353 gauge_t mem_slab_total = 0;
354 gauge_t mem_slab_reclaimable = 0;
355 gauge_t mem_slab_unreclaimable = 0;
356
357 if ((fh = fopen("/proc/meminfo", "r")) == NULL) {
358 WARNING("memory: fopen: %s", STRERRNO);
359 return -1;
360 }
361
362 while (fgets(buffer, sizeof(buffer), fh) != NULL) {
363 gauge_t *val = NULL;
364
365 if (strncasecmp(buffer, "MemTotal:", 9) == 0)
366 val = &mem_total;
367 else if (strncasecmp(buffer, "MemFree:", 8) == 0)
368 val = &mem_free;
369 else if (strncasecmp(buffer, "Buffers:", 8) == 0)
370 val = &mem_buffered;
371 else if (strncasecmp(buffer, "Cached:", 7) == 0)
372 val = &mem_cached;
373 else if (strncasecmp(buffer, "Slab:", 5) == 0)
374 val = &mem_slab_total;
375 else if (strncasecmp(buffer, "SReclaimable:", 13) == 0) {
376 val = &mem_slab_reclaimable;
377 detailed_slab_info = true;
378 } else if (strncasecmp(buffer, "SUnreclaim:", 11) == 0) {
379 val = &mem_slab_unreclaimable;
380 detailed_slab_info = true;
381 } else if (strncasecmp(buffer, "MemAvailable:", 13) == 0) {
382 val = &mem_available;
383 mem_available_info = true;
384 } else
385 continue;
386
387 numfields = strsplit(buffer, fields, STATIC_ARRAY_SIZE(fields));
388 if (numfields < 2)
389 continue;
390
391 *val = 1024.0 * atof(fields[1]);
392 }
393
394 if (fclose(fh)) {
395 WARNING("memory: fclose: %s", STRERRNO);
396 }
397
398 if (mem_total < (mem_free + mem_buffered + mem_cached + mem_slab_total))
399 return -1;
400
401 if (detailed_slab_info)
402 mem_used = mem_total -
403 (mem_free + mem_buffered + mem_cached + mem_slab_reclaimable);
404 else
405 mem_used =
406 mem_total - (mem_free + mem_buffered + mem_cached + mem_slab_total);
407
408 /* SReclaimable and SUnreclaim were introduced in kernel 2.6.19
409 * They sum up to the value of Slab, which is available on older & newer
410 * kernels. So SReclaimable/SUnreclaim are submitted if available, and Slab
411 * if not. */
412 if (detailed_slab_info)
413 MEMORY_SUBMIT("used", mem_used, "buffered", mem_buffered, "cached",
414 mem_cached, "free", mem_free, "slab_unrecl",
415 mem_slab_unreclaimable, "slab_recl", mem_slab_reclaimable);
416 else
417 MEMORY_SUBMIT("used", mem_used, "buffered", mem_buffered, "cached",
418 mem_cached, "free", mem_free, "slab", mem_slab_total);
419
420 if (mem_available_info)
421 memory_submit_available(mem_available);
422 /* #endif KERNEL_LINUX */
423
424 #elif HAVE_LIBKSTAT
425 /* Most of the additions here were taken as-is from the k9toolkit from
426 * Brendan Gregg and are subject to change I guess */
427 long long mem_used;
428 long long mem_free;
429 long long mem_lock;
430 long long mem_kern;
431 long long mem_unus;
432 long long arcsize;
433
434 long long pp_kernel;
435 long long physmem;
436 long long availrmem;
437
438 if (ksp == NULL)
439 return -1;
440 if (ksz == NULL)
441 return -1;
442
443 mem_used = get_kstat_value(ksp, "pagestotal");
444 mem_free = get_kstat_value(ksp, "pagesfree");
445 mem_lock = get_kstat_value(ksp, "pageslocked");
446 arcsize = get_kstat_value(ksz, "size");
447 pp_kernel = get_kstat_value(ksp, "pp_kernel");
448 physmem = get_kstat_value(ksp, "physmem");
449 availrmem = get_kstat_value(ksp, "availrmem");
450
451 mem_kern = 0;
452 mem_unus = 0;
453
454 if ((mem_used < 0LL) || (mem_free < 0LL) || (mem_lock < 0LL)) {
455 WARNING("memory plugin: one of used, free or locked is negative.");
456 return -1;
457 }
458
459 mem_unus = physmem - mem_used;
460
461 if (mem_used < (mem_free + mem_lock)) {
462 /* source: http://wesunsolve.net/bugid/id/4909199
463 * this seems to happen when swap space is small, e.g. 2G on a 32G system
464 * we will make some assumptions here
465 * educated solaris internals help welcome here */
466 DEBUG("memory plugin: pages total is smaller than \"free\" "
467 "+ \"locked\". This is probably due to small "
468 "swap space");
469 mem_free = availrmem;
470 mem_used = 0;
471 } else {
472 mem_used -= mem_free + mem_lock;
473 }
474
475 /* mem_kern is accounted for in mem_lock */
476 if (pp_kernel < mem_lock) {
477 mem_kern = pp_kernel;
478 mem_lock -= pp_kernel;
479 } else {
480 mem_kern = mem_lock;
481 mem_lock = 0;
482 }
483
484 mem_used *= pagesize; /* If this overflows you have some serious */
485 mem_free *= pagesize; /* memory.. Why not call me up and give me */
486 mem_lock *= pagesize; /* some? ;) */
487 mem_kern *= pagesize; /* it's 2011 RAM is cheap */
488 mem_unus *= pagesize;
489 mem_kern -= arcsize;
490
491 MEMORY_SUBMIT("used", (gauge_t)mem_used, "free", (gauge_t)mem_free, "locked",
492 (gauge_t)mem_lock, "kernel", (gauge_t)mem_kern, "arc",
493 (gauge_t)arcsize, "unusable", (gauge_t)mem_unus);
494 /* #endif HAVE_LIBKSTAT */
495
496 #elif HAVE_SYSCTL && __OpenBSD__
497 /* OpenBSD variant does not have HAVE_SYSCTLBYNAME */
498 int mib[] = {CTL_VM, VM_METER};
499 struct vmtotal vmtotal = {0};
500 gauge_t mem_active;
501 gauge_t mem_inactive;
502 gauge_t mem_free;
503 size_t size;
504
505 size = sizeof(vmtotal);
506
507 if (sysctl(mib, 2, &vmtotal, &size, NULL, 0) < 0) {
508 WARNING("memory plugin: sysctl failed: %s", STRERRNO);
509 return -1;
510 }
511
512 assert(pagesize > 0);
513 mem_active = (gauge_t)(vmtotal.t_arm * pagesize);
514 mem_inactive = (gauge_t)((vmtotal.t_rm - vmtotal.t_arm) * pagesize);
515 mem_free = (gauge_t)(vmtotal.t_free * pagesize);
516
517 MEMORY_SUBMIT("active", mem_active, "inactive", mem_inactive, "free",
518 mem_free);
519 /* #endif HAVE_SYSCTL && __OpenBSD__ */
520
521 #elif HAVE_LIBSTATGRAB
522 sg_mem_stats *ios;
523
524 ios = sg_get_mem_stats();
525 if (ios == NULL)
526 return -1;
527
528 MEMORY_SUBMIT("used", (gauge_t)ios->used, "cached", (gauge_t)ios->cache,
529 "free", (gauge_t)ios->free);
530 /* #endif HAVE_LIBSTATGRAB */
531
532 #elif HAVE_PERFSTAT
533 perfstat_memory_total_t pmemory = {0};
534
535 if (perfstat_memory_total(NULL, &pmemory, sizeof(pmemory), 1) < 0) {
536 WARNING("memory plugin: perfstat_memory_total failed: %s", STRERRNO);
537 return -1;
538 }
539
540 /* Unfortunately, the AIX documentation is not very clear on how these
541 * numbers relate to one another. The only thing is states explcitly
542 * is:
543 * real_total = real_process + real_free + numperm + real_system
544 *
545 * Another segmentation, which would be closer to the numbers reported
546 * by the "svmon" utility, would be:
547 * real_total = real_free + real_inuse
548 * real_inuse = "active" + real_pinned + numperm
549 */
550 MEMORY_SUBMIT("free", (gauge_t)(pmemory.real_free * pagesize), "cached",
551 (gauge_t)(pmemory.numperm * pagesize), "system",
552 (gauge_t)(pmemory.real_system * pagesize), "user",
553 (gauge_t)(pmemory.real_process * pagesize));
554 #endif /* HAVE_PERFSTAT */
555
556 return 0;
557 } /* }}} int memory_read_internal */
558
559 static int memory_read(void) /* {{{ */
560 {
561 value_t v[1];
562 value_list_t vl = VALUE_LIST_INIT;
563
564 vl.values = v;
565 vl.values_len = STATIC_ARRAY_SIZE(v);
566 sstrncpy(vl.plugin, "memory", sizeof(vl.plugin));
567 sstrncpy(vl.type, "memory", sizeof(vl.type));
568 vl.time = cdtime();
569
570 return memory_read_internal(&vl);
571 } /* }}} int memory_read */
572
573 void module_register(void) {
574 plugin_register_complex_config("memory", memory_config);
575 plugin_register_init("memory", memory_init);
576 plugin_register_read("memory", memory_read);
577 } /* void module_register */