Skip to content

Commit 2bddc62

Browse files
committed
Move profiler start/stop/restart logic into native code.
Make ring buffer operations interrupt safe.
1 parent 2160862 commit 2bddc62

4 files changed

Lines changed: 169 additions & 154 deletions

File tree

bindings/profilers/wall.cc

Lines changed: 127 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -61,19 +61,17 @@ static void sighandler(int sig, siginfo_t* info, void* context) {
6161
}
6262

6363
auto isolate = Isolate::GetCurrent();
64-
WallProfiler* prof = nullptr;
6564
auto prof_map = profilers.exchange(nullptr, std::memory_order_acq_rel);
6665
if (prof_map) {
66+
WallProfiler* prof = nullptr;
6767
auto prof_it = prof_map->find(isolate);
6868
if (prof_it != prof_map->end()) {
6969
prof = prof_it->second;
7070
}
7171
profilers.store(prof_map, std::memory_order_release);
72-
}
73-
auto now = v8::base::TimeTicks::Now();
74-
old_handler(sig, info, context);
75-
if (prof) {
76-
prof->PushContext(now);
72+
if (prof) {
73+
prof->DoSignalHandler(sig, info, context);
74+
}
7775
}
7876
}
7977
#endif
@@ -301,7 +299,8 @@ bool isIdleSample(const CpuProfileNode* sample) {
301299
strncmp("(idle)", sample->GetFunctionNameStr(), 7) == 0;
302300
}
303301

304-
LabelSetsByNode WallProfiler::GetLabelSetsByNode(CpuProfile* profile) {
302+
LabelSetsByNode WallProfiler::GetLabelSetsByNode(
303+
CpuProfile* profile, RingBuffer<SampleContext>& contexts) {
305304
LabelSetsByNode labelSetsByNode;
306305

307306
auto sampleCount = profile->GetSamplesCount();
@@ -356,12 +355,21 @@ LabelSetsByNode WallProfiler::GetLabelSetsByNode(CpuProfile* profile) {
356355
}
357356
}
358357
}
358+
while (!contexts.empty()) {
359+
contexts.pop_front();
360+
}
359361
return labelSetsByNode;
360362
}
361363

362-
WallProfiler::WallProfiler(int intervalMicros, int durationMicros)
364+
WallProfiler::WallProfiler(int intervalMicros,
365+
int durationMicros,
366+
bool includeLines_,
367+
bool withLabels_)
363368
: samplingInterval(intervalMicros),
364-
contexts(durationMicros * 2 / intervalMicros) {}
369+
includeLines(includeLines_),
370+
withLabels(withLabels_),
371+
session1(durationMicros * 2 / intervalMicros),
372+
session2(durationMicros * 2 / intervalMicros) {}
365373

366374
WallProfiler::~WallProfiler() {
367375
Dispose(nullptr);
@@ -414,16 +422,9 @@ void WallProfiler::Dispose(Isolate* isolate) {
414422
}
415423
}
416424

417-
NAN_METHOD(WallProfiler::Dispose) {
418-
WallProfiler* wallProfiler =
419-
Nan::ObjectWrap::Unwrap<WallProfiler>(info.Holder());
420-
421-
wallProfiler->Dispose(info.GetIsolate());
422-
}
423-
424425
NAN_METHOD(WallProfiler::New) {
425-
if (info.Length() != 2) {
426-
return Nan::ThrowTypeError("WallProfiler must have two arguments.");
426+
if (info.Length() != 4) {
427+
return Nan::ThrowTypeError("WallProfiler must have four arguments.");
427428
}
428429
if (!info[0]->IsNumber()) {
429430
return Nan::ThrowTypeError("Sample rate must be a number.");
@@ -433,6 +434,14 @@ NAN_METHOD(WallProfiler::New) {
433434
return Nan::ThrowTypeError("Duration must be a number.");
434435
}
435436

437+
if (!info[2]->IsBoolean()) {
438+
return Nan::ThrowTypeError("includeLines must be a boolean.");
439+
}
440+
441+
if (!info[3]->IsBoolean()) {
442+
return Nan::ThrowTypeError("withLabels must be a boolean.");
443+
}
444+
436445
if (info.IsConstructCall()) {
437446
int interval = Nan::MaybeLocal<Integer>(info[0].As<Integer>())
438447
.ToLocalChecked()
@@ -451,12 +460,21 @@ NAN_METHOD(WallProfiler::New) {
451460
return Nan::ThrowTypeError("Duration must not be less than sample rate.");
452461
}
453462

454-
WallProfiler* obj = new WallProfiler(interval, duration);
463+
bool includeLines = Nan::MaybeLocal<Boolean>(info[2].As<Boolean>())
464+
.ToLocalChecked()
465+
->Value();
466+
467+
bool withLabels = Nan::MaybeLocal<Boolean>(info[3].As<Boolean>())
468+
.ToLocalChecked()
469+
->Value();
470+
471+
WallProfiler* obj =
472+
new WallProfiler(interval, duration, includeLines, withLabels);
455473
obj->Wrap(info.This());
456474
info.GetReturnValue().Set(info.This());
457475
} else {
458-
const int argc = 2;
459-
v8::Local<v8::Value> argv[argc] = {info[0], info[1]};
476+
const int argc = 4;
477+
v8::Local<v8::Value> argv[argc] = {info[0], info[1], info[2], info[3]};
460478
v8::Local<v8::Function> cons = Nan::New(
461479
PerIsolateData::For(info.GetIsolate())->WallProfilerConstructor());
462480
info.GetReturnValue().Set(
@@ -468,93 +486,101 @@ NAN_METHOD(WallProfiler::Start) {
468486
WallProfiler* wallProfiler =
469487
Nan::ObjectWrap::Unwrap<WallProfiler>(info.Holder());
470488

471-
if (info.Length() != 3) {
472-
return Nan::ThrowTypeError("Start must have three arguments.");
473-
}
474-
if (!info[0]->IsString()) {
475-
return Nan::ThrowTypeError("Profile name must be a string.");
476-
}
477-
if (!info[1]->IsBoolean()) {
478-
return Nan::ThrowTypeError("Include lines flag must be a boolean.");
479-
}
480-
if (!info[2]->IsBoolean()) {
481-
return Nan::ThrowTypeError("With labels flag must be a boolean.");
489+
if (info.Length() != 0) {
490+
return Nan::ThrowTypeError("Start must have no arguments.");
482491
}
483492

484-
Local<String> name =
485-
Nan::MaybeLocal<String>(info[0].As<String>()).ToLocalChecked();
493+
return wallProfiler->StartStop(true, false, info);
494+
}
486495

487-
bool includeLines =
488-
Nan::MaybeLocal<Boolean>(info[1].As<Boolean>()).ToLocalChecked()->Value();
496+
NAN_METHOD(WallProfiler::Stop) {
497+
if (info.Length() != 1) {
498+
return Nan::ThrowTypeError("Stop must have one arguments.");
499+
}
500+
if (!info[0]->IsBoolean()) {
501+
return Nan::ThrowTypeError("restart must be a boolean.");
502+
}
503+
bool restart =
504+
Nan::MaybeLocal<Boolean>(info[0].As<Boolean>()).ToLocalChecked()->Value();
489505

490-
bool withLabels =
491-
Nan::MaybeLocal<Boolean>(info[2].As<Boolean>()).ToLocalChecked()->Value();
506+
WallProfiler* wallProfiler =
507+
Nan::ObjectWrap::Unwrap<WallProfiler>(info.Holder());
492508

493-
auto now = v8::base::TimeTicks::Now();
494-
wallProfiler->StartImpl(name, includeLines, withLabels);
495-
#ifdef DD_WALL_USE_SIGPROF
496-
if (withLabels) {
497-
wallProfiler->PushContext(now);
498-
struct sigaction sa, old_sa;
499-
sa.sa_flags = SA_SIGINFO | SA_RESTART;
500-
sa.sa_sigaction = &sighandler;
501-
sigemptyset(&sa.sa_mask);
502-
sigaction(SIGPROF, &sa, &old_sa);
503-
504-
// At the end of a cycle start is called before stop,
505-
// at this point old_sa.sa_sigaction is sighandler !
506-
if (!old_handler) {
507-
old_handler = old_sa.sa_sigaction;
508-
}
509+
wallProfiler->StartStop(restart, true, info);
510+
if (!restart) {
511+
wallProfiler->Dispose(info.GetIsolate());
509512
}
510-
#endif
511513
}
512514

513-
void WallProfiler::StartImpl(Local<String> name,
514-
bool includeLines,
515-
bool withLabels) {
516-
if (includeLines) {
517-
GetProfiler()->StartProfiling(
518-
name, CpuProfilingMode::kCallerLineNumbers, withLabels);
519-
} else {
520-
GetProfiler()->StartProfiling(name, withLabels);
521-
}
522-
}
515+
Nan::NAN_METHOD_RETURN_TYPE WallProfiler::StartStop(
516+
bool start, bool stop, Nan::NAN_METHOD_ARGS_TYPE info) {
517+
auto prevSession =
518+
currentSession.exchange(nullptr, std::memory_order_relaxed);
519+
std::atomic_signal_fence(std::memory_order_release);
523520

524-
NAN_METHOD(WallProfiler::Stop) {
525-
if (info.Length() != 2) {
526-
return Nan::ThrowTypeError("Stop must have two arguments.");
527-
}
528-
if (!info[0]->IsString()) {
529-
return Nan::ThrowTypeError("Profile name must be a string.");
521+
if (start && !stop && prevSession) {
522+
currentSession.store(prevSession, std::memory_order_relaxed);
523+
std::atomic_signal_fence(std::memory_order_release);
524+
return Nan::ThrowError("Start called with already started profiling "
525+
"session. Call stop first.");
530526
}
531-
if (!info[1]->IsBoolean()) {
532-
return Nan::ThrowTypeError("Include lines must be a boolean.");
527+
528+
if (stop && !prevSession) {
529+
return Nan::ThrowError(
530+
"Stop called without started profiling session. Call start first.");
533531
}
534532

535-
Local<String> name =
536-
Nan::MaybeLocal<String>(info[0].As<String>()).ToLocalChecked();
533+
ProfilingSession* nextSession = nullptr;
534+
if (start) {
535+
nextSession = prevSession == &session2 ? &session1 : &session2;
537536

538-
bool includeLines =
539-
Nan::MaybeLocal<Boolean>(info[1].As<Boolean>()).ToLocalChecked()->Value();
537+
auto now = v8::base::TimeTicks::Now();
538+
auto nextNameLocal = Nan::New<String>(std::to_string(now)).ToLocalChecked();
539+
nextSession->name.Reset(info.GetIsolate(), nextNameLocal);
540+
if (includeLines) {
541+
GetProfiler()->StartProfiling(
542+
nextNameLocal, CpuProfilingMode::kCallerLineNumbers, withLabels);
543+
} else {
544+
GetProfiler()->StartProfiling(nextNameLocal, withLabels);
545+
}
540546

541-
WallProfiler* wallProfiler =
542-
Nan::ObjectWrap::Unwrap<WallProfiler>(info.Holder());
547+
#ifdef DD_WALL_USE_SIGPROF
548+
if (withLabels) {
549+
auto labels = curLabels.load(std::memory_order_relaxed);
550+
nextSession->contexts.push_back(
551+
SampleContext(*labels, now, v8::base::TimeTicks::Now()));
552+
553+
struct sigaction sa, old_sa;
554+
sa.sa_flags = SA_SIGINFO | SA_RESTART;
555+
sa.sa_sigaction = &sighandler;
556+
sigemptyset(&sa.sa_mask);
557+
sigaction(SIGPROF, &sa, &old_sa);
558+
559+
if (!old_handler) {
560+
old_handler = old_sa.sa_sigaction;
561+
}
562+
}
563+
#endif
564+
}
543565

544-
auto profile = wallProfiler->StopImpl(name, includeLines);
566+
CpuProfile* v8_profile = nullptr;
545567

546-
info.GetReturnValue().Set(profile);
547-
}
568+
if (stop) {
569+
v8_profile =
570+
GetProfiler()->StopProfiling(prevSession->name.Get(info.GetIsolate()));
571+
prevSession->name.Reset();
572+
}
573+
574+
currentSession.store(nextSession, std::memory_order_relaxed);
575+
std::atomic_signal_fence(std::memory_order_release);
548576

549-
Local<Value> WallProfiler::StopImpl(Local<String> name,
550-
bool includeLines) {
551-
auto profiler = GetProfiler();
552-
auto v8_profile = profiler->StopProfiling(name);
553-
Local<Value> profile =
554-
ProfileTranslator(GetLabelSetsByNode(v8_profile))
555-
.TranslateTimeProfile(v8_profile, includeLines);
556-
v8_profile->Delete();
557-
return profile;
577+
if (v8_profile) {
578+
Local<Value> profile =
579+
ProfileTranslator(GetLabelSetsByNode(v8_profile, prevSession->contexts))
580+
.TranslateTimeProfile(v8_profile, includeLines);
581+
v8_profile->Delete();
582+
info.GetReturnValue().Set(profile);
583+
}
558584
}
559585

560586
NAN_MODULE_INIT(WallProfiler::Init) {
@@ -569,7 +595,6 @@ NAN_MODULE_INIT(WallProfiler::Init) {
569595
SetLabels);
570596

571597
Nan::SetPrototypeMethod(tpl, "start", Start);
572-
Nan::SetPrototypeMethod(tpl, "dispose", Dispose);
573598
Nan::SetPrototypeMethod(tpl, "stop", Stop);
574599

575600
PerIsolateData::For(Isolate::GetCurrent())
@@ -622,13 +647,17 @@ NAN_SETTER(WallProfiler::SetLabels) {
622647
profiler->SetLabels(info.GetIsolate(), value);
623648
}
624649

625-
void WallProfiler::PushContext(int64_t time_from) {
626-
// Be careful this is called in a signal handler context therefore all
627-
// operations must be async signal safe (in particular no allocations). Our
628-
// ring buffer avoids allocations.
629-
auto labels = curLabels.load(std::memory_order_relaxed);
650+
void WallProfiler::DoSignalHandler(int sig, siginfo_t* info, void* context) {
651+
auto currSession = currentSession.load(std::memory_order_relaxed);
630652
std::atomic_signal_fence(std::memory_order_acquire);
631-
contexts.push_back(SampleContext(*labels, time_from, v8::base::TimeTicks::Now()));
653+
if (currSession) {
654+
auto labels = curLabels.load(std::memory_order_relaxed);
655+
std::atomic_signal_fence(std::memory_order_acquire);
656+
auto start = v8::base::TimeTicks::Now();
657+
old_handler(sig, info, context);
658+
currSession->contexts.push_back(
659+
SampleContext(*labels, start, v8::base::TimeTicks::Now()));
660+
}
632661
}
633662

634663
} // namespace dd

bindings/profilers/wall.hh

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ using ValuePtr = std::shared_ptr<v8::Global<v8::Value>>;
1818
class WallProfiler : public Nan::ObjectWrap {
1919
private:
2020
int samplingInterval = 0;
21+
bool includeLines;
22+
bool withLabels;
2123
v8::CpuProfiler* cpuProfiler = nullptr;
2224
// TODO: Investigate use of v8::Persistent instead of shared_ptr<Global> to
2325
// avoid heap allocation. Need to figure out the right move/copy semantics in
@@ -37,10 +39,20 @@ class WallProfiler : public Nan::ObjectWrap {
3739
// Needed to initialize ring buffer elements
3840
SampleContext() = default;
3941

40-
SampleContext(const ValuePtr& l, int64_t from, int64_t to) : labels(l), time_from(from), time_to(to) {}
42+
SampleContext(const ValuePtr& l, int64_t from, int64_t to)
43+
: labels(l), time_from(from), time_to(to) {}
4144
};
4245

43-
RingBuffer<SampleContext> contexts;
46+
struct ProfilingSession {
47+
ProfilingSession(int contextsCapacity) : contexts(contextsCapacity) {}
48+
49+
v8::Global<v8::String> name;
50+
RingBuffer<SampleContext> contexts;
51+
};
52+
53+
ProfilingSession session1;
54+
ProfilingSession session2;
55+
std::atomic<ProfilingSession*> currentSession = nullptr;
4456

4557
~WallProfiler();
4658
void Dispose(v8::Isolate* isolate);
@@ -49,7 +61,8 @@ class WallProfiler : public Nan::ObjectWrap {
4961
// to work around https://bugs.chromium.org/p/v8/issues/detail?id=11051.
5062
v8::CpuProfiler* GetProfiler();
5163

52-
LabelSetsByNode GetLabelSetsByNode(v8::CpuProfile* profile);
64+
LabelSetsByNode GetLabelSetsByNode(v8::CpuProfile* profile,
65+
RingBuffer<SampleContext>& contexts);
5366

5467
public:
5568
/**
@@ -59,20 +72,20 @@ class WallProfiler : public Nan::ObjectWrap {
5972
* every period. The parameter is used to preallocate data structures that
6073
* should not be reallocated in async signal safe code.
6174
*/
62-
explicit WallProfiler(int intervalMicros, int durationMicros);
75+
explicit WallProfiler(int intervalMicros,
76+
int durationMicros,
77+
bool includeLines,
78+
bool withLabels);
6379

6480
v8::Local<v8::Value> GetLabels(v8::Isolate*);
6581
void SetLabels(v8::Isolate*, v8::Local<v8::Value>);
6682

67-
void PushContext(int64_t time_from);
68-
void StartImpl(v8::Local<v8::String> name,
69-
bool includeLines,
70-
bool withLabels);
71-
v8::Local<v8::Value> StopImpl(v8::Local<v8::String> name,
72-
bool includeLines);
83+
void DoSignalHandler(int sig, siginfo_t* info, void* context);
84+
Nan::NAN_METHOD_RETURN_TYPE StartStop(bool start,
85+
bool stop,
86+
Nan::NAN_METHOD_ARGS_TYPE info);
7387

7488
static NAN_METHOD(New);
75-
static NAN_METHOD(Dispose);
7689
static NAN_METHOD(Start);
7790
static NAN_METHOD(Stop);
7891
static NAN_MODULE_INIT(Init);

0 commit comments

Comments
 (0)