1 // Copyright 2014 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.
5 #include "chrome/test/chromedriver/performance_logger.h"
10 #include "base/bind.h"
11 #include "base/json/json_writer.h"
12 #include "base/strings/string_util.h"
13 #include "base/values.h"
14 #include "chrome/test/chromedriver/chrome/browser_info.h"
15 #include "chrome/test/chromedriver/chrome/chrome.h"
16 #include "chrome/test/chromedriver/chrome/devtools_client.h"
17 #include "chrome/test/chromedriver/chrome/devtools_client_impl.h"
18 #include "chrome/test/chromedriver/chrome/log.h"
19 #include "chrome/test/chromedriver/chrome/status.h"
20 #include "chrome/test/chromedriver/session.h"
24 // DevTools event domain prefixes to intercept.
25 const char* const kDomains
[] = {"Network.", "Page.", "Timeline."};
27 // Whitelist of WebDriver commands on which to request buffered trace events.
28 const char* const kRequestTraceCommands
[] = {"GetLog" /* required */,
31 bool IsBrowserwideClient(DevToolsClient
* client
) {
32 return (client
->GetId() == DevToolsClientImpl::kBrowserwideDevToolsClientId
);
35 bool IsEnabled(const PerfLoggingPrefs::InspectorDomainStatus
& domain_status
) {
36 return (domain_status
==
37 PerfLoggingPrefs::InspectorDomainStatus::kDefaultEnabled
||
39 PerfLoggingPrefs::InspectorDomainStatus::kExplicitlyEnabled
);
42 // Returns whether |command| is in kRequestTraceCommands[] (case-insensitive).
43 bool ShouldRequestTraceEvents(const std::string
& command
) {
44 for (size_t i_domain
= 0; i_domain
< arraysize(kRequestTraceCommands
);
46 if (base::EqualsCaseInsensitiveASCII(command
,
47 kRequestTraceCommands
[i_domain
]))
53 // Returns whether the event belongs to one of kDomains.
54 bool ShouldLogEvent(const std::string
& method
) {
55 for (size_t i_domain
= 0; i_domain
< arraysize(kDomains
); ++i_domain
) {
56 if (base::StartsWith(method
, kDomains
[i_domain
],
57 base::CompareCase::SENSITIVE
))
65 PerformanceLogger::PerformanceLogger(Log
* log
, const Session
* session
)
68 browser_client_(NULL
),
69 trace_buffering_(false) {}
71 PerformanceLogger::PerformanceLogger(Log
* log
,
72 const Session
* session
,
73 const PerfLoggingPrefs
& prefs
)
77 browser_client_(NULL
),
78 trace_buffering_(false) {}
80 bool PerformanceLogger::subscribes_to_browser() {
84 Status
PerformanceLogger::OnConnected(DevToolsClient
* client
) {
85 if (IsBrowserwideClient(client
)) {
86 browser_client_
= client
;
87 if (!prefs_
.trace_categories
.empty()) {
88 Status status
= StartTrace();
94 return EnableInspectorDomains(client
);
97 Status
PerformanceLogger::OnEvent(
98 DevToolsClient
* client
,
99 const std::string
& method
,
100 const base::DictionaryValue
& params
) {
101 if (IsBrowserwideClient(client
)) {
102 return HandleTraceEvents(client
, method
, params
);
104 return HandleInspectorEvents(client
, method
, params
);
108 Status
PerformanceLogger::BeforeCommand(const std::string
& command_name
) {
109 // Only dump trace buffer after tracing has been started.
110 if (trace_buffering_
&& ShouldRequestTraceEvents(command_name
)) {
111 Status status
= CollectTraceEvents();
112 if (status
.IsError())
118 void PerformanceLogger::AddLogEntry(
120 const std::string
& webview
,
121 const std::string
& method
,
122 const base::DictionaryValue
& params
) {
123 base::DictionaryValue log_message_dict
;
124 log_message_dict
.SetString("webview", webview
);
125 log_message_dict
.SetString("message.method", method
);
126 log_message_dict
.Set("message.params", params
.DeepCopy());
127 std::string log_message_json
;
128 base::JSONWriter::Write(log_message_dict
, &log_message_json
);
130 // TODO(klm): extract timestamp from params?
131 // Look at where it is for Page, Network, Timeline, and trace events.
132 log_
->AddEntry(level
, log_message_json
);
135 void PerformanceLogger::AddLogEntry(
136 const std::string
& webview
,
137 const std::string
& method
,
138 const base::DictionaryValue
& params
) {
139 AddLogEntry(Log::kInfo
, webview
, method
, params
);
142 Status
PerformanceLogger::EnableInspectorDomains(DevToolsClient
* client
) {
143 std::vector
<std::string
> enable_commands
;
144 if (IsEnabled(prefs_
.network
))
145 enable_commands
.push_back("Network.enable");
146 if (IsEnabled(prefs_
.page
))
147 enable_commands
.push_back("Page.enable");
148 if (IsEnabled(prefs_
.timeline
)) {
149 // Timeline feed implicitly disabled when trace categories are specified.
150 // So even if kDefaultEnabled, don't enable unless empty |trace_categories|.
151 if (prefs_
.trace_categories
.empty() || prefs_
.timeline
==
152 PerfLoggingPrefs::InspectorDomainStatus::kExplicitlyEnabled
)
153 enable_commands
.push_back("Timeline.start");
155 for (std::vector
<std::string
>::const_iterator it
= enable_commands
.begin();
156 it
!= enable_commands
.end(); ++it
) {
157 base::DictionaryValue params
; // All the enable commands have empty params.
158 Status status
= client
->SendCommand(*it
, params
);
159 if (status
.IsError())
165 Status
PerformanceLogger::HandleInspectorEvents(
166 DevToolsClient
* client
,
167 const std::string
& method
,
168 const base::DictionaryValue
& params
) {
169 if (!ShouldLogEvent(method
))
172 AddLogEntry(client
->GetId(), method
, params
);
176 Status
PerformanceLogger::HandleTraceEvents(
177 DevToolsClient
* client
,
178 const std::string
& method
,
179 const base::DictionaryValue
& params
) {
180 if (method
== "Tracing.tracingComplete") {
181 trace_buffering_
= false;
182 } else if (method
== "Tracing.dataCollected") {
183 // The Tracing.dataCollected event contains a list of trace events.
184 // Add each one as an individual log entry of method Tracing.dataCollected.
185 const base::ListValue
* traces
;
186 if (!params
.GetList("value", &traces
)) {
187 return Status(kUnknownError
,
188 "received DevTools trace data in unexpected format");
190 for (base::ListValue::const_iterator it
= traces
->begin();
193 base::DictionaryValue
* event_dict
;
194 if (!(*it
)->GetAsDictionary(&event_dict
))
195 return Status(kUnknownError
, "trace event must be a dictionary");
196 AddLogEntry(client
->GetId(), "Tracing.dataCollected", *event_dict
);
198 } else if (method
== "Tracing.bufferUsage") {
199 // 'value' will be between 0-1 and represents how full the DevTools trace
200 // buffer is. If the buffer is full, warn the user.
201 double buffer_usage
= 0;
202 if (!params
.GetDouble("value", &buffer_usage
)) {
203 // Tracing.bufferUsage event will occur once per second, and it really
204 // only serves as a warning, so if we can't reliably tell whether the
205 // buffer is full, just fail silently instead of spamming the logs.
208 if (buffer_usage
>= 0.99999) {
209 base::DictionaryValue params
;
210 std::string
err("Chrome's trace buffer filled while collecting events, "
211 "so some trace events may have been lost");
212 params
.SetString("error", err
);
213 // Expose error to client via perf log using same format as other entries.
214 AddLogEntry(Log::kWarning
,
215 DevToolsClientImpl::kBrowserwideDevToolsClientId
,
216 "Tracing.bufferUsage", params
);
223 Status
PerformanceLogger::StartTrace() {
224 if (!browser_client_
) {
225 return Status(kUnknownError
, "tried to start tracing, but connection to "
226 "browser was not yet established");
228 if (trace_buffering_
) {
229 LOG(WARNING
) << "tried to start tracing, but a trace was already started";
232 base::DictionaryValue params
;
233 params
.SetString("categories", prefs_
.trace_categories
);
234 // Ask DevTools to report buffer usage.
235 params
.SetInteger("bufferUsageReportingInterval",
236 prefs_
.buffer_usage_reporting_interval
);
237 Status status
= browser_client_
->SendCommand("Tracing.start", params
);
238 if (status
.IsError()) {
239 LOG(ERROR
) << "error when starting trace: " << status
.message();
242 trace_buffering_
= true;
246 Status
PerformanceLogger::CollectTraceEvents() {
247 if (!browser_client_
) {
248 return Status(kUnknownError
, "tried to collect trace events, but "
249 "connection to browser was not yet established");
251 if (!trace_buffering_
) {
252 return Status(kUnknownError
, "tried to collect trace events, but tracing "
256 // As of r307466, DevTools no longer returns a response to Tracing.end
257 // commands, so we need to ignore it here to avoid a timeout. See
258 // https://code.google.com/p/chromedriver/issues/detail?id=997 for details.
259 // TODO(samuong): find other commands where we don't need the response.
260 bool wait_for_response
= false;
261 if (session_
->chrome
) {
262 const BrowserInfo
* browser_info
= session_
->chrome
->GetBrowserInfo();
263 if (browser_info
->browser_name
== "chrome" && browser_info
->build_no
< 2245)
264 wait_for_response
= true;
266 base::DictionaryValue params
;
268 if (wait_for_response
)
269 status
= browser_client_
->SendCommand("Tracing.end", params
);
271 status
= browser_client_
->SendAsyncCommand("Tracing.end", params
);
272 if (status
.IsError()) {
273 LOG(ERROR
) << "error when stopping trace: " << status
.message();
277 // Block up to 30 seconds until Tracing.tracingComplete event is received.
278 status
= browser_client_
->HandleEventsUntil(
279 base::Bind(&PerformanceLogger::IsTraceDone
, base::Unretained(this)),
280 base::TimeDelta::FromSeconds(30));
281 if (status
.IsError())
287 Status
PerformanceLogger::IsTraceDone(bool* trace_done
) const {
288 *trace_done
= !trace_buffering_
;