OLD | NEW |
---|---|
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/test/chromedriver/performance_logger.h" | |
6 | |
5 #include <string> | 7 #include <string> |
6 #include <vector> | 8 #include <vector> |
7 | 9 |
8 #include "chrome/test/chromedriver/performance_logger.h" | 10 #include "base/bind.h" |
9 | |
10 #include "base/json/json_writer.h" | 11 #include "base/json/json_writer.h" |
11 #include "base/strings/string_util.h" | 12 #include "base/strings/string_util.h" |
12 #include "base/values.h" | 13 #include "base/values.h" |
14 #include "chrome/test/chromedriver/chrome/chrome.h" | |
13 #include "chrome/test/chromedriver/chrome/devtools_client.h" | 15 #include "chrome/test/chromedriver/chrome/devtools_client.h" |
14 #include "chrome/test/chromedriver/chrome/devtools_client_impl.h" | 16 #include "chrome/test/chromedriver/chrome/devtools_client_impl.h" |
15 #include "chrome/test/chromedriver/chrome/log.h" | 17 #include "chrome/test/chromedriver/chrome/log.h" |
16 #include "chrome/test/chromedriver/chrome/status.h" | 18 #include "chrome/test/chromedriver/chrome/status.h" |
19 #include "chrome/test/chromedriver/chrome/version.h" | |
20 #include "chrome/test/chromedriver/session.h" | |
17 | 21 |
18 namespace { | 22 namespace { |
19 | 23 |
20 // DevTools event domain prefixes to intercept. | 24 // DevTools event domain prefixes to intercept. |
21 const char* const kDomains[] = {"Network.", "Page.", "Timeline."}; | 25 const char* const kDomains[] = {"Network.", "Page.", "Timeline."}; |
22 | 26 |
27 // Whitelist of WebDriver commands on which to request buffered trace events. | |
28 const char* const kRequestTraceCommands[] = {"GetLog" /* required */, | |
stgao
2014/08/10 02:37:03
Just an idea: do we want this to be configurable i
johnmoore
2014/08/11 16:00:01
I don't think we should expose something that comp
stgao
2014/08/15 17:43:22
Not sure if we want to do every command, because s
| |
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 method is in kRequestTraceCommands[] (case-insensitive). | |
43 bool ShouldRequestTraceEvents(const std::string& method) { | |
stgao
2014/08/10 02:37:03
|method| -> |command|?
johnmoore
2014/08/11 16:00:01
Done.
| |
44 for (size_t i_domain = 0; i_domain < arraysize(kRequestTraceCommands); | |
45 ++i_domain) { | |
46 if (base::strcasecmp(method.c_str(), kRequestTraceCommands[i_domain]) == 0) | |
47 return true; | |
48 } | |
49 return false; | |
50 } | |
51 | |
23 // Returns whether the event belongs to one of kDomains. | 52 // Returns whether the event belongs to one of kDomains. |
24 bool ShouldLogEvent(const std::string& method) { | 53 bool ShouldLogEvent(const std::string& method) { |
25 for (size_t i_domain = 0; i_domain < arraysize(kDomains); ++i_domain) { | 54 for (size_t i_domain = 0; i_domain < arraysize(kDomains); ++i_domain) { |
26 if (StartsWithASCII(method, kDomains[i_domain], true /* case_sensitive */)) | 55 if (StartsWithASCII(method, kDomains[i_domain], true /* case_sensitive */)) |
27 return true; | 56 return true; |
28 } | 57 } |
29 return false; | 58 return false; |
30 } | 59 } |
31 | 60 |
32 bool IsEnabled(const PerfLoggingPrefs::InspectorDomainStatus& domain_status) { | |
33 return (domain_status == | |
34 PerfLoggingPrefs::InspectorDomainStatus::kDefaultEnabled || | |
35 domain_status == | |
36 PerfLoggingPrefs::InspectorDomainStatus::kExplicitlyEnabled); | |
37 } | |
38 | |
39 } // namespace | 61 } // namespace |
40 | 62 |
41 PerformanceLogger::PerformanceLogger(Log* log) | 63 PerformanceLogger::PerformanceLogger(Log* log, const Session* session) |
42 : log_(log) {} | 64 : log_(log), |
65 session_(session), | |
66 browser_client_(NULL), | |
67 trace_buffering_(false) {} | |
43 | 68 |
44 PerformanceLogger::PerformanceLogger(Log* log, const PerfLoggingPrefs& prefs) | 69 PerformanceLogger::PerformanceLogger(Log* log, |
70 const Session* session, | |
71 const PerfLoggingPrefs& prefs) | |
45 : log_(log), | 72 : log_(log), |
46 prefs_(prefs) { | 73 session_(session), |
47 if (!prefs_.trace_categories.empty()) { | 74 prefs_(prefs), |
48 LOG(WARNING) << "Ignoring trace categories because tracing support not yet " | 75 browser_client_(NULL), |
49 << "implemented: " << prefs_.trace_categories; | 76 trace_buffering_(false) {} |
50 prefs_.trace_categories = ""; | |
51 } | |
52 } | |
53 | 77 |
54 bool PerformanceLogger::subscribes_to_browser() { | 78 bool PerformanceLogger::subscribes_to_browser() { |
55 return true; | 79 return true; |
56 } | 80 } |
57 | 81 |
58 Status PerformanceLogger::OnConnected(DevToolsClient* client) { | 82 Status PerformanceLogger::OnConnected(DevToolsClient* client) { |
59 if (client->GetId() == DevToolsClientImpl::kBrowserwideDevToolsClientId) { | 83 if (IsBrowserwideClient(client)) { |
60 // TODO(johnmoore): Implement tracing log. | 84 browser_client_ = client; |
85 if (!prefs_.trace_categories.empty()) { | |
86 Status status = StartTrace(); | |
87 if (status.IsError()) | |
88 return status; | |
89 } | |
61 return Status(kOk); | 90 return Status(kOk); |
62 } | 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 const std::string& webview, | |
118 const std::string& method, | |
119 const base::DictionaryValue& params) { | |
120 base::DictionaryValue log_message_dict; | |
121 log_message_dict.SetString("webview", webview); | |
122 log_message_dict.SetString("message.method", method); | |
123 log_message_dict.Set("message.params", params.DeepCopy()); | |
124 std::string log_message_json; | |
125 base::JSONWriter::Write(&log_message_dict, &log_message_json); | |
126 | |
127 // TODO(klm): extract timestamp from params? | |
128 // Look at where it is for Page, Network, Timeline, and trace events. | |
129 log_->AddEntry(Log::kInfo, log_message_json); | |
130 } | |
131 | |
132 Status PerformanceLogger::EnableInspectorDomains(DevToolsClient* client) { | |
63 std::vector<std::string> enable_commands; | 133 std::vector<std::string> enable_commands; |
64 if (IsEnabled(prefs_.network)) | 134 if (IsEnabled(prefs_.network)) |
65 enable_commands.push_back("Network.enable"); | 135 enable_commands.push_back("Network.enable"); |
66 if (IsEnabled(prefs_.page)) | 136 if (IsEnabled(prefs_.page)) |
67 enable_commands.push_back("Page.enable"); | 137 enable_commands.push_back("Page.enable"); |
68 if (IsEnabled(prefs_.timeline)) { | 138 if (IsEnabled(prefs_.timeline)) { |
69 // Timeline feed implicitly disabled when trace categories are specified. | 139 // Timeline feed implicitly disabled when trace categories are specified. |
70 // So even if kDefaultEnabled, don't enable unless empty |trace_categories|. | 140 // So even if kDefaultEnabled, don't enable unless empty |trace_categories|. |
71 if (prefs_.trace_categories.empty() || prefs_.timeline == | 141 if (prefs_.trace_categories.empty() || prefs_.timeline == |
72 PerfLoggingPrefs::InspectorDomainStatus::kExplicitlyEnabled) | 142 PerfLoggingPrefs::InspectorDomainStatus::kExplicitlyEnabled) |
73 enable_commands.push_back("Timeline.start"); | 143 enable_commands.push_back("Timeline.start"); |
74 } | 144 } |
75 for (std::vector<std::string>::const_iterator it = enable_commands.begin(); | 145 for (std::vector<std::string>::const_iterator it = enable_commands.begin(); |
76 it != enable_commands.end(); ++it) { | 146 it != enable_commands.end(); ++it) { |
77 base::DictionaryValue params; // All the enable commands have empty params. | 147 base::DictionaryValue params; // All the enable commands have empty params. |
78 Status status = client->SendCommand(*it, params); | 148 Status status = client->SendCommand(*it, params); |
79 if (status.IsError()) | 149 if (status.IsError()) |
80 return status; | 150 return status; |
81 } | 151 } |
82 return Status(kOk); | 152 return Status(kOk); |
83 } | 153 } |
84 | 154 |
85 Status PerformanceLogger::OnEvent( | 155 Status PerformanceLogger::HandleInspectorEvents( |
86 DevToolsClient* client, | 156 DevToolsClient* client, |
87 const std::string& method, | 157 const std::string& method, |
88 const base::DictionaryValue& params) { | 158 const base::DictionaryValue& params) { |
159 | |
stgao
2014/08/10 02:37:04
This line could be removed.
johnmoore
2014/08/11 16:00:02
Done.
| |
89 if (!ShouldLogEvent(method)) | 160 if (!ShouldLogEvent(method)) |
90 return Status(kOk); | 161 return Status(kOk); |
91 | 162 |
92 base::DictionaryValue log_message_dict; | 163 AddLogEntry(client->GetId(), method, params); |
93 log_message_dict.SetString("webview", client->GetId()); | |
94 log_message_dict.SetString("message.method", method); | |
95 log_message_dict.Set("message.params", params.DeepCopy()); | |
96 std::string log_message_json; | |
97 // TODO(klm): extract timestamp from params? | |
98 // Look at where it is for Page, Network, Timeline events. | |
99 base::JSONWriter::Write(&log_message_dict, &log_message_json); | |
100 | |
101 log_->AddEntry(Log::kInfo, log_message_json); | |
102 return Status(kOk); | 164 return Status(kOk); |
103 } | 165 } |
104 | 166 |
105 // TODO(johnmoore): Use BeforeCommand to implement tracing log. | 167 Status PerformanceLogger::HandleTraceEvents( |
106 Status PerformanceLogger::BeforeCommand(const std::string& command_name) { | 168 DevToolsClient* client, |
169 const std::string& method, | |
170 const base::DictionaryValue& params) { | |
171 if (method == "Tracing.tracingComplete") { | |
172 trace_buffering_ = false; | |
173 } else if (method == "Tracing.dataCollected") { | |
174 // The Tracing.dataCollected event contains a list of trace events. | |
175 // Add each one as an individual log entry of method Tracing.dataCollected. | |
176 const base::ListValue* traces; | |
177 if (!params.GetList("value", &traces)) { | |
178 return Status(kUnknownError, | |
179 "Received DevTools trace data in unexpected format"); | |
stgao
2014/08/10 02:37:03
error message usually starts with lowercase unless
johnmoore
2014/08/11 16:00:01
Done.
| |
180 } | |
181 for (base::ListValue::const_iterator it = traces->begin(); | |
182 it != traces->end(); | |
183 ++it) { | |
184 base::DictionaryValue* event_dict; | |
185 if (!(*it)->GetAsDictionary(&event_dict)) | |
186 return Status(kUnknownError, "trace event must be a dictionary"); | |
187 AddLogEntry(client->GetId(), "Tracing.dataCollected", *event_dict); | |
188 } | |
189 } else if (method == "Tracing.bufferUsage") { | |
190 // 'value' will be between 0-1 and represents how full the DevTools trace | |
191 // buffer is. If the buffer is full, warn the user. | |
192 double buffer_usage = 0; | |
193 if (!params.GetDouble("value", &buffer_usage)) { | |
194 // Tracing.bufferUsage event will occur once per second, and it really | |
195 // only serves as a warning, so if we can't reliably tell whether the | |
196 // buffer is full, just fail silently instead of spamming the logs. | |
197 return Status(kOk); | |
198 } | |
199 if (buffer_usage >= 1.0) { | |
stgao
2014/08/10 02:37:04
Floating point comparison is not accurate.
Maybe u
johnmoore
2014/08/11 16:00:01
Done.
| |
200 LOG(WARNING) << "Chrome's trace buffer filled while collecting events, " | |
stgao
2014/08/10 02:37:04
This warning is on server side.
Do we want to mak
johnmoore
2014/08/11 16:00:01
Good idea. I've exposed the message to the user by
| |
201 << "so some trace events may have been lost"; | |
202 } | |
203 } | |
107 return Status(kOk); | 204 return Status(kOk); |
108 } | 205 } |
206 | |
207 bool PerformanceLogger::ShouldReportTracingError() { | |
208 // Chromium builds 1967-2000, which correspond to Blink revisions 172887- | |
209 // 174227, contain a regression where Tracing.start and Tracing.end commands | |
210 // erroneously return error -32601 "no such method". The commands still work. | |
211 if (session_->chrome) { | |
212 const BrowserInfo* browser_info = session_->chrome->GetBrowserInfo(); | |
213 | |
214 bool should_report_error = true; | |
215 if (browser_info->browser_name == "chrome") { | |
216 should_report_error = !(browser_info->build_no >= 1967 && | |
217 browser_info->build_no <= 2000); | |
218 } else { | |
219 should_report_error = !(browser_info->blink_revision >= 172887 && | |
220 browser_info->blink_revision <= 174227); | |
221 } | |
222 return should_report_error; | |
223 } | |
224 // We're not yet able to tell the Chrome version, so don't report this error. | |
stgao
2014/08/10 02:37:04
Maybe add an empty line before this one.
johnmoore
2014/08/11 16:00:01
Done.
| |
225 return false; | |
226 } | |
227 | |
228 Status PerformanceLogger::StartTrace() { | |
229 CHECK(browser_client_); | |
230 if (trace_buffering_) { | |
231 LOG(WARNING) << "Ignoring PerformanceLogger::StartTrace because tracing " | |
232 << "was already started"; | |
233 return Status(kOk); | |
234 } | |
235 base::DictionaryValue params; | |
236 params.SetString("categories", prefs_.trace_categories); | |
237 // Ask DevTools to report buffer usage percentage once per second. | |
238 params.SetInteger("bufferUsageReportingInterval", 1000 /* milliseconds */); | |
stgao
2014/08/10 02:37:03
Will this be too frequent?
johnmoore
2014/08/11 16:00:02
This is the default value that I've seen used in C
stgao
2014/08/15 17:43:22
Vote for making it configurable in perf log settin
| |
239 Status status = browser_client_->SendCommand("Tracing.start", params); | |
240 if (status.IsError() && ShouldReportTracingError()) { | |
241 LOG(ERROR) << "Error when starting trace: " << status.message(); | |
242 return status; | |
243 } | |
244 trace_buffering_ = true; | |
245 return Status(kOk); | |
246 } | |
247 | |
248 Status PerformanceLogger::CollectTraceEvents() { | |
249 CHECK(browser_client_); | |
250 if (!trace_buffering_) { | |
251 return Status(kUnknownError, "PerformanceLogger::CollectTraceEvents called," | |
stgao
2014/08/10 02:37:03
Maybe remove PerformanceLogger::CollectTraceEvents
johnmoore
2014/08/11 16:00:01
Done.
| |
252 " but tracing was not started"); | |
253 } | |
254 Status status = browser_client_->SendCommand("Tracing.end", | |
255 base::DictionaryValue()); | |
256 if (status.IsError() && ShouldReportTracingError()) { | |
257 LOG(ERROR) << "Error when stopping trace: " << status.message(); | |
258 return status; | |
259 } | |
260 // Block up to 30 seconds until Tracing.tracingComplete event is received. | |
stgao
2014/08/10 02:37:03
Add an empty line for each logic block.
That will
johnmoore
2014/08/11 16:00:01
Done.
| |
261 status = browser_client_->HandleEventsUntil( | |
262 base::Bind(&PerformanceLogger::IsTraceDone, base::Unretained(this)), | |
263 base::TimeDelta::FromSeconds(30)); | |
264 if (status.IsError()) | |
265 return status; | |
266 return StartTrace(); | |
267 } | |
268 | |
269 Status PerformanceLogger::IsTraceDone(bool* trace_done) const { | |
270 *trace_done = !trace_buffering_; | |
271 return Status(kOk); | |
272 } | |
OLD | NEW |