slabinfo.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. // SPDX-License-Identifier: GPL-3.0-or-later
  2. #include "daemon/common.h"
  3. #include "libnetdata/required_dummies.h"
  4. #define PLUGIN_SLABINFO_NAME "slabinfo.plugin"
  5. #define PLUGIN_SLABINFO_PROCFILE "/proc/slabinfo"
  6. #define CHART_TYPE "mem"
  7. #define CHART_FAMILY "slab"
  8. #define CHART_PRIO 3000
  9. // #define slabdebug(...) if (debug) { fprintf(stderr, __VA_ARGS__); }
  10. #define slabdebug(args...) do { \
  11. if (debug) { \
  12. fprintf(stderr, "slabinfo.plugin DEBUG (%04d@%-10.10s:%-15.15s)::", __LINE__, __FILE__, __FUNCTION__); \
  13. fprintf(stderr, ##args); \
  14. fprintf(stderr, "\n"); \
  15. } \
  16. } while(0)
  17. int running = 1;
  18. int debug = 0;
  19. size_t lines_discovered = 0;
  20. int redraw_chart = 0;
  21. // ----------------------------------------------------------------------------
  22. // Slabinfo format :
  23. // format 2.1 Was provided by 57ed3eda977a215f054102b460ab0eb5d8d112e6 (2.6.24-rc6) as:
  24. // seq_puts(m, "# name <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab>");
  25. // seq_puts(m, " : tunables <limit> <batchcount> <sharedfactor>");
  26. // seq_puts(m, " : slabdata <active_slabs> <num_slabs> <sharedavail>");
  27. //
  28. // With max values:
  29. // seq_printf(m, "%-17s %6lu %6lu %6u %4u %4d",
  30. // cache_name(s), sinfo.active_objs, sinfo.num_objs, s->size, sinfo.objects_per_slab, (1 << sinfo.cache_order));
  31. // seq_printf(m, " : tunables %4u %4u %4u",
  32. // sinfo.limit, sinfo.batchcount, sinfo.shared);
  33. // seq_printf(m, " : slabdata %6lu %6lu %6lu",
  34. // sinfo.active_slabs, sinfo.num_slabs, sinfo.shared_avail);
  35. //
  36. // If CONFIG_DEBUG_SLAB is set, it will also add columns from slabinfo_show_stats (for SLAB only):
  37. // seq_printf(m, " : globalstat %7lu %6lu %5lu %4lu %4lu %4lu %4lu %4lu %4lu",
  38. // allocs, high, grown, reaped, errors, max_freeable, node_allocs, node_frees, overflows);
  39. // seq_printf(m, " : cpustat %6lu %6lu %6lu %6lu",
  40. // allochit, allocmiss, freehit, freemiss);
  41. //
  42. // Implementation choices:
  43. // - Iterates through a linked list of kmem_cache.
  44. // - Name is a char* from struct kmem_cache (mm/slab.h).
  45. // - max name size found is 24:
  46. // grep -roP 'kmem_cache_create\(".+"'| awk '{split($0,a,"\""); print a[2],length(a[2]); }' | sort -k2 -n
  47. // - Using uint64 everywhere, as types fits and allows to use standard helpers
  48. struct slabinfo {
  49. // procfile fields
  50. const char *name;
  51. uint64_t active_objs;
  52. uint64_t num_objs;
  53. uint64_t obj_size;
  54. uint64_t obj_per_slab;
  55. uint64_t pages_per_slab;
  56. uint64_t tune_limit;
  57. uint64_t tune_batchcnt;
  58. uint64_t tune_shared_factor;
  59. uint64_t data_active_slabs;
  60. uint64_t data_num_slabs;
  61. uint64_t data_shared_avail;
  62. // Calculated fields
  63. uint64_t mem_usage;
  64. uint64_t mem_waste;
  65. uint8_t obj_filling;
  66. uint32_t hash;
  67. struct slabinfo *next;
  68. } *slabinfo_root = NULL, *slabinfo_next = NULL, *slabinfo_last_used = NULL;
  69. // The code is very inspired from "proc_net_dev.c" and "perf_plugin.c"
  70. // Get the existing object, or create a new one
  71. static struct slabinfo *get_slabstruct(const char *name) {
  72. struct slabinfo *s;
  73. slabdebug("--> Requested slabstruct %s", name);
  74. uint32_t hash = simple_hash(name);
  75. // Search it, from the next to the end
  76. for (s = slabinfo_next; s; s = s->next) {
  77. if ((hash = s->hash) && !strcmp(name, s->name)) {
  78. slabdebug("<-- Found existing slabstruct after %s", slabinfo_last_used->name);
  79. // Prepare the next run
  80. slabinfo_next = s->next;
  81. slabinfo_last_used = s;
  82. return s;
  83. }
  84. }
  85. // Search it from the beginning to the last position we used
  86. for (s = slabinfo_root; s != slabinfo_last_used; s = s->next) {
  87. if (hash == s->hash && !strcmp(name, s->name)) {
  88. slabdebug("<-- Found existing slabstruct after root %s", slabinfo_root->name);
  89. slabinfo_next = s->next;
  90. slabinfo_last_used = s;
  91. return s;
  92. }
  93. }
  94. // Create a new one
  95. s = callocz(1, sizeof(struct slabinfo));
  96. s->name = strdupz(name);
  97. s->hash = hash;
  98. // Add it to the current position
  99. if (slabinfo_root) {
  100. slabdebug("<-- Creating new slabstruct after %s", slabinfo_last_used->name);
  101. s->next = slabinfo_last_used->next;
  102. slabinfo_last_used->next = s;
  103. slabinfo_last_used = s;
  104. }
  105. else {
  106. slabdebug("<-- Creating new slabstruct as root");
  107. slabinfo_root = slabinfo_last_used = s;
  108. }
  109. return s;
  110. }
  111. // Read a full pass of slabinfo to update the structs
  112. struct slabinfo *read_file_slabinfo() {
  113. slabdebug("-> Reading procfile %s", PLUGIN_SLABINFO_PROCFILE);
  114. static procfile *ff = NULL;
  115. static long slab_pagesize = 0;
  116. if (unlikely(!slab_pagesize)) {
  117. slab_pagesize = sysconf(_SC_PAGESIZE);
  118. slabdebug(" Discovered pagesize: %ld", slab_pagesize);
  119. }
  120. if(unlikely(!ff)) {
  121. ff = procfile_reopen(ff, PLUGIN_SLABINFO_PROCFILE, " ,:" , PROCFILE_FLAG_DEFAULT);
  122. if(unlikely(!ff)) {
  123. collector_error("<- Cannot open file '%s", PLUGIN_SLABINFO_PROCFILE);
  124. exit(1);
  125. }
  126. }
  127. ff = procfile_readall(ff);
  128. if(unlikely(!ff)) {
  129. collector_error("<- Cannot read file '%s'", PLUGIN_SLABINFO_PROCFILE);
  130. exit(0);
  131. }
  132. // Iterate on all lines to populate / update the slabinfo struct
  133. size_t lines = procfile_lines(ff), l;
  134. if (unlikely(lines != lines_discovered)) {
  135. lines_discovered = lines;
  136. redraw_chart = 1;
  137. }
  138. slabdebug(" Read %lu lines from procfile", (unsigned long)lines);
  139. for(l = 2; l < lines; l++) {
  140. if (unlikely(procfile_linewords(ff, l) < 14)) {
  141. slabdebug(" Line %zu has only %zu words, skipping", l, procfile_linewords(ff,l));
  142. continue;
  143. }
  144. char *name = procfile_lineword(ff, l, 0);
  145. struct slabinfo *s = get_slabstruct(name);
  146. s->active_objs = str2uint64_t(procfile_lineword(ff, l, 1), NULL);
  147. s->num_objs = str2uint64_t(procfile_lineword(ff, l, 2), NULL);
  148. s->obj_size = str2uint64_t(procfile_lineword(ff, l, 3), NULL);
  149. s->obj_per_slab = str2uint64_t(procfile_lineword(ff, l, 4), NULL);
  150. s->pages_per_slab = str2uint64_t(procfile_lineword(ff, l, 5), NULL);
  151. s->tune_limit = str2uint64_t(procfile_lineword(ff, l, 7), NULL);
  152. s->tune_batchcnt = str2uint64_t(procfile_lineword(ff, l, 8), NULL);
  153. s->tune_shared_factor = str2uint64_t(procfile_lineword(ff, l, 9), NULL);
  154. s->data_active_slabs = str2uint64_t(procfile_lineword(ff, l, 11), NULL);
  155. s->data_num_slabs = str2uint64_t(procfile_lineword(ff, l, 12), NULL);
  156. s->data_shared_avail = str2uint64_t(procfile_lineword(ff, l, 13), NULL);
  157. uint32_t memperslab = s->pages_per_slab * slab_pagesize;
  158. // Internal fragmentation: loss per slab, due to objects not being a multiple of pagesize
  159. //uint32_t lossperslab = memperslab - s->obj_per_slab * s->obj_size;
  160. // Total usage = slabs * pages per slab * page size
  161. s->mem_usage = (uint64_t)(s->data_num_slabs * memperslab);
  162. // Wasted memory (filling): slabs allocated but not filled: sum total slab - sum total objects
  163. s->mem_waste = s->mem_usage - (uint64_t)(s->active_objs * s->obj_size);
  164. //if (s->data_num_slabs > 1)
  165. // s->mem_waste += s->data_num_slabs * lossperslab;
  166. // Slab filling efficiency
  167. if (s->num_objs > 0)
  168. s->obj_filling = 100 * s->active_objs / s->num_objs;
  169. else
  170. s->obj_filling = 0;
  171. slabdebug(" Updated slab %s: %"PRIu64" %"PRIu64" %"PRIu64" %"PRIu64" %"PRIu64" / %"PRIu64" %"PRIu64" %"PRIu64" / %"PRIu64" %"PRIu64" %"PRIu64" / %"PRIu64" %"PRIu64" %hhu",
  172. name, s->active_objs, s->num_objs, s->obj_size, s->obj_per_slab, s->pages_per_slab,
  173. s->tune_limit, s->tune_batchcnt, s->tune_shared_factor,
  174. s->data_active_slabs, s->data_num_slabs, s->data_shared_avail,
  175. s->mem_usage, s->mem_waste, s->obj_filling);
  176. }
  177. return slabinfo_root;
  178. }
  179. unsigned int do_slab_stats(int update_every) {
  180. static unsigned int loops = 0;
  181. struct slabinfo *sactive = NULL, *s = NULL;
  182. // Main processing loop
  183. while (running) {
  184. sactive = read_file_slabinfo();
  185. // Init Charts
  186. if (unlikely(redraw_chart)) {
  187. redraw_chart = 0;
  188. // Memory Usage
  189. printf("CHART %s.%s '' 'Memory Usage' 'B' '%s' '' line %d %d %s\n"
  190. , CHART_TYPE
  191. , "slabmemory"
  192. , CHART_FAMILY
  193. , CHART_PRIO
  194. , update_every
  195. , PLUGIN_SLABINFO_NAME
  196. );
  197. for (s = sactive; s; s = s->next) {
  198. printf("DIMENSION %s '' absolute 1 1\n", s->name);
  199. }
  200. // Slab active usage (filling)
  201. printf("CHART %s.%s '' 'Object Filling' '%%' '%s' '' line %d %d %s\n"
  202. , CHART_TYPE
  203. , "slabfilling"
  204. , CHART_FAMILY
  205. , CHART_PRIO + 1
  206. , update_every
  207. , PLUGIN_SLABINFO_NAME
  208. );
  209. for (s = sactive; s; s = s->next) {
  210. printf("DIMENSION %s '' absolute 1 1\n", s->name);
  211. }
  212. // Memory waste
  213. printf("CHART %s.%s '' 'Memory waste' 'B' '%s' '' line %d %d %s\n"
  214. , CHART_TYPE
  215. , "slabwaste"
  216. , CHART_FAMILY
  217. , CHART_PRIO + 2
  218. , update_every
  219. , PLUGIN_SLABINFO_NAME
  220. );
  221. for (s = sactive; s; s = s->next) {
  222. printf("DIMENSION %s '' absolute 1 1\n", s->name);
  223. }
  224. }
  225. //
  226. // Memory usage
  227. //
  228. printf("BEGIN %s.%s\n"
  229. , CHART_TYPE
  230. , "slabmemory"
  231. );
  232. for (s = sactive; s; s = s->next) {
  233. printf("SET %s = %"PRIu64"\n"
  234. , s->name
  235. , s->mem_usage
  236. );
  237. }
  238. printf("END\n");
  239. //
  240. // Slab active usage
  241. //
  242. printf("BEGIN %s.%s\n"
  243. , CHART_TYPE
  244. , "slabfilling"
  245. );
  246. for (s = sactive; s; s = s->next) {
  247. printf("SET %s = %u\n"
  248. , s->name
  249. , s->obj_filling
  250. );
  251. }
  252. printf("END\n");
  253. //
  254. // Memory waste
  255. //
  256. printf("BEGIN %s.%s\n"
  257. , CHART_TYPE
  258. , "slabwaste"
  259. );
  260. for (s = sactive; s; s = s->next) {
  261. printf("SET %s = %"PRIu64"\n"
  262. , s->name
  263. , s->mem_waste
  264. );
  265. }
  266. printf("END\n");
  267. loops++;
  268. sleep(update_every);
  269. }
  270. return loops;
  271. }
  272. // ----------------------------------------------------------------------------
  273. // main
  274. void usage(void) {
  275. fprintf(stderr, "%s\n", program_name);
  276. exit(1);
  277. }
  278. int main(int argc, char **argv) {
  279. clocks_init();
  280. nd_log_initialize_for_external_plugins("slabinfo.plugin");
  281. program_name = argv[0];
  282. program_version = "0.1";
  283. int update_every = 1, i, n, freq = 0;
  284. for (i = 1; i < argc; i++) {
  285. // Frequency parsing
  286. if(isdigit(*argv[i]) && !freq) {
  287. n = (int) str2l(argv[i]);
  288. if (n > 0) {
  289. if (n >= UPDATE_EVERY_MAX) {
  290. collector_error("Invalid interval value: %s", argv[i]);
  291. exit(1);
  292. }
  293. freq = n;
  294. }
  295. }
  296. else if (strcmp("debug", argv[i]) == 0) {
  297. debug = 1;
  298. continue;
  299. }
  300. else {
  301. fprintf(stderr,
  302. "netdata slabinfo.plugin %s\n"
  303. "This program is a data collector plugin for netdata.\n"
  304. "\n"
  305. "Available command line options:\n"
  306. "\n"
  307. " COLLECTION_FREQUENCY data collection frequency in seconds\n"
  308. " minimum: %d\n"
  309. "\n"
  310. " debug enable verbose output\n"
  311. " default: disabled\n"
  312. "\n",
  313. program_version,
  314. update_every
  315. );
  316. exit(1);
  317. }
  318. }
  319. if(freq >= update_every)
  320. update_every = freq;
  321. else if(freq)
  322. collector_error("update frequency %d seconds is too small for slabinfo. Using %d.", freq, update_every);
  323. // Call the main function. Time drift to be added
  324. do_slab_stats(update_every);
  325. return 0;
  326. }