Skip to content

Commit ffcb879

Browse files
committed
Add the list_lru interator helper and test for the helper
The drgn iterator for list_lru and test that walks filesystem(s) and verifies the memcg and NUMA node id. Provides the functions: list_lru_for_each_list() iterate the list_lru and return each list_lru_one, the NUMA node and memcg number. list_lru_for_each_entry() iterate the list_lru and return each entry of specified type, the NUMA node and memcg number. list_lru_from_memcg_node_for_each_list() iterate the list_lru for the specified NUMA node and memcg id and return each list_lru_one. list_lru_from_memcg_node_for_each_entry() iterate the list_lru for the specified NUMA node and memcg id and return each entry of specified type. list_lru_kmem_to_memcgidx() return the memcg index for the specified list_lru. list_lru_kmem_to_nodeid() return the NUMA node id for the specified list_lru. The test defaults to the quick verification of the information from list_lru_for_each() but adding "verify", the test walks the memcg/NUMA node portion of the list_lru to verify the entry exists. Signed-off-by: Mark Tinguely <[email protected]>
1 parent 4830729 commit ffcb879

File tree

3 files changed

+450
-0
lines changed

3 files changed

+450
-0
lines changed

doc/api.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,14 @@ drgn\_tools.itertools module
129129
:undoc-members:
130130
:show-inheritance:
131131

132+
drgn\_tools.list_lru module
133+
-----------------------
134+
135+
.. automodule:: drgn_tools.list_lru
136+
:members:
137+
:undoc-members:
138+
:show-inheritance:
139+
132140
drgn\_tools.lock module
133141
-----------------------
134142

drgn_tools/list_lru.py

Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
# Copyright (c) 2025, Oracle and/or its affiliates.
2+
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
3+
"""
4+
LRU Lists
5+
------------
6+
7+
Helper to work with LRU lists. LRU can be created to be memcg aware and
8+
ordered by NUMA node.
9+
10+
The routines iterate through the specified LRU and on NUMA machines, the
11+
output keeps the entries ordered by NUMA node.
12+
13+
The list_lru_for_each_list() function iterates all of the list_lru_one
14+
list. The list_lru_for_each_entry() function iterates through all the
15+
specified entries on a list_lru and returns the NUMA nodeid, memcg
16+
and Object of the specified type.
17+
18+
The list_lru_from_memcg_node_for_each_list() and
19+
list_lru_from_memcg_node_for_each_entry() functions allows the user to
20+
restrict the iteration of the list_lru_one and entries by the memcg
21+
index when the list_lru is memcg_aware and the NUMA node identifier.
22+
23+
list_lru_kmem_to_memcgidx() is a helper to find the mem_cgroup index
24+
from a list_lru kvm address. This helper will find the memcg of a list_lru
25+
address. This routine is only interested in slab allocated entries and does
26+
not check nor handle the MEMCG_DATA_KMEM case.
27+
"""
28+
from typing import Iterator
29+
from typing import Tuple
30+
from typing import Union
31+
32+
from drgn import cast
33+
from drgn import IntegerLike
34+
from drgn import NULL
35+
from drgn import Object
36+
from drgn import Program
37+
from drgn import Type
38+
from drgn.helpers.linux.list import list_empty
39+
from drgn.helpers.linux.list import list_for_each_entry
40+
from drgn.helpers.linux.mm import compound_head
41+
from drgn.helpers.linux.mm import page_to_pfn
42+
from drgn.helpers.linux.mm import page_to_virt
43+
from drgn.helpers.linux.mm import PageSlab
44+
from drgn.helpers.linux.mm import virt_to_page
45+
from drgn.helpers.linux.nodemask import for_each_online_node
46+
from drgn.helpers.linux.nodemask import node_state
47+
from drgn.helpers.linux.xarray import xa_for_each
48+
from drgn.helpers.linux.xarray import xa_load
49+
50+
from drgn_tools.meminfo import get_active_numa_nodes
51+
from drgn_tools.util import has_member
52+
53+
MEMCG_DATA_OBJCGS = 1
54+
MEMCG_DATA_KMEM = 2
55+
56+
__all__ = (
57+
"list_lru_for_each_list",
58+
"list_lru_for_each_entry",
59+
"list_lru_from_memcg_node_for_each_list",
60+
"list_lru_from_memcg_node_for_each_entry",
61+
"list_lru_kmem_to_memcgidx",
62+
"list_lru_kmem_to_nodeid",
63+
)
64+
65+
66+
def list_lru_for_each_list(lru: Object) -> Iterator[Tuple[int, int, Object]]:
67+
"""
68+
Iterate over a list_lru and return each NUMA nodeid, memcgid and
69+
list_lru_one object.
70+
71+
:param lru: ``struct list_lru *``
72+
:return: Iterator of the Tuple (node_id, memcg_idx, ``list_lru_one *``)
73+
"""
74+
prog = lru.prog_
75+
memcg_aware = 0
76+
if has_member(lru, "memcg_aware") and lru.memcg_aware:
77+
memcg_aware = 1
78+
79+
if has_member(lru, "node"):
80+
# no lru.node in uek7 but covered in above test
81+
if has_member(lru.node, "memcg_lrus") and lru.node[0].memcg_lrus:
82+
memcg_aware = 1
83+
84+
if memcg_aware:
85+
if has_member(lru, "ext") or has_member(lru, "xa"):
86+
# v5.13 (uek7) or newer
87+
if has_member(lru, "ext"):
88+
# uek7 has a UEK_KABI_REPLACE of node to ext
89+
xa = lru.ext.xa
90+
else:
91+
# uek8
92+
xa = lru.xa
93+
# Keep the entries grouped by the NUMA node.
94+
for nid in for_each_online_node(prog):
95+
for memcgid, memcg in xa_for_each(xa.address_of_()):
96+
# convert from the void ptr
97+
memcg = Object(prog, "struct list_lru_memcg *", memcg)
98+
yield (nid, memcgid, memcg.node[nid])
99+
else:
100+
# Before v5.13, memcg entries are in an array
101+
# Keep the entries grouped by the NUMA node.
102+
for nid in for_each_online_node(prog):
103+
for i in range(prog["memcg_nr_cache_ids"]):
104+
llru1 = lru.node[nid].memcg_lrus.lru[i]
105+
if not list_empty(llru1.list.address_of_()):
106+
yield (nid, i, llru1)
107+
else:
108+
# not lru.memcg_aware
109+
for nid in for_each_online_node(prog):
110+
# not lru.memcg_aware
111+
if has_member(lru, "ext"):
112+
yield (nid, 0, lru.ext.node[nid].lru)
113+
else:
114+
yield (nid, 0, lru.node[nid].lru)
115+
116+
117+
def list_lru_for_each_entry(
118+
type: Union[str, Type], lru: Object, member: str
119+
) -> Iterator[Tuple[int, int, Object]]:
120+
"""
121+
Iterate over all of the entries in a list_lru.
122+
This function calls list_lru_for_each_list() and then iterates over
123+
each list_lru_one.
124+
125+
:param type: Entry type.
126+
:param lru: ``struct list_lru *``
127+
:param member: Name of list node member in entry type.
128+
:return: Iterator of ``type *`` objects.
129+
"""
130+
for nid, memcgid, llru1 in list_lru_for_each_list(lru):
131+
for entry in list_for_each_entry(
132+
type, llru1.list.address_of_(), member
133+
):
134+
yield (nid, memcgid, entry)
135+
136+
137+
def list_lru_from_memcg_node_for_each_list(
138+
mindx: IntegerLike,
139+
nid: IntegerLike,
140+
lru: Object,
141+
) -> Iterator[Object]:
142+
"""
143+
Iterate over each list_lru_one entries for the provided memcg and NUMA node.
144+
145+
:param mindx: memcg index.
146+
:param nid: NUMA node ID.
147+
:param lru: ``struct list_lru *``
148+
:return: Iterator of ``struct list_lru_one`` objects.
149+
"""
150+
prog = lru.prog_
151+
if node_state(nid, prog["N_ONLINE"]):
152+
memcg_aware = 0
153+
if has_member(lru, "memcg_aware") and lru.memcg_aware:
154+
memcg_aware = 1
155+
if has_member(lru, "node"):
156+
# no lru.node in uek7 but covered in above test
157+
if has_member(lru.node, "memcg_lrus") and lru.node[0].memcg_lrus:
158+
memcg_aware = 1
159+
if memcg_aware:
160+
if has_member(lru, "ext") or has_member(lru, "xa"):
161+
# v5.13 (uek7) or newer
162+
if has_member(lru, "ext"):
163+
# uek7 has a UEK_KABI_REPLACE of node to ext
164+
xa = lru.ext.xa
165+
else:
166+
# uek8
167+
xa = lru.xa
168+
# Keep the entries grouped by the NUMA node.
169+
memcg = xa_load(xa.address_of_(), mindx)
170+
# convert from the void ptr unless it is a NULL
171+
if memcg != NULL(prog, "void *"):
172+
memcg = Object(prog, "struct list_lru_memcg *", memcg)
173+
yield memcg.node[nid]
174+
else:
175+
# Before v5.13
176+
# make sure the memcg index is within the legal limits
177+
if mindx >= 0 and mindx < prog["memcg_nr_cache_ids"]:
178+
llru1 = lru.node[nid].memcg_lrus.lru[mindx]
179+
if not list_empty(llru1.list.address_of_()):
180+
yield llru1
181+
else:
182+
# not lru.memcg_aware
183+
if has_member(lru, "ext"):
184+
yield lru.ext.node[nid].lru
185+
else:
186+
yield lru.node[nid].lru
187+
188+
189+
def list_lru_from_memcg_node_for_each_entry(
190+
mindx: IntegerLike,
191+
nid: IntegerLike,
192+
type: Union[str, Type],
193+
lru: Object,
194+
member: str,
195+
) -> Iterator[Object]:
196+
"""
197+
Iterate over the entries in a list_lru by the provided memcg and NUMA node.
198+
This function calls list_lru_from_memcg_node_for_each_list() and
199+
then iterates over each list_lru_one.
200+
201+
:param mindx: memcg index.
202+
:param nid: NUMA node ID.
203+
:param type: Entry type.
204+
:param lru: ``struct list_lru *``
205+
:param member: Name of list node member in entry type.
206+
:return: Iterator of ``type *`` objects.
207+
"""
208+
for llru1 in list_lru_from_memcg_node_for_each_list(mindx, nid, lru):
209+
yield from list_for_each_entry(type, llru1.list.address_of_(), member)
210+
211+
212+
def list_lru_kmem_to_memcgidx(prog: Program, kvm: IntegerLike) -> IntegerLike:
213+
"""
214+
Return the memcg index of the list_lru entry.
215+
Return -1 if the list_lru is not memcg enabled or value could not be found.
216+
Memory cgroups for slab allocation are per object. This code expects a slab
217+
allocated kvm and the MEMCG_DATA_KMEM case is NOT covered in this routine.
218+
219+
:param prog: Kernel being debugged
220+
:param kvm: address of a list_lru
221+
:return: memcg index, -1 means not found
222+
"""
223+
page = virt_to_page(prog, kvm)
224+
cpage = compound_head(page)
225+
# page_objcgs_check() MEMCG_DATA_OBJCGS memcg are managed per object
226+
if has_member(cpage, "memcg_data") or has_member(cpage, "obj_cgroups"):
227+
if has_member(cpage, "memcg_data"):
228+
memcg_data = cpage.memcg_data
229+
else:
230+
# cast to an integer for the MEMCG_DATA_KMEM test.
231+
memcg_data = cast("unsigned long", cpage.obj_cgroups)
232+
if memcg_data & MEMCG_DATA_OBJCGS:
233+
objcgrp = Object(
234+
prog, "struct obj_cgroup **", memcg_data - MEMCG_DATA_OBJCGS
235+
)
236+
# offset of object calculation
237+
pvm = page_to_virt(cpage)
238+
kvm = Object(prog, "void *", kvm)
239+
if has_member(cpage, "slab_cache"):
240+
slab_cache = cpage.slab_cache
241+
else:
242+
# v5.17 (uek8) moved the kmem_cache to a new slab structure.
243+
# and since v6.10 the slab pages are identified by a page type
244+
if PageSlab(cpage):
245+
slab = Object(prog, "struct slab *", cpage)
246+
slab_cache = slab.slab_cache
247+
else:
248+
return -1
249+
objoffset = (kvm - pvm) / slab_cache.size
250+
memcgrp = objcgrp[objoffset].memcg
251+
if memcgrp == NULL(prog, "struct mem_cgroup *"):
252+
return -1
253+
else:
254+
return memcgrp.kmemcg_id
255+
else:
256+
return -1
257+
else:
258+
# Before v5.13
259+
scache = cpage.slab_cache
260+
if scache == NULL(prog, "struct kmem_cache *"):
261+
return -1
262+
else:
263+
return cpage.slab_cache.memcg_params.memcg.kmemcg_id
264+
265+
266+
def list_lru_kmem_to_nodeid(
267+
prog: Program,
268+
kvm: IntegerLike
269+
) -> IntegerLike:
270+
"""
271+
Return the NUMA node id of the list_lru entry.
272+
273+
:param prog: Kernel being debugged
274+
:param kvm: address of a list_lru entry
275+
:return: NUMA node id
276+
"""
277+
page = virt_to_page(prog, kvm)
278+
cpage = compound_head(page)
279+
#
280+
pfn = page_to_pfn(cpage)
281+
nodes = get_active_numa_nodes(cpage.prog_)
282+
for i in range(1, len(nodes)):
283+
if nodes[i - 1].node_start_pfn <= pfn < nodes[i].node_start_pfn:
284+
return nodes[i - 1].node_id
285+
return nodes[-1].node_id

0 commit comments

Comments
 (0)