[cpu-profiler] Implement CPU profiler subsampling/multiplexing
Permit individual calls to CpuProfiler::StartSampling to provide their own requested sampling interval, to be snapped to the profiler's sampling interval. Use the greatest common divisor of all sample rates to determine what sample rate should be chosen for the sampling thread, and dispatch samples to attached profilers based on their requested sample periodicity. Change-Id: I0b076d09761d7176f31725e112578b68ab5da54c Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1484461 Reviewed-by: Ulan Degenbaev <ulan@chromium.org> Reviewed-by: Alexei Filippov <alph@chromium.org> Reviewed-by: Peter Marshall <petermarshall@chromium.org> Commit-Queue: Andrew Comminos <acomminos@fb.com> Cr-Commit-Position: refs/heads/master@{#61548}
This commit is contained in:
parent
a7985022f6
commit
deb3231a23
@ -321,22 +321,33 @@ class V8_EXPORT CpuProfilingOptions {
|
|||||||
* \param max_samples The maximum number of samples that should be recorded by
|
* \param max_samples The maximum number of samples that should be recorded by
|
||||||
* the profiler. Samples obtained after this limit will be
|
* the profiler. Samples obtained after this limit will be
|
||||||
* discarded.
|
* discarded.
|
||||||
|
* \param sampling_interval_us controls the profile-specific target
|
||||||
|
* sampling interval. The provided sampling
|
||||||
|
* interval will be snapped to the next lowest
|
||||||
|
* non-zero multiple of the profiler's sampling
|
||||||
|
* interval, set via SetSamplingInterval(). If
|
||||||
|
* zero, the sampling interval will be equal to
|
||||||
|
* the profiler's sampling interval.
|
||||||
*/
|
*/
|
||||||
CpuProfilingOptions(CpuProfilingMode mode = kLeafNodeLineNumbers,
|
CpuProfilingOptions(CpuProfilingMode mode = kLeafNodeLineNumbers,
|
||||||
bool record_samples = false,
|
bool record_samples = false,
|
||||||
unsigned max_samples = kNoSampleLimit)
|
unsigned max_samples = kNoSampleLimit,
|
||||||
|
int sampling_interval_us = 0)
|
||||||
: mode_(mode),
|
: mode_(mode),
|
||||||
record_samples_(record_samples),
|
record_samples_(record_samples),
|
||||||
max_samples_(max_samples) {}
|
max_samples_(max_samples),
|
||||||
|
sampling_interval_us_(sampling_interval_us) {}
|
||||||
|
|
||||||
CpuProfilingMode mode() const { return mode_; }
|
CpuProfilingMode mode() const { return mode_; }
|
||||||
bool record_samples() const { return record_samples_; }
|
bool record_samples() const { return record_samples_; }
|
||||||
unsigned max_samples() const { return max_samples_; }
|
unsigned max_samples() const { return max_samples_; }
|
||||||
|
int sampling_interval_us() const { return sampling_interval_us_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
CpuProfilingMode mode_;
|
CpuProfilingMode mode_;
|
||||||
bool record_samples_;
|
bool record_samples_;
|
||||||
unsigned max_samples_;
|
unsigned max_samples_;
|
||||||
|
int sampling_interval_us_;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -32,7 +32,9 @@ class CpuSampler : public sampler::Sampler {
|
|||||||
TickSample* sample = processor_->StartTickSample();
|
TickSample* sample = processor_->StartTickSample();
|
||||||
if (sample == nullptr) return;
|
if (sample == nullptr) return;
|
||||||
Isolate* isolate = reinterpret_cast<Isolate*>(this->isolate());
|
Isolate* isolate = reinterpret_cast<Isolate*>(this->isolate());
|
||||||
sample->Init(isolate, regs, TickSample::kIncludeCEntryFrame, true);
|
sample->Init(isolate, regs, TickSample::kIncludeCEntryFrame,
|
||||||
|
/* update_stats */ true,
|
||||||
|
/* use_simulator_reg_state */ true, processor_->period());
|
||||||
if (is_counting_samples_ && !sample->timestamp.IsNull()) {
|
if (is_counting_samples_ && !sample->timestamp.IsNull()) {
|
||||||
if (sample->state == JS) ++js_sample_count_;
|
if (sample->state == JS) ++js_sample_count_;
|
||||||
if (sample->state == EXTERNAL) ++external_sample_count_;
|
if (sample->state == EXTERNAL) ++external_sample_count_;
|
||||||
@ -243,6 +245,16 @@ void SamplingEventsProcessor::Run() {
|
|||||||
} while (ProcessCodeEvent());
|
} while (ProcessCodeEvent());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SamplingEventsProcessor::SetSamplingInterval(base::TimeDelta period) {
|
||||||
|
if (period_ == period) return;
|
||||||
|
StopSynchronously();
|
||||||
|
|
||||||
|
period_ = period;
|
||||||
|
base::Relaxed_Store(&running_, 1);
|
||||||
|
|
||||||
|
StartSynchronously();
|
||||||
|
}
|
||||||
|
|
||||||
void* SamplingEventsProcessor::operator new(size_t size) {
|
void* SamplingEventsProcessor::operator new(size_t size) {
|
||||||
return AlignedAlloc(size, alignof(SamplingEventsProcessor));
|
return AlignedAlloc(size, alignof(SamplingEventsProcessor));
|
||||||
}
|
}
|
||||||
@ -321,7 +333,7 @@ CpuProfiler::CpuProfiler(Isolate* isolate, CpuProfilingNamingMode naming_mode,
|
|||||||
ProfilerEventsProcessor* test_processor)
|
ProfilerEventsProcessor* test_processor)
|
||||||
: isolate_(isolate),
|
: isolate_(isolate),
|
||||||
naming_mode_(naming_mode),
|
naming_mode_(naming_mode),
|
||||||
sampling_interval_(base::TimeDelta::FromMicroseconds(
|
base_sampling_interval_(base::TimeDelta::FromMicroseconds(
|
||||||
FLAG_cpu_profiler_sampling_interval)),
|
FLAG_cpu_profiler_sampling_interval)),
|
||||||
profiles_(test_profiles),
|
profiles_(test_profiles),
|
||||||
generator_(test_generator),
|
generator_(test_generator),
|
||||||
@ -338,7 +350,7 @@ CpuProfiler::~CpuProfiler() {
|
|||||||
|
|
||||||
void CpuProfiler::set_sampling_interval(base::TimeDelta value) {
|
void CpuProfiler::set_sampling_interval(base::TimeDelta value) {
|
||||||
DCHECK(!is_profiling_);
|
DCHECK(!is_profiling_);
|
||||||
sampling_interval_ = value;
|
base_sampling_interval_ = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CpuProfiler::set_use_precise_sampling(bool value) {
|
void CpuProfiler::set_use_precise_sampling(bool value) {
|
||||||
@ -365,6 +377,17 @@ void CpuProfiler::CreateEntriesForRuntimeCallStats() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
base::TimeDelta CpuProfiler::ComputeSamplingInterval() const {
|
||||||
|
return profiles_->GetCommonSamplingInterval();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CpuProfiler::AdjustSamplingInterval() {
|
||||||
|
if (!processor_) return;
|
||||||
|
|
||||||
|
base::TimeDelta base_interval = ComputeSamplingInterval();
|
||||||
|
processor_->SetSamplingInterval(base_interval);
|
||||||
|
}
|
||||||
|
|
||||||
// static
|
// static
|
||||||
void CpuProfiler::CollectSample(Isolate* isolate) {
|
void CpuProfiler::CollectSample(Isolate* isolate) {
|
||||||
GetProfilersManager()->CallCollectSample(isolate);
|
GetProfilersManager()->CallCollectSample(isolate);
|
||||||
@ -380,6 +403,7 @@ void CpuProfiler::StartProfiling(const char* title,
|
|||||||
CpuProfilingOptions options) {
|
CpuProfilingOptions options) {
|
||||||
if (profiles_->StartProfiling(title, options)) {
|
if (profiles_->StartProfiling(title, options)) {
|
||||||
TRACE_EVENT0("v8", "CpuProfiler::StartProfiling");
|
TRACE_EVENT0("v8", "CpuProfiler::StartProfiling");
|
||||||
|
AdjustSamplingInterval();
|
||||||
StartProcessorIfNotStarted();
|
StartProcessorIfNotStarted();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -402,8 +426,9 @@ void CpuProfiler::StartProcessorIfNotStarted() {
|
|||||||
codemap_needs_initialization = true;
|
codemap_needs_initialization = true;
|
||||||
CreateEntriesForRuntimeCallStats();
|
CreateEntriesForRuntimeCallStats();
|
||||||
}
|
}
|
||||||
|
base::TimeDelta sampling_interval = ComputeSamplingInterval();
|
||||||
processor_.reset(new SamplingEventsProcessor(
|
processor_.reset(new SamplingEventsProcessor(
|
||||||
isolate_, generator_.get(), sampling_interval_, use_precise_sampling_));
|
isolate_, generator_.get(), sampling_interval, use_precise_sampling_));
|
||||||
if (profiler_listener_) {
|
if (profiler_listener_) {
|
||||||
profiler_listener_->set_observer(processor_.get());
|
profiler_listener_->set_observer(processor_.get());
|
||||||
} else {
|
} else {
|
||||||
@ -430,7 +455,9 @@ void CpuProfiler::StartProcessorIfNotStarted() {
|
|||||||
CpuProfile* CpuProfiler::StopProfiling(const char* title) {
|
CpuProfile* CpuProfiler::StopProfiling(const char* title) {
|
||||||
if (!is_profiling_) return nullptr;
|
if (!is_profiling_) return nullptr;
|
||||||
StopProcessorIfLastProfile(title);
|
StopProcessorIfLastProfile(title);
|
||||||
return profiles_->StopProfiling(title);
|
CpuProfile* result = profiles_->StopProfiling(title);
|
||||||
|
AdjustSamplingInterval();
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
CpuProfile* CpuProfiler::StopProfiling(String title) {
|
CpuProfile* CpuProfiler::StopProfiling(String title) {
|
||||||
|
@ -172,6 +172,8 @@ class V8_EXPORT_PRIVATE ProfilerEventsProcessor : public base::Thread,
|
|||||||
// Add a sample into the tick sample events buffer. Used for testing.
|
// Add a sample into the tick sample events buffer. Used for testing.
|
||||||
void AddSample(TickSample sample);
|
void AddSample(TickSample sample);
|
||||||
|
|
||||||
|
virtual void SetSamplingInterval(base::TimeDelta) {}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
ProfilerEventsProcessor(Isolate* isolate, ProfileGenerator* generator);
|
ProfilerEventsProcessor(Isolate* isolate, ProfileGenerator* generator);
|
||||||
|
|
||||||
@ -211,6 +213,8 @@ class V8_EXPORT_PRIVATE SamplingEventsProcessor
|
|||||||
|
|
||||||
void Run() override;
|
void Run() override;
|
||||||
|
|
||||||
|
void SetSamplingInterval(base::TimeDelta period) override;
|
||||||
|
|
||||||
// Tick sample events are filled directly in the buffer of the circular
|
// Tick sample events are filled directly in the buffer of the circular
|
||||||
// queue (because the structure is of fixed width, but usually not all
|
// queue (because the structure is of fixed width, but usually not all
|
||||||
// stack frame entries are filled.) This method returns a pointer to the
|
// stack frame entries are filled.) This method returns a pointer to the
|
||||||
@ -221,6 +225,7 @@ class V8_EXPORT_PRIVATE SamplingEventsProcessor
|
|||||||
inline void FinishTickSample();
|
inline void FinishTickSample();
|
||||||
|
|
||||||
sampler::Sampler* sampler() { return sampler_.get(); }
|
sampler::Sampler* sampler() { return sampler_.get(); }
|
||||||
|
base::TimeDelta period() const { return period_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SampleProcessingResult ProcessOneSample() override;
|
SampleProcessingResult ProcessOneSample() override;
|
||||||
@ -231,7 +236,7 @@ class V8_EXPORT_PRIVATE SamplingEventsProcessor
|
|||||||
SamplingCircularQueue<TickSampleEventRecord,
|
SamplingCircularQueue<TickSampleEventRecord,
|
||||||
kTickSampleQueueLength> ticks_buffer_;
|
kTickSampleQueueLength> ticks_buffer_;
|
||||||
std::unique_ptr<sampler::Sampler> sampler_;
|
std::unique_ptr<sampler::Sampler> sampler_;
|
||||||
const base::TimeDelta period_; // Samples & code events processing period.
|
base::TimeDelta period_; // Samples & code events processing period.
|
||||||
const bool use_precise_sampling_; // Whether or not busy-waiting is used for
|
const bool use_precise_sampling_; // Whether or not busy-waiting is used for
|
||||||
// low sampling intervals on Windows.
|
// low sampling intervals on Windows.
|
||||||
};
|
};
|
||||||
@ -251,6 +256,7 @@ class V8_EXPORT_PRIVATE CpuProfiler {
|
|||||||
typedef v8::CpuProfilingMode ProfilingMode;
|
typedef v8::CpuProfilingMode ProfilingMode;
|
||||||
typedef v8::CpuProfilingNamingMode NamingMode;
|
typedef v8::CpuProfilingNamingMode NamingMode;
|
||||||
|
|
||||||
|
base::TimeDelta sampling_interval() const { return base_sampling_interval_; }
|
||||||
void set_sampling_interval(base::TimeDelta value);
|
void set_sampling_interval(base::TimeDelta value);
|
||||||
void set_use_precise_sampling(bool);
|
void set_use_precise_sampling(bool);
|
||||||
void CollectSample();
|
void CollectSample();
|
||||||
@ -281,10 +287,18 @@ class V8_EXPORT_PRIVATE CpuProfiler {
|
|||||||
void LogBuiltins();
|
void LogBuiltins();
|
||||||
void CreateEntriesForRuntimeCallStats();
|
void CreateEntriesForRuntimeCallStats();
|
||||||
|
|
||||||
|
// Computes a sampling interval sufficient to accomodate attached profiles.
|
||||||
|
base::TimeDelta ComputeSamplingInterval() const;
|
||||||
|
// Dynamically updates the sampler to use a sampling interval sufficient for
|
||||||
|
// child profiles.
|
||||||
|
void AdjustSamplingInterval();
|
||||||
|
|
||||||
Isolate* const isolate_;
|
Isolate* const isolate_;
|
||||||
const NamingMode naming_mode_;
|
const NamingMode naming_mode_;
|
||||||
base::TimeDelta sampling_interval_;
|
|
||||||
bool use_precise_sampling_ = true;
|
bool use_precise_sampling_ = true;
|
||||||
|
// Sampling interval to which per-profile sampling intervals will be clamped
|
||||||
|
// to a multiple of, or used as the default if unspecified.
|
||||||
|
base::TimeDelta base_sampling_interval_;
|
||||||
std::unique_ptr<CpuProfilesCollection> profiles_;
|
std::unique_ptr<CpuProfilesCollection> profiles_;
|
||||||
std::unique_ptr<ProfileGenerator> generator_;
|
std::unique_ptr<ProfileGenerator> generator_;
|
||||||
std::unique_ptr<ProfilerEventsProcessor> processor_;
|
std::unique_ptr<ProfilerEventsProcessor> processor_;
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
#include "src/profiler/profile-generator.h"
|
#include "src/profiler/profile-generator.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
#include "src/objects/shared-function-info-inl.h"
|
#include "src/objects/shared-function-info-inl.h"
|
||||||
#include "src/profiler/cpu-profiler.h"
|
#include "src/profiler/cpu-profiler.h"
|
||||||
#include "src/profiler/profile-generator-inl.h"
|
#include "src/profiler/profile-generator-inl.h"
|
||||||
@ -488,9 +490,28 @@ CpuProfile::CpuProfile(CpuProfiler* profiler, const char* title,
|
|||||||
"Profile", id_, "data", std::move(value));
|
"Profile", id_, "data", std::move(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CpuProfile::CheckSubsample(base::TimeDelta source_sampling_interval) {
|
||||||
|
DCHECK_GE(source_sampling_interval, base::TimeDelta());
|
||||||
|
|
||||||
|
// If the sampling source's sampling interval is 0, record as many samples
|
||||||
|
// are possible irrespective of the profile's sampling interval. Manually
|
||||||
|
// taken samples (via CollectSample) fall into this case as well.
|
||||||
|
if (source_sampling_interval.IsZero()) return true;
|
||||||
|
|
||||||
|
next_sample_delta_ -= source_sampling_interval;
|
||||||
|
if (next_sample_delta_ <= base::TimeDelta()) {
|
||||||
|
next_sample_delta_ =
|
||||||
|
base::TimeDelta::FromMicroseconds(options_.sampling_interval_us());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void CpuProfile::AddPath(base::TimeTicks timestamp,
|
void CpuProfile::AddPath(base::TimeTicks timestamp,
|
||||||
const ProfileStackTrace& path, int src_line,
|
const ProfileStackTrace& path, int src_line,
|
||||||
bool update_stats) {
|
bool update_stats, base::TimeDelta sampling_interval) {
|
||||||
|
if (!CheckSubsample(sampling_interval)) return;
|
||||||
|
|
||||||
ProfileNode* top_frame_node =
|
ProfileNode* top_frame_node =
|
||||||
top_down_.AddPathFromEnd(path, src_line, update_stats, options_.mode());
|
top_down_.AddPathFromEnd(path, src_line, update_stats, options_.mode());
|
||||||
|
|
||||||
@ -760,15 +781,46 @@ void CpuProfilesCollection::RemoveProfile(CpuProfile* profile) {
|
|||||||
finished_profiles_.erase(pos);
|
finished_profiles_.erase(pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
int64_t GreatestCommonDivisor(int64_t a, int64_t b) {
|
||||||
|
return b ? GreatestCommonDivisor(b, a % b) : a;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
base::TimeDelta CpuProfilesCollection::GetCommonSamplingInterval() const {
|
||||||
|
DCHECK(profiler_);
|
||||||
|
|
||||||
|
int64_t base_sampling_interval_us =
|
||||||
|
profiler_->sampling_interval().InMicroseconds();
|
||||||
|
if (base_sampling_interval_us == 0) return base::TimeDelta();
|
||||||
|
|
||||||
|
int64_t interval_us = 0;
|
||||||
|
for (const auto& profile : current_profiles_) {
|
||||||
|
// Snap the profile's requested sampling interval to the next multiple of
|
||||||
|
// the base sampling interval.
|
||||||
|
int64_t profile_interval_us =
|
||||||
|
std::max<int64_t>(
|
||||||
|
(profile->sampling_interval_us() + base_sampling_interval_us - 1) /
|
||||||
|
base_sampling_interval_us,
|
||||||
|
1) *
|
||||||
|
base_sampling_interval_us;
|
||||||
|
interval_us = GreatestCommonDivisor(interval_us, profile_interval_us);
|
||||||
|
}
|
||||||
|
return base::TimeDelta::FromMicroseconds(interval_us);
|
||||||
|
}
|
||||||
|
|
||||||
void CpuProfilesCollection::AddPathToCurrentProfiles(
|
void CpuProfilesCollection::AddPathToCurrentProfiles(
|
||||||
base::TimeTicks timestamp, const ProfileStackTrace& path, int src_line,
|
base::TimeTicks timestamp, const ProfileStackTrace& path, int src_line,
|
||||||
bool update_stats) {
|
bool update_stats, base::TimeDelta sampling_interval) {
|
||||||
// As starting / stopping profiles is rare relatively to this
|
// As starting / stopping profiles is rare relatively to this
|
||||||
// method, we don't bother minimizing the duration of lock holding,
|
// method, we don't bother minimizing the duration of lock holding,
|
||||||
// e.g. copying contents of the list to a local vector.
|
// e.g. copying contents of the list to a local vector.
|
||||||
current_profiles_semaphore_.Wait();
|
current_profiles_semaphore_.Wait();
|
||||||
for (const std::unique_ptr<CpuProfile>& profile : current_profiles_) {
|
for (const std::unique_ptr<CpuProfile>& profile : current_profiles_) {
|
||||||
profile->AddPath(timestamp, path, src_line, update_stats);
|
profile->AddPath(timestamp, path, src_line, update_stats,
|
||||||
|
sampling_interval);
|
||||||
}
|
}
|
||||||
current_profiles_semaphore_.Signal();
|
current_profiles_semaphore_.Signal();
|
||||||
}
|
}
|
||||||
@ -902,7 +954,8 @@ void ProfileGenerator::RecordTickSample(const TickSample& sample) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
profiles_->AddPathToCurrentProfiles(sample.timestamp, stack_trace, src_line,
|
profiles_->AddPathToCurrentProfiles(sample.timestamp, stack_trace, src_line,
|
||||||
sample.update_stats);
|
sample.update_stats,
|
||||||
|
sample.sampling_interval);
|
||||||
}
|
}
|
||||||
|
|
||||||
CodeEntry* ProfileGenerator::EntryForVMState(StateTag tag) {
|
CodeEntry* ProfileGenerator::EntryForVMState(StateTag tag) {
|
||||||
|
@ -364,12 +364,16 @@ class CpuProfile {
|
|||||||
int line;
|
int line;
|
||||||
};
|
};
|
||||||
|
|
||||||
CpuProfile(CpuProfiler* profiler, const char* title,
|
V8_EXPORT_PRIVATE CpuProfile(CpuProfiler* profiler, const char* title,
|
||||||
CpuProfilingOptions options);
|
CpuProfilingOptions options);
|
||||||
|
|
||||||
|
// Checks whether or not the given TickSample should be (sub)sampled, given
|
||||||
|
// the sampling interval of the profiler that recorded it (in microseconds).
|
||||||
|
V8_EXPORT_PRIVATE bool CheckSubsample(base::TimeDelta sampling_interval);
|
||||||
// Add pc -> ... -> main() call path to the profile.
|
// Add pc -> ... -> main() call path to the profile.
|
||||||
void AddPath(base::TimeTicks timestamp, const ProfileStackTrace& path,
|
void AddPath(base::TimeTicks timestamp, const ProfileStackTrace& path,
|
||||||
int src_line, bool update_stats);
|
int src_line, bool update_stats,
|
||||||
|
base::TimeDelta sampling_interval);
|
||||||
void FinishProfile();
|
void FinishProfile();
|
||||||
|
|
||||||
const char* title() const { return title_; }
|
const char* title() const { return title_; }
|
||||||
@ -378,6 +382,10 @@ class CpuProfile {
|
|||||||
int samples_count() const { return static_cast<int>(samples_.size()); }
|
int samples_count() const { return static_cast<int>(samples_.size()); }
|
||||||
const SampleInfo& sample(int index) const { return samples_[index]; }
|
const SampleInfo& sample(int index) const { return samples_[index]; }
|
||||||
|
|
||||||
|
int64_t sampling_interval_us() const {
|
||||||
|
return options_.sampling_interval_us();
|
||||||
|
}
|
||||||
|
|
||||||
base::TimeTicks start_time() const { return start_time_; }
|
base::TimeTicks start_time() const { return start_time_; }
|
||||||
base::TimeTicks end_time() const { return end_time_; }
|
base::TimeTicks end_time() const { return end_time_; }
|
||||||
CpuProfiler* cpu_profiler() const { return profiler_; }
|
CpuProfiler* cpu_profiler() const { return profiler_; }
|
||||||
@ -398,6 +406,9 @@ class CpuProfile {
|
|||||||
CpuProfiler* const profiler_;
|
CpuProfiler* const profiler_;
|
||||||
size_t streaming_next_sample_;
|
size_t streaming_next_sample_;
|
||||||
uint32_t id_;
|
uint32_t id_;
|
||||||
|
// Number of microseconds worth of profiler ticks that should elapse before
|
||||||
|
// the next sample is recorded.
|
||||||
|
base::TimeDelta next_sample_delta_;
|
||||||
|
|
||||||
static std::atomic<uint32_t> last_id_;
|
static std::atomic<uint32_t> last_id_;
|
||||||
|
|
||||||
@ -446,6 +457,7 @@ class V8_EXPORT_PRIVATE CpuProfilesCollection {
|
|||||||
|
|
||||||
void set_cpu_profiler(CpuProfiler* profiler) { profiler_ = profiler; }
|
void set_cpu_profiler(CpuProfiler* profiler) { profiler_ = profiler; }
|
||||||
bool StartProfiling(const char* title, CpuProfilingOptions options = {});
|
bool StartProfiling(const char* title, CpuProfilingOptions options = {});
|
||||||
|
|
||||||
CpuProfile* StopProfiling(const char* title);
|
CpuProfile* StopProfiling(const char* title);
|
||||||
std::vector<std::unique_ptr<CpuProfile>>* profiles() {
|
std::vector<std::unique_ptr<CpuProfile>>* profiles() {
|
||||||
return &finished_profiles_;
|
return &finished_profiles_;
|
||||||
@ -454,10 +466,16 @@ class V8_EXPORT_PRIVATE CpuProfilesCollection {
|
|||||||
bool IsLastProfile(const char* title);
|
bool IsLastProfile(const char* title);
|
||||||
void RemoveProfile(CpuProfile* profile);
|
void RemoveProfile(CpuProfile* profile);
|
||||||
|
|
||||||
|
// Finds a common sampling interval dividing each CpuProfile's interval,
|
||||||
|
// rounded up to the nearest multiple of the CpuProfiler's sampling interval.
|
||||||
|
// Returns 0 if no profiles are attached.
|
||||||
|
base::TimeDelta GetCommonSamplingInterval() const;
|
||||||
|
|
||||||
// Called from profile generator thread.
|
// Called from profile generator thread.
|
||||||
void AddPathToCurrentProfiles(base::TimeTicks timestamp,
|
void AddPathToCurrentProfiles(base::TimeTicks timestamp,
|
||||||
const ProfileStackTrace& path, int src_line,
|
const ProfileStackTrace& path, int src_line,
|
||||||
bool update_stats);
|
bool update_stats,
|
||||||
|
base::TimeDelta sampling_interval);
|
||||||
|
|
||||||
// Limits the number of profiles that can be simultaneously collected.
|
// Limits the number of profiles that can be simultaneously collected.
|
||||||
static const int kMaxSimultaneousProfiles = 100;
|
static const int kMaxSimultaneousProfiles = 100;
|
||||||
|
@ -292,10 +292,12 @@ namespace internal {
|
|||||||
|
|
||||||
void TickSample::Init(Isolate* isolate, const v8::RegisterState& state,
|
void TickSample::Init(Isolate* isolate, const v8::RegisterState& state,
|
||||||
RecordCEntryFrame record_c_entry_frame, bool update_stats,
|
RecordCEntryFrame record_c_entry_frame, bool update_stats,
|
||||||
bool use_simulator_reg_state) {
|
bool use_simulator_reg_state,
|
||||||
|
base::TimeDelta sampling_interval) {
|
||||||
v8::TickSample::Init(reinterpret_cast<v8::Isolate*>(isolate), state,
|
v8::TickSample::Init(reinterpret_cast<v8::Isolate*>(isolate), state,
|
||||||
record_c_entry_frame, update_stats,
|
record_c_entry_frame, update_stats,
|
||||||
use_simulator_reg_state);
|
use_simulator_reg_state);
|
||||||
|
this->sampling_interval = sampling_interval;
|
||||||
if (pc == nullptr) return;
|
if (pc == nullptr) return;
|
||||||
timestamp = base::TimeTicks::HighResolutionNow();
|
timestamp = base::TimeTicks::HighResolutionNow();
|
||||||
}
|
}
|
||||||
@ -312,6 +314,8 @@ void TickSample::print() const {
|
|||||||
PrintF(" - %s: %p\n",
|
PrintF(" - %s: %p\n",
|
||||||
has_external_callback ? "external_callback_entry" : "tos", tos);
|
has_external_callback ? "external_callback_entry" : "tos", tos);
|
||||||
PrintF(" - update_stats: %d\n", update_stats);
|
PrintF(" - update_stats: %d\n", update_stats);
|
||||||
|
PrintF(" - sampling_interval: %" PRId64 "\n",
|
||||||
|
sampling_interval.InMicroseconds());
|
||||||
PrintF("\n");
|
PrintF("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,8 +17,10 @@ class Isolate;
|
|||||||
struct TickSample : public v8::TickSample {
|
struct TickSample : public v8::TickSample {
|
||||||
void Init(Isolate* isolate, const v8::RegisterState& state,
|
void Init(Isolate* isolate, const v8::RegisterState& state,
|
||||||
RecordCEntryFrame record_c_entry_frame, bool update_stats,
|
RecordCEntryFrame record_c_entry_frame, bool update_stats,
|
||||||
bool use_simulator_reg_state = true);
|
bool use_simulator_reg_state = true,
|
||||||
|
base::TimeDelta sampling_interval = base::TimeDelta());
|
||||||
base::TimeTicks timestamp;
|
base::TimeTicks timestamp;
|
||||||
|
base::TimeDelta sampling_interval; // Sampling interval used to capture.
|
||||||
|
|
||||||
void print() const;
|
void print() const;
|
||||||
};
|
};
|
||||||
|
@ -3171,6 +3171,171 @@ TEST(SampleLimit) {
|
|||||||
CHECK_EQ(profile->GetSamplesCount(), 50);
|
CHECK_EQ(profile->GetSamplesCount(), 50);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tests that a CpuProfile instance subsamples from a stream of tick samples
|
||||||
|
// appropriately.
|
||||||
|
TEST(ProflilerSubsampling) {
|
||||||
|
LocalContext env;
|
||||||
|
i::Isolate* isolate = CcTest::i_isolate();
|
||||||
|
i::HandleScope scope(isolate);
|
||||||
|
|
||||||
|
CpuProfilesCollection* profiles = new CpuProfilesCollection(isolate);
|
||||||
|
ProfileGenerator* generator = new ProfileGenerator(profiles);
|
||||||
|
ProfilerEventsProcessor* processor = new SamplingEventsProcessor(
|
||||||
|
isolate, generator, v8::base::TimeDelta::FromMicroseconds(1),
|
||||||
|
/* use_precise_sampling */ true);
|
||||||
|
CpuProfiler profiler(isolate, kDebugNaming, profiles, generator, processor);
|
||||||
|
|
||||||
|
// Create a new CpuProfile that wants samples at 8us.
|
||||||
|
CpuProfile profile(&profiler, "",
|
||||||
|
{v8::CpuProfilingMode::kLeafNodeLineNumbers, true,
|
||||||
|
v8::CpuProfilingOptions::kNoSampleLimit, 8});
|
||||||
|
// Verify that the first sample is always included.
|
||||||
|
CHECK(profile.CheckSubsample(base::TimeDelta::FromMicroseconds(10)));
|
||||||
|
|
||||||
|
// 4 2us samples should result in one 8us sample.
|
||||||
|
CHECK(!profile.CheckSubsample(base::TimeDelta::FromMicroseconds(2)));
|
||||||
|
CHECK(!profile.CheckSubsample(base::TimeDelta::FromMicroseconds(2)));
|
||||||
|
CHECK(!profile.CheckSubsample(base::TimeDelta::FromMicroseconds(2)));
|
||||||
|
CHECK(profile.CheckSubsample(base::TimeDelta::FromMicroseconds(2)));
|
||||||
|
|
||||||
|
// Profiles should expect the source sample interval to change, in which case
|
||||||
|
// they should still take the first sample elapsed after their interval.
|
||||||
|
CHECK(!profile.CheckSubsample(base::TimeDelta::FromMicroseconds(2)));
|
||||||
|
CHECK(!profile.CheckSubsample(base::TimeDelta::FromMicroseconds(2)));
|
||||||
|
CHECK(!profile.CheckSubsample(base::TimeDelta::FromMicroseconds(2)));
|
||||||
|
CHECK(profile.CheckSubsample(base::TimeDelta::FromMicroseconds(4)));
|
||||||
|
|
||||||
|
// Aligned samples (at 8us) are always included.
|
||||||
|
CHECK(profile.CheckSubsample(base::TimeDelta::FromMicroseconds(8)));
|
||||||
|
|
||||||
|
// Samples with a rate of 0 should always be included.
|
||||||
|
CHECK(profile.CheckSubsample(base::TimeDelta::FromMicroseconds(0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests that the base sampling rate of a CpuProfilesCollection is dynamically
|
||||||
|
// chosen based on the GCD of its child profiles.
|
||||||
|
TEST(DynamicResampling) {
|
||||||
|
LocalContext env;
|
||||||
|
i::Isolate* isolate = CcTest::i_isolate();
|
||||||
|
i::HandleScope scope(isolate);
|
||||||
|
|
||||||
|
CpuProfilesCollection* profiles = new CpuProfilesCollection(isolate);
|
||||||
|
ProfileGenerator* generator = new ProfileGenerator(profiles);
|
||||||
|
ProfilerEventsProcessor* processor = new SamplingEventsProcessor(
|
||||||
|
isolate, generator, v8::base::TimeDelta::FromMicroseconds(1),
|
||||||
|
/* use_precise_sampling */ true);
|
||||||
|
CpuProfiler profiler(isolate, kDebugNaming, profiles, generator, processor);
|
||||||
|
|
||||||
|
// Set a 1us base sampling rate, dividing all possible intervals.
|
||||||
|
profiler.set_sampling_interval(base::TimeDelta::FromMicroseconds(1));
|
||||||
|
|
||||||
|
// Verify that the sampling interval with no started profilers is unset.
|
||||||
|
CHECK_EQ(profiles->GetCommonSamplingInterval(), base::TimeDelta());
|
||||||
|
|
||||||
|
// Add a 10us profiler, verify that the base sampling interval is as high as
|
||||||
|
// possible (10us).
|
||||||
|
profiles->StartProfiling("10us",
|
||||||
|
{v8::CpuProfilingMode::kLeafNodeLineNumbers, true,
|
||||||
|
v8::CpuProfilingOptions::kNoSampleLimit, 10});
|
||||||
|
CHECK_EQ(profiles->GetCommonSamplingInterval(),
|
||||||
|
base::TimeDelta::FromMicroseconds(10));
|
||||||
|
|
||||||
|
// Add a 5us profiler, verify that the base sampling interval is as high as
|
||||||
|
// possible given a 10us and 5us profiler (5us).
|
||||||
|
profiles->StartProfiling("5us",
|
||||||
|
{v8::CpuProfilingMode::kLeafNodeLineNumbers, true,
|
||||||
|
v8::CpuProfilingOptions::kNoSampleLimit, 5});
|
||||||
|
CHECK_EQ(profiles->GetCommonSamplingInterval(),
|
||||||
|
base::TimeDelta::FromMicroseconds(5));
|
||||||
|
|
||||||
|
// Add a 3us profiler, verify that the base sampling interval is 1us (due to
|
||||||
|
// coprime intervals).
|
||||||
|
profiles->StartProfiling("3us",
|
||||||
|
{v8::CpuProfilingMode::kLeafNodeLineNumbers, true,
|
||||||
|
v8::CpuProfilingOptions::kNoSampleLimit, 3});
|
||||||
|
CHECK_EQ(profiles->GetCommonSamplingInterval(),
|
||||||
|
base::TimeDelta::FromMicroseconds(1));
|
||||||
|
|
||||||
|
// Remove the 5us profiler, verify that the sample interval stays at 1us.
|
||||||
|
profiles->StopProfiling("5us");
|
||||||
|
CHECK_EQ(profiles->GetCommonSamplingInterval(),
|
||||||
|
base::TimeDelta::FromMicroseconds(1));
|
||||||
|
|
||||||
|
// Remove the 10us profiler, verify that the sample interval becomes 3us.
|
||||||
|
profiles->StopProfiling("10us");
|
||||||
|
CHECK_EQ(profiles->GetCommonSamplingInterval(),
|
||||||
|
base::TimeDelta::FromMicroseconds(3));
|
||||||
|
|
||||||
|
// Remove the 3us profiler, verify that the sample interval becomes unset.
|
||||||
|
profiles->StopProfiling("3us");
|
||||||
|
CHECK_EQ(profiles->GetCommonSamplingInterval(), base::TimeDelta());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensures that when a non-unit base sampling interval is set on the profiler,
|
||||||
|
// that the sampling rate gets snapped to the nearest multiple prior to GCD
|
||||||
|
// computation.
|
||||||
|
TEST(DynamicResamplingWithBaseInterval) {
|
||||||
|
LocalContext env;
|
||||||
|
i::Isolate* isolate = CcTest::i_isolate();
|
||||||
|
i::HandleScope scope(isolate);
|
||||||
|
|
||||||
|
CpuProfilesCollection* profiles = new CpuProfilesCollection(isolate);
|
||||||
|
ProfileGenerator* generator = new ProfileGenerator(profiles);
|
||||||
|
ProfilerEventsProcessor* processor = new SamplingEventsProcessor(
|
||||||
|
isolate, generator, v8::base::TimeDelta::FromMicroseconds(1),
|
||||||
|
/* use_precise_sampling */ true);
|
||||||
|
CpuProfiler profiler(isolate, kDebugNaming, profiles, generator, processor);
|
||||||
|
|
||||||
|
profiler.set_sampling_interval(base::TimeDelta::FromMicroseconds(7));
|
||||||
|
|
||||||
|
// Verify that the sampling interval with no started profilers is unset.
|
||||||
|
CHECK_EQ(profiles->GetCommonSamplingInterval(), base::TimeDelta());
|
||||||
|
|
||||||
|
// Add a profiler with an unset sampling interval, verify that the common
|
||||||
|
// sampling interval is equal to the base.
|
||||||
|
profiles->StartProfiling("unset",
|
||||||
|
{v8::CpuProfilingMode::kLeafNodeLineNumbers, true,
|
||||||
|
v8::CpuProfilingOptions::kNoSampleLimit});
|
||||||
|
CHECK_EQ(profiles->GetCommonSamplingInterval(),
|
||||||
|
base::TimeDelta::FromMicroseconds(7));
|
||||||
|
profiles->StopProfiling("unset");
|
||||||
|
|
||||||
|
// Adding a 8us sampling interval rounds to a 14us base interval.
|
||||||
|
profiles->StartProfiling("8us",
|
||||||
|
{v8::CpuProfilingMode::kLeafNodeLineNumbers, true,
|
||||||
|
v8::CpuProfilingOptions::kNoSampleLimit, 8});
|
||||||
|
CHECK_EQ(profiles->GetCommonSamplingInterval(),
|
||||||
|
base::TimeDelta::FromMicroseconds(14));
|
||||||
|
|
||||||
|
// Adding a 4us sampling interval should cause a lowering to a 7us interval.
|
||||||
|
profiles->StartProfiling("4us",
|
||||||
|
{v8::CpuProfilingMode::kLeafNodeLineNumbers, true,
|
||||||
|
v8::CpuProfilingOptions::kNoSampleLimit, 4});
|
||||||
|
CHECK_EQ(profiles->GetCommonSamplingInterval(),
|
||||||
|
base::TimeDelta::FromMicroseconds(7));
|
||||||
|
|
||||||
|
// Removing the 4us sampling interval should restore the 14us sampling
|
||||||
|
// interval.
|
||||||
|
profiles->StopProfiling("4us");
|
||||||
|
CHECK_EQ(profiles->GetCommonSamplingInterval(),
|
||||||
|
base::TimeDelta::FromMicroseconds(14));
|
||||||
|
|
||||||
|
// Removing the 8us sampling interval should unset the common sampling
|
||||||
|
// interval.
|
||||||
|
profiles->StopProfiling("8us");
|
||||||
|
CHECK_EQ(profiles->GetCommonSamplingInterval(), base::TimeDelta());
|
||||||
|
|
||||||
|
// A sampling interval of 0us should enforce all profiles to have a sampling
|
||||||
|
// interval of 0us (the only multiple of 0).
|
||||||
|
profiler.set_sampling_interval(base::TimeDelta::FromMicroseconds(0));
|
||||||
|
profiles->StartProfiling("5us",
|
||||||
|
{v8::CpuProfilingMode::kLeafNodeLineNumbers, true,
|
||||||
|
v8::CpuProfilingOptions::kNoSampleLimit, 5});
|
||||||
|
CHECK_EQ(profiles->GetCommonSamplingInterval(),
|
||||||
|
base::TimeDelta::FromMicroseconds(0));
|
||||||
|
profiles->StopProfiling("5us");
|
||||||
|
}
|
||||||
|
|
||||||
enum class EntryCountMode { kAll, kOnlyInlined };
|
enum class EntryCountMode { kAll, kOnlyInlined };
|
||||||
|
|
||||||
// Count the number of unique source positions.
|
// Count the number of unique source positions.
|
||||||
|
Loading…
Reference in New Issue
Block a user