| Index: src/v8-tracing-controller.cc
|
| diff --git a/src/v8-tracing-controller.cc b/src/v8-tracing-controller.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..a3a0c30e09e99fbbf5e76bd61732a58b810d3686
|
| --- /dev/null
|
| +++ b/src/v8-tracing-controller.cc
|
| @@ -0,0 +1,505 @@
|
| +// Copyright 2016 the V8 project authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include "include/v8-tracing-controller.h"
|
| +#include "src/base/platform/platform.h"
|
| +#include "src/log.h"
|
| +#include "src/version.h"
|
| +#include <iostream>
|
| +
|
| +namespace v8 {
|
| +
|
| +// Functions and data for performing output of log messages.
|
| +class TracingLogger {
|
| + public:
|
| + // Performs process-wide initialization.
|
| + void Initialize(const char* log_file_name);
|
| +
|
| + // Disables logging, but preserves acquired resources.
|
| + void stop() { is_stopped_ = true; }
|
| +
|
| + // Frees all resources acquired in Initialize and Open... functions.
|
| + // When a temporary file is used for the log, returns its stream descriptor,
|
| + // leaving the file open.
|
| + FILE* Close();
|
| +
|
| + // Returns whether logging is enabled.
|
| + bool IsEnabled() {
|
| + return !is_stopped_ && output_handle_ != NULL;
|
| + }
|
| +
|
| + // Size of buffer used for formatting log messages.
|
| + static const int kMessageBufferSize = 8192;
|
| +
|
| + void SharedLibraryEvent(const std::string& library_path,
|
| + uintptr_t start,
|
| + uintptr_t end);
|
| + void BeginEvent(const char* event_name);
|
| + void EndEvent(const char* event_name);
|
| +
|
| + // Utility class for formatting log messages. It fills the message into the
|
| + // static buffer in TracingLogger.
|
| + class MessageBuilder {
|
| + public:
|
| + // Create a message builder starting from position 0.
|
| + // This acquires the mutex in the log as well.
|
| + explicit MessageBuilder(TracingLogger* logger);
|
| + ~MessageBuilder() { }
|
| +
|
| + // Append string data to the log message.
|
| + void Append(const char* format, ...);
|
| +
|
| + // Append string data to the log message.
|
| + void AppendVA(const char* format, va_list args);
|
| +
|
| + // Append a character to the log message.
|
| + void Append(const char c);
|
| +
|
| + // Append double quoted string to the log message.
|
| + void AppendDoubleQuotedString(const char* string);
|
| +
|
| + // Append a portion of a string.
|
| + void AppendStringPart(const char* str, int len);
|
| +
|
| + // Write the log message to the log file currently opened.
|
| + void WriteToLogFile();
|
| +
|
| + private:
|
| + TracingLogger* logger_;
|
| + base::LockGuard<base::Mutex> lock_guard_;
|
| + int pos_;
|
| + };
|
| +
|
| + private:
|
| + friend class TracingController;
|
| +
|
| + explicit TracingLogger();
|
| + // Opens file for logging.
|
| + void OpenFile(const char* name);
|
| +
|
| + // Implementation of writing to a log file.
|
| + int WriteToFile(const char* msg, int length) {
|
| + DCHECK(output_handle_ != NULL);
|
| + size_t rv = fwrite(msg, 1, length, output_handle_);
|
| + DCHECK(static_cast<size_t>(length) == rv);
|
| + USE(rv);
|
| + fflush(output_handle_);
|
| + return length;
|
| + }
|
| +
|
| + // Whether logging is stopped (e.g. due to insufficient resources).
|
| + bool is_stopped_;
|
| +
|
| + // When logging is active output_handle_ is used to store a pointer to log
|
| + // destination. mutex_ should be acquired before using output_handle_.
|
| + FILE* output_handle_;
|
| +
|
| + // mutex_ is a Mutex used for enforcing exclusive
|
| + // access to the formatting buffer and the log file or log memory buffer.
|
| + base::Mutex mutex_;
|
| +
|
| + // Buffer used for formatting log messages. This is a singleton buffer and
|
| + // mutex_ should be acquired before using it.
|
| + char* message_buffer_;
|
| +};
|
| +
|
| +TracingLogger::TracingLogger()
|
| + : is_stopped_(false),
|
| + output_handle_(NULL),
|
| + message_buffer_(NULL) {}
|
| +
|
| +void TracingLogger::Initialize(const char* log_file_name) {
|
| + message_buffer_ = i::NewArray<char>(kMessageBufferSize);
|
| + // If we're logging anything, we need to open the log file.
|
| + OpenFile(log_file_name);
|
| + if (output_handle_ != nullptr) {
|
| + TracingLogger::MessageBuilder msg(this);
|
| + msg.Append("v8-version,%d,%d,%d,%d,%d", i::Version::GetMajor(),
|
| + i::Version::GetMinor(), i::Version::GetBuild(),
|
| + i::Version::GetPatch(), i::Version::IsCandidate());
|
| + msg.WriteToLogFile();
|
| + }
|
| +}
|
| +
|
| +void TracingLogger::OpenFile(const char* name) {
|
| + DCHECK(!IsEnabled());
|
| + output_handle_ = base::OS::FOpen(name, base::OS::LogFileOpenMode);
|
| +}
|
| +
|
| +FILE* TracingLogger::Close() {
|
| + FILE* result = NULL;
|
| + if (output_handle_ != NULL) {
|
| + fclose(output_handle_);
|
| + }
|
| + output_handle_ = NULL;
|
| + i::DeleteArray(message_buffer_);
|
| + message_buffer_ = NULL;
|
| + is_stopped_ = false;
|
| + return result;
|
| +}
|
| +
|
| +void TracingLogger::SharedLibraryEvent(const std::string& library_path,
|
| + uintptr_t start,
|
| + uintptr_t end) {
|
| + TracingLogger::MessageBuilder msg(this);
|
| + msg.Append("shared-library,\"%s\",0x%08" V8PRIxPTR ",0x%08" V8PRIxPTR,
|
| + library_path.c_str(), start, end);
|
| + msg.WriteToLogFile();
|
| +}
|
| +
|
| +void TracingLogger::BeginEvent(const char* event_name) {
|
| + TracingLogger::MessageBuilder msg(this);
|
| + msg.Append("%s,\"begin\"", event_name);
|
| + msg.WriteToLogFile();
|
| +}
|
| +
|
| +void TracingLogger::EndEvent(const char* event_name) {
|
| + TracingLogger::MessageBuilder msg(this);
|
| + msg.Append("%s,\"end\"", event_name);
|
| + msg.WriteToLogFile();
|
| +}
|
| +
|
| +
|
| +TracingLogger::MessageBuilder::MessageBuilder(TracingLogger* logger)
|
| + : logger_(logger),
|
| + lock_guard_(&logger_->mutex_),
|
| + pos_(0) {
|
| + DCHECK(logger_->message_buffer_ != NULL);
|
| +}
|
| +
|
| +void TracingLogger::MessageBuilder::Append(const char* format, ...) {
|
| + i::Vector<char> buf(logger_->message_buffer_ + pos_,
|
| + TracingLogger::kMessageBufferSize - pos_);
|
| + va_list args;
|
| + va_start(args, format);
|
| + AppendVA(format, args);
|
| + va_end(args);
|
| + DCHECK(pos_ <= TracingLogger::kMessageBufferSize);
|
| +}
|
| +
|
| +void TracingLogger::MessageBuilder::AppendVA(const char* format, va_list args) {
|
| + i::Vector<char> buf(logger_->message_buffer_ + pos_,
|
| + TracingLogger::kMessageBufferSize - pos_);
|
| + int result = v8::internal::VSNPrintF(buf, format, args);
|
| +
|
| + // Result is -1 if output was truncated.
|
| + if (result >= 0) {
|
| + pos_ += result;
|
| + } else {
|
| + pos_ = TracingLogger::kMessageBufferSize;
|
| + }
|
| + DCHECK(pos_ <= TracingLogger::kMessageBufferSize);
|
| +}
|
| +
|
| +void TracingLogger::MessageBuilder::Append(const char c) {
|
| + if (pos_ < TracingLogger::kMessageBufferSize) {
|
| + logger_->message_buffer_[pos_++] = c;
|
| + }
|
| + DCHECK(pos_ <= TracingLogger::kMessageBufferSize);
|
| +}
|
| +
|
| +void TracingLogger::MessageBuilder::AppendDoubleQuotedString(const char* string) {
|
| + Append('"');
|
| + for (const char* p = string; *p != '\0'; p++) {
|
| + if (*p == '"') {
|
| + Append('\\');
|
| + }
|
| + Append(*p);
|
| + }
|
| + Append('"');
|
| +}
|
| +
|
| +void TracingLogger::MessageBuilder::AppendStringPart(const char* str, int len) {
|
| + if (pos_ + len > TracingLogger::kMessageBufferSize) {
|
| + len = TracingLogger::kMessageBufferSize - pos_;
|
| + DCHECK(len >= 0);
|
| + if (len == 0) return;
|
| + }
|
| + i::Vector<char> buf(logger_->message_buffer_ + pos_,
|
| + TracingLogger::kMessageBufferSize - pos_);
|
| + StrNCpy(buf, str, len);
|
| + pos_ += len;
|
| + DCHECK(pos_ <= TracingLogger::kMessageBufferSize);
|
| +}
|
| +
|
| +void TracingLogger::MessageBuilder::WriteToLogFile() {
|
| + DCHECK(pos_ <= TracingLogger::kMessageBufferSize);
|
| + // Assert that we do not already have a new line at the end.
|
| + DCHECK(pos_ == 0 || logger_->message_buffer_[pos_ - 1] != '\n');
|
| + if (pos_ == TracingLogger::kMessageBufferSize) pos_--;
|
| + logger_->message_buffer_[pos_++] = '\n';
|
| + const int written = logger_->WriteToFile(logger_->message_buffer_, pos_);
|
| + if (written != pos_) {
|
| + logger_->stop();
|
| + }
|
| +}
|
| +
|
| +
|
| +// The Profiler samples pc and sp values for the main thread.
|
| +// Each sample is appended to a circular buffer.
|
| +// An independent thread removes data and writes it to the log.
|
| +// This design minimizes the time spent in the sampler.
|
| +//
|
| +class Profiler: public base::Thread {
|
| + public:
|
| + explicit Profiler();
|
| + void Engage();
|
| + void Disengage();
|
| +
|
| + // Inserts collected profiling data into buffer.
|
| + void Insert(i::TickSample* sample) {
|
| + if (paused_)
|
| + return;
|
| +
|
| + if (Succ(head_) == static_cast<int>(base::NoBarrier_Load(&tail_))) {
|
| + overflow_ = true;
|
| + } else {
|
| + buffer_[head_] = *sample;
|
| + head_ = Succ(head_);
|
| + buffer_semaphore_.Signal(); // Tell we have an element.
|
| + }
|
| + }
|
| +
|
| + virtual void Run();
|
| +
|
| + // Pause and Resume TickSample data collection.
|
| + void pause() { paused_ = true; }
|
| + void resume() { paused_ = false; }
|
| +
|
| + private:
|
| + // Waits for a signal and removes profiling data.
|
| + bool Remove(i::TickSample* sample) {
|
| + buffer_semaphore_.Wait(); // Wait for an element.
|
| + *sample = buffer_[base::NoBarrier_Load(&tail_)];
|
| + bool result = overflow_;
|
| + base::NoBarrier_Store(&tail_, static_cast<base::Atomic32>(
|
| + Succ(base::NoBarrier_Load(&tail_))));
|
| + overflow_ = false;
|
| + return result;
|
| + }
|
| +
|
| + // Returns the next index in the cyclic buffer.
|
| + int Succ(int index) { return (index + 1) % kBufferSize; }
|
| +
|
| + // Cyclic buffer for communicating profiling samples
|
| + // between the signal handler and the worker thread.
|
| + static const int kBufferSize = 128;
|
| + i::TickSample buffer_[kBufferSize]; // Buffer storage.
|
| + int head_; // Index to the buffer head.
|
| + base::Atomic32 tail_; // Index to the buffer tail.
|
| + bool overflow_; // Tell whether a buffer overflow has occurred.
|
| + // Sempahore used for buffer synchronization.
|
| + base::Semaphore buffer_semaphore_;
|
| +
|
| + // Tells whether profiler is engaged, that is, processing thread is stated.
|
| + bool engaged_;
|
| +
|
| + // Tells whether worker thread should continue running.
|
| + base::Atomic32 running_;
|
| +
|
| + // Tells whether we are currently recording tick samples.
|
| + bool paused_;
|
| +};
|
| +
|
| +//
|
| +// Ticker used to provide samples to the tracing controller.
|
| +//
|
| +class Ticker: public V8Sampler {
|
| + public:
|
| + Ticker(Isolate* isolate):
|
| + V8Sampler(isolate),
|
| + profiler_(NULL) {}
|
| +
|
| + ~Ticker() { if (IsActive()) Stop(); }
|
| +
|
| + void SetProfiler(Profiler* profiler) {
|
| + DCHECK(profiler_ == NULL);
|
| + profiler_ = profiler;
|
| + IncreaseProfilingDepth();
|
| + if (!IsActive())
|
| + Start();
|
| + }
|
| +
|
| + void ClearProfiler() {
|
| + profiler_ = NULL;
|
| + if (IsActive()) Stop();
|
| + DecreaseProfilingDepth();
|
| + }
|
| +
|
| + void Tick(i::TickSample* sample) override {
|
| + if (profiler_) profiler_->Insert(sample);
|
| + }
|
| +
|
| + private:
|
| + Profiler* profiler_;
|
| +};
|
| +
|
| +
|
| +//
|
| +// Profiler implementation.
|
| +//
|
| +Profiler::Profiler()
|
| + : base::Thread(Options("v8:Profiler")),
|
| + head_(0),
|
| + overflow_(false),
|
| + buffer_semaphore_(0),
|
| + engaged_(false),
|
| + paused_(false) {
|
| + base::NoBarrier_Store(&tail_, 0);
|
| + base::NoBarrier_Store(&running_, 0);
|
| +}
|
| +
|
| +void Profiler::Engage() {
|
| + if (engaged_) return;
|
| + engaged_ = true;
|
| +
|
| + std::vector<base::OS::SharedLibraryAddress> addresses =
|
| + base::OS::GetSharedLibraryAddresses();
|
| + for (size_t i = 0; i < addresses.size(); ++i) {
|
| + TracingController::tracing_controller_->logger_->SharedLibraryEvent(
|
| + addresses[i].library_path, addresses[i].start, addresses[i].end);
|
| + }
|
| +
|
| + // Start thread processing the profiler buffer.
|
| + base::NoBarrier_Store(&running_, 1);
|
| + Start();
|
| +
|
| + TracingLogger::MessageBuilder msg(
|
| + TracingController::tracing_controller_->logger_);
|
| +}
|
| +
|
| +void Profiler::Disengage() {
|
| + if (!engaged_) return;
|
| +
|
| + // Stop receiving ticks.
|
| + TracingController::tracing_controller_->ticker_->ClearProfiler();
|
| +
|
| + // Terminate the worker thread by setting running_ to false,
|
| + // inserting a fake element in the queue and then wait for
|
| + // the thread to terminate.
|
| + base::NoBarrier_Store(&running_, 0);
|
| + i::TickSample sample;
|
| + // Reset 'paused_' flag, otherwise semaphore may not be signalled.
|
| + resume();
|
| + Insert(&sample);
|
| + Join();
|
| +
|
| + TracingLogger::MessageBuilder msg(TracingController::tracing_controller_->logger_);
|
| +}
|
| +
|
| +void Profiler::Run() {
|
| + i::TickSample sample;
|
| + bool overflow = Remove(&sample);
|
| + while (base::NoBarrier_Load(&running_)) {
|
| + TracingController::tracing_controller_->LogSample(&sample, overflow);
|
| + overflow = Remove(&sample);
|
| + }
|
| +}
|
| +
|
| +
|
| +base::Mutex* TracingController::mutex_ = NULL;
|
| +TracingController* TracingController::tracing_controller_ = NULL;
|
| +
|
| +void TracingController::StartTracing(Isolate* isolate) {
|
| + base::LockGuard<base::Mutex> lock_guard(mutex_);
|
| + if (TracingController::tracing_controller_ == NULL) {
|
| + TracingController::tracing_controller_ = new TracingController(isolate);
|
| + }
|
| + TracingController::tracing_controller_->Start();
|
| +}
|
| +
|
| +void TracingController::StopTracing() {
|
| + base::LockGuard<base::Mutex> lock_guard(mutex_);
|
| + if (TracingController::tracing_controller_ != NULL) {
|
| + TracingController::tracing_controller_->Stop();
|
| + }
|
| +}
|
| +
|
| +void TracingController::InstallJitCodeEventHandler(Isolate* isolate, void* data) {
|
| + TracingController::tracing_controller_->ticker_->SetJitCodeEventHandler(
|
| + v8::JitCodeEventOptions::kJitCodeEventEnumExisting, data);
|
| +}
|
| +
|
| +void TracingController::HandleJitCodeEvent(const JitCodeEvent* event) {
|
| +}
|
| +
|
| +void TracingController::SetInterval(int interval) {
|
| + V8Sampler::SetInterval(interval);
|
| +}
|
| +
|
| +TracingController::TracingController(Isolate* isolate)
|
| + : is_tracing_(false),
|
| + profiler_(NULL),
|
| + ticker_(NULL),
|
| + logger_(new TracingLogger()) {
|
| + isolate_ = isolate;
|
| +}
|
| +
|
| +void TracingController::Start() {
|
| + if (is_tracing_)
|
| + return;
|
| + is_tracing_ = true;
|
| + logger_->Initialize("d8.log");
|
| + timer_.Start();
|
| + profiler_ = new Profiler();
|
| + profiler_->Engage();
|
| + ticker_ = new Ticker(isolate_);
|
| + v8::JitCodeEventHandler handler = &HandleJitCodeEvent;
|
| + isolate_->RequestInterrupt(&InstallJitCodeEventHandler,
|
| + reinterpret_cast<void*>(handler));
|
| + ticker_->SetProfiler(profiler_);
|
| +}
|
| +
|
| +void TracingController::Stop() {
|
| + if (!is_tracing_)
|
| + return;
|
| + is_tracing_ = false;
|
| + // Stop the profiler before closing the file.
|
| + if (profiler_ != NULL) {
|
| + profiler_->Disengage();
|
| + delete profiler_;
|
| + profiler_ = NULL;
|
| + }
|
| +
|
| + ticker_->ClearProfiler();
|
| + delete ticker_;
|
| + ticker_ = NULL;
|
| +
|
| + logger_->Close();
|
| + logger_ = NULL;
|
| +
|
| + delete tracing_controller_;
|
| + tracing_controller_ = NULL;
|
| +}
|
| +
|
| +void TracingController::LogSample(i::TickSample* sample, bool overflow) {
|
| + /*
|
| + TracingLogger::MessageBuilder msg(logger_);
|
| + msg.Append("%s,", "tick");
|
| + msg.Append("0x%x", sample->pc);
|
| + msg.Append(",%ld", static_cast<int>(timer_.Elapsed().InMicroseconds()));
|
| + if (sample->has_external_callback) {
|
| + msg.Append(",1,");
|
| +#if USES_FUNCTION_DESCRIPTORS
|
| + msg.Append("0x%x", *FUNCTION_ENTRYPOINT_ADDRESS(sample->external_callback));
|
| +#else
|
| + msg.Append("0x%x", sample->external_callback);
|
| +#endif
|
| + } else {
|
| + msg.Append(",0,");
|
| + msg.Append("0x%x", sample->tos);
|
| + }
|
| + msg.Append(",%d", static_cast<int>(sample->state));
|
| + if (overflow) {
|
| + msg.Append(",overflow");
|
| + }
|
| + for (unsigned i = 0; i < sample->frames_count; ++i) {
|
| + msg.Append(',');
|
| + msg.Append("0x%x", sample->stack[i]);
|
| + }
|
| + msg.WriteToLogFile();
|
| + */
|
| +}
|
| +
|
| +}
|
|
|