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. 4 5 #include "chrome/test/chromedriver/performance_logger.h" 6 7 #include <string> 8 #include <vector> 9 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" 21 22 namespace { 23 24 // DevTools event domain prefixes to intercept. 25 const char* const kDomains[] = {"Network.", "Page.", "Timeline."}; 26 27 // Whitelist of WebDriver commands on which to request buffered trace events. 28 const char* const kRequestTraceCommands[] = {"GetLog" /* required */, 29 "Navigate"}; 30 31 bool IsBrowserwideClient(DevToolsClient* client) { 32 return (client->GetId() == DevToolsClientImpl::kBrowserwideDevToolsClientId); 33 } 34 35 bool IsEnabled(const PerfLoggingPrefs::InspectorDomainStatus& domain_status) { 36 return (domain_status == 37 PerfLoggingPrefs::InspectorDomainStatus::kDefaultEnabled || 38 domain_status == 39 PerfLoggingPrefs::InspectorDomainStatus::kExplicitlyEnabled); 40 } 41 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); 45 ++i_domain) { 46 if (base::strcasecmp(command.c_str(), kRequestTraceCommands[i_domain]) == 0) 47 return true; 48 } 49 return false; 50 } 51 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 */)) 56 return true; 57 } 58 return false; 59 } 60 61 } // namespace 62 63 PerformanceLogger::PerformanceLogger(Log* log, const Session* session) 64 : log_(log), 65 session_(session), 66 browser_client_(NULL), 67 trace_buffering_(false) {} 68 69 PerformanceLogger::PerformanceLogger(Log* log, 70 const Session* session, 71 const PerfLoggingPrefs& prefs) 72 : log_(log), 73 session_(session), 74 prefs_(prefs), 75 browser_client_(NULL), 76 trace_buffering_(false) {} 77 78 bool PerformanceLogger::subscribes_to_browser() { 79 return true; 80 } 81 82 Status PerformanceLogger::OnConnected(DevToolsClient* client) { 83 if (IsBrowserwideClient(client)) { 84 browser_client_ = client; 85 if (!prefs_.trace_categories.empty()) { 86 Status status = StartTrace(); 87 if (status.IsError()) 88 return status; 89 } 90 return Status(kOk); 91 } 92 return EnableInspectorDomains(client); 93 } 94 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); 101 } else { 102 return HandleInspectorEvents(client, method, params); 103 } 104 } 105 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()) 111 return status; 112 } 113 return Status(kOk); 114 } 115 116 void PerformanceLogger::AddLogEntry( 117 Log::Level level, 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); 127 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); 131 } 132 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); 138 } 139 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"); 152 } 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()) 158 return status; 159 } 160 return Status(kOk); 161 } 162 163 Status PerformanceLogger::HandleInspectorEvents( 164 DevToolsClient* client, 165 const std::string& method, 166 const base::DictionaryValue& params) { 167 if (!ShouldLogEvent(method)) 168 return Status(kOk); 169 170 AddLogEntry(client->GetId(), method, params); 171 return Status(kOk); 172 } 173 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"); 187 } 188 for (base::ListValue::const_iterator it = traces->begin(); 189 it != traces->end(); 190 ++it) { 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); 195 } 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. 204 return Status(kOk); 205 } 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); 215 LOG(WARNING) << err; 216 } 217 } 218 return Status(kOk); 219 } 220 221 bool PerformanceLogger::ShouldReportTracingError() { 222 // Chromium builds 1967-2000, which correspond to Blink revisions 172887- 223 // 174227, contain a regression where Tracing.start and Tracing.end commands 224 // erroneously return error -32601 "no such method". The commands still work. 225 if (session_->chrome) { 226 const BrowserInfo* browser_info = session_->chrome->GetBrowserInfo(); 227 228 bool should_report_error = true; 229 if (browser_info->browser_name == "chrome") { 230 should_report_error = !(browser_info->build_no >= 1967 && 231 browser_info->build_no <= 2000); 232 } else { 233 should_report_error = !(browser_info->blink_revision >= 172887 && 234 browser_info->blink_revision <= 174227); 235 } 236 return should_report_error; 237 } 238 239 // We're not yet able to tell the Chrome version, so don't report this error. 240 return false; 241 } 242 243 Status PerformanceLogger::StartTrace() { 244 if (!browser_client_) { 245 return Status(kUnknownError, "tried to start tracing, but connection to " 246 "browser was not yet established"); 247 } 248 if (trace_buffering_) { 249 LOG(WARNING) << "tried to start tracing, but a trace was already started"; 250 return Status(kOk); 251 } 252 base::DictionaryValue params; 253 params.SetString("categories", prefs_.trace_categories); 254 // Ask DevTools to report buffer usage. 255 params.SetInteger("bufferUsageReportingInterval", 256 prefs_.buffer_usage_reporting_interval); 257 Status status = browser_client_->SendCommand("Tracing.start", params); 258 if (status.IsError() && ShouldReportTracingError()) { 259 LOG(ERROR) << "error when starting trace: " << status.message(); 260 return status; 261 } 262 trace_buffering_ = true; 263 return Status(kOk); 264 } 265 266 Status PerformanceLogger::CollectTraceEvents() { 267 if (!browser_client_) { 268 return Status(kUnknownError, "tried to collect trace events, but " 269 "connection to browser was not yet established"); 270 } 271 if (!trace_buffering_) { 272 return Status(kUnknownError, "tried to collect trace events, but tracing " 273 "was not started"); 274 } 275 276 Status status = browser_client_->SendCommand("Tracing.end", 277 base::DictionaryValue()); 278 if (status.IsError() && ShouldReportTracingError()) { 279 LOG(ERROR) << "error when stopping trace: " << status.message(); 280 return status; 281 } 282 283 // Block up to 30 seconds until Tracing.tracingComplete event is received. 284 status = browser_client_->HandleEventsUntil( 285 base::Bind(&PerformanceLogger::IsTraceDone, base::Unretained(this)), 286 base::TimeDelta::FromSeconds(30)); 287 if (status.IsError()) 288 return status; 289 290 return StartTrace(); 291 } 292 293 Status PerformanceLogger::IsTraceDone(bool* trace_done) const { 294 *trace_done = !trace_buffering_; 295 return Status(kOk); 296 } 297