Add perf_event_open-based live LBR capture.  The backend manages per-CPU
ring buffers and feeds captured branch stacks through the same normalization
path as offline perf.data, so live and offline samples reach correlation in
the same form.  The afdo_create command line that selects live capture is
added together with that command later in the series.

afdo_tools/ChangeLog:

        * src/live_branch_stack_reader.cc: New file.
        * src/live_branch_stack_reader.h: New file.

Signed-off-by: Kugan Vivekanandarajah <[email protected]>
Co-authored-by: Dhruv Chawla <[email protected]>
Co-authored-by: Rahul Shinde <[email protected]>
---
 afdo_tools/src/live_branch_stack_reader.cc | 844 +++++++++++++++++++++
 afdo_tools/src/live_branch_stack_reader.h  |  94 +++
 2 files changed, 938 insertions(+)
 create mode 100644 afdo_tools/src/live_branch_stack_reader.cc
 create mode 100644 afdo_tools/src/live_branch_stack_reader.h

diff --git a/afdo_tools/src/live_branch_stack_reader.cc 
b/afdo_tools/src/live_branch_stack_reader.cc
new file mode 100644
index 00000000000..ca7789dce7e
--- /dev/null
+++ b/afdo_tools/src/live_branch_stack_reader.cc
@@ -0,0 +1,844 @@
+/* Copyright The GNU Toolchain Authors
+
+This file is part of GCC.
+
+GCC 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, or (at your option) any later
+version.
+
+GCC 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 GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#include "live_branch_stack_reader.h"
+
+#include "perf_event_sample_util.h"
+#include "perf.h"
+#include "perf_file_reader.h"
+
+#include <algorithm>
+#include <cerrno>
+#include <cinttypes>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+
+#include <linux/perf_event.h>
+#include <poll.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/syscall.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <vector>
+
+#ifndef __NR_perf_event_open
+#if defined(__x86_64__)
+#define PERF_EVENT_OPEN_NR 298
+#elif defined(__aarch64__)
+#define PERF_EVENT_OPEN_NR 241
+#else
+#error "Unsupported architecture for perf_event_open"
+#endif
+#else
+#define PERF_EVENT_OPEN_NR __NR_perf_event_open
+#endif
+
+namespace perf_data {
+namespace {
+
+/* Sum of each row's count weighted by its inclusive byte span.  */
+uint64_t
+total_mass_from_range_counts (const std::vector<profile_range> &ranges)
+{
+  uint64_t total = 0;
+  for (const profile_range &row : ranges)
+    {
+      if (row.end >= row.beg)
+       total += row.count * (row.end - row.beg + 1);
+    }
+  return total;
+}
+
+/* Cap on data pages per CPU, to bound total buffer memory.  */
+constexpr size_t MAX_RING_DATA_PAGES = 256;
+
+/* One CPU's perf event and its ring buffer.  */
+struct cpu_ring_buffer
+{
+  int fd = -1;
+  void *base = nullptr;
+  size_t len = 0;
+  uint64_t data_offset = 0;
+  uint64_t data_size = 0;
+};
+
+/* Online CPU ids, parsed from the kernel's list ("0-3,8").  Falls back to
+   0 to N-1 when the list cannot be read.  */
+std::vector<int>
+online_cpu_ids ()
+{
+  std::vector<int> cpus;
+  if (FILE *f = fopen ("/sys/devices/system/cpu/online", "r"))
+    {
+      char mask[512] = "";
+      const bool read_ok = fgets (mask, sizeof (mask), f) != nullptr;
+      fclose (f);
+      for (const char *p = read_ok ? mask : ""; *p != '\0';)
+       {
+         char *after = nullptr;
+         const long first = strtol (p, &after, 10);
+         if (after == p)
+           break;
+         long last = first;
+         if (*after == '-')
+           {
+             const char *range_start = after + 1;
+             last = strtol (range_start, &after, 10);
+             if (after == range_start)
+               break;
+           }
+         for (long cpu = first; cpu <= last; ++cpu)
+           cpus.push_back (static_cast<int> (cpu));
+         if (*after != ',')
+           break;
+         p = after + 1;
+       }
+    }
+  if (cpus.empty ())
+    {
+      const long ncpu = sysconf (_SC_NPROCESSORS_ONLN);
+      for (long cpu = 0; cpu < ncpu; ++cpu)
+       cpus.push_back (static_cast<int> (cpu));
+    }
+  return cpus;
+}
+
+/* Data pages to request per CPU.  perf_event_mlock_kb is a per-CPU limit, so
+   asking for more fails once every CPU is mapped.  */
+size_t
+ring_data_pages (size_t page_size)
+{
+  /* The kernel's default, if the sysctl cannot be read.  */
+  unsigned long mlock_kb = 512;
+  if (FILE *f = fopen ("/proc/sys/kernel/perf_event_mlock_kb", "r"))
+    {
+      unsigned long value = 0;
+      if (fscanf (f, "%lu", &value) == 1 && value != 0)
+       mlock_kb = value;
+      fclose (f);
+    }
+
+  /* One page goes to the metadata page, and the count must be a power of
+     two.  */
+  const size_t budget = (mlock_kb * 1024) / page_size;
+  if (budget < 2)
+    return 1;
+  size_t pages = 1;
+  while (pages * 2 <= budget - 1 && pages < MAX_RING_DATA_PAGES)
+    pages *= 2;
+  return pages;
+}
+
+/* Map BUFFER's ring, halving DATA_PAGES while the kernel refuses it: other
+   perf users share the same limit.  */
+bool
+map_cpu_ring_buffer (cpu_ring_buffer &buffer, size_t page_size,
+                    size_t data_pages)
+{
+  while (data_pages != 0)
+    {
+      const size_t len = page_size * (1 + data_pages);
+      void *base
+       = mmap (nullptr, len, PROT_READ | PROT_WRITE, MAP_SHARED, buffer.fd, 0);
+      if (base != MAP_FAILED)
+       {
+         buffer.base = base;
+         buffer.len = len;
+         return true;
+       }
+      data_pages /= 2;
+    }
+  return false;
+}
+
+} /* anonymous namespace.  */
+
+static long
+sys_perf_event_open (perf_event_attr *attr, pid_t pid, int cpu, int group_fd,
+                    unsigned long flags)
+{
+  return syscall (PERF_EVENT_OPEN_NR, attr, pid, cpu, group_fd, flags);
+}
+
+/* The component of PATH after its last separator, or PATH when it has none.  
*/
+static std::string
+path_basename (const std::string &path)
+{
+  size_t slash = path.find_last_of ("/\\");
+  return slash != std::string::npos ? path.substr (slash + 1) : path;
+}
+
+/* Wait for PID, retrying when a signal interrupts the wait, and store its wait
+   status in *STATUS when STATUS is non-null.  Returns false when PID could not
+   be reaped, in which case *STATUS is left alone: a wait status of zero is
+   indistinguishable from a clean exit and must not be reported as one.  */
+static bool
+reap_child (pid_t pid, int *status = nullptr)
+{
+  int wait_status = 0;
+  while (waitpid (pid, &wait_status, 0) < 0)
+    if (errno != EINTR)
+      return false;
+  if (status != nullptr)
+    *status = wait_status;
+  return true;
+}
+
+live_branch_stack_reader::live_branch_stack_reader (
+  const std::string &_binary_path,
+  const std::vector<std::string> &_command_argv, uint64_t _sample_period)
+  : binary_path (_binary_path), command_argv (_command_argv),
+    sample_period (_sample_period)
+{}
+
+live_branch_stack_reader::~live_branch_stack_reader () = default;
+
+/* True when MMAP_FILENAME names the binary we are profiling.  */
+bool
+live_branch_stack_reader::is_target_binary (
+  const std::string &mmap_filename) const
+{
+  if (mmap_filename.empty ())
+    return false;
+
+  /* The kernel reports unlinked files as "<path> (deleted)".  */
+  std::string mmap_path = mmap_filename;
+  size_t del = mmap_path.find (" (deleted)");
+  if (del != std::string::npos)
+    mmap_path.erase (del);
+
+  if (mmap_path == binary_path
+      || (!binary_path_resolved.empty () && mmap_path == binary_path_resolved))
+    return true;
+
+  /* Fall back to the basename: the child may be exec'd through a different
+     path (symlink, $PATH lookup) than the one named on the command line.  */
+  const std::string mmap_base = path_basename (mmap_path);
+  return mmap_base == path_basename (binary_path)
+        || (!binary_path_resolved.empty ()
+            && mmap_base == path_basename (binary_path_resolved));
+}
+
+/* Record the half-open START to END range at PGOFF as PID's mapping of the
+   profiled binary.  Keep mappings separate: pgoff alone does not establish
+   that two runtime intervals have the same affine translation.  */
+void
+live_branch_stack_reader::add_target_mapping (uint32_t pid, uint64_t start,
+                                             uint64_t end, uint64_t pgoff)
+{
+  target_mapping_pids.insert (pid);
+  const std::string &path
+    = binary_path_resolved.empty () ? binary_path : binary_path_resolved;
+  uint64_t vaddr = 0;
+  get_elf_segment_vaddr_for_pgoff (path, pgoff, vaddr);
+  bool is_pie = get_elf_is_pie (path);
+  target_mmaps_by_pid[pid].emplace_back (start, end, pgoff, vaddr, is_pie);
+}
+
+/* Note that PID mapped something else over one of its target mappings, which
+   leaves its later samples unattributable.  */
+void
+live_branch_stack_reader::note_replaced_target_mapping (uint32_t pid,
+                                                       uint64_t start,
+                                                       uint64_t end)
+{
+  const auto it = target_mmaps_by_pid.find (pid);
+  if (it == target_mmaps_by_pid.end ())
+    return;
+  for (const simple_mmap &old : it->second)
+    if (start < old.end_addr && end > old.start_addr)
+      target_executable_mappings_changed = true;
+}
+
+void
+live_branch_stack_reader::process_mmap_record (const uint8_t *payload,
+                                              size_t payload_size,
+                                              uint16_t misc)
+{
+  if (payload_size < sizeof (perf_mmap_record))
+    return;
+  perf_mmap_record mmap;
+  std::memcpy (&mmap, payload, sizeof (mmap));
+  std::string name = perf_mmap_filename_view (
+    reinterpret_cast<const char *> (payload + sizeof (perf_mmap_record)),
+    payload_size - sizeof (perf_mmap_record));
+  uint64_t end = 0;
+  if ((misc & PERF_RECORD_MISC_MMAP_DATA) != 0
+      || !try_compute_mmap_end (mmap.addr, mmap.len, &end))
+    return;
+  if (!is_target_binary (name))
+    {
+      note_replaced_target_mapping (mmap.pid, mmap.addr, end);
+      return;
+    }
+  add_target_mapping (mmap.pid, mmap.addr, end, mmap.pgoff);
+}
+
+void
+live_branch_stack_reader::process_mmap2_record (const uint8_t *payload,
+                                               size_t payload_size)
+{
+  if (payload_size < sizeof (perf_mmap2_record))
+    return;
+  perf_mmap2_record mmap2;
+  std::memcpy (&mmap2, payload, sizeof (mmap2));
+  std::string name = perf_mmap_filename_view (
+    reinterpret_cast<const char *> (payload + sizeof (perf_mmap2_record)),
+    payload_size - sizeof (perf_mmap2_record));
+  uint64_t end = 0;
+  if ((mmap2.prot & PROT_EXEC) == 0
+      || !try_compute_mmap_end (mmap2.addr, mmap2.len, &end))
+    return;
+  if (!is_target_binary (name))
+    {
+      note_replaced_target_mapping (mmap2.pid, mmap2.addr, end);
+      return;
+    }
+  add_target_mapping (mmap2.pid, mmap2.addr, end, mmap2.pgoff);
+}
+
+/* Ingest one PERF_RECORD_SAMPLE; returns true when it contributed mass.  */
+bool
+live_branch_stack_reader::process_sample_record (const uint8_t *payload,
+                                                size_t payload_size)
+{
+  if (payload_size == 0)
+    return false;
+
+  const uint8_t *ptr = payload;
+  const uint8_t *const end = payload + payload_size;
+  uint64_t ip = 0;
+  bool have_ip = false;
+  uint32_t pid = 0;
+  uint64_t observed_period = 0;
+  uint64_t sample_time = 0;
+
+  if (!perf_event_sample_util::walk_sample_before_branch (
+       ptr, end, sample_type, 0, &ip, &have_ip, &pid, nullptr,
+       &observed_period, &sample_time))
+    return false;
+  if (observed_period != sample_period)
+    return false;
+
+  uint64_t nr = 0;
+  const perf_branch_stack_entry *entries = nullptr;
+  if (!perf_event_sample_util::walk_sample_branch_stack (
+       ptr, end, branch_sample_type, sample_type, &nr, &entries))
+    return false;
+
+  if (!perf_event_sample_util::walk_sample_after_branch (
+       ptr, end, sample_type, 0, 0)
+      || ptr != end)
+    return false;
+
+  const auto mmap_it = target_mmaps_by_pid.find (pid);
+  if (mmap_it == target_mmaps_by_pid.end ())
+    return false;
+  const std::vector<simple_mmap> &pid_mmaps = mmap_it->second;
+
+  /* Primary path: LBR branch stack.  Sample IP fallbacks improve coverage
+     when the stack is empty or has no target branches.  */
+  uint64_t dwarf_ip = 0;
+  const bool ip_is_target
+    = have_ip && ip != 0
+      && try_translate_runtime_with_mmaps (pid_mmaps, ip, &dwarf_ip);
+  bool recorded = record_sample_ip (
+    dwarf_ip, ip_is_target, sample_time, accumulate_sample_ips_,
+    address_frequencies);
+  if ((sample_type & perf_sample_type::BRANCH_STACK) != 0 && nr > 0
+      && accumulate_branch_stack_sample (entries,
+                                        nr,
+                                        *this,
+                                        sample_time,
+                                        pid_mmaps,
+                                        range_count_map,
+                                        branch_count_map))
+    recorded = true;
+
+  return recorded;
+}
+
+/* Drain the records the kernel has published into the perf ring buffer,
+   handling records that wrap around its end.  */
+void
+live_branch_stack_reader::process_ring_buffer (void *mmap_base,
+                                              uint64_t data_offset,
+                                              uint64_t data_size)
+{
+  if (mmap_base == nullptr || data_size == 0)
+    return;
+  char *base = static_cast<char *> (mmap_base);
+  perf_event_mmap_page *meta = reinterpret_cast<perf_event_mmap_page *> (base);
+  char *data = base + data_offset;
+  uint64_t ring_size = data_size;
+
+  for (;;)
+    {
+      uint64_t head = __atomic_load_n (&meta->data_head, __ATOMIC_ACQUIRE);
+      uint64_t tail = __atomic_load_n (&meta->data_tail, __ATOMIC_RELAXED);
+      if (tail == head)
+       break;
+      if (tail > head || head - tail > ring_size)
+       {
+         /* Once the producer has lapped the consumer, head - ring_size is
+            not necessarily a record boundary.  Discard this snapshot rather
+            than interpreting overwritten bytes as event headers.  */
+         ring_overwrite = true;
+         __atomic_store_n (&meta->data_tail, head, __ATOMIC_RELEASE);
+         break;
+       }
+      const uint64_t initial_tail = tail;
+      while (tail != head)
+       {
+         if (tail > head || head - tail < sizeof (perf_event_header)
+             || ring_size < sizeof (perf_event_header))
+           break;
+         uint64_t offset = tail % ring_size;
+         perf_event_header header;
+         const size_t header_first
+           = std::min<uint64_t> (sizeof (header), ring_size - offset);
+         std::memcpy (&header, data + offset, header_first);
+         if (header_first < sizeof (header))
+           std::memcpy (reinterpret_cast<uint8_t *> (&header) + header_first,
+                        data,
+                        sizeof (header) - header_first);
+         uint32_t size = header.size;
+         if (size < sizeof (perf_event_header) || size > ring_size)
+           {
+             /* Skip the bad size, so the scan cannot loop forever.  */
+             tail += sizeof (perf_event_header);
+             continue;
+           }
+         if (size > head - tail)
+           break;
+
+         std::vector<uint8_t> wrap_buf;
+         const uint8_t *payload = nullptr;
+         if (size > ring_size - offset)
+           {
+             wrap_buf.resize (size);
+             const size_t first = ring_size - offset;
+             std::memcpy (wrap_buf.data (), data + offset, first);
+             std::memcpy (wrap_buf.data () + first, data, size - first);
+             payload = wrap_buf.data () + sizeof (perf_event_header);
+           }
+         else
+           payload = reinterpret_cast<const uint8_t *> (data + offset)
+                     + sizeof (perf_event_header);
+         size_t payload_size = size - sizeof (perf_event_header);
+
+         switch (header.type)
+           {
+           case perf_record_type::SAMPLE:
+             if (process_sample_record (payload, payload_size))
+               total_samples++;
+             break;
+           case perf_record_type::MMAP:
+             process_mmap_record (payload, payload_size, header.misc);
+             break;
+           case perf_record_type::MMAP2:
+             process_mmap2_record (payload, payload_size);
+             break;
+           case perf_record_type::LOST:
+             /* Payload is { u64 id; u64 lost; }.  */
+             if (payload_size >= 2 * sizeof (uint64_t))
+               {
+                 uint64_t lost = read_u64 (payload + sizeof (uint64_t));
+                 lost_records += lost;
+               }
+             break;
+           case perf_record_type::COMM:
+             if ((header.misc & PERF_RECORD_MISC_COMM_EXEC) != 0
+                 && payload_size >= 2 * sizeof (uint32_t)
+                 && target_mapping_pids.count (read_u32 (payload)) != 0)
+               target_executable_mappings_changed = true;
+             break;
+           default:
+             break;
+           }
+         tail += size;
+       }
+      __atomic_store_n (&meta->data_tail, tail, __ATOMIC_RELEASE);
+      if (tail == initial_tail)
+       break;
+    }
+}
+
+/* Run the command under a branch-stack perf event and collect its profile.  */
+bool
+live_branch_stack_reader::load ()
+{
+  range_count_map.clear ();
+  branch_count_map.clear ();
+  address_frequencies.clear ();
+  reset_branch_stack_profile ();
+  reset_profile_state ();
+  target_mmaps_by_pid.clear ();
+  target_mapping_pids.clear ();
+  target_executable_mappings_changed = false;
+  total_samples = 0;
+  lost_records = 0;
+  ring_overwrite = false;
+  if (command_argv.empty ())
+    {
+      fprintf (stderr, _("Error: No command to run for direct profiling\n"));
+      return false;
+    }
+  if (sample_period == 0)
+    {
+      fprintf (stderr, _("Error: Live sample period must be nonzero\n"));
+      return false;
+    }
+
+  /* Resolve binary path so we can match kernel MMAP paths (often absolute).  
*/
+  char *resolved = realpath (binary_path.c_str (), nullptr);
+  if (resolved)
+    {
+      binary_path_resolved = resolved;
+      free (resolved);
+    }
+  else
+    binary_path_resolved.clear ();
+
+  /* Prepare ATTR before fork so the parent can call perf_event_open
+     immediately (the child may exit quickly).  */
+  perf_event_attr attr = {};
+  attr.type = PERF_TYPE_HARDWARE;
+  attr.size = sizeof (attr);
+  attr.config = PERF_COUNT_HW_CPU_CYCLES;
+  attr.sample_period = sample_period;
+  attr.sample_type
+    = use_branch_stack_for_correlation_
+       ? DEFAULT_BRANCH_STACK_SAMPLE_TYPE
+       : DEFAULT_BRANCH_STACK_SAMPLE_TYPE & ~perf_sample_type::BRANCH_STACK;
+  attr.branch_sample_type
+    = use_branch_stack_for_correlation_ ? PERF_SAMPLE_BRANCH_ANY : 0;
+  attr.exclude_kernel = 1;
+  attr.exclude_hv = 1;
+  attr.inherit = 1;
+  attr.mmap = 1;
+  attr.mmap2 = 1;
+
+  int sync_pipe[2];
+  if (pipe (sync_pipe) != 0)
+    {
+      fprintf (stderr, _("Error: pipe failed: %s\n"), strerror (errno));
+      return false;
+    }
+
+  pid_t pid = fork ();
+  if (pid < 0)
+    {
+      fprintf (stderr, _("Error: fork failed: %s\n"), strerror (errno));
+      close (sync_pipe[0]);
+      close (sync_pipe[1]);
+      return false;
+    }
+
+  if (pid == 0)
+    {
+      /* We start the command here; we block in read () until the parent
+        attaches perf, then exec.  */
+      close (sync_pipe[1]);
+      char ignore;
+      if (read (sync_pipe[0], &ignore, 1) != 1)
+       _exit (126);
+      close (sync_pipe[0]);
+      std::vector<char *> argv;
+      for (auto &s : command_argv)
+       argv.push_back (const_cast<char *> (s.c_str ()));
+      argv.push_back (nullptr);
+      execvp (argv[0], argv.data ());
+      fprintf (stderr, _("Error: execvp %s failed: %s\n"), argv[0],
+              strerror (errno));
+      _exit (127);
+    }
+
+  /* Parent: attach perf to the child, which is blocked in read () and so
+     cannot exit.  */
+  close (sync_pipe[0]);
+
+  const long system_page_size = sysconf (_SC_PAGESIZE);
+  if (system_page_size <= 0)
+    {
+      fprintf (stderr, _("Error: unable to determine system page size\n"));
+      close (sync_pipe[1]);
+      reap_child (pid);
+      return false;
+    }
+  const size_t page_size = static_cast<size_t> (system_page_size);
+
+  /* The kernel refuses to mmap an inheriting event opened with cpu == -1, so
+     open one event per CPU; attr.inherit still follows the workload's
+     threads.  */
+  const std::vector<int> cpus = online_cpu_ids ();
+  const size_t data_pages = ring_data_pages (page_size);
+  std::vector<cpu_ring_buffer> buffers;
+  int open_errno = 0;
+  int mmap_errno = 0;
+  for (const int cpu : cpus)
+    {
+      const long opened = sys_perf_event_open (&attr, pid, cpu, -1, 0);
+      if (opened < 0)
+       {
+         if (open_errno == 0)
+           open_errno = errno;
+         continue;
+       }
+      cpu_ring_buffer buffer;
+      buffer.fd = static_cast<int> (opened);
+      if (!map_cpu_ring_buffer (buffer, page_size, data_pages))
+       {
+         if (mmap_errno == 0)
+           mmap_errno = errno;
+         close (buffer.fd);
+         continue;
+       }
+      buffers.push_back (buffer);
+    }
+
+  if (buffers.empty ())
+    {
+      close (sync_pipe[1]);
+      reap_child (pid);
+      if (open_errno != 0 && use_branch_stack_for_correlation_)
+       fprintf (stderr,
+                _("Error: live capture requires LBR/BRBE branch-stack support"
+                  " (perf_event_open failed: %s)\n"),
+                strerror (open_errno));
+      else if (open_errno != 0)
+       fprintf (stderr, _("Error: live perf_event_open failed: %s\n"),
+                strerror (open_errno));
+      else
+       fprintf (stderr, _("Error: mmap perf buffer failed: %s\n"),
+                strerror (mmap_errno));
+      fprintf (stderr,
+              _("  Check kernel.perf_event_paranoid or grant CAP_PERFMON to "
+                "afdo_create; do not run the profiled workload as root.\n"));
+      fprintf (stderr,
+              _("  Offline mode (-p <perf.data>) still works without live"
+                " branch-stack.\n"));
+      return false;
+    }
+  /* A CPU we could not open costs only the samples that run on it.  */
+  if (buffers.size () != cpus.size ())
+    fprintf (stderr,
+            _("Warning: profiling %zu of %zu CPUs (%s); samples that run on"
+              " the rest are not collected\n"),
+            buffers.size (), cpus.size (),
+            strerror (open_errno != 0 ? open_errno : mmap_errno));
+
+  sample_type = attr.sample_type;
+  branch_sample_type = attr.branch_sample_type;
+
+  /* Find each data area, ignoring a layout that would read past the
+     mapping.  */
+  for (cpu_ring_buffer &buffer : buffers)
+    {
+      const perf_event_mmap_page *meta
+       = static_cast<const perf_event_mmap_page *> (buffer.base);
+      buffer.data_offset = meta->data_offset;
+      buffer.data_size = meta->data_size;
+      if (buffer.data_size == 0 || buffer.data_offset >= buffer.len
+         || buffer.data_size > buffer.len - buffer.data_offset)
+       {
+         buffer.data_offset = page_size;
+         buffer.data_size = buffer.len - page_size;
+       }
+    }
+
+  bool enabled = false;
+  for (const cpu_ring_buffer &buffer : buffers)
+    if (ioctl (buffer.fd, PERF_EVENT_IOC_ENABLE, 0) == 0)
+      enabled = true;
+  if (!enabled)
+    {
+      fprintf (stderr, _("Error: PERF_EVENT_IOC_ENABLE failed: %s\n"),
+              strerror (errno));
+      for (const cpu_ring_buffer &buffer : buffers)
+       {
+         munmap (buffer.base, buffer.len);
+         close (buffer.fd);
+       }
+      close (sync_pipe[1]);
+      reap_child (pid);
+      return false;
+    }
+  /* Unblock child only after perf is enabled.  */
+  {
+    char one = 1;
+    ssize_t written;
+    do
+      written = write (sync_pipe[1], &one, 1);
+    while (written < 0 && errno == EINTR);
+    /* Closing the write end unblocks the child either way, but on a short
+       write it reads EOF and exits instead of running the workload.  */
+    if (written != 1)
+      fprintf (stderr,
+              _("Error: could not release the profiled workload: %s\n"),
+              strerror (errno));
+    close (sync_pipe[1]);
+  }
+
+  fprintf (stderr,
+          use_branch_stack_for_correlation_
+            ? _("Profiling %s (PID %d, branch-stack)...\n")
+            : _("Profiling %s (PID %d, sampled IPs)...\n"),
+          command_argv[0].c_str (),
+          pid);
+  int status = 0;
+  bool have_status = false;
+  std::vector<pollfd> pfds;
+  pfds.reserve (buffers.size ());
+  for (const cpu_ring_buffer &buffer : buffers)
+    pfds.push_back (pollfd{buffer.fd, POLLIN, 0});
+  for (;;)
+    {
+      const pid_t reaped = waitpid (pid, &status, WNOHANG);
+      if (reaped == pid)
+       {
+         have_status = true;
+         break;
+       }
+      if (reaped < 0 && errno != EINTR)
+       break;
+      const int r = poll (pfds.data (), pfds.size (), 100);
+      if (r < 0 && errno != EINTR)
+       {
+         /* Without poll () there is nothing left to pace this loop, so stop
+            draining incrementally and wait for the workload instead.  */
+         fprintf (stderr,
+                  _("Warning: poll on the perf event fds failed: %s\n"),
+                  strerror (errno));
+         break;
+       }
+      if (r <= 0)
+       continue;
+      for (size_t i = 0; i < buffers.size (); ++i)
+       if ((pfds[i].revents & POLLIN) != 0)
+         process_ring_buffer (buffers[i].base, buffers[i].data_offset,
+                              buffers[i].data_size);
+    }
+  int reap_errno = 0;
+  if (!have_status)
+    {
+      have_status = reap_child (pid, &status);
+      if (!have_status)
+       reap_errno = errno;
+    }
+
+  /* Closing the fds below stops sampling, so a failed disable costs nothing
+     beyond the records already drained.  */
+  for (const cpu_ring_buffer &buffer : buffers)
+    {
+      process_ring_buffer (buffer.base, buffer.data_offset, buffer.data_size);
+      if (ioctl (buffer.fd, PERF_EVENT_IOC_DISABLE, 0) < 0)
+       fprintf (stderr, _("Warning: PERF_EVENT_IOC_DISABLE failed: %s\n"),
+                strerror (errno));
+      munmap (buffer.base, buffer.len);
+      close (buffer.fd);
+    }
+
+  if (lost_records != 0)
+    fprintf (stderr,
+            _("Warning: kernel dropped %" PRIu64 " perf records; raise"
+              " --sample-period to sample less often\n"),
+            lost_records);
+  if (ring_overwrite)
+    fprintf (stderr,
+            _("Warning: the perf ring buffer was overwritten; discarded "
+              "an incomplete snapshot.  Raise --sample-period to sample "
+              "less often\n"));
+
+  if (!have_status)
+    fprintf (stderr,
+            _("Warning: could not reap the profiled workload (%s); its exit"
+              " status is unknown and the profile may be incomplete\n"),
+            strerror (reap_errno));
+  else if (WIFEXITED (status) && WEXITSTATUS (status) == 0)
+    fprintf (stderr, _("Process exited with code 0\n"));
+  else if (WIFEXITED (status))
+    fprintf (stderr,
+            _("Warning: process exited with code %d; the workload did not"
+              " run to completion and the profile may be incomplete\n"),
+            WEXITSTATUS (status));
+  else if (WIFSIGNALED (status))
+    fprintf (stderr,
+            _("Warning: process killed by signal %d (e.g.  SIGSEGV); profile"
+              " may be incomplete.\n"),
+            WTERMSIG (status));
+
+  if (target_mmaps_by_pid.empty ())
+    {
+      fprintf (stderr,
+              _("Error: No memory mappings found for target binary '%s'\n"),
+              path_basename (binary_path).c_str ());
+      return false;
+    }
+  if (target_executable_mappings_changed)
+    {
+      fprintf (stderr,
+              _("Error: target executable mappings changed during live "
+                "profiling; this is not supported\n"));
+      return false;
+    }
+
+  if (range_count_map.empty () && branch_count_map.empty ()
+      && address_frequencies.empty ())
+    {
+      fprintf (stderr,
+              use_branch_stack_for_correlation_
+                ? _("Error: No branch-stack samples collected (binary "
+                    "may not match or workload too short)\n")
+                : _("Error: No sampled IPs collected (binary may not match "
+                    "or workload too short)\n"));
+      return false;
+    }
+
+  fprintf (stderr,
+          _("Collected %" PRIu64 " samples, %zu ranges, %zu branches"),
+          total_samples,
+          range_count_map.size (),
+          branch_count_map.size ());
+  if (!address_frequencies.empty ())
+    fprintf (stderr, _(", %zu sample IPs"), address_frequencies.size ());
+  fprintf (stderr, "\n");
+  finalize_profile_after_capture ();
+  /* Report mass like offline, not raw record count.  */
+  if (!sorted_ranges_.empty ())
+    total_samples = total_mass_from_range_counts (sorted_ranges_);
+  else
+    total_samples = total_mass_from_address_frequencies (address_frequencies);
+  return true;
+}
+
+/* Publish the profile, whose samples were translated through PID-scoped
+   mappings while being ingested.  */
+void
+live_branch_stack_reader::finalize_profile_after_capture ()
+{
+  publish_dwarf_branch_stack_maps (
+    range_count_map, branch_count_map, &sorted_touch_pcs_);
+  target_mmaps_by_pid.clear ();
+  target_mmaps_by_pid.rehash (0);
+  target_mapping_pids.clear ();
+}
+
+} /* namespace perf_data.  */
diff --git a/afdo_tools/src/live_branch_stack_reader.h 
b/afdo_tools/src/live_branch_stack_reader.h
new file mode 100644
index 00000000000..a1099c5486a
--- /dev/null
+++ b/afdo_tools/src/live_branch_stack_reader.h
@@ -0,0 +1,94 @@
+/* Copyright The GNU Toolchain Authors
+
+This file is part of GCC.
+
+GCC 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, or (at your option) any later
+version.
+
+GCC 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 GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#ifndef GCC_LIVE_BRANCH_STACK_READER_H
+#define GCC_LIVE_BRANCH_STACK_READER_H
+
+#include "branch_stack_reader_base.h"
+#include "profile_reader.h"
+
+#include <cstdint>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+namespace perf_data {
+
+/* Live LBR/BRBE: run a command under perf and ingest its branch stacks.  */
+class live_branch_stack_reader : public profile_reader,
+                                public branch_stack_reader_base
+{
+  std::string binary_path;
+  std::vector<std::string> command_argv;
+  uint64_t total_samples = 0;
+  std::unordered_map<uint64_t, uint64_t> address_frequencies;
+
+  branch_stack_edge_count_map range_count_map;
+  branch_stack_edge_count_map branch_count_map;
+
+  std::string binary_path_resolved;
+  pid_mmap_map target_mmaps_by_pid;
+  std::unordered_set<uint32_t> target_mapping_pids;
+  bool target_executable_mappings_changed = false;
+  uint64_t sample_period;
+  uint64_t sample_type = 0;
+  uint64_t branch_sample_type = 0;
+  /* Records the kernel dropped because the ring buffer was full.  */
+  uint64_t lost_records = 0;
+  /* True when producer head lapped the consumer tail.  */
+  bool ring_overwrite = false;
+
+  bool is_target_binary (const std::string &mmap_filename) const;
+  void add_target_mapping (uint32_t pid, uint64_t start, uint64_t end,
+                          uint64_t pgoff);
+  void note_replaced_target_mapping (uint32_t pid, uint64_t start,
+                                    uint64_t end);
+  void process_mmap_record (const uint8_t *payload, size_t payload_size,
+                           uint16_t misc);
+  void process_mmap2_record (const uint8_t *payload, size_t payload_size);
+  bool process_sample_record (const uint8_t *payload, size_t payload_size);
+  void process_ring_buffer (void *mmap_base, uint64_t data_offset,
+                           uint64_t data_size);
+  void finalize_profile_after_capture ();
+
+public:
+  live_branch_stack_reader (const std::string &_binary_path,
+                           const std::vector<std::string> &_command_argv,
+                           uint64_t _sample_period);
+
+  ~live_branch_stack_reader () override;
+
+  bool load () override;
+  const std::unordered_map<uint64_t, uint64_t> &
+  get_address_frequencies () const override
+  {
+    return address_frequencies;
+  }
+  uint64_t get_total_samples () const override { return total_samples; }
+
+  branch_stack_reader_base *as_branch_stack_reader () override { return this; }
+  const branch_stack_reader_base *as_branch_stack_reader () const override
+  {
+    return this;
+  }
+};
+
+} /* namespace perf_data.  */
+
+#endif /* GCC_LIVE_BRANCH_STACK_READER_H.  */
-- 
2.34.1

Reply via email to