Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(553)

Side by Side Diff: net/log/net_log.cc

Issue 1059843002: Refactor NetLog::LogLevel --> NetLogCaptureMode. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: fix some translation bugs Created 5 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 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 "net/log/net_log.h" 5 #include "net/log/net_log.h"
6 6
7 #include "base/bind.h" 7 #include "base/bind.h"
8 #include "base/debug/alias.h" 8 #include "base/debug/alias.h"
9 #include "base/logging.h" 9 #include "base/logging.h"
10 #include "base/strings/string_number_conversions.h" 10 #include "base/strings/string_number_conversions.h"
11 #include "base/strings/utf_string_conversions.h" 11 #include "base/strings/utf_string_conversions.h"
12 #include "base/time/time.h" 12 #include "base/time/time.h"
13 #include "base/values.h" 13 #include "base/values.h"
14 #include "net/base/net_errors.h" 14 #include "net/base/net_errors.h"
15 15
16 namespace net { 16 namespace net {
17 17
18 namespace { 18 namespace {
19 19
20 // Returns parameters for logging data transferred events. Includes number of 20 // Returns parameters for logging data transferred events. At a minum includes
21 // bytes transferred and, if the log level indicates bytes should be logged and 21 // the number of bytes transferred. If the capture mode allows logging byte
22 // |byte_count| > 0, the bytes themselves. The bytes are hex-encoded, since 22 // contents and |byte_count| > 0, then will include the actual bytes. The
23 // base::StringValue only supports UTF-8. 23 // bytes are hex-encoded, since base::StringValue only supports UTF-8.
24 base::Value* BytesTransferredCallback(int byte_count, 24 base::Value* BytesTransferredCallback(int byte_count,
25 const char* bytes, 25 const char* bytes,
26 NetLog::LogLevel log_level) { 26 NetLogCaptureMode capture_mode) {
27 base::DictionaryValue* dict = new base::DictionaryValue(); 27 base::DictionaryValue* dict = new base::DictionaryValue();
28 dict->SetInteger("byte_count", byte_count); 28 dict->SetInteger("byte_count", byte_count);
29 if (NetLog::IsLoggingBytes(log_level) && byte_count > 0) 29 if (capture_mode.include_socket_bytes() && byte_count > 0)
30 dict->SetString("hex_encoded_bytes", base::HexEncode(bytes, byte_count)); 30 dict->SetString("hex_encoded_bytes", base::HexEncode(bytes, byte_count));
31 return dict; 31 return dict;
32 } 32 }
33 33
34 base::Value* SourceEventParametersCallback(const NetLog::Source source, 34 base::Value* SourceEventParametersCallback(
35 NetLog::LogLevel /* log_level */) { 35 const NetLog::Source source,
36 NetLogCaptureMode /* capture_mode */) {
36 if (!source.IsValid()) 37 if (!source.IsValid())
37 return NULL; 38 return NULL;
38 base::DictionaryValue* event_params = new base::DictionaryValue(); 39 base::DictionaryValue* event_params = new base::DictionaryValue();
39 source.AddToEventParameters(event_params); 40 source.AddToEventParameters(event_params);
40 return event_params; 41 return event_params;
41 } 42 }
42 43
43 base::Value* NetLogIntegerCallback(const char* name, 44 base::Value* NetLogIntegerCallback(const char* name,
44 int value, 45 int value,
45 NetLog::LogLevel /* log_level */) { 46 NetLogCaptureMode /* capture_mode */) {
46 base::DictionaryValue* event_params = new base::DictionaryValue(); 47 base::DictionaryValue* event_params = new base::DictionaryValue();
47 event_params->SetInteger(name, value); 48 event_params->SetInteger(name, value);
48 return event_params; 49 return event_params;
49 } 50 }
50 51
51 base::Value* NetLogInt64Callback(const char* name, 52 base::Value* NetLogInt64Callback(const char* name,
52 int64 value, 53 int64 value,
53 NetLog::LogLevel /* log_level */) { 54 NetLogCaptureMode /* capture_mode */) {
54 base::DictionaryValue* event_params = new base::DictionaryValue(); 55 base::DictionaryValue* event_params = new base::DictionaryValue();
55 event_params->SetString(name, base::Int64ToString(value)); 56 event_params->SetString(name, base::Int64ToString(value));
56 return event_params; 57 return event_params;
57 } 58 }
58 59
59 base::Value* NetLogStringCallback(const char* name, 60 base::Value* NetLogStringCallback(const char* name,
60 const std::string* value, 61 const std::string* value,
61 NetLog::LogLevel /* log_level */) { 62 NetLogCaptureMode /* capture_mode */) {
62 base::DictionaryValue* event_params = new base::DictionaryValue(); 63 base::DictionaryValue* event_params = new base::DictionaryValue();
63 event_params->SetString(name, *value); 64 event_params->SetString(name, *value);
64 return event_params; 65 return event_params;
65 } 66 }
66 67
67 base::Value* NetLogString16Callback(const char* name, 68 base::Value* NetLogString16Callback(const char* name,
68 const base::string16* value, 69 const base::string16* value,
69 NetLog::LogLevel /* log_level */) { 70 NetLogCaptureMode /* capture_mode */) {
70 base::DictionaryValue* event_params = new base::DictionaryValue(); 71 base::DictionaryValue* event_params = new base::DictionaryValue();
71 event_params->SetString(name, *value); 72 event_params->SetString(name, *value);
72 return event_params; 73 return event_params;
73 } 74 }
74 75
75 } // namespace 76 } // namespace
76 77
77 // LoadTimingInfo requires this be 0. 78 // LoadTimingInfo requires this be 0.
78 const uint32 NetLog::Source::kInvalidId = 0; 79 const uint32 NetLog::Source::kInvalidId = 0;
79 80
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after
130 source_dict->SetInteger("id", data_->source.id); 131 source_dict->SetInteger("id", data_->source.id);
131 source_dict->SetInteger("type", static_cast<int>(data_->source.type)); 132 source_dict->SetInteger("type", static_cast<int>(data_->source.type));
132 entry_dict->Set("source", source_dict); 133 entry_dict->Set("source", source_dict);
133 134
134 // Set the event info. 135 // Set the event info.
135 entry_dict->SetInteger("type", static_cast<int>(data_->type)); 136 entry_dict->SetInteger("type", static_cast<int>(data_->type));
136 entry_dict->SetInteger("phase", static_cast<int>(data_->phase)); 137 entry_dict->SetInteger("phase", static_cast<int>(data_->phase));
137 138
138 // Set the event-specific parameters. 139 // Set the event-specific parameters.
139 if (data_->parameters_callback) { 140 if (data_->parameters_callback) {
140 base::Value* value = data_->parameters_callback->Run(log_level_); 141 base::Value* value = data_->parameters_callback->Run(capture_mode_);
141 if (value) 142 if (value)
142 entry_dict->Set("params", value); 143 entry_dict->Set("params", value);
143 } 144 }
144 145
145 return entry_dict; 146 return entry_dict;
146 } 147 }
147 148
148 base::Value* NetLog::Entry::ParametersToValue() const { 149 base::Value* NetLog::Entry::ParametersToValue() const {
149 if (data_->parameters_callback) 150 if (data_->parameters_callback)
150 return data_->parameters_callback->Run(log_level_); 151 return data_->parameters_callback->Run(capture_mode_);
151 return NULL; 152 return NULL;
152 } 153 }
153 154
154 NetLog::EntryData::EntryData(EventType type, 155 NetLog::EntryData::EntryData(EventType type,
155 Source source, 156 Source source,
156 EventPhase phase, 157 EventPhase phase,
157 base::TimeTicks time, 158 base::TimeTicks time,
158 const ParametersCallback* parameters_callback) 159 const ParametersCallback* parameters_callback)
159 : type(type), 160 : type(type),
160 source(source), 161 source(source),
161 phase(phase), 162 phase(phase),
162 time(time), 163 time(time),
163 parameters_callback(parameters_callback) { 164 parameters_callback(parameters_callback) {
164 } 165 }
165 166
166 NetLog::EntryData::~EntryData() { 167 NetLog::EntryData::~EntryData() {
167 } 168 }
168 169
169 NetLog::Entry::Entry(const EntryData* data, LogLevel log_level) 170 NetLog::Entry::Entry(const EntryData* data, NetLogCaptureMode capture_mode)
170 : data_(data), log_level_(log_level) { 171 : data_(data), capture_mode_(capture_mode) {
171 } 172 }
172 173
173 NetLog::Entry::~Entry() { 174 NetLog::Entry::~Entry() {
174 } 175 }
175 176
176 NetLog::ThreadSafeObserver::ThreadSafeObserver() 177 NetLog::ThreadSafeObserver::ThreadSafeObserver() : net_log_(NULL) {
177 : log_level_(LOG_NONE), net_log_(NULL) {
178 } 178 }
179 179
180 NetLog::ThreadSafeObserver::~ThreadSafeObserver() { 180 NetLog::ThreadSafeObserver::~ThreadSafeObserver() {
181 // Make sure we aren't watching a NetLog on destruction. Because the NetLog 181 // Make sure we aren't watching a NetLog on destruction. Because the NetLog
182 // may pass events to each observer on multiple threads, we cannot safely 182 // may pass events to each observer on multiple threads, we cannot safely
183 // stop watching a NetLog automatically from a parent class. 183 // stop watching a NetLog automatically from a parent class.
184 DCHECK(!net_log_); 184 DCHECK(!net_log_);
185 } 185 }
186 186
187 NetLog::LogLevel NetLog::ThreadSafeObserver::log_level() const { 187 NetLogCaptureMode NetLog::ThreadSafeObserver::capture_mode() const {
188 DCHECK(net_log_); 188 DCHECK(net_log_);
189 return log_level_; 189 return capture_mode_;
190 } 190 }
191 191
192 NetLog* NetLog::ThreadSafeObserver::net_log() const { 192 NetLog* NetLog::ThreadSafeObserver::net_log() const {
193 return net_log_; 193 return net_log_;
194 } 194 }
195 195
196 void NetLog::ThreadSafeObserver::OnAddEntryData(const EntryData& entry_data) { 196 void NetLog::ThreadSafeObserver::OnAddEntryData(const EntryData& entry_data) {
197 OnAddEntry(Entry(&entry_data, log_level())); 197 OnAddEntry(Entry(&entry_data, capture_mode()));
198 } 198 }
199 199
200 NetLog::NetLog() : last_id_(0), effective_log_level_(LOG_NONE) { 200 NetLog::NetLog()
201 : last_id_(0), effective_capture_mode_level_(NetLogCaptureMode::NONE) {
eroman 2015/04/09 01:11:27 Is it legal to initialize an atomic int in this ma
mmenke 2015/04/09 15:47:11 It won't lead to bugs (Since we can only pass the
201 } 202 }
202 203
203 NetLog::~NetLog() { 204 NetLog::~NetLog() {
204 } 205 }
205 206
206 void NetLog::AddGlobalEntry(EventType type) { 207 void NetLog::AddGlobalEntry(EventType type) {
207 AddEntry(type, Source(net::NetLog::SOURCE_NONE, NextID()), 208 AddEntry(type, Source(net::NetLog::SOURCE_NONE, NextID()),
208 net::NetLog::PHASE_NONE, NULL); 209 net::NetLog::PHASE_NONE, NULL);
209 } 210 }
210 211
211 void NetLog::AddGlobalEntry( 212 void NetLog::AddGlobalEntry(
212 EventType type, 213 EventType type,
213 const NetLog::ParametersCallback& parameters_callback) { 214 const NetLog::ParametersCallback& parameters_callback) {
214 AddEntry(type, Source(net::NetLog::SOURCE_NONE, NextID()), 215 AddEntry(type, Source(net::NetLog::SOURCE_NONE, NextID()),
215 net::NetLog::PHASE_NONE, &parameters_callback); 216 net::NetLog::PHASE_NONE, &parameters_callback);
216 } 217 }
217 218
218 uint32 NetLog::NextID() { 219 uint32 NetLog::NextID() {
219 return base::subtle::NoBarrier_AtomicIncrement(&last_id_, 1); 220 return base::subtle::NoBarrier_AtomicIncrement(&last_id_, 1);
220 } 221 }
221 222
222 NetLog::LogLevel NetLog::GetLogLevel() const { 223 NetLogCaptureMode NetLog::GetCaptureMode() const {
223 base::subtle::Atomic32 log_level = 224 base::subtle::Atomic32 capture_mode_level =
224 base::subtle::NoBarrier_Load(&effective_log_level_); 225 base::subtle::NoBarrier_Load(&effective_capture_mode_level_);
225 return static_cast<net::NetLog::LogLevel>(log_level); 226 return NetLogCaptureMode(
227 static_cast<NetLogCaptureMode::Level>(capture_mode_level));
226 } 228 }
227 229
228 void NetLog::DeprecatedAddObserver(net::NetLog::ThreadSafeObserver* observer, 230 void NetLog::DeprecatedAddObserver(net::NetLog::ThreadSafeObserver* observer,
229 LogLevel log_level) { 231 NetLogCaptureMode capture_mode) {
230 DCHECK_NE(LOG_NONE, log_level); 232 DCHECK(capture_mode.enabled());
233
231 base::AutoLock lock(lock_); 234 base::AutoLock lock(lock_);
232 235
233 DCHECK(!observer->net_log_); 236 DCHECK(!observer->net_log_);
234 DCHECK_EQ(LOG_NONE, observer->log_level_); 237 DCHECK(!observer->capture_mode_.enabled());
235 observers_.AddObserver(observer); 238 observers_.AddObserver(observer);
236 observer->net_log_ = this; 239 observer->net_log_ = this;
237 observer->log_level_ = log_level; 240 observer->capture_mode_ = capture_mode;
238 UpdateLogLevel(); 241 UpdateCaptureMode();
239 } 242 }
240 243
241 void NetLog::SetObserverLogLevel(net::NetLog::ThreadSafeObserver* observer, 244 void NetLog::SetObserverCaptureMode(net::NetLog::ThreadSafeObserver* observer,
242 LogLevel log_level) { 245 NetLogCaptureMode capture_mode) {
243 DCHECK_NE(LOG_NONE, log_level); 246 DCHECK(capture_mode.enabled());
244 base::AutoLock lock(lock_); 247 base::AutoLock lock(lock_);
245 248
246 DCHECK(observers_.HasObserver(observer)); 249 DCHECK(observers_.HasObserver(observer));
247 DCHECK_EQ(this, observer->net_log_); 250 DCHECK_EQ(this, observer->net_log_);
248 DCHECK_NE(LOG_NONE, observer->log_level_); 251 DCHECK(observer->capture_mode_.enabled());
249 observer->log_level_ = log_level; 252 observer->capture_mode_ = capture_mode;
250 UpdateLogLevel(); 253 UpdateCaptureMode();
251 } 254 }
252 255
253 void NetLog::DeprecatedRemoveObserver( 256 void NetLog::DeprecatedRemoveObserver(
254 net::NetLog::ThreadSafeObserver* observer) { 257 net::NetLog::ThreadSafeObserver* observer) {
255 base::AutoLock lock(lock_); 258 base::AutoLock lock(lock_);
256 259
257 DCHECK(observers_.HasObserver(observer)); 260 DCHECK(observers_.HasObserver(observer));
258 DCHECK_EQ(this, observer->net_log_); 261 DCHECK_EQ(this, observer->net_log_);
259 DCHECK_NE(LOG_NONE, observer->log_level_); 262 DCHECK(observer->capture_mode_.enabled());
260 observers_.RemoveObserver(observer); 263 observers_.RemoveObserver(observer);
261 observer->net_log_ = NULL; 264 observer->net_log_ = NULL;
262 observer->log_level_ = LOG_NONE; 265 observer->capture_mode_ = NetLogCaptureMode();
263 UpdateLogLevel(); 266 UpdateCaptureMode();
264 } 267 }
265 268
266 void NetLog::UpdateLogLevel() { 269 void NetLog::UpdateCaptureMode() {
267 lock_.AssertAcquired(); 270 lock_.AssertAcquired();
268 271
269 // Look through all the observers and find the finest granularity 272 // Accumulate the capture mode of all the observers to find the maximum level.
270 // log level (higher values of the enum imply *lower* log levels). 273 NetLogCaptureMode new_capture_mode;
271 LogLevel new_effective_log_level = LOG_NONE;
272 ObserverListBase<ThreadSafeObserver>::Iterator it(&observers_); 274 ObserverListBase<ThreadSafeObserver>::Iterator it(&observers_);
273 ThreadSafeObserver* observer; 275 ThreadSafeObserver* observer;
274 while ((observer = it.GetNext()) != NULL) { 276 while ((observer = it.GetNext()) != NULL) {
275 new_effective_log_level = 277 new_capture_mode.AddMode(observer->capture_mode());
276 std::min(new_effective_log_level, observer->log_level());
277 } 278 }
278 base::subtle::NoBarrier_Store(&effective_log_level_, new_effective_log_level); 279 base::subtle::NoBarrier_Store(&effective_capture_mode_level_,
280 new_capture_mode.level());
mmenke 2015/04/09 15:47:11 Should we really be storing the raw level here? S
eroman 2015/04/16 22:51:09 I have made an allowance for NetLog (by friending
279 } 281 }
280 282
281 // static 283 // static
282 std::string NetLog::TickCountToString(const base::TimeTicks& time) { 284 std::string NetLog::TickCountToString(const base::TimeTicks& time) {
283 int64 delta_time = (time - base::TimeTicks()).InMilliseconds(); 285 int64 delta_time = (time - base::TimeTicks()).InMilliseconds();
284 return base::Int64ToString(delta_time); 286 return base::Int64ToString(delta_time);
285 } 287 }
286 288
287 // static 289 // static
288 const char* NetLog::EventTypeToString(EventType event) { 290 const char* NetLog::EventTypeToString(EventType event) {
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after
338 case PHASE_END: 340 case PHASE_END:
339 return "PHASE_END"; 341 return "PHASE_END";
340 case PHASE_NONE: 342 case PHASE_NONE:
341 return "PHASE_NONE"; 343 return "PHASE_NONE";
342 } 344 }
343 NOTREACHED(); 345 NOTREACHED();
344 return NULL; 346 return NULL;
345 } 347 }
346 348
347 // static 349 // static
348 bool NetLog::IsLoggingBytes(LogLevel log_level) {
349 return log_level == NetLog::LOG_ALL;
350 }
351
352 // static
353 bool NetLog::IsLogging(LogLevel log_level) {
354 return log_level < NetLog::LOG_NONE;
355 }
356
357 // static
358 NetLog::ParametersCallback NetLog::IntegerCallback(const char* name, 350 NetLog::ParametersCallback NetLog::IntegerCallback(const char* name,
359 int value) { 351 int value) {
360 return base::Bind(&NetLogIntegerCallback, name, value); 352 return base::Bind(&NetLogIntegerCallback, name, value);
361 } 353 }
362 354
363 // static 355 // static
364 NetLog::ParametersCallback NetLog::Int64Callback(const char* name, 356 NetLog::ParametersCallback NetLog::Int64Callback(const char* name,
365 int64 value) { 357 int64 value) {
366 return base::Bind(&NetLogInt64Callback, name, value); 358 return base::Bind(&NetLogInt64Callback, name, value);
367 } 359 }
368 360
369 // static 361 // static
370 NetLog::ParametersCallback NetLog::StringCallback(const char* name, 362 NetLog::ParametersCallback NetLog::StringCallback(const char* name,
371 const std::string* value) { 363 const std::string* value) {
372 DCHECK(value); 364 DCHECK(value);
373 return base::Bind(&NetLogStringCallback, name, value); 365 return base::Bind(&NetLogStringCallback, name, value);
374 } 366 }
375 367
376 // static 368 // static
377 NetLog::ParametersCallback NetLog::StringCallback(const char* name, 369 NetLog::ParametersCallback NetLog::StringCallback(const char* name,
378 const base::string16* value) { 370 const base::string16* value) {
379 DCHECK(value); 371 DCHECK(value);
380 return base::Bind(&NetLogString16Callback, name, value); 372 return base::Bind(&NetLogString16Callback, name, value);
381 } 373 }
382 374
383 void NetLog::AddEntry(EventType type, 375 void NetLog::AddEntry(EventType type,
384 const Source& source, 376 const Source& source,
385 EventPhase phase, 377 EventPhase phase,
386 const NetLog::ParametersCallback* parameters_callback) { 378 const NetLog::ParametersCallback* parameters_callback) {
387 if (GetLogLevel() == LOG_NONE) 379 if (!GetCaptureMode().enabled())
388 return; 380 return;
389 EntryData entry_data(type, source, phase, base::TimeTicks::Now(), 381 EntryData entry_data(type, source, phase, base::TimeTicks::Now(),
390 parameters_callback); 382 parameters_callback);
391 383
392 // Notify all of the log observers. 384 // Notify all of the log observers.
393 base::AutoLock lock(lock_); 385 base::AutoLock lock(lock_);
394 FOR_EACH_OBSERVER(ThreadSafeObserver, observers_, OnAddEntryData(entry_data)); 386 FOR_EACH_OBSERVER(ThreadSafeObserver, observers_, OnAddEntryData(entry_data));
395 } 387 }
396 388
397 BoundNetLog::~BoundNetLog() { 389 BoundNetLog::~BoundNetLog() {
(...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after
467 EndEvent(event_type, NetLog::IntegerCallback("net_error", net_error)); 459 EndEvent(event_type, NetLog::IntegerCallback("net_error", net_error));
468 } 460 }
469 } 461 }
470 462
471 void BoundNetLog::AddByteTransferEvent(NetLog::EventType event_type, 463 void BoundNetLog::AddByteTransferEvent(NetLog::EventType event_type,
472 int byte_count, 464 int byte_count,
473 const char* bytes) const { 465 const char* bytes) const {
474 AddEvent(event_type, base::Bind(BytesTransferredCallback, byte_count, bytes)); 466 AddEvent(event_type, base::Bind(BytesTransferredCallback, byte_count, bytes));
475 } 467 }
476 468
477 NetLog::LogLevel BoundNetLog::GetLogLevel() const { 469 NetLogCaptureMode BoundNetLog::GetCaptureMode() const {
478 CrashIfInvalid(); 470 CrashIfInvalid();
479 471
480 if (net_log_) 472 if (net_log_)
481 return net_log_->GetLogLevel(); 473 return net_log_->GetCaptureMode();
482 return NetLog::LOG_NONE; 474 return NetLogCaptureMode();
483 }
484
485 bool BoundNetLog::IsLoggingBytes() const {
486 return NetLog::IsLoggingBytes(GetLogLevel());
487 }
488
489 bool BoundNetLog::IsLogging() const {
490 return NetLog::IsLogging(GetLogLevel());
491 } 475 }
492 476
493 // static 477 // static
494 BoundNetLog BoundNetLog::Make(NetLog* net_log, NetLog::SourceType source_type) { 478 BoundNetLog BoundNetLog::Make(NetLog* net_log, NetLog::SourceType source_type) {
495 if (!net_log) 479 if (!net_log)
496 return BoundNetLog(); 480 return BoundNetLog();
497 481
498 NetLog::Source source(source_type, net_log->NextID()); 482 NetLog::Source source(source_type, net_log->NextID());
499 return BoundNetLog(source, net_log); 483 return BoundNetLog(source, net_log);
500 } 484 }
501 485
502 void BoundNetLog::CrashIfInvalid() const { 486 void BoundNetLog::CrashIfInvalid() const {
503 Liveness liveness = liveness_; 487 Liveness liveness = liveness_;
504 488
505 if (liveness == ALIVE) 489 if (liveness == ALIVE)
506 return; 490 return;
507 491
508 base::debug::Alias(&liveness); 492 base::debug::Alias(&liveness);
509 CHECK_EQ(ALIVE, liveness); 493 CHECK_EQ(ALIVE, liveness);
510 } 494 }
511 495
512 } // namespace net 496 } // namespace net
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698