Skip to content

Commit 5df66fb

Browse files
committed
Move profiler start/stop/restart logic into native code.
Make ring buffer operations interrupt safe.
1 parent cf22c9d commit 5df66fb

4 files changed

Lines changed: 178 additions & 153 deletions

File tree

bindings/profilers/wall.cc

Lines changed: 137 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,13 +355,23 @@ 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
curLabels.store(&labels1, std::memory_order_relaxed);
374+
currentSession.store(nullptr, std::memory_order_relaxed);
366375
}
367376

368377
WallProfiler::~WallProfiler() {
@@ -416,16 +425,9 @@ void WallProfiler::Dispose(Isolate* isolate) {
416425
}
417426
}
418427

419-
NAN_METHOD(WallProfiler::Dispose) {
420-
WallProfiler* wallProfiler =
421-
Nan::ObjectWrap::Unwrap<WallProfiler>(info.Holder());
422-
423-
wallProfiler->Dispose(info.GetIsolate());
424-
}
425-
426428
NAN_METHOD(WallProfiler::New) {
427-
if (info.Length() != 2) {
428-
return Nan::ThrowTypeError("WallProfiler must have two arguments.");
429+
if (info.Length() != 4) {
430+
return Nan::ThrowTypeError("WallProfiler must have four arguments.");
429431
}
430432
if (!info[0]->IsNumber()) {
431433
return Nan::ThrowTypeError("Sample rate must be a number.");
@@ -435,6 +437,14 @@ NAN_METHOD(WallProfiler::New) {
435437
return Nan::ThrowTypeError("Duration must be a number.");
436438
}
437439

440+
if (!info[2]->IsBoolean()) {
441+
return Nan::ThrowTypeError("includeLines must be a boolean.");
442+
}
443+
444+
if (!info[3]->IsBoolean()) {
445+
return Nan::ThrowTypeError("withLabels must be a boolean.");
446+
}
447+
438448
if (info.IsConstructCall()) {
439449
int interval = Nan::MaybeLocal<Integer>(info[0].As<Integer>())
440450
.ToLocalChecked()
@@ -453,12 +463,21 @@ NAN_METHOD(WallProfiler::New) {
453463
return Nan::ThrowTypeError("Duration must not be less than sample rate.");
454464
}
455465

456-
WallProfiler* obj = new WallProfiler(interval, duration);
466+
bool includeLines = Nan::MaybeLocal<Boolean>(info[2].As<Boolean>())
467+
.ToLocalChecked()
468+
->Value();
469+
470+
bool withLabels = Nan::MaybeLocal<Boolean>(info[3].As<Boolean>())
471+
.ToLocalChecked()
472+
->Value();
473+
474+
WallProfiler* obj =
475+
new WallProfiler(interval, duration, includeLines, withLabels);
457476
obj->Wrap(info.This());
458477
info.GetReturnValue().Set(info.This());
459478
} else {
460-
const int argc = 2;
461-
v8::Local<v8::Value> argv[argc] = {info[0], info[1]};
479+
const int argc = 4;
480+
v8::Local<v8::Value> argv[argc] = {info[0], info[1], info[2], info[3]};
462481
v8::Local<v8::Function> cons = Nan::New(
463482
PerIsolateData::For(info.GetIsolate())->WallProfilerConstructor());
464483
info.GetReturnValue().Set(
@@ -470,93 +489,110 @@ NAN_METHOD(WallProfiler::Start) {
470489
WallProfiler* wallProfiler =
471490
Nan::ObjectWrap::Unwrap<WallProfiler>(info.Holder());
472491

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

486-
Local<String> name =
487-
Nan::MaybeLocal<String>(info[0].As<String>()).ToLocalChecked();
496+
return wallProfiler->StartStop(true, false, info);
497+
}
488498

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

492-
bool withLabels =
493-
Nan::MaybeLocal<Boolean>(info[2].As<Boolean>()).ToLocalChecked()->Value();
509+
WallProfiler* wallProfiler =
510+
Nan::ObjectWrap::Unwrap<WallProfiler>(info.Holder());
494511

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

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

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

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

540-
bool includeLines =
541-
Nan::MaybeLocal<Boolean>(info[1].As<Boolean>()).ToLocalChecked()->Value();
540+
auto now = v8::base::TimeTicks::Now();
541+
auto nameLocal = Nan::New<String>(std::to_string(now)).ToLocalChecked();
542+
nextSession->name.Reset(info.GetIsolate(), nameLocal);
543+
CpuProfilingMode mode = includeLines
544+
? CpuProfilingMode::kCallerLineNumbers
545+
: CpuProfilingMode::kLeafNodeLineNumbers;
546+
auto status = GetProfiler()->StartProfiling(nameLocal, mode, withLabels);
547+
switch (status) {
548+
case CpuProfilingStatus::kStarted:
549+
break;
550+
case CpuProfilingStatus::kAlreadyStarted:
551+
return Nan::ThrowError("Failed to start V8 profiler; already started");
552+
case CpuProfilingStatus::kErrorTooManyProfilers:
553+
return Nan::ThrowError(
554+
"Failed to start V8 profiler; too many profilers");
555+
default:
556+
return Nan::ThrowError("Failed to start V8 profiler; unknown error");
557+
}
542558

543-
WallProfiler* wallProfiler =
544-
Nan::ObjectWrap::Unwrap<WallProfiler>(info.Holder());
559+
#ifdef DD_WALL_USE_SIGPROF
560+
if (withLabels) {
561+
auto labels = curLabels.load(std::memory_order_relaxed);
562+
nextSession->contexts.push_back(
563+
SampleContext(*labels, now, v8::base::TimeTicks::Now()));
564+
565+
struct sigaction sa, old_sa;
566+
sa.sa_flags = SA_SIGINFO | SA_RESTART;
567+
sa.sa_sigaction = &sighandler;
568+
sigemptyset(&sa.sa_mask);
569+
sigaction(SIGPROF, &sa, &old_sa);
570+
571+
if (!old_handler) {
572+
old_handler = old_sa.sa_sigaction;
573+
}
574+
}
575+
#endif
576+
}
545577

546-
auto profile = wallProfiler->StopImpl(name, includeLines);
578+
CpuProfile* v8_profile = nullptr;
547579

548-
info.GetReturnValue().Set(profile);
549-
}
580+
if (stop) {
581+
v8_profile =
582+
GetProfiler()->StopProfiling(prevSession->name.Get(info.GetIsolate()));
583+
prevSession->name.Reset();
584+
}
585+
586+
currentSession.store(nextSession, std::memory_order_relaxed);
587+
std::atomic_signal_fence(std::memory_order_release);
550588

551-
Local<Value> WallProfiler::StopImpl(Local<String> name,
552-
bool includeLines) {
553-
auto profiler = GetProfiler();
554-
auto v8_profile = profiler->StopProfiling(name);
555-
Local<Value> profile =
556-
ProfileTranslator(GetLabelSetsByNode(v8_profile))
557-
.TranslateTimeProfile(v8_profile, includeLines);
558-
v8_profile->Delete();
559-
return profile;
589+
if (v8_profile) {
590+
Local<Value> profile =
591+
ProfileTranslator(GetLabelSetsByNode(v8_profile, prevSession->contexts))
592+
.TranslateTimeProfile(v8_profile, includeLines);
593+
v8_profile->Delete();
594+
info.GetReturnValue().Set(profile);
595+
}
560596
}
561597

562598
NAN_MODULE_INIT(WallProfiler::Init) {
@@ -571,7 +607,6 @@ NAN_MODULE_INIT(WallProfiler::Init) {
571607
SetLabels);
572608

573609
Nan::SetPrototypeMethod(tpl, "start", Start);
574-
Nan::SetPrototypeMethod(tpl, "dispose", Dispose);
575610
Nan::SetPrototypeMethod(tpl, "stop", Stop);
576611

577612
PerIsolateData::For(Isolate::GetCurrent())
@@ -624,13 +659,17 @@ NAN_SETTER(WallProfiler::SetLabels) {
624659
profiler->SetLabels(info.GetIsolate(), value);
625660
}
626661

627-
void WallProfiler::PushContext(int64_t time_from) {
628-
// Be careful this is called in a signal handler context therefore all
629-
// operations must be async signal safe (in particular no allocations). Our
630-
// ring buffer avoids allocations.
631-
auto labels = curLabels.load(std::memory_order_relaxed);
662+
void WallProfiler::DoSignalHandler(int sig, siginfo_t* info, void* context) {
663+
auto currSession = currentSession.load(std::memory_order_relaxed);
632664
std::atomic_signal_fence(std::memory_order_acquire);
633-
contexts.push_back(SampleContext(*labels, time_from, v8::base::TimeTicks::Now()));
665+
if (currSession) {
666+
auto labels = curLabels.load(std::memory_order_relaxed);
667+
std::atomic_signal_fence(std::memory_order_acquire);
668+
auto start = v8::base::TimeTicks::Now();
669+
old_handler(sig, info, context);
670+
currSession->contexts.push_back(
671+
SampleContext(*labels, start, v8::base::TimeTicks::Now()));
672+
}
634673
}
635674

636675
} // namespace dd

0 commit comments

Comments
 (0)