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

Side by Side Diff: base/debug/trace_event.h

Issue 13590005: Add a ConvertableToTraceFormat type to the trace framework. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 7 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
« no previous file with comments | « no previous file | base/debug/trace_event_impl.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 // This header file defines the set of trace_event macros without specifying 5 // This header file defines the set of trace_event macros without specifying
6 // how the events actually get collected and stored. If you need to expose trace 6 // how the events actually get collected and stored. If you need to expose trace
7 // events to some other universe, you can copy-and-paste this file as well as 7 // events to some other universe, you can copy-and-paste this file as well as
8 // trace_event.h, modifying the macros contained there as necessary for the 8 // trace_event.h, modifying the macros contained there as necessary for the
9 // target platform. The end result is that multiple libraries can funnel events 9 // target platform. The end result is that multiple libraries can funnel events
10 // through to a shared trace event collector. 10 // through to a shared trace event collector.
(...skipping 105 matching lines...) Expand 10 before | Expand all | Expand 10 after
116 // TRACE_EVENT1("category", "name", 116 // TRACE_EVENT1("category", "name",
117 // "arg1", "literal string is only referenced"); 117 // "arg1", "literal string is only referenced");
118 // Use TRACE_STR_COPY to force copying of a const char*: 118 // Use TRACE_STR_COPY to force copying of a const char*:
119 // TRACE_EVENT1("category", "name", 119 // TRACE_EVENT1("category", "name",
120 // "arg1", TRACE_STR_COPY("string will be copied")); 120 // "arg1", TRACE_STR_COPY("string will be copied"));
121 // std::string arg_values are always copied: 121 // std::string arg_values are always copied:
122 // TRACE_EVENT1("category", "name", 122 // TRACE_EVENT1("category", "name",
123 // "arg1", std::string("string will be copied")); 123 // "arg1", std::string("string will be copied"));
124 // 124 //
125 // 125 //
126 // Convertable notes:
127 // Converting a large data type to a string can be costly. To help with this,
128 // the trace framework provides an interface ConvertableToTraceFormat. If you
129 // inherit from it and implement the AppendAsTraceFormat method the trace
130 // framework will call back to your object to convert a trace output time. This
131 // means, if the category for the event is disabled, the conversion will not
132 // happen.
133 //
134 // class MyData : public base::debug::ConvertableToTraceFormat {
135 // public:
136 // MyData() {}
137 // virtual ~MyData() {}
138 // virtual void AppendAsTraceFormat(std::string* out) const OVERRIDE {
139 // out->append("{\"foo\":1}");
140 // }
141 // private:
142 // DISALLOW_COPY_AND_ASSIGN(MyData);
143 // };
144 //
145 // scoped_ptr<MyData> data(new MyData());
146 // TRACE_EVENT1("foo", "bar", "data",
147 // data.PassAs<base::debug::ConvertableToTraceFormat>());
148 //
149 // The trace framework will take ownership if the passed pointer and it will
150 // be free'd when the trace buffer is flushed.
151 //
152 // Note, we only do the conversion when the buffer is flushed, so the provided
153 // data object should not be modified after it's passed to the trace framework.
154 //
155 //
126 // Thread Safety: 156 // Thread Safety:
127 // A thread safe singleton and mutex are used for thread safety. Category 157 // A thread safe singleton and mutex are used for thread safety. Category
128 // enabled flags are used to limit the performance impact when the system 158 // enabled flags are used to limit the performance impact when the system
129 // is not enabled. 159 // is not enabled.
130 // 160 //
131 // TRACE_EVENT macros first cache a pointer to a category. The categories are 161 // TRACE_EVENT macros first cache a pointer to a category. The categories are
132 // statically allocated and safe at all times, even after exit. Fetching a 162 // statically allocated and safe at all times, even after exit. Fetching a
133 // category is protected by the TraceLog::lock_. Multiple threads initializing 163 // category is protected by the TraceLog::lock_. Multiple threads initializing
134 // the static variable is safe, as they will be serialized by the lock and 164 // the static variable is safe, as they will be serialized by the lock and
135 // multiple calls will return the same pointer to the category. 165 // multiple calls will return the same pointer to the category.
(...skipping 656 matching lines...) Expand 10 before | Expand all | Expand 10 after
792 TRACE_EVENT_FLAG_SCOPE_OFFSET | (TRACE_EVENT_FLAG_SCOPE_OFFSET << 1))) 822 TRACE_EVENT_FLAG_SCOPE_OFFSET | (TRACE_EVENT_FLAG_SCOPE_OFFSET << 1)))
793 823
794 // Type values for identifying types in the TraceValue union. 824 // Type values for identifying types in the TraceValue union.
795 #define TRACE_VALUE_TYPE_BOOL (static_cast<unsigned char>(1)) 825 #define TRACE_VALUE_TYPE_BOOL (static_cast<unsigned char>(1))
796 #define TRACE_VALUE_TYPE_UINT (static_cast<unsigned char>(2)) 826 #define TRACE_VALUE_TYPE_UINT (static_cast<unsigned char>(2))
797 #define TRACE_VALUE_TYPE_INT (static_cast<unsigned char>(3)) 827 #define TRACE_VALUE_TYPE_INT (static_cast<unsigned char>(3))
798 #define TRACE_VALUE_TYPE_DOUBLE (static_cast<unsigned char>(4)) 828 #define TRACE_VALUE_TYPE_DOUBLE (static_cast<unsigned char>(4))
799 #define TRACE_VALUE_TYPE_POINTER (static_cast<unsigned char>(5)) 829 #define TRACE_VALUE_TYPE_POINTER (static_cast<unsigned char>(5))
800 #define TRACE_VALUE_TYPE_STRING (static_cast<unsigned char>(6)) 830 #define TRACE_VALUE_TYPE_STRING (static_cast<unsigned char>(6))
801 #define TRACE_VALUE_TYPE_COPY_STRING (static_cast<unsigned char>(7)) 831 #define TRACE_VALUE_TYPE_COPY_STRING (static_cast<unsigned char>(7))
832 #define TRACE_VALUE_TYPE_CONVERTABLE (static_cast<unsigned char>(8))
802 833
803 // Enum reflecting the scope of an INSTANT event. Must fit within 834 // Enum reflecting the scope of an INSTANT event. Must fit within
804 // TRACE_EVENT_FLAG_SCOPE_MASK. 835 // TRACE_EVENT_FLAG_SCOPE_MASK.
805 #define TRACE_EVENT_SCOPE_GLOBAL (static_cast<unsigned char>(0 << 3)) 836 #define TRACE_EVENT_SCOPE_GLOBAL (static_cast<unsigned char>(0 << 3))
806 #define TRACE_EVENT_SCOPE_PROCESS (static_cast<unsigned char>(1 << 3)) 837 #define TRACE_EVENT_SCOPE_PROCESS (static_cast<unsigned char>(1 << 3))
807 #define TRACE_EVENT_SCOPE_THREAD (static_cast<unsigned char>(2 << 3)) 838 #define TRACE_EVENT_SCOPE_THREAD (static_cast<unsigned char>(2 << 3))
808 839
809 #define TRACE_EVENT_SCOPE_NAME_GLOBAL ('g') 840 #define TRACE_EVENT_SCOPE_NAME_GLOBAL ('g')
810 #define TRACE_EVENT_SCOPE_NAME_PROCESS ('p') 841 #define TRACE_EVENT_SCOPE_NAME_PROCESS ('p')
811 #define TRACE_EVENT_SCOPE_NAME_THREAD ('t') 842 #define TRACE_EVENT_SCOPE_NAME_THREAD ('t')
(...skipping 87 matching lines...) Expand 10 before | Expand all | Expand 10 after
899 private: 930 private:
900 const char* str_; 931 const char* str_;
901 }; 932 };
902 933
903 // Define SetTraceValue for each allowed type. It stores the type and 934 // Define SetTraceValue for each allowed type. It stores the type and
904 // value in the return arguments. This allows this API to avoid declaring any 935 // value in the return arguments. This allows this API to avoid declaring any
905 // structures so that it is portable to third_party libraries. 936 // structures so that it is portable to third_party libraries.
906 #define INTERNAL_DECLARE_SET_TRACE_VALUE(actual_type, \ 937 #define INTERNAL_DECLARE_SET_TRACE_VALUE(actual_type, \
907 union_member, \ 938 union_member, \
908 value_type_id) \ 939 value_type_id) \
909 static inline void SetTraceValue(actual_type arg, \ 940 static inline void SetTraceValue( \
910 unsigned char* type, \ 941 actual_type arg, \
911 unsigned long long* value) { \ 942 unsigned char* type, \
943 unsigned long long* value) { \
912 TraceValueUnion type_value; \ 944 TraceValueUnion type_value; \
913 type_value.union_member = arg; \ 945 type_value.union_member = arg; \
914 *type = value_type_id; \ 946 *type = value_type_id; \
915 *value = type_value.as_uint; \ 947 *value = type_value.as_uint; \
916 } 948 }
917 // Simpler form for int types that can be safely casted. 949 // Simpler form for int types that can be safely casted.
918 #define INTERNAL_DECLARE_SET_TRACE_VALUE_INT(actual_type, \ 950 #define INTERNAL_DECLARE_SET_TRACE_VALUE_INT(actual_type, \
919 value_type_id) \ 951 value_type_id) \
920 static inline void SetTraceValue(actual_type arg, \ 952 static inline void SetTraceValue( \
921 unsigned char* type, \ 953 actual_type arg, \
922 unsigned long long* value) { \ 954 unsigned char* type, \
955 unsigned long long* value) { \
923 *type = value_type_id; \ 956 *type = value_type_id; \
924 *value = static_cast<unsigned long long>(arg); \ 957 *value = static_cast<unsigned long long>(arg); \
925 } 958 }
926 959
927 INTERNAL_DECLARE_SET_TRACE_VALUE_INT(unsigned long long, TRACE_VALUE_TYPE_UINT) 960 INTERNAL_DECLARE_SET_TRACE_VALUE_INT(unsigned long long, TRACE_VALUE_TYPE_UINT)
928 INTERNAL_DECLARE_SET_TRACE_VALUE_INT(unsigned long, TRACE_VALUE_TYPE_UINT) 961 INTERNAL_DECLARE_SET_TRACE_VALUE_INT(unsigned long, TRACE_VALUE_TYPE_UINT)
929 INTERNAL_DECLARE_SET_TRACE_VALUE_INT(unsigned int, TRACE_VALUE_TYPE_UINT) 962 INTERNAL_DECLARE_SET_TRACE_VALUE_INT(unsigned int, TRACE_VALUE_TYPE_UINT)
930 INTERNAL_DECLARE_SET_TRACE_VALUE_INT(unsigned short, TRACE_VALUE_TYPE_UINT) 963 INTERNAL_DECLARE_SET_TRACE_VALUE_INT(unsigned short, TRACE_VALUE_TYPE_UINT)
931 INTERNAL_DECLARE_SET_TRACE_VALUE_INT(unsigned char, TRACE_VALUE_TYPE_UINT) 964 INTERNAL_DECLARE_SET_TRACE_VALUE_INT(unsigned char, TRACE_VALUE_TYPE_UINT)
932 INTERNAL_DECLARE_SET_TRACE_VALUE_INT(long long, TRACE_VALUE_TYPE_INT) 965 INTERNAL_DECLARE_SET_TRACE_VALUE_INT(long long, TRACE_VALUE_TYPE_INT)
(...skipping 29 matching lines...) Expand all
962 // pointers to the internal c_str and pass through to the tracing API, 995 // pointers to the internal c_str and pass through to the tracing API,
963 // the arg_values must live throughout these procedures. 996 // the arg_values must live throughout these procedures.
964 997
965 static inline void AddTraceEventWithThreadIdAndTimestamp( 998 static inline void AddTraceEventWithThreadIdAndTimestamp(
966 char phase, 999 char phase,
967 const unsigned char* category_enabled, 1000 const unsigned char* category_enabled,
968 const char* name, 1001 const char* name,
969 unsigned long long id, 1002 unsigned long long id,
970 int thread_id, 1003 int thread_id,
971 const base::TimeTicks& timestamp, 1004 const base::TimeTicks& timestamp,
1005 unsigned char flags,
1006 const char* arg1_name,
1007 scoped_ptr<base::debug::ConvertableToTraceFormat> arg1_val) {
1008 const int num_args = 1;
1009 unsigned char arg_types[1] = { TRACE_VALUE_TYPE_CONVERTABLE };
1010 scoped_ptr<base::debug::ConvertableToTraceFormat> convertable_values[1];
1011 convertable_values[0].reset(arg1_val.release());
1012
1013 TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_THREAD_ID_AND_TIMESTAMP(
1014 phase, category_enabled, name, id, thread_id, timestamp,
1015 num_args, &arg1_name, arg_types, NULL, convertable_values, flags);
1016 }
1017
1018 static inline void AddTraceEvent(
1019 char phase,
1020 const unsigned char* category_enabled,
1021 const char* name,
1022 unsigned long long id,
1023 unsigned char flags,
1024 const char* arg1_name,
1025 scoped_ptr<base::debug::ConvertableToTraceFormat> arg1_val) {
1026 int thread_id = static_cast<int>(base::PlatformThread::CurrentId());
1027 base::TimeTicks now = base::TimeTicks::NowFromSystemTraceTime();
1028 AddTraceEventWithThreadIdAndTimestamp(phase, category_enabled, name, id,
1029 thread_id, now, flags, arg1_name,
1030 arg1_val.Pass());
1031 }
1032
1033 static inline void AddTraceEventWithThreadIdAndTimestamp(
1034 char phase,
1035 const unsigned char* category_enabled,
1036 const char* name,
1037 unsigned long long id,
1038 int thread_id,
1039 const base::TimeTicks& timestamp,
1040 unsigned char flags,
1041 const char* arg1_name,
1042 scoped_ptr<base::debug::ConvertableToTraceFormat> arg1_val,
1043 const char* arg2_name,
1044 scoped_ptr<base::debug::ConvertableToTraceFormat> arg2_val) {
1045 const int num_args = 2;
1046 unsigned char arg_types[2] =
1047 { TRACE_VALUE_TYPE_CONVERTABLE, TRACE_VALUE_TYPE_CONVERTABLE };
1048 scoped_ptr<base::debug::ConvertableToTraceFormat> convertable_values[2];
1049 convertable_values[0].reset(arg1_val.release());
1050 convertable_values[1].reset(arg2_val.release());
1051
1052 TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_THREAD_ID_AND_TIMESTAMP(
1053 phase, category_enabled, name, id, thread_id, timestamp,
1054 num_args, &arg1_name, arg_types, NULL, convertable_values, flags);
1055 }
1056
1057 static inline void AddTraceEvent(
1058 char phase,
1059 const unsigned char* category_enabled,
1060 const char* name,
1061 unsigned long long id,
1062 unsigned char flags,
1063 const char* arg1_name,
1064 scoped_ptr<base::debug::ConvertableToTraceFormat> arg1_val,
1065 const char* arg2_name,
1066 scoped_ptr<base::debug::ConvertableToTraceFormat> arg2_val) {
1067 int thread_id = static_cast<int>(base::PlatformThread::CurrentId());
1068 base::TimeTicks now = base::TimeTicks::NowFromSystemTraceTime();
1069 AddTraceEventWithThreadIdAndTimestamp(phase, category_enabled, name, id,
1070 thread_id, now, flags,
1071 arg1_name, arg1_val.Pass(),
1072 arg2_name, arg2_val.Pass());
1073 }
1074
1075 static inline void AddTraceEventWithThreadIdAndTimestamp(
1076 char phase,
1077 const unsigned char* category_enabled,
1078 const char* name,
1079 unsigned long long id,
1080 int thread_id,
1081 const base::TimeTicks& timestamp,
972 unsigned char flags) { 1082 unsigned char flags) {
973 TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_THREAD_ID_AND_TIMESTAMP( 1083 TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_THREAD_ID_AND_TIMESTAMP(
974 phase, category_enabled, name, id, thread_id, timestamp, 1084 phase, category_enabled, name, id, thread_id, timestamp,
975 kZeroNumArgs, NULL, NULL, NULL, flags); 1085 kZeroNumArgs, NULL, NULL, NULL, NULL, flags);
976 } 1086 }
977 1087
978 static inline void AddTraceEvent(char phase, 1088 static inline void AddTraceEvent(char phase,
979 const unsigned char* category_enabled, 1089 const unsigned char* category_enabled,
980 const char* name, 1090 const char* name,
981 unsigned long long id, 1091 unsigned long long id,
982 unsigned char flags) { 1092 unsigned char flags) {
983 int thread_id = static_cast<int>(base::PlatformThread::CurrentId()); 1093 int thread_id = static_cast<int>(base::PlatformThread::CurrentId());
984 base::TimeTicks now = base::TimeTicks::NowFromSystemTraceTime(); 1094 base::TimeTicks now = base::TimeTicks::NowFromSystemTraceTime();
985 AddTraceEventWithThreadIdAndTimestamp(phase, category_enabled, name, id, 1095 AddTraceEventWithThreadIdAndTimestamp(phase, category_enabled, name, id,
(...skipping 10 matching lines...) Expand all
996 const base::TimeTicks& timestamp, 1106 const base::TimeTicks& timestamp,
997 unsigned char flags, 1107 unsigned char flags,
998 const char* arg1_name, 1108 const char* arg1_name,
999 const ARG1_TYPE& arg1_val) { 1109 const ARG1_TYPE& arg1_val) {
1000 const int num_args = 1; 1110 const int num_args = 1;
1001 unsigned char arg_types[1]; 1111 unsigned char arg_types[1];
1002 unsigned long long arg_values[1]; 1112 unsigned long long arg_values[1];
1003 SetTraceValue(arg1_val, &arg_types[0], &arg_values[0]); 1113 SetTraceValue(arg1_val, &arg_types[0], &arg_values[0]);
1004 TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_THREAD_ID_AND_TIMESTAMP( 1114 TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_THREAD_ID_AND_TIMESTAMP(
1005 phase, category_enabled, name, id, thread_id, timestamp, 1115 phase, category_enabled, name, id, thread_id, timestamp,
1006 num_args, &arg1_name, arg_types, arg_values, flags); 1116 num_args, &arg1_name, arg_types, arg_values, NULL, flags);
1007 } 1117 }
1008 1118
1009 template<class ARG1_TYPE> 1119 template<class ARG1_TYPE>
1010 static inline void AddTraceEvent(char phase, 1120 static inline void AddTraceEvent(char phase,
1011 const unsigned char* category_enabled, 1121 const unsigned char* category_enabled,
1012 const char* name, 1122 const char* name,
1013 unsigned long long id, 1123 unsigned long long id,
1014 unsigned char flags, 1124 unsigned char flags,
1015 const char* arg1_name, 1125 const char* arg1_name,
1016 const ARG1_TYPE& arg1_val) { 1126 const ARG1_TYPE& arg1_val) {
(...skipping 18 matching lines...) Expand all
1035 const char* arg2_name, 1145 const char* arg2_name,
1036 const ARG2_TYPE& arg2_val) { 1146 const ARG2_TYPE& arg2_val) {
1037 const int num_args = 2; 1147 const int num_args = 2;
1038 const char* arg_names[2] = { arg1_name, arg2_name }; 1148 const char* arg_names[2] = { arg1_name, arg2_name };
1039 unsigned char arg_types[2]; 1149 unsigned char arg_types[2];
1040 unsigned long long arg_values[2]; 1150 unsigned long long arg_values[2];
1041 SetTraceValue(arg1_val, &arg_types[0], &arg_values[0]); 1151 SetTraceValue(arg1_val, &arg_types[0], &arg_values[0]);
1042 SetTraceValue(arg2_val, &arg_types[1], &arg_values[1]); 1152 SetTraceValue(arg2_val, &arg_types[1], &arg_values[1]);
1043 TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_THREAD_ID_AND_TIMESTAMP( 1153 TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_THREAD_ID_AND_TIMESTAMP(
1044 phase, category_enabled, name, id, thread_id, timestamp, 1154 phase, category_enabled, name, id, thread_id, timestamp,
1045 num_args, arg_names, arg_types, arg_values, flags); 1155 num_args, arg_names, arg_types, arg_values, NULL, flags);
1046 } 1156 }
1047 1157
1048 template<class ARG1_TYPE, class ARG2_TYPE> 1158 template<class ARG1_TYPE, class ARG2_TYPE>
1049 static inline void AddTraceEvent(char phase, 1159 static inline void AddTraceEvent(char phase,
1050 const unsigned char* category_enabled, 1160 const unsigned char* category_enabled,
1051 const char* name, 1161 const char* name,
1052 unsigned long long id, 1162 unsigned long long id,
1053 unsigned char flags, 1163 unsigned char flags,
1054 const char* arg1_name, 1164 const char* arg1_name,
1055 const ARG1_TYPE& arg1_val, 1165 const ARG1_TYPE& arg1_val,
(...skipping 25 matching lines...) Expand all
1081 1191
1082 private: 1192 private:
1083 // Add the end event if the category is still enabled. 1193 // Add the end event if the category is still enabled.
1084 void AddEventIfEnabled() { 1194 void AddEventIfEnabled() {
1085 // Only called when p_data_ is non-null. 1195 // Only called when p_data_ is non-null.
1086 if (*p_data_->category_enabled) { 1196 if (*p_data_->category_enabled) {
1087 TRACE_EVENT_API_ADD_TRACE_EVENT( 1197 TRACE_EVENT_API_ADD_TRACE_EVENT(
1088 TRACE_EVENT_PHASE_END, 1198 TRACE_EVENT_PHASE_END,
1089 p_data_->category_enabled, 1199 p_data_->category_enabled,
1090 p_data_->name, kNoEventId, 1200 p_data_->name, kNoEventId,
1091 kZeroNumArgs, NULL, NULL, NULL, 1201 kZeroNumArgs, NULL, NULL, NULL, NULL,
1092 TRACE_EVENT_FLAG_NONE); 1202 TRACE_EVENT_FLAG_NONE);
1093 } 1203 }
1094 } 1204 }
1095 1205
1096 // This Data struct workaround is to avoid initializing all the members 1206 // This Data struct workaround is to avoid initializing all the members
1097 // in Data during construction of this object, since this object is always 1207 // in Data during construction of this object, since this object is always
1098 // constructed, even when tracing is disabled. If the members of Data were 1208 // constructed, even when tracing is disabled. If the members of Data were
1099 // members of this class instead, compiler warnings occur about potential 1209 // members of this class instead, compiler warnings occur about potential
1100 // uninitialized accesses. 1210 // uninitialized accesses.
1101 struct Data { 1211 struct Data {
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after
1155 const char* name_; 1265 const char* name_;
1156 IDType id_; 1266 IDType id_;
1157 1267
1158 DISALLOW_COPY_AND_ASSIGN(TraceScopedTrackableObject); 1268 DISALLOW_COPY_AND_ASSIGN(TraceScopedTrackableObject);
1159 }; 1269 };
1160 1270
1161 } // namespace debug 1271 } // namespace debug
1162 } // namespace base 1272 } // namespace base
1163 1273
1164 #endif /* BASE_DEBUG_TRACE_EVENT_H_ */ 1274 #endif /* BASE_DEBUG_TRACE_EVENT_H_ */
OLDNEW
« no previous file with comments | « no previous file | base/debug/trace_event_impl.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698