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

Side by Side Diff: common/tsmon/metric/metric.go

Issue 2123853002: Added unit annotation supports onto tsmon in go. (Closed) Base URL: https://chromium.googlesource.com/external/github.com/luci/luci-go@master
Patch Set: Specify the unit of logdog/collector/subscription/processing_time_ms with types.Milliseconds Created 4 years, 5 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 | « common/tsmon/flush_test.go ('k') | common/tsmon/metric/metric_test.go » ('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 2015 The LUCI Authors. All rights reserved. 1 // Copyright 2015 The LUCI Authors. All rights reserved.
2 // Use of this source code is governed under the Apache License, Version 2.0 2 // Use of this source code is governed under the Apache License, Version 2.0
3 // that can be found in the LICENSE file. 3 // that can be found in the LICENSE file.
4 4
5 // Package metric is the API for defining metrics and updating their values. 5 // Package metric is the API for defining metrics and updating their values.
6 // 6 //
7 // When you define a metric you must also define the names and types of any 7 // When you define a metric you must also define the names and types of any
8 // fields on that metric. It is an error to define two metrics with the same 8 // fields on that metric. It is an error to define two metrics with the same
9 // name (this will cause a panic). 9 // name (this will cause a panic).
10 // 10 //
(...skipping 119 matching lines...) Expand 10 before | Expand all | Expand 10 after
130 // values can only be set from a callback. Use tsmon.RegisterCallback to add a 130 // values can only be set from a callback. Use tsmon.RegisterCallback to add a
131 // callback to set the metric's values. 131 // callback to set the metric's values.
132 type CallbackDistribution interface { 132 type CallbackDistribution interface {
133 types.DistributionMetric 133 types.DistributionMetric
134 134
135 Set(ctx context.Context, d *distribution.Distribution, fieldVals ...inte rface{}) error 135 Set(ctx context.Context, d *distribution.Distribution, fieldVals ...inte rface{}) error
136 } 136 }
137 137
138 // NewInt returns a new non-cumulative integer gauge metric. This will panic if 138 // NewInt returns a new non-cumulative integer gauge metric. This will panic if
139 // another metric already exists with this name. 139 // another metric already exists with this name.
140 func NewInt(name string, description string, fields ...field.Field) Int { 140 func NewInt(name string, description string, metadata types.MetricMetadata, fiel ds ...field.Field) Int {
141 » return NewIntIn(context.Background(), name, description, fields...) 141 » return NewIntIn(context.Background(), name, description, metadata, field s...)
142 } 142 }
143 143
144 // NewIntIn is like NewInt but registers in a given context. 144 // NewIntIn is like NewInt but registers in a given context.
145 func NewIntIn(c context.Context, name string, description string, fields ...fiel d.Field) Int { 145 func NewIntIn(c context.Context, name string, description string, metadata types .MetricMetadata, fields ...field.Field) Int {
146 » m := &intMetric{metric{MetricInfo: types.MetricInfo{ 146 » m := &intMetric{metric{
147 » » Name: name, 147 » » MetricInfo: types.MetricInfo{
148 » » Description: description, 148 » » » Name: name,
149 » » Fields: fields, 149 » » » Description: description,
150 » » ValueType: types.NonCumulativeIntType, 150 » » » Fields: fields,
151 » }}} 151 » » » ValueType: types.NonCumulativeIntType,
152 » » },
153 » » MetricMetadata: metadata,
154 » }}
152 tsmon.Register(c, m) 155 tsmon.Register(c, m)
153 return m 156 return m
154 } 157 }
155 158
156 // NewCounter returns a new cumulative integer metric. This will panic if 159 // NewCounter returns a new cumulative integer metric. This will panic if
157 // another metric already exists with this name. 160 // another metric already exists with this name.
158 func NewCounter(name string, description string, fields ...field.Field) Counter { 161 func NewCounter(name string, description string, metadata types.MetricMetadata, fields ...field.Field) Counter {
159 » return NewCounterIn(context.Background(), name, description, fields...) 162 » return NewCounterIn(context.Background(), name, description, metadata, f ields...)
160 } 163 }
161 164
162 // NewCounterIn is like NewCounter but registers in a given context. 165 // NewCounterIn is like NewCounter but registers in a given context.
163 func NewCounterIn(c context.Context, name string, description string, fields ... field.Field) Counter { 166 func NewCounterIn(c context.Context, name string, description string, metadata t ypes.MetricMetadata, fields ...field.Field) Counter {
164 m := &counter{intMetric{metric{MetricInfo: types.MetricInfo{ 167 m := &counter{intMetric{metric{MetricInfo: types.MetricInfo{
165 Name: name, 168 Name: name,
166 Description: description, 169 Description: description,
167 Fields: fields, 170 Fields: fields,
168 ValueType: types.CumulativeIntType, 171 ValueType: types.CumulativeIntType,
169 }}}} 172 }}}}
170 tsmon.Register(c, m) 173 tsmon.Register(c, m)
171 return m 174 return m
172 } 175 }
173 176
174 // NewFloat returns a new non-cumulative floating-point gauge metric. This will 177 // NewFloat returns a new non-cumulative floating-point gauge metric. This will
175 // panic if another metric already exists with this name. 178 // panic if another metric already exists with this name.
176 func NewFloat(name string, description string, fields ...field.Field) Float { 179 func NewFloat(name string, description string, metadata types.MetricMetadata, fi elds ...field.Field) Float {
177 » return NewFloatIn(context.Background(), name, description, fields...) 180 » return NewFloatIn(context.Background(), name, description, metadata, fie lds...)
178 } 181 }
179 182
180 // NewFloatIn is like NewFloat but registers in a given context. 183 // NewFloatIn is like NewFloat but registers in a given context.
181 func NewFloatIn(c context.Context, name string, description string, fields ...fi eld.Field) Float { 184 func NewFloatIn(c context.Context, name string, description string, metadata typ es.MetricMetadata, fields ...field.Field) Float {
182 m := &floatMetric{metric{MetricInfo: types.MetricInfo{ 185 m := &floatMetric{metric{MetricInfo: types.MetricInfo{
183 Name: name, 186 Name: name,
184 Description: description, 187 Description: description,
185 Fields: fields, 188 Fields: fields,
186 ValueType: types.NonCumulativeFloatType, 189 ValueType: types.NonCumulativeFloatType,
187 }}} 190 }}}
188 tsmon.Register(c, m) 191 tsmon.Register(c, m)
189 return m 192 return m
190 } 193 }
191 194
192 // NewFloatCounter returns a new cumulative floating-point metric. This will 195 // NewFloatCounter returns a new cumulative floating-point metric. This will
193 // panic if another metric already exists with this name. 196 // panic if another metric already exists with this name.
194 func NewFloatCounter(name string, description string, fields ...field.Field) Flo atCounter { 197 func NewFloatCounter(name string, description string, metadata types.MetricMetad ata, fields ...field.Field) FloatCounter {
195 » return NewFloatCounterIn(context.Background(), name, description, fields ...) 198 » return NewFloatCounterIn(context.Background(), name, description, metada ta, fields...)
196 } 199 }
197 200
198 // NewFloatCounterIn is like NewFloatCounter but registers in a given context. 201 // NewFloatCounterIn is like NewFloatCounter but registers in a given context.
199 func NewFloatCounterIn(c context.Context, name string, description string, field s ...field.Field) FloatCounter { 202 func NewFloatCounterIn(c context.Context, name string, description string, metad ata types.MetricMetadata, fields ...field.Field) FloatCounter {
200 m := &floatCounter{floatMetric{metric{MetricInfo: types.MetricInfo{ 203 m := &floatCounter{floatMetric{metric{MetricInfo: types.MetricInfo{
201 Name: name, 204 Name: name,
202 Description: description, 205 Description: description,
203 Fields: fields, 206 Fields: fields,
204 ValueType: types.CumulativeFloatType, 207 ValueType: types.CumulativeFloatType,
205 }}}} 208 }}}}
206 tsmon.Register(c, m) 209 tsmon.Register(c, m)
207 return m 210 return m
208 } 211 }
209 212
210 // NewString returns a new string-valued metric. This will panic if another 213 // NewString returns a new string-valued metric. This will panic if another
211 // metric already exists with this name. 214 // metric already exists with this name.
212 func NewString(name string, description string, fields ...field.Field) String { 215 func NewString(name string, description string, metadata types.MetricMetadata, f ields ...field.Field) String {
213 » return NewStringIn(context.Background(), name, description, fields...) 216 » return NewStringIn(context.Background(), name, description, metadata, fi elds...)
214 } 217 }
215 218
216 // NewStringIn is like NewString but registers in a given context. 219 // NewStringIn is like NewString but registers in a given context.
217 func NewStringIn(c context.Context, name string, description string, fields ...f ield.Field) String { 220 func NewStringIn(c context.Context, name string, description string, metadata ty pes.MetricMetadata, fields ...field.Field) String {
218 m := &stringMetric{metric{MetricInfo: types.MetricInfo{ 221 m := &stringMetric{metric{MetricInfo: types.MetricInfo{
219 Name: name, 222 Name: name,
220 Description: description, 223 Description: description,
221 Fields: fields, 224 Fields: fields,
222 ValueType: types.StringType, 225 ValueType: types.StringType,
223 }}} 226 }}}
224 tsmon.Register(c, m) 227 tsmon.Register(c, m)
225 return m 228 return m
226 } 229 }
227 230
228 // NewBool returns a new bool-valued metric. This will panic if another 231 // NewBool returns a new bool-valued metric. This will panic if another
229 // metric already exists with this name. 232 // metric already exists with this name.
230 func NewBool(name string, description string, fields ...field.Field) Bool { 233 func NewBool(name string, description string, metadata types.MetricMetadata, fie lds ...field.Field) Bool {
231 » return NewBoolIn(context.Background(), name, description, fields...) 234 » return NewBoolIn(context.Background(), name, description, metadata, fiel ds...)
232 } 235 }
233 236
234 // NewBoolIn is like NewBool but registers in a given context. 237 // NewBoolIn is like NewBool but registers in a given context.
235 func NewBoolIn(c context.Context, name string, description string, fields ...fie ld.Field) Bool { 238 func NewBoolIn(c context.Context, name string, description string, metadata type s.MetricMetadata, fields ...field.Field) Bool {
236 m := &boolMetric{metric{MetricInfo: types.MetricInfo{ 239 m := &boolMetric{metric{MetricInfo: types.MetricInfo{
237 Name: name, 240 Name: name,
238 Description: description, 241 Description: description,
239 Fields: fields, 242 Fields: fields,
240 ValueType: types.BoolType, 243 ValueType: types.BoolType,
241 }}} 244 }}}
242 tsmon.Register(c, m) 245 tsmon.Register(c, m)
243 return m 246 return m
244 } 247 }
245 248
246 // NewCumulativeDistribution returns a new cumulative-distribution-valued 249 // NewCumulativeDistribution returns a new cumulative-distribution-valued
247 // metric. This will panic if another metric already exists with this name. 250 // metric. This will panic if another metric already exists with this name.
248 func NewCumulativeDistribution(name string, description string, bucketer *distri bution.Bucketer, fields ...field.Field) CumulativeDistribution { 251 func NewCumulativeDistribution(name string, description string, metadata types.M etricMetadata, bucketer *distribution.Bucketer, fields ...field.Field) Cumulativ eDistribution {
249 » return NewCumulativeDistributionIn(context.Background(), name, descripti on, bucketer, fields...) 252 » return NewCumulativeDistributionIn(context.Background(), name, descripti on, metadata, bucketer, fields...)
250 } 253 }
251 254
252 // NewCumulativeDistributionIn is like NewCumulativeDistribution but registers i n a given context. 255 // NewCumulativeDistributionIn is like NewCumulativeDistribution but registers i n a given context.
253 func NewCumulativeDistributionIn(c context.Context, name string, description str ing, bucketer *distribution.Bucketer, fields ...field.Field) CumulativeDistribut ion { 256 func NewCumulativeDistributionIn(c context.Context, name string, description str ing, metadata types.MetricMetadata, bucketer *distribution.Bucketer, fields ...f ield.Field) CumulativeDistribution {
254 m := &cumulativeDistributionMetric{ 257 m := &cumulativeDistributionMetric{
255 nonCumulativeDistributionMetric{ 258 nonCumulativeDistributionMetric{
256 metric: metric{MetricInfo: types.MetricInfo{ 259 metric: metric{MetricInfo: types.MetricInfo{
257 Name: name, 260 Name: name,
258 Description: description, 261 Description: description,
259 Fields: fields, 262 Fields: fields,
260 ValueType: types.CumulativeDistributionType, 263 ValueType: types.CumulativeDistributionType,
261 }}, 264 }},
262 bucketer: bucketer, 265 bucketer: bucketer,
263 }, 266 },
264 } 267 }
265 tsmon.Register(c, m) 268 tsmon.Register(c, m)
266 return m 269 return m
267 } 270 }
268 271
269 // NewNonCumulativeDistribution returns a new non-cumulative-distribution-valued 272 // NewNonCumulativeDistribution returns a new non-cumulative-distribution-valued
270 // metric. This will panic if another metric already exists with this name. 273 // metric. This will panic if another metric already exists with this name.
271 func NewNonCumulativeDistribution(name string, description string, bucketer *dis tribution.Bucketer, fields ...field.Field) NonCumulativeDistribution { 274 func NewNonCumulativeDistribution(name string, description string, metadata type s.MetricMetadata, bucketer *distribution.Bucketer, fields ...field.Field) NonCum ulativeDistribution {
272 » return NewNonCumulativeDistributionIn(context.Background(), name, descri ption, bucketer, fields...) 275 » return NewNonCumulativeDistributionIn(context.Background(), name, descri ption, metadata, bucketer, fields...)
273 } 276 }
274 277
275 // NewNonCumulativeDistributionIn is like NewNonCumulativeDistribution but regis ters in a given context. 278 // NewNonCumulativeDistributionIn is like NewNonCumulativeDistribution but regis ters in a given context.
276 func NewNonCumulativeDistributionIn(c context.Context, name string, description string, bucketer *distribution.Bucketer, fields ...field.Field) NonCumulativeDis tribution { 279 func NewNonCumulativeDistributionIn(c context.Context, name string, description string, metadata types.MetricMetadata, bucketer *distribution.Bucketer, fields . ..field.Field) NonCumulativeDistribution {
277 m := &nonCumulativeDistributionMetric{ 280 m := &nonCumulativeDistributionMetric{
278 metric: metric{MetricInfo: types.MetricInfo{ 281 metric: metric{MetricInfo: types.MetricInfo{
279 Name: name, 282 Name: name,
280 Description: description, 283 Description: description,
281 Fields: fields, 284 Fields: fields,
282 ValueType: types.NonCumulativeDistributionType, 285 ValueType: types.NonCumulativeDistributionType,
283 }}, 286 }},
284 bucketer: bucketer, 287 bucketer: bucketer,
285 } 288 }
286 tsmon.Register(c, m) 289 tsmon.Register(c, m)
287 return m 290 return m
288 } 291 }
289 292
290 // NewCallbackInt returns a new integer metric whose value is populated by a 293 // NewCallbackInt returns a new integer metric whose value is populated by a
291 // callback at collection time. 294 // callback at collection time.
292 func NewCallbackInt(name string, description string, fields ...field.Field) Call backInt { 295 func NewCallbackInt(name string, description string, metadata types.MetricMetada ta, fields ...field.Field) CallbackInt {
293 » return NewInt(name, description, fields...) 296 » return NewInt(name, description, metadata, fields...)
294 } 297 }
295 298
296 // NewCallbackFloat returns a new float metric whose value is populated by a 299 // NewCallbackFloat returns a new float metric whose value is populated by a
297 // callback at collection time. 300 // callback at collection time.
298 func NewCallbackFloat(name string, description string, fields ...field.Field) Ca llbackFloat { 301 func NewCallbackFloat(name string, description string, metadata types.MetricMeta data, fields ...field.Field) CallbackFloat {
299 » return NewFloat(name, description, fields...) 302 » return NewFloat(name, description, metadata, fields...)
300 } 303 }
301 304
302 // NewCallbackString returns a new string metric whose value is populated by a 305 // NewCallbackString returns a new string metric whose value is populated by a
303 // callback at collection time. 306 // callback at collection time.
304 func NewCallbackString(name string, description string, fields ...field.Field) C allbackString { 307 func NewCallbackString(name string, description string, metadata types.MetricMet adata, fields ...field.Field) CallbackString {
305 » return NewString(name, description, fields...) 308 » return NewString(name, description, metadata, fields...)
306 } 309 }
307 310
308 // NewCallbackBool returns a new bool metric whose value is populated by a 311 // NewCallbackBool returns a new bool metric whose value is populated by a
309 // callback at collection time. 312 // callback at collection time.
310 func NewCallbackBool(name string, description string, fields ...field.Field) Cal lbackBool { 313 func NewCallbackBool(name string, description string, metadata types.MetricMetad ata, fields ...field.Field) CallbackBool {
311 » return NewBool(name, description, fields...) 314 » return NewBool(name, description, metadata, fields...)
312 } 315 }
313 316
314 // NewCallbackDistribution returns a new distribution metric whose value is 317 // NewCallbackDistribution returns a new distribution metric whose value is
315 // populated by a callback at collection time. 318 // populated by a callback at collection time.
316 func NewCallbackDistribution(name string, description string, bucketer *distribu tion.Bucketer, fields ...field.Field) CallbackDistribution { 319 func NewCallbackDistribution(name string, description string, metadata types.Met ricMetadata, bucketer *distribution.Bucketer, fields ...field.Field) CallbackDis tribution {
317 » return NewNonCumulativeDistribution(name, description, bucketer, fields. ..) 320 » return NewNonCumulativeDistribution(name, description, metadata, buckete r, fields...)
318 } 321 }
319 322
320 // NewCallbackIntIn is like NewCallbackInt but registers in a given context. 323 // NewCallbackIntIn is like NewCallbackInt but registers in a given context.
321 func NewCallbackIntIn(c context.Context, name string, description string, fields ...field.Field) CallbackInt { 324 func NewCallbackIntIn(c context.Context, name string, description string, metada ta types.MetricMetadata, fields ...field.Field) CallbackInt {
322 » return NewIntIn(c, name, description, fields...) 325 » return NewIntIn(c, name, description, metadata, fields...)
323 } 326 }
324 327
325 // NewCallbackFloatIn is like NewCallbackFloat but registers in a given context. 328 // NewCallbackFloatIn is like NewCallbackFloat but registers in a given context.
326 func NewCallbackFloatIn(c context.Context, name string, description string, fiel ds ...field.Field) CallbackFloat { 329 func NewCallbackFloatIn(c context.Context, name string, description string, meta data types.MetricMetadata, fields ...field.Field) CallbackFloat {
327 » return NewFloatIn(c, name, description, fields...) 330 » return NewFloatIn(c, name, description, metadata, fields...)
328 } 331 }
329 332
330 // NewCallbackStringIn is like NewCallbackString but registers in a given contex t. 333 // NewCallbackStringIn is like NewCallbackString but registers in a given contex t.
331 func NewCallbackStringIn(c context.Context, name string, description string, fie lds ...field.Field) CallbackString { 334 func NewCallbackStringIn(c context.Context, name string, description string, met adata types.MetricMetadata, fields ...field.Field) CallbackString {
332 » return NewStringIn(c, name, description, fields...) 335 » return NewStringIn(c, name, description, metadata, fields...)
333 } 336 }
334 337
335 // NewCallbackBoolIn is like NewCallbackBool but registers in a given context. 338 // NewCallbackBoolIn is like NewCallbackBool but registers in a given context.
336 func NewCallbackBoolIn(c context.Context, name string, description string, field s ...field.Field) CallbackBool { 339 func NewCallbackBoolIn(c context.Context, name string, description string, metad ata types.MetricMetadata, fields ...field.Field) CallbackBool {
337 » return NewBoolIn(c, name, description, fields...) 340 » return NewBoolIn(c, name, description, metadata, fields...)
338 } 341 }
339 342
340 // NewCallbackDistributionIn is like NewCallbackDistribution but registers in a given context. 343 // NewCallbackDistributionIn is like NewCallbackDistribution but registers in a given context.
341 func NewCallbackDistributionIn(c context.Context, name string, description strin g, bucketer *distribution.Bucketer, fields ...field.Field) CallbackDistribution { 344 func NewCallbackDistributionIn(c context.Context, name string, description strin g, metadata types.MetricMetadata, bucketer *distribution.Bucketer, fields ...fie ld.Field) CallbackDistribution {
342 » return NewNonCumulativeDistributionIn(c, name, description, bucketer, fi elds...) 345 » return NewNonCumulativeDistributionIn(c, name, description, metadata, bu cketer, fields...)
343 } 346 }
344 347
345 // genericGet is a convenience function that tries to get a metric value from 348 // genericGet is a convenience function that tries to get a metric value from
346 // the store and returns the zero value if it didn't exist. 349 // the store and returns the zero value if it didn't exist.
347 func (m *metric) genericGet(zero interface{}, c context.Context, fieldVals []int erface{}) (interface{}, error) { 350 func (m *metric) genericGet(zero interface{}, c context.Context, fieldVals []int erface{}) (interface{}, error) {
348 switch ret, err := tsmon.Store(c).Get(c, m, m.fixedResetTime, fieldVals) ; { 351 switch ret, err := tsmon.Store(c).Get(c, m, m.fixedResetTime, fieldVals) ; {
349 case err != nil: 352 case err != nil:
350 return zero, err 353 return zero, err
351 case ret == nil: 354 case ret == nil:
352 return zero, nil 355 return zero, nil
353 default: 356 default:
354 return ret, nil 357 return ret, nil
355 } 358 }
356 } 359 }
357 360
358 type metric struct { 361 type metric struct {
359 types.MetricInfo 362 types.MetricInfo
363 types.MetricMetadata
360 fixedResetTime time.Time 364 fixedResetTime time.Time
361 } 365 }
362 366
363 func (m *metric) Info() types.MetricInfo { return m.MetricInfo } 367 func (m *metric) Info() types.MetricInfo { return m.MetricInfo }
364 func (m *metric) SetFixedResetTime(t time.Time) { m.fixedResetTime = t } 368 func (m *metric) Metadata() types.MetricMetadata { return m.MetricMetadata }
369 func (m *metric) SetFixedResetTime(t time.Time) { m.fixedResetTime = t }
365 370
366 type intMetric struct{ metric } 371 type intMetric struct{ metric }
367 372
368 func (m *intMetric) Get(c context.Context, fieldVals ...interface{}) (int64, err or) { 373 func (m *intMetric) Get(c context.Context, fieldVals ...interface{}) (int64, err or) {
369 ret, err := m.genericGet(int64(0), c, fieldVals) 374 ret, err := m.genericGet(int64(0), c, fieldVals)
370 return ret.(int64), err 375 return ret.(int64), err
371 } 376 }
372 377
373 func (m *intMetric) Set(c context.Context, v int64, fieldVals ...interface{}) er ror { 378 func (m *intMetric) Set(c context.Context, v int64, fieldVals ...interface{}) er ror {
374 return tsmon.Store(c).Set(c, m, m.fixedResetTime, fieldVals, v) 379 return tsmon.Store(c).Set(c, m, m.fixedResetTime, fieldVals, v)
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after
435 return tsmon.Store(c).Set(c, m, m.fixedResetTime, fieldVals, v) 440 return tsmon.Store(c).Set(c, m, m.fixedResetTime, fieldVals, v)
436 } 441 }
437 442
438 type cumulativeDistributionMetric struct { 443 type cumulativeDistributionMetric struct {
439 nonCumulativeDistributionMetric 444 nonCumulativeDistributionMetric
440 } 445 }
441 446
442 func (m *cumulativeDistributionMetric) Add(c context.Context, v float64, fieldVa ls ...interface{}) error { 447 func (m *cumulativeDistributionMetric) Add(c context.Context, v float64, fieldVa ls ...interface{}) error {
443 return tsmon.Store(c).Incr(c, m, m.fixedResetTime, fieldVals, v) 448 return tsmon.Store(c).Incr(c, m, m.fixedResetTime, fieldVals, v)
444 } 449 }
OLDNEW
« no previous file with comments | « common/tsmon/flush_test.go ('k') | common/tsmon/metric/metric_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698