]> git.ipfire.org Git - thirdparty/elfutils.git/commitdiff
snap
authorFrank Ch. Eigler <fche@redhat.com>
Sat, 6 Dec 2025 19:11:22 +0000 (14:11 -0500)
committerFrank Ch. Eigler <fche@elastic.org>
Sat, 6 Dec 2025 19:11:42 +0000 (14:11 -0500)
src/Makefile.am
src/stackprof.cxx [new file with mode: 0644]

index 4a3fb95746ca15cc3eb8e35d0d62d5dcdcece950..0e7c0f609e34655fcd5e3c943871c91f1c40333e 100644 (file)
@@ -34,6 +34,7 @@ bin_PROGRAMS = readelf nm size strip elflint findtextrel addr2line \
 
 if ENABLE_STACKTRACE
 bin_PROGRAMS += stacktrace
+bin_PROGRAMS += stackprof
 endif
 
 noinst_LIBRARIES = libar.a
@@ -111,7 +112,10 @@ endif
 elfcompress_LDADD = $(libebl) $(libelf) $(libdw) $(libeu) $(argp_LDADD)
 elfclassify_LDADD = $(libelf) $(libdw) $(libeu) $(argp_LDADD)
 srcfiles_SOURCES = srcfiles.cxx
-srcfiles_LDADD = $(libdw) $(libelf) $(libeu)  $(argp_LDADD) $(libarchive_LIBS) $(libdebuginfod)
+srcfiles_LDADD = $(libdw) $(libelf) $(libeu) $(argp_LDADD) $(libarchive_LIBS) $(libdebuginfod)
+stackprof_SOURCES = stackprof.cxx
+stackprof_CXXFLAGS = -std=c++11
+stackprof_LDADD = $(libebl) $(libdw) $(libelf) $(libeu) $(argp_LDADD)
 
 installcheck-binPROGRAMS: $(bin_PROGRAMS)
        bad=0; pid=$$$$; list="$(bin_PROGRAMS)"; for p in $$list; do \
diff --git a/src/stackprof.cxx b/src/stackprof.cxx
new file mode 100644 (file)
index 0000000..94e6ab4
--- /dev/null
@@ -0,0 +1,377 @@
+/* Collect stack-trace profiles of running program(s).
+   Copyright (C) 2025 Red Hat, Inc.
+   This file is part of elfutils.
+
+   This file is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   elfutils is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "printversion.h"
+
+#include <set>
+#include <string>
+#include <memory>
+#include <sstream>
+#include <vector>
+#include <bitset>
+#include <stdexcept>
+#include <cstring>
+#include <csignal>
+#include <cassert>
+#include <chrono>
+
+#include <sys/syscall.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <poll.h>
+#include <linux/perf_event.h>
+
+#include <dwarf.h>
+#include <argp.h>
+#include <gelf.h>
+#include <libdwfl.h>
+#include <fcntl.h>
+#include <iostream>
+#include <libdw.h>
+
+#include "../libebl/libebl.h"
+
+
+#include ELFUTILS_HEADER(dwfl_stacktrace)
+
+using namespace std;
+
+
+////////////////////////////////////////////////////////////////////////
+// class decls
+
+class PerfConsumer;
+
+
+class PerfReader
+{
+private:
+  /* Sized by number of CPUs or threads: */
+  vector<int> perf_fds;
+  vector<perf_event_mmap_page *> perf_headers;
+  vector<pollfd> pollfds;
+
+  PerfConsumer* consumer; // pluralize!
+  
+  uint64_t sample_regs_user;
+  int sample_regs_count;
+  bool enabled;
+  int page_size;
+  int page_count;
+  int mmap_size;
+
+public:
+  // PerfReader(perf_event_attr* attr, int pid, PerfConsumer* consumer); // attach to process hierarchy; may modify *attr
+  PerfReader(perf_event_attr* attr, PerfConsumer* consumer, int pid=-1);          // systemwide; may modify *attr
+  
+  ~PerfReader();
+
+  void process_some(); // run briefly, relay PerfSample events to consumer
+};
+
+
+struct PerfSample // perf event as found in ring buffer
+{
+  struct perf_event_header header;
+  uint64_t ip;
+  uint32_t pid, tid;
+  uint64_t time;
+  uint64_t abi;
+  uint64_t *regs; /* XXX variable size */
+  /* uint64_t size; */
+  /* char *data; -- XXX variable size */
+};
+
+
+class PerfConsumer
+{
+public:
+  PerfConsumer() {}
+  virtual ~PerfConsumer() {}
+  virtual void process(const PerfSample* sample) = 0;
+};
+
+
+struct UnwindSample
+{
+  // pid_t pid; etc.?
+  vector<pair<string,Dwarf_Addr>> buildid_reladdrs;
+};
+
+class UnwindSampleConsumer
+{
+public:
+  virtual unsigned maxdepth() const; // how deep unwinding is desired
+  virtual void process(const UnwindSample* sample) = 0;
+};
+
+
+class StatsPerfConsumer: public PerfConsumer
+{
+public:
+  StatsPerfConsumer() {}
+  ~StatsPerfConsumer(); // report to stdout
+  void process(const PerfSample* sample);
+};
+
+class PerfConsumerUnwinder: public PerfConsumer // a perf sample consumer that always unwinds perf samples 
+{
+public:
+  PerfConsumerUnwinder(perf_event_attr* attr, UnwindSampleConsumer* usc) {}
+  virtual ~PerfConsumerUnwinder() {}
+  void process(const PerfSample* sample); // handle process lifecycle events; relay unwound call stack events to a consumer 
+};
+
+
+class GprofUnwindSampleConsumer: public UnwindSampleConsumer
+{
+public:
+  GprofUnwindSampleConsumer() {}
+  ~GprofUnwindSampleConsumer(); // write out all the gmon.$BUILDID.out files
+  void process(const UnwindSample* sample); // accumulate hits / callgraph edges (need maxdepth=1 only)
+};
+
+// hypothetical: FlamegraphUnwindSampleConsumer, taking in a bigger maxdepth
+// hypothetical: PprofUnwindSampleConsumer, https://github.com/google/pprof
+
+
+
+////////////////////////////////////////////////////////////////////////
+// command line parsing
+
+
+/* Name and version of program.  */
+ARGP_PROGRAM_VERSION_HOOK_DEF = print_version;
+
+/* Bug report address.  */
+ARGP_PROGRAM_BUG_ADDRESS_DEF = PACKAGE_BUGREPORT;
+
+
+/* Definitions of arguments for argp functions.  */
+static const struct argp_option options[] =
+{
+  { NULL, 0, NULL, OPTION_DOC, N_("Output options:"), 1 },
+  { "verbose", 'v', NULL, 0,
+    N_ ("Increase verbosity of logging messages."), 0 },
+  { "gmon", 'g', NULL, 0, N_("Generate gmon.out files for each binary."), 0 },
+  // --pid $PID
+  // --cmd $CMD
+  // --systemwide [assumed for now]
+  // --event $LIBPFM
+  { NULL, 0, NULL, 0, NULL, 0 }
+};
+
+/* Short description of program.  */
+static const char doc[] = N_("Collect systemwide stack-trace profiles.");
+/* Strings for arguments in help texts.  */
+static const char args_doc[] = N_("");
+/* Prototype for option handler.  */
+static error_t parse_opt (int key, char *arg, struct argp_state *state);
+/* Data structure to communicate with argp functions.  */
+static const struct argp argp =
+{
+  options, parse_opt, args_doc, doc, NULL, NULL, NULL
+};
+
+
+// Globals set based on command line options:
+
+static unsigned verbose;
+static bool gmon;
+
+
+static error_t
+parse_opt (int key, char *arg, struct argp_state *state)
+{
+  /* Suppress "unused parameter" warning.  */
+  (void)arg;
+  (void)state;
+  switch (key)
+    {
+    case ARGP_KEY_INIT:
+      break;
+
+    case 'v':
+      verbose ++;
+      break;
+
+    case 'g':
+      gmon = true;
+      break;
+      
+    default:
+      return ARGP_ERR_UNKNOWN;
+    }
+  return 0;
+}
+
+
+sig_atomic_t interrupted;
+
+void sigint_handler(int sig)
+{
+  interrupted ++;
+  if (interrupted > 1)
+    _exit(1);
+}
+
+
+
+int
+main (int argc, char *argv[])
+{
+  (void) argp_parse (&argp, argc, argv, ARGP_IN_ORDER, NULL /* CMD */, NULL);
+
+
+  try
+    {
+      // Create the perf processing pipeline as per command line options
+      GprofUnwindSampleConsumer usc;
+      perf_event_attr x; // initialize
+      PerfConsumerUnwinder pcu(&x, &usc);
+      PerfReader pr(&x, &pcu); // , CMD->fork->pid
+      
+      signal(SIGINT, sigint_handler);
+      signal(SIGTERM, sigint_handler);      
+      
+      while (! interrupted)
+        pr.process_some();
+    }
+  catch (const exception& e)
+    {
+      cerr << e.what() << endl; 
+    }
+  
+  return 0;
+}
+
+
+
+////////////////////////////////////////////////////////////////////////
+// perf reader
+
+PerfReader::PerfReader(perf_event_attr* attr, PerfConsumer* consumer, int pid)
+{
+  this->page_size = getpagesize();
+  this->page_count = 64; /* TODO: Decide on a large-enough power-of-2. */
+  this->mmap_size = this->page_size * (this->page_count + 1);
+
+  Ebl *default_ebl = NULL; // XXX
+  this->sample_regs_user = ebl_perf_frame_regs_mask (default_ebl);
+  this->sample_regs_count = bitset<64>(this->sample_regs_user).count();
+  attr->sample_regs_user = this->sample_regs_user;
+  attr->sample_stack_user = 8192;
+  /* TODO? attr.sample_stack_user = 65536; */
+
+  this->consumer = consumer;
+  
+  if (pid > 0) // attach to all threads
+    throw invalid_argument("pid attachment not yet supported");
+  else
+    {
+      // iterate over all cpus
+      int ncpus = sysconf(_SC_NPROCESSORS_CONF);
+      if (ncpus <= 0)
+        for (int cpu=0; cpu<ncpus; cpu++)
+          {
+            int fd = syscall(__NR_perf_event_open, attr, -1, cpu, -1, 0);
+            if (fd < 0)
+              {
+                cerr << "WARNING: unable to open perf event for cpu " << cpu
+                     << ": " << strerror(errno) << endl;
+                continue;
+              }
+            void *buf = mmap(NULL, this->mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+            if (buf == MAP_FAILED)
+              {
+                cerr << "ERROR: perf event mmap failed"
+                     << ": " << strerror(errno) << endl;
+                close(fd);
+                continue;
+              }
+            this->perf_fds.push_back(fd);
+            this->perf_headers.push_back((perf_event_mmap_page*) buf);
+            struct pollfd pfd = {.fd = fd, .events=POLLIN};
+            this->pollfds.push_back(pfd);
+          }
+    }
+}
+
+
+
+PerfReader::~PerfReader()
+{
+  for (auto fd : this->perf_fds)
+    close(fd);
+  for (auto m : this->perf_headers)
+    munmap((void*) m, this->mmap_size);
+}
+
+
+
+uint64_t millis_monotonic()
+{
+  return chrono::duration_cast<chrono::milliseconds>(chrono::steady_clock::now().time_since_epoch()).count();
+}
+
+
+void PerfReader::process_some()
+{
+  if (! this->enabled)
+    {
+      for (auto fd : this->perf_fds)
+       ioctl(fd, PERF_EVENT_IOC_ENABLE, 0 /* value ignored */);
+      this->enabled = true;
+    }
+
+  uint64_t starttime = millis_monotonic();
+  uint64_t endtime = starttime + 1000; // run at most one second
+  while (true)
+    {
+      uint64_t now = millis_monotonic();
+      if (endtime < now)
+        break;
+      int ready = poll(this->pollfds.data(), this->pollfds.size(), (int)(endtime-now));
+      if (ready < 0)
+        break;
+
+      for (auto& pollfd : this->pollfds)
+        {
+          if (pollfd.revents & POLLIN) ;
+        }
+      
+    }
+  
+
+  
+}
+
+
+
+
+////////////////////////////////////////////////////////////////////////
+// perf event consumers / unwinders
+
+
+////////////////////////////////////////////////////////////////////////
+// unwind consumers
+
+