lru_gen_util.c 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. // SPDX-License-Identifier: GPL-2.0-only
  2. /*
  3. * Copyright (C) 2025, Google LLC.
  4. */
  5. #include <time.h>
  6. #include "lru_gen_util.h"
  7. /*
  8. * Tracks state while we parse memcg lru_gen stats. The file we're parsing is
  9. * structured like this (some extra whitespace elided):
  10. *
  11. * memcg (id) (path)
  12. * node (id)
  13. * (gen_nr) (age_in_ms) (nr_anon_pages) (nr_file_pages)
  14. */
  15. struct memcg_stats_parse_context {
  16. bool consumed; /* Whether or not this line was consumed */
  17. /* Next parse handler to invoke */
  18. void (*next_handler)(struct memcg_stats *stats,
  19. struct memcg_stats_parse_context *ctx,
  20. char *line);
  21. int current_node_idx; /* Current index in nodes array */
  22. const char *name; /* The name of the memcg we're looking for */
  23. };
  24. static void memcg_stats_handle_searching(struct memcg_stats *stats,
  25. struct memcg_stats_parse_context *ctx,
  26. char *line);
  27. static void memcg_stats_handle_in_memcg(struct memcg_stats *stats,
  28. struct memcg_stats_parse_context *ctx,
  29. char *line);
  30. static void memcg_stats_handle_in_node(struct memcg_stats *stats,
  31. struct memcg_stats_parse_context *ctx,
  32. char *line);
  33. struct split_iterator {
  34. char *str;
  35. char *save;
  36. };
  37. static char *split_next(struct split_iterator *it)
  38. {
  39. char *ret = strtok_r(it->str, " \t\n\r", &it->save);
  40. it->str = NULL;
  41. return ret;
  42. }
  43. static void memcg_stats_handle_searching(struct memcg_stats *stats,
  44. struct memcg_stats_parse_context *ctx,
  45. char *line)
  46. {
  47. struct split_iterator it = { .str = line };
  48. char *prefix = split_next(&it);
  49. char *memcg_id = split_next(&it);
  50. char *memcg_name = split_next(&it);
  51. char *end;
  52. ctx->consumed = true;
  53. if (!prefix || strcmp("memcg", prefix))
  54. return; /* Not a memcg line (maybe empty), skip */
  55. TEST_ASSERT(memcg_id && memcg_name,
  56. "malformed memcg line; no memcg id or memcg_name");
  57. if (strcmp(memcg_name + 1, ctx->name))
  58. return; /* Wrong memcg, skip */
  59. /* Found it! */
  60. stats->memcg_id = strtoul(memcg_id, &end, 10);
  61. TEST_ASSERT(*end == '\0', "malformed memcg id '%s'", memcg_id);
  62. if (!stats->memcg_id)
  63. return; /* Removed memcg? */
  64. ctx->next_handler = memcg_stats_handle_in_memcg;
  65. }
  66. static void memcg_stats_handle_in_memcg(struct memcg_stats *stats,
  67. struct memcg_stats_parse_context *ctx,
  68. char *line)
  69. {
  70. struct split_iterator it = { .str = line };
  71. char *prefix = split_next(&it);
  72. char *id = split_next(&it);
  73. long found_node_id;
  74. char *end;
  75. ctx->consumed = true;
  76. ctx->current_node_idx = -1;
  77. if (!prefix)
  78. return; /* Skip empty lines */
  79. if (!strcmp("memcg", prefix)) {
  80. /* Memcg done, found next one; stop. */
  81. ctx->next_handler = NULL;
  82. return;
  83. } else if (strcmp("node", prefix))
  84. TEST_ASSERT(false, "found malformed line after 'memcg ...',"
  85. "token: '%s'", prefix);
  86. /* At this point we know we have a node line. Parse the ID. */
  87. TEST_ASSERT(id, "malformed node line; no node id");
  88. found_node_id = strtol(id, &end, 10);
  89. TEST_ASSERT(*end == '\0', "malformed node id '%s'", id);
  90. ctx->current_node_idx = stats->nr_nodes++;
  91. TEST_ASSERT(ctx->current_node_idx < MAX_NR_NODES,
  92. "memcg has stats for too many nodes, max is %d",
  93. MAX_NR_NODES);
  94. stats->nodes[ctx->current_node_idx].node = found_node_id;
  95. ctx->next_handler = memcg_stats_handle_in_node;
  96. }
  97. static void memcg_stats_handle_in_node(struct memcg_stats *stats,
  98. struct memcg_stats_parse_context *ctx,
  99. char *line)
  100. {
  101. char *my_line = strdup(line);
  102. struct split_iterator it = { .str = my_line };
  103. char *gen, *age, *nr_anon, *nr_file;
  104. struct node_stats *node_stats;
  105. struct generation_stats *gen_stats;
  106. char *end;
  107. TEST_ASSERT(it.str, "failed to copy input line");
  108. gen = split_next(&it);
  109. if (!gen)
  110. goto out_consume; /* Skip empty lines */
  111. if (!strcmp("memcg", gen) || !strcmp("node", gen)) {
  112. /*
  113. * Reached next memcg or node section. Don't consume, let the
  114. * other handler deal with this.
  115. */
  116. ctx->next_handler = memcg_stats_handle_in_memcg;
  117. goto out;
  118. }
  119. node_stats = &stats->nodes[ctx->current_node_idx];
  120. TEST_ASSERT(node_stats->nr_gens < MAX_NR_GENS,
  121. "found too many generation lines; max is %d",
  122. MAX_NR_GENS);
  123. gen_stats = &node_stats->gens[node_stats->nr_gens++];
  124. age = split_next(&it);
  125. nr_anon = split_next(&it);
  126. nr_file = split_next(&it);
  127. TEST_ASSERT(age && nr_anon && nr_file,
  128. "malformed generation line; not enough tokens");
  129. gen_stats->gen = (int)strtol(gen, &end, 10);
  130. TEST_ASSERT(*end == '\0', "malformed generation number '%s'", gen);
  131. gen_stats->age_ms = strtol(age, &end, 10);
  132. TEST_ASSERT(*end == '\0', "malformed generation age '%s'", age);
  133. gen_stats->nr_anon = strtol(nr_anon, &end, 10);
  134. TEST_ASSERT(*end == '\0', "malformed anonymous page count '%s'",
  135. nr_anon);
  136. gen_stats->nr_file = strtol(nr_file, &end, 10);
  137. TEST_ASSERT(*end == '\0', "malformed file page count '%s'", nr_file);
  138. out_consume:
  139. ctx->consumed = true;
  140. out:
  141. free(my_line);
  142. }
  143. static void print_memcg_stats(const struct memcg_stats *stats, const char *name)
  144. {
  145. int node, gen;
  146. pr_debug("stats for memcg %s (id %lu):\n", name, stats->memcg_id);
  147. for (node = 0; node < stats->nr_nodes; ++node) {
  148. pr_debug("\tnode %d\n", stats->nodes[node].node);
  149. for (gen = 0; gen < stats->nodes[node].nr_gens; ++gen) {
  150. const struct generation_stats *gstats =
  151. &stats->nodes[node].gens[gen];
  152. pr_debug("\t\tgen %d\tage_ms %ld"
  153. "\tnr_anon %ld\tnr_file %ld\n",
  154. gstats->gen, gstats->age_ms, gstats->nr_anon,
  155. gstats->nr_file);
  156. }
  157. }
  158. }
  159. /* Re-read lru_gen debugfs information for @memcg into @stats. */
  160. void lru_gen_read_memcg_stats(struct memcg_stats *stats, const char *memcg)
  161. {
  162. FILE *f;
  163. ssize_t read = 0;
  164. char *line = NULL;
  165. size_t bufsz;
  166. struct memcg_stats_parse_context ctx = {
  167. .next_handler = memcg_stats_handle_searching,
  168. .name = memcg,
  169. };
  170. memset(stats, 0, sizeof(struct memcg_stats));
  171. f = fopen(LRU_GEN_DEBUGFS, "r");
  172. TEST_ASSERT(f, "fopen(%s) failed", LRU_GEN_DEBUGFS);
  173. while (ctx.next_handler && (read = getline(&line, &bufsz, f)) > 0) {
  174. ctx.consumed = false;
  175. do {
  176. ctx.next_handler(stats, &ctx, line);
  177. if (!ctx.next_handler)
  178. break;
  179. } while (!ctx.consumed);
  180. }
  181. if (read < 0 && !feof(f))
  182. TEST_ASSERT(false, "getline(%s) failed", LRU_GEN_DEBUGFS);
  183. TEST_ASSERT(stats->memcg_id > 0, "Couldn't find memcg: %s\n"
  184. "Did the memcg get created in the proper mount?",
  185. memcg);
  186. if (line)
  187. free(line);
  188. TEST_ASSERT(!fclose(f), "fclose(%s) failed", LRU_GEN_DEBUGFS);
  189. print_memcg_stats(stats, memcg);
  190. }
  191. /*
  192. * Find all pages tracked by lru_gen for this memcg in generation @target_gen.
  193. *
  194. * If @target_gen is negative, look for all generations.
  195. */
  196. long lru_gen_sum_memcg_stats_for_gen(int target_gen,
  197. const struct memcg_stats *stats)
  198. {
  199. int node, gen;
  200. long total_nr = 0;
  201. for (node = 0; node < stats->nr_nodes; ++node) {
  202. const struct node_stats *node_stats = &stats->nodes[node];
  203. for (gen = 0; gen < node_stats->nr_gens; ++gen) {
  204. const struct generation_stats *gen_stats =
  205. &node_stats->gens[gen];
  206. if (target_gen >= 0 && gen_stats->gen != target_gen)
  207. continue;
  208. total_nr += gen_stats->nr_anon + gen_stats->nr_file;
  209. }
  210. }
  211. return total_nr;
  212. }
  213. /* Find all pages tracked by lru_gen for this memcg. */
  214. long lru_gen_sum_memcg_stats(const struct memcg_stats *stats)
  215. {
  216. return lru_gen_sum_memcg_stats_for_gen(-1, stats);
  217. }
  218. /*
  219. * If lru_gen aging should force page table scanning.
  220. *
  221. * If you want to set this to false, you will need to do eviction
  222. * before doing extra aging passes.
  223. */
  224. static const bool force_scan = true;
  225. static void run_aging_impl(unsigned long memcg_id, int node_id, int max_gen)
  226. {
  227. FILE *f = fopen(LRU_GEN_DEBUGFS, "w");
  228. char *command;
  229. size_t sz;
  230. TEST_ASSERT(f, "fopen(%s) failed", LRU_GEN_DEBUGFS);
  231. sz = asprintf(&command, "+ %lu %d %d 1 %d\n",
  232. memcg_id, node_id, max_gen, force_scan);
  233. TEST_ASSERT(sz > 0, "creating aging command failed");
  234. pr_debug("Running aging command: %s", command);
  235. if (fwrite(command, sizeof(char), sz, f) < sz) {
  236. TEST_ASSERT(false, "writing aging command %s to %s failed",
  237. command, LRU_GEN_DEBUGFS);
  238. }
  239. TEST_ASSERT(!fclose(f), "fclose(%s) failed", LRU_GEN_DEBUGFS);
  240. }
  241. void lru_gen_do_aging(struct memcg_stats *stats, const char *memcg)
  242. {
  243. int node, gen;
  244. pr_debug("lru_gen: invoking aging...\n");
  245. /* Must read memcg stats to construct the proper aging command. */
  246. lru_gen_read_memcg_stats(stats, memcg);
  247. for (node = 0; node < stats->nr_nodes; ++node) {
  248. int max_gen = 0;
  249. for (gen = 0; gen < stats->nodes[node].nr_gens; ++gen) {
  250. int this_gen = stats->nodes[node].gens[gen].gen;
  251. max_gen = max_gen > this_gen ? max_gen : this_gen;
  252. }
  253. run_aging_impl(stats->memcg_id, stats->nodes[node].node,
  254. max_gen);
  255. }
  256. /* Re-read so callers get updated information */
  257. lru_gen_read_memcg_stats(stats, memcg);
  258. }
  259. /*
  260. * Find which generation contains at least @pages pages, assuming that
  261. * such a generation exists.
  262. */
  263. int lru_gen_find_generation(const struct memcg_stats *stats,
  264. unsigned long pages)
  265. {
  266. int node, gen, gen_idx, min_gen = INT_MAX, max_gen = -1;
  267. for (node = 0; node < stats->nr_nodes; ++node)
  268. for (gen_idx = 0; gen_idx < stats->nodes[node].nr_gens;
  269. ++gen_idx) {
  270. gen = stats->nodes[node].gens[gen_idx].gen;
  271. max_gen = gen > max_gen ? gen : max_gen;
  272. min_gen = gen < min_gen ? gen : min_gen;
  273. }
  274. for (gen = min_gen; gen <= max_gen; ++gen)
  275. /* See if this generation has enough pages. */
  276. if (lru_gen_sum_memcg_stats_for_gen(gen, stats) > pages)
  277. return gen;
  278. return -1;
  279. }
  280. bool lru_gen_usable(void)
  281. {
  282. long required_features = LRU_GEN_ENABLED | LRU_GEN_MM_WALK;
  283. int lru_gen_fd, lru_gen_debug_fd;
  284. char mglru_feature_str[8] = {};
  285. long mglru_features;
  286. lru_gen_fd = open(LRU_GEN_ENABLED_PATH, O_RDONLY);
  287. if (lru_gen_fd < 0) {
  288. puts("lru_gen: Could not open " LRU_GEN_ENABLED_PATH);
  289. return false;
  290. }
  291. if (read(lru_gen_fd, &mglru_feature_str, 7) < 7) {
  292. puts("lru_gen: Could not read from " LRU_GEN_ENABLED_PATH);
  293. close(lru_gen_fd);
  294. return false;
  295. }
  296. close(lru_gen_fd);
  297. mglru_features = strtol(mglru_feature_str, NULL, 16);
  298. if ((mglru_features & required_features) != required_features) {
  299. printf("lru_gen: missing features, got: 0x%lx, expected: 0x%lx\n",
  300. mglru_features, required_features);
  301. printf("lru_gen: Try 'echo 0x%lx > /sys/kernel/mm/lru_gen/enabled'\n",
  302. required_features);
  303. return false;
  304. }
  305. lru_gen_debug_fd = open(LRU_GEN_DEBUGFS, O_RDWR);
  306. __TEST_REQUIRE(lru_gen_debug_fd >= 0,
  307. "lru_gen: Could not open " LRU_GEN_DEBUGFS ", "
  308. "but lru_gen is enabled, so cannot use page_idle.");
  309. close(lru_gen_debug_fd);
  310. return true;
  311. }