Skip to content

Commit d4b879d

Browse files
committed
Introduce spl_cache walker
1 parent 07a7b34 commit d4b879d

13 files changed

+4103
-27
lines changed

sdb/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
'type_canonical_name',
7474
'type_canonicalize',
7575
'type_canonicalize_name',
76+
'type_canonicalize_size',
7677
'type_equals',
7778
]
7879

sdb/commands/internal/fmt.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,17 @@
1313
# See the License for the specific language governing permissions and
1414
# limitations under the License.
1515
#
16-
17-
# pylint: disable=missing-docstring
16+
"""
17+
Common String and Number formatting functions.
18+
"""
1819

1920
from typing import Union
2021

2122

2223
def size_nicenum(num: Union[int, float]) -> str:
24+
"""
25+
Return `num` bytes as a human-readable string.
26+
"""
2327
num = float(num)
2428
for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
2529
if num < 1024.0:

sdb/commands/internal/p2.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#
2+
# Copyright 2020 Delphix
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#
16+
"""
17+
Common C macros for bit-manipulation and alignment.
18+
"""
19+
20+
21+
def p2roundup(val: int, align: int) -> int:
22+
"""
23+
Round up `val` to the next `align` boundary.
24+
"""
25+
return ((val - 1) | (align - 1)) + 1

sdb/commands/linux/linked_lists.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,3 +124,11 @@ def _call(self, objs: Iterable[drgn.Object]) -> Iterable[drgn.Object]:
124124
yield from hlist_for_each_entry(sname, obj, self.args.member)
125125
except LookupError as err:
126126
raise sdb.CommandError(self.name, str(err))
127+
128+
129+
def is_list_empty(l: drgn.Object) -> bool:
130+
"""
131+
True if list is empty, False otherwise.
132+
"""
133+
assert sdb.type_canonical_name(l.type_) == 'struct list_head'
134+
return int(l.address_of_().value_()) == int(l.next.value_())

sdb/commands/spl/internal/kmem_helpers.py

Lines changed: 91 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -22,42 +22,44 @@
2222
import drgn.helpers.linux.list as drgn_list
2323

2424
import sdb
25+
from sdb.commands.internal import p2
26+
from sdb.commands.linux import linked_lists
2527
from sdb.commands.linux.internal import slub_helpers as slub
2628

2729

28-
def list_for_each_spl_kmem_cache() -> Iterable[drgn.Object]:
30+
def for_each_spl_kmem_cache() -> Iterable[drgn.Object]:
2931
yield from drgn_list.list_for_each_entry(
3032
"spl_kmem_cache_t",
3133
sdb.get_object("spl_kmem_cache_list").address_of_(), "skc_list")
3234

3335

3436
def backed_by_linux_cache(cache: drgn.Object) -> bool:
35-
assert cache.type_.type_name() == 'spl_kmem_cache_t *'
37+
assert sdb.type_canonical_name(cache.type_) == 'struct spl_kmem_cache *'
3638
return int(cache.skc_linux_cache.value_()) != 0x0
3739

3840

3941
def slab_name(cache: drgn.Object) -> str:
40-
assert cache.type_.type_name() == 'spl_kmem_cache_t *'
42+
assert sdb.type_canonical_name(cache.type_) == 'struct spl_kmem_cache *'
4143
return str(cache.skc_name.string_().decode('utf-8'))
4244

4345

4446
def nr_slabs(cache: drgn.Object) -> int:
45-
assert cache.type_.type_name() == 'spl_kmem_cache_t *'
47+
assert sdb.type_canonical_name(cache.type_) == 'struct spl_kmem_cache *'
4648
return int(cache.skc_slab_total.value_())
4749

4850

4951
def slab_alloc(cache: drgn.Object) -> int:
50-
assert cache.type_.type_name() == 'spl_kmem_cache_t *'
52+
assert sdb.type_canonical_name(cache.type_) == 'struct spl_kmem_cache *'
5153
return int(cache.skc_slab_alloc.value_())
5254

5355

5456
def slab_size(cache: drgn.Object) -> int:
55-
assert cache.type_.type_name() == 'spl_kmem_cache_t *'
57+
assert sdb.type_canonical_name(cache.type_) == 'struct spl_kmem_cache *'
5658
return int(cache.skc_slab_size.value_())
5759

5860

5961
def slab_linux_cache_source(cache: drgn.Object) -> str:
60-
assert cache.type_.type_name() == 'spl_kmem_cache_t *'
62+
assert sdb.type_canonical_name(cache.type_) == 'struct spl_kmem_cache *'
6163
if not backed_by_linux_cache(cache):
6264
name = slab_name(cache)
6365
subsystem = "SPL"
@@ -67,46 +69,49 @@ def slab_linux_cache_source(cache: drgn.Object) -> str:
6769
return f"{name}[{subsystem:4}]"
6870

6971

70-
def slab_flags(cache: drgn.Object) -> str:
71-
assert cache.type_.type_name() == 'spl_kmem_cache_t *'
72+
def for_each_slab_flag_in_cache(cache: drgn.Object) -> Iterable[str]:
73+
assert sdb.type_canonical_name(cache.type_) == 'struct spl_kmem_cache *'
7274
flag = cache.skc_flags.value_()
73-
flags_detected = []
7475
for enum_entry, enum_entry_bit in cache.prog_.type(
7576
'enum kmc_bit').enumerators:
7677
if flag & (1 << enum_entry_bit):
77-
flags_detected.append(enum_entry.replace('_BIT', ''))
78-
return '|'.join(flags_detected)
78+
yield enum_entry.replace('_BIT', '')
79+
80+
81+
def slab_flags(cache: drgn.Object) -> str:
82+
assert sdb.type_canonical_name(cache.type_) == 'struct spl_kmem_cache *'
83+
return '|'.join(for_each_slab_flag_in_cache(cache))
7984

8085

8186
def object_size(cache: drgn.Object) -> int:
82-
assert cache.type_.type_name() == 'spl_kmem_cache_t *'
87+
assert sdb.type_canonical_name(cache.type_) == 'struct spl_kmem_cache *'
8388
return int(cache.skc_obj_size.value_())
8489

8590

8691
def nr_objects(cache: drgn.Object) -> int:
87-
assert cache.type_.type_name() == 'spl_kmem_cache_t *'
92+
assert sdb.type_canonical_name(cache.type_) == 'struct spl_kmem_cache *'
8893
if backed_by_linux_cache(cache):
8994
return int(cache.skc_obj_alloc.value_())
9095
return int(cache.skc_obj_total.value_())
9196

9297

9398
def obj_alloc(cache: drgn.Object) -> int:
94-
assert cache.type_.type_name() == 'spl_kmem_cache_t *'
99+
assert sdb.type_canonical_name(cache.type_) == 'struct spl_kmem_cache *'
95100
return int(cache.skc_obj_alloc.value_())
96101

97102

98103
def obj_inactive(cache: drgn.Object) -> int:
99-
assert cache.type_.type_name() == 'spl_kmem_cache_t *'
104+
assert sdb.type_canonical_name(cache.type_) == 'struct spl_kmem_cache *'
100105
return nr_objects(cache) - obj_alloc(cache)
101106

102107

103108
def objs_per_slab(cache: drgn.Object) -> int:
104-
assert cache.type_.type_name() == 'spl_kmem_cache_t *'
109+
assert sdb.type_canonical_name(cache.type_) == 'struct spl_kmem_cache *'
105110
return int(cache.skc_slab_objs.value_())
106111

107112

108113
def entry_size(cache: drgn.Object) -> int:
109-
assert cache.type_.type_name() == 'spl_kmem_cache_t *'
114+
assert sdb.type_canonical_name(cache.type_) == 'struct spl_kmem_cache *'
110115
if backed_by_linux_cache(cache):
111116
return slub.entry_size(cache.skc_linux_cache)
112117
ops = objs_per_slab(cache)
@@ -116,20 +121,85 @@ def entry_size(cache: drgn.Object) -> int:
116121

117122

118123
def active_memory(cache: drgn.Object) -> int:
119-
assert cache.type_.type_name() == 'spl_kmem_cache_t *'
124+
assert sdb.type_canonical_name(cache.type_) == 'struct spl_kmem_cache *'
120125
return obj_alloc(cache) * entry_size(cache)
121126

122127

123128
def total_memory(cache: drgn.Object) -> int:
124-
assert cache.type_.type_name() == 'spl_kmem_cache_t *'
129+
assert sdb.type_canonical_name(cache.type_) == 'struct spl_kmem_cache *'
125130
if backed_by_linux_cache(cache):
126131
return slub.total_memory(cache.skc_linux_cache)
127132
return slab_size(cache) * nr_slabs(cache)
128133

129134

130135
def util(cache: drgn.Object) -> int:
131-
assert cache.type_.type_name() == 'spl_kmem_cache_t *'
136+
assert sdb.type_canonical_name(cache.type_) == 'struct spl_kmem_cache *'
132137
total_mem = total_memory(cache)
133138
if total_mem == 0:
134139
return 0
135140
return int((active_memory(cache) / total_mem) * 100)
141+
142+
143+
def sko_from_obj(cache: drgn.Object, obj: drgn.Object) -> drgn.Object:
144+
assert sdb.type_canonical_name(cache.type_) == 'struct spl_kmem_cache *'
145+
cache_obj_align = cache.skc_obj_align.value_()
146+
return sdb.create_object(
147+
'spl_kmem_obj_t *',
148+
obj.value_() + p2.p2roundup(object_size(cache), cache_obj_align))
149+
150+
151+
def spl_aligned_obj_size(cache: drgn.Object) -> int:
152+
assert sdb.type_canonical_name(cache.type_) == 'struct spl_kmem_cache *'
153+
cache_obj_align = cache.skc_obj_align.value_()
154+
spl_obj_type_size = sdb.type_canonicalize_size('spl_kmem_obj_t')
155+
return p2.p2roundup(object_size(cache), cache_obj_align) + p2.p2roundup(
156+
spl_obj_type_size, cache_obj_align)
157+
158+
159+
def spl_aligned_slab_size(cache: drgn.Object) -> int:
160+
assert sdb.type_canonical_name(cache.type_) == 'struct spl_kmem_cache *'
161+
cache_obj_align = cache.skc_obj_align.value_()
162+
spl_slab_type_size = sdb.type_canonicalize_size('spl_kmem_slab_t')
163+
return p2.p2roundup(spl_slab_type_size, cache_obj_align)
164+
165+
166+
def for_each_onslab_object_in_slab(slab: drgn.Object) -> Iterable[drgn.Object]:
167+
assert sdb.type_canonical_name(slab.type_) == 'struct spl_kmem_slab *'
168+
cache = slab.sks_cache
169+
sks_size = spl_aligned_slab_size(cache)
170+
spl_obj_size = spl_aligned_obj_size(cache)
171+
172+
for i in range(slab.sks_objs.value_()):
173+
obj = sdb.create_object('void *',
174+
slab.value_() + sks_size + (i * spl_obj_size))
175+
#
176+
# If the sko_list of the object is empty, it means that
177+
# this object is not part of the slab's internal free list
178+
# and therefore it is allocated. NOTE: sko_list in the
179+
# actual code is not a list, but a link on a list. Thus,
180+
# the check below is not checking whether the "object
181+
# list" is empty for this slab, but rather whether the
182+
# link is part of any list.
183+
#
184+
sko = sko_from_obj(cache, obj)
185+
assert sko.sko_magic.value_() == 0x20202020 # SKO_MAGIC
186+
if linked_lists.is_list_empty(sko.sko_list):
187+
yield obj
188+
189+
190+
def for_each_object_in_spl_cache(cache: drgn.Object) -> Iterable[drgn.Object]:
191+
assert sdb.type_canonical_name(cache.type_) == 'struct spl_kmem_cache *'
192+
#
193+
# ZFSonLinux initially implemented OFFSLAB caches for certain cases
194+
# that never showed up and thus have never been used in practice.
195+
# Ensure here that we are not looking at such a cache.
196+
#
197+
if 'KMC_OFFSLAB' in list(for_each_slab_flag_in_cache(cache)):
198+
raise sdb.CommandError("spl_caches",
199+
"KMC_OFFSLAB caches are not supported")
200+
201+
for slab_list in [cache.skc_complete_list, cache.skc_partial_list]:
202+
for slab in drgn_list.list_for_each_entry("spl_kmem_slab_t",
203+
slab_list.address_of_(),
204+
"sks_list"):
205+
yield from for_each_onslab_object_in_slab(slab)

sdb/commands/spl/spl_kmem_caches.py

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import sdb
2626
from sdb.commands.internal.fmt import size_nicenum
2727
from sdb.commands.internal.table import Table
28+
from sdb.commands.linux.internal import slub_helpers as slub
2829
from sdb.commands.spl.internal import kmem_helpers as kmem
2930

3031

@@ -97,12 +98,12 @@ def no_input(self) -> Iterable[drgn.Object]:
9798
raise sdb.CommandInvalidInputError(
9899
self.name, f"'{self.args.s}' is not a valid field")
99100
yield from sorted(
100-
kmem.list_for_each_spl_kmem_cache(),
101+
kmem.for_each_spl_kmem_cache(),
101102
key=SplKmemCaches.FIELDS[self.args.s],
102103
reverse=(self.args.s not in
103104
SplKmemCaches.DEFAULT_INCREASING_ORDER_FIELDS))
104105
else:
105-
yield from kmem.list_for_each_spl_kmem_cache()
106+
yield from kmem.for_each_spl_kmem_cache()
106107

107108
FIELDS = {
108109
"address": lambda obj: hex(obj.value_()),
@@ -203,3 +204,35 @@ def pretty_print(self, objs: Iterable[drgn.Object]) -> None:
203204
table.add_row(row_dict[sort_field], row_dict)
204205
table.print_(print_headers=self.args.H,
205206
reverse_sort=(sort_field not in ["name", "address"]))
207+
208+
209+
class SplKmemCacheWalker(sdb.Walker):
210+
"""
211+
Walk through all allocated entries of an spl_kmem_cache.
212+
213+
DESCRIPTION
214+
Walk through all allocated entries of an spl_kmem_cache. If
215+
the cache is backed by a SLUB cache then iteration will be
216+
delegated to the appropriate walker (keep in mind that in
217+
this case not all objects may be part of the actual SPL
218+
cache due to the SLUB allocator in Linux merging objects).
219+
220+
EXAMPLES
221+
Print all the objects in the ddt_cache:
222+
223+
sdb> spl_kmem_caches | filter obj.skc_name == "ddt_cache" | spl_cache
224+
(void *)0xffffa08937e80040
225+
(void *)0xffffa08937e86180
226+
(void *)0xffffa08937e8c2c0
227+
(void *)0xffffa08937e92400
228+
...
229+
"""
230+
231+
names = ["spl_cache"]
232+
input_type = "spl_kmem_cache_t *"
233+
234+
def walk(self, obj: drgn.Object) -> Iterable[drgn.Object]:
235+
if kmem.backed_by_linux_cache(obj):
236+
yield from slub.for_each_object_in_cache(obj.skc_linux_cache)
237+
else:
238+
yield from kmem.for_each_object_in_spl_cache(obj)

sdb/target.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ def type_canonical_name(t: drgn.Type) -> str:
117117
"""
118118
Return the "canonical name" of this type. See type_canonicalize().
119119
"""
120-
return type_canonicalize(t).type_name()
120+
return str(type_canonicalize(t).type_name())
121121

122122

123123
def type_canonicalize_name(type_name: str) -> str:
@@ -137,7 +137,7 @@ def type_canonicalize_size(t: Union[drgn.Type, str]) -> int:
137137
else:
138138
assert isinstance(t, drgn.Type)
139139
type_ = t
140-
return type_canonicalize(type_).size
140+
return int(type_canonicalize(type_).size)
141141

142142

143143
def type_equals(a: drgn.Type, b: drgn.Type) -> bool:

0 commit comments

Comments
 (0)