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::strcasecmp(command
.c_str(), kRequestTraceCommands
[i_domain
]) == 0)
52 // Returns whether the event belongs to one of kDomains.
53 bool ShouldLogEvent(const std::string
& method
) {
54 for (size_t i_domain
= 0; i_domain
< arraysize(kDomains
); ++i_domain
) {
55 if (base::StartsWithASCII(method
, kDomains
[i_domain
],
56 true /* case_sensitive */))
64 PerformanceLogger::PerformanceLogger(Log
* log
, const Session
* session
)
67 browser_client_(NULL
),
68 trace_buffering_(false) {}
70 PerformanceLogger::PerformanceLogger(Log
* log
,
71 const Session
* session
,
72 const PerfLoggingPrefs
& prefs
)
76 browser_client_(NULL
),
77 trace_buffering_(false) {}
79 bool PerformanceLogger::subscribes_to_browser() {
83 Status
PerformanceLogger::OnConnected(DevToolsClient
* client
) {
84 if (IsBrowserwideClient(client
)) {
85 browser_client_
= client
;
86 if (!prefs_
.trace_categories
.empty()) {
87 Status status
= StartTrace();
93 return EnableInspectorDomains(client
);
96 Status
PerformanceLogger::OnEvent(
97 DevToolsClient
* client
,
98 const std::string
& method
,
99 const base::DictionaryValue
& params
) {
100 if (IsBrowserwideClient(client
)) {
101 return HandleTraceEvents(client
, method
, params
);
103 return HandleInspectorEvents(client
, method
, params
);
107 Status
PerformanceLogger::BeforeCommand(const std::string
& command_name
) {
108 // Only dump trace buffer after tracing has been started.
109 if (trace_buffering_
&& ShouldRequestTraceEvents(command_name
)) {
110 Status status
= CollectTraceEvents();
111 if (status
.IsError())
117 void PerformanceLogger::AddLogEntry(
119 const std::string
& webview
,
120 const std::string
& method
,
121 const base::DictionaryValue
& params
) {
122 base::DictionaryValue log_message_dict
;
123 log_message_dict
.SetString("webview", webview
);
124 log_message_dict
.SetString("message.method", method
);
125 log_message_dict
.Set("message.params", params
.DeepCopy());
126 std::string log_message_json
;
127 base::JSONWriter::Write(log_message_dict
, &log_message_json
);
129 // TODO(klm): extract timestamp from params?
130 // Look at where it is for Page, Network, Timeline, and trace events.
131 log_
->AddEntry(level
, log_message_json
);
134 void PerformanceLogger::AddLogEntry(
135 const std::string
& webview
,
136 const std::string
& method
,
137 const base::DictionaryValue
& params
) {
138 AddLogEntry(Log::kInfo
, webview
, method
, params
);
141 Status
PerformanceLogger::EnableInspectorDomains(DevToolsClient
* client
) {
142 std::vector
<std::string
> enable_commands
;
143 if (IsEnabled(prefs_
.network
))
144 enable_commands
.push_back("Network.enable");
145 if (IsEnabled(prefs_
.page
))
146 enable_commands
.push_back("Page.enable");
147 if (IsEnabled(prefs_
.timeline
)) {
148 // Timeline feed implicitly disabled when trace categories are specified.
149 // So even if kDefaultEnabled, don't enable unless empty |trace_categories|.
150 if (prefs_
.trace_categories
.empty() || prefs_
.timeline
==
151 PerfLoggingPrefs::InspectorDomainStatus::kExplicitlyEnabled
)
152 enable_commands
.push_back("Timeline.start");
154 for (std::vector
<std::string
>::const_iterator it
= enable_commands
.begin();
155 it
!= enable_commands
.end(); ++it
) {
156 base::DictionaryValue params
; // All the enable commands have empty params.
157 Status status
= client
->SendCommand(*it
, params
);
158 if (status
.IsError())
164 Status
PerformanceLogger::HandleInspectorEvents(
165 DevToolsClient
* client
,
166 const std::string
& method
,
167 const base::DictionaryValue
& params
) {
168 if (!ShouldLogEvent(method
))
171 AddLogEntry(client
->GetId(), method
, params
);
175 Status
PerformanceLogger::HandleTraceEvents(
176 DevToolsClient
* client
,
177 const std::string
& method
,
178 const base::DictionaryValue
& params
) {
179 if (method
== "Tracing.tracingComplete") {
180 trace_buffering_
= false;
181 } else if (method
== "Tracing.dataCollected") {
182 // The Tracing.dataCollected event contains a list of trace events.
183 // Add each one as an individual log entry of method Tracing.dataCollected.
184 const base::ListValue
* traces
;
185 if (!params
.GetList("value", &traces
)) {
186 return Status(kUnknownError
,
187 "received DevTools trace data in unexpected format");
189 for (base::ListValue::const_iterator it
= traces
->begin();
192 base::DictionaryValue
* event_dict
;
193 if (!(*it
)->GetAsDictionary(&event_dict
))
194 return Status(kUnknownError
, "trace event must be a dictionary");
195 AddLogEntry(client
->GetId(), "Tracing.dataCollected", *event_dict
);
197 } else if (method
== "Tracing.bufferUsage") {
198 // 'value' will be between 0-1 and represents how full the DevTools trace
199 // buffer is. If the buffer is full, warn the user.
200 double buffer_usage
= 0;
201 if (!params
.GetDouble("value", &buffer_usage
)) {
202 // Tracing.bufferUsage event will occur once per second, and it really
203 // only serves as a warning, so if we can't reliably tell whether the
204 // buffer is full, just fail silently instead of spamming the logs.
207 if (buffer_usage
>= 0.99999) {
208 base::DictionaryValue params
;
209 std::string
err("Chrome's trace buffer filled while collecting events, "
210 "so some trace events may have been lost");
211 params
.SetString("error", err
);
212 // Expose error to client via perf log using same format as other entries.
213 AddLogEntry(Log::kWarning
,
214 DevToolsClientImpl::kBrowserwideDevToolsClientId
,
215 "Tracing.bufferUsage", params
);
222 Status
PerformanceLogger::StartTrace() {
223 if (!browser_client_
) {
224 return Status(kUnknownError
, "tried to start tracing, but connection to "
225 "browser was not yet established");
227 if (trace_buffering_
) {
228 LOG(WARNING
) << "tried to start tracing, but a trace was already started";
231 base::DictionaryValue params
;
232 params
.SetString("categories", prefs_
.trace_categories
);
233 // Ask DevTools to report buffer usage.
234 params
.SetInteger("bufferUsageReportingInterval",
235 prefs_
.buffer_usage_reporting_interval
);
236 Status status
= browser_client_
->SendCommand("Tracing.start", params
);
237 if (status
.IsError()) {
238 LOG(ERROR
) << "error when starting trace: " << status
.message();
241 trace_buffering_
= true;
245 Status
PerformanceLogger::CollectTraceEvents() {
246 if (!browser_client_
) {
247 return Status(kUnknownError
, "tried to collect trace events, but "
248 "connection to browser was not yet established");
250 if (!trace_buffering_
) {
251 return Status(kUnknownError
, "tried to collect trace events, but tracing "
255 // As of r307466, DevTools no longer returns a response to Tracing.end
256 // commands, so we need to ignore it here to avoid a timeout. See
257 // https://code.google.com/p/chromedriver/issues/detail?id=997 for details.
258 // TODO(samuong): find other commands where we don't need the response.
259 bool wait_for_response
= false;
260 if (session_
->chrome
) {
261 const BrowserInfo
* browser_info
= session_
->chrome
->GetBrowserInfo();
262 if (browser_info
->browser_name
== "chrome" && browser_info
->build_no
< 2245)
263 wait_for_response
= true;
265 base::DictionaryValue params
;
267 if (wait_for_response
)
268 status
= browser_client_
->SendCommand("Tracing.end", params
);
270 status
= browser_client_
->SendAsyncCommand("Tracing.end", params
);
271 if (status
.IsError()) {
272 LOG(ERROR
) << "error when stopping trace: " << status
.message();
276 // Block up to 30 seconds until Tracing.tracingComplete event is received.
277 status
= browser_client_
->HandleEventsUntil(
278 base::Bind(&PerformanceLogger::IsTraceDone
, base::Unretained(this)),
279 base::TimeDelta::FromSeconds(30));
280 if (status
.IsError())
286 Status
PerformanceLogger::IsTraceDone(bool* trace_done
) const {
287 *trace_done
= !trace_buffering_
;