Skip to content

Commit

Permalink
WIP: frame level sampling
Browse files Browse the repository at this point in the history
  • Loading branch information
richardstartin committed Mar 11, 2024
1 parent a836950 commit 62628a8
Show file tree
Hide file tree
Showing 21 changed files with 353 additions and 4 deletions.
82 changes: 82 additions & 0 deletions ddprof-lib/src/main/cpp/FrameSampler.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#include "event.h"
#include "FrameSampler.h"
#include "profiler.h"
#include "vmEntry.h"
#include "stackFrame.h"


#include <iostream>

FrameSampler* const FrameSampler::_instance = new FrameSampler();

void FrameSampler::forget_sampled_methods() {
// TODO clear out methods seen in the last recording interval
}

void FrameSampler::record_sampled_method(int tid, NMethod* nmethod) {
// TODO record once per method per recording interval,
// requires a signal safe concurrent set<u64>
u64 methodId = (u64) nmethod->method()->id();
int codeSize = nmethod->codeSize();
// we can't work with the code blob unless we know where it ends
// it is not null terminated so releasing a pointer to it as a
// c string is dangerous - calls to strlen may segfault etc.
if (codeSize >= 0) {
CodeEvent code;
code._id = methodId;
code._name = Profiler::instance()->stringLabelMap()->lookup(nmethod->name());
code._code_size = codeSize;
code._code = nmethod->code();
Profiler::instance()->recordCode(tid, &code);
} else {
std::cout << "no code size" << std::endl;
}
}

void FrameSampler::do_sample(int tid, u64 counter, void *ucontext) {
JNIEnv* jni = VM::jni();
if (jni == NULL) {
return; // not Java
}
StackFrame frame(ucontext);
uintptr_t pc = frame.pc();
bool inCodeHeap = CodeHeap::contains((const void*) pc);
if (!inCodeHeap) {
std::cout << "using last Java PC" << std::endl;
VMThread* vmThread = VMThread::current();
if (vmThread == NULL) {
return;
}
pc = vmThread->lastJavaPC();
}
if (pc == 0) {
std::cout << "null PC" << std::endl;
return;
}
NMethod* nm = CodeHeap::findNMethod((const void*) pc);
if (nm != NULL) {
if (!nm->isNMethod()) {
std::cout << "not nmethod" << std::endl;
} else if (!nm->isFrameCompleteAt((const void*) pc)) {
std::cout << "not complete" << std::endl;
}
if (nm->isNMethod() && nm->isFrameCompleteAt((const void*) pc)) {
int compilationTier = nm->level();
jmethodID methodID = nm->method()->id();
assert((u64)pc > (u64)nm->code());
record_sampled_method(tid, nm);
FrameEvent event(methodID, (u64)nm->code() - (u64)pc, compilationTier);
Profiler::instance()->recordFrameSample(counter, tid, &event);
}
} else {
std::cout << "null nmethod" << std::endl;
}
}

void FrameSampler::sample(int tid, u64 counter, void *ucontext) {
_instance->do_sample(tid, counter, ucontext);
}

void FrameSampler::clear() {
_instance->forget_sampled_methods();
}
20 changes: 20 additions & 0 deletions ddprof-lib/src/main/cpp/FrameSampler.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#ifndef JAVA_PROFILER_FRAMESAMPLER_H
#define JAVA_PROFILER_FRAMESAMPLER_H

#include "arch.h"
#include "vmStructs.h"

class FrameSampler {
private:
static FrameSampler* const _instance;
void do_sample(int tid, u64 counter, void* ucontext);
void forget_sampled_methods();
void record_sampled_method(int tid, NMethod* nmethod);
public:
static void sample(int tid, u64 counter, void* ucontext);
static void clear();

};


#endif //JAVA_PROFILER_FRAMESAMPLER_H
14 changes: 13 additions & 1 deletion ddprof-lib/src/main/cpp/arguments.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
#include <unistd.h>
#include "arguments.h"


// Predefined value that denotes successful operation
const Error Error::OK(NULL);

Expand Down Expand Up @@ -187,6 +186,18 @@ Error Arguments::parse(const char* args) {
_event = value;
}

CASE("frames")
if (value != NULL && value[0] != 0) {
switch (value[0]) {
case 'y':
case 't':
_frame_samples = true;
break;
default:
_frame_samples = false;
}
}

CASE("memory")
char* config = value ? strchr(value, ':') : NULL;
if (config) {
Expand All @@ -213,6 +224,7 @@ Error Arguments::parse(const char* args) {
msg = "memory sampling interval must be >= 0";
}


CASE("interval")
if (value == NULL || (_interval = parseUnits(value, UNIVERSAL)) <= 0) {
msg = "Invalid interval";
Expand Down
4 changes: 3 additions & 1 deletion ddprof-lib/src/main/cpp/arguments.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ class Arguments {
Action _action;
Ring _ring;
const char* _event;
bool _frame_samples;
long _interval;
long _cpu;
long _wall;
Expand Down Expand Up @@ -176,7 +177,8 @@ class Arguments {
_cstack(CSTACK_DEFAULT),
_jfr_options(0),
_context_attributes({}),
_lightweight(false) {
_lightweight(false),
_frame_samples(false) {
}

~Arguments();
Expand Down
3 changes: 2 additions & 1 deletion ddprof-lib/src/main/cpp/ctimer.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class CTimer : public Engine {
static long _interval;
static CStack _cstack;
static int _signal;
static bool _frame_samples;

static int _max_timers;
static int* _timers;
Expand Down Expand Up @@ -81,4 +82,4 @@ class CTimer : public Engine {

#endif // __linux__

#endif // _CTIMER_H
#endif // _CTIMER_H
9 changes: 8 additions & 1 deletion ddprof-lib/src/main/cpp/ctimer_linux.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include "debugSupport.h"
#include "profiler.h"
#include "vmStructs.h"
#include "FrameSampler.h"


#ifndef SIGEV_THREAD_ID
Expand Down Expand Up @@ -81,6 +82,7 @@ int* CTimer::_timers = NULL;
CStack CTimer::_cstack;
volatile bool CTimer::_enabled = false;
int CTimer::_signal;
bool CTimer::_frame_samples;

int CTimer::registerThread(int tid) {
if (tid >= _max_timers) {
Expand Down Expand Up @@ -153,6 +155,7 @@ Error CTimer::start(Arguments& args) {
_interval = args.cpuSamplerInterval();
_cstack = args._cstack;
_signal = SIGPROF;
_frame_samples = args._frame_samples;

int max_timers = OS::getMaxThreadId();
if (max_timers != _max_timers) {
Expand Down Expand Up @@ -200,6 +203,10 @@ void CTimer::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) {
}
Shims::instance().setSighandlerTid(tid);

if (_frame_samples) {
FrameSampler::sample(tid, _interval, ucontext);
}

ExecutionEvent event;
VMThread* vm_thread = VMThread::current();
if (vm_thread) {
Expand All @@ -211,4 +218,4 @@ void CTimer::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) {
Shims::instance().setSighandlerTid(-1);
}

#endif // __linux__
#endif // __linux__
19 changes: 19 additions & 0 deletions ddprof-lib/src/main/cpp/event.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,18 @@ class ExecutionEvent : public Event {
ExecutionEvent() : Event(), _thread_state(ThreadState::RUNNABLE), _weight(1), _execution_mode(ExecutionMode::UNKNOWN) {}
};

class FrameEvent : public Event {
public:
jmethodID _methodID; // link to method
u64 _pcRelative; // to get the pc, link to the nmethod and add its offset
u8 _compilationTier;

FrameEvent(jmethodID methodID, u64 pcRelative, u8 compilationTier) : Event(),
_methodID(methodID),
_pcRelative(pcRelative),
_compilationTier(compilationTier) {}
};

class AllocEvent : public Event {
public:
u64 _size;
Expand Down Expand Up @@ -158,4 +170,11 @@ typedef struct QueueTimeEvent {
u32 _origin;
} QueueTimeEvent;

typedef struct CodeEvent {
u64 _id;
u32 _name;
u32 _code_size;
const char* _code;
} CodeEvent;

#endif // _EVENT_H
36 changes: 36 additions & 0 deletions ddprof-lib/src/main/cpp/flightRecorder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1214,6 +1214,32 @@ void Recording::recordMethodSample(Buffer* buf, int tid, u32 call_trace_id, Exec
flushIfNeeded(buf);
}

void Recording::recordFrameSample(Buffer* buf, int tid, FrameEvent* event) {
int start = buf->skip(1);
buf->putVar64(T_DATADOG_FRAME_SAMPLE);
buf->putVar64(TSC::ticks());
buf->putVar64(tid);
buf->putVar64((u64) event->_methodID);
buf->putVar64(event->_pcRelative);
buf->put8(event->_compilationTier);
writeContext(buf, Contexts::get(tid));
writeEventSizePrefix(buf, start);
flushIfNeeded(buf);
}

void Recording::recordCodeSample(Buffer *buf, CodeEvent* event) {
int length = 1 + MAX_VAR64_LENGTH + MAX_VAR32_LENGTH + event->_code_size;
// ensure the code does not get truncated, because we won't be able to disassemble it if it is
flushIfNeeded(buf, RECORDING_BUFFER_LIMIT - length);
int start = buf->skip(1);
buf->putVar64(T_DATADOG_CODE_SAMPLE);
buf->putVar64((u64) event->_id);
buf->putVar64(event->_name);
buf->putUtf8(event->_code, event->_code_size);
writeEventSizePrefix(buf, start);
flushIfNeeded(buf);
}

void Recording::recordWallClockEpoch(Buffer* buf, WallClockEpochEvent* event) {
int start = buf->skip(1);
buf->putVar64(T_WALLCLOCK_SAMPLE_EPOCH);
Expand Down Expand Up @@ -1451,6 +1477,13 @@ void FlightRecorder::recordHeapUsage(int lock_index, long value, bool live) {
}
}

void FlightRecorder::recordCode(int lock_index, CodeEvent *code) {
if (_rec != NULL) {
Buffer *buf = _rec->buffer(lock_index);
_rec->recordCodeSample(buf, code);
}
}

void FlightRecorder::recordEvent(int lock_index, int tid, u32 call_trace_id,
int event_type, Event* event, u64 counter) {
if (_rec != NULL) {
Expand All @@ -1474,6 +1507,9 @@ void FlightRecorder::recordEvent(int lock_index, int tid, u32 call_trace_id,
case BCI_PARK:
_rec->recordThreadPark(buf, tid, call_trace_id, (LockEvent*)event);
break;
case BCI_FRAME:
_rec->recordFrameSample(buf, tid, (FrameEvent*)event);
break;
}
_rec->flushIfNeeded(buf);
_rec->addThread(tid);
Expand Down
4 changes: 4 additions & 0 deletions ddprof-lib/src/main/cpp/flightRecorder.h
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,8 @@ class Recording {

void recordExecutionSample(Buffer* buf, int tid, u32 call_trace_id, ExecutionEvent* event);
void recordMethodSample(Buffer* buf, int tid, u32 call_trace_id, ExecutionEvent* event);
void recordFrameSample(Buffer* buf, int tid, FrameEvent* event);
void recordCodeSample(Buffer* buf, CodeEvent* event);
void recordWallClockEpoch(Buffer* buf, WallClockEpochEvent* event);
void recordTraceRoot(Buffer* buf, int tid, TraceRootEvent* event);
void recordQueueTime(Buffer* buf, int tid, QueueTimeEvent* event);
Expand Down Expand Up @@ -301,6 +303,8 @@ class FlightRecorder {
void recordDatadogSetting(int lock_index, int length, const char* name, const char* value, const char* unit);

void recordHeapUsage(int lock_index, long value, bool live);

void recordCode(int lock_index, CodeEvent* code);
};

#endif // _FLIGHTRECORDER_H
9 changes: 9 additions & 0 deletions ddprof-lib/src/main/cpp/itimer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,20 @@

#include <sys/time.h>
#include "debugSupport.h"
#include "FrameSampler.h"
#include "itimer.h"
#include "os.h"
#include "profiler.h"
#include "stackWalker.h"
#include "thread.h"
#include "vmStructs.h"

#include <iostream>

volatile bool ITimer::_enabled = false;
long ITimer::_interval;
CStack ITimer::_cstack;
bool ITimer::_frame_samples;

void ITimer::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) {
if (!_enabled) return;
Expand All @@ -39,6 +43,10 @@ void ITimer::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) {
}
Shims::instance().setSighandlerTid(tid);

if (_frame_samples) {
FrameSampler::sample(tid, _interval, ucontext);
}

ExecutionEvent event;
VMThread* vm_thread = VMThread::current();
if (vm_thread) {
Expand Down Expand Up @@ -67,6 +75,7 @@ Error ITimer::check(Arguments& args) {
Error ITimer::start(Arguments& args) {
_interval = args.cpuSamplerInterval();
_cstack = args._cstack;
_frame_samples = args._frame_samples;

OS::installSignalHandler(SIGPROF, signalHandler);

Expand Down
1 change: 1 addition & 0 deletions ddprof-lib/src/main/cpp/itimer.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class ITimer : public Engine {
static volatile bool _enabled;
static long _interval;
static CStack _cstack;
static bool _frame_samples;

static void signalHandler(int signo, siginfo_t* siginfo, void* ucontext);

Expand Down
17 changes: 17 additions & 0 deletions ddprof-lib/src/main/cpp/jfrMetadata.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,23 @@ void JfrMetadata::initialize(const std::vector<std::string>& contextAttributes)
<< field("localRootSpanId", T_LONG, "Local Root Span ID")
|| contextAttributes)

<< (type("datadog.FrameSample", T_DATADOG_FRAME_SAMPLE, "Intra-frame CPU sample")
<< category("Datadog", "Profiling")
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
<< field("eventThread", T_THREAD, "Thread", F_CPOOL)
<< field("methodId", T_LONG, "Method Id")
<< field("compilationTier", T_INT, "Compilation Tier")
<< field("pcRelative", T_LONG, "Program counter relative to method start")
<< field("spanId", T_LONG, "Span ID")
<< field("localRootSpanId", T_LONG, "Local Root Span ID")
|| contextAttributes)

<< (type("datadog.CodeSample", T_DATADOG_CODE_SAMPLE, "Method Code Sample")
<< category("Datadog", "Profiling")
<< field("methodId", T_LONG, "Method Id")
<< field("name", T_STRING, "Method Name", F_CPOOL)
<< field("code", T_STRING, "Compiled Code"))

<< (type("datadog.WallClockSamplingEpoch", T_WALLCLOCK_SAMPLE_EPOCH, "WallClock Sampling Epoch")
<< category("Datadog", "Profiling")
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
Expand Down
Loading

0 comments on commit 62628a8

Please sign in to comment.