diff options
Diffstat (limited to 'tools/perf/util/hist.c')
-rw-r--r-- | tools/perf/util/hist.c | 613 |
1 files changed, 501 insertions, 112 deletions
diff --git a/tools/perf/util/hist.c b/tools/perf/util/hist.c index 2be33c7dbf03..9a71c94f057a 100644 --- a/tools/perf/util/hist.c +++ b/tools/perf/util/hist.c | |||
@@ -1,3 +1,4 @@ | |||
1 | #include "util.h" | ||
1 | #include "hist.h" | 2 | #include "hist.h" |
2 | #include "session.h" | 3 | #include "session.h" |
3 | #include "sort.h" | 4 | #include "sort.h" |
@@ -8,25 +9,69 @@ struct callchain_param callchain_param = { | |||
8 | .min_percent = 0.5 | 9 | .min_percent = 0.5 |
9 | }; | 10 | }; |
10 | 11 | ||
12 | static void hist_entry__add_cpumode_period(struct hist_entry *self, | ||
13 | unsigned int cpumode, u64 period) | ||
14 | { | ||
15 | switch (cpumode) { | ||
16 | case PERF_RECORD_MISC_KERNEL: | ||
17 | self->period_sys += period; | ||
18 | break; | ||
19 | case PERF_RECORD_MISC_USER: | ||
20 | self->period_us += period; | ||
21 | break; | ||
22 | case PERF_RECORD_MISC_GUEST_KERNEL: | ||
23 | self->period_guest_sys += period; | ||
24 | break; | ||
25 | case PERF_RECORD_MISC_GUEST_USER: | ||
26 | self->period_guest_us += period; | ||
27 | break; | ||
28 | default: | ||
29 | break; | ||
30 | } | ||
31 | } | ||
32 | |||
11 | /* | 33 | /* |
12 | * histogram, sorted on item, collects counts | 34 | * histogram, sorted on item, collects periods |
13 | */ | 35 | */ |
14 | 36 | ||
15 | struct hist_entry *__perf_session__add_hist_entry(struct rb_root *hists, | 37 | static struct hist_entry *hist_entry__new(struct hist_entry *template) |
16 | struct addr_location *al, | 38 | { |
17 | struct symbol *sym_parent, | 39 | size_t callchain_size = symbol_conf.use_callchain ? sizeof(struct callchain_node) : 0; |
18 | u64 count, bool *hit) | 40 | struct hist_entry *self = malloc(sizeof(*self) + callchain_size); |
41 | |||
42 | if (self != NULL) { | ||
43 | *self = *template; | ||
44 | self->nr_events = 1; | ||
45 | if (symbol_conf.use_callchain) | ||
46 | callchain_init(self->callchain); | ||
47 | } | ||
48 | |||
49 | return self; | ||
50 | } | ||
51 | |||
52 | static void hists__inc_nr_entries(struct hists *self, struct hist_entry *entry) | ||
19 | { | 53 | { |
20 | struct rb_node **p = &hists->rb_node; | 54 | if (entry->ms.sym && self->max_sym_namelen < entry->ms.sym->namelen) |
55 | self->max_sym_namelen = entry->ms.sym->namelen; | ||
56 | ++self->nr_entries; | ||
57 | } | ||
58 | |||
59 | struct hist_entry *__hists__add_entry(struct hists *self, | ||
60 | struct addr_location *al, | ||
61 | struct symbol *sym_parent, u64 period) | ||
62 | { | ||
63 | struct rb_node **p = &self->entries.rb_node; | ||
21 | struct rb_node *parent = NULL; | 64 | struct rb_node *parent = NULL; |
22 | struct hist_entry *he; | 65 | struct hist_entry *he; |
23 | struct hist_entry entry = { | 66 | struct hist_entry entry = { |
24 | .thread = al->thread, | 67 | .thread = al->thread, |
25 | .map = al->map, | 68 | .ms = { |
26 | .sym = al->sym, | 69 | .map = al->map, |
70 | .sym = al->sym, | ||
71 | }, | ||
27 | .ip = al->addr, | 72 | .ip = al->addr, |
28 | .level = al->level, | 73 | .level = al->level, |
29 | .count = count, | 74 | .period = period, |
30 | .parent = sym_parent, | 75 | .parent = sym_parent, |
31 | }; | 76 | }; |
32 | int cmp; | 77 | int cmp; |
@@ -38,8 +83,9 @@ struct hist_entry *__perf_session__add_hist_entry(struct rb_root *hists, | |||
38 | cmp = hist_entry__cmp(&entry, he); | 83 | cmp = hist_entry__cmp(&entry, he); |
39 | 84 | ||
40 | if (!cmp) { | 85 | if (!cmp) { |
41 | *hit = true; | 86 | he->period += period; |
42 | return he; | 87 | ++he->nr_events; |
88 | goto out; | ||
43 | } | 89 | } |
44 | 90 | ||
45 | if (cmp < 0) | 91 | if (cmp < 0) |
@@ -48,13 +94,14 @@ struct hist_entry *__perf_session__add_hist_entry(struct rb_root *hists, | |||
48 | p = &(*p)->rb_right; | 94 | p = &(*p)->rb_right; |
49 | } | 95 | } |
50 | 96 | ||
51 | he = malloc(sizeof(*he)); | 97 | he = hist_entry__new(&entry); |
52 | if (!he) | 98 | if (!he) |
53 | return NULL; | 99 | return NULL; |
54 | *he = entry; | ||
55 | rb_link_node(&he->rb_node, parent, p); | 100 | rb_link_node(&he->rb_node, parent, p); |
56 | rb_insert_color(&he->rb_node, hists); | 101 | rb_insert_color(&he->rb_node, &self->entries); |
57 | *hit = false; | 102 | hists__inc_nr_entries(self, he); |
103 | out: | ||
104 | hist_entry__add_cpumode_period(he, al->cpumode, period); | ||
58 | return he; | 105 | return he; |
59 | } | 106 | } |
60 | 107 | ||
@@ -65,7 +112,7 @@ hist_entry__cmp(struct hist_entry *left, struct hist_entry *right) | |||
65 | int64_t cmp = 0; | 112 | int64_t cmp = 0; |
66 | 113 | ||
67 | list_for_each_entry(se, &hist_entry__sort_list, list) { | 114 | list_for_each_entry(se, &hist_entry__sort_list, list) { |
68 | cmp = se->cmp(left, right); | 115 | cmp = se->se_cmp(left, right); |
69 | if (cmp) | 116 | if (cmp) |
70 | break; | 117 | break; |
71 | } | 118 | } |
@@ -82,7 +129,7 @@ hist_entry__collapse(struct hist_entry *left, struct hist_entry *right) | |||
82 | list_for_each_entry(se, &hist_entry__sort_list, list) { | 129 | list_for_each_entry(se, &hist_entry__sort_list, list) { |
83 | int64_t (*f)(struct hist_entry *, struct hist_entry *); | 130 | int64_t (*f)(struct hist_entry *, struct hist_entry *); |
84 | 131 | ||
85 | f = se->collapse ?: se->cmp; | 132 | f = se->se_collapse ?: se->se_cmp; |
86 | 133 | ||
87 | cmp = f(left, right); | 134 | cmp = f(left, right); |
88 | if (cmp) | 135 | if (cmp) |
@@ -101,7 +148,7 @@ void hist_entry__free(struct hist_entry *he) | |||
101 | * collapse the histogram | 148 | * collapse the histogram |
102 | */ | 149 | */ |
103 | 150 | ||
104 | static void collapse__insert_entry(struct rb_root *root, struct hist_entry *he) | 151 | static bool collapse__insert_entry(struct rb_root *root, struct hist_entry *he) |
105 | { | 152 | { |
106 | struct rb_node **p = &root->rb_node; | 153 | struct rb_node **p = &root->rb_node; |
107 | struct rb_node *parent = NULL; | 154 | struct rb_node *parent = NULL; |
@@ -115,9 +162,9 @@ static void collapse__insert_entry(struct rb_root *root, struct hist_entry *he) | |||
115 | cmp = hist_entry__collapse(iter, he); | 162 | cmp = hist_entry__collapse(iter, he); |
116 | 163 | ||
117 | if (!cmp) { | 164 | if (!cmp) { |
118 | iter->count += he->count; | 165 | iter->period += he->period; |
119 | hist_entry__free(he); | 166 | hist_entry__free(he); |
120 | return; | 167 | return false; |
121 | } | 168 | } |
122 | 169 | ||
123 | if (cmp < 0) | 170 | if (cmp < 0) |
@@ -128,9 +175,10 @@ static void collapse__insert_entry(struct rb_root *root, struct hist_entry *he) | |||
128 | 175 | ||
129 | rb_link_node(&he->rb_node, parent, p); | 176 | rb_link_node(&he->rb_node, parent, p); |
130 | rb_insert_color(&he->rb_node, root); | 177 | rb_insert_color(&he->rb_node, root); |
178 | return true; | ||
131 | } | 179 | } |
132 | 180 | ||
133 | void perf_session__collapse_resort(struct rb_root *hists) | 181 | void hists__collapse_resort(struct hists *self) |
134 | { | 182 | { |
135 | struct rb_root tmp; | 183 | struct rb_root tmp; |
136 | struct rb_node *next; | 184 | struct rb_node *next; |
@@ -140,72 +188,77 @@ void perf_session__collapse_resort(struct rb_root *hists) | |||
140 | return; | 188 | return; |
141 | 189 | ||
142 | tmp = RB_ROOT; | 190 | tmp = RB_ROOT; |
143 | next = rb_first(hists); | 191 | next = rb_first(&self->entries); |
192 | self->nr_entries = 0; | ||
193 | self->max_sym_namelen = 0; | ||
144 | 194 | ||
145 | while (next) { | 195 | while (next) { |
146 | n = rb_entry(next, struct hist_entry, rb_node); | 196 | n = rb_entry(next, struct hist_entry, rb_node); |
147 | next = rb_next(&n->rb_node); | 197 | next = rb_next(&n->rb_node); |
148 | 198 | ||
149 | rb_erase(&n->rb_node, hists); | 199 | rb_erase(&n->rb_node, &self->entries); |
150 | collapse__insert_entry(&tmp, n); | 200 | if (collapse__insert_entry(&tmp, n)) |
201 | hists__inc_nr_entries(self, n); | ||
151 | } | 202 | } |
152 | 203 | ||
153 | *hists = tmp; | 204 | self->entries = tmp; |
154 | } | 205 | } |
155 | 206 | ||
156 | /* | 207 | /* |
157 | * reverse the map, sort on count. | 208 | * reverse the map, sort on period. |
158 | */ | 209 | */ |
159 | 210 | ||
160 | static void perf_session__insert_output_hist_entry(struct rb_root *root, | 211 | static void __hists__insert_output_entry(struct rb_root *entries, |
161 | struct hist_entry *he, | 212 | struct hist_entry *he, |
162 | u64 min_callchain_hits) | 213 | u64 min_callchain_hits) |
163 | { | 214 | { |
164 | struct rb_node **p = &root->rb_node; | 215 | struct rb_node **p = &entries->rb_node; |
165 | struct rb_node *parent = NULL; | 216 | struct rb_node *parent = NULL; |
166 | struct hist_entry *iter; | 217 | struct hist_entry *iter; |
167 | 218 | ||
168 | if (symbol_conf.use_callchain) | 219 | if (symbol_conf.use_callchain) |
169 | callchain_param.sort(&he->sorted_chain, &he->callchain, | 220 | callchain_param.sort(&he->sorted_chain, he->callchain, |
170 | min_callchain_hits, &callchain_param); | 221 | min_callchain_hits, &callchain_param); |
171 | 222 | ||
172 | while (*p != NULL) { | 223 | while (*p != NULL) { |
173 | parent = *p; | 224 | parent = *p; |
174 | iter = rb_entry(parent, struct hist_entry, rb_node); | 225 | iter = rb_entry(parent, struct hist_entry, rb_node); |
175 | 226 | ||
176 | if (he->count > iter->count) | 227 | if (he->period > iter->period) |
177 | p = &(*p)->rb_left; | 228 | p = &(*p)->rb_left; |
178 | else | 229 | else |
179 | p = &(*p)->rb_right; | 230 | p = &(*p)->rb_right; |
180 | } | 231 | } |
181 | 232 | ||
182 | rb_link_node(&he->rb_node, parent, p); | 233 | rb_link_node(&he->rb_node, parent, p); |
183 | rb_insert_color(&he->rb_node, root); | 234 | rb_insert_color(&he->rb_node, entries); |
184 | } | 235 | } |
185 | 236 | ||
186 | void perf_session__output_resort(struct rb_root *hists, u64 total_samples) | 237 | void hists__output_resort(struct hists *self) |
187 | { | 238 | { |
188 | struct rb_root tmp; | 239 | struct rb_root tmp; |
189 | struct rb_node *next; | 240 | struct rb_node *next; |
190 | struct hist_entry *n; | 241 | struct hist_entry *n; |
191 | u64 min_callchain_hits; | 242 | u64 min_callchain_hits; |
192 | 243 | ||
193 | min_callchain_hits = | 244 | min_callchain_hits = self->stats.total_period * (callchain_param.min_percent / 100); |
194 | total_samples * (callchain_param.min_percent / 100); | ||
195 | 245 | ||
196 | tmp = RB_ROOT; | 246 | tmp = RB_ROOT; |
197 | next = rb_first(hists); | 247 | next = rb_first(&self->entries); |
248 | |||
249 | self->nr_entries = 0; | ||
250 | self->max_sym_namelen = 0; | ||
198 | 251 | ||
199 | while (next) { | 252 | while (next) { |
200 | n = rb_entry(next, struct hist_entry, rb_node); | 253 | n = rb_entry(next, struct hist_entry, rb_node); |
201 | next = rb_next(&n->rb_node); | 254 | next = rb_next(&n->rb_node); |
202 | 255 | ||
203 | rb_erase(&n->rb_node, hists); | 256 | rb_erase(&n->rb_node, &self->entries); |
204 | perf_session__insert_output_hist_entry(&tmp, n, | 257 | __hists__insert_output_entry(&tmp, n, min_callchain_hits); |
205 | min_callchain_hits); | 258 | hists__inc_nr_entries(self, n); |
206 | } | 259 | } |
207 | 260 | ||
208 | *hists = tmp; | 261 | self->entries = tmp; |
209 | } | 262 | } |
210 | 263 | ||
211 | static size_t callchain__fprintf_left_margin(FILE *fp, int left_margin) | 264 | static size_t callchain__fprintf_left_margin(FILE *fp, int left_margin) |
@@ -237,7 +290,7 @@ static size_t ipchain__fprintf_graph_line(FILE *fp, int depth, int depth_mask, | |||
237 | } | 290 | } |
238 | 291 | ||
239 | static size_t ipchain__fprintf_graph(FILE *fp, struct callchain_list *chain, | 292 | static size_t ipchain__fprintf_graph(FILE *fp, struct callchain_list *chain, |
240 | int depth, int depth_mask, int count, | 293 | int depth, int depth_mask, int period, |
241 | u64 total_samples, int hits, | 294 | u64 total_samples, int hits, |
242 | int left_margin) | 295 | int left_margin) |
243 | { | 296 | { |
@@ -250,7 +303,7 @@ static size_t ipchain__fprintf_graph(FILE *fp, struct callchain_list *chain, | |||
250 | ret += fprintf(fp, "|"); | 303 | ret += fprintf(fp, "|"); |
251 | else | 304 | else |
252 | ret += fprintf(fp, " "); | 305 | ret += fprintf(fp, " "); |
253 | if (!count && i == depth - 1) { | 306 | if (!period && i == depth - 1) { |
254 | double percent; | 307 | double percent; |
255 | 308 | ||
256 | percent = hits * 100.0 / total_samples; | 309 | percent = hits * 100.0 / total_samples; |
@@ -258,8 +311,8 @@ static size_t ipchain__fprintf_graph(FILE *fp, struct callchain_list *chain, | |||
258 | } else | 311 | } else |
259 | ret += fprintf(fp, "%s", " "); | 312 | ret += fprintf(fp, "%s", " "); |
260 | } | 313 | } |
261 | if (chain->sym) | 314 | if (chain->ms.sym) |
262 | ret += fprintf(fp, "%s\n", chain->sym->name); | 315 | ret += fprintf(fp, "%s\n", chain->ms.sym->name); |
263 | else | 316 | else |
264 | ret += fprintf(fp, "%p\n", (void *)(long)chain->ip); | 317 | ret += fprintf(fp, "%p\n", (void *)(long)chain->ip); |
265 | 318 | ||
@@ -278,7 +331,7 @@ static void init_rem_hits(void) | |||
278 | } | 331 | } |
279 | 332 | ||
280 | strcpy(rem_sq_bracket->name, "[...]"); | 333 | strcpy(rem_sq_bracket->name, "[...]"); |
281 | rem_hits.sym = rem_sq_bracket; | 334 | rem_hits.ms.sym = rem_sq_bracket; |
282 | } | 335 | } |
283 | 336 | ||
284 | static size_t __callchain__fprintf_graph(FILE *fp, struct callchain_node *self, | 337 | static size_t __callchain__fprintf_graph(FILE *fp, struct callchain_node *self, |
@@ -293,6 +346,7 @@ static size_t __callchain__fprintf_graph(FILE *fp, struct callchain_node *self, | |||
293 | u64 remaining; | 346 | u64 remaining; |
294 | size_t ret = 0; | 347 | size_t ret = 0; |
295 | int i; | 348 | int i; |
349 | uint entries_printed = 0; | ||
296 | 350 | ||
297 | if (callchain_param.mode == CHAIN_GRAPH_REL) | 351 | if (callchain_param.mode == CHAIN_GRAPH_REL) |
298 | new_total = self->children_hit; | 352 | new_total = self->children_hit; |
@@ -328,8 +382,6 @@ static size_t __callchain__fprintf_graph(FILE *fp, struct callchain_node *self, | |||
328 | left_margin); | 382 | left_margin); |
329 | i = 0; | 383 | i = 0; |
330 | list_for_each_entry(chain, &child->val, list) { | 384 | list_for_each_entry(chain, &child->val, list) { |
331 | if (chain->ip >= PERF_CONTEXT_MAX) | ||
332 | continue; | ||
333 | ret += ipchain__fprintf_graph(fp, chain, depth, | 385 | ret += ipchain__fprintf_graph(fp, chain, depth, |
334 | new_depth_mask, i++, | 386 | new_depth_mask, i++, |
335 | new_total, | 387 | new_total, |
@@ -341,6 +393,8 @@ static size_t __callchain__fprintf_graph(FILE *fp, struct callchain_node *self, | |||
341 | new_depth_mask | (1 << depth), | 393 | new_depth_mask | (1 << depth), |
342 | left_margin); | 394 | left_margin); |
343 | node = next; | 395 | node = next; |
396 | if (++entries_printed == callchain_param.print_limit) | ||
397 | break; | ||
344 | } | 398 | } |
345 | 399 | ||
346 | if (callchain_param.mode == CHAIN_GRAPH_REL && | 400 | if (callchain_param.mode == CHAIN_GRAPH_REL && |
@@ -366,11 +420,9 @@ static size_t callchain__fprintf_graph(FILE *fp, struct callchain_node *self, | |||
366 | bool printed = false; | 420 | bool printed = false; |
367 | int i = 0; | 421 | int i = 0; |
368 | int ret = 0; | 422 | int ret = 0; |
423 | u32 entries_printed = 0; | ||
369 | 424 | ||
370 | list_for_each_entry(chain, &self->val, list) { | 425 | list_for_each_entry(chain, &self->val, list) { |
371 | if (chain->ip >= PERF_CONTEXT_MAX) | ||
372 | continue; | ||
373 | |||
374 | if (!i++ && sort__first_dimension == SORT_SYM) | 426 | if (!i++ && sort__first_dimension == SORT_SYM) |
375 | continue; | 427 | continue; |
376 | 428 | ||
@@ -385,10 +437,13 @@ static size_t callchain__fprintf_graph(FILE *fp, struct callchain_node *self, | |||
385 | } else | 437 | } else |
386 | ret += callchain__fprintf_left_margin(fp, left_margin); | 438 | ret += callchain__fprintf_left_margin(fp, left_margin); |
387 | 439 | ||
388 | if (chain->sym) | 440 | if (chain->ms.sym) |
389 | ret += fprintf(fp, " %s\n", chain->sym->name); | 441 | ret += fprintf(fp, " %s\n", chain->ms.sym->name); |
390 | else | 442 | else |
391 | ret += fprintf(fp, " %p\n", (void *)(long)chain->ip); | 443 | ret += fprintf(fp, " %p\n", (void *)(long)chain->ip); |
444 | |||
445 | if (++entries_printed == callchain_param.print_limit) | ||
446 | break; | ||
392 | } | 447 | } |
393 | 448 | ||
394 | ret += __callchain__fprintf_graph(fp, self, total_samples, 1, 1, left_margin); | 449 | ret += __callchain__fprintf_graph(fp, self, total_samples, 1, 1, left_margin); |
@@ -411,8 +466,8 @@ static size_t callchain__fprintf_flat(FILE *fp, struct callchain_node *self, | |||
411 | list_for_each_entry(chain, &self->val, list) { | 466 | list_for_each_entry(chain, &self->val, list) { |
412 | if (chain->ip >= PERF_CONTEXT_MAX) | 467 | if (chain->ip >= PERF_CONTEXT_MAX) |
413 | continue; | 468 | continue; |
414 | if (chain->sym) | 469 | if (chain->ms.sym) |
415 | ret += fprintf(fp, " %s\n", chain->sym->name); | 470 | ret += fprintf(fp, " %s\n", chain->ms.sym->name); |
416 | else | 471 | else |
417 | ret += fprintf(fp, " %p\n", | 472 | ret += fprintf(fp, " %p\n", |
418 | (void *)(long)chain->ip); | 473 | (void *)(long)chain->ip); |
@@ -427,6 +482,7 @@ static size_t hist_entry_callchain__fprintf(FILE *fp, struct hist_entry *self, | |||
427 | struct rb_node *rb_node; | 482 | struct rb_node *rb_node; |
428 | struct callchain_node *chain; | 483 | struct callchain_node *chain; |
429 | size_t ret = 0; | 484 | size_t ret = 0; |
485 | u32 entries_printed = 0; | ||
430 | 486 | ||
431 | rb_node = rb_first(&self->sorted_chain); | 487 | rb_node = rb_first(&self->sorted_chain); |
432 | while (rb_node) { | 488 | while (rb_node) { |
@@ -449,55 +505,88 @@ static size_t hist_entry_callchain__fprintf(FILE *fp, struct hist_entry *self, | |||
449 | break; | 505 | break; |
450 | } | 506 | } |
451 | ret += fprintf(fp, "\n"); | 507 | ret += fprintf(fp, "\n"); |
508 | if (++entries_printed == callchain_param.print_limit) | ||
509 | break; | ||
452 | rb_node = rb_next(rb_node); | 510 | rb_node = rb_next(rb_node); |
453 | } | 511 | } |
454 | 512 | ||
455 | return ret; | 513 | return ret; |
456 | } | 514 | } |
457 | 515 | ||
458 | static size_t hist_entry__fprintf(struct hist_entry *self, | 516 | int hist_entry__snprintf(struct hist_entry *self, char *s, size_t size, |
459 | struct perf_session *pair_session, | 517 | struct hists *pair_hists, bool show_displacement, |
460 | bool show_displacement, | 518 | long displacement, bool color, u64 session_total) |
461 | long displacement, FILE *fp, | ||
462 | u64 session_total) | ||
463 | { | 519 | { |
464 | struct sort_entry *se; | 520 | struct sort_entry *se; |
465 | u64 count, total; | 521 | u64 period, total, period_sys, period_us, period_guest_sys, period_guest_us; |
466 | const char *sep = symbol_conf.field_sep; | 522 | const char *sep = symbol_conf.field_sep; |
467 | size_t ret; | 523 | int ret; |
468 | 524 | ||
469 | if (symbol_conf.exclude_other && !self->parent) | 525 | if (symbol_conf.exclude_other && !self->parent) |
470 | return 0; | 526 | return 0; |
471 | 527 | ||
472 | if (pair_session) { | 528 | if (pair_hists) { |
473 | count = self->pair ? self->pair->count : 0; | 529 | period = self->pair ? self->pair->period : 0; |
474 | total = pair_session->events_stats.total; | 530 | total = pair_hists->stats.total_period; |
531 | period_sys = self->pair ? self->pair->period_sys : 0; | ||
532 | period_us = self->pair ? self->pair->period_us : 0; | ||
533 | period_guest_sys = self->pair ? self->pair->period_guest_sys : 0; | ||
534 | period_guest_us = self->pair ? self->pair->period_guest_us : 0; | ||
475 | } else { | 535 | } else { |
476 | count = self->count; | 536 | period = self->period; |
477 | total = session_total; | 537 | total = session_total; |
538 | period_sys = self->period_sys; | ||
539 | period_us = self->period_us; | ||
540 | period_guest_sys = self->period_guest_sys; | ||
541 | period_guest_us = self->period_guest_us; | ||
478 | } | 542 | } |
479 | 543 | ||
480 | if (total) | 544 | if (total) { |
481 | ret = percent_color_fprintf(fp, sep ? "%.2f" : " %6.2f%%", | 545 | if (color) |
482 | (count * 100.0) / total); | 546 | ret = percent_color_snprintf(s, size, |
483 | else | 547 | sep ? "%.2f" : " %6.2f%%", |
484 | ret = fprintf(fp, sep ? "%lld" : "%12lld ", count); | 548 | (period * 100.0) / total); |
549 | else | ||
550 | ret = snprintf(s, size, sep ? "%.2f" : " %6.2f%%", | ||
551 | (period * 100.0) / total); | ||
552 | if (symbol_conf.show_cpu_utilization) { | ||
553 | ret += percent_color_snprintf(s + ret, size - ret, | ||
554 | sep ? "%.2f" : " %6.2f%%", | ||
555 | (period_sys * 100.0) / total); | ||
556 | ret += percent_color_snprintf(s + ret, size - ret, | ||
557 | sep ? "%.2f" : " %6.2f%%", | ||
558 | (period_us * 100.0) / total); | ||
559 | if (perf_guest) { | ||
560 | ret += percent_color_snprintf(s + ret, | ||
561 | size - ret, | ||
562 | sep ? "%.2f" : " %6.2f%%", | ||
563 | (period_guest_sys * 100.0) / | ||
564 | total); | ||
565 | ret += percent_color_snprintf(s + ret, | ||
566 | size - ret, | ||
567 | sep ? "%.2f" : " %6.2f%%", | ||
568 | (period_guest_us * 100.0) / | ||
569 | total); | ||
570 | } | ||
571 | } | ||
572 | } else | ||
573 | ret = snprintf(s, size, sep ? "%lld" : "%12lld ", period); | ||
485 | 574 | ||
486 | if (symbol_conf.show_nr_samples) { | 575 | if (symbol_conf.show_nr_samples) { |
487 | if (sep) | 576 | if (sep) |
488 | fprintf(fp, "%c%lld", *sep, count); | 577 | ret += snprintf(s + ret, size - ret, "%c%lld", *sep, period); |
489 | else | 578 | else |
490 | fprintf(fp, "%11lld", count); | 579 | ret += snprintf(s + ret, size - ret, "%11lld", period); |
491 | } | 580 | } |
492 | 581 | ||
493 | if (pair_session) { | 582 | if (pair_hists) { |
494 | char bf[32]; | 583 | char bf[32]; |
495 | double old_percent = 0, new_percent = 0, diff; | 584 | double old_percent = 0, new_percent = 0, diff; |
496 | 585 | ||
497 | if (total > 0) | 586 | if (total > 0) |
498 | old_percent = (count * 100.0) / total; | 587 | old_percent = (period * 100.0) / total; |
499 | if (session_total > 0) | 588 | if (session_total > 0) |
500 | new_percent = (self->count * 100.0) / session_total; | 589 | new_percent = (self->period * 100.0) / session_total; |
501 | 590 | ||
502 | diff = new_percent - old_percent; | 591 | diff = new_percent - old_percent; |
503 | 592 | ||
@@ -507,9 +596,9 @@ static size_t hist_entry__fprintf(struct hist_entry *self, | |||
507 | snprintf(bf, sizeof(bf), " "); | 596 | snprintf(bf, sizeof(bf), " "); |
508 | 597 | ||
509 | if (sep) | 598 | if (sep) |
510 | ret += fprintf(fp, "%c%s", *sep, bf); | 599 | ret += snprintf(s + ret, size - ret, "%c%s", *sep, bf); |
511 | else | 600 | else |
512 | ret += fprintf(fp, "%11.11s", bf); | 601 | ret += snprintf(s + ret, size - ret, "%11.11s", bf); |
513 | 602 | ||
514 | if (show_displacement) { | 603 | if (show_displacement) { |
515 | if (displacement) | 604 | if (displacement) |
@@ -518,9 +607,9 @@ static size_t hist_entry__fprintf(struct hist_entry *self, | |||
518 | snprintf(bf, sizeof(bf), " "); | 607 | snprintf(bf, sizeof(bf), " "); |
519 | 608 | ||
520 | if (sep) | 609 | if (sep) |
521 | fprintf(fp, "%c%s", *sep, bf); | 610 | ret += snprintf(s + ret, size - ret, "%c%s", *sep, bf); |
522 | else | 611 | else |
523 | fprintf(fp, "%6.6s", bf); | 612 | ret += snprintf(s + ret, size - ret, "%6.6s", bf); |
524 | } | 613 | } |
525 | } | 614 | } |
526 | 615 | ||
@@ -528,33 +617,43 @@ static size_t hist_entry__fprintf(struct hist_entry *self, | |||
528 | if (se->elide) | 617 | if (se->elide) |
529 | continue; | 618 | continue; |
530 | 619 | ||
531 | fprintf(fp, "%s", sep ?: " "); | 620 | ret += snprintf(s + ret, size - ret, "%s", sep ?: " "); |
532 | ret += se->print(fp, self, se->width ? *se->width : 0); | 621 | ret += se->se_snprintf(self, s + ret, size - ret, |
622 | se->se_width ? *se->se_width : 0); | ||
533 | } | 623 | } |
534 | 624 | ||
535 | ret += fprintf(fp, "\n"); | 625 | return ret; |
626 | } | ||
536 | 627 | ||
537 | if (symbol_conf.use_callchain) { | 628 | int hist_entry__fprintf(struct hist_entry *self, struct hists *pair_hists, |
538 | int left_margin = 0; | 629 | bool show_displacement, long displacement, FILE *fp, |
630 | u64 session_total) | ||
631 | { | ||
632 | char bf[512]; | ||
633 | hist_entry__snprintf(self, bf, sizeof(bf), pair_hists, | ||
634 | show_displacement, displacement, | ||
635 | true, session_total); | ||
636 | return fprintf(fp, "%s\n", bf); | ||
637 | } | ||
539 | 638 | ||
540 | if (sort__first_dimension == SORT_COMM) { | 639 | static size_t hist_entry__fprintf_callchain(struct hist_entry *self, FILE *fp, |
541 | se = list_first_entry(&hist_entry__sort_list, typeof(*se), | 640 | u64 session_total) |
542 | list); | 641 | { |
543 | left_margin = se->width ? *se->width : 0; | 642 | int left_margin = 0; |
544 | left_margin -= thread__comm_len(self->thread); | ||
545 | } | ||
546 | 643 | ||
547 | hist_entry_callchain__fprintf(fp, self, session_total, | 644 | if (sort__first_dimension == SORT_COMM) { |
548 | left_margin); | 645 | struct sort_entry *se = list_first_entry(&hist_entry__sort_list, |
646 | typeof(*se), list); | ||
647 | left_margin = se->se_width ? *se->se_width : 0; | ||
648 | left_margin -= thread__comm_len(self->thread); | ||
549 | } | 649 | } |
550 | 650 | ||
551 | return ret; | 651 | return hist_entry_callchain__fprintf(fp, self, session_total, |
652 | left_margin); | ||
552 | } | 653 | } |
553 | 654 | ||
554 | size_t perf_session__fprintf_hists(struct rb_root *hists, | 655 | size_t hists__fprintf(struct hists *self, struct hists *pair, |
555 | struct perf_session *pair, | 656 | bool show_displacement, FILE *fp) |
556 | bool show_displacement, FILE *fp, | ||
557 | u64 session_total) | ||
558 | { | 657 | { |
559 | struct sort_entry *se; | 658 | struct sort_entry *se; |
560 | struct rb_node *nd; | 659 | struct rb_node *nd; |
@@ -563,7 +662,7 @@ size_t perf_session__fprintf_hists(struct rb_root *hists, | |||
563 | long displacement = 0; | 662 | long displacement = 0; |
564 | unsigned int width; | 663 | unsigned int width; |
565 | const char *sep = symbol_conf.field_sep; | 664 | const char *sep = symbol_conf.field_sep; |
566 | char *col_width = symbol_conf.col_width_list_str; | 665 | const char *col_width = symbol_conf.col_width_list_str; |
567 | 666 | ||
568 | init_rem_hits(); | 667 | init_rem_hits(); |
569 | 668 | ||
@@ -576,6 +675,24 @@ size_t perf_session__fprintf_hists(struct rb_root *hists, | |||
576 | fputs(" Samples ", fp); | 675 | fputs(" Samples ", fp); |
577 | } | 676 | } |
578 | 677 | ||
678 | if (symbol_conf.show_cpu_utilization) { | ||
679 | if (sep) { | ||
680 | ret += fprintf(fp, "%csys", *sep); | ||
681 | ret += fprintf(fp, "%cus", *sep); | ||
682 | if (perf_guest) { | ||
683 | ret += fprintf(fp, "%cguest sys", *sep); | ||
684 | ret += fprintf(fp, "%cguest us", *sep); | ||
685 | } | ||
686 | } else { | ||
687 | ret += fprintf(fp, " sys "); | ||
688 | ret += fprintf(fp, " us "); | ||
689 | if (perf_guest) { | ||
690 | ret += fprintf(fp, " guest sys "); | ||
691 | ret += fprintf(fp, " guest us "); | ||
692 | } | ||
693 | } | ||
694 | } | ||
695 | |||
579 | if (pair) { | 696 | if (pair) { |
580 | if (sep) | 697 | if (sep) |
581 | ret += fprintf(fp, "%cDelta", *sep); | 698 | ret += fprintf(fp, "%cDelta", *sep); |
@@ -594,22 +711,22 @@ size_t perf_session__fprintf_hists(struct rb_root *hists, | |||
594 | if (se->elide) | 711 | if (se->elide) |
595 | continue; | 712 | continue; |
596 | if (sep) { | 713 | if (sep) { |
597 | fprintf(fp, "%c%s", *sep, se->header); | 714 | fprintf(fp, "%c%s", *sep, se->se_header); |
598 | continue; | 715 | continue; |
599 | } | 716 | } |
600 | width = strlen(se->header); | 717 | width = strlen(se->se_header); |
601 | if (se->width) { | 718 | if (se->se_width) { |
602 | if (symbol_conf.col_width_list_str) { | 719 | if (symbol_conf.col_width_list_str) { |
603 | if (col_width) { | 720 | if (col_width) { |
604 | *se->width = atoi(col_width); | 721 | *se->se_width = atoi(col_width); |
605 | col_width = strchr(col_width, ','); | 722 | col_width = strchr(col_width, ','); |
606 | if (col_width) | 723 | if (col_width) |
607 | ++col_width; | 724 | ++col_width; |
608 | } | 725 | } |
609 | } | 726 | } |
610 | width = *se->width = max(*se->width, width); | 727 | width = *se->se_width = max(*se->se_width, width); |
611 | } | 728 | } |
612 | fprintf(fp, " %*s", width, se->header); | 729 | fprintf(fp, " %*s", width, se->se_header); |
613 | } | 730 | } |
614 | fprintf(fp, "\n"); | 731 | fprintf(fp, "\n"); |
615 | 732 | ||
@@ -631,10 +748,10 @@ size_t perf_session__fprintf_hists(struct rb_root *hists, | |||
631 | continue; | 748 | continue; |
632 | 749 | ||
633 | fprintf(fp, " "); | 750 | fprintf(fp, " "); |
634 | if (se->width) | 751 | if (se->se_width) |
635 | width = *se->width; | 752 | width = *se->se_width; |
636 | else | 753 | else |
637 | width = strlen(se->header); | 754 | width = strlen(se->se_header); |
638 | for (i = 0; i < width; i++) | 755 | for (i = 0; i < width; i++) |
639 | fprintf(fp, "."); | 756 | fprintf(fp, "."); |
640 | } | 757 | } |
@@ -642,7 +759,7 @@ size_t perf_session__fprintf_hists(struct rb_root *hists, | |||
642 | fprintf(fp, "\n#\n"); | 759 | fprintf(fp, "\n#\n"); |
643 | 760 | ||
644 | print_entries: | 761 | print_entries: |
645 | for (nd = rb_first(hists); nd; nd = rb_next(nd)) { | 762 | for (nd = rb_first(&self->entries); nd; nd = rb_next(nd)) { |
646 | struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); | 763 | struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); |
647 | 764 | ||
648 | if (show_displacement) { | 765 | if (show_displacement) { |
@@ -654,10 +771,14 @@ print_entries: | |||
654 | ++position; | 771 | ++position; |
655 | } | 772 | } |
656 | ret += hist_entry__fprintf(h, pair, show_displacement, | 773 | ret += hist_entry__fprintf(h, pair, show_displacement, |
657 | displacement, fp, session_total); | 774 | displacement, fp, self->stats.total_period); |
658 | if (h->map == NULL && verbose > 1) { | 775 | |
776 | if (symbol_conf.use_callchain) | ||
777 | ret += hist_entry__fprintf_callchain(h, fp, self->stats.total_period); | ||
778 | |||
779 | if (h->ms.map == NULL && verbose > 1) { | ||
659 | __map_groups__fprintf_maps(&h->thread->mg, | 780 | __map_groups__fprintf_maps(&h->thread->mg, |
660 | MAP__FUNCTION, fp); | 781 | MAP__FUNCTION, verbose, fp); |
661 | fprintf(fp, "%.10s end\n", graph_dotted_line); | 782 | fprintf(fp, "%.10s end\n", graph_dotted_line); |
662 | } | 783 | } |
663 | } | 784 | } |
@@ -666,3 +787,271 @@ print_entries: | |||
666 | 787 | ||
667 | return ret; | 788 | return ret; |
668 | } | 789 | } |
790 | |||
791 | enum hist_filter { | ||
792 | HIST_FILTER__DSO, | ||
793 | HIST_FILTER__THREAD, | ||
794 | }; | ||
795 | |||
796 | void hists__filter_by_dso(struct hists *self, const struct dso *dso) | ||
797 | { | ||
798 | struct rb_node *nd; | ||
799 | |||
800 | self->nr_entries = self->stats.total_period = 0; | ||
801 | self->stats.nr_events[PERF_RECORD_SAMPLE] = 0; | ||
802 | self->max_sym_namelen = 0; | ||
803 | |||
804 | for (nd = rb_first(&self->entries); nd; nd = rb_next(nd)) { | ||
805 | struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); | ||
806 | |||
807 | if (symbol_conf.exclude_other && !h->parent) | ||
808 | continue; | ||
809 | |||
810 | if (dso != NULL && (h->ms.map == NULL || h->ms.map->dso != dso)) { | ||
811 | h->filtered |= (1 << HIST_FILTER__DSO); | ||
812 | continue; | ||
813 | } | ||
814 | |||
815 | h->filtered &= ~(1 << HIST_FILTER__DSO); | ||
816 | if (!h->filtered) { | ||
817 | ++self->nr_entries; | ||
818 | self->stats.total_period += h->period; | ||
819 | self->stats.nr_events[PERF_RECORD_SAMPLE] += h->nr_events; | ||
820 | if (h->ms.sym && | ||
821 | self->max_sym_namelen < h->ms.sym->namelen) | ||
822 | self->max_sym_namelen = h->ms.sym->namelen; | ||
823 | } | ||
824 | } | ||
825 | } | ||
826 | |||
827 | void hists__filter_by_thread(struct hists *self, const struct thread *thread) | ||
828 | { | ||
829 | struct rb_node *nd; | ||
830 | |||
831 | self->nr_entries = self->stats.total_period = 0; | ||
832 | self->stats.nr_events[PERF_RECORD_SAMPLE] = 0; | ||
833 | self->max_sym_namelen = 0; | ||
834 | |||
835 | for (nd = rb_first(&self->entries); nd; nd = rb_next(nd)) { | ||
836 | struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); | ||
837 | |||
838 | if (thread != NULL && h->thread != thread) { | ||
839 | h->filtered |= (1 << HIST_FILTER__THREAD); | ||
840 | continue; | ||
841 | } | ||
842 | h->filtered &= ~(1 << HIST_FILTER__THREAD); | ||
843 | if (!h->filtered) { | ||
844 | ++self->nr_entries; | ||
845 | self->stats.total_period += h->period; | ||
846 | self->stats.nr_events[PERF_RECORD_SAMPLE] += h->nr_events; | ||
847 | if (h->ms.sym && | ||
848 | self->max_sym_namelen < h->ms.sym->namelen) | ||
849 | self->max_sym_namelen = h->ms.sym->namelen; | ||
850 | } | ||
851 | } | ||
852 | } | ||
853 | |||
854 | static int symbol__alloc_hist(struct symbol *self) | ||
855 | { | ||
856 | struct sym_priv *priv = symbol__priv(self); | ||
857 | const int size = (sizeof(*priv->hist) + | ||
858 | (self->end - self->start) * sizeof(u64)); | ||
859 | |||
860 | priv->hist = zalloc(size); | ||
861 | return priv->hist == NULL ? -1 : 0; | ||
862 | } | ||
863 | |||
864 | int hist_entry__inc_addr_samples(struct hist_entry *self, u64 ip) | ||
865 | { | ||
866 | unsigned int sym_size, offset; | ||
867 | struct symbol *sym = self->ms.sym; | ||
868 | struct sym_priv *priv; | ||
869 | struct sym_hist *h; | ||
870 | |||
871 | if (!sym || !self->ms.map) | ||
872 | return 0; | ||
873 | |||
874 | priv = symbol__priv(sym); | ||
875 | if (priv->hist == NULL && symbol__alloc_hist(sym) < 0) | ||
876 | return -ENOMEM; | ||
877 | |||
878 | sym_size = sym->end - sym->start; | ||
879 | offset = ip - sym->start; | ||
880 | |||
881 | pr_debug3("%s: ip=%#Lx\n", __func__, self->ms.map->unmap_ip(self->ms.map, ip)); | ||
882 | |||
883 | if (offset >= sym_size) | ||
884 | return 0; | ||
885 | |||
886 | h = priv->hist; | ||
887 | h->sum++; | ||
888 | h->ip[offset]++; | ||
889 | |||
890 | pr_debug3("%#Lx %s: period++ [ip: %#Lx, %#Lx] => %Ld\n", self->ms.sym->start, | ||
891 | self->ms.sym->name, ip, ip - self->ms.sym->start, h->ip[offset]); | ||
892 | return 0; | ||
893 | } | ||
894 | |||
895 | static struct objdump_line *objdump_line__new(s64 offset, char *line) | ||
896 | { | ||
897 | struct objdump_line *self = malloc(sizeof(*self)); | ||
898 | |||
899 | if (self != NULL) { | ||
900 | self->offset = offset; | ||
901 | self->line = line; | ||
902 | } | ||
903 | |||
904 | return self; | ||
905 | } | ||
906 | |||
907 | void objdump_line__free(struct objdump_line *self) | ||
908 | { | ||
909 | free(self->line); | ||
910 | free(self); | ||
911 | } | ||
912 | |||
913 | static void objdump__add_line(struct list_head *head, struct objdump_line *line) | ||
914 | { | ||
915 | list_add_tail(&line->node, head); | ||
916 | } | ||
917 | |||
918 | struct objdump_line *objdump__get_next_ip_line(struct list_head *head, | ||
919 | struct objdump_line *pos) | ||
920 | { | ||
921 | list_for_each_entry_continue(pos, head, node) | ||
922 | if (pos->offset >= 0) | ||
923 | return pos; | ||
924 | |||
925 | return NULL; | ||
926 | } | ||
927 | |||
928 | static int hist_entry__parse_objdump_line(struct hist_entry *self, FILE *file, | ||
929 | struct list_head *head) | ||
930 | { | ||
931 | struct symbol *sym = self->ms.sym; | ||
932 | struct objdump_line *objdump_line; | ||
933 | char *line = NULL, *tmp, *tmp2, *c; | ||
934 | size_t line_len; | ||
935 | s64 line_ip, offset = -1; | ||
936 | |||
937 | if (getline(&line, &line_len, file) < 0) | ||
938 | return -1; | ||
939 | |||
940 | if (!line) | ||
941 | return -1; | ||
942 | |||
943 | while (line_len != 0 && isspace(line[line_len - 1])) | ||
944 | line[--line_len] = '\0'; | ||
945 | |||
946 | c = strchr(line, '\n'); | ||
947 | if (c) | ||
948 | *c = 0; | ||
949 | |||
950 | line_ip = -1; | ||
951 | |||
952 | /* | ||
953 | * Strip leading spaces: | ||
954 | */ | ||
955 | tmp = line; | ||
956 | while (*tmp) { | ||
957 | if (*tmp != ' ') | ||
958 | break; | ||
959 | tmp++; | ||
960 | } | ||
961 | |||
962 | if (*tmp) { | ||
963 | /* | ||
964 | * Parse hexa addresses followed by ':' | ||
965 | */ | ||
966 | line_ip = strtoull(tmp, &tmp2, 16); | ||
967 | if (*tmp2 != ':') | ||
968 | line_ip = -1; | ||
969 | } | ||
970 | |||
971 | if (line_ip != -1) { | ||
972 | u64 start = map__rip_2objdump(self->ms.map, sym->start); | ||
973 | offset = line_ip - start; | ||
974 | } | ||
975 | |||
976 | objdump_line = objdump_line__new(offset, line); | ||
977 | if (objdump_line == NULL) { | ||
978 | free(line); | ||
979 | return -1; | ||
980 | } | ||
981 | objdump__add_line(head, objdump_line); | ||
982 | |||
983 | return 0; | ||
984 | } | ||
985 | |||
986 | int hist_entry__annotate(struct hist_entry *self, struct list_head *head) | ||
987 | { | ||
988 | struct symbol *sym = self->ms.sym; | ||
989 | struct map *map = self->ms.map; | ||
990 | struct dso *dso = map->dso; | ||
991 | const char *filename = dso->long_name; | ||
992 | char command[PATH_MAX * 2]; | ||
993 | FILE *file; | ||
994 | u64 len; | ||
995 | |||
996 | if (!filename) | ||
997 | return -1; | ||
998 | |||
999 | if (dso->origin == DSO__ORIG_KERNEL) { | ||
1000 | if (dso->annotate_warned) | ||
1001 | return 0; | ||
1002 | dso->annotate_warned = 1; | ||
1003 | pr_err("Can't annotate %s: No vmlinux file was found in the " | ||
1004 | "path:\n", sym->name); | ||
1005 | vmlinux_path__fprintf(stderr); | ||
1006 | return -1; | ||
1007 | } | ||
1008 | |||
1009 | pr_debug("%s: filename=%s, sym=%s, start=%#Lx, end=%#Lx\n", __func__, | ||
1010 | filename, sym->name, map->unmap_ip(map, sym->start), | ||
1011 | map->unmap_ip(map, sym->end)); | ||
1012 | |||
1013 | len = sym->end - sym->start; | ||
1014 | |||
1015 | pr_debug("annotating [%p] %30s : [%p] %30s\n", | ||
1016 | dso, dso->long_name, sym, sym->name); | ||
1017 | |||
1018 | snprintf(command, sizeof(command), | ||
1019 | "objdump --start-address=0x%016Lx --stop-address=0x%016Lx -dS %s|grep -v %s|expand", | ||
1020 | map__rip_2objdump(map, sym->start), | ||
1021 | map__rip_2objdump(map, sym->end), | ||
1022 | filename, filename); | ||
1023 | |||
1024 | pr_debug("Executing: %s\n", command); | ||
1025 | |||
1026 | file = popen(command, "r"); | ||
1027 | if (!file) | ||
1028 | return -1; | ||
1029 | |||
1030 | while (!feof(file)) | ||
1031 | if (hist_entry__parse_objdump_line(self, file, head) < 0) | ||
1032 | break; | ||
1033 | |||
1034 | pclose(file); | ||
1035 | return 0; | ||
1036 | } | ||
1037 | |||
1038 | void hists__inc_nr_events(struct hists *self, u32 type) | ||
1039 | { | ||
1040 | ++self->stats.nr_events[0]; | ||
1041 | ++self->stats.nr_events[type]; | ||
1042 | } | ||
1043 | |||
1044 | size_t hists__fprintf_nr_events(struct hists *self, FILE *fp) | ||
1045 | { | ||
1046 | int i; | ||
1047 | size_t ret = 0; | ||
1048 | |||
1049 | for (i = 0; i < PERF_RECORD_HEADER_MAX; ++i) { | ||
1050 | if (!event__name[i]) | ||
1051 | continue; | ||
1052 | ret += fprintf(fp, "%10s events: %10d\n", | ||
1053 | event__name[i], self->stats.nr_events[i]); | ||
1054 | } | ||
1055 | |||
1056 | return ret; | ||
1057 | } | ||