Skip to content

Commit da31d9c

Browse files
committed
Add the list_lru interator helper and test for the helper
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 da31d9c

File tree

3 files changed

+413
-0
lines changed

3 files changed

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

0 commit comments

Comments
 (0)