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

Side by Side Diff: tracing/tracing/value/histogram_test.html

Issue 2283213002: Rename Histogram.add() to addSample(). (Closed) Base URL: https://github.com/catapult-project/catapult.git@master
Patch Set: rebase Created 4 years, 3 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 <!DOCTYPE html> 1 <!DOCTYPE html>
2 <!-- 2 <!--
3 Copyright 2016 The Chromium Authors. All rights reserved. 3 Copyright 2016 The Chromium Authors. All rights reserved.
4 Use of this source code is governed by a BSD-style license that can be 4 Use of this source code is governed by a BSD-style license that can be
5 found in the LICENSE file. 5 found in the LICENSE file.
6 --> 6 -->
7 7
8 <link rel="import" href="/tracing/value/diagnostics/generic.html"> 8 <link rel="import" href="/tracing/value/diagnostics/generic.html">
9 <link rel="import" href="/tracing/value/histogram.html"> 9 <link rel="import" href="/tracing/value/histogram.html">
10 10
(...skipping 29 matching lines...) Expand all
40 40
41 test('significance', function() { 41 test('significance', function() {
42 assert.strictEqual( 42 assert.strictEqual(
43 new tr.v.Histogram(unitlessNumber).getDifferenceSignificance( 43 new tr.v.Histogram(unitlessNumber).getDifferenceSignificance(
44 new tr.v.Histogram(unitlessNumber)), tr.v.Significance.DONT_CARE); 44 new tr.v.Histogram(unitlessNumber)), tr.v.Significance.DONT_CARE);
45 45
46 var boundaries = tr.v.HistogramBinBoundaries.createLinear(0, 100, 10); 46 var boundaries = tr.v.HistogramBinBoundaries.createLinear(0, 100, 10);
47 var numericA = new tr.v.Histogram(unitlessNumber_sIB, boundaries); 47 var numericA = new tr.v.Histogram(unitlessNumber_sIB, boundaries);
48 var numericB = new tr.v.Histogram(unitlessNumber_sIB, boundaries); 48 var numericB = new tr.v.Histogram(unitlessNumber_sIB, boundaries);
49 for (var i = 0; i < 100; ++i) { 49 for (var i = 0; i < 100; ++i) {
50 numericA.add(i); 50 numericA.addSample(i);
51 numericB.add(i * 0.85); 51 numericB.addSample(i * 0.85);
52 } 52 }
53 53
54 assert.strictEqual(numericA.getDifferenceSignificance(numericB), 54 assert.strictEqual(numericA.getDifferenceSignificance(numericB),
55 tr.v.Significance.INSIGNIFICANT); 55 tr.v.Significance.INSIGNIFICANT);
56 assert.strictEqual(numericB.getDifferenceSignificance(numericA), 56 assert.strictEqual(numericB.getDifferenceSignificance(numericA),
57 tr.v.Significance.INSIGNIFICANT); 57 tr.v.Significance.INSIGNIFICANT);
58 assert.strictEqual(numericA.getDifferenceSignificance(numericB, 0.1), 58 assert.strictEqual(numericA.getDifferenceSignificance(numericB, 0.1),
59 tr.v.Significance.SIGNIFICANT); 59 tr.v.Significance.SIGNIFICANT);
60 assert.strictEqual(numericB.getDifferenceSignificance(numericA, 0.1), 60 assert.strictEqual(numericB.getDifferenceSignificance(numericA, 0.1),
61 tr.v.Significance.SIGNIFICANT); 61 tr.v.Significance.SIGNIFICANT);
62 }); 62 });
63 63
64 test('numericBasic', function() { 64 test('numericBasic', function() {
65 var n = new tr.v.Histogram(unitlessNumber, TEST_BOUNDARIES); 65 var n = new tr.v.Histogram(unitlessNumber, TEST_BOUNDARIES);
66 assert.equal(n.getBinForValue(250).range.min, 200); 66 assert.equal(n.getBinForValue(250).range.min, 200);
67 assert.equal(n.getBinForValue(250).range.max, 300); 67 assert.equal(n.getBinForValue(250).range.max, 300);
68 68
69 var dm0 = new tr.v.d.DiagnosticMap(); 69 n.addSample(-1, {foo: new tr.v.d.Generic('a')});
70 var dm1 = new tr.v.d.DiagnosticMap(); 70 n.addSample(0, {foo: new tr.v.d.Generic('b')});
71 var dm2 = new tr.v.d.DiagnosticMap(); 71 n.addSample(0, {foo: new tr.v.d.Generic('c')});
72 var dm3 = new tr.v.d.DiagnosticMap(); 72 n.addSample(500, {foo: new tr.v.d.Generic('c')});
73 var dm4 = new tr.v.d.DiagnosticMap(); 73 n.addSample(999, {foo: new tr.v.d.Generic('d')});
74 var dm5 = new tr.v.d.DiagnosticMap(); 74 n.addSample(1000, {foo: new tr.v.d.Generic('d')});
75 dm0.set('foo', new tr.v.d.Generic('a'));
76 dm1.set('foo', new tr.v.d.Generic('b'));
77 dm2.set('foo', new tr.v.d.Generic('c'));
78 dm3.set('foo', new tr.v.d.Generic('c'));
79 dm4.set('foo', new tr.v.d.Generic('d'));
80 dm5.set('foo', new tr.v.d.Generic('d'));
81
82 n.add(-1, dm0);
83 n.add(0, dm1);
84 n.add(0, dm2);
85 n.add(500, dm3);
86 n.add(999, dm4);
87 n.add(1000, dm5);
88 assert.equal(n.underflowBin.count, 1); 75 assert.equal(n.underflowBin.count, 1);
89 76
90 assert.equal(n.getBinForValue(0).count, 2); 77 assert.equal(n.getBinForValue(0).count, 2);
91 assert.deepEqual( 78 assert.deepEqual(
92 n.getBinForValue(0).diagnosticMaps.map(dm => dm.get('foo').value), 79 n.getBinForValue(0).diagnosticMaps.map(dm => dm.get('foo').value),
93 ['b', 'c']); 80 ['b', 'c']);
94 81
95 assert.equal(n.getBinForValue(500).count, 1); 82 assert.equal(n.getBinForValue(500).count, 1);
96 assert.equal(n.getBinForValue(999).count, 1); 83 assert.equal(n.getBinForValue(999).count, 1);
97 84
98 assert.equal(n.overflowBin.count, 1); 85 assert.equal(n.overflowBin.count, 1);
99 assert.equal(n.numValues, 6); 86 assert.equal(n.numValues, 6);
100 assert.closeTo(n.average, 416.3, 0.1); 87 assert.closeTo(n.average, 416.3, 0.1);
101 }); 88 });
102 89
103 test('numericNans', function() { 90 test('numericNans', function() {
104 var n = new tr.v.Histogram(unitlessNumber, TEST_BOUNDARIES); 91 var n = new tr.v.Histogram(unitlessNumber, TEST_BOUNDARIES);
105 92
106 var dm0 = tr.v.d.DiagnosticMap.fromObject({foo: new tr.v.d.Generic('b')}); 93 n.addSample(undefined, {foo: new tr.v.d.Generic('b')});
107 var dm1 = tr.v.d.DiagnosticMap.fromObject({'foo': new tr.v.d.Generic('c')}); 94 n.addSample(NaN, {'foo': new tr.v.d.Generic('c')});
108 95 n.addSample(undefined);
109 n.add(undefined, dm0); 96 n.addSample(NaN);
110 n.add(NaN, dm1);
111 n.add(undefined);
112 n.add(NaN);
113 97
114 assert.equal(n.numNans, 4); 98 assert.equal(n.numNans, 4);
115 assert.deepEqual(n.nanDiagnosticMaps.map(dm => dm.get('foo').value), 99 assert.deepEqual(n.nanDiagnosticMaps.map(dm => dm.get('foo').value),
116 ['b', 'c']); 100 ['b', 'c']);
117 101
118 var n2 = n.clone(); 102 var n2 = n.clone();
119 assert.instanceOf(n2.nanDiagnosticMaps[0], tr.v.d.DiagnosticMap); 103 assert.instanceOf(n2.nanDiagnosticMaps[0], tr.v.d.DiagnosticMap);
120 assert.instanceOf(n2.nanDiagnosticMaps[0].get('foo'), tr.v.d.Generic); 104 assert.instanceOf(n2.nanDiagnosticMaps[0].get('foo'), tr.v.d.Generic);
121 }); 105 });
122 106
123 test('addHistogramsValid', function() { 107 test('addHistogramsValid', function() {
124 var n0 = new tr.v.Histogram(unitlessNumber, TEST_BOUNDARIES); 108 var n0 = new tr.v.Histogram(unitlessNumber, TEST_BOUNDARIES);
125 var n1 = new tr.v.Histogram(unitlessNumber, TEST_BOUNDARIES); 109 var n1 = new tr.v.Histogram(unitlessNumber, TEST_BOUNDARIES);
126 110
127 var dm0 = tr.v.d.DiagnosticMap.fromObject({foo: new tr.v.d.Generic('a0')}); 111 n0.addSample(-1, {foo: new tr.v.d.Generic('a0')});
128 var dm1 = tr.v.d.DiagnosticMap.fromObject({foo: new tr.v.d.Generic('b0')}); 112 n0.addSample(0, {foo: new tr.v.d.Generic('b0')});
129 var dm2 = tr.v.d.DiagnosticMap.fromObject({foo: new tr.v.d.Generic('c0')}); 113 n0.addSample(0, {foo: new tr.v.d.Generic('c0')});
130 var dm3 = tr.v.d.DiagnosticMap.fromObject({foo: new tr.v.d.Generic('c0')}); 114 n0.addSample(500, {foo: new tr.v.d.Generic('c0')});
131 var dm4 = tr.v.d.DiagnosticMap.fromObject({foo: new tr.v.d.Generic('d0')}); 115 n0.addSample(1000, {foo: new tr.v.d.Generic('d0')});
132 var dm5 = tr.v.d.DiagnosticMap.fromObject({foo: new tr.v.d.Generic('e0')}); 116 n0.addSample(NaN, {foo: new tr.v.d.Generic('e0')});
133 var dm6 = tr.v.d.DiagnosticMap.fromObject({foo: new tr.v.d.Generic('a1')});
134 var dm7 = tr.v.d.DiagnosticMap.fromObject({foo: new tr.v.d.Generic('b1')});
135 var dm8 = tr.v.d.DiagnosticMap.fromObject({foo: new tr.v.d.Generic('c1')});
136 var dm9 = tr.v.d.DiagnosticMap.fromObject({foo: new tr.v.d.Generic('d1')});
137 var dm10 = tr.v.d.DiagnosticMap.fromObject({foo: new tr.v.d.Generic('d1')});
138 var dm11 = tr.v.d.DiagnosticMap.fromObject({foo: new tr.v.d.Generic('e1')});
139 117
140 n0.add(-1, dm0); 118 n1.addSample(-1, {foo: new tr.v.d.Generic('a1')});
141 n0.add(0, dm1); 119 n1.addSample(0, {foo: new tr.v.d.Generic('b1')});
142 n0.add(0, dm2); 120 n1.addSample(0, {foo: new tr.v.d.Generic('c1')});
143 n0.add(500, dm3); 121 n1.addSample(999, {foo: new tr.v.d.Generic('d1')});
144 n0.add(1000, dm4); 122 n1.addSample(1000, {foo: new tr.v.d.Generic('d1')});
145 n0.add(NaN, dm5); 123 n1.addSample(NaN, {foo: new tr.v.d.Generic('e1')});
146
147 n1.add(-1, dm6);
148 n1.add(0, dm7);
149 n1.add(0, dm8);
150 n1.add(999, dm9);
151 n1.add(1000, dm10);
152 n1.add(NaN, dm11);
153 124
154 n0.addNumeric(n1); 125 n0.addNumeric(n1);
155 126
156 assert.equal(n0.numNans, 2); 127 assert.equal(n0.numNans, 2);
157 assert.deepEqual(n0.nanDiagnosticMaps.map(dmd => dmd.get('foo').value), 128 assert.deepEqual(n0.nanDiagnosticMaps.map(dmd => dmd.get('foo').value),
158 ['e0', 'e1']); 129 ['e0', 'e1']);
159 130
160 assert.equal(n0.underflowBin.count, 2); 131 assert.equal(n0.underflowBin.count, 2);
161 assert.deepEqual( 132 assert.deepEqual(
162 n0.underflowBin.diagnosticMaps.map(dmd => dmd.get('foo').value), 133 n0.underflowBin.diagnosticMaps.map(dmd => dmd.get('foo').value),
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after
201 tr.v.HistogramBinBoundaries.createLinear(0, 1001, 10)); 172 tr.v.HistogramBinBoundaries.createLinear(0, 1001, 10));
202 var n2 = new tr.v.Histogram(tr.v.Unit.byName.timeDurationInMs, 173 var n2 = new tr.v.Histogram(tr.v.Unit.byName.timeDurationInMs,
203 tr.v.HistogramBinBoundaries.createLinear(0, 1000, 11)); 174 tr.v.HistogramBinBoundaries.createLinear(0, 1000, 11));
204 175
205 assert.throws(n0.addNumeric.bind(n0, n1), Error); 176 assert.throws(n0.addNumeric.bind(n0, n1), Error);
206 assert.throws(n0.addNumeric.bind(n0, n2), Error); 177 assert.throws(n0.addNumeric.bind(n0, n2), Error);
207 }); 178 });
208 179
209 test('addNumericWithNonDiagnosticMapThrows', function() { 180 test('addNumericWithNonDiagnosticMapThrows', function() {
210 var n = new tr.v.Histogram(unitlessNumber, TEST_BOUNDARIES); 181 var n = new tr.v.Histogram(unitlessNumber, TEST_BOUNDARIES);
211 assert.throws(n.add.bind(42, 'foo'), Error); 182 assert.throws(n.addSample.bind(42, 'foo'), Error);
212 }); 183 });
213 184
214 test('numericPercentile', function() { 185 test('numericPercentile', function() {
215 function check(array, min, max, bins, precision) { 186 function check(array, min, max, bins, precision) {
216 var boundaries = tr.v.HistogramBinBoundaries.createLinear(min, max, bins); 187 var boundaries = tr.v.HistogramBinBoundaries.createLinear(min, max, bins);
217 var n = new tr.v.Histogram(tr.v.Unit.byName.timeDurationInMs, boundaries); 188 var n = new tr.v.Histogram(tr.v.Unit.byName.timeDurationInMs, boundaries);
218 array.forEach((x) => n.add( 189 array.forEach((x) => n.addSample(x, {foo: new tr.v.d.Generic('x')}));
219 x, tr.v.d.DiagnosticMap.fromObject({foo: new tr.v.d.Generic('x')})));
220 [0.25, 0.5, 0.75, 0.8, 0.95, 0.99].forEach(function(percent) { 190 [0.25, 0.5, 0.75, 0.8, 0.95, 0.99].forEach(function(percent) {
221 var expected = tr.b.Statistics.percentile(array, percent); 191 var expected = tr.b.Statistics.percentile(array, percent);
222 var actual = n.getApproximatePercentile(percent); 192 var actual = n.getApproximatePercentile(percent);
223 assert.closeTo(expected, actual, precision); 193 assert.closeTo(expected, actual, precision);
224 }); 194 });
225 } 195 }
226 check([1, 2, 5, 7], 0.5, 10.5, 10, 1e-3); 196 check([1, 2, 5, 7], 0.5, 10.5, 10, 1e-3);
227 check([3, 3, 4, 4], 0.5, 10.5, 10, 1e-3); 197 check([3, 3, 4, 4], 0.5, 10.5, 10, 1e-3);
228 check([1, 10], 0.5, 10.5, 10, 1e-3); 198 check([1, 10], 0.5, 10.5, 10, 1e-3);
229 check([1, 2, 3, 4, 5], 0.5, 10.5, 10, 1e-3); 199 check([1, 2, 3, 4, 5], 0.5, 10.5, 10, 1e-3);
(...skipping 105 matching lines...) Expand 10 before | Expand all | Expand 10 after
335 var b1 = new tr.v.HistogramBinBoundaries(8); 305 var b1 = new tr.v.HistogramBinBoundaries(8);
336 assert.throws(() => b1.addExponentialBins(20, 0 /* must be > 0 */)); 306 assert.throws(() => b1.addExponentialBins(20, 0 /* must be > 0 */));
337 assert.throws(() => b1.addExponentialBins(5 /* must be > 8 */, 3)); 307 assert.throws(() => b1.addExponentialBins(5 /* must be > 8 */, 3));
338 assert.throws(() => b1.addExponentialBins(8 /* must be > 8 */, 3)); 308 assert.throws(() => b1.addExponentialBins(8 /* must be > 8 */, 3));
339 }); 309 });
340 310
341 test('getSummarizedScalarNumericsWithNames', function() { 311 test('getSummarizedScalarNumericsWithNames', function() {
342 var boundaries = tr.v.HistogramBinBoundaries.createLinear(0, 100, 100); 312 var boundaries = tr.v.HistogramBinBoundaries.createLinear(0, 100, 100);
343 var n = new tr.v.Histogram(unitlessNumber, boundaries); 313 var n = new tr.v.Histogram(unitlessNumber, boundaries);
344 314
345 n.add(50); 315 n.addSample(50);
346 n.add(60); 316 n.addSample(60);
347 n.add(70); 317 n.addSample(70);
348 318
349 n.customizeSummaryOptions({ 319 n.customizeSummaryOptions({
350 count: true, 320 count: true,
351 min: true, 321 min: true,
352 max: true, 322 max: true,
353 sum: true, 323 sum: true,
354 avg: true, 324 avg: true,
355 std: true, 325 std: true,
356 percentile: [0.5, 1] 326 percentile: [0.5, 1]
357 }); 327 });
(...skipping 11 matching lines...) Expand all
369 assert.strictEqual(values.avg.value, 60); 339 assert.strictEqual(values.avg.value, 60);
370 assert.strictEqual(values.std.value, 10); 340 assert.strictEqual(values.std.value, 10);
371 assert.closeTo(values.pct_050.value, 60, 1); 341 assert.closeTo(values.pct_050.value, 60, 1);
372 assert.closeTo(values.pct_100.value, 70, 1); 342 assert.closeTo(values.pct_100.value, 70, 1);
373 }); 343 });
374 344
375 test('getSummarizedScalarNumericsWithNamesNoSummaryOptions', function() { 345 test('getSummarizedScalarNumericsWithNamesNoSummaryOptions', function() {
376 var boundaries = tr.v.HistogramBinBoundaries.createLinear(0, 100, 100); 346 var boundaries = tr.v.HistogramBinBoundaries.createLinear(0, 100, 100);
377 var n = new tr.v.Histogram(unitlessNumber, boundaries); 347 var n = new tr.v.Histogram(unitlessNumber, boundaries);
378 348
379 n.add(50); 349 n.addSample(50);
380 n.add(60); 350 n.addSample(60);
381 n.add(70); 351 n.addSample(70);
382 352
383 n.customizeSummaryOptions({ 353 n.customizeSummaryOptions({
384 count: false, 354 count: false,
385 min: false, 355 min: false,
386 max: false, 356 max: false,
387 sum: false, 357 sum: false,
388 avg: false, 358 avg: false,
389 std: false, 359 std: false,
390 percentile: [] 360 percentile: []
391 }); 361 });
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after
433 var n1 = new tr.v.Histogram(unitlessNumber, TEST_BOUNDARIES); 403 var n1 = new tr.v.Histogram(unitlessNumber, TEST_BOUNDARIES);
434 // maxNumSampleValues defaults to numBins * 10, which, including the 404 // maxNumSampleValues defaults to numBins * 10, which, including the
435 // underflowBin and overflowBin plus this builder's 10 central bins, 405 // underflowBin and overflowBin plus this builder's 10 central bins,
436 // is 12 * 10. 406 // is 12 * 10.
437 assert.strictEqual(n0.maxNumSampleValues, 120); 407 assert.strictEqual(n0.maxNumSampleValues, 120);
438 assert.strictEqual(n1.maxNumSampleValues, 120); 408 assert.strictEqual(n1.maxNumSampleValues, 120);
439 var values0 = []; 409 var values0 = [];
440 var values1 = []; 410 var values1 = [];
441 for (var i = 0; i < 10; ++i) { 411 for (var i = 0; i < 10; ++i) {
442 values0.push(i); 412 values0.push(i);
443 n0.add(i); 413 n0.addSample(i);
444 } 414 }
445 for (var i = 10; i < 20; ++i) { 415 for (var i = 10; i < 20; ++i) {
446 values1.push(i); 416 values1.push(i);
447 n1.add(i); 417 n1.addSample(i);
448 } 418 }
449 assert.deepEqual(n0.sampleValues, values0); 419 assert.deepEqual(n0.sampleValues, values0);
450 assert.deepEqual(n1.sampleValues, values1); 420 assert.deepEqual(n1.sampleValues, values1);
451 n0.addNumeric(n1); 421 n0.addNumeric(n1);
452 assert.deepEqual(n0.sampleValues, values0.concat(values1)); 422 assert.deepEqual(n0.sampleValues, values0.concat(values1));
453 var n2 = n0.clone(); 423 var n2 = n0.clone();
454 assert.deepEqual(n2.sampleValues, values0.concat(values1)); 424 assert.deepEqual(n2.sampleValues, values0.concat(values1));
455 425
456 for (var i = 0; i < 200; ++i) 426 for (var i = 0; i < 200; ++i)
457 n0.add(i); 427 n0.addSample(i);
458 assert.strictEqual(n0.sampleValues.length, n0.maxNumSampleValues); 428 assert.strictEqual(n0.sampleValues.length, n0.maxNumSampleValues);
459 429
460 var n3 = new tr.v.Histogram(unitlessNumber, TEST_BOUNDARIES); 430 var n3 = new tr.v.Histogram(unitlessNumber, TEST_BOUNDARIES);
461 n3.maxNumSampleValues = 10; 431 n3.maxNumSampleValues = 10;
462 for (var i = 0; i < 100; ++i) 432 for (var i = 0; i < 100; ++i)
463 n3.add(i); 433 n3.addSample(i);
464 assert.strictEqual(n3.sampleValues.length, 10); 434 assert.strictEqual(n3.sampleValues.length, 10);
465 }); 435 });
466 436
467 test('mergeScalarNumerics', function() { 437 test('mergeScalarNumerics', function() {
468 var scalarA = new tr.v.ScalarNumeric(tr.v.Unit.byName.timeDurationInMs, 10); 438 var scalarA = new tr.v.ScalarNumeric(tr.v.Unit.byName.timeDurationInMs, 10);
469 var scalarB = new tr.v.ScalarNumeric(tr.v.Unit.byName.timeDurationInMs, 20); 439 var scalarB = new tr.v.ScalarNumeric(tr.v.Unit.byName.timeDurationInMs, 20);
470 440
471 var numeric = scalarA.merge(scalarB); 441 var numeric = scalarA.merge(scalarB);
472 assert.instanceOf(numeric, tr.v.Histogram); 442 assert.instanceOf(numeric, tr.v.Histogram);
473 assert.strictEqual(numeric.maxNumSampleValues, 1000); 443 assert.strictEqual(numeric.maxNumSampleValues, 1000);
(...skipping 15 matching lines...) Expand all
489 scalarB.value = 'i am also not a number'; 459 scalarB.value = 'i am also not a number';
490 numeric = scalarA.merge(scalarB); 460 numeric = scalarA.merge(scalarB);
491 assert.instanceOf(numeric, tr.v.Histogram); 461 assert.instanceOf(numeric, tr.v.Histogram);
492 assert.strictEqual(numeric.maxNumSampleValues, 1000); 462 assert.strictEqual(numeric.maxNumSampleValues, 1000);
493 assert.strictEqual(numeric.numValues, 0); 463 assert.strictEqual(numeric.numValues, 0);
494 assert.strictEqual(numeric.numNans, 2); 464 assert.strictEqual(numeric.numNans, 2);
495 assert.lengthOf(numeric.sampleValues, 2); 465 assert.lengthOf(numeric.sampleValues, 2);
496 assert.lengthOf(numeric.allBins, 4); 466 assert.lengthOf(numeric.allBins, 4);
497 }); 467 });
498 }); 468 });
499
500 </script> 469 </script>
OLDNEW
« no previous file with comments | « tracing/tracing/value/histogram.html ('k') | tracing/tracing/value/ui/composition_span_test.html » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698