blob: 6f298cf23d105115c6164f41cffb498547dd18d1 [file] [log] [blame]
// Copyright 2015 The Chromium 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 "base/files/file_util.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/trace_event/trace_config.h"
#include "base/trace_event/trace_event.h"
#include "sky/shell/shell.h"
#include "sky/shell/tracing_controller.h"
#include "dart/runtime/include/dart_tools_api.h"
#include <string>
#include <sstream>
namespace sky {
namespace shell {
const char kBaseTraceStart[] = "{\"traceEvents\":[";
const char kBaseTraceEnd[] = "]}";
const char kObservatoryMethodStartTracing[] = "flutter_startTracing";
const char kObservatoryMethodStopTracing[] = "flutter_stopTracing";
const char kObservatoryResultOk[] = "{\"success\" : true}";
const char kObservatoryResultFail[] = "{\"success\" : false}";
static const char* ObservatoryInvoke(const char* method,
const char** param_keys,
const char** param_values,
intptr_t num_params,
void* user_data) {
if (user_data == nullptr) {
// During the desctruction on the tracing controller, the user data is
// cleared. Make sure that observatory requests to service calls are not
// attempted after tracing controller destruction.
return strdup(kObservatoryResultFail);
}
auto tracing_controller = reinterpret_cast<TracingController*>(user_data);
if (strncmp(method, kObservatoryMethodStartTracing,
sizeof(kObservatoryMethodStartTracing)) == 0) {
tracing_controller->StartTracing();
return strdup(kObservatoryResultOk);
}
if (strncmp(method, kObservatoryMethodStopTracing,
sizeof(kObservatoryMethodStopTracing)) == 0) {
// Flushing the trace log requires an active message loop. However,
// observatory callbacks are made on a dart worker thread. We setup a
// message loop manually and tell the flush completion handler to terminate
// the loop when done
base::MessageLoop worker_thread_loop;
base::FilePath temp_dir;
bool temp_access = base::GetTempDir(&temp_dir);
DCHECK(temp_access) << "Must be able to access the temp directory";
base::FilePath path = tracing_controller->TracePathForCurrentTime(temp_dir);
tracing_controller->StopTracing(path, true);
// Run the loop till the flush callback terminates the activation
worker_thread_loop.Run();
base::File file(
path, base::File::Flags::FLAG_OPEN | base::File::Flags::FLAG_READ);
int64 length = file.GetLength();
if (length == 0) {
base::DeleteFile(path, false);
return strdup(kObservatoryResultFail);
}
char* data = reinterpret_cast<char*>(malloc(length));
int length_read = file.Read(0, data, length);
DCHECK(length == length_read);
base::DeleteFile(path, false);
return data;
}
return strdup(kObservatoryResultFail);
}
TracingController::TracingController()
: picture_tracing_enabled_(false),
terminate_loop_on_write_(false),
weak_factory_(this) {
ManageObservatoryCallbacks(true);
}
TracingController::~TracingController() {
ManageObservatoryCallbacks(false);
}
void TracingController::ManageObservatoryCallbacks(bool add) {
void* baton = add ? this : nullptr;
Dart_RegisterRootServiceRequestCallback(kObservatoryMethodStartTracing,
&ObservatoryInvoke, baton);
Dart_RegisterRootServiceRequestCallback(kObservatoryMethodStopTracing,
&ObservatoryInvoke, baton);
}
void TracingController::StartTracing() {
LOG(INFO) << "Starting trace";
StartDartTracing();
StartBaseTracing();
}
void TracingController::StopTracing(const base::FilePath& path,
bool terminate_loop_when_done) {
base::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(&TracingController::StopTracingAsync,
weak_factory_.GetWeakPtr(), path, terminate_loop_when_done));
}
void TracingController::StopTracingAsync(const base::FilePath& path,
bool terminate_loop_when_done) {
if (terminate_loop_on_write_) {
DLOG(INFO) << "Observatory is attempting to capture a trace.";
return;
}
terminate_loop_on_write_ = terminate_loop_when_done;
LOG(INFO) << "Saving trace to " << path.LossyDisplayName();
trace_file_ = std::unique_ptr<base::File>(new base::File(
path, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE));
base::SetPosixFilePermissions(path, base::FILE_PERMISSION_MASK);
StopBaseTracing();
}
void TracingController::StartDartTracing() {
Dart_GlobalTimelineSetRecordedStreams(~0);
}
static void TracingController_DartStreamConsumer(
Dart_StreamConsumer_State state,
const char* stream_name,
uint8_t* buffer,
intptr_t buffer_length,
base::File* traceFile) {
if (state == Dart_StreamConsumer_kData) {
// Trim trailing null characters.
if (buffer[buffer_length - 1] == 0)
--buffer_length;
if (buffer_length) {
if (traceFile != nullptr) {
traceFile->WriteAtCurrentPos(reinterpret_cast<char*>(buffer),
buffer_length);
}
}
}
}
void TracingController::StopDartTracing() {
if (trace_file_){
trace_file_->WriteAtCurrentPos(",", 1);
}
Dart_GlobalTimelineSetRecordedStreams(DART_TIMELINE_STREAM_DISABLE);
auto callback = reinterpret_cast<Dart_StreamConsumer>(
&TracingController_DartStreamConsumer);
Dart_GlobalTimelineGetTrace(callback, trace_file_.get());
FinalizeTraceFile();
}
void TracingController::StartBaseTracing() {
base::trace_event::TraceLog::GetInstance()->SetEnabled(
base::trace_event::TraceConfig("*", base::trace_event::RECORD_UNTIL_FULL),
base::trace_event::TraceLog::RECORDING_MODE);
}
void TracingController::StopBaseTracing() {
base::trace_event::TraceLog* log = base::trace_event::TraceLog::GetInstance();
log->SetDisabled();
if (trace_file_) {
trace_file_->WriteAtCurrentPos(kBaseTraceStart,
sizeof(kBaseTraceStart) - 1);
}
log->Flush(base::Bind(&TracingController::OnBaseTraceChunk,
weak_factory_.GetWeakPtr()));
}
void TracingController::FinalizeTraceFile() {
if (trace_file_) {
trace_file_->WriteAtCurrentPos(kBaseTraceEnd, sizeof(kBaseTraceEnd) - 1);
trace_file_ = nullptr;
}
if (terminate_loop_on_write_) {
base::MessageLoop::current()->Quit();
terminate_loop_on_write_ = false;
}
LOG(INFO) << "Trace complete";
}
void TracingController::OnBaseTraceChunk(
const scoped_refptr<base::RefCountedString>& chunk,
bool has_more_events) {
if (trace_file_) {
std::string& str = chunk->data();
trace_file_->WriteAtCurrentPos(str.data(), str.size());
if (has_more_events)
trace_file_->WriteAtCurrentPos(",", 1);
}
if (!has_more_events) {
StopDartTracing();
}
}
base::FilePath TracingController::TracePathWithExtension(
base::FilePath dir,
std::string extension) const {
base::Time::Exploded exploded;
base::Time now = base::Time::Now();
now.LocalExplode(&exploded);
std::stringstream stream;
// Example: trace_2015-10-08_at_11.38.25.121_.extension
stream << "trace_" << exploded.year << "-" << exploded.month << "-"
<< exploded.day_of_month << "_at_" << exploded.hour << "."
<< exploded.minute << "." << exploded.second << "."
<< exploded.millisecond << "." << extension;
return dir.Append(stream.str());
}
base::FilePath TracingController::PictureTracingPathForCurrentTime() const {
return PictureTracingPathForCurrentTime(traces_base_path_);
}
base::FilePath TracingController::PictureTracingPathForCurrentTime(
base::FilePath dir) const {
return TracePathWithExtension(dir, "skp");
}
base::FilePath TracingController::TracePathForCurrentTime(
base::FilePath dir) const {
return TracePathWithExtension(dir, "json");
}
} // namespace shell
} // namespace sky