Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

perf dsos: Switch backing storage to array from rbtree/list

DSOs were held on a list for fast iteration and in an rbtree for fast
finds.

Switch to using a lazily sorted array where iteration is just iterating
through the array and binary searches are the same complexity as
searching the rbtree.

The find may need to sort the array first which does increase the
complexity, but add operations have lower complexity and overall the
complexity should remain about the same.

The set name operations on the dso just records that the array is no
longer sorted, avoiding complexity in rebalancing the rbtree.

Tighter locking discipline is enforced to avoid the array being resorted
while long and short names or ids are changed.

The array is smaller in size, replacing 6 pointers with 2, and so even
with extra allocated space in the array, the array may be 50%
unoccupied, the memory saving should be at least 2x.

Committer testing:

On a previous version of this patchset we were getting a lot of warnings
about deleting a DSO still on a list, now it is ok:

root@x1:~# perf probe -l
root@x1:~# perf probe finish_task_switch
Added new event:
probe:finish_task_switch (on finish_task_switch)

You can now use it in all perf tools, such as:

perf record -e probe:finish_task_switch -aR sleep 1

root@x1:~# perf probe -l
probe:finish_task_switch (on finish_task_switch@kernel/sched/core.c)
root@x1:~# perf trace -e probe:finish_task_switch/max-stack=8/ --max-events=1
0.000 migration/0/19 probe:finish_task_switch(__probe_ip: -1894408688)
finish_task_switch.isra.0 ([kernel.kallsyms])
__schedule ([kernel.kallsyms])
schedule ([kernel.kallsyms])
smpboot_thread_fn ([kernel.kallsyms])
kthread ([kernel.kallsyms])
ret_from_fork ([kernel.kallsyms])
ret_from_fork_asm ([kernel.kallsyms])
root@x1:~#
root@x1:~# perf probe -d probe:*
Removed event: probe:finish_task_switch
root@x1:~# perf probe -l
root@x1:~#

I also ran the full 'perf test' suite after applying this one, no
regressions.

Signed-off-by: Ian Rogers <irogers@google.com>
Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: Ahelenia Ziemiańska <nabijaczleweli@nabijaczleweli.xyz>
Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Cc: Andi Kleen <ak@linux.intel.com>
Cc: Athira Rajeev <atrajeev@linux.vnet.ibm.com>
Cc: Ben Gainey <ben.gainey@arm.com>
Cc: Changbin Du <changbin.du@huawei.com>
Cc: Chengen Du <chengen.du@canonical.com>
Cc: Colin Ian King <colin.i.king@gmail.com>
Cc: Dima Kogan <dima@secretsauce.net>
Cc: Ilkka Koskinen <ilkka@os.amperecomputing.com>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: James Clark <james.clark@arm.com>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: K Prateek Nayak <kprateek.nayak@amd.com>
Cc: Kan Liang <kan.liang@linux.intel.com>
Cc: Leo Yan <leo.yan@linux.dev>
Cc: Li Dong <lidong@vivo.com>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Masami Hiramatsu <mhiramat@kernel.org>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Paran Lee <p4ranlee@gmail.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Song Liu <song@kernel.org>
Cc: Sun Haiyong <sunhaiyong@loongson.cn>
Cc: Thomas Richter <tmricht@linux.ibm.com>
Cc: Tiezhu Yang <yangtiezhu@loongson.cn>
Cc: Yanteng Si <siyanteng@loongson.cn>
Cc: zhaimingbing <zhaimingbing@cmss.chinamobile.com>
Link: https://lore.kernel.org/r/20240504213803.218974-2-irogers@google.com
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>

authored by

Ian Rogers and committed by
Arnaldo Carvalho de Melo
3f4ac23a 77a70f80

+179 -111
+43 -26
tools/perf/util/dso.c
··· 1241 1241 return dso; 1242 1242 } 1243 1243 1244 - static void dso__set_long_name_id(struct dso *dso, const char *name, struct dso_id *id, bool name_allocated) 1244 + static void dso__set_long_name_id(struct dso *dso, const char *name, bool name_allocated) 1245 1245 { 1246 - struct rb_root *root = dso->root; 1246 + struct dsos *dsos = dso->dsos; 1247 1247 1248 1248 if (name == NULL) 1249 1249 return; 1250 1250 1251 + if (dsos) { 1252 + /* 1253 + * Need to avoid re-sorting the dsos breaking by non-atomically 1254 + * renaming the dso. 1255 + */ 1256 + down_write(&dsos->lock); 1257 + } 1258 + 1251 1259 if (dso->long_name_allocated) 1252 1260 free((char *)dso->long_name); 1253 - 1254 - if (root) { 1255 - rb_erase(&dso->rb_node, root); 1256 - /* 1257 - * __dsos__findnew_link_by_longname_id() isn't guaranteed to 1258 - * add it back, so a clean removal is required here. 1259 - */ 1260 - RB_CLEAR_NODE(&dso->rb_node); 1261 - dso->root = NULL; 1262 - } 1263 1261 1264 1262 dso->long_name = name; 1265 1263 dso->long_name_len = strlen(name); 1266 1264 dso->long_name_allocated = name_allocated; 1267 1265 1268 - if (root) 1269 - __dsos__findnew_link_by_longname_id(root, dso, NULL, id); 1266 + if (dsos) { 1267 + dsos->sorted = false; 1268 + up_write(&dsos->lock); 1269 + } 1270 1270 } 1271 1271 1272 - static int __dso_id__cmp(struct dso_id *a, struct dso_id *b) 1272 + static int __dso_id__cmp(const struct dso_id *a, const struct dso_id *b) 1273 1273 { 1274 1274 if (a->maj > b->maj) return -1; 1275 1275 if (a->maj < b->maj) return 1; ··· 1297 1297 return 0; 1298 1298 } 1299 1299 1300 - bool dso_id__empty(struct dso_id *id) 1300 + bool dso_id__empty(const struct dso_id *id) 1301 1301 { 1302 1302 if (!id) 1303 1303 return true; ··· 1305 1305 return !id->maj && !id->min && !id->ino && !id->ino_generation; 1306 1306 } 1307 1307 1308 - void dso__inject_id(struct dso *dso, struct dso_id *id) 1308 + void __dso__inject_id(struct dso *dso, struct dso_id *id) 1309 1309 { 1310 + struct dsos *dsos = dso->dsos; 1311 + 1312 + /* dsos write lock held by caller. */ 1313 + 1310 1314 dso->id.maj = id->maj; 1311 1315 dso->id.min = id->min; 1312 1316 dso->id.ino = id->ino; 1313 1317 dso->id.ino_generation = id->ino_generation; 1318 + 1319 + if (dsos) 1320 + dsos->sorted = false; 1314 1321 } 1315 1322 1316 - int dso_id__cmp(struct dso_id *a, struct dso_id *b) 1323 + int dso_id__cmp(const struct dso_id *a, const struct dso_id *b) 1317 1324 { 1318 1325 /* 1319 1326 * The second is always dso->id, so zeroes if not set, assume passing ··· 1339 1332 1340 1333 void dso__set_long_name(struct dso *dso, const char *name, bool name_allocated) 1341 1334 { 1342 - dso__set_long_name_id(dso, name, NULL, name_allocated); 1335 + dso__set_long_name_id(dso, name, name_allocated); 1343 1336 } 1344 1337 1345 1338 void dso__set_short_name(struct dso *dso, const char *name, bool name_allocated) 1346 1339 { 1340 + struct dsos *dsos = dso->dsos; 1341 + 1347 1342 if (name == NULL) 1348 1343 return; 1349 1344 1345 + if (dsos) { 1346 + /* 1347 + * Need to avoid re-sorting the dsos breaking by non-atomically 1348 + * renaming the dso. 1349 + */ 1350 + down_write(&dsos->lock); 1351 + } 1350 1352 if (dso->short_name_allocated) 1351 1353 free((char *)dso->short_name); 1352 1354 1353 1355 dso->short_name = name; 1354 1356 dso->short_name_len = strlen(name); 1355 1357 dso->short_name_allocated = name_allocated; 1358 + 1359 + if (dsos) { 1360 + dsos->sorted = false; 1361 + up_write(&dsos->lock); 1362 + } 1356 1363 } 1357 1364 1358 1365 int dso__name_len(const struct dso *dso) ··· 1402 1381 strcpy(dso->name, name); 1403 1382 if (id) 1404 1383 dso->id = *id; 1405 - dso__set_long_name_id(dso, dso->name, id, false); 1384 + dso__set_long_name_id(dso, dso->name, false); 1406 1385 dso__set_short_name(dso, dso->name, false); 1407 1386 dso->symbols = RB_ROOT_CACHED; 1408 1387 dso->symbol_names = NULL; ··· 1427 1406 dso->is_kmod = 0; 1428 1407 dso->needs_swap = DSO_SWAP__UNSET; 1429 1408 dso->comp = COMP_ID__NONE; 1430 - RB_CLEAR_NODE(&dso->rb_node); 1431 - dso->root = NULL; 1432 - INIT_LIST_HEAD(&dso->node); 1433 1409 INIT_LIST_HEAD(&dso->data.open_entry); 1434 1410 mutex_init(&dso->lock); 1435 1411 refcount_set(&dso->refcnt, 1); ··· 1442 1424 1443 1425 void dso__delete(struct dso *dso) 1444 1426 { 1445 - if (!RB_EMPTY_NODE(&dso->rb_node)) 1446 - pr_err("DSO %s is still in rbtree when being deleted!\n", 1447 - dso->long_name); 1427 + if (dso->dsos) 1428 + pr_err("DSO %s is still in rbtree when being deleted!\n", dso->long_name); 1448 1429 1449 1430 /* free inlines first, as they reference symbols */ 1450 1431 inlines__tree_delete(&dso->inlined_nodes);
+4 -6
tools/perf/util/dso.h
··· 146 146 147 147 struct dso { 148 148 struct mutex lock; 149 - struct list_head node; 150 - struct rb_node rb_node; /* rbtree node sorted by long name */ 151 - struct rb_root *root; /* root of rbtree that rb_node is in */ 149 + struct dsos *dsos; 152 150 struct rb_root_cached symbols; 153 151 struct symbol **symbol_names; 154 152 size_t symbol_names_len; ··· 236 238 dso->loaded = true; 237 239 } 238 240 239 - int dso_id__cmp(struct dso_id *a, struct dso_id *b); 240 - bool dso_id__empty(struct dso_id *id); 241 + int dso_id__cmp(const struct dso_id *a, const struct dso_id *b); 242 + bool dso_id__empty(const struct dso_id *id); 241 243 242 244 struct dso *dso__new_id(const char *name, struct dso_id *id); 243 245 struct dso *dso__new(const char *name); ··· 246 248 int dso__cmp_id(struct dso *a, struct dso *b); 247 249 void dso__set_short_name(struct dso *dso, const char *name, bool name_allocated); 248 250 void dso__set_long_name(struct dso *dso, const char *name, bool name_allocated); 249 - void dso__inject_id(struct dso *dso, struct dso_id *id); 251 + void __dso__inject_id(struct dso *dso, struct dso_id *id); 250 252 251 253 int dso__name_len(const struct dso *dso); 252 254
+119 -71
tools/perf/util/dsos.c
··· 14 14 15 15 void dsos__init(struct dsos *dsos) 16 16 { 17 - INIT_LIST_HEAD(&dsos->head); 18 - dsos->root = RB_ROOT; 19 17 init_rwsem(&dsos->lock); 18 + 19 + dsos->cnt = 0; 20 + dsos->allocated = 0; 21 + dsos->dsos = NULL; 22 + dsos->sorted = true; 20 23 } 21 24 22 25 static void dsos__purge(struct dsos *dsos) 23 26 { 24 - struct dso *pos, *n; 25 - 26 27 down_write(&dsos->lock); 27 28 28 - list_for_each_entry_safe(pos, n, &dsos->head, node) { 29 - RB_CLEAR_NODE(&pos->rb_node); 30 - pos->root = NULL; 31 - list_del_init(&pos->node); 32 - dso__put(pos); 29 + for (unsigned int i = 0; i < dsos->cnt; i++) { 30 + struct dso *dso = dsos->dsos[i]; 31 + 32 + dso->dsos = NULL; 33 + dso__put(dso); 33 34 } 35 + 36 + zfree(&dsos->dsos); 37 + dsos->cnt = 0; 38 + dsos->allocated = 0; 39 + dsos->sorted = true; 34 40 35 41 up_write(&dsos->lock); 36 42 } ··· 52 46 int (*cb)(struct dso *dso, void *data), 53 47 void *data) 54 48 { 55 - struct dso *dso; 56 - 57 - list_for_each_entry(dso, &dsos->head, node) { 49 + for (unsigned int i = 0; i < dsos->cnt; i++) { 50 + struct dso *dso = dsos->dsos[i]; 58 51 int err; 59 52 60 53 err = cb(dso, data); ··· 124 119 return __dso__cmp_short_name(a->short_name, &a->id, b); 125 120 } 126 121 122 + static int dsos__cmp_long_name_id_short_name(const void *va, const void *vb) 123 + { 124 + const struct dso *a = *((const struct dso **)va); 125 + const struct dso *b = *((const struct dso **)vb); 126 + int rc = strcmp(a->long_name, b->long_name); 127 + 128 + if (!rc) { 129 + rc = dso_id__cmp(&a->id, &b->id); 130 + if (!rc) 131 + rc = strcmp(a->short_name, b->short_name); 132 + } 133 + return rc; 134 + } 135 + 127 136 /* 128 137 * Find a matching entry and/or link current entry to RB tree. 129 138 * Either one of the dso or name parameter must be non-NULL or the 130 139 * function will not work. 131 140 */ 132 - struct dso *__dsos__findnew_link_by_longname_id(struct rb_root *root, struct dso *dso, 133 - const char *name, struct dso_id *id) 141 + struct dso *__dsos__findnew_link_by_longname_id(struct dsos *dsos, 142 + struct dso *dso, 143 + const char *name, 144 + struct dso_id *id, 145 + bool write_locked) 134 146 { 135 - struct rb_node **p = &root->rb_node; 136 - struct rb_node *parent = NULL; 147 + int low = 0, high = dsos->cnt - 1; 148 + 149 + if (!dsos->sorted) { 150 + if (!write_locked) { 151 + up_read(&dsos->lock); 152 + down_write(&dsos->lock); 153 + dso = __dsos__findnew_link_by_longname_id(dsos, dso, name, id, 154 + /*write_locked=*/true); 155 + up_write(&dsos->lock); 156 + down_read(&dsos->lock); 157 + return dso; 158 + } 159 + qsort(dsos->dsos, dsos->cnt, sizeof(struct dso *), 160 + dsos__cmp_long_name_id_short_name); 161 + dsos->sorted = true; 162 + } 137 163 138 164 if (!name) 139 165 name = dso->long_name; ··· 172 136 /* 173 137 * Find node with the matching name 174 138 */ 175 - while (*p) { 176 - struct dso *this = rb_entry(*p, struct dso, rb_node); 139 + while (low <= high) { 140 + int mid = (low + high) / 2; 141 + struct dso *this = dsos->dsos[mid]; 177 142 int rc = __dso__cmp_long_name(name, id, this); 178 143 179 - parent = *p; 180 144 if (rc == 0) { 181 145 /* 182 146 * In case the new DSO is a duplicate of an existing ··· 197 161 } 198 162 } 199 163 if (rc < 0) 200 - p = &parent->rb_left; 164 + high = mid - 1; 201 165 else 202 - p = &parent->rb_right; 166 + low = mid + 1; 203 167 } 204 - if (dso) { 205 - /* Add new node and rebalance tree */ 206 - rb_link_node(&dso->rb_node, parent, p); 207 - rb_insert_color(&dso->rb_node, root); 208 - dso->root = root; 209 - } 168 + if (dso) 169 + __dsos__add(dsos, dso); 210 170 return NULL; 211 171 } 212 172 213 - void __dsos__add(struct dsos *dsos, struct dso *dso) 173 + int __dsos__add(struct dsos *dsos, struct dso *dso) 214 174 { 215 - list_add_tail(&dso->node, &dsos->head); 216 - __dsos__findnew_link_by_longname_id(&dsos->root, dso, NULL, &dso->id); 217 - /* 218 - * It is now in the linked list, grab a reference, then garbage collect 219 - * this when needing memory, by looking at LRU dso instances in the 220 - * list with atomic_read(&dso->refcnt) == 1, i.e. no references 221 - * anywhere besides the one for the list, do, under a lock for the 222 - * list: remove it from the list, then a dso__put(), that probably will 223 - * be the last and will then call dso__delete(), end of life. 224 - * 225 - * That, or at the end of the 'struct machine' lifetime, when all 226 - * 'struct dso' instances will be removed from the list, in 227 - * dsos__exit(), if they have no other reference from some other data 228 - * structure. 229 - * 230 - * E.g.: after processing a 'perf.data' file and storing references 231 - * to objects instantiated while processing events, we will have 232 - * references to the 'thread', 'map', 'dso' structs all from 'struct 233 - * hist_entry' instances, but we may not need anything not referenced, 234 - * so we might as well call machines__exit()/machines__delete() and 235 - * garbage collect it. 236 - */ 237 - dso__get(dso); 175 + if (dsos->cnt == dsos->allocated) { 176 + unsigned int to_allocate = 2; 177 + struct dso **temp; 178 + 179 + if (dsos->allocated > 0) 180 + to_allocate = dsos->allocated * 2; 181 + temp = realloc(dsos->dsos, sizeof(struct dso *) * to_allocate); 182 + if (!temp) 183 + return -ENOMEM; 184 + dsos->dsos = temp; 185 + dsos->allocated = to_allocate; 186 + } 187 + dsos->dsos[dsos->cnt++] = dso__get(dso); 188 + if (dsos->cnt >= 2 && dsos->sorted) { 189 + dsos->sorted = dsos__cmp_long_name_id_short_name(&dsos->dsos[dsos->cnt - 2], 190 + &dsos->dsos[dsos->cnt - 1]) 191 + <= 0; 192 + } 193 + dso->dsos = dsos; 194 + return 0; 238 195 } 239 196 240 - void dsos__add(struct dsos *dsos, struct dso *dso) 197 + int dsos__add(struct dsos *dsos, struct dso *dso) 241 198 { 199 + int ret; 200 + 242 201 down_write(&dsos->lock); 243 - __dsos__add(dsos, dso); 202 + ret = __dsos__add(dsos, dso); 244 203 up_write(&dsos->lock); 204 + return ret; 245 205 } 246 206 247 - static struct dso *__dsos__findnew_by_longname_id(struct rb_root *root, const char *name, struct dso_id *id) 207 + static struct dso *__dsos__findnew_by_longname_id(struct dsos *dsos, const char *name, 208 + struct dso_id *id, bool write_locked) 248 209 { 249 - return __dsos__findnew_link_by_longname_id(root, NULL, name, id); 210 + return __dsos__findnew_link_by_longname_id(dsos, NULL, name, id, write_locked); 250 211 } 251 212 252 213 struct dsos__find_id_cb_args { ··· 264 231 265 232 } 266 233 267 - static struct dso *__dsos__find_id(struct dsos *dsos, const char *name, struct dso_id *id, bool cmp_short) 234 + static struct dso *__dsos__find_id(struct dsos *dsos, const char *name, struct dso_id *id, 235 + bool cmp_short, bool write_locked) 268 236 { 269 237 struct dso *res; 270 238 ··· 279 245 __dsos__for_each_dso(dsos, dsos__find_id_cb, &args); 280 246 return args.res; 281 247 } 282 - res = __dsos__findnew_by_longname_id(&dsos->root, name, id); 248 + res = __dsos__findnew_by_longname_id(dsos, name, id, write_locked); 283 249 return res; 284 250 } 285 251 ··· 288 254 struct dso *res; 289 255 290 256 down_read(&dsos->lock); 291 - res = __dsos__find_id(dsos, name, NULL, cmp_short); 257 + res = __dsos__find_id(dsos, name, NULL, cmp_short, /*write_locked=*/false); 292 258 up_read(&dsos->lock); 293 259 return res; 294 260 } ··· 330 296 struct dso *dso = dso__new_id(name, id); 331 297 332 298 if (dso != NULL) { 333 - __dsos__add(dsos, dso); 299 + /* 300 + * The dsos lock is held on entry, so rename the dso before 301 + * adding it to avoid needing to take the dsos lock again to say 302 + * the array isn't sorted. 303 + */ 334 304 dso__set_basename(dso); 305 + __dsos__add(dsos, dso); 335 306 } 336 307 return dso; 337 308 } ··· 348 309 349 310 static struct dso *__dsos__findnew_id(struct dsos *dsos, const char *name, struct dso_id *id) 350 311 { 351 - struct dso *dso = __dsos__find_id(dsos, name, id, false); 312 + struct dso *dso = __dsos__find_id(dsos, name, id, false, /*write_locked=*/true); 352 313 353 314 if (dso && dso_id__empty(&dso->id) && !dso_id__empty(id)) 354 - dso__inject_id(dso, id); 315 + __dso__inject_id(dso, id); 355 316 356 317 return dso ? dso : __dsos__addnew_id(dsos, name, id); 357 318 } ··· 442 403 443 404 down_write(&dsos->lock); 444 405 445 - dso = __dsos__find_id(dsos, m->name, NULL, /*cmp_short=*/true); 446 - if (!dso) { 447 - dso = __dsos__addnew(dsos, m->name); 448 - if (dso == NULL) 449 - goto out_unlock; 450 - 451 - dso__set_module_info(dso, m, machine); 452 - dso__set_long_name(dso, strdup(filename), true); 453 - dso->kernel = DSO_SPACE__KERNEL; 406 + dso = __dsos__find_id(dsos, m->name, NULL, /*cmp_short=*/true, /*write_locked=*/true); 407 + if (dso) { 408 + up_write(&dsos->lock); 409 + return dso; 454 410 } 411 + /* 412 + * Failed to find the dso so create it. Change the name before adding it 413 + * to the array, to avoid unnecessary sorts and potential locking 414 + * issues. 415 + */ 416 + dso = dso__new_id(m->name, /*id=*/NULL); 417 + if (!dso) { 418 + up_write(&dsos->lock); 419 + return NULL; 420 + } 421 + dso__set_basename(dso); 422 + dso__set_module_info(dso, m, machine); 423 + dso__set_long_name(dso, strdup(filename), true); 424 + dso->kernel = DSO_SPACE__KERNEL; 425 + __dsos__add(dsos, dso); 455 426 456 - out_unlock: 457 427 up_write(&dsos->lock); 458 428 return dso; 459 429 }
+13 -8
tools/perf/util/dsos.h
··· 14 14 struct machine; 15 15 16 16 /* 17 - * DSOs are put into both a list for fast iteration and rbtree for fast 18 - * long name lookup. 17 + * Collection of DSOs as an array for iteration speed, but sorted for O(n) 18 + * lookup. 19 19 */ 20 20 struct dsos { 21 - struct list_head head; 22 - struct rb_root root; /* rbtree root sorted by long name */ 23 21 struct rw_semaphore lock; 22 + struct dso **dsos; 23 + unsigned int cnt; 24 + unsigned int allocated; 25 + bool sorted; 24 26 }; 25 27 26 28 void dsos__init(struct dsos *dsos); 27 29 void dsos__exit(struct dsos *dsos); 28 30 29 - void __dsos__add(struct dsos *dsos, struct dso *dso); 30 - void dsos__add(struct dsos *dsos, struct dso *dso); 31 + int __dsos__add(struct dsos *dsos, struct dso *dso); 32 + int dsos__add(struct dsos *dsos, struct dso *dso); 31 33 struct dso *__dsos__addnew(struct dsos *dsos, const char *name); 32 34 struct dso *dsos__find(struct dsos *dsos, const char *name, bool cmp_short); 33 35 ··· 37 35 38 36 bool dsos__read_build_ids(struct dsos *dsos, bool with_hits); 39 37 40 - struct dso *__dsos__findnew_link_by_longname_id(struct rb_root *root, struct dso *dso, 41 - const char *name, struct dso_id *id); 38 + struct dso *__dsos__findnew_link_by_longname_id(struct dsos *dsos, 39 + struct dso *dso, 40 + const char *name, 41 + struct dso_id *id, 42 + bool write_locked); 42 43 43 44 size_t dsos__fprintf_buildid(struct dsos *dsos, FILE *fp, 44 45 bool (skip)(struct dso *dso, int parm), int parm);