Branch data Line data Source code
1 : : /* Collect stack-trace profiles of running program(s).
2 : : Copyright (C) 2025-2026 Red Hat, Inc.
3 : : This file is part of elfutils.
4 : :
5 : : This file is free software; you can redistribute it and/or modify
6 : : it under the terms of the GNU General Public License as published by
7 : : the Free Software Foundation; either version 3 of the License, or
8 : : (at your option) any later version.
9 : :
10 : : elfutils is distributed in the hope that it will be useful, but
11 : : WITHOUT ANY WARRANTY; without even the implied warranty of
12 : : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 : : GNU General Public License for more details.
14 : :
15 : : You should have received a copy of the GNU General Public License
16 : : along with this program. If not, see <http://www.gnu.org/licenses/>. */
17 : :
18 : : #ifdef HAVE_CONFIG_H
19 : : # include <config.h>
20 : : #endif
21 : :
22 : : #include "printversion.h"
23 : :
24 : : #include <string>
25 : : #include <memory>
26 : : #include <iomanip>
27 : : #include <map>
28 : : #include <unordered_map>
29 : : #include <vector>
30 : : #include <bitset>
31 : : #include <stdexcept>
32 : : #include <cstring>
33 : : #include <csignal>
34 : : #include <cassert>
35 : : #include <chrono>
36 : : #include <iostream>
37 : : #include <fstream>
38 : : #include <sstream>
39 : : #include <cinttypes>
40 : : #include <format>
41 : : #include <filesystem>
42 : :
43 : : #include <sys/utsname.h>
44 : :
45 : : #include <sys/syscall.h>
46 : : #include <sys/ioctl.h>
47 : : #include <sys/mman.h>
48 : : #include <sys/wait.h>
49 : : #include <poll.h>
50 : : #ifdef HAVE_LINUX_PERF_EVENT_H
51 : : #include <linux/perf_event.h>
52 : : #endif
53 : : #include <argp.h>
54 : : #include <fcntl.h>
55 : : #include <dirent.h>
56 : :
57 : : #include <system.h>
58 : :
59 : : #ifdef HAVE_PERFMON_PFMLIB_PERF_EVENT_H
60 : : #include <perfmon/pfmlib_perf_event.h>
61 : : #endif
62 : :
63 : : #include <json-c/json.h>
64 : :
65 : : #include <gelf.h>
66 : : #include <dwarf.h>
67 : : #include <libdwfl.h>
68 : : #include <libdwfl_stacktrace.h>
69 : : #include <libdw.h>
70 : : #include "../libebl/libebl.h"
71 : :
72 : : // optional debug code
73 : : //#define STACKPROF_STATS_DEBUG
74 : :
75 : : using namespace std;
76 : :
77 : : ////////////////////////////////////////////////////////////////////////
78 : : // find_debuginfo callbacks
79 : :
80 : : #ifdef FIND_DEBUGINFO
81 : :
82 : : static char *debuginfo_path = NULL;
83 : :
84 : : static const Dwfl_Callbacks dwfl_cfi_callbacks =
85 : : {
86 : : .find_elf = dwflst_tracker_linux_proc_find_elf,
87 : : .find_debuginfo = dwfl_standard_find_debuginfo,
88 : : .debuginfo_path = &debuginfo_path,
89 : : };
90 : :
91 : : #else
92 : :
93 : : int
94 : 0 : nop_find_debuginfo (Dwfl_Module *mod __attribute__((unused)),
95 : : void **userdata __attribute__((unused)),
96 : : const char *modname __attribute__((unused)),
97 : : GElf_Addr base __attribute__((unused)),
98 : : const char *file_name __attribute__((unused)),
99 : : const char *debuglink_file __attribute__((unused)),
100 : : GElf_Word debuglink_crc __attribute__((unused)),
101 : : char **debuginfo_file_name __attribute__((unused)))
102 : : {
103 : : #ifdef DEBUG_MODULES
104 : : cerr << format("nop_find_debuginfo: modname={} file_name={} debuglink_file={}\n", modname, file_name, debuglink_file);
105 : : #endif
106 : 0 : return -1;
107 : : }
108 : :
109 : : static const Dwfl_Callbacks dwfl_cfi_callbacks =
110 : : {
111 : : .find_elf = dwflst_tracker_linux_proc_find_elf,
112 : : .find_debuginfo = nop_find_debuginfo, /* work with CFI only */
113 : : };
114 : :
115 : : #endif /* FIND_DEBUGINFO */
116 : :
117 : :
118 : : ////////////////////////////////////////////////////////////////////////
119 : : // class decls
120 : :
121 : : // Unwind statistics for a Dwfl and associated process.
122 : 0 : struct UnwindDwflStats {
123 : : Dwfl *dwfl;
124 : : string comm;
125 : : int max_frames; /* for diagnostic purposes */
126 : : int total_samples; /* for diagnostic purposes */
127 : : int lost_samples; /* for diagnostic purposes */
128 : : int shown_errors; /* for diagnostic purposes */
129 : : Dwfl_Unwound_Source last_unwound; /* track CFI source, for diagnostic purposes */
130 : : Dwfl_Unwound_Source worst_unwound; /* track CFI source, for diagnostic purposes */
131 : : };
132 : :
133 : : struct hash_arc {
134 : : template <class T1, class T2>
135 : 0 : size_t operator()(const pair<T1, T2> &p) const {
136 [ # # # # ]: 0 : return hash<T1>()(p.first) ^ hash<T2>()(p.second);
137 : : }
138 : : };
139 : :
140 : : // Unwind statistics for a single module identified by build-id.
141 : 0 : struct UnwindModuleStats {
142 : : map<uint64_t, uint32_t> histogram; /* sorted by pc */
143 : : unordered_map<pair<uint64_t, uint64_t>, uint32_t, hash_arc> callgraph;
144 : :
145 : 0 : void record_pc(Dwarf_Addr pc) {
146 [ # # ]: 0 : if (histogram.count(pc) == 0)
147 : 0 : histogram[pc]=1;
148 : : else
149 : 0 : histogram[pc]++;
150 : 0 : }
151 : 0 : void record_callgraph_arc(Dwarf_Addr from, Dwarf_Addr to) {
152 [ # # ]: 0 : pair<uint64_t, uint64_t> arc(from, to);
153 [ # # ]: 0 : if (callgraph.count(arc) == 0)
154 : 0 : callgraph[arc]=1;
155 : : else
156 : 0 : callgraph[arc]++;
157 : 0 : }
158 : : };
159 : :
160 : : struct UnwindStatsTable
161 : : {
162 : : unordered_map<pid_t, UnwindDwflStats> dwfl_tab;
163 : : unordered_map<string, UnwindModuleStats> buildid_tab;
164 : : typedef map<string, UnwindModuleStats> buildid_map_t;
165 : :
166 : 8 : UnwindStatsTable () {}
167 : 16 : ~UnwindStatsTable () {}
168 : :
169 : : UnwindDwflStats *pid_find_or_create(pid_t pid);
170 : : string pid_find_comm(pid_t pid);
171 : : Dwfl *pid_find_dwfl(pid_t pid);
172 : : void pid_store_dwfl(pid_t pid, Dwfl *dwfl);
173 : :
174 : : UnwindModuleStats *buildid_find(string buildid);
175 : : UnwindModuleStats *buildid_find_or_create(string buildid, Dwfl_Module *mod);
176 : :
177 : : void print_summary() const;
178 : : };
179 : :
180 : : class PerfConsumer;
181 : :
182 : : // A PerfReader creates perf_events fds, monitors for events,
183 : : // and dispatches decoded forms to a PerfConsumer.
184 : : class PerfReader
185 : : {
186 : : private:
187 : : /* Sized by number of CPUs or threads: */
188 : : vector<int> perf_fds;
189 : : vector<perf_event_mmap_page *> perf_headers;
190 : : vector<pollfd> pollfds;
191 : :
192 : : PerfConsumer *consumer; // XXX: pluralize
193 : : Ebl *default_ebl;
194 : : uint64_t sample_regs_user;
195 : : int sample_regs_count;
196 : : bool enabled;
197 : : int page_size;
198 : : int page_count;
199 : : int mmap_size;
200 : : vector<uint8_t> event_wraparound_temp; // for events straddling ring buffer end
201 : :
202 : : void decode_event(const perf_event_header* ehdr);
203 : :
204 : : public:
205 : : // PerfReader(perf_event_attr* attr, int pid, PerfConsumer* consumer); // TODO attach to process hierarchy; may modify *attr
206 : : PerfReader(perf_event_attr* attr, PerfConsumer* consumer, int pid=-1); // TODO systemwide; may modify *attr
207 : : ~PerfReader();
208 : :
209 : : void process_some(); // run briefly, relay decoded perf_events to consumer
210 : 0 : uint64_t regs_mask() { return this->sample_regs_user; }
211 : 0 : Ebl *ebl() { return this->default_ebl; }
212 : : };
213 : :
214 : : // A PerfConsumer receives both raw and decoded (fields split out into function parameters)
215 : : // perf event records from a PerfReader. Pure interface.
216 : : class PerfConsumer
217 : : {
218 : : protected:
219 : : PerfReader *reader; /* access sample_regs_user etc. metadata */
220 : :
221 : : public:
222 : 8 : PerfConsumer() {}
223 : : PerfConsumer(PerfReader *reader) : reader(reader) {}
224 : 8 : void set_reader(PerfReader *reader) { this->reader = reader; }
225 : :
226 : 8 : virtual ~PerfConsumer() {}
227 : 0 : virtual void process(const perf_event_header* sample) {}
228 : :
229 : 0 : virtual void process_comm(const perf_event_header* sample,
230 : 0 : uint32_t pid, uint32_t tid, bool exec, const string& comm) {}
231 : 0 : virtual void process_exit(const perf_event_header* sample,
232 : : uint32_t pid, uint32_t ppid,
233 : 0 : uint32_t tid, uint32_t ptid) {}
234 : 0 : virtual void process_fork(const perf_event_header* sample,
235 : : uint32_t pid, uint32_t ppid,
236 : 0 : uint32_t tid, uint32_t ptid) {}
237 : 0 : virtual void process_sample(const perf_event_header* sample,
238 : : uint64_t ip,
239 : : uint32_t pid, uint32_t tid,
240 : : uint64_t time,
241 : : uint64_t abi,
242 : : uint32_t nregs, const uint64_t *regs,
243 : 0 : uint64_t data_size, const uint8_t *data) {}
244 : 0 : virtual void process_mmap2(const perf_event_header* sample,
245 : : uint32_t pid, uint32_t tid,
246 : : uint64_t addr, uint64_t len, uint64_t pgoff,
247 : : uint8_t build_id_size, const uint8_t *build_id,
248 : 0 : const char *filename) {}
249 : : };
250 : :
251 : : // A StatsPerfConsumer collects basic stats about perf event records.
252 : : class StatsPerfConsumer: public PerfConsumer
253 : : {
254 : : unordered_map<int,unsigned> event_type_counts;
255 : :
256 : : public:
257 : : StatsPerfConsumer() {}
258 : : ~StatsPerfConsumer(); // report to clog
259 : : void process_comm(const perf_event_header* sample,
260 : : uint32_t pid, uint32_t tid, bool exec, const string& comm);
261 : : void process_exit(const perf_event_header* sample,
262 : : uint32_t pid, uint32_t ppid,
263 : : uint32_t tid, uint32_t ptid);
264 : : void process_fork(const perf_event_header* sample,
265 : : uint32_t pid, uint32_t ppid,
266 : : uint32_t tid, uint32_t ptid);
267 : : void process_sample(const perf_event_header* sample,
268 : : uint64_t ip,
269 : : uint32_t pid, uint32_t tid,
270 : : uint64_t time,
271 : : uint64_t abi,
272 : : uint32_t nregs, const uint64_t *regs,
273 : : uint64_t data_size, const uint8_t *data);
274 : : void process_mmap2(const perf_event_header* sample,
275 : : uint32_t pid, uint32_t tid,
276 : : uint64_t addr, uint64_t len, uint64_t pgoff,
277 : : uint8_t build_id_size, const uint8_t *build_id,
278 : : const char *filename);
279 : : void process(const perf_event_header* sample);
280 : : };
281 : :
282 : : // An UnwindSample records an unwound call stack from a perf sample.
283 : 24 : struct UnwindSample
284 : : {
285 : : const perf_event_header *event;
286 : : Dwfl *dwfl;
287 : : uint32_t pid, tid;
288 : : vector<Dwarf_Addr> addrs;
289 : : int elfclass;
290 : :
291 : : Dwarf_Addr base; /* for diagnostic purposes */
292 : : Dwarf_Addr sp; /* for diagnostic purposes */
293 : : };
294 : :
295 : : class UnwindSampleConsumer;
296 : :
297 : : // A PerfConsumerUnwinder accepts decoded perf events, and relays
298 : : // UnwindSample objects to an UnwindSampleConsumer.
299 : : class PerfConsumerUnwinder: public PerfConsumer
300 : : {
301 : : UnwindSampleConsumer *consumer;
302 : : UnwindSample last_us; // XXX: why & is this safe to hang onto?
303 : : Dwflst_Process_Tracker *tracker;
304 : : UnwindStatsTable *stats;
305 : : unsigned maxframes;
306 : :
307 : : int find_procfile(Dwfl *dwfl, pid_t *pid, Elf **elf, int *elf_fd);
308 : : Dwfl *find_dwfl(pid_t pid, const uint64_t *regs, uint32_t nregs,
309 : : Elf **elf, bool *cached);
310 : :
311 : : public:
312 : : PerfConsumerUnwinder(UnwindSampleConsumer* usc, UnwindStatsTable *ust);
313 : : PerfConsumerUnwinder(UnwindSampleConsumer* usc, UnwindStatsTable *ust, PerfReader *reader);
314 : : ~PerfConsumerUnwinder();
315 : :
316 : : /* libdwfl{st} callbacks */
317 : : Dwfl *init_dwfl(pid_t pid);
318 : : int unwind_frame_cb(Dwfl_Frame *state);
319 : :
320 : : void process_comm(const perf_event_header* sample,
321 : : uint32_t pid, uint32_t tid, bool exec, const string& comm);
322 : : void process_exit(const perf_event_header* sample,
323 : : uint32_t pid, uint32_t ppid,
324 : : uint32_t tid, uint32_t ptid);
325 : : void process_fork(const perf_event_header* sample,
326 : : uint32_t pid, uint32_t ppid,
327 : : uint32_t tid, uint32_t ptid);
328 : : void process_sample(const perf_event_header* sample,
329 : : uint64_t ip,
330 : : uint32_t pid, uint32_t tid,
331 : : uint64_t time,
332 : : uint64_t abi,
333 : : uint32_t nregs, const uint64_t *regs,
334 : : uint64_t data_size, const uint8_t *data);
335 : : void process_mmap2(const perf_event_header* sample,
336 : : uint32_t pid, uint32_t tid,
337 : : uint64_t addr, uint64_t len, uint64_t pgoff,
338 : : uint8_t build_id_size, const uint8_t *build_id,
339 : : const char *filename);
340 : : };
341 : :
342 : : // An UnwindSampleConsumer receives an UnwindSample from a PerfConsumerUnwinder.
343 : : // Pure abstract.
344 : : class UnwindSampleConsumer
345 : : {
346 : : public:
347 : 8 : UnwindSampleConsumer() {}
348 : 0 : virtual ~UnwindSampleConsumer() {}
349 : : virtual void process(const UnwindSample* sample) = 0;
350 : : virtual int maxframes() = 0;
351 : : };
352 : :
353 : : // An UnwindStatsConsumer collects basic stats about
354 : : // a received stream of UnwindSamples.
355 : : class UnwindStatsConsumer: public UnwindSampleConsumer
356 : : {
357 : : UnwindStatsTable *stats;
358 : :
359 : : public:
360 : 8 : UnwindStatsConsumer(UnwindStatsTable *usc) : stats(usc) {}
361 : : ~UnwindStatsConsumer();
362 : : void process(const UnwindSample* sample);
363 : : int maxframes();
364 : : };
365 : :
366 : : // A GprofUnwindSampleConsumer instance consumes UnwindSamples and tabulates
367 : : // them by buildid, for eventual writing out into gmon.out format files.
368 : : class GprofUnwindSampleConsumer: public UnwindSampleConsumer
369 : : {
370 : : UnwindStatsTable *stats;
371 : : unordered_map<string, string> buildid_to_mainfile;
372 : : unordered_map<string, string> buildid_to_debugfile;
373 : : void record_gmon_hist(ostream &of, map<uint64_t, uint32_t> &histogram, uint64_t low_pc, uint64_t high_pc, uint64_t alignment);
374 : :
375 : : public:
376 : 0 : GprofUnwindSampleConsumer(UnwindStatsTable *usc) : stats(usc) {}
377 : : ~GprofUnwindSampleConsumer(); // writes out all the gmon.$BUILDID.out files
378 : : void record_gmon_out(const string& buildid, UnwindModuleStats& m); // write out one gmon.$BUILDID.out file
379 : : void process(const UnwindSample* sample); // accumulate hits / callgraph edges (need maxdepth=1 only)
380 : : int maxframes();
381 : : };
382 : :
383 : : // hypothetical: FlamegraphUnwindSampleConsumer, taking in a bigger maxdepth
384 : : // hypothetical: PprofUnwindSampleConsumer, https://github.com/google/pprof
385 : :
386 : :
387 : : ////////////////////////////////////////////////////////////////////////
388 : : // command line parsing and main()
389 : :
390 : : /* Name, version, and bug report address. */
391 : : ARGP_PROGRAM_VERSION_HOOK_DEF = print_version;
392 : : ARGP_PROGRAM_BUG_ADDRESS_DEF = PACKAGE_BUGREPORT;
393 : :
394 : : #define HIST_SPLIT_OPTS "none/even/flex"
395 : :
396 : : static const struct argp_option options[] =
397 : : {
398 : : { NULL, 0, NULL, OPTION_DOC, N_("Output options:"), 1 },
399 : : { "verbose", 'v', NULL, 0, N_("Increase verbosity of logging messages (modules/samples/frames/more)."), 0 },
400 : : /* TODO: Add "quiet" option suppressing summary table. */
401 : : { "gmon", 'g', NULL, 0, N_("Generate gmon.BUILDID.out files for each binary."), 0 },
402 : : { "hist-split", 'G', HIST_SPLIT_OPTS, 0, N_("Split gmon histogram output into even or flexible chunks, default 'even'."), 0 },
403 : : { "maxframes", 'n', "MAXFRAMES", 0, N_("Maximum number of frames to unwind, default 1 with --gmon, 256 otherwise."), 0 },
404 : : { "output", 'o', "DIR", 0, N_("Output directory for gmon.BUILDID.out files."), 0 },
405 : : { "force", 'f', NULL, 0, N_("Unlink output files to force writing as new."), 0 },
406 : : { "pid", 'p', "PID", 0, N_("Profile given PID, and its future children."), 0 },
407 : : #ifdef HAVE_PERFMON_PFMLIB_PERF_EVENT_H
408 : : { "event", 'e', "EVENT", 0, N_("Sample given LIBPFM event specification."), 0 },
409 : : #define ARGP_KEY_EVENT_LIST 0x1000
410 : : { "event-list", ARGP_KEY_EVENT_LIST, NULL, 0, N_("Sample given LIBPFM event specification."), 0 },
411 : : #endif
412 : : { NULL, 0, NULL, 0, NULL, 0 }
413 : : };
414 : :
415 : : static error_t parse_opt (int key, char *arg, struct argp_state *state);
416 : : static const struct argp argp =
417 : : {
418 : : options, parse_opt, "[--] [CMD]...", N_("Collect systemwide stack-trace profiles."),
419 : : NULL, NULL, NULL
420 : : };
421 : :
422 : : // How to divide the program counter histograms in gmon output:
423 : : enum hist_split_method {
424 : : HIST_SPLIT_NONE = 0, /* one histogram for the entire executable */
425 : : HIST_SPLIT_EVEN = 1, /* all histograms the same size */
426 : : HIST_SPLIT_FLEX = 2, /* variable-size histograms */
427 : : };
428 : :
429 : : // Globals for command line options:
430 : : static unsigned verbose;
431 : : static bool gmon;
432 : : static hist_split_method gmon_hist_split = HIST_SPLIT_EVEN;
433 : : static string output_dir = ".";
434 : : static bool output_force = false; // overwrite preexisting output files?
435 : : static int pid;
436 : : static int opt_maxframes = -1; // set to >= 0 to override default maxframes in consumer
437 : : static string libpfm_event;
438 : : static string libpfm_event_decoded;
439 : : static perf_event_attr attr;
440 : : static bool branch_record = false; // use accurate branch recording for call-graph arcs rather than backtrace heuristics
441 : :
442 : : // Verbosity categories:
443 : : static bool show_summary = true; /* XXX could suppress with --quiet */
444 : : static bool show_modules = false; /* -> first sample for each module */
445 : : static bool show_samples = false; /* -> every sample */
446 : : static bool show_frames = false;
447 : : static bool show_debugfile = false;
448 : : static bool show_tmi = false; /* -> perf, cfi details */
449 : :
450 : : static error_t
451 : 96 : parse_opt (int key, char *arg, struct argp_state *state)
452 : : {
453 [ + + - - : 96 : switch (key)
- - - -
+ ]
454 : : {
455 : : case ARGP_KEY_INIT:
456 : : break;
457 : :
458 : 16 : case 'v':
459 : 16 : verbose ++;
460 : 16 : break;
461 : :
462 : 0 : case 'g':
463 : 0 : gmon = true;
464 : 0 : break;
465 : :
466 : 0 : case 'G':
467 : 0 : gmon = true; // Automatically enable gmon mode since a gmon-related option was set.
468 [ # # ]: 0 : if (std::string_view(arg) == "none")
469 : 0 : gmon_hist_split = HIST_SPLIT_NONE;
470 [ # # ]: 0 : else if (std::string_view(arg) == "even")
471 : 0 : gmon_hist_split = HIST_SPLIT_EVEN;
472 [ # # ]: 0 : else if (std::string_view(arg) == "flex")
473 : 0 : gmon_hist_split = HIST_SPLIT_FLEX;
474 : : break;
475 : :
476 : 0 : case 'o':
477 : 0 : gmon = true;
478 : 0 : output_dir = arg;
479 : 0 : break;
480 : :
481 : 0 : case 'p':
482 : 0 : pid = atoi(arg);
483 [ # # ]: 0 : if (pid == 0)
484 : 0 : argp_error (state, N_("-p PID should be a positive process id."));
485 : : break;
486 : :
487 : 0 : case 'n':
488 : 0 : opt_maxframes = atoi(arg);
489 [ # # ]: 0 : if (opt_maxframes < 0)
490 : : {
491 : 0 : argp_error (state, N_("-n MAXFRAMES should be 0 or higher."));
492 : 0 : return EINVAL;
493 : : }
494 : : break;
495 : :
496 : 0 : case 'f':
497 : 0 : output_force = true;
498 : 0 : break;
499 : :
500 : : #ifdef HAVE_PERFMON_PFMLIB_PERF_EVENT_H
501 : : case 'e':
502 : : libpfm_event = arg;
503 : : break;
504 : :
505 : : case ARGP_KEY_EVENT_LIST:
506 : : {
507 : : pfm_pmu_info_t pinfo;
508 : : pfm_event_info_t info;
509 : :
510 : : pfm_err_t rc = pfm_initialize();
511 : : if (rc != PFM_SUCCESS)
512 : : {
513 : : cerr << format("ERROR: pfm_initialize failed: {}\n", pfm_strerror(rc));
514 : : exit(1);
515 : : }
516 : :
517 : : memset(&pinfo, 0, sizeof(pinfo));
518 : : memset(&info, 0, sizeof(info));
519 : : pinfo.size = sizeof(pinfo);
520 : : info.size = sizeof(info);
521 : :
522 : : for (int j = PFM_PMU_NONE; j < PFM_PMU_MAX; j++)
523 : : {
524 : : pfm_err_t ret = pfm_get_pmu_info((pfm_pmu_t)j, &pinfo);
525 : : if (ret != PFM_SUCCESS)
526 : : continue;
527 : : if (! pinfo.is_present)
528 : : continue;
529 : : for (int i = pinfo.first_event; i != -1; i = pfm_get_event_next(i))
530 : : {
531 : : ret = pfm_get_event_info(i, PFM_OS_PERF_EVENT_EXT, &info);
532 : : if (ret == PFM_SUCCESS)
533 : : clog << format("{}::{}\n", pinfo.name, info.name);
534 : : }
535 : : }
536 : : }
537 : : exit(0);
538 : : #endif
539 : :
540 : : default:
541 : : return ARGP_ERR_UNKNOWN;
542 : : }
543 : : return 0;
544 : : }
545 : :
546 : : sig_atomic_t interrupted;
547 : :
548 : 0 : void sigint_handler (int sig)
549 : : {
550 : 0 : interrupted ++;
551 [ # # ]: 0 : if (interrupted > 1)
552 : 0 : _exit(1);
553 : 0 : }
554 : :
555 : 16 : int main (int argc, char *argv[])
556 : : {
557 : 16 : int remaining;
558 : 16 : int pipefd[2] = {-1, -1}; // for post-fork sync with CMD child process
559 : 16 : bool has_cmd = false;
560 : 16 : (void) argp_parse (&argp, argc, argv, 0, &remaining, NULL);
561 : :
562 : : /* show_summary is true by default */
563 [ + - ]: 16 : if (verbose > 0) show_modules = true;
564 [ - + ]: 16 : if (verbose > 1) show_samples = true;
565 [ - + ]: 16 : if (verbose > 2) show_frames = true;
566 [ - + ]: 16 : if (verbose > 3) show_debugfile = true;
567 [ - + ]: 16 : if (verbose > 4) show_tmi = true;
568 : :
569 [ - + - - ]: 16 : if (pid > 0 && remaining < argc) // got a pid AND a cmd? reject
570 : : {
571 : 0 : cerr << format("ERROR: Must not specify both -p PID and CMD\n");
572 : 0 : exit(1);
573 : : }
574 : :
575 : 16 : bool systemwide = (pid == 0) || (remaining == argc);
576 : 16 : (void) systemwide;
577 : :
578 : 16 : PerfReader *pr = nullptr;
579 : 16 : UnwindStatsTable *tab = nullptr;
580 : 16 : UnwindSampleConsumer *usc = nullptr;
581 : 16 : PerfConsumerUnwinder *pcu = nullptr;
582 : 16 : StatsPerfConsumer *spc = nullptr;
583 : :
584 : 16 : try
585 : : {
586 [ + - ]: 16 : memset(&attr, 0, sizeof(attr));
587 : 16 : attr.size = sizeof(attr);
588 : :
589 [ + - ]: 16 : if (libpfm_event != "")
590 : : {
591 : : #if HAVE_PERFMON_PFMLIB_PERF_EVENT_H
592 : : pfm_err_t rc = pfm_initialize();
593 : : if (rc != PFM_SUCCESS)
594 : : {
595 : : cerr << format("ERROR: pfm_initialize failed: {}\n", pfm_strerror(rc));
596 : : exit(1);
597 : : }
598 : : char *fstr = nullptr;
599 : : pfm_perf_encode_arg_t arg = { .attr = &attr, .fstr = &fstr, .size = sizeof(arg) };
600 : : rc = pfm_get_os_event_encoding(libpfm_event.c_str(),
601 : : PFM_PLM3, /* userspace, whether systemwide or not */
602 : : PFM_OS_PERF_EVENT_EXT, &arg);
603 : : if (rc != PFM_SUCCESS)
604 : : {
605 : : cerr << format("ERROR: pfm_get_os_event_encoding failed: {}\n", pfm_strerror(rc));
606 : : exit(1);
607 : : }
608 : : if (verbose)
609 : : {
610 : : clog << format("libpfm expanded {} to {}\n", libpfm_event, fstr);
611 : : }
612 : : libpfm_event_decoded = fstr; // overwrite
613 : : free(fstr);
614 : : #endif
615 : : }
616 : : else
617 : : {
618 : : // same as: -e perf::CPU-CLOCK:freq=1000
619 : 16 : attr.type = PERF_TYPE_SOFTWARE;
620 : 16 : attr.config = PERF_COUNT_SW_CPU_CLOCK;
621 : 16 : attr.sample_freq = 1000;
622 : 16 : attr.freq = 1;
623 : 16 : attr.exclude_kernel = 1;
624 : 16 : attr.exclude_hv = 1;
625 : 16 : attr.exclude_guest = 1;
626 : : }
627 : :
628 [ + - ]: 16 : if (show_summary)
629 : : {
630 : 0 : clog << format("perf_event_attr configuration type={:x} config={:x} {}{}\n",
631 : : attr.type, attr.config,
632 [ - + + - ]: 16 : (attr.freq ? "sample_freq=" : "sample_period="),
633 : 16 : (attr.freq ? attr.sample_freq : attr.sample_period));
634 [ + - ]: 16 : clog << endl;
635 : : }
636 : :
637 [ + - ]: 16 : if (remaining < argc) // got a CMD... suffix? ok start it
638 : : {
639 : 16 : has_cmd = true;
640 : 16 : int rc = pipe(pipefd);
641 [ - + ]: 16 : if (rc < 0)
642 : : {
643 [ # # ]: 0 : cerr << format("ERROR: pipe failed: {}\n", strerror(errno));
644 : 0 : exit(1);
645 : : }
646 : :
647 : 16 : pid = fork();
648 [ + + ]: 16 : if (pid == 0) // in child
649 : : {
650 [ + - ]: 8 : close(pipefd[1]); // close write end
651 : 8 : char dummy;
652 [ + - ]: 8 : int rc = read(pipefd[0], &dummy, 1); // block until parent is ready
653 [ + - ]: 8 : if (rc != 1)
654 : : {
655 [ + - ]: 16 : cerr << format("ERROR: child sync read failed: {}\n", strerror(errno));
656 : 8 : exit(1);
657 : : }
658 [ # # ]: 0 : close(pipefd[0]);
659 : 0 : execvp(argv[remaining], &argv[remaining] /* including child argv[0] */);
660 : : // fallthrough
661 [ # # ]: 0 : cerr << format("ERROR: execvp failed: {}\n", strerror(errno));
662 : 0 : exit(1);
663 : : }
664 [ + - ]: 8 : else if (pid > 0) // in parent
665 : : {
666 [ + - ]: 8 : close(pipefd[0]); // close read end
667 : : // will write to pipefd[1] after perfreader sicced at child
668 : : }
669 : : else // error
670 : : {
671 [ # # ]: 0 : cerr << format("ERROR: fork failed: {}\n", strerror(errno));
672 : 0 : exit(1);
673 : : }
674 : : }
675 : :
676 : : // Create the perf processing pipeline:
677 [ - + ]: 8 : if (gmon)
678 : : {
679 [ # # # # ]: 0 : tab = new UnwindStatsTable();
680 [ # # # # ]: 0 : usc = new GprofUnwindSampleConsumer(tab);
681 [ # # # # ]: 0 : pcu = new PerfConsumerUnwinder(usc, tab);
682 [ # # # # ]: 0 : pr = new PerfReader(&attr, pcu, pid);
683 : : }
684 : : else
685 : : {
686 : : #ifndef STACKPROF_STATS_DEBUG
687 [ + - + - ]: 8 : tab = new UnwindStatsTable();
688 [ + - ]: 8 : usc = new UnwindStatsConsumer(tab);
689 [ + - + - ]: 8 : pcu = new PerfConsumerUnwinder(usc, tab);
690 [ + - - + ]: 8 : pr = new PerfReader(&attr, pcu, pid);
691 : : #else
692 : : /* early debug mode */
693 : : spc = new StatsPerfConsumer();
694 : : pr = new PerfReader(&attr, spc, pid);
695 : : #endif
696 : : }
697 : :
698 : 0 : signal(SIGINT, sigint_handler);
699 : 0 : signal(SIGTERM, sigint_handler);
700 : :
701 [ # # # # ]: 0 : if (pid > 0 && has_cmd) // need to release child CMD process?
702 : : {
703 [ # # ]: 0 : int rc = write(pipefd[1], "x", 1); // unblock child
704 [ # # ]: 0 : assert (rc == 1); // XXX
705 [ # # ]: 0 : close(pipefd[1]);
706 : : }
707 : :
708 [ # # ]: 0 : if (show_summary) // TODO: move before child CMD release
709 : : {
710 [ # # ]: 0 : clog << "Starting stack profile collection ";
711 [ # # # # ]: 0 : if (pid) clog << format("pid {}", pid);
712 [ # # ]: 0 : else clog << "systemwide";
713 [ # # ]: 0 : clog << "\n"; // TODO: replace with endl throughout?
714 : : }
715 : :
716 : 0 : while (true) // main loop
717 : : {
718 [ # # ]: 0 : if (interrupted) break;
719 [ # # # # ]: 0 : if (pid > 0) waitpid(pid, NULL, WNOHANG); // reap dead child to allow kill(pid, 0) to signal death
720 [ # # # # ]: 0 : if (pid > 0 && kill(pid, 0) != 0) break; // exit if child or targeted non-child process died
721 [ # # ]: 0 : pr->process_some();
722 : : }
723 : :
724 : 0 : delete pr;
725 : 0 : delete usc;
726 : 0 : delete pcu;
727 : 0 : delete spc;
728 : 0 : delete tab;
729 : : // reporting done in various destructors
730 : : }
731 [ - + ]: 8 : catch (const exception& e)
732 : : {
733 [ + - ]: 16 : cerr << format("{}\n", e.what());
734 : : // XXX for now, call only the destructors that do not print anything:
735 [ - + ]: 8 : delete pr;
736 [ + - ]: 8 : delete pcu;
737 [ + - ]: 8 : delete tab;
738 : 8 : return 1;
739 : 8 : }
740 : :
741 : 0 : return 0;
742 : : }
743 : :
744 : :
745 : : ////////////////////////////////////////////////////////////////////////
746 : : // perf reader
747 : :
748 [ + - ]: 8 : PerfReader::PerfReader(perf_event_attr* attr, PerfConsumer* consumer, int pid)
749 : : {
750 : 8 : this->page_size = getpagesize();
751 : 8 : this->page_count = 64; /* XXX May want to verify if this is a large-enough power-of-2. */
752 : 8 : this->mmap_size = this->page_size * (this->page_count + 1); // total mmap size, including header page
753 [ + - ]: 8 : this->event_wraparound_temp.resize(this->mmap_size); // NB: never resize again!
754 : 8 : this->consumer = consumer;
755 : 8 : this->consumer->set_reader(this);
756 : 8 : this->enabled = false;
757 : :
758 : 8 : struct utsname u;
759 : 8 : uname(&u);
760 : : /* XXX Possibly could be a libdwfl api, but can't be libebl since it
761 : : must be accessible by external tools. */
762 [ + - ]: 8 : int em = dwflst_arch_from_uname(u.machine);
763 [ - + ]: 8 : if (em == EM_NONE) {
764 [ # # ]: 0 : cerr << format("ERROR: Unsupported architecture: {}\n", u.machine);
765 : 0 : exit(1);
766 : : }
767 [ + - ]: 8 : this->default_ebl = ebl_openbackend_machine(em);
768 [ + - ]: 8 : this->sample_regs_user = ebl_perf_frame_regs_mask (this->default_ebl);
769 [ + - ]: 8 : this->sample_regs_count = bitset<64>(this->sample_regs_user).count();
770 : :
771 : 8 : attr->sample_regs_user = this->sample_regs_user;
772 : 8 : attr->sample_stack_user = 8192; // enough?
773 : 8 : attr->sample_type = (PERF_SAMPLE_IP | PERF_SAMPLE_TID | PERF_SAMPLE_TIME);
774 : 8 : attr->sample_type |= PERF_SAMPLE_REGS_USER;
775 : 8 : attr->sample_type |= PERF_SAMPLE_STACK_USER;
776 : : // XXX Maybe: ask for PERF_SAMPLE_CALLCHAIN, in case kernel can
777 : : // unwind for us? Would want an option to control this, to allow
778 : : // eu-stackprof to exercise our own unwinding functionality when
779 : : // testing.
780 : 8 : attr->mmap = 1;
781 : 8 : attr->mmap2 = 1;
782 : 8 : attr->exclude_kernel = 1; /* in-kernel unwinding not relevant for our usecase */
783 : 8 : attr->disabled = 1; /* will get enabled soon */
784 : 8 : attr->task = 1; // catch FORK/EXIT
785 : 8 : attr->comm = 1; // catch EXEC
786 : 8 : attr->comm_exec = 1; // catch EXEC
787 : : // attr->precise_ip = 2; // request 0 skid ... but that conflicts with PERF_COUNT_HW_BRANCH_INSTRUCTIONS:freq=4000
788 : 8 : attr->build_id = 1; // request build ids in MMAP2 events
789 : :
790 [ + - ]: 8 : if (pid > 0) // actually only once, to allow break in case of error
791 : 8 : attr->inherit = 1; // propagate to child processes
792 : :
793 [ - + ]: 8 : if (show_tmi)
794 : : {
795 [ # # ]: 0 : clog << "perf_event_attr hexdump: ";
796 : : auto bytes = (unsigned char *)attr;
797 [ # # ]: 0 : for (size_t x = 0; x < sizeof(*attr); x++)
798 : 0 : clog << ((x % 8) ? "" : " ")
799 : 0 : << ((x % 32) ? "" : "\n")
800 [ # # # # : 0 : << format("{:02x}", (unsigned)bytes[x]);
# # # # #
# ]
801 [ # # ]: 0 : clog << "\n";
802 : : }
803 : :
804 : : // Iterate over all cpus to handle possible concurrency, even if
805 : : // attaching to a single pid, because we set ->inherit=1.
806 : 8 : int ncpus = sysconf(_SC_NPROCESSORS_CONF);
807 [ + + ]: 40 : for (int cpu = 0; cpu < ncpus; cpu++)
808 : : {
809 [ - + ]: 32 : int fd = syscall(__NR_perf_event_open, attr,
810 : : (pid > 0 ? pid : -1), cpu, -1,
811 : 32 : PERF_FLAG_FD_CLOEXEC);
812 [ + - ]: 32 : if (fd < 0)
813 : : {
814 [ + - ]: 64 : cerr << format("WARNING: unable to open perf fd for cpu {}: {}\n", cpu, strerror(errno));
815 : 32 : continue;
816 : : }
817 : 0 : void *buf = mmap(NULL, this->mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
818 [ # # ]: 0 : if (buf == MAP_FAILED)
819 : : {
820 [ # # ]: 0 : cerr << format("ERROR: perf event mmap failed: {}\n", strerror(errno));
821 [ # # ]: 0 : close(fd);
822 : 0 : continue;
823 : : }
824 [ # # ]: 0 : this->perf_fds.push_back(fd);
825 [ # # ]: 0 : this->perf_headers.push_back((perf_event_mmap_page *)buf);
826 : 0 : struct pollfd pfd = { .fd = fd, .events = POLLIN };
827 [ # # ]: 0 : this->pollfds.push_back(pfd);
828 : : }
829 : :
830 [ + - ]: 8 : if (this->perf_fds.size() == 0)
831 [ + - ]: 8 : throw runtime_error("ERROR: no perf fds opened");
832 : 8 : }
833 : :
834 : 0 : PerfReader::~PerfReader()
835 : : {
836 [ # # ]: 0 : for (auto fd : this->perf_fds)
837 : 0 : close(fd);
838 [ # # ]: 0 : for (auto m : this->perf_headers)
839 : 0 : munmap((void *)m, this->mmap_size);
840 : 0 : ebl_closebackend (this->default_ebl);
841 : 0 : }
842 : :
843 : 0 : uint64_t millis_monotonic()
844 : : {
845 [ # # ]: 0 : return chrono::duration_cast<chrono::milliseconds>(chrono::steady_clock::now().time_since_epoch()).count();
846 : : }
847 : :
848 : : static inline uint64_t
849 : 0 : ring_buffer_read_head(volatile struct perf_event_mmap_page *base)
850 : : {
851 : 0 : uint64_t head = base->data_head;
852 : 0 : asm volatile("" ::: "memory"); // memory fence
853 : 0 : return head;
854 : : }
855 : :
856 : : static inline void
857 : 0 : ring_buffer_write_tail(volatile struct perf_event_mmap_page *base,
858 : : uint64_t tail)
859 : : {
860 : 0 : asm volatile("" ::: "memory"); // memory fence
861 : 0 : base->data_tail = tail;
862 : : }
863 : :
864 : : // TODO: diagnostics to see how well the buffer processing keeps from overflow
865 : 0 : void PerfReader::process_some()
866 : : {
867 [ # # ]: 0 : if (!this->enabled)
868 : : {
869 [ # # ]: 0 : for (auto fd : this->perf_fds)
870 : 0 : ioctl(fd, PERF_EVENT_IOC_ENABLE, 0 /* value ignored */);
871 : 0 : this->enabled = true;
872 : : }
873 : :
874 : 0 : uint64_t starttime = millis_monotonic();
875 : 0 : uint64_t endtime = starttime + 1000; // run at most for one second
876 : 0 : uint64_t ring_buffer_size = this->page_size * this->page_count; // just the ring buffer size
877 : :
878 [ # # ]: 0 : while (!interrupted)
879 : : {
880 : 0 : uint64_t now = millis_monotonic();
881 [ # # ]: 0 : if (endtime < now)
882 : : break;
883 [ # # ]: 0 : int ready = poll(this->pollfds.data(), this->pollfds.size(), (int)(endtime-now)); // wait a little while
884 [ # # ]: 0 : if (ready < 0)
885 : : break;
886 : :
887 [ # # ]: 0 : for (size_t i = 0; i < pollfds.size(); i++)
888 [ # # ]: 0 : if (this->pollfds[i].revents & POLLIN) // found an fd with fresh yummy events
889 : : {
890 : 0 : perf_event_mmap_page *header = perf_headers[i];
891 : 0 : uint64_t data_head = ring_buffer_read_head(header);
892 : 0 : uint64_t data_tail = header->data_tail;
893 : 0 : uint8_t *base = ((uint8_t *)header) + this->page_size;
894 : 0 : struct perf_event_header *ehdr;
895 : 0 : size_t ehdr_size;
896 : :
897 [ # # ]: 0 : while (data_head != data_tail) // consume all packets in ring buffer XXX why?
898 : : {
899 : 0 : ehdr = (perf_event_header *) (base + (data_tail & (ring_buffer_size - 1)));
900 : 0 : ehdr_size = ehdr->size;
901 [ # # ]: 0 : if (show_tmi)
902 : 0 : clog << format("perf head={:p} tail={:p} ehdr={:p} size={:d}{:x}\n",
903 : 0 : (void *)data_head, (void *)data_tail, (void *)ehdr, ehdr_size, 0);
904 : :
905 [ # # ]: 0 : if (((uint8_t *)ehdr) + ehdr_size > base + ring_buffer_size) // mmap region wraparound?
906 : : {
907 : : // need to copy it to a contiguous temporary
908 : 0 : uint8_t *copy_start = (uint8_t *)ehdr;
909 : 0 : size_t len_first = base + ring_buffer_size - copy_start;
910 : 0 : size_t len_secnd = ehdr_size - len_first;
911 : 0 : uint8_t *event_temp = this->event_wraparound_temp.data();
912 : 0 : memcpy(event_temp, copy_start, len_first); // part at end of mmap'd region
913 : 0 : memcpy(event_temp + len_first, base, len_secnd); // part at beginning of mmap'd region
914 : 0 : ehdr = (perf_event_header *)event_temp;
915 : : }
916 : :
917 : 0 : this->decode_event(ehdr);
918 : 0 : data_tail += ehdr_size;
919 : : }
920 : :
921 : 0 : ring_buffer_write_tail(header, data_tail);
922 : : }
923 : : }
924 : 0 : }
925 : :
926 : 0 : void PerfReader::decode_event(const perf_event_header* ehdr)
927 : : {
928 : 0 : consumer->process(ehdr); // allow general processing
929 : :
930 : : // ... and decode into individual event types
931 [ # # # # : 0 : switch (ehdr->type)
# # ]
932 : : {
933 : 0 : case PERF_RECORD_SAMPLE:
934 : 0 : {
935 : 0 : const uint8_t *data = reinterpret_cast<const uint8_t *>(ehdr) + sizeof(perf_event_header);
936 : 0 : uint64_t ip = *reinterpret_cast<const uint64_t *>(data); data += sizeof(uint64_t);
937 : 0 : uint32_t pid = *reinterpret_cast<const uint32_t *>(data); data += sizeof(uint32_t);
938 : 0 : uint32_t tid = *reinterpret_cast<const uint32_t *>(data); data += sizeof(uint32_t);
939 : 0 : uint64_t time = *reinterpret_cast<const uint64_t *>(data); data += sizeof(uint64_t);
940 : : // PERF_SAMPLE_CALLCHAIN would be here if requested
941 : 0 : uint64_t abi = *reinterpret_cast<const uint64_t *>(data); data += sizeof(uint64_t);
942 : 0 : uint32_t nregs = this->sample_regs_count;
943 : 0 : const uint64_t *regs = reinterpret_cast<const uint64_t *>(data); data += nregs * sizeof(uint64_t);
944 : 0 : uint64_t data_size = *reinterpret_cast<const uint64_t *>(data); data += sizeof(uint64_t);
945 : 0 : const uint8_t *stack_data = data;
946 : 0 : consumer->process_sample(ehdr, ip, pid, tid, time, abi, nregs, regs, data_size, stack_data);
947 : 0 : break;
948 : : }
949 : 0 : case PERF_RECORD_COMM:
950 : 0 : {
951 : 0 : const uint8_t *data = reinterpret_cast<const uint8_t *>(ehdr) + sizeof(perf_event_header);
952 : 0 : uint32_t pid = *reinterpret_cast<const uint32_t *>(data); data += sizeof(uint32_t);
953 : 0 : uint32_t tid = *reinterpret_cast<const uint32_t *>(data); data += sizeof(uint32_t);
954 : 0 : const char *comm = reinterpret_cast<const char *>(data);
955 [ # # ]: 0 : consumer->process_comm(ehdr, pid, tid, (ehdr->misc & PERF_RECORD_MISC_COMM_EXEC)/* XXX why? */, comm);
956 : 0 : break;
957 : : }
958 : 0 : case PERF_RECORD_EXIT:
959 : 0 : {
960 : 0 : const uint8_t *data = reinterpret_cast<const uint8_t *>(ehdr) + sizeof(perf_event_header);
961 : 0 : uint32_t pid = *reinterpret_cast<const uint32_t *>(data); data += sizeof(uint32_t);
962 : 0 : uint32_t ppid = *reinterpret_cast<const uint32_t *>(data); data += sizeof(uint32_t);
963 : 0 : uint32_t tid = *reinterpret_cast<const uint32_t *>(data); data += sizeof(uint32_t);
964 : 0 : uint32_t ptid = *reinterpret_cast<const uint32_t *>(data);
965 : 0 : consumer->process_exit(ehdr, pid, ppid, tid, ptid);
966 : 0 : break;
967 : : }
968 : 0 : case PERF_RECORD_FORK:
969 : 0 : {
970 : 0 : const uint8_t *data = reinterpret_cast<const uint8_t *>(ehdr) + sizeof(perf_event_header);
971 : 0 : uint32_t pid = *reinterpret_cast<const uint32_t *>(data); data += sizeof(uint32_t);
972 : 0 : uint32_t ppid = *reinterpret_cast<const uint32_t *>(data); data += sizeof(uint32_t);
973 : 0 : uint32_t tid = *reinterpret_cast<const uint32_t *>(data); data += sizeof(uint32_t);
974 : 0 : uint32_t ptid = *reinterpret_cast<const uint32_t *>(data);
975 : 0 : consumer->process_fork(ehdr, pid, ppid, tid, ptid);
976 : 0 : break;
977 : : }
978 : 0 : case PERF_RECORD_MMAP2:
979 : 0 : {
980 : 0 : const uint8_t *data = reinterpret_cast<const uint8_t *>(ehdr) + sizeof(perf_event_header);
981 : 0 : uint32_t pid = *reinterpret_cast<const uint32_t *>(data); data += sizeof(uint32_t);
982 : 0 : uint32_t tid = *reinterpret_cast<const uint32_t *>(data); data += sizeof(uint32_t);
983 : 0 : uint64_t addr = *reinterpret_cast<const uint64_t *>(data); data += sizeof(uint64_t);
984 : 0 : uint64_t len = *reinterpret_cast<const uint64_t *>(data); data += sizeof(uint64_t);
985 : 0 : uint64_t pgoff = *reinterpret_cast<const uint64_t *>(data); data += sizeof(uint64_t);
986 : 0 : uint8_t build_id_size = 0;
987 : 0 : const uint8_t *build_id = nullptr;
988 [ # # ]: 0 : if (ehdr->misc & PERF_RECORD_MISC_MMAP_BUILD_ID)
989 : : {
990 : 0 : build_id_size = *reinterpret_cast<const uint8_t *>(data); data += sizeof(uint8_t);
991 : 0 : data += sizeof(uint8_t) + sizeof(uint16_t); // skip padding
992 : 0 : build_id = reinterpret_cast<const uint8_t *>(data);
993 : 0 : data += build_id_size;
994 : : }
995 : : else
996 : : {
997 : 0 : data += 4 + 4 + 8 + 8; // skip maj, min, ino, ino_generation
998 : : }
999 : 0 : data += 2 * sizeof(uint32_t); // skip prot, flags
1000 : 0 : const char *filename = reinterpret_cast<const char *>(data);
1001 : 0 : consumer->process_mmap2(ehdr, pid, tid, addr, len, pgoff, build_id_size, build_id, filename);
1002 : 0 : break;
1003 : : }
1004 : : default:
1005 : : break;
1006 : : }
1007 : 0 : }
1008 : :
1009 : :
1010 : : ////////////////////////////////////////////////////////////////////////
1011 : : // perf event consumers
1012 : :
1013 : 0 : void StatsPerfConsumer::process_comm(const perf_event_header *sample,
1014 : : uint32_t pid, uint32_t tid, bool exec, const string &comm)
1015 : : {
1016 [ # # ]: 0 : if (show_modules)
1017 : : {
1018 : 0 : clog << format("process_comm: pid={} tid={} exec={} comm={}\n", pid, tid, exec, comm);
1019 : : }
1020 : 0 : }
1021 : :
1022 : 0 : void StatsPerfConsumer::process_exit(const perf_event_header *sample,
1023 : : uint32_t pid, uint32_t ppid,
1024 : : uint32_t tid, uint32_t ptid)
1025 : : {
1026 [ # # ]: 0 : if (show_modules)
1027 : : {
1028 : 0 : clog << format("process_exit: pid={} ppid={} tid={} ptid={}\n", pid, ppid, tid, ptid);
1029 : : }
1030 : 0 : }
1031 : :
1032 : 0 : void StatsPerfConsumer::process_fork(const perf_event_header *sample,
1033 : : uint32_t pid, uint32_t ppid,
1034 : : uint32_t tid, uint32_t ptid)
1035 : : {
1036 [ # # ]: 0 : if (show_modules)
1037 : : {
1038 : 0 : clog << format("process_fork: pid={} ppid={} tid={} ptid={}\n", pid, ppid, tid, ptid);
1039 : : }
1040 : 0 : }
1041 : :
1042 : 0 : void StatsPerfConsumer::process_sample(const perf_event_header *sample,
1043 : : uint64_t ip,
1044 : : uint32_t pid, uint32_t tid,
1045 : : uint64_t time,
1046 : : uint64_t abi,
1047 : : uint32_t nregs, const uint64_t *regs,
1048 : : uint64_t data_size, const uint8_t *data)
1049 : : {
1050 [ # # ]: 0 : if (show_samples)
1051 : : {
1052 : 0 : clog << format("process_sample: pid={:d} tid={:d} ip={:x} time={:d} abi={:d} nregs={:d} data_size={:d}\n",
1053 : 0 : pid, tid, ip, time, abi, nregs, data_size);
1054 : : }
1055 : 0 : }
1056 : :
1057 : 0 : void StatsPerfConsumer::process_mmap2(const perf_event_header *sample,
1058 : : uint32_t pid, uint32_t tid,
1059 : : uint64_t addr, uint64_t len, uint64_t pgoff,
1060 : : uint8_t build_id_size, const uint8_t *build_id,
1061 : : const char *filename)
1062 : : {
1063 [ # # ]: 0 : if (show_modules)
1064 : : {
1065 : 0 : clog << format("process_mmap2: pid={:d} tid={:d} addr={:x} len={:x} pgoff={:x} build_id_size={:d} filename={:s}\n",
1066 : 0 : pid, tid, addr, len, pgoff, (unsigned)build_id_size, filename);
1067 : : }
1068 : 0 : }
1069 : :
1070 : 0 : StatsPerfConsumer::~StatsPerfConsumer()
1071 : : {
1072 [ # # ]: 0 : for (const auto& kv : this->event_type_counts)
1073 : : {
1074 : : // TODO: Decode event type.
1075 : 0 : clog << format("event type {} count {}\n", kv.first, kv.second);
1076 : : }
1077 : 0 : }
1078 : :
1079 : 0 : void StatsPerfConsumer::process(const perf_event_header* ehdr)
1080 : : {
1081 : 0 : this->event_type_counts[ehdr->type] ++;
1082 : 0 : }
1083 : :
1084 : :
1085 : : ////////////////////////////////////////////////////////////////////////
1086 : : // unwind stats table for PerfConsumerUnwinder + downstream consumers
1087 : :
1088 : 0 : UnwindDwflStats *UnwindStatsTable::pid_find_or_create (pid_t pid)
1089 : : {
1090 [ # # ]: 0 : if (this->dwfl_tab.count(pid) == 0)
1091 [ # # ]: 0 : this->dwfl_tab.emplace(pid, UnwindDwflStats());
1092 : 0 : return &this->dwfl_tab[pid];
1093 : : }
1094 : :
1095 : : static const string unknown_comm = "<unknown>";
1096 : :
1097 : 0 : string UnwindStatsTable::pid_find_comm (pid_t pid)
1098 : : {
1099 : 0 : UnwindDwflStats *entry = this->pid_find_or_create(pid);
1100 : 0 : if (entry == NULL)
1101 : : return unknown_comm;
1102 [ # # ]: 0 : if (!entry->comm.empty())
1103 : 0 : return entry->comm;
1104 : 0 : string name = format("/proc/{}/comm", pid);
1105 [ # # ]: 0 : ifstream procfile(name);
1106 [ # # ]: 0 : string buf;
1107 [ # # # # : 0 : if (!procfile || !getline(procfile, buf))
# # ]
1108 [ # # ]: 0 : entry->comm = unknown_comm;
1109 : : else
1110 [ # # ]: 0 : entry->comm = buf;
1111 : :
1112 [ # # ]: 0 : return entry->comm;
1113 : 0 : }
1114 : :
1115 : 0 : Dwfl *UnwindStatsTable::pid_find_dwfl (pid_t pid)
1116 : : {
1117 [ # # ]: 0 : if (this->dwfl_tab.count(pid) == 0)
1118 : 0 : return NULL;
1119 : 0 : return this->dwfl_tab[pid].dwfl;
1120 : : }
1121 : :
1122 : 0 : void UnwindStatsTable::pid_store_dwfl (pid_t pid, Dwfl *dwfl)
1123 : : {
1124 : 0 : UnwindDwflStats *entry = this->pid_find_or_create(pid);
1125 : 0 : if (entry == NULL)
1126 : : return;
1127 : 0 : entry->dwfl = dwfl;
1128 [ # # ]: 0 : if (show_summary)
1129 : 0 : this->pid_find_comm(pid);
1130 : : return;
1131 : : }
1132 : :
1133 : 0 : UnwindModuleStats *UnwindStatsTable::buildid_find (string buildid)
1134 : : {
1135 : 0 : if (this->buildid_tab.count(buildid) == 0)
1136 : 0 : return NULL;
1137 : 0 : return &this->buildid_tab[buildid];
1138 : : }
1139 : :
1140 : 0 : UnwindModuleStats *UnwindStatsTable::buildid_find_or_create (string buildid, Dwfl_Module *mod)
1141 : : {
1142 : 0 : if (this->buildid_tab.count(buildid) == 0)
1143 : : {
1144 [ # # ]: 0 : this->buildid_tab.emplace(buildid, UnwindModuleStats());
1145 : : /* TODO: Guess text range for mod? */
1146 : 0 : (void)mod;
1147 : : }
1148 : 0 : return &this->buildid_tab[buildid];
1149 : : }
1150 : :
1151 : 0 : void UnwindStatsTable::print_summary () const
1152 : : {
1153 : : #define PERCENT(x,tot) ((x+tot == 0)?0.0:((double)x)/((double)tot)*100.0)
1154 : 0 : int total_samples = 0;
1155 : 0 : int total_lost_samples = 0;
1156 : 0 : clog << "\n=== pid / sample counts ===\n";
1157 [ # # ]: 0 : for (auto& p : this->dwfl_tab)
1158 : : {
1159 : 0 : pid_t pid = p.first;
1160 : 0 : const UnwindDwflStats& d = p.second;
1161 : 0 : clog << format(N_("{} {} -- max {} frames, received {} samples, lost {} samples ({:.1f}%) (last {}, worst {})\n"),
1162 : 0 : pid, d.comm, d.max_frames,
1163 : 0 : d.total_samples, d.lost_samples,
1164 : 0 : PERCENT(d.lost_samples, d.total_samples),
1165 [ # # ]: 0 : dwfl_unwound_source_str(d.last_unwound),
1166 : 0 : dwfl_unwound_source_str(d.worst_unwound));
1167 : 0 : total_samples += d.total_samples;
1168 : 0 : total_lost_samples += d.lost_samples;
1169 : : }
1170 : 0 : clog << "===\n";
1171 : 0 : clog << format(N_("TOTAL -- received {} samples, lost {} samples, loaded {} processes\n"),
1172 : : total_samples, total_lost_samples,
1173 : 0 : this->dwfl_tab.size() /* TODO: If implementing eviction, need to maintain a separate count of evicted pids. */);
1174 : 0 : clog << "\n";
1175 : : #undef PERCENT
1176 : 0 : }
1177 : :
1178 : :
1179 : : ////////////////////////////////////////////////////////////////////////
1180 : : // real perf consumer: unwind helpers
1181 : :
1182 : 8 : PerfConsumerUnwinder::PerfConsumerUnwinder(UnwindSampleConsumer *usc, UnwindStatsTable *ust)
1183 [ + - ]: 8 : : consumer(usc), stats(ust)
1184 : : {
1185 [ + - ]: 8 : maxframes = usc->maxframes();
1186 [ + - ]: 8 : this->tracker = dwflst_tracker_begin(&dwfl_cfi_callbacks);
1187 : 8 : }
1188 : :
1189 : 0 : PerfConsumerUnwinder::PerfConsumerUnwinder(UnwindSampleConsumer *usc, UnwindStatsTable *ust, PerfReader *reader)
1190 [ # # ]: 0 : : consumer(usc), stats(ust)
1191 : : {
1192 [ # # ]: 0 : maxframes = usc->maxframes();
1193 : 0 : this->reader = reader;
1194 [ # # ]: 0 : this->tracker = dwflst_tracker_begin(&dwfl_cfi_callbacks);
1195 : 0 : }
1196 : :
1197 : 16 : PerfConsumerUnwinder::~PerfConsumerUnwinder()
1198 : : {
1199 : 8 : dwflst_tracker_end(this->tracker);
1200 : 16 : }
1201 : :
1202 : : /* TODO: Could be relocated to libdwfl/linux-pid-attach.c
1203 : : to remove some duplication of existing linux-pid-attach code. */
1204 : 0 : int PerfConsumerUnwinder::find_procfile(Dwfl *dwfl, pid_t *pid, Elf **elf, int *elf_fd)
1205 : : {
1206 : 0 : int err = 0; /* The errno to return. XXX libdwfl would also set this for dwfl->attacherr. */
1207 : :
1208 : : /* Make sure to report the actual PID (thread group leader) to
1209 : : dwfl_attach_state. */
1210 : 0 : string buffer = format("/proc/{}/status", *pid);
1211 [ # # ]: 0 : ifstream procfile(buffer);
1212 [ # # ]: 0 : if (!procfile)
1213 : : {
1214 : 0 : err = errno;
1215 : 0 : fail:
1216 : 0 : return err;
1217 : : }
1218 : :
1219 : 0 : string line;
1220 [ # # # # ]: 0 : while (getline (procfile, line))
1221 [ # # ]: 0 : if (startswith (line.c_str(), "Tgid:"))
1222 : : {
1223 : 0 : errno = 0;
1224 : 0 : char *endptr;
1225 : 0 : long val = strtol (&line.c_str()[5], &endptr, 10);
1226 [ # # # # ]: 0 : if ((errno == ERANGE && val == LONG_MAX)
1227 [ # # ]: 0 : || (*endptr != 0 && *endptr != '\n')
1228 : : /* <- getline(3) ambiguous on what ends the string */
1229 [ # # # # ]: 0 : || val < 0 || val != (pid_t) val)
1230 : 0 : *pid = 0;
1231 : : else
1232 : 0 : *pid = (pid_t) val;
1233 : 0 : break;
1234 : : }
1235 : :
1236 [ # # ]: 0 : if (*pid == 0)
1237 : : {
1238 : 0 : err = ESRCH;
1239 : 0 : goto fail;
1240 : : }
1241 : :
1242 : 0 : {
1243 [ # # ]: 0 : string name = format("/proc/{}/task", *pid);
1244 [ # # ]: 0 : DIR *dir = opendir(name.c_str());
1245 [ # # ]: 0 : if (dir == NULL)
1246 : : {
1247 : 0 : err = errno;
1248 : 0 : goto fail;
1249 : : }
1250 : : else
1251 [ # # ]: 0 : closedir(dir);
1252 : 0 : }
1253 : :
1254 : 0 : {
1255 [ # # ]: 0 : string name = format("/proc/{}/exe", *pid);
1256 [ # # ]: 0 : *elf_fd = open(name.c_str(), O_RDONLY);
1257 : 0 : }
1258 : :
1259 [ # # ]: 0 : if (*elf_fd >= 0)
1260 : : {
1261 [ # # ]: 0 : *elf = elf_begin(*elf_fd, ELF_C_READ_MMAP, NULL);
1262 [ # # ]: 0 : if (*elf == NULL)
1263 : : {
1264 : : /* Just ignore, dwfl_attach_state will fall back to trying
1265 : : to associate the Dwfl with one of the existing Dwfl_Module
1266 : : ELF images (to know the machine/class backend to use). */
1267 [ # # ]: 0 : if (verbose)
1268 [ # # ]: 0 : cerr << format(N_("WARNING: find_procfile pid {}: elf not found\n"), (long long) *pid);
1269 [ # # ]: 0 : close(*elf_fd);
1270 : 0 : *elf_fd = -1;
1271 : : }
1272 : : }
1273 : : else
1274 : 0 : *elf = NULL;
1275 : 0 : return 0;
1276 : 0 : }
1277 : :
1278 : 0 : Dwfl *PerfConsumerUnwinder::init_dwfl(pid_t pid)
1279 : : {
1280 : 0 : Dwfl *dwfl = dwflst_tracker_dwfl_begin(this->tracker);
1281 : :
1282 : 0 : int err = dwfl_linux_proc_report(dwfl, pid);
1283 [ # # ]: 0 : if (err < 0)
1284 : : {
1285 [ # # ]: 0 : if (verbose)
1286 : 0 : cerr << format("WARNING: dwfl_linux_proc_report pid {}: {}\n", (long long) pid, dwfl_errmsg(-1));
1287 : 0 : return NULL;
1288 : : }
1289 : 0 : err = dwfl_report_end(dwfl, NULL, NULL);
1290 [ # # ]: 0 : if (err != 0)
1291 : : {
1292 [ # # ]: 0 : if (verbose)
1293 : 0 : cerr << format("WARNING: dwfl_report_end pid {}: {}\n", (long long) pid, dwfl_errmsg(-1));
1294 : 0 : return NULL;
1295 : : }
1296 : :
1297 : : return dwfl;
1298 : : }
1299 : :
1300 : 0 : Dwfl *pcu_init_dwfl_cb (Dwflst_Process_Tracker *cb_tracker __attribute__ ((unused)),
1301 : : pid_t pid,
1302 : : void *arg)
1303 : : {
1304 : 0 : PerfConsumerUnwinder *pcu = (PerfConsumerUnwinder *)arg;
1305 : 0 : return pcu->init_dwfl(pid);
1306 : : }
1307 : :
1308 : 0 : Dwfl *PerfConsumerUnwinder::find_dwfl(pid_t pid, const uint64_t *regs, uint32_t nregs,
1309 : : Elf **out_elf, bool *cached)
1310 : : {
1311 : 0 : int machine = ebl_get_elfmachine(this->reader->ebl());
1312 [ # # ]: 0 : if (nregs < dwflst_arch_expected_frame_nregs(machine))
1313 : : {
1314 [ # # ]: 0 : if (verbose)
1315 : 0 : cerr << format(N_("WARNING: find_dwfl: nregs={}, expected at least {}\n"), nregs, ebl_frame_nregs(this->reader->ebl()));
1316 : 0 : return NULL;
1317 : : }
1318 : :
1319 : 0 : Elf *elf = NULL;
1320 : 0 : Dwfl *dwfl = dwflst_tracker_find_pid(this->tracker, pid, pcu_init_dwfl_cb, this);
1321 : 0 : int elf_fd = -1;
1322 : 0 : int err;
1323 [ # # # # ]: 0 : if (dwfl != NULL && dwfl_pid(dwfl) != -1 /* dwfl is attached */)
1324 : : {
1325 : 0 : *cached = true;
1326 : 0 : goto reuse;
1327 : : }
1328 : 0 : err = this->find_procfile(dwfl, &pid, &elf, &elf_fd);
1329 [ # # ]: 0 : if (err != 0)
1330 : : {
1331 [ # # ]: 0 : if (verbose)
1332 : 0 : cerr << format("WARNING: find_procfile pid {}: {}\n", (long long) pid, dwfl_errmsg(-1));
1333 : 0 : return NULL;
1334 : : }
1335 : :
1336 : 0 : reuse:
1337 : 0 : bool is_abi32 = this->last_us.elfclass == ELFCLASS32;
1338 : 0 : int user_regs_sp = dwflst_arch_sp_perf_reg(machine, this->reader->regs_mask(), is_abi32);
1339 : : /* Bounds check, unlikely to fail: */
1340 [ # # ]: 0 : this->last_us.sp = user_regs_sp >= 0 ? regs[user_regs_sp] : 0;
1341 : 0 : this->last_us.base = this->last_us.sp;
1342 : :
1343 [ # # ]: 0 : if (!*cached)
1344 : 0 : this->stats->pid_store_dwfl(pid, dwfl);
1345 : 0 : *out_elf = elf;
1346 : 0 : return dwfl;
1347 : : }
1348 : :
1349 : 0 : int PerfConsumerUnwinder::unwind_frame_cb(Dwfl_Frame *state)
1350 : : {
1351 : 0 : Dwarf_Addr pc;
1352 : 0 : bool isactivation;
1353 [ # # ]: 0 : if (! dwfl_frame_pc(state, &pc, &isactivation))
1354 : : {
1355 [ # # ]: 0 : if (verbose)
1356 : 0 : cerr << format("WARNING: dwfl_frame_pc: {}\n", dwfl_errmsg(-1));
1357 : 0 : return DWARF_CB_ABORT;
1358 : : }
1359 : :
1360 [ # # ]: 0 : Dwarf_Addr pc_adjusted = pc - (isactivation ? 0 : 1);
1361 : 0 : Dwarf_Addr sp;
1362 : :
1363 : 0 : int is_abi32 = (this->last_us.elfclass == ELFCLASS32);
1364 : 0 : int m = ebl_get_elfmachine(this->reader->ebl());
1365 : 0 : int user_regs_sp = dwflst_arch_sp_dwarf_reg(m, is_abi32);
1366 : : /* Bounds check, unlikely to fail: */
1367 [ # # ]: 0 : int rc = user_regs_sp >= 0 ? dwfl_frame_reg(state, user_regs_sp, &sp) : -1;
1368 [ # # ]: 0 : if (rc < 0)
1369 : : {
1370 [ # # # # ]: 0 : if (verbose && user_regs_sp < 0)
1371 : 0 : cerr << "WARNING: dwflst_arch_sp_dwarf_reg: arch unsupported\n";
1372 [ # # ]: 0 : else if (verbose)
1373 : 0 : cerr << format("WARNING: dwfl_frame_reg: {}\n", dwfl_errmsg(-1));
1374 : 0 : return DWARF_CB_ABORT;
1375 : : }
1376 : :
1377 : 0 : UnwindDwflStats *dwfl_ent = this->stats->pid_find_or_create(this->last_us.pid);
1378 : 0 : if (dwfl_ent != NULL)
1379 : : {
1380 : 0 : Dwfl_Unwound_Source unwound_source = dwfl_frame_unwound_source(state);
1381 [ # # ]: 0 : if (unwound_source > dwfl_ent->worst_unwound)
1382 : 0 : dwfl_ent->worst_unwound = unwound_source;
1383 : 0 : dwfl_ent->last_unwound = unwound_source;
1384 [ # # ]: 0 : if (show_frames)
1385 : : {
1386 : 0 : Dwfl_Module *m = dwfl_addrmodule(this->last_us.dwfl, pc);
1387 : 0 : uint64_t rel_pc = pc_adjusted;
1388 : 0 : int j = dwfl_module_relocate_address(m, &rel_pc);
1389 : 0 : (void) j;
1390 : 0 : clog << format("* frame {:d}: rel_pc={:x} raw_pc={:x} sp={:x}+{:x} [{}]\n",
1391 : 0 : this->last_us.addrs.size(), rel_pc, pc_adjusted, this->last_us.base, (sp - this->last_us.base), dwfl_unwound_source_str(unwound_source));
1392 : : }
1393 : : }
1394 : : else
1395 : : {
1396 : : if (show_frames)
1397 : : {
1398 : : Dwfl_Module *m = dwfl_addrmodule(this->last_us.dwfl, pc);
1399 : : uint64_t rel_pc = pc_adjusted;
1400 : : int j = dwfl_module_relocate_address(m, &rel_pc);
1401 : : (void) j;
1402 : : clog << format(N_("* frame {:d}: rel_pc={:x} raw_pc={:x} sp={:x}+{:x} [dwfl_ent not found]\n"),
1403 : : this->last_us.addrs.size(), rel_pc, pc_adjusted, this->last_us.base, (sp - this->last_us.base));
1404 : : }
1405 : : }
1406 [ # # ]: 0 : if (show_debugfile)
1407 : : {
1408 : 0 : Dwfl_Module *m = dwfl_addrmodule(this->last_us.dwfl, pc);
1409 [ # # ]: 0 : if (m == NULL)
1410 : : {
1411 : 0 : clog << format("* pid {:d} pc={:x} -> MODULE NOT FOUND\n",
1412 : 0 : this->last_us.pid, pc);
1413 : : }
1414 : : else
1415 : : {
1416 : 0 : const unsigned char *desc;
1417 : 0 : GElf_Addr vaddr;
1418 : 0 : int build_id_len = dwfl_module_build_id(m, &desc, &vaddr);
1419 : 0 : clog << format("* pid {:d} build_id=", this->last_us.pid);
1420 [ # # ]: 0 : for (int i = 0; i < build_id_len; ++i)
1421 : 0 : clog << format("{:02x}", static_cast<int>(desc[i]));
1422 : :
1423 : 0 : const char *mainfile;
1424 : 0 : const char *debugfile;
1425 : 0 : const char *modname = dwfl_module_info(m, NULL, NULL, NULL, NULL,
1426 : 0 : NULL, &mainfile, &debugfile);
1427 : 0 : clog << format("module={} mainfile={} debugfile={}\n",
1428 : : modname,
1429 : 0 : mainfile ? mainfile : "<none>",
1430 [ # # # # ]: 0 : debugfile ? debugfile : "<none>");
1431 : : /* TODO: Also store this data to avoid repeated extraction for
1432 : : the final buildid summary? */
1433 : : #ifdef DEBUG_MODULES
1434 : : Dwarf_Addr bias;
1435 : : Dwarf_CFI *cfi_eh = dwfl_module_eh_cfi(m, &bias);
1436 : : if (cfi_eh == NULL)
1437 : : clog << format("* pc={:x} -> NO EH_CFI\n", pc);
1438 : : #endif
1439 : : }
1440 : : }
1441 : :
1442 : 0 : this->last_us.sp = sp;
1443 : 0 : this->last_us.addrs.push_back(pc);
1444 : :
1445 : : /* e.g. gmon callgraphs only requires maxframes=1
1446 : : (initial pc + one frame for caller ID only) */
1447 [ # # ]: 0 : if (this->last_us.addrs.size() > this->maxframes)
1448 : : {
1449 : : /* XXX without maxframes, very rarely, the unwinder can loop
1450 : : infinitely; worth investigating? */
1451 : : return DWARF_CB_ABORT;
1452 : : }
1453 : : return DWARF_CB_OK;
1454 : : }
1455 : :
1456 : 0 : int pcu_unwind_frame_cb(Dwfl_Frame *state, void *arg)
1457 : : {
1458 : 0 : PerfConsumerUnwinder *pcu = (PerfConsumerUnwinder *)arg;
1459 : 0 : return pcu->unwind_frame_cb(state);
1460 : : }
1461 : :
1462 : :
1463 : : ////////////////////////////////////////////////////////////////////////
1464 : : // real perf consumer: event handler callbacks
1465 : :
1466 : 0 : void PerfConsumerUnwinder::process_comm(const perf_event_header *sample,
1467 : : uint32_t pid, uint32_t tid, bool exec, const string &comm)
1468 : : {
1469 : : // NB: Could have dwflst ditch data for process and start anew, if EXEC.
1470 : : // XXX: is this needed to avoid gradual memory leaks or pid reuse?
1471 : 0 : }
1472 : :
1473 : 0 : void PerfConsumerUnwinder::process_exit(const perf_event_header *sample,
1474 : : uint32_t pid, uint32_t ppid,
1475 : : uint32_t tid, uint32_t ptid)
1476 : : {
1477 : : // NB: Could have dwflst ditch data for process.
1478 : : // XXX: is this needed to avoid gradual memory leaks of pid reuse?
1479 : 0 : }
1480 : :
1481 : 0 : void PerfConsumerUnwinder::process_fork(const perf_event_header *sample,
1482 : : uint32_t pid, uint32_t ppid,
1483 : : uint32_t tid, uint32_t ptid)
1484 : : {
1485 : : // NB: Could have dwflst begin tracking a new process, but
1486 : : // this will likely happen automatically when a packet is received
1487 : : // from it. The short duration between fork/exec typically means
1488 : : // elfutils will pick up on the post-exec process -- we would have
1489 : : // to work hard to replicate a situation where
1490 : : // process_fork/process_comm handling are needed.
1491 : 0 : }
1492 : :
1493 : 0 : void PerfConsumerUnwinder::process_sample(const perf_event_header *sample,
1494 : : uint64_t ip,
1495 : : uint32_t pid, uint32_t tid,
1496 : : uint64_t time,
1497 : : uint64_t abi,
1498 : : uint32_t nregs, const uint64_t *regs,
1499 : : uint64_t data_size, const uint8_t *data)
1500 : : {
1501 [ # # ]: 0 : string comm;
1502 [ # # ]: 0 : if (show_summary)
1503 [ # # ]: 0 : comm = this->stats->pid_find_comm(pid);
1504 : :
1505 [ # # ]: 0 : if (show_frames)
1506 [ # # ]: 0 : clog << "\n"; /* extra newline for padding */
1507 : :
1508 : 0 : Elf *elf = NULL; // Released during dwflst_tracker_end
1509 : 0 : bool cached = false;
1510 [ # # ]: 0 : Dwfl *dwfl = this->find_dwfl(pid, regs, nregs, &elf, &cached);
1511 : 0 : UnwindDwflStats *dwfl_ent = NULL;
1512 : 0 : bool first_load = false; /* -> for show_modules: pid is loaded first time */
1513 [ # # # # : 0 : if (verbose || show_summary || show_modules)
# # ]
1514 : : {
1515 : 0 : if (dwfl_ent == NULL)
1516 [ # # ]: 0 : dwfl_ent = this->stats->pid_find_or_create(pid);
1517 [ # # ]: 0 : if (dwfl_ent->total_samples == 0)
1518 : 0 : first_load = true;
1519 : : }
1520 [ # # ]: 0 : if (dwfl == NULL)
1521 : : {
1522 [ # # # # ]: 0 : if (show_summary || show_modules)
1523 : : {
1524 : : /* dwfl_ent loaded above */
1525 : 0 : dwfl_ent->total_samples++;
1526 : 0 : dwfl_ent->lost_samples++;
1527 : : }
1528 [ # # # # ]: 0 : if (verbose && show_summary)
1529 : : {
1530 [ # # ]: 0 : cerr << format("WARNING: find_dwfl pid {} ({}) (failed)\n", (long long)pid, comm);
1531 : : }
1532 [ # # ]: 0 : else if (verbose) // XXX
1533 : : {
1534 [ # # ]: 0 : cerr << format("WARNING: find_dwfl pid {} (failed)\n", (long long)pid);
1535 : : }
1536 : 0 : return;
1537 : : }
1538 : :
1539 [ # # # # : 0 : if (show_samples || (first_load && show_modules))
# # ]
1540 : : {
1541 : 0 : bool is_abi32 = (abi == PERF_SAMPLE_REGS_ABI_32);
1542 : 0 : clog << format("find_dwfl {}pid {:d} {}({}): hdr_size={:d} size={:d}{} pc={:x} sp={:x}+{:d}\n",
1543 [ # # ]: 0 : first_load ? "newly seen " : "", (long long)pid,
1544 [ # # ]: 0 : (cached ? "(cached) " : ""), comm,
1545 : 0 : sample->size, data_size,
1546 [ # # ]: 0 : (is_abi32 ? " (32-bit)" : ""), ip,
1547 [ # # ]: 0 : this->last_us.base, 0);
1548 : : }
1549 : :
1550 [ # # ]: 0 : this->last_us.addrs.clear();
1551 [ # # ]: 0 : this->last_us.elfclass = (abi == PERF_SAMPLE_REGS_ABI_32 ? ELFCLASS32 : ELFCLASS64);
1552 : 0 : this->last_us.dwfl = dwfl;
1553 : 0 : this->last_us.pid = pid;
1554 : 0 : int rc = dwflst_perf_sample_getframes(dwfl, elf, pid, tid,
1555 : : data, data_size,
1556 : : regs, nregs,
1557 [ # # ]: 0 : this->reader->regs_mask(), abi,
1558 : : pcu_unwind_frame_cb, this);
1559 [ # # ]: 0 : if (rc < 0)
1560 : : {
1561 : : /* dwfl_ent loaded above */
1562 [ # # # # ]: 0 : if (verbose && dwfl_ent->shown_errors < 10)
1563 : : {
1564 : 0 : dwfl_ent->shown_errors ++;
1565 : 0 : cerr << format("WARNING: dwflst_perf_sample_getframes pid {}: {}{}\n",
1566 [ # # ]: 0 : (long long)pid, dwfl_errmsg(-1),
1567 [ # # # # ]: 0 : dwfl_ent->shown_errors >= 10 ?
1568 : 0 : " (...suppressing further warnings for this pid)" : "");
1569 : : }
1570 : : }
1571 [ # # ]: 0 : if (show_summary)
1572 : : {
1573 : : /* For final diagnostics. dwfl_ent loaded above */
1574 [ # # ]: 0 : if (this->last_us.addrs.size() > (unsigned long)dwfl_ent->max_frames)
1575 : 0 : dwfl_ent->max_frames = this->last_us.addrs.size();
1576 : 0 : dwfl_ent->total_samples++;
1577 [ # # # # ]: 0 : if (this->maxframes > 2 && this->last_us.addrs.size() <= 2)
1578 : 0 : dwfl_ent->lost_samples++;
1579 : : }
1580 : :
1581 [ # # ]: 0 : this->consumer->process(&this->last_us);
1582 : : return;
1583 : 0 : }
1584 : :
1585 : 0 : void PerfConsumerUnwinder::process_mmap2(const perf_event_header *sample,
1586 : : uint32_t pid, uint32_t tid,
1587 : : uint64_t addr, uint64_t len, uint64_t pgoff,
1588 : : uint8_t build_id_size, const uint8_t *build_id,
1589 : : const char *filename)
1590 : : {
1591 : 0 : Dwfl *dwfl = this->stats->pid_find_dwfl(pid);
1592 [ # # ]: 0 : if (dwfl != NULL)
1593 : : {
1594 : 0 : dwfl_report_begin_add(dwfl);
1595 : 0 : dwfl_report_module(dwfl, filename, /*start*/ addr, /*end*/ addr + len);
1596 : 0 : dwfl_report_end(dwfl, NULL, NULL);
1597 : : }
1598 : 0 : }
1599 : :
1600 : :
1601 : : ////////////////////////////////////////////////////////////////////////
1602 : : // unwind data consumers: basic statistics
1603 : :
1604 : 0 : UnwindStatsConsumer::~UnwindStatsConsumer()
1605 : : {
1606 : 0 : this->stats->print_summary();
1607 : 0 : }
1608 : :
1609 : 0 : void UnwindStatsConsumer::process(const UnwindSample *sample)
1610 : : {
1611 : : /* Most of the logic is handled by UnwindStatsTable. */
1612 : 0 : }
1613 : :
1614 : 8 : int UnwindStatsConsumer::maxframes()
1615 : : {
1616 [ - + ]: 8 : return opt_maxframes >= 0 ? opt_maxframes : 256;
1617 : : }
1618 : :
1619 : :
1620 : : ////////////////////////////////////////////////////////////////////////
1621 : : // unwind data consumers: gprof
1622 : :
1623 : : /* gmon.out file format bits */
1624 : : #define GMON_MAGIC "gmon"
1625 : : #define GMON_VERSION 1
1626 : :
1627 : : struct gmon_hdr {
1628 : : char cookie[4];
1629 : : char version[4];
1630 : : char spare[3 * 4];
1631 : : };
1632 : :
1633 : : enum gmon_entry_tag {
1634 : : GMON_TAG_TIME_HIST = 0,
1635 : : GMON_TAG_CG_ARC = 1,
1636 : : GMON_TAG_BB_COUNT = 2,
1637 : : };
1638 : :
1639 : : struct gmon_hist_hdr {
1640 : : uint8_t tag; /* GMON_TAG_TIME_HIST */
1641 : : uint8_t unused[3];
1642 : : uint64_t low_pc;
1643 : : uint64_t high_pc;
1644 : : uint32_t num_buckets;
1645 : : uint32_t prof_rate;
1646 : : char _dimension_string[16];
1647 : : };
1648 : :
1649 : 0 : void GprofUnwindSampleConsumer::record_gmon_hist(ostream &of,
1650 : : map<uint64_t, uint32_t> &histogram,
1651 : : uint64_t low_pc, uint64_t high_pc,
1652 : : uint64_t alignment)
1653 : : {
1654 : : // write one histogram from low_pc ... high_pc
1655 : 0 : uint32_t num_buckets = (high_pc-low_pc)/alignment + 1;
1656 : 0 : double result_scale = (double)((high_pc-low_pc)/sizeof(uint16_t))/num_buckets;
1657 [ # # ]: 0 : if (verbose > 5)
1658 : : /* It's the @scale value that must be kept within 0.000001 of 0.5 to
1659 : : keep gprof from complaining. */
1660 : 0 : clog << format("DEBUG +hist {:x}..{:x} (alignment {}) of {} buckets @scale {}\n",
1661 : 0 : low_pc, high_pc, alignment, num_buckets, result_scale);
1662 : :
1663 : : // write histogram record header
1664 : 0 : unsigned char tag = GMON_TAG_TIME_HIST;
1665 : 0 : of.write(reinterpret_cast<const char *>(&tag), sizeof(tag));
1666 : 0 : int wordsize = (sizeof (void *) == 8) ? 8 : 4;
1667 : 0 : if (wordsize == 4) {
1668 : : uint32_t addr = low_pc;
1669 : : of.write(reinterpret_cast<const char *>(&addr), sizeof(addr));
1670 : : addr = high_pc;
1671 : : of.write(reinterpret_cast<const char *>(&addr), sizeof(addr));
1672 : : } else {
1673 : 0 : of.write(reinterpret_cast<const char *>(&low_pc), sizeof(low_pc));
1674 : 0 : of.write(reinterpret_cast<const char *>(&high_pc), sizeof(high_pc));
1675 : : }
1676 : 0 : of.write(reinterpret_cast<const char *>(&num_buckets), sizeof(num_buckets));
1677 : 0 : uint32_t prof_rate = attr.sample_freq;
1678 : 0 : of.write(reinterpret_cast<const char *>(&prof_rate), sizeof(prof_rate));
1679 : : // dimension string is 15 chars long (not null terminated)
1680 [ # # ]: 0 : std::string dimension_base = libpfm_event.empty() ? "ticks" :
1681 : 0 : libpfm_event.substr(0, 15);
1682 [ # # ]: 0 : dimension_base.resize(15, '\0'); // ensure exactly 15 bytes
1683 [ # # ]: 0 : of.write(dimension_base.data(), 15);
1684 : : // dimension character abbreviation: just take the first char of above
1685 [ # # ]: 0 : of.write(dimension_base.data(), 1);
1686 : :
1687 : : // write histogram buckets
1688 : 0 : uint64_t bucket_addr = low_pc;
1689 : 0 : int n_overflows = 0, max_overflows = 5; // limit 'bucket overflow' spam
1690 [ # # ]: 0 : for (uint32_t bucket = 0; bucket < num_buckets; bucket++)
1691 : : {
1692 : 0 : uint16_t count = 0;
1693 : 0 : for (auto it = histogram.lower_bound(bucket_addr);
1694 [ # # ]: 0 : it != histogram.upper_bound(bucket_addr+alignment-1);
1695 : 0 : it ++)
1696 : : {
1697 [ # # ]: 0 : if (numeric_limits<uint16_t>::max() <= (int) count + (int) it->second)
1698 : : {
1699 : 0 : count = numeric_limits<uint16_t>::max();
1700 : : // XXX: a provisional error message to give a sense of
1701 : : // whether this happens often-enough to do something
1702 : : // more complex, such as adjusting the histogram
1703 : : // granularity:
1704 [ # # ]: 0 : if (n_overflows >= max_overflows) break;
1705 : 0 : n_overflows++;
1706 : 0 : cerr << format("WARNING: histogram bucket overflow at {:x}{}",
1707 : : bucket_addr,
1708 [ # # # # ]: 0 : n_overflows >= max_overflows ?
1709 [ # # ]: 0 : " (... suppressing further warnings for this histogram)" : "")
1710 : 0 : << endl;
1711 : 0 : break;
1712 : : }
1713 : 0 : count += it->second;
1714 : : }
1715 : 0 : bucket_addr += alignment;
1716 [ # # ]: 0 : of.write(reinterpret_cast<const char *>(&count), sizeof(count));
1717 : : }
1718 : 0 : }
1719 : :
1720 : 0 : void GprofUnwindSampleConsumer::record_gmon_out(const string& buildid, UnwindModuleStats& m)
1721 : : {
1722 [ # # ]: 0 : string filename = output_dir + "/" + "gmon." + buildid + ".out";
1723 [ # # # # ]: 0 : string exe_symlink_path = output_dir + "/" + "gmon." + buildid + ".exe";
1724 [ # # # # ]: 0 : string json_path = output_dir + "/" + "gmon." + buildid + ".json";
1725 : :
1726 [ # # ]: 0 : if (output_force) {
1727 [ # # # # ]: 0 : filesystem::remove(filename);
1728 [ # # # # ]: 0 : filesystem::remove(exe_symlink_path);
1729 [ # # # # ]: 0 : filesystem::remove(json_path);
1730 : : }
1731 : :
1732 [ # # # # ]: 0 : string target_path = buildid_to_mainfile[buildid];
1733 [ # # ]: 0 : if (target_path != unknown_comm) // skip .exe symlink if there's no path
1734 [ # # ]: 0 : if (symlink(target_path.c_str(), exe_symlink_path.c_str()) == -1) {
1735 : : // Handle error, e.g., print errno or throw exception
1736 [ # # ]: 0 : cerr << format("WARNING: symlink failed: {}\n", strerror(errno));
1737 : : // NB: no return needed here; proceed to write out other bits.
1738 : : // A smart enough consumer will make do with buildid based executable lookup.
1739 : : }
1740 : :
1741 [ # # ]: 0 : json_object *metadata = json_object_new_object();
1742 [ # # ]: 0 : if (!metadata) {
1743 : 0 : json_fail:
1744 [ # # ]: 0 : cerr << format("ERROR: json allocation failed: {}\n", strerror(errno));
1745 : 0 : return;
1746 : : }
1747 [ # # ]: 0 : json_object *buildid_js = json_object_new_string(buildid.c_str());
1748 [ # # ]: 0 : if (NULL == buildid_js) goto json_fail;
1749 [ # # ]: 0 : json_object_object_add(metadata, "buildid", buildid_js);
1750 : 0 : if (buildid_to_mainfile.count(buildid) != 0) {
1751 [ # # ]: 0 : const string &mainfile = buildid_to_mainfile[buildid];
1752 [ # # ]: 0 : json_object *mainfile_js = json_object_new_string(mainfile.c_str());
1753 [ # # ]: 0 : if (NULL == mainfile_js) goto json_fail;
1754 [ # # ]: 0 : json_object_object_add(metadata, "mainfile", mainfile_js);
1755 : : }
1756 : 0 : if (buildid_to_debugfile.count(buildid) != 0) {
1757 [ # # ]: 0 : const string &debugfile = buildid_to_debugfile[buildid];
1758 [ # # ]: 0 : json_object *debugfile_js = json_object_new_string(debugfile.c_str());
1759 [ # # ]: 0 : if (NULL == debugfile_js) goto json_fail;
1760 [ # # ]: 0 : json_object_object_add(metadata, "debugfile", debugfile_js);
1761 : : }
1762 [ # # ]: 0 : if (libpfm_event != "") {
1763 [ # # ]: 0 : json_object *event_js = json_object_new_string(libpfm_event.c_str());
1764 [ # # ]: 0 : if (NULL == event_js) goto json_fail;
1765 [ # # ]: 0 : json_object_object_add(metadata, "libpfm-event", event_js);
1766 : : }
1767 [ # # ]: 0 : if (libpfm_event_decoded != "") {
1768 [ # # ]: 0 : json_object *event_js = json_object_new_string(libpfm_event_decoded.c_str());
1769 [ # # ]: 0 : if (NULL == event_js) goto json_fail;
1770 [ # # ]: 0 : json_object_object_add(metadata, "libpfm-event-decoded", event_js);
1771 : : }
1772 : 0 : {
1773 [ # # ]: 0 : json_object *br_js = json_object_new_boolean(branch_record);
1774 [ # # ]: 0 : if (NULL == br_js) goto json_fail;
1775 [ # # ]: 0 : json_object_object_add(metadata, "branch-record", br_js);
1776 : : }
1777 : :
1778 [ # # ]: 0 : const char *metadata_str = json_object_to_json_string(metadata);
1779 [ # # ]: 0 : if (!metadata_str) goto json_fail;
1780 [ # # ]: 0 : ofstream of_js(json_path);
1781 [ # # ]: 0 : of_js << metadata_str;
1782 [ # # ]: 0 : of_js.close();
1783 [ # # ]: 0 : json_object_put(metadata);
1784 : :
1785 [ # # ]: 0 : ofstream of(filename, ios::binary);
1786 [ # # ]: 0 : if (!of)
1787 : : {
1788 [ # # ]: 0 : cerr << format(N_("ERROR: buildid {} -- could not open '{}' for writing\n"), buildid, filename);
1789 : 0 : return;
1790 : : }
1791 : :
1792 : : /* Write gmon header. It and other headers mostly hold
1793 : : native-endian and fixed (or native) bitwidth values. In
1794 : : principle, we should get the bitwidth/endianness from the
1795 : : particular executable associated with the buildid. But, being a
1796 : : live profiler, we don't really have to deal with CROSS
1797 : : architecture work, and for now can just hard-code the bitwidth to
1798 : : match this host program. XXX
1799 : : */
1800 : 0 : int wordsize = (sizeof(void *) == 8) ? 8 : 4;
1801 : 0 : struct gmon_hdr ghdr;
1802 [ # # ]: 0 : memcpy(&ghdr.cookie[0], GMON_MAGIC, 4);
1803 : 0 : uint32_t version = GMON_VERSION;
1804 : 0 : memcpy(&ghdr.version[0], reinterpret_cast<const char *>(&version), 4);
1805 [ # # ]: 0 : memset(&ghdr.spare[0], 0, sizeof(ghdr.spare));
1806 [ # # ]: 0 : of.write(reinterpret_cast<const char *>(&ghdr), sizeof(ghdr));
1807 : :
1808 [ # # ]: 0 : if (m.histogram.size() > 0)
1809 : : {
1810 [ # # ]: 0 : uint64_t low_pc = m.histogram.begin()->first;
1811 [ # # ]: 0 : uint64_t high_pc = m.histogram.rbegin()->first;
1812 : 0 : uint64_t alignment = (high_pc - low_pc + 1) / UINT_MAX + 1;
1813 : :
1814 [ # # ]: 0 : if (gmon_hist_split == HIST_SPLIT_NONE)
1815 : : {
1816 : : /* Put everything into one histogram. */
1817 [ # # ]: 0 : this->record_gmon_hist(of, m.histogram, low_pc, high_pc, alignment);
1818 : : }
1819 [ # # ]: 0 : else if (gmon_hist_split == HIST_SPLIT_EVEN)
1820 : : {
1821 : : /* This option attempts to satisfy gprof's histogram scale
1822 : : consistency check, which requires all values
1823 : : '(double)(high_pc-low_pc)/num_buckets' to fall within
1824 : : EPSILON. In practice, we can only be sure of this if we
1825 : : cover the address space with histograms all one size. */
1826 : :
1827 : : /* Keep the search for 'optimal' size simple -- we just need
1828 : : a plausible order of magnitude. XXX Some rechecking of
1829 : : correctness needed. */
1830 : : //uint64_t min_size = 1; // this is 'optimal' much of the time
1831 : : uint64_t min_size = 1024;
1832 : : uint64_t max_size = high_pc - low_pc;
1833 : : uint64_t opt_size = min_size;
1834 : : uint64_t opt_est = 0;
1835 : : uint64_t next_size = opt_size;
1836 [ # # ]: 0 : while (next_size < max_size)
1837 : : {
1838 : 0 : uint64_t size_inc = sizeof(struct gmon_hdr) + next_size;
1839 : 0 : uint64_t size_est = size_inc;
1840 : 0 : uint64_t pc = low_pc;
1841 [ # # ]: 0 : while (pc + size_est < high_pc)
1842 : : {
1843 : 0 : auto it = m.histogram.upper_bound(pc + size_est/alignment);
1844 [ # # ]: 0 : if (it == m.histogram.end())
1845 : : break;
1846 : 0 : pc = it->first;
1847 : 0 : size_est += sizeof(struct gmon_hdr) + next_size;
1848 : : }
1849 [ # # ]: 0 : if (opt_est == 0 || size_est < opt_est)
1850 : : {
1851 : 0 : opt_size = next_size;
1852 : 0 : opt_est = size_est;
1853 : : }
1854 : : // if (opt_est > prev_est) break; /* XXX: We've hit the lowest point. */
1855 : 0 : next_size = 2 * next_size;
1856 : : }
1857 : :
1858 : : /* Partition into histograms of opt_size.
1859 : : TODO: Need to check if low_pc must be aligned. */
1860 : 0 : uint64_t prev_pc = low_pc;
1861 : 0 : uint64_t pc = prev_pc;
1862 [ # # ]: 0 : for (const auto& p : m.histogram)
1863 : : {
1864 : 0 : pc = p.first;
1865 [ # # ]: 0 : if (pc - low_pc > opt_size)
1866 : : {
1867 : : /* Record a histogram from low_pc to low_pc+opt_size. */
1868 : 0 : this->record_gmon_hist(of, m.histogram,
1869 [ # # ]: 0 : low_pc, low_pc+opt_size-1 /* >= prev_pc */,
1870 : : alignment);
1871 : : low_pc = pc;
1872 : : }
1873 : 0 : prev_pc = pc;
1874 : : }
1875 : : /* Record a final histogram from low_pc to low_pc+opt_size.
1876 : : TODO: Edge case -- adjust for overflow of
1877 : : low_pc+opt_size at end of address space. */
1878 : 0 : this->record_gmon_hist(of, m.histogram,
1879 [ # # ]: 0 : low_pc, low_pc+opt_size-1 /* >= prev_pc */,
1880 : : alignment);
1881 : : }
1882 [ # # ]: 0 : else if (gmon_hist_split == HIST_SPLIT_FLEX)
1883 : : {
1884 : : /* Allow variable-size histograms to save on storage space.
1885 : : Will fail gprof's input consistency checks, XXX but ok
1886 : : for profiledb purposes? */
1887 : : uint64_t prev_pc = low_pc;
1888 : : uint64_t pc = prev_pc;
1889 : : /* Iterate histogram ascending by key, faster than by addr
1890 : : when we just need to scan for gaps. */
1891 [ # # ]: 0 : for (const auto& p : m.histogram)
1892 : : {
1893 : 0 : pc = p.first;
1894 : 0 : uint64_t bin_dist = (pc - prev_pc) / alignment;
1895 [ # # ]: 0 : if (bin_dist > sizeof(struct gmon_hist_hdr))
1896 : : /* XXX If we add '&& low_pc != prev_pc && pc != high_pc',
1897 : : this avoids producing a histogram with only 1 entry,
1898 : : but this is still not enough to satisfy gprof's
1899 : : histogram scale calculation. */
1900 : : {
1901 : : /* Record a histogram from low_pc to prev_pc. */
1902 [ # # ]: 0 : this->record_gmon_hist(of, m.histogram, low_pc, prev_pc, alignment);
1903 : : low_pc = pc;
1904 : : }
1905 : 0 : prev_pc = pc;
1906 : : }
1907 : : /* Record a final histogram from low_pc to pc. */
1908 [ # # ]: 0 : this->record_gmon_hist(of, m.histogram, low_pc, pc, alignment);
1909 : : }
1910 : : }
1911 : :
1912 : : /* Write call graph arcs. */
1913 [ # # ]: 0 : for (auto& p : m.callgraph)
1914 : : {
1915 : 0 : unsigned char tag = GMON_TAG_CG_ARC;
1916 [ # # ]: 0 : of.write(reinterpret_cast<const char *>(&tag), sizeof(tag));
1917 : : /* p is (from,to) -> count */
1918 : 0 : if (wordsize == 4) {
1919 : : uint32_t addr = p.first.first;
1920 : : of.write(reinterpret_cast<const char *>(&addr), sizeof(addr));
1921 : : addr = p.first.second;
1922 : : of.write(reinterpret_cast<const char *>(&addr), sizeof(addr));
1923 : : } else {
1924 : 0 : uint64_t addr = p.first.first;
1925 [ # # ]: 0 : of.write(reinterpret_cast<const char *>(&addr), sizeof(addr));
1926 : 0 : addr = p.first.second;
1927 [ # # ]: 0 : of.write(reinterpret_cast<const char *>(&addr), sizeof(addr));
1928 : : }
1929 : 0 : uint32_t count = p.second;
1930 [ # # ]: 0 : of.write(reinterpret_cast<const char *>(&count), sizeof(count));
1931 : : }
1932 : :
1933 [ # # ]: 0 : of.close();
1934 : 0 : }
1935 : :
1936 : 0 : GprofUnwindSampleConsumer::~GprofUnwindSampleConsumer()
1937 : : {
1938 [ # # ]: 0 : if (show_summary)
1939 : : {
1940 : 0 : this->stats->print_summary();
1941 : 0 : clog << "=== buildid / sample counts ===\n";
1942 : : }
1943 : :
1944 : 0 : UnwindStatsTable::buildid_map_t sorted_map(this->stats->buildid_tab.begin(), this->stats->buildid_tab.end());
1945 [ # # ]: 0 : for (auto& p : sorted_map) // traverse in sorted order
1946 : : {
1947 : 0 : const string& buildid = p.first;
1948 : 0 : UnwindModuleStats& module_stats = p.second;
1949 : 0 : this->record_gmon_out(buildid, module_stats);
1950 [ # # ]: 0 : if (show_summary)
1951 : : {
1952 : : /* In record_gmon_out we will write the buildid->path mapping
1953 : : to a json metadata file. That makes for a reasonable hint;
1954 : : debuginfod-find can be used as a mostly-functional fallback
1955 : : (for packaged rather than locally-built executables) if the
1956 : : results are moved to another system. */
1957 : 0 : string mainfile = "<unknown>";
1958 : 0 : if (buildid_to_mainfile.count(buildid) != 0)
1959 : 0 : mainfile = buildid_to_mainfile[buildid];
1960 : 0 : string debugfile = "";
1961 : 0 : if (buildid_to_debugfile.count(buildid) != 0)
1962 : 0 : debugfile = buildid_to_debugfile[buildid];
1963 : 0 : clog << format(N_("buildid {} ({}{}{}) -- received {} distinct pcs, {} callgraph arcs\n"), /* TODO also count samples / estimated histogram size? */
1964 : : buildid,
1965 : : mainfile,
1966 [ # # ]: 0 : debugfile.empty() ? "" : " +debugfile ",
1967 : : debugfile,
1968 [ # # ]: 0 : module_stats.histogram.size(),
1969 [ # # ]: 0 : module_stats.callgraph.size());
1970 : 0 : }
1971 : : }
1972 [ # # ]: 0 : if (show_summary)
1973 : : {
1974 : 0 : clog << "===\n";
1975 : 0 : clog << format(N_("TOTAL -- received {} buildids\n"), this->stats->buildid_tab.size());
1976 : : }
1977 : 0 : clog << "\n";
1978 : 0 : }
1979 : :
1980 : 0 : int GprofUnwindSampleConsumer::maxframes()
1981 : : {
1982 : : // gprof only needs one level of backtracing,
1983 : : // but user can override consumer's preference
1984 : : // with --maxframes option:
1985 [ # # ]: 0 : return opt_maxframes >= 0 ? opt_maxframes : 1;
1986 : : }
1987 : :
1988 : 0 : void GprofUnwindSampleConsumer::process(const UnwindSample *sample)
1989 : : {
1990 [ # # ]: 0 : if (sample->addrs.size() < 1)
1991 : 0 : return; // edge case -- no pc or callgraph arc
1992 : :
1993 : 0 : Dwarf_Addr pc = sample->addrs[0];
1994 [ # # ]: 0 : Dwarf_Addr pc2 = sample->addrs.size() < 2 ? 0 : sample->addrs[1];
1995 : :
1996 : 0 : Dwfl_Module *mod = dwfl_addrmodule(sample->dwfl, pc);
1997 [ # # ]: 0 : if (mod == NULL)
1998 : : return;
1999 : :
2000 : 0 : Dwfl_Module *mod2 = dwfl_addrmodule(sample->dwfl, pc2);
2001 : : // XXX: allowing mod2 == NULL -- callgraph arc will be skipped
2002 : :
2003 : : // extract buildid for pc (hit callee)
2004 : 0 : const unsigned char *desc = nullptr;
2005 : 0 : GElf_Addr vaddr;
2006 : 0 : int build_id_len = dwfl_module_build_id(mod, &desc, &vaddr);
2007 [ # # ]: 0 : if (build_id_len <= 0)
2008 : : return; // TODO: report/tabulate hit outside known modules
2009 : :
2010 : : // possible optimization would be to use the unconverted build_id_desc as hash key
2011 : 0 : string buildid;
2012 [ # # ]: 0 : for (int i = 0; i < build_id_len; ++i) {
2013 [ # # ]: 0 : buildid += format("{:02x}", static_cast<int>(desc[i]));
2014 : : }
2015 : :
2016 : 0 : const char *mainfile_cstr;
2017 : 0 : const char *debugfile_cstr;
2018 : 0 : Dwarf_Addr low_addr;
2019 : 0 : Dwarf_Addr high_addr;
2020 [ # # ]: 0 : dwfl_module_info(mod, NULL, &low_addr, &high_addr, NULL,
2021 : : NULL, &mainfile_cstr, &debugfile_cstr);
2022 [ # # # # ]: 0 : string mainfile = mainfile_cstr ? mainfile_cstr : "<unknown>";
2023 [ # # # # ]: 0 : string debugfile = debugfile_cstr ? debugfile_cstr : "";
2024 : 0 : if (!buildid_to_mainfile.count(buildid))
2025 [ # # # # ]: 0 : buildid_to_mainfile[buildid] = mainfile;
2026 : 0 : if (!buildid_to_debugfile.count(buildid))
2027 [ # # # # ]: 0 : buildid_to_debugfile[buildid] = debugfile;
2028 : : // TODO: Also monitor for collisions here.
2029 : :
2030 [ # # # # ]: 0 : UnwindModuleStats *buildid_ent = this->stats->buildid_find_or_create(buildid, mod);
2031 : :
2032 : 0 : uint64_t last_pc = pc;
2033 [ # # ]: 0 : int i = dwfl_module_relocate_address(mod, &pc);
2034 : : /* XXX: Out-of-range address seen with ld-linux.so, not useful for profiledb purposes: */
2035 [ # # # # ]: 0 : if (last_pc < low_addr || last_pc > high_addr)
2036 : : {
2037 [ # # ]: 0 : if (verbose)
2038 [ # # ]: 0 : clog << format(N_("{}: Skipping pc={:x} raw_pc={:x} outside module range start={:x}..end={:x}\n"),
2039 : 0 : mainfile, pc, last_pc, low_addr, high_addr);
2040 : 0 : return;
2041 : : }
2042 : 0 : (void) i;
2043 : : // XXX: could get dwfl_module_relocation_info(mod, i, NULL), but no need?
2044 [ # # ]: 0 : buildid_ent->record_pc(pc);
2045 : :
2046 : : // If caller & callee are in different modules, this is a cross-shared-library
2047 : : // call, so we can't track it as a call-graph arc. TODO: at least count them
2048 [ # # # # ]: 0 : if (sample->addrs.size() >= 2 && mod == mod2) // intra-module call
2049 : : {
2050 : 0 : last_pc = pc2;
2051 [ # # ]: 0 : int j = dwfl_module_relocate_address(mod, &pc2); // map pc2 also
2052 [ # # # # ]: 0 : if (last_pc < low_addr || last_pc > high_addr)
2053 : : {
2054 [ # # ]: 0 : if (verbose)
2055 [ # # ]: 0 : clog << format(N_("{}: Skipping pc={:x} raw_pc={:x} outside module range start={:x}..end={:x}\n"),
2056 : 0 : mainfile, pc2, last_pc, low_addr, high_addr);
2057 : 0 : return;
2058 : : }
2059 : 0 : (void) j;
2060 [ # # ]: 0 : buildid_ent->record_callgraph_arc(pc2, pc);
2061 : : }
2062 : 0 : }
|