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

Side by Side Diff: net/nqe/network_quality_estimator_unittest.cc

Issue 2695783003: NQE: Record the main frame metrics at transaction start (Closed)
Patch Set: Fix comments Created 3 years, 10 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 | « net/nqe/network_quality_estimator.cc ('k') | no next file » | 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 Chromium Authors. All rights reserved. 1 // Copyright 2015 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/nqe/network_quality_estimator.h" 5 #include "net/nqe/network_quality_estimator.h"
6 6
7 #include <stddef.h> 7 #include <stddef.h>
8 #include <stdint.h> 8 #include <stdint.h>
9 9
10 #include <limits> 10 #include <limits>
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after
52 const std::string& histogram, 52 const std::string& histogram,
53 int32_t bucket_min, 53 int32_t bucket_min,
54 int32_t expected_min_count_samples) { 54 int32_t expected_min_count_samples) {
55 std::vector<base::Bucket> buckets = 55 std::vector<base::Bucket> buckets =
56 histogram_tester->GetAllSamples(histogram); 56 histogram_tester->GetAllSamples(histogram);
57 int actual_count_samples = 0; 57 int actual_count_samples = 0;
58 for (const auto& bucket : buckets) { 58 for (const auto& bucket : buckets) {
59 if (bucket.min == bucket_min) 59 if (bucket.min == bucket_min)
60 actual_count_samples += bucket.count; 60 actual_count_samples += bucket.count;
61 } 61 }
62 EXPECT_LE(expected_min_count_samples, actual_count_samples); 62 EXPECT_LE(expected_min_count_samples, actual_count_samples)
63 << " histogram=" << histogram << " bucket_min=" << bucket_min
64 << " expected_min_count_samples=" << expected_min_count_samples;
63 } 65 }
64 66
65 } // namespace 67 } // namespace
66 68
67 namespace net { 69 namespace net {
68 70
69 namespace { 71 namespace {
70 72
71 class TestEffectiveConnectionTypeObserver 73 class TestEffectiveConnectionTypeObserver
72 : public NetworkQualityEstimator::EffectiveConnectionTypeObserver { 74 : public NetworkQualityEstimator::EffectiveConnectionTypeObserver {
(...skipping 1613 matching lines...) Expand 10 before | Expand all | Expand 10 after
1686 // interval, and that the observers are notified of any change. 1688 // interval, and that the observers are notified of any change.
1687 TEST(NetworkQualityEstimatorTest, MAYBE_TestEffectiveConnectionTypeObserver) { 1689 TEST(NetworkQualityEstimatorTest, MAYBE_TestEffectiveConnectionTypeObserver) {
1688 base::HistogramTester histogram_tester; 1690 base::HistogramTester histogram_tester;
1689 std::unique_ptr<base::SimpleTestTickClock> tick_clock( 1691 std::unique_ptr<base::SimpleTestTickClock> tick_clock(
1690 new base::SimpleTestTickClock()); 1692 new base::SimpleTestTickClock());
1691 base::SimpleTestTickClock* tick_clock_ptr = tick_clock.get(); 1693 base::SimpleTestTickClock* tick_clock_ptr = tick_clock.get();
1692 1694
1693 TestEffectiveConnectionTypeObserver observer; 1695 TestEffectiveConnectionTypeObserver observer;
1694 TestNetworkQualityEstimator estimator; 1696 TestNetworkQualityEstimator estimator;
1695 estimator.AddEffectiveConnectionTypeObserver(&observer); 1697 estimator.AddEffectiveConnectionTypeObserver(&observer);
1698 // |observer| may be notified as soon as it is added. Run the loop to so that
1699 // the notification to |observer| is finished.
1700 base::RunLoop().RunUntilIdle();
1696 estimator.SetTickClockForTesting(std::move(tick_clock)); 1701 estimator.SetTickClockForTesting(std::move(tick_clock));
1697 1702
1698 TestDelegate test_delegate; 1703 TestDelegate test_delegate;
1699 TestURLRequestContext context(true); 1704 TestURLRequestContext context(true);
1700 context.set_network_quality_estimator(&estimator); 1705 context.set_network_quality_estimator(&estimator);
1701 context.Init(); 1706 context.Init();
1702 1707
1703 EXPECT_EQ(0U, observer.effective_connection_types().size()); 1708 EXPECT_EQ(0U, observer.effective_connection_types().size());
1704 1709
1705 estimator.set_start_time_null_http_rtt( 1710 estimator.set_start_time_null_http_rtt(
(...skipping 222 matching lines...) Expand 10 before | Expand all | Expand 10 after
1928 std::unique_ptr<base::SimpleTestTickClock> tick_clock( 1933 std::unique_ptr<base::SimpleTestTickClock> tick_clock(
1929 new base::SimpleTestTickClock()); 1934 new base::SimpleTestTickClock());
1930 base::SimpleTestTickClock* tick_clock_ptr = tick_clock.get(); 1935 base::SimpleTestTickClock* tick_clock_ptr = tick_clock.get();
1931 1936
1932 TestEffectiveConnectionTypeObserver observer; 1937 TestEffectiveConnectionTypeObserver observer;
1933 TestNetworkQualityEstimator estimator; 1938 TestNetworkQualityEstimator estimator;
1934 estimator.SetTickClockForTesting(std::move(tick_clock)); 1939 estimator.SetTickClockForTesting(std::move(tick_clock));
1935 estimator.SimulateNetworkChange(NetworkChangeNotifier::CONNECTION_WIFI, 1940 estimator.SimulateNetworkChange(NetworkChangeNotifier::CONNECTION_WIFI,
1936 "test"); 1941 "test");
1937 estimator.AddEffectiveConnectionTypeObserver(&observer); 1942 estimator.AddEffectiveConnectionTypeObserver(&observer);
1943 // |observer| may be notified as soon as it is added. Run the loop to so that
1944 // the notification to |observer| is finished.
1945 base::RunLoop().RunUntilIdle();
1938 1946
1939 TestDelegate test_delegate; 1947 TestDelegate test_delegate;
1940 TestURLRequestContext context(true); 1948 TestURLRequestContext context(true);
1941 context.set_network_quality_estimator(&estimator); 1949 context.set_network_quality_estimator(&estimator);
1942 context.Init(); 1950 context.Init();
1943 1951
1944 EXPECT_EQ(0U, observer.effective_connection_types().size()); 1952 EXPECT_EQ(0U, observer.effective_connection_types().size());
1945 1953
1946 estimator.set_recent_effective_connection_type(EFFECTIVE_CONNECTION_TYPE_2G); 1954 estimator.set_recent_effective_connection_type(EFFECTIVE_CONNECTION_TYPE_2G);
1947 tick_clock_ptr->Advance(base::TimeDelta::FromMinutes(60)); 1955 tick_clock_ptr->Advance(base::TimeDelta::FromMinutes(60));
(...skipping 154 matching lines...) Expand 10 before | Expand all | Expand 10 after
2102 #define MAYBE_TestTCPSocketRTT DISABLED_TestTCPSocketRTT 2110 #define MAYBE_TestTCPSocketRTT DISABLED_TestTCPSocketRTT
2103 #endif 2111 #endif
2104 // Tests that the TCP socket notifies the Network Quality Estimator of TCP RTTs, 2112 // Tests that the TCP socket notifies the Network Quality Estimator of TCP RTTs,
2105 // which in turn notifies registered RTT observers. 2113 // which in turn notifies registered RTT observers.
2106 TEST(NetworkQualityEstimatorTest, MAYBE_TestTCPSocketRTT) { 2114 TEST(NetworkQualityEstimatorTest, MAYBE_TestTCPSocketRTT) {
2107 base::HistogramTester histogram_tester; 2115 base::HistogramTester histogram_tester;
2108 TestRTTObserver rtt_observer; 2116 TestRTTObserver rtt_observer;
2109 2117
2110 std::map<std::string, std::string> variation_params; 2118 std::map<std::string, std::string> variation_params;
2111 variation_params["persistent_cache_reading_enabled"] = "true"; 2119 variation_params["persistent_cache_reading_enabled"] = "true";
2112 TestNetworkQualityEstimator estimator(variation_params); 2120 TestNetworkQualityEstimator estimator(
2121 nullptr, variation_params, true, true,
2122 true /* add_default_platform_observations */,
2123 base::MakeUnique<BoundTestNetLog>());
2113 estimator.SimulateNetworkChange( 2124 estimator.SimulateNetworkChange(
2114 NetworkChangeNotifier::ConnectionType::CONNECTION_2G, "test"); 2125 NetworkChangeNotifier::ConnectionType::CONNECTION_2G, "test");
2115 2126
2116 estimator.AddRTTObserver(&rtt_observer); 2127 estimator.AddRTTObserver(&rtt_observer);
2128 // |observer| may be notified as soon as it is added. Run the loop to so that
2129 // the notification to |observer| is finished.
2130 base::RunLoop().RunUntilIdle();
2117 2131
2118 TestDelegate test_delegate; 2132 TestDelegate test_delegate;
2119 TestURLRequestContext context(true); 2133 TestURLRequestContext context(true);
2120 context.set_network_quality_estimator(&estimator); 2134 context.set_network_quality_estimator(&estimator);
2121 2135
2122 std::unique_ptr<HttpNetworkSession::Params> params( 2136 std::unique_ptr<HttpNetworkSession::Params> params(
2123 new HttpNetworkSession::Params); 2137 new HttpNetworkSession::Params);
2124 // |estimator| should be notified of TCP RTT observations. 2138 // |estimator| should be notified of TCP RTT observations.
2125 params->socket_performance_watcher_factory = 2139 params->socket_performance_watcher_factory =
2126 estimator.GetSocketPerformanceWatcherFactory(); 2140 estimator.GetSocketPerformanceWatcherFactory();
2127 context.set_http_network_session_params(std::move(params)); 2141 context.set_http_network_session_params(std::move(params));
2128 context.Init(); 2142 context.Init();
2129 2143
2130 EXPECT_EQ(0U, rtt_observer.observations().size()); 2144 EXPECT_EQ(0U, rtt_observer.observations().size());
2131 base::TimeDelta rtt; 2145 base::TimeDelta rtt;
2132 EXPECT_FALSE(estimator.GetRecentHttpRTT(base::TimeTicks(), &rtt)); 2146 EXPECT_TRUE(estimator.GetRecentHttpRTT(base::TimeTicks(), &rtt));
2133 EXPECT_FALSE(estimator.GetRecentTransportRTT(base::TimeTicks(), &rtt)); 2147 EXPECT_TRUE(estimator.GetRecentTransportRTT(base::TimeTicks(), &rtt));
2134 2148
2135 // Send two requests. Verify that the completion of each request generates at 2149 // Send two requests. Verify that the completion of each request generates at
2136 // least one TCP RTT observation. 2150 // least one TCP RTT observation.
2137 const size_t num_requests = 2; 2151 const size_t num_requests = 2;
2138 for (size_t i = 0; i < num_requests; ++i) { 2152 for (size_t i = 0; i < num_requests; ++i) {
2139 size_t before_count_tcp_rtt_observations = 0; 2153 size_t before_count_tcp_rtt_observations = 0;
2140 for (const auto& observation : rtt_observer.observations()) { 2154 for (const auto& observation : rtt_observer.observations()) {
2141 if (observation.source == NETWORK_QUALITY_OBSERVATION_SOURCE_TCP) 2155 if (observation.source == NETWORK_QUALITY_OBSERVATION_SOURCE_TCP)
2142 ++before_count_tcp_rtt_observations; 2156 ++before_count_tcp_rtt_observations;
2143 } 2157 }
(...skipping 24 matching lines...) Expand all
2168 histogram_tester.ExpectBucketCount("NQE.TransportRTT.Percentile50.2G", 2182 histogram_tester.ExpectBucketCount("NQE.TransportRTT.Percentile50.2G",
2169 rtt.InMilliseconds(), 1); 2183 rtt.InMilliseconds(), 1);
2170 histogram_tester.ExpectTotalCount("NQE.TransportRTT.Percentile10.2G", 1); 2184 histogram_tester.ExpectTotalCount("NQE.TransportRTT.Percentile10.2G", 1);
2171 histogram_tester.ExpectTotalCount("NQE.TransportRTT.Percentile50.2G", 1); 2185 histogram_tester.ExpectTotalCount("NQE.TransportRTT.Percentile50.2G", 1);
2172 histogram_tester.ExpectTotalCount("NQE.TransportRTT.Percentile90.2G", 1); 2186 histogram_tester.ExpectTotalCount("NQE.TransportRTT.Percentile90.2G", 1);
2173 histogram_tester.ExpectTotalCount("NQE.TransportRTT.Percentile100.2G", 1); 2187 histogram_tester.ExpectTotalCount("NQE.TransportRTT.Percentile100.2G", 1);
2174 2188
2175 // Verify that metrics are logged correctly on main-frame requests. 2189 // Verify that metrics are logged correctly on main-frame requests.
2176 histogram_tester.ExpectTotalCount("NQE.MainFrame.TransportRTT.Percentile50", 2190 histogram_tester.ExpectTotalCount("NQE.MainFrame.TransportRTT.Percentile50",
2177 num_requests); 2191 num_requests);
2178 histogram_tester.ExpectBucketCount("NQE.EstimateAvailable.MainFrame.RTT", 0, 2192 histogram_tester.ExpectUniqueSample("NQE.EstimateAvailable.MainFrame.RTT", 1,
2179 1); 2193 num_requests);
2180 histogram_tester.ExpectBucketCount("NQE.EstimateAvailable.MainFrame.RTT", 1,
2181 num_requests - 1);
2182 histogram_tester.ExpectUniqueSample( 2194 histogram_tester.ExpectUniqueSample(
2183 "NQE.EstimateAvailable.MainFrame.TransportRTT", 1, num_requests); 2195 "NQE.EstimateAvailable.MainFrame.TransportRTT", 1, num_requests);
2184 histogram_tester.ExpectBucketCount("NQE.EstimateAvailable.MainFrame.Kbps", 0, 2196 histogram_tester.ExpectUniqueSample("NQE.EstimateAvailable.MainFrame.Kbps", 1,
2185 1); 2197 num_requests);
2186 histogram_tester.ExpectBucketCount("NQE.EstimateAvailable.MainFrame.Kbps", 1,
2187 num_requests - 1);
2188 2198
2189 histogram_tester.ExpectTotalCount( 2199 histogram_tester.ExpectTotalCount(
2190 "NQE.MainFrame.TransportRTT.Percentile50.2G", num_requests); 2200 "NQE.MainFrame.TransportRTT.Percentile50.2G", num_requests);
2191 histogram_tester.ExpectTotalCount("NQE.MainFrame.EffectiveConnectionType", 2201 histogram_tester.ExpectTotalCount("NQE.MainFrame.EffectiveConnectionType",
2192 num_requests); 2202 num_requests);
2193 histogram_tester.ExpectTotalCount("NQE.MainFrame.EffectiveConnectionType.2G", 2203 histogram_tester.ExpectTotalCount("NQE.MainFrame.EffectiveConnectionType.2G",
2194 num_requests); 2204 num_requests);
2195 histogram_tester.ExpectBucketCount("NQE.MainFrame.EffectiveConnectionType.2G", 2205 histogram_tester.ExpectBucketCount("NQE.MainFrame.EffectiveConnectionType.2G",
2196 EFFECTIVE_CONNECTION_TYPE_UNKNOWN, 1); 2206 EFFECTIVE_CONNECTION_TYPE_UNKNOWN, 0);
2197 ExpectBucketCountAtLeast(&histogram_tester, "NQE.RTT.ObservationSource", 2207 ExpectBucketCountAtLeast(&histogram_tester, "NQE.RTT.ObservationSource",
2198 NETWORK_QUALITY_OBSERVATION_SOURCE_TCP, 1); 2208 NETWORK_QUALITY_OBSERVATION_SOURCE_TCP, 1);
2199 ExpectBucketCountAtLeast(&histogram_tester, "NQE.Kbps.ObservationSource", 2209 ExpectBucketCountAtLeast(&histogram_tester, "NQE.Kbps.ObservationSource",
2200 NETWORK_QUALITY_OBSERVATION_SOURCE_HTTP, 1); 2210 NETWORK_QUALITY_OBSERVATION_SOURCE_HTTP, 1);
2201 histogram_tester.ExpectBucketCount("NQE.MainFrame.EffectiveConnectionType.2G",
2202 EFFECTIVE_CONNECTION_TYPE_UNKNOWN, 1);
2203 EXPECT_LE(1u, 2211 EXPECT_LE(1u,
2204 histogram_tester 2212 histogram_tester
2205 .GetAllSamples("NQE.EffectiveConnectionType.OnECTComputation") 2213 .GetAllSamples("NQE.EffectiveConnectionType.OnECTComputation")
2206 .size()); 2214 .size());
2207 EXPECT_LE(1u, 2215 EXPECT_LE(1u,
2208 histogram_tester.GetAllSamples("NQE.TransportRTT.OnECTComputation") 2216 histogram_tester.GetAllSamples("NQE.TransportRTT.OnECTComputation")
2209 .size()); 2217 .size());
2210 EXPECT_LE(1u, 2218 EXPECT_LE(1u,
2211 histogram_tester.GetAllSamples("NQE.RTT.OnECTComputation").size()); 2219 histogram_tester.GetAllSamples("NQE.RTT.OnECTComputation").size());
2212 2220
(...skipping 335 matching lines...) Expand 10 before | Expand all | Expand 10 after
2548 estimator.set_recent_http_rtt(test.http_rtt); 2556 estimator.set_recent_http_rtt(test.http_rtt);
2549 estimator.set_start_time_null_downlink_throughput_kbps( 2557 estimator.set_start_time_null_downlink_throughput_kbps(
2550 test.downstream_throughput_kbps); 2558 test.downstream_throughput_kbps);
2551 estimator.set_rand_double(test.rand_double); 2559 estimator.set_rand_double(test.rand_double);
2552 2560
2553 TestDelegate test_delegate; 2561 TestDelegate test_delegate;
2554 TestURLRequestContext context(true); 2562 TestURLRequestContext context(true);
2555 context.set_network_quality_estimator(&estimator); 2563 context.set_network_quality_estimator(&estimator);
2556 context.Init(); 2564 context.Init();
2557 2565
2566 histogram_tester.ExpectTotalCount(
2567 "NQE.Correlation.ResourceLoadTime.0Kb_128Kb", 0);
2568
2558 // Start a main-frame request that should cause network quality estimator to 2569 // Start a main-frame request that should cause network quality estimator to
2559 // record the network quality at the last main frame request. 2570 // record the network quality at the last main frame request.
2560 std::unique_ptr<URLRequest> request_1(context.CreateRequest( 2571 std::unique_ptr<URLRequest> request_1(context.CreateRequest(
2561 estimator.GetEchoURL(), DEFAULT_PRIORITY, &test_delegate)); 2572 estimator.GetEchoURL(), DEFAULT_PRIORITY, &test_delegate));
2562 request_1->SetLoadFlags(request_1->load_flags() | 2573 request_1->SetLoadFlags(request_1->load_flags() |
2563 LOAD_MAIN_FRAME_DEPRECATED); 2574 LOAD_MAIN_FRAME_DEPRECATED);
2564 request_1->Start(); 2575 request_1->Start();
2565 base::RunLoop().Run(); 2576 base::RunLoop().Run();
2566 histogram_tester.ExpectTotalCount(
2567 "NQE.Correlation.ResourceLoadTime.0Kb_128Kb", 0);
2568
2569 // Start another main-frame request which should cause network quality
2570 // estimator to record the correlation UMA.
2571 std::unique_ptr<URLRequest> request_2(context.CreateRequest(
2572 estimator.GetEchoURL(), DEFAULT_PRIORITY, &test_delegate));
2573 request_2->Start();
2574 base::RunLoop().Run();
2575 2577
2576 if (test.rand_double >= test.correlation_logging_probability) { 2578 if (test.rand_double >= test.correlation_logging_probability) {
2577 histogram_tester.ExpectTotalCount( 2579 histogram_tester.ExpectTotalCount(
2578 "NQE.Correlation.ResourceLoadTime.0Kb_128Kb", 0); 2580 "NQE.Correlation.ResourceLoadTime.0Kb_128Kb", 0);
2579 continue; 2581 continue;
2580 } 2582 }
2581 if (!test.use_transport_rtt && 2583 if (!test.use_transport_rtt &&
2582 test.http_rtt == nqe::internal::InvalidRTT()) { 2584 test.http_rtt == nqe::internal::InvalidRTT()) {
2583 histogram_tester.ExpectTotalCount( 2585 histogram_tester.ExpectTotalCount(
2584 "NQE.Correlation.ResourceLoadTime.0Kb_128Kb", 0); 2586 "NQE.Correlation.ResourceLoadTime.0Kb_128Kb", 0);
(...skipping 137 matching lines...) Expand 10 before | Expand all | Expand 10 after
2722 ForceEffectiveConnectionTypeThroughFieldTrial) { 2724 ForceEffectiveConnectionTypeThroughFieldTrial) {
2723 for (int i = 0; i < EFFECTIVE_CONNECTION_TYPE_LAST; ++i) { 2725 for (int i = 0; i < EFFECTIVE_CONNECTION_TYPE_LAST; ++i) {
2724 std::map<std::string, std::string> variation_params; 2726 std::map<std::string, std::string> variation_params;
2725 variation_params["force_effective_connection_type"] = 2727 variation_params["force_effective_connection_type"] =
2726 GetNameForEffectiveConnectionType( 2728 GetNameForEffectiveConnectionType(
2727 static_cast<EffectiveConnectionType>(i)); 2729 static_cast<EffectiveConnectionType>(i));
2728 TestNetworkQualityEstimator estimator(variation_params); 2730 TestNetworkQualityEstimator estimator(variation_params);
2729 2731
2730 TestEffectiveConnectionTypeObserver observer; 2732 TestEffectiveConnectionTypeObserver observer;
2731 estimator.AddEffectiveConnectionTypeObserver(&observer); 2733 estimator.AddEffectiveConnectionTypeObserver(&observer);
2734 // |observer| may be notified as soon as it is added. Run the loop to so
2735 // that the notification to |observer| is finished.
2736 base::RunLoop().RunUntilIdle();
2732 2737
2733 TestDelegate test_delegate; 2738 TestDelegate test_delegate;
2734 TestURLRequestContext context(true); 2739 TestURLRequestContext context(true);
2735 context.set_network_quality_estimator(&estimator); 2740 context.set_network_quality_estimator(&estimator);
2736 context.Init(); 2741 context.Init();
2737 2742
2738 EXPECT_EQ(0U, observer.effective_connection_types().size()); 2743 EXPECT_EQ(0U, observer.effective_connection_types().size());
2739 2744
2740 std::unique_ptr<URLRequest> request(context.CreateRequest( 2745 std::unique_ptr<URLRequest> request(context.CreateRequest(
2741 estimator.GetEchoURL(), DEFAULT_PRIORITY, &test_delegate)); 2746 estimator.GetEchoURL(), DEFAULT_PRIORITY, &test_delegate));
(...skipping 329 matching lines...) Expand 10 before | Expand all | Expand 10 after
3071 3076
3072 // Cleanup. 3077 // Cleanup.
3073 estimator.RemoveRTTObserver(&rtt_observer); 3078 estimator.RemoveRTTObserver(&rtt_observer);
3074 estimator.RemoveThroughputObserver(&throughput_observer); 3079 estimator.RemoveThroughputObserver(&throughput_observer);
3075 estimator.RemoveRTTAndThroughputEstimatesObserver(&rtt_throughput_observer); 3080 estimator.RemoveRTTAndThroughputEstimatesObserver(&rtt_throughput_observer);
3076 estimator.RemoveEffectiveConnectionTypeObserver( 3081 estimator.RemoveEffectiveConnectionTypeObserver(
3077 &effective_connection_type_observer); 3082 &effective_connection_type_observer);
3078 } 3083 }
3079 3084
3080 } // namespace net 3085 } // namespace net
OLDNEW
« no previous file with comments | « net/nqe/network_quality_estimator.cc ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698