]> git.ipfire.org Git - thirdparty/elfutils.git/commitdiff
snap
authorFrank Ch. Eigler <fche@redhat.com>
Sun, 7 Dec 2025 02:26:49 +0000 (21:26 -0500)
committerFrank Ch. Eigler <fche@elastic.org>
Sun, 7 Dec 2025 02:26:49 +0000 (21:26 -0500)
src/stackprof.cxx

index 94e6ab45f13a15e97a3871ba2af68c91e422e8f4..63e56e1c1eb38d79335543c18009252261be8ff7 100644 (file)
@@ -24,6 +24,7 @@
 #include <set>
 #include <string>
 #include <memory>
+#include <unordered_map>
 #include <sstream>
 #include <vector>
 #include <bitset>
@@ -78,26 +79,15 @@ private:
   int page_count;
   int mmap_size;
 
+  vector<uint8_t> event_wraparound_temp;
+
 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 */
+  void process_some(); // run briefly, relay perf_events to consumer
 };
 
 
@@ -106,7 +96,7 @@ class PerfConsumer
 public:
   PerfConsumer() {}
   virtual ~PerfConsumer() {}
-  virtual void process(const PerfSample* sample) = 0;
+  virtual void process(const perf_event_header* sample) = 0;
 };
 
 
@@ -126,10 +116,12 @@ public:
 
 class StatsPerfConsumer: public PerfConsumer
 {
+  unordered_map<int,unsigned> event_type_counts;
+  
 public:
   StatsPerfConsumer() {}
   ~StatsPerfConsumer(); // report to stdout
-  void process(const PerfSample* sample);
+  void process(const perf_event_header* sample);
 };
 
 class PerfConsumerUnwinder: public PerfConsumer // a perf sample consumer that always unwinds perf samples 
@@ -137,7 +129,7 @@ class PerfConsumerUnwinder: public PerfConsumer // a perf sample consumer that a
 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 
+  void process(const perf_event_header* sample); // handle process lifecycle events; relay unwound call stack events to a consumer 
 };
 
 
@@ -197,7 +189,6 @@ static const struct argp argp =
 static unsigned verbose;
 static bool gmon;
 
-
 static error_t
 parse_opt (int key, char *arg, struct argp_state *state)
 {
@@ -240,14 +231,31 @@ main (int argc, char *argv[])
 {
   (void) argp_parse (&argp, argc, argv, ARGP_IN_ORDER, NULL /* CMD */, NULL);
 
-
   try
     {
+      perf_event_attr attr;
+      memset(&attr, 0, sizeof(attr));
+      attr.size = sizeof(attr);
+      attr.type = PERF_TYPE_SOFTWARE;
+      attr.config = PERF_COUNT_SW_CPU_CLOCK;
+      attr.sample_freq = 1000;
+      attr.sample_type =  (PERF_SAMPLE_IP | PERF_SAMPLE_TID | PERF_SAMPLE_TIME);
+      attr.disabled = 1;
+      attr.exclude_kernel = 1; /* TODO: Probably don't care about this for our initial usecase. */
+      attr.mmap = 1;
+      attr.mmap2 = 1;
+      attr.inherit = 1; // propagate to child processes
+      attr.task = 1; // catch FORK/EXIT
+      attr.comm = 1; // catch EXEC
+#if 0
       // 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
+      PerfConsumerUnwinder pcu(&attr, &usc);
+      PerfReader pr(&attr, &pcu); // , CMD->fork->pid
+#else      
+      StatsPerfConsumer pcu;
+      PerfReader pr(&attr, &pcu); // , CMD->fork->pid
+#endif
       
       signal(SIGINT, sigint_handler);
       signal(SIGTERM, sigint_handler);      
@@ -272,13 +280,16 @@ 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->mmap_size = this->page_size * (this->page_count + 1); // total mmap size, incl header page
+  this->event_wraparound_temp.resize(this->mmap_size); // NB: never resize this object again!
+  
+  Ebl *default_ebl = ebl_openbackend_machine(EM_X86_64); // 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;
+  attr->sample_type |= PERF_SAMPLE_REGS_USER;
+  attr->sample_type |= PERF_SAMPLE_STACK_USER;
   /* TODO? attr.sample_stack_user = 65536; */
 
   this->consumer = consumer;
@@ -289,30 +300,32 @@ PerfReader::PerfReader(perf_event_attr* attr, PerfConsumer* consumer, int pid)
     {
       // 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);
-          }
+      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);
+        }
     }
+
+  if (this->perf_fds.size() == 0)
+    throw runtime_error("ERROR: no perf events opened");
 }
 
 
@@ -344,34 +357,80 @@ void PerfReader::process_some()
 
   uint64_t starttime = millis_monotonic();
   uint64_t endtime = starttime + 1000; // run at most one second
-  while (true)
+  uint64_t ring_buffer_size = this->page_size * this->page_count; // just the ring buffer size
+  
+  while (! interrupted)
     {
       uint64_t now = millis_monotonic();
       if (endtime < now)
         break;
-      int ready = poll(this->pollfds.data(), this->pollfds.size(), (int)(endtime-now));
+      int ready = poll(this->pollfds.data(), this->pollfds.size(), (int)(endtime-now)); // wait a little while
       if (ready < 0)
         break;
 
-      for (auto& pollfd : this->pollfds)
-        {
-          if (pollfd.revents & POLLIN) ;
-        }
-      
-    }
-  
+      for (int i = 0; i < pollfds.size(); i++)
+        if (this->pollfds[i].revents & POLLIN) // found an fd with fresh yummy events
+          {
+            perf_event_mmap_page *header = perf_headers[i];
+            uint64_t data_head = header->data_head;
+            asm volatile("" ::: "memory"); // memory fence
+            uint64_t data_tail = header->data_tail; 
+            uint8_t *base = ((uint8_t *) header) + this->page_size;
+            struct perf_event_header *ehdr;
+            size_t ehdr_size;
+
+            while (data_head != data_tail) // consume all packets in ring buffer XXX why?
+              {
+                ehdr = (perf_event_header*) (base + (data_tail & (ring_buffer_size - 1)));
+                ehdr_size = ehdr->size;
+                if (verbose > 3)
+                  clog << "perf head=" << (void*) data_head
+                       << " tail=" << (void*) data_tail
+                       << " ehdr=" << (void*) ehdr << " size=" << ehdr_size << endl;
+                
+                if (((uint8_t *)ehdr) + ehdr_size > base + ring_buffer_size) // mmap region wraparound?
+                  {
+                    // need to copy it to a contiguous temporary
+                    uint8_t *copy_start = (uint8_t*) ehdr;
+                    size_t len_first = base + ring_buffer_size - copy_start;
+                    size_t len_secnd = ehdr_size - len_first;
+                    uint8_t *event_temp = this->event_wraparound_temp.data();
+                    memcpy(event_temp, copy_start, len_first);       // part at end of mmap'd region
+                    memcpy(event_temp + len_first, base, len_secnd); // part at beginning of mmap'd region
+                    ehdr = (perf_event_header*) event_temp; 
+                  }
+
+                this->consumer->process (ehdr);
+                data_tail += ehdr_size;
+              }
 
-  
+            asm volatile("" ::: "memory"); // memory fence
+            header->data_tail = data_tail;
+          }
+    }
 }
 
 
-
-
 ////////////////////////////////////////////////////////////////////////
 // perf event consumers / unwinders
 
+StatsPerfConsumer::~StatsPerfConsumer()
+{
+  for (const auto& kv : this->event_type_counts)
+    {
+      cout << "event type " << kv.first << " count " << kv.second << endl;
+    }
+}
+
+void StatsPerfConsumer::process(const perf_event_header* ehdr)
+{
+  this->event_type_counts[ehdr->type] ++;
+}
+
+
+
 
 ////////////////////////////////////////////////////////////////////////
-// unwind consumers
+// unwind consumers // gprof