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