Skip to content

Commit 16cbc8f

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 <[email protected]>
1 parent 4830729 commit 16cbc8f

File tree

3 files changed

+421
-0
lines changed

3 files changed

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

0 commit comments

Comments
 (0)