Skip to content

Commit 4c0dc97

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. Signed-off-by: Mark Tinguely <mark.tinguely@oracle.com>
1 parent 4830729 commit 4c0dc97

File tree

3 files changed

+448
-0
lines changed

3 files changed

+448
-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: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
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(
67+
lru: Object
68+
) -> Iterator[Tuple[int, int, Object]]:
69+
"""
70+
Iterate over a list_lru and return each NUMA nodeid, memcgid and
71+
list_lru_one object.
72+
73+
:param lru: ``struct list_lru *``
74+
:return: Iterator of the Tuple (node_id, memcg_idx, ``list_lru_one *``)
75+
"""
76+
prog = lru.prog_
77+
memcg_aware = 0
78+
if has_member(lru, "memcg_aware") and lru.memcg_aware:
79+
memcg_aware = 1
80+
81+
if has_member(lru, "node"):
82+
# no lru.node in uek7 but covered in above test
83+
if has_member(lru.node, "memcg_lrus") and lru.node[0].memcg_lrus:
84+
memcg_aware = 1
85+
86+
if memcg_aware:
87+
if has_member(lru, "ext") or has_member(lru, "xa"):
88+
# v5.13 (uek7) or newer
89+
if has_member(lru, "ext"):
90+
# uek7 has a UEK_KABI_REPLACE of node to ext
91+
xa = lru.ext.xa
92+
else:
93+
# uek8
94+
xa = lru.xa
95+
# Keep the entries grouped by the NUMA node.
96+
for nid in for_each_online_node(prog):
97+
for memcgid, memcg in xa_for_each(xa.address_of_()):
98+
# convert from the void ptr
99+
memcg = Object(prog, "struct list_lru_memcg *", memcg)
100+
yield (nid, memcgid, memcg.node[nid])
101+
else:
102+
# Before v5.13, memcg entries are in an array
103+
# Keep the entries grouped by the NUMA node.
104+
for nid in for_each_online_node(prog):
105+
for i in range(prog["memcg_nr_cache_ids"]):
106+
llru1 = lru.node[nid].memcg_lrus.lru[i]
107+
if not list_empty(llru1.list.address_of_()):
108+
yield (nid, i, llru1)
109+
else:
110+
# not lru.memcg_aware
111+
for nid in for_each_online_node(prog):
112+
# not lru.memcg_aware
113+
if has_member(lru, "ext"):
114+
yield (nid, 0, lru.ext.node[nid].lru)
115+
else:
116+
yield (nid, 0, lru.node[nid].lru)
117+
118+
119+
def list_lru_for_each_entry(
120+
type: Union[str, Type],
121+
lru: Object, member: str
122+
) -> Iterator[Tuple[int, int, Object]]:
123+
"""
124+
Iterate over all of the entries in a list_lru.
125+
This function calls list_lru_for_each_list() and then iterates over
126+
each list_lru_one.
127+
128+
:param type: Entry type.
129+
:param lru: ``struct list_lru *``
130+
:param member: Name of list node member in entry type.
131+
:return: Iterator of ``type *`` objects.
132+
"""
133+
for nid, memcgid, llru1 in list_lru_for_each_list(lru):
134+
for entry in list_for_each_entry(
135+
type, llru1.list.address_of_(), member
136+
):
137+
yield (nid, memcgid, entry)
138+
139+
140+
def list_lru_from_memcg_node_for_each_list(
141+
mindx: IntegerLike,
142+
nid: IntegerLike,
143+
lru: Object,
144+
) -> Iterator[Object]:
145+
"""
146+
Iterate over each list_lru_one entries for the provided memcg and NUMA node.
147+
148+
:param mindx: memcg index.
149+
:param nid: NUMA node ID.
150+
:param lru: ``struct list_lru *``
151+
:return: Iterator of ``struct list_lru_one`` objects.
152+
"""
153+
prog = lru.prog_
154+
if node_state(nid, prog["N_ONLINE"]):
155+
memcg_aware = 0
156+
if has_member(lru, "memcg_aware") and lru.memcg_aware:
157+
memcg_aware = 1
158+
if has_member(lru, "node"):
159+
# no lru.node in uek7 but covered in above test
160+
if has_member(lru.node, "memcg_lrus") and lru.node[0].memcg_lrus:
161+
memcg_aware = 1
162+
if memcg_aware:
163+
if has_member(lru, "ext") or has_member(lru, "xa"):
164+
# v5.13 (uek7) or newer
165+
if has_member(lru, "ext"):
166+
# uek7 has a UEK_KABI_REPLACE of node to ext
167+
xa = lru.ext.xa
168+
else:
169+
# uek8
170+
xa = lru.xa
171+
# Keep the entries grouped by the NUMA node.
172+
memcg = xa_load(xa.address_of_(), mindx)
173+
# convert from the void ptr unless it is a NULL
174+
if memcg != NULL(prog, "void *"):
175+
memcg = Object(prog, "struct list_lru_memcg *", memcg)
176+
yield memcg.node[nid]
177+
else:
178+
# Before v5.13
179+
# make sure the memcg index is within the legal limits
180+
if mindx >= 0 and mindx < prog["memcg_nr_cache_ids"]:
181+
llru1 = lru.node[nid].memcg_lrus.lru[mindx]
182+
if not list_empty(llru1.list.address_of_()):
183+
yield llru1
184+
else:
185+
# not lru.memcg_aware
186+
if has_member(lru, "ext"):
187+
yield lru.ext.node[nid].lru
188+
else:
189+
yield lru.node[nid].lru
190+
191+
192+
def list_lru_from_memcg_node_for_each_entry(
193+
mindx: IntegerLike,
194+
nid: IntegerLike,
195+
type: Union[str, Type],
196+
lru: Object,
197+
member: str,
198+
) -> Iterator[Object]:
199+
"""
200+
Iterate over the entries in a list_lru by the provided memcg and NUMA node.
201+
This function calls list_lru_from_memcg_node_for_each_list() and
202+
then iterates over each list_lru_one.
203+
204+
:param mindx: memcg index.
205+
:param nid: NUMA node ID.
206+
:param type: Entry type.
207+
:param lru: ``struct list_lru *``
208+
:param member: Name of list node member in entry type.
209+
:return: Iterator of ``type *`` objects.
210+
"""
211+
for llru1 in list_lru_from_memcg_node_for_each_list(mindx, nid, lru):
212+
yield from list_for_each_entry(
213+
type, llru1.list.address_of_(), member)
214+
215+
216+
def list_lru_kmem_to_memcgidx(
217+
prog: Program,
218+
kvm: IntegerLike
219+
) -> IntegerLike:
220+
"""
221+
Return the memcg index of the list_lru entry.
222+
Return -1 if the list_lru is not memcg enabled or value could not be found.
223+
Memory cgroups for slab allocation are per object. This code expects a slab
224+
allocated kvm and the MEMCG_DATA_KMEM case is NOT covered in this routine.
225+
226+
:param prog: Kernel being debugged
227+
:param kvm: address of a list_lru
228+
:return: memcg index, -1 means not found
229+
"""
230+
page = virt_to_page(prog, kvm)
231+
cpage = compound_head(page)
232+
# page_objcgs_check() MEMCG_DATA_OBJCGS memcg are managed per object
233+
if has_member(cpage, "memcg_data") or has_member(cpage, "obj_cgroups"):
234+
if has_member(cpage, "memcg_data"):
235+
memcg_data = cpage.memcg_data
236+
else:
237+
# cast to an integer for the MEMCG_DATA_KMEM test.
238+
memcg_data = cast("unsigned long", cpage.obj_cgroups)
239+
if memcg_data & MEMCG_DATA_OBJCGS:
240+
objcgrp = Object(
241+
prog, "struct obj_cgroup **", memcg_data - MEMCG_DATA_OBJCGS
242+
)
243+
# offset of object calculation
244+
pvm = page_to_virt(cpage)
245+
kvm = Object(prog, "void *", kvm)
246+
if has_member(cpage, "slab_cache"):
247+
slab_cache = cpage.slab_cache
248+
else:
249+
# v5.17 (uek8) moved the kmem_cache to a new slab structure.
250+
# and since v6.10 the slab pages are identified by a page type
251+
if PageSlab(cpage):
252+
slab = Object(prog, "struct slab *", cpage)
253+
slab_cache = slab.slab_cache
254+
else:
255+
return -1
256+
objoffset = (kvm - pvm) / slab_cache.size
257+
memcgrp = objcgrp[objoffset].memcg
258+
if memcgrp == NULL(prog, "struct mem_cgroup *"):
259+
return -1
260+
else:
261+
return memcgrp.kmemcg_id
262+
else:
263+
return -1
264+
else:
265+
# Before v5.13
266+
scache = cpage.slab_cache
267+
if scache == NULL(prog, "struct kmem_cache *"):
268+
return -1
269+
else:
270+
return cpage.slab_cache.memcg_params.memcg.kmemcg_id
271+
272+
273+
def list_lru_kmem_to_nodeid(
274+
prog: Program,
275+
kvm: IntegerLike
276+
) -> IntegerLike:
277+
"""
278+
Return the NUMA node id of the list_lru entry.
279+
280+
:param prog: Kernel being debugged
281+
:param kvm: address of a list_lru entry
282+
:return: NUMA node id
283+
"""
284+
page = virt_to_page(prog, kvm)
285+
cpage = compound_head(page)
286+
#
287+
pfn = page_to_pfn(cpage)
288+
nodes = get_active_numa_nodes(cpage.prog_)
289+
for i in range(1, len(nodes)):
290+
if nodes[i - 1].node_start_pfn <= pfn < nodes[i].node_start_pfn:
291+
return nodes[i - 1].node_id
292+
return nodes[-1].node_id

0 commit comments

Comments
 (0)