Skip to content

Commit 83e7c52

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 83e7c52

File tree

3 files changed

+423
-0
lines changed

3 files changed

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

0 commit comments

Comments
 (0)