Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(328)

Unified Diff: runtime/vm/profiler.cc

Issue 25909002: Sampling profiler (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: runtime/vm/profiler.cc
diff --git a/runtime/vm/profiler.cc b/runtime/vm/profiler.cc
new file mode 100644
index 0000000000000000000000000000000000000000..892835ee1c509e3872740096a5975e434df65123
--- /dev/null
+++ b/runtime/vm/profiler.cc
@@ -0,0 +1,652 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+#include <cstdio>
+
+#include "platform/utils.h"
+
+#include "vm/isolate.h"
+#include "vm/json_stream.h"
+#include "vm/native_symbol.h"
+#include "vm/object.h"
+#include "vm/os.h"
+#include "vm/profiler.h"
+#include "vm/signal_handler.h"
+
+namespace dart {
+
+#if defined(TARGET_OS_LINUX) || defined(TARGET_OS_MACOS) || \
+ defined(TARGET_OS_ANDROID)
+#define PROFILER_USES_SIGNALS
+#endif
+
+DEFINE_FLAG(bool, profile, true, "Enable Sampling Profiler");
+
+#if defined(PROFILER_USES_SIGNALS)
+static void ProfileSignalAction(int signal, siginfo_t* info, void* context_);
+#endif
+
+class ProfilerSampleStackWalker {
+ public:
+ ProfilerSampleStackWalker(Sample* sample, uintptr_t stack_lower,
+ uintptr_t stack_upper) : sample_(sample),
+ stack_lower_(stack_lower),
+ stack_upper_(stack_upper) {
+ }
+
+ int walk(uintptr_t pc_, uintptr_t fp_) {
siva 2013/10/28 05:19:21 why underscrore for these param names?
Cutch 2013/11/04 20:36:05 Cleaned up.
+ original_pc_ = pc_;
+ original_fp_ = fp_;
+ uword* pc = reinterpret_cast<uword*>(pc_);
+ uword* fp = reinterpret_cast<uword*>(fp_);
+ int i = 0;
+ for (; i < Sample::kNumStackFrames; i++) {
+ sample_->pcs[i] = reinterpret_cast<uintptr_t>(pc);
+ if (!ValidInstructionPointer(pc) || !ValidFramePointer(fp, i)) {
+ break;
+ }
+ pc = parent_pc(fp);
+ fp = parent_fp(fp);
+ }
+ return i;
+ }
+
+ private:
+ uword* parent_pc(uword* fp) {
+ ASSERT(fp != NULL);
+ return reinterpret_cast<uword*>(*(fp+1));
+ }
+
+ uword* parent_fp(uword* fp) {
+ ASSERT(fp != NULL);
+ return reinterpret_cast<uword*>(*fp);
+ }
+
+ bool ValidInstructionPointer(uword* pc) {
+ uintptr_t cursor = reinterpret_cast<uintptr_t>(pc);
+ return cursor != 0;
+ }
+
+ bool ValidFramePointer(uword* fp, int i) {
+ uintptr_t cursor = reinterpret_cast<uintptr_t>(fp);
+ cursor += sizeof(fp);
+ bool r = cursor >= stack_lower_ && cursor <= stack_upper_;
+ return r;
+ }
+
+ Sample* sample_;
+ uintptr_t original_fp_;
+ uintptr_t original_pc_;
+ uintptr_t stack_lower_;
+ uintptr_t stack_upper_;
siva 2013/10/28 05:19:21 DISALLOW stuff
Cutch 2013/11/04 20:36:05 Done.
+};
+
+
+bool ProfilerManager::initialized_ = false;
+bool ProfilerManager::shutdown_ = false;
+Monitor* ProfilerManager::monitor_ = NULL;
+Isolate** ProfilerManager::isolates_ = NULL;
+intptr_t ProfilerManager::isolates_capacity_ = 0;
+intptr_t ProfilerManager::isolates_size_ = 0;
+
+
+void ProfilerManager::InitOnce() {
+ if (!FLAG_profile) {
+ return;
+ }
+ NativeSymbolResolver::InitOnce();
+ ASSERT(!initialized_);
+ monitor_ = new Monitor();
+ initialized_ = true;
+ ResizeIsolates(16);
+#if defined(PROFILER_USES_SIGNALS)
+ SignalHandler::Install(ProfileSignalAction);
+#endif
+ Thread::Start(ThreadMain, 0);
+}
+
+
+void ProfilerManager::Shutdown() {
+ if (!FLAG_profile) {
+ return;
+ }
+ ScopedMonitorLock lock(monitor_);
+ shutdown_ = true;
+ for (intptr_t i = 0; i < isolates_size_; i++) {
+ Isolate* isolate = isolates_[i];
siva 2013/10/28 05:19:21 ASSERT(isolate != NULL);
Cutch 2013/11/04 20:36:05 Done.
+ ScopedMutexLock profiler_data_lock(isolate->profiler_data_mutex());
+ FreeIsolateProfilingData(isolate);
+ }
+ isolates_size_ = 0;
+ lock.Notify();
+ NativeSymbolResolver::ShutdownOnce();
+}
+
+
+void ProfilerManager::SetupIsolateForProfiling(Isolate* isolate) {
+ if (!FLAG_profile) {
+ return;
+ }
+ ASSERT(isolate != NULL);
+ ScopedMutexLock profiler_data_lock(isolate->profiler_data_mutex());
+ SampleBuffer* sample_buffer = new SampleBuffer();
+ IsolateProfilerData* profiler_data =
+ new IsolateProfilerData(isolate, sample_buffer);
+ profiler_data->set_sample_interval(1000);
+ isolate->set_profiler_data(profiler_data);
+}
+
+
+void ProfilerManager::FreeIsolateProfilingData(Isolate* isolate) {
siva 2013/10/28 05:19:21 why not move the lock here ScopedMutexLock profile
Cutch 2013/11/04 20:36:05 I've added a TODO. I need to think about this agai
+ IsolateProfilerData* profiler_data = isolate->profiler_data();
+ if (profiler_data == NULL) {
+ // Already freed.
+ return;
+ }
+ isolate->set_profiler_data(NULL);
+ SampleBuffer* sample_buffer = profiler_data->sample_buffer();
+ ASSERT(sample_buffer != NULL);
+ delete sample_buffer;
+ delete profiler_data;
+}
+
+
+void ProfilerManager::ShutdownIsolate(Isolate* isolate) {
+ ASSERT(isolate != NULL);
+ if (!FLAG_profile) {
+ return;
+ }
+ ScopedMutexLock profiler_data_lock(isolate->profiler_data_mutex());
+ FreeIsolateProfilingData(isolate);
+}
+
+
+static void CollectSample(IsolateProfilerData* profiler_data,
+ uintptr_t pc,
+ uintptr_t fp,
+ uintptr_t stack_lower,
+ uintptr_t stack_upper) {
+ SampleBuffer* sample_buffer = profiler_data->sample_buffer();
+ Sample* sample = sample_buffer->ReserveSample();
+ ASSERT(sample != NULL);
+ sample->timestamp = OS::GetCurrentTimeMicros();
+ // TODO(johnmccutchan): Make real use of vm_tags and runtime_tags.
siva 2013/10/28 05:19:21 open an issue and use the issue number in the TODO
Cutch 2013/11/04 20:36:05 Done.
+ sample->vm_tags = Sample::kExecuting;
+ sample->runtime_tags = 0;
+ int64_t cpu_usage;
+ Thread::GetThreadCPUUsage(&cpu_usage);
+ sample->cpu_usage = profiler_data->set_and_delta_cpu_usage(cpu_usage);
+ ProfilerSampleStackWalker stackWalker(sample, stack_lower, stack_upper);
+ stackWalker.walk(pc, fp);
+}
+
+
+#if defined(PROFILER_USES_SIGNALS)
+static void ProfileSignalAction(int signal, siginfo_t* info, void* context_) {
+ if (signal != SIGPROF) {
+ return;
+ }
+ ucontext_t* context = reinterpret_cast<ucontext_t*>(context_);
+ mcontext_t mcontext = context->uc_mcontext;
+ Isolate* isolate = Isolate::Current();
+ if (isolate == NULL) {
+ return;
+ }
+ {
+ ScopedMutexLock profiler_data_lock(isolate->profiler_data_mutex());
+ IsolateProfilerData* profiler_data = isolate->profiler_data();
+ if (profiler_data == NULL) {
+ return;
+ }
+
+ uintptr_t stack_lower = isolate->stack_limit();
+ uintptr_t stack_upper = stack_lower + isolate->GetSpecifiedStackSize();
siva 2013/10/28 05:19:21 There is an implicit assumption here that the stac
Cutch 2013/11/04 20:36:05 Yes. When would this not be true?
+ if (stack_lower == static_cast<uintptr_t>(~0)) {
+ stack_lower = isolate->saved_stack_limit();
+ stack_upper = stack_lower + isolate->GetSpecifiedStackSize();
+ }
+ if (stack_lower == static_cast<uintptr_t>(~0)) {
+ stack_lower = 0;
+ stack_upper = 0;
+ }
siva 2013/10/28 05:19:21 Not sure I understand the logic here.
Cutch 2013/11/04 20:36:05 I'm trying to get the address space bounds for the
+ uintptr_t PC = SignalHandler::GetProgramCounter(mcontext);
+ uintptr_t FP = SignalHandler::GetFramePointer(mcontext);
+ stack_lower = SignalHandler::GetStackPointer(mcontext);
+ int64_t sample_time = OS::GetCurrentTimeMicros();
+ profiler_data->SampledAt(sample_time);
+ CollectSample(profiler_data, PC, FP, stack_lower, stack_upper);
+ }
+ ProfilerManager::ScheduleIsolate(isolate);
+}
+#endif
+
+
+void ProfilerManager::ScheduleIsolate(Isolate* isolate) {
+ if (!FLAG_profile) {
+ return;
+ }
+ ASSERT(initialized_);
+ ASSERT(isolate != NULL);
+ ScopedMonitorLock lock(monitor_);
+ ScopedMutexLock profiler_data_lock(isolate->profiler_data_mutex());
+ IsolateProfilerData* profiler_data = isolate->profiler_data();
+ if (profiler_data == NULL) {
+ return;
+ }
+ profiler_data->Scheduled(OS::GetCurrentTimeMicros(),
+ Thread::GetCurrentThreadId());
+ AddIsolate(isolate);
+ lock.Notify();
+}
+
+
+void ProfilerManager::DescheduleIsolate(Isolate* isolate) {
+ if (!FLAG_profile) {
+ return;
+ }
+ ASSERT(initialized_);
+ ASSERT(isolate != NULL);
+ ScopedMonitorLock lock(monitor_);
+ intptr_t i = FindIsolate(isolate);
+ if (i < 0) {
+ // Not scheduled.
+ return;
+ }
+ {
+ ScopedMutexLock profiler_data_lock(isolate->profiler_data_mutex());
+ IsolateProfilerData* profiler_data = isolate->profiler_data();
+ ASSERT(profiler_data != NULL);
+ profiler_data->Descheduled();
+ }
+ RemoveIsolate(i);
+ lock.Notify();
+}
+
+
+void PrintToJSONStream(Isolate* isolate, JSONStream* stream) {
+ ASSERT(isolate == Isolate::Current());
+ {
+ // We can't get signals here.
+ }
+ UNIMPLEMENTED();
+}
+
+
+void ProfilerManager::ResizeIsolates(intptr_t new_capacity) {
+ ASSERT(new_capacity > isolates_capacity_);
+ Isolate* isolate = NULL;
+ isolates_ = reinterpret_cast<Isolate**>(
+ realloc(isolates_, sizeof(isolate) * new_capacity));
+ isolates_capacity_ = new_capacity;
+}
+
+
+void ProfilerManager::AddIsolate(Isolate* isolate) {
+ if (isolates_size_ == isolates_capacity_) {
+ ResizeIsolates(isolates_capacity_ == 0 ? 16 : isolates_capacity_ * 2);
+ }
+ isolates_[isolates_size_] = isolate;
+ isolates_size_++;
+}
+
+
+intptr_t ProfilerManager::FindIsolate(Isolate* isolate) {
+ for (intptr_t i = 0; i < isolates_size_; i++) {
+ if (isolates_[i] == isolate) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+
+void ProfilerManager::RemoveIsolate(intptr_t i) {
+ ASSERT(i < isolates_size_);
+ intptr_t last = isolates_size_ - 1;
+ if (i != last) {
+ // Swap.
+ Isolate* temp = isolates_[last];
+ isolates_[last] = isolates_[i];
+ isolates_[i] = temp;
siva 2013/10/28 05:19:21 why do they have to be swapped why not just isolat
Cutch 2013/11/04 20:36:05 Done.
+ }
+ // Mark as NULL.
+ isolates_[last] = NULL;
+ // Pop.
+ isolates_size_--;
+}
+
+
+#if defined(PROFILER_USES_SIGNALS)
+int64_t ProfilerManager::SampleAndRescheduleIsolates(int64_t current_time) {
+ if (isolates_size_ == 0) {
+ return 0;
+ }
+ static const int64_t max_time = 0x7fffffffffffffffLL;
+ int64_t lowest = max_time;
+ for (intptr_t i = 0; i < isolates_size_; i++) {
+ Isolate* isolate = isolates_[i];
+ ScopedMutexLock isolate_lock(isolate->profiler_data_mutex());
+ IsolateProfilerData* profiler_data = isolate->profiler_data();
+ ASSERT(profiler_data != NULL);
+ if (profiler_data->ShouldSample(current_time)) {
+ pthread_kill(profiler_data->thread_id(), SIGPROF);
+ RemoveIsolate(i);
+ i--; // Remove moves the last element into i, this decrement cancels
+ // the increment in the for loop.
siva 2013/10/28 05:19:21 This is tricky, why not replace the for loop with
Cutch 2013/11/04 20:36:05 Done.
+ continue;
+ }
+ if (profiler_data->CanExpire()) {
+ int64_t isolate_time_left =
+ profiler_data->TimeUntilExpiration(current_time);
+ if (isolate_time_left < 0) {
+ continue;
+ }
+ if (isolate_time_left < lowest) {
+ lowest = isolate_time_left;
+ }
+ }
+ }
+ if (isolates_size_ == 0) {
+ return 0;
+ }
+ if (lowest == max_time) {
+ return 0;
+ }
+ ASSERT(lowest != max_time);
+ ASSERT(lowest > 0);
+ return lowest;
+}
+#else
+int64_t ProfilerManager::SampleAndRescheduleIsolates(int64_t current_time) {
+ if (isolates_size_ == 0) {
+ return 0;
+ }
+ static const int64_t max_time = 0x7fffffffffffffffLL;
+ int64_t lowest = max_time;
+ // TODO(johnmccutchan): Implement sampling loop on Windows.
+ if (isolates_size_ == 0) {
+ return 0;
+ }
+ if (lowest == max_time) {
+ return 0;
+ }
+ ASSERT(lowest != max_time);
+ ASSERT(lowest > 0);
+ return lowest;
+}
+#endif
siva 2013/10/28 05:19:21 I would prefer if we moved this code to indiviual
Cutch 2013/11/04 20:36:05 Agreed and Done.
+
+
+static const char* FindSymbolName(uintptr_t pc, bool* native_symbol) {
+ // TODO(johnmccutchan): Differentiate between symbols which can't be found
+ // and symbols which were GCed. (Heap::CodeContains).
+ ASSERT(native_symbol != NULL);
+ const char* symbol_name = "Unknown";
+ *native_symbol = false;
+ const Code& code = Code::Handle(Code::LookupCode(pc));
+ if (code.IsNull()) {
+ // Possibly a native symbol.
+ const char* native_name =
+ NativeSymbolResolver::LookupSymbolName(pc);
+ if (native_name != NULL) {
+ symbol_name = native_name;
+ *native_symbol = true;
+ }
+ } else {
+ const Function& function = Function::Handle(code.function());
+ if (!function.IsNull()) {
+ const String& name = String::Handle(function.QualifiedUserVisibleName());
+ if (!name.IsNull()) {
+ symbol_name = name.ToCString();
+ }
+ }
+ }
+ return symbol_name;
+}
+
+
+void ProfilerManager::WriteTracing(Isolate* isolate, const char* name,
+ Dart_Port port) {
+ ASSERT(isolate == Isolate::Current());
+ ScopedMutexLock profiler_data_lock(isolate->profiler_data_mutex());
+ IsolateProfilerData* profiler_data = isolate->profiler_data();
+ if (profiler_data == NULL) {
+ return;
+ }
+ SampleBuffer* sample_buffer = profiler_data->sample_buffer();
+ ASSERT(sample_buffer != NULL);
+ JSONStream stream(10 * MB);
+ intptr_t tid = reinterpret_cast<intptr_t>(sample_buffer);
+ intptr_t pid = 1;
+ {
+ JSONArray events(&stream);
+ {
+ JSONObject thread_name(&events);
+ thread_name.AddProperty("name", "thread_name");
+ thread_name.AddProperty("ph", "M");
+ thread_name.AddProperty("tid", tid);
+ thread_name.AddProperty("pid", pid);
+ {
+ JSONObject args(&thread_name, "args");
+ args.AddProperty("name", name);
+ }
+ }
+ {
+ JSONObject process_name(&events);
+ process_name.AddProperty("name", "process_name");
+ process_name.AddProperty("ph", "M");
+ process_name.AddProperty("tid", tid);
+ process_name.AddProperty("pid", pid);
+ {
+ JSONObject args(&process_name, "args");
+ args.AddProperty("name", "Dart VM");
+ }
+ }
+ uint64_t last_time = 0;
+ for (Sample* i = sample_buffer->FirstSample();
+ i != sample_buffer->LastSample();
+ i = sample_buffer->NextSample(i)) {
+ if (last_time == 0) {
+ last_time = i->timestamp;
+ }
+ intptr_t delta = i->timestamp - last_time;
+ {
+ double percentage = static_cast<double>(i->cpu_usage) /
+ static_cast<double>(delta) * 100.0;
+ if (percentage != percentage) {
+ percentage = 0.0;
+ }
+ percentage = percentage < 0.0 ? 0.0 : percentage;
+ percentage = percentage > 100.0 ? 100.0 : percentage;
+ {
+ JSONObject cpu_usage(&events);
+ cpu_usage.AddProperty("name", "CPU Usage");
+ cpu_usage.AddProperty("ph", "C");
+ cpu_usage.AddProperty("tid", tid);
+ cpu_usage.AddProperty("pid", pid);
+ cpu_usage.AddProperty("ts", static_cast<double>(last_time));
+ {
+ JSONObject args(&cpu_usage, "args");
+ args.AddProperty("CPU", percentage);
+ }
+ }
+ {
+ JSONObject cpu_usage(&events);
+ cpu_usage.AddProperty("name", "CPU Usage");
+ cpu_usage.AddProperty("ph", "C");
+ cpu_usage.AddProperty("tid", tid);
+ cpu_usage.AddProperty("pid", pid);
+ cpu_usage.AddProperty("ts", static_cast<double>(i->timestamp));
+ {
+ JSONObject args(&cpu_usage, "args");
+ args.AddProperty("CPU", percentage);
+ }
+ }
+ }
+ for (int j = 0; j < Sample::kNumStackFrames; j++) {
+ if (i->pcs[j] == 0) {
+ continue;
+ }
+ bool native_symbol = false;
+ const char* symbol_name = FindSymbolName(i->pcs[j], &native_symbol);
+ {
+ JSONObject begin(&events);
+ begin.AddProperty("ph", "B");
+ begin.AddProperty("tid", tid);
+ begin.AddProperty("pid", pid);
+ begin.AddProperty("name", symbol_name);
+ begin.AddProperty("ts", static_cast<double>(last_time));
+ }
+ if (native_symbol) {
+ NativeSymbolResolver::FreeSymbolName(symbol_name);
+ }
+ }
+ for (int j = Sample::kNumStackFrames-1; j >= 0; j--) {
+ if (i->pcs[j] == 0) {
+ continue;
+ }
+ bool native_symbol = false;
+ const char* symbol_name = FindSymbolName(i->pcs[j], &native_symbol);
+ {
+ JSONObject end(&events);
+ end.AddProperty("ph", "E");
+ end.AddProperty("tid", tid);
+ end.AddProperty("pid", pid);
+ end.AddProperty("name", symbol_name);
+ end.AddProperty("ts", static_cast<double>(i->timestamp));
+ }
+ if (native_symbol) {
+ NativeSymbolResolver::FreeSymbolName(symbol_name);
+ }
+ }
+ last_time = i->timestamp;
+ }
+ }
+ char fname[1024];
+ snprintf(fname, sizeof(fname)-1, "/tmp/isolate-%d.prof",
+ static_cast<int>(port));
+ FILE* f = fopen(fname, "wb");
+ ASSERT(f != NULL);
+ fputs(stream.ToCString(), f);
+ fclose(f);
+}
+
+
+void ProfilerManager::ThreadMain(uword parameters) {
+ ASSERT(initialized_);
+ ASSERT(FLAG_profile);
+ ScopedMonitorLock lock(monitor_);
+ while (!shutdown_) {
+ int64_t current_time = OS::GetCurrentTimeMicros();
+ int64_t next_sample = SampleAndRescheduleIsolates(current_time);
+ lock.WaitMicros(next_sample);
+ }
+}
+
+
+IsolateProfilerData::IsolateProfilerData(Isolate* isolate,
+ SampleBuffer* sample_buffer) {
+ isolate_ = isolate;
+ sample_buffer_ = sample_buffer;
+ timer_expiration_micros_ = kNoExpirationTime;
+ last_sampled_micros_ = 0;
+ thread_id_ = 0;
+}
+
+
+IsolateProfilerData::~IsolateProfilerData() {
+}
+
+
+void IsolateProfilerData::SampledAt(int64_t current_time) {
+ last_sampled_micros_ = current_time;
+}
+
+
+void IsolateProfilerData::Scheduled(int64_t current_time, ThreadId thread_id) {
+ timer_expiration_micros_ = current_time + sample_interval_micros_;
+ Thread::GetThreadCPUUsage(&cpu_usage_);
+ thread_id_ = thread_id;
+}
+
+
+void IsolateProfilerData::Descheduled() {
+ cpu_usage_ = kDescheduledCpuUsage;
+ timer_expiration_micros_ = kNoExpirationTime;
+ thread_id_ = 0;
+ Sample* sample = sample_buffer_->ReserveSample();
+ ASSERT(sample != NULL);
+ sample->timestamp = OS::GetCurrentTimeMicros();
+ sample->cpu_usage = 0;
+ sample->vm_tags = Sample::kIdle;
+}
+
+
+const char* Sample::kLookupSymbol = "Symbol Not Looked Up";
+const char* Sample::kNoSymbol = "No Symbol Found";
+
+Sample::Sample() {
+ timestamp = 0;
+ cpu_usage = 0;
+ for (int i = 0; i < kNumStackFrames; i++) {
+ pcs[i] = 0;
+ }
+ vm_tags = kIdle;
+ runtime_tags = 0;
+}
+
+
+SampleBuffer::SampleBuffer(intptr_t capacity) {
+ start_ = 0;
+ end_ = 0;
+ capacity_ = capacity;
+ samples_ = reinterpret_cast<Sample*>(calloc(capacity, sizeof(Sample)));
+}
+
+
+SampleBuffer::~SampleBuffer() {
+ if (samples_ != NULL) {
+ free(samples_);
+ samples_ = NULL;
+ }
+}
+
+
+Sample* SampleBuffer::ReserveSample() {
+ intptr_t index = end_;
+ end_ = WrapIncrement(end_);
+ if (end_ == start_) {
+ start_ = WrapIncrement(start_);
+ }
+ // Reset.
+ samples_[index] = Sample();
+ return &samples_[index];
+}
+
+
+Sample* SampleBuffer::FirstSample() const {
+ return &samples_[start_];
+}
+
+
+Sample* SampleBuffer::NextSample(Sample* sample) const {
+ ASSERT(sample >= &samples_[0]);
+ ASSERT(sample < &samples_[capacity_]);
+ intptr_t index = sample - samples_;
+ index = WrapIncrement(index);
+ return &samples_[index];
+}
+
+
+Sample* SampleBuffer::LastSample() const {
+ return &samples_[end_];
+}
+
+
+intptr_t SampleBuffer::WrapIncrement(intptr_t i) const {
+ return (i + 1) % capacity_;
+}
+
+
+} // namespace dart

Powered by Google App Engine
This is Rietveld 408576698