1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "content/browser/devtools/devtools_tracing_handler.h"
6
7 #include <cmath>
8
9 #include "base/bind.h"
10 #include "base/debug/trace_event_impl.h"
11 #include "base/strings/string_split.h"
12 #include "base/strings/stringprintf.h"
13 #include "base/time/time.h"
14 #include "base/timer/timer.h"
15 #include "base/values.h"
16 #include "content/browser/devtools/devtools_http_handler_impl.h"
17 #include "content/browser/devtools/devtools_protocol_constants.h"
18 #include "content/public/browser/browser_thread.h"
19 #include "content/public/browser/tracing_controller.h"
20
21 namespace content {
22
23 namespace {
24
25 const char kRecordUntilFull[] = "record-until-full";
26 const char kRecordContinuously[] = "record-continuously";
27 const char kRecordAsMuchAsPossible[] = "record-as-much-as-possible";
28 const char kEnableSampling[] = "enable-sampling";
29
30 class DevToolsTraceSinkProxy : public TracingController::TraceDataSink {
31 public:
DevToolsTraceSinkProxy(base::WeakPtr<DevToolsTracingHandler> handler)32 explicit DevToolsTraceSinkProxy(base::WeakPtr<DevToolsTracingHandler> handler)
33 : tracing_handler_(handler) {}
34
AddTraceChunk(const std::string & chunk)35 virtual void AddTraceChunk(const std::string& chunk) OVERRIDE {
36 if (DevToolsTracingHandler* h = tracing_handler_.get())
37 h->OnTraceDataCollected(chunk);
38 }
Close()39 virtual void Close() OVERRIDE {
40 if (DevToolsTracingHandler* h = tracing_handler_.get())
41 h->OnTraceComplete();
42 }
43
44 private:
~DevToolsTraceSinkProxy()45 virtual ~DevToolsTraceSinkProxy() {}
46
47 base::WeakPtr<DevToolsTracingHandler> tracing_handler_;
48 };
49
50 } // namespace
51
52 const char* DevToolsTracingHandler::kDefaultCategories =
53 "-*,disabled-by-default-devtools.timeline*";
54 const double DevToolsTracingHandler::kDefaultReportingInterval = 1000.0;
55 const double DevToolsTracingHandler::kMinimumReportingInterval = 250.0;
56
DevToolsTracingHandler(DevToolsTracingHandler::Target target)57 DevToolsTracingHandler::DevToolsTracingHandler(
58 DevToolsTracingHandler::Target target)
59 : target_(target), is_recording_(false), weak_factory_(this) {
60 RegisterCommandHandler(devtools::Tracing::start::kName,
61 base::Bind(&DevToolsTracingHandler::OnStart,
62 base::Unretained(this)));
63 RegisterCommandHandler(devtools::Tracing::end::kName,
64 base::Bind(&DevToolsTracingHandler::OnEnd,
65 base::Unretained(this)));
66 RegisterCommandHandler(devtools::Tracing::getCategories::kName,
67 base::Bind(&DevToolsTracingHandler::OnGetCategories,
68 base::Unretained(this)));
69 }
70
~DevToolsTracingHandler()71 DevToolsTracingHandler::~DevToolsTracingHandler() {
72 }
73
OnTraceDataCollected(const std::string & trace_fragment)74 void DevToolsTracingHandler::OnTraceDataCollected(
75 const std::string& trace_fragment) {
76 // Hand-craft protocol notification message so we can substitute JSON
77 // that we already got as string as a bare object, not a quoted string.
78 std::string message =
79 base::StringPrintf("{ \"method\": \"%s\", \"params\": { \"%s\": [",
80 devtools::Tracing::dataCollected::kName,
81 devtools::Tracing::dataCollected::kParamValue);
82 const size_t messageSuffixSize = 10;
83 message.reserve(message.size() + trace_fragment.size() + messageSuffixSize);
84 message += trace_fragment;
85 message += "] } }", SendRawMessage(message);
86 }
87
OnTraceComplete()88 void DevToolsTracingHandler::OnTraceComplete() {
89 SendNotification(devtools::Tracing::tracingComplete::kName, NULL);
90 }
91
TraceOptionsFromString(const std::string & options)92 base::debug::TraceOptions DevToolsTracingHandler::TraceOptionsFromString(
93 const std::string& options) {
94 std::vector<std::string> split;
95 std::vector<std::string>::iterator iter;
96 base::debug::TraceOptions ret;
97
98 base::SplitString(options, ',', &split);
99 for (iter = split.begin(); iter != split.end(); ++iter) {
100 if (*iter == kRecordUntilFull) {
101 ret.record_mode = base::debug::RECORD_UNTIL_FULL;
102 } else if (*iter == kRecordContinuously) {
103 ret.record_mode = base::debug::RECORD_CONTINUOUSLY;
104 } else if (*iter == kRecordAsMuchAsPossible) {
105 ret.record_mode = base::debug::RECORD_AS_MUCH_AS_POSSIBLE;
106 } else if (*iter == kEnableSampling) {
107 ret.enable_sampling = true;
108 }
109 }
110 return ret;
111 }
112
113 scoped_refptr<DevToolsProtocol::Response>
OnStart(scoped_refptr<DevToolsProtocol::Command> command)114 DevToolsTracingHandler::OnStart(
115 scoped_refptr<DevToolsProtocol::Command> command) {
116 if (is_recording_) {
117 return command->InternalErrorResponse("Tracing is already started");
118 }
119 is_recording_ = true;
120
121 std::string categories;
122 base::debug::TraceOptions options;
123 double usage_reporting_interval = 0.0;
124
125 base::DictionaryValue* params = command->params();
126 if (params) {
127 params->GetString(devtools::Tracing::start::kParamCategories, &categories);
128 std::string options_param;
129 if (params->GetString(devtools::Tracing::start::kParamOptions,
130 &options_param)) {
131 options = TraceOptionsFromString(options_param);
132 }
133 params->GetDouble(
134 devtools::Tracing::start::kParamBufferUsageReportingInterval,
135 &usage_reporting_interval);
136 }
137
138 SetupTimer(usage_reporting_interval);
139
140 // If inspected target is a render process Tracing.start will be handled by
141 // tracing agent in the renderer.
142 if (target_ == Renderer) {
143 TracingController::GetInstance()->EnableRecording(
144 base::debug::CategoryFilter(categories),
145 options,
146 TracingController::EnableRecordingDoneCallback());
147 return NULL;
148 }
149
150 TracingController::GetInstance()->EnableRecording(
151 base::debug::CategoryFilter(categories),
152 options,
153 base::Bind(&DevToolsTracingHandler::OnRecordingEnabled,
154 weak_factory_.GetWeakPtr(),
155 command));
156 return command->AsyncResponsePromise();
157 }
158
SetupTimer(double usage_reporting_interval)159 void DevToolsTracingHandler::SetupTimer(double usage_reporting_interval) {
160 if (usage_reporting_interval == 0) return;
161
162 if (usage_reporting_interval < kMinimumReportingInterval)
163 usage_reporting_interval = kMinimumReportingInterval;
164
165 base::TimeDelta interval = base::TimeDelta::FromMilliseconds(
166 std::ceil(usage_reporting_interval));
167 buffer_usage_poll_timer_.reset(new base::Timer(
168 FROM_HERE,
169 interval,
170 base::Bind(
171 base::IgnoreResult(&TracingController::GetTraceBufferPercentFull),
172 base::Unretained(TracingController::GetInstance()),
173 base::Bind(&DevToolsTracingHandler::OnBufferUsage,
174 weak_factory_.GetWeakPtr())),
175 true));
176 buffer_usage_poll_timer_->Reset();
177 }
178
OnRecordingEnabled(scoped_refptr<DevToolsProtocol::Command> command)179 void DevToolsTracingHandler::OnRecordingEnabled(
180 scoped_refptr<DevToolsProtocol::Command> command) {
181 SendAsyncResponse(command->SuccessResponse(NULL));
182 }
183
OnBufferUsage(float usage)184 void DevToolsTracingHandler::OnBufferUsage(float usage) {
185 base::DictionaryValue* params = new base::DictionaryValue();
186 params->SetDouble(devtools::Tracing::bufferUsage::kParamValue, usage);
187 SendNotification(devtools::Tracing::bufferUsage::kName, params);
188 }
189
190 scoped_refptr<DevToolsProtocol::Response>
OnEnd(scoped_refptr<DevToolsProtocol::Command> command)191 DevToolsTracingHandler::OnEnd(
192 scoped_refptr<DevToolsProtocol::Command> command) {
193 if (!is_recording_) {
194 return command->InternalErrorResponse("Tracing is not started");
195 }
196 DisableRecording(false);
197 // If inspected target is a render process Tracing.end will be handled by
198 // tracing agent in the renderer.
199 if (target_ == Renderer)
200 return NULL;
201 return command->SuccessResponse(NULL);
202 }
203
DisableRecording(bool abort)204 void DevToolsTracingHandler::DisableRecording(bool abort) {
205 is_recording_ = false;
206 buffer_usage_poll_timer_.reset();
207 TracingController::GetInstance()->DisableRecording(
208 abort ? NULL : new DevToolsTraceSinkProxy(weak_factory_.GetWeakPtr()));
209 }
210
OnClientDetached()211 void DevToolsTracingHandler::OnClientDetached() {
212 if (is_recording_)
213 DisableRecording(true);
214 }
215
216 scoped_refptr<DevToolsProtocol::Response>
OnGetCategories(scoped_refptr<DevToolsProtocol::Command> command)217 DevToolsTracingHandler::OnGetCategories(
218 scoped_refptr<DevToolsProtocol::Command> command) {
219 TracingController::GetInstance()->GetCategories(
220 base::Bind(&DevToolsTracingHandler::OnCategoriesReceived,
221 weak_factory_.GetWeakPtr(),
222 command));
223 return command->AsyncResponsePromise();
224 }
225
OnCategoriesReceived(scoped_refptr<DevToolsProtocol::Command> command,const std::set<std::string> & category_set)226 void DevToolsTracingHandler::OnCategoriesReceived(
227 scoped_refptr<DevToolsProtocol::Command> command,
228 const std::set<std::string>& category_set) {
229 base::DictionaryValue* response = new base::DictionaryValue;
230 base::ListValue* category_list = new base::ListValue;
231 for (std::set<std::string>::const_iterator it = category_set.begin();
232 it != category_set.end(); ++it) {
233 category_list->AppendString(*it);
234 }
235
236 response->Set(devtools::Tracing::getCategories::kResponseCategories,
237 category_list);
238 SendAsyncResponse(command->SuccessResponse(response));
239 }
240
241 } // namespace content
242