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