/* Copyright (c) 2015 Google Inc
* Davide Libenzi <dlibenzi@google.com>
* See LICENSE for details.
- */
+ *
+ * This controls the emitting, collecting, and exporting of samples for perf
+ * events. Examples of events are PMU counter overflows, mmaps, and process
+ * creation.
+ *
+ * Events are collected in a central qio queue. High-frequency events (e.g.
+ * IRQ backtraces()) are collected in per-core buffers, which are flushed to the
+ * central queue when they fill up or on command. Lower-frequency events (e.g.
+ * profiler_notify_mmap()) just go straight to the central queue.
+ *
+ * Currently there is one global profiler. Kprof is careful to only have one
+ * open profiler at a time. We assert that this is true. TODO: stop using the
+ * global profiler!
+ *
+ * A few other notes:
+ * - profiler_control_trace() controls the per-core trace collection. When it
+ * is disabled, it also flushes the per-core blocks to the central queue.
+ * - The collection of mmap and comm samples is independent of trace collection.
+ * Those will occur whenever the profiler is open (refcnt check, for now). */
#include <ros/common.h>
#include <ros/mman.h>
#include "profiler.h"
#define PROFILER_MAX_PRG_PATH 256
-#define PROFILER_BT_DEPTH 16
#define VBE_MAX_SIZE(t) ((8 * sizeof(t) + 6) / 7)
+/* Do not rely on the contents of the PCPU ctx with IRQs enabled. */
struct profiler_cpu_context {
struct block *block;
int cpu;
return block_alloc(profiler_cpu_buffer_size, MEM_ATOMIC);
}
+/* Helper, paired with profiler_cpu_buffer_write_commit. Ensures there is
+ * enough room in the pcpu block for our write. May alloc a new one.
+ *
+ * IRQs must be disabled before calling, until after write_commit. */
static char *profiler_cpu_buffer_write_reserve(
struct profiler_cpu_context *cpu_buf, size_t size, struct block **pb)
{
return (char *) b->wp;
}
+/* Helper, paired with write_reserve. Finalizes the writing into the block's
+ * main body of @size bytes. IRQs must be disabled until after this is called.
+ */
static inline void profiler_cpu_buffer_write_commit(
struct profiler_cpu_context *cpu_buf, struct block *b, size_t size)
{
const uintptr_t *trace, size_t count,
uint64_t info)
{
+ struct per_cpu_info *pcpui = &per_cpu_info[core_id()];
size_t size = sizeof(struct proftype_kern_trace64) +
count * sizeof(uint64_t);
struct block *b;
- void *resptr = profiler_cpu_buffer_write_reserve(
+ void *resptr, *ptr;
+
+ assert(!irq_is_enabled());
+ resptr = profiler_cpu_buffer_write_reserve(
cpu_buf, size + profiler_max_envelope_size(), &b);
- void *ptr = resptr;
+ ptr = resptr;
if (likely(ptr)) {
struct proftype_kern_trace64 *record;
record->info = info;
record->tstamp = nsec();
+ if (is_ktask(pcpui->cur_kthread) || !pcpui->cur_proc)
+ record->pid = -1;
+ else
+ record->pid = pcpui->cur_proc->pid;
record->cpu = cpu_buf->cpu;
record->num_traces = count;
for (size_t i = 0; i < count; i++)
size_t size = sizeof(struct proftype_user_trace64) +
count * sizeof(uint64_t);
struct block *b;
- void *resptr = profiler_cpu_buffer_write_reserve(
+ void *resptr, *ptr;
+
+ assert(!irq_is_enabled());
+ resptr = profiler_cpu_buffer_write_reserve(
cpu_buf, size + profiler_max_envelope_size(), &b);
- void *ptr = resptr;
+ ptr = resptr;
if (likely(ptr)) {
struct proftype_user_trace64 *record;
qunlock(&profiler_mtx);
nexterror();
}
- if (!profiler_queue)
- alloc_cpu_buffers();
+ assert(!profiler_queue);
+ alloc_cpu_buffers();
/* Do this only when everything is initialized (as last init operation).
*/
static void profiler_cpu_flush(struct profiler_cpu_context *cpu_buf)
{
+ int8_t irq_state = 0;
+
+ disable_irqsave(&irq_state);
if (cpu_buf->block && profiler_queue) {
qibwrite(profiler_queue, cpu_buf->block);
cpu_buf->block = NULL;
}
+ enable_irqsave(&irq_state);
}
static void profiler_core_trace_enable(void *opaque)
profiler_cpu_flush(cpu_buf);
}
-void profiler_control_trace(int onoff)
+static void profiler_control_trace(int onoff)
{
struct core_set cset;
(void *) (uintptr_t) onoff);
}
+void profiler_start(void)
+{
+ assert(profiler_queue);
+ profiler_control_trace(1);
+ qreopen(profiler_queue);
+}
+
+void profiler_stop(void)
+{
+ assert(profiler_queue);
+ profiler_control_trace(0);
+ qhangup(profiler_queue, 0);
+}
+
static void profiler_core_flush(void *opaque)
{
struct profiler_cpu_context *cpu_buf = profiler_get_cpu_ctx(core_id());
smp_do_in_cores(&cset, profiler_core_flush, NULL);
}
-void profiler_add_trace(uintptr_t pc, uint64_t info)
-{
- if (is_user_raddr((void *) pc, 1))
- profiler_add_user_backtrace(pc, 0, info);
- else
- profiler_add_kernel_backtrace(pc, 0, info);
-}
-
-void profiler_add_kernel_backtrace(uintptr_t pc, uintptr_t fp, uint64_t info)
+void profiler_push_kernel_backtrace(uintptr_t *pc_list, size_t nr_pcs,
+ uint64_t info)
{
if (kref_get_not_zero(&profiler_kref, 1)) {
struct profiler_cpu_context *cpu_buf = profiler_get_cpu_ctx(core_id());
- if (profiler_percpu_ctx && cpu_buf->tracing) {
- uintptr_t trace[PROFILER_BT_DEPTH];
- size_t n = 1;
-
- trace[0] = pc;
- if (likely(fp))
- n = backtrace_list(pc, fp, trace + 1,
- PROFILER_BT_DEPTH - 1) + 1;
-
- profiler_push_kernel_trace64(cpu_buf, trace, n, info);
- }
+ if (profiler_percpu_ctx && cpu_buf->tracing)
+ profiler_push_kernel_trace64(cpu_buf, pc_list, nr_pcs, info);
kref_put(&profiler_kref);
}
}
-void profiler_add_user_backtrace(uintptr_t pc, uintptr_t fp, uint64_t info)
+void profiler_push_user_backtrace(uintptr_t *pc_list, size_t nr_pcs,
+ uint64_t info)
{
if (kref_get_not_zero(&profiler_kref, 1)) {
struct proc *p = current;
struct profiler_cpu_context *cpu_buf = profiler_get_cpu_ctx(core_id());
- if (p && profiler_percpu_ctx && cpu_buf->tracing) {
- uintptr_t trace[PROFILER_BT_DEPTH];
- size_t n = 1;
-
- trace[0] = pc;
- if (likely(fp))
- n = backtrace_user_list(pc, fp, trace + 1,
- PROFILER_BT_DEPTH - 1) + 1;
-
- profiler_push_user_trace64(cpu_buf, p, trace, n, info);
- }
+ if (profiler_percpu_ctx && cpu_buf->tracing)
+ profiler_push_user_trace64(cpu_buf, p, pc_list, nr_pcs, info);
kref_put(&profiler_kref);
}
}
-void profiler_add_hw_sample(struct hw_trapframe *hw_tf, uint64_t info)
-{
- if (in_kernel(hw_tf))
- profiler_add_kernel_backtrace(get_hwtf_pc(hw_tf), get_hwtf_fp(hw_tf),
- info);
- else
- profiler_add_user_backtrace(get_hwtf_pc(hw_tf), get_hwtf_fp(hw_tf),
- info);
-}
-
int profiler_size(void)
{
return profiler_queue ? qlen(profiler_queue) : 0;