#include <set>
#include <string>
#include <memory>
+#include <unordered_map>
#include <sstream>
#include <vector>
#include <bitset>
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
};
public:
PerfConsumer() {}
virtual ~PerfConsumer() {}
- virtual void process(const PerfSample* sample) = 0;
+ virtual void process(const perf_event_header* sample) = 0;
};
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
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
};
static unsigned verbose;
static bool gmon;
-
static error_t
parse_opt (int key, char *arg, struct argp_state *state)
{
{
(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);
{
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;
{
// 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");
}
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