1 // Copyright 2020 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "net/dns/httpssvc_metrics.h"
6
7 #include <optional>
8 #include <string>
9 #include <string_view>
10 #include <tuple>
11
12 #include "base/feature_list.h"
13 #include "base/strings/strcat.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/strings/string_util.h"
16 #include "base/test/metrics/histogram_tester.h"
17 #include "base/test/scoped_feature_list.h"
18 #include "net/base/features.h"
19 #include "testing/gtest/include/gtest/gtest.h"
20
21 namespace net {
22
23 // Base for testing the metrics collection code in |HttpssvcMetrics|.
24 class HttpssvcMetricsTest : public ::testing::TestWithParam<bool> {
25 public:
SetUp()26 void SetUp() override { secure_ = GetParam(); }
27
BuildMetricNamePrefix() const28 std::string BuildMetricNamePrefix() const {
29 return base::StrCat({"Net.DNS.HTTPSSVC.RecordHttps.",
30 secure_ ? "Secure." : "Insecure.", "ExpectNoerror."});
31 }
32
33 template <typename T>
ExpectSample(std::string_view name,std::optional<T> sample) const34 void ExpectSample(std::string_view name, std::optional<T> sample) const {
35 if (sample)
36 histo().ExpectUniqueSample(name, *sample, 1);
37 else
38 histo().ExpectTotalCount(name, 0);
39 }
40
ExpectSample(std::string_view name,std::optional<base::TimeDelta> sample) const41 void ExpectSample(std::string_view name,
42 std::optional<base::TimeDelta> sample) const {
43 std::optional<int64_t> sample_ms;
44 if (sample)
45 sample_ms = {sample->InMilliseconds()};
46 ExpectSample<int64_t>(name, sample_ms);
47 }
48
VerifyAddressResolveTimeMetric(std::optional<base::TimeDelta> expect_noerror_time=std::nullopt)49 void VerifyAddressResolveTimeMetric(
50 std::optional<base::TimeDelta> expect_noerror_time = std::nullopt) {
51 const std::string kExpectNoerror =
52 base::StrCat({BuildMetricNamePrefix(), "ResolveTimeAddress"});
53
54 ExpectSample(kExpectNoerror, expect_noerror_time);
55 }
56
VerifyHttpsMetricsForExpectNoerror(std::optional<HttpssvcDnsRcode> rcode=std::nullopt,std::optional<bool> parsable=std::nullopt,std::optional<bool> record_with_error=std::nullopt,std::optional<base::TimeDelta> resolve_time_https=std::nullopt,std::optional<int> resolve_time_ratio=std::nullopt) const57 void VerifyHttpsMetricsForExpectNoerror(
58 std::optional<HttpssvcDnsRcode> rcode = std::nullopt,
59 std::optional<bool> parsable = std::nullopt,
60 std::optional<bool> record_with_error = std::nullopt,
61 std::optional<base::TimeDelta> resolve_time_https = std::nullopt,
62 std::optional<int> resolve_time_ratio = std::nullopt) const {
63 const std::string kPrefix = BuildMetricNamePrefix();
64 const std::string kMetricDnsRcode = base::StrCat({kPrefix, "DnsRcode"});
65 const std::string kMetricParsable = base::StrCat({kPrefix, "Parsable"});
66 const std::string kMetricRecordWithError =
67 base::StrCat({kPrefix, "RecordWithError"});
68 const std::string kMetricResolveTimeExperimental =
69 base::StrCat({kPrefix, "ResolveTimeExperimental"});
70 const std::string kMetricResolveTimeRatio =
71 base::StrCat({kPrefix, "ResolveTimeRatio"});
72
73 ExpectSample(kMetricDnsRcode, rcode);
74 ExpectSample(kMetricParsable, parsable);
75 ExpectSample(kMetricRecordWithError, record_with_error);
76 ExpectSample(kMetricResolveTimeExperimental, resolve_time_https);
77 ExpectSample(kMetricResolveTimeRatio, resolve_time_ratio);
78 }
79
histo() const80 const base::HistogramTester& histo() const { return histogram_; }
81
82 protected:
83 bool secure_;
84
85 private:
86 base::HistogramTester histogram_;
87 };
88
89 INSTANTIATE_TEST_SUITE_P(HttpssvcMetricsTestSimple,
90 HttpssvcMetricsTest,
91 testing::Bool() // Querying over DoH or Do53.
92 );
93
94 // Only record metrics for a non-HTTPS query.
TEST_P(HttpssvcMetricsTest,AddressAndExperimentalMissing)95 TEST_P(HttpssvcMetricsTest, AddressAndExperimentalMissing) {
96 const base::TimeDelta kResolveTime = base::Milliseconds(10);
97 auto metrics = std::make_optional<HttpssvcMetrics>(secure_);
98 metrics->SaveForAddressQuery(kResolveTime, HttpssvcDnsRcode::kNoError);
99 metrics.reset(); // Record the metrics to UMA.
100
101 VerifyAddressResolveTimeMetric();
102 VerifyHttpsMetricsForExpectNoerror();
103 }
104
TEST_P(HttpssvcMetricsTest,AddressAndHttpsParsable)105 TEST_P(HttpssvcMetricsTest, AddressAndHttpsParsable) {
106 const base::TimeDelta kResolveTime = base::Milliseconds(10);
107 const base::TimeDelta kResolveTimeHttps = base::Milliseconds(15);
108 auto metrics = std::make_optional<HttpssvcMetrics>(secure_);
109 metrics->SaveForHttps(HttpssvcDnsRcode::kNoError, {true}, kResolveTimeHttps);
110 metrics->SaveForAddressQuery(kResolveTime, HttpssvcDnsRcode::kNoError);
111 metrics.reset(); // Record the metrics to UMA.
112
113 VerifyAddressResolveTimeMetric({kResolveTime} /* expect_noerror_time */);
114 VerifyHttpsMetricsForExpectNoerror(
115 {HttpssvcDnsRcode::kNoError} /* rcode */, {true} /* parsable */,
116 std::nullopt /* record_with_error */,
117 {kResolveTimeHttps} /* resolve_time_https */,
118 {15} /* resolve_time_ratio */);
119 }
120
121 // This test simulates an HTTPS response that includes no HTTPS records,
122 // but does have an error value for the RCODE.
TEST_P(HttpssvcMetricsTest,AddressAndHttpsMissingWithRcode)123 TEST_P(HttpssvcMetricsTest, AddressAndHttpsMissingWithRcode) {
124 const base::TimeDelta kResolveTime = base::Milliseconds(10);
125 const base::TimeDelta kResolveTimeHttps = base::Milliseconds(15);
126
127 auto metrics = std::make_optional<HttpssvcMetrics>(secure_);
128 metrics->SaveForHttps(HttpssvcDnsRcode::kNxDomain, {}, kResolveTimeHttps);
129 metrics->SaveForAddressQuery(kResolveTime, HttpssvcDnsRcode::kNoError);
130 metrics.reset(); // Record the metrics to UMA.
131
132 VerifyAddressResolveTimeMetric({kResolveTime} /* expect_noerror_time */);
133 VerifyHttpsMetricsForExpectNoerror(
134 {HttpssvcDnsRcode::kNxDomain} /* rcode */, std::nullopt /* parsable */,
135 std::nullopt /* record_with_error */,
136 {kResolveTimeHttps} /* resolve_time_https */,
137 {15} /* resolve_time_ratio */);
138 }
139
140 // This test simulates an HTTPS response that includes a parsable HTTPS
141 // record, but also has an error RCODE.
TEST_P(HttpssvcMetricsTest,AddressAndHttpsParsableWithRcode)142 TEST_P(HttpssvcMetricsTest, AddressAndHttpsParsableWithRcode) {
143 const base::TimeDelta kResolveTime = base::Milliseconds(10);
144 const base::TimeDelta kResolveTimeHttps = base::Milliseconds(15);
145
146 auto metrics = std::make_optional<HttpssvcMetrics>(secure_);
147 metrics->SaveForHttps(HttpssvcDnsRcode::kNxDomain, {true}, kResolveTimeHttps);
148 metrics->SaveForAddressQuery(kResolveTime, HttpssvcDnsRcode::kNoError);
149 metrics.reset(); // Record the metrics to UMA.
150
151 VerifyAddressResolveTimeMetric({kResolveTime} /* expect_noerror_time */);
152 VerifyHttpsMetricsForExpectNoerror(
153 {HttpssvcDnsRcode::kNxDomain} /* rcode */,
154 // "parsable" metric is omitted because the RCODE is not NOERROR.
155 std::nullopt /* parsable */, {true} /* record_with_error */,
156 {kResolveTimeHttps} /* resolve_time_https */,
157 {15} /* resolve_time_ratio */);
158 }
159
160 // This test simulates an HTTPS response that includes a mangled HTTPS
161 // record *and* has an error RCODE.
TEST_P(HttpssvcMetricsTest,AddressAndHttpsMangledWithRcode)162 TEST_P(HttpssvcMetricsTest, AddressAndHttpsMangledWithRcode) {
163 const base::TimeDelta kResolveTime = base::Milliseconds(10);
164 const base::TimeDelta kResolveTimeHttps = base::Milliseconds(15);
165 auto metrics = std::make_optional<HttpssvcMetrics>(secure_);
166 metrics->SaveForHttps(HttpssvcDnsRcode::kNxDomain, {false},
167 kResolveTimeHttps);
168 metrics->SaveForAddressQuery(kResolveTime, HttpssvcDnsRcode::kNoError);
169 metrics.reset(); // Record the metrics to UMA.
170
171 VerifyAddressResolveTimeMetric({kResolveTime} /* expect_noerror_time */);
172 VerifyHttpsMetricsForExpectNoerror(
173 {HttpssvcDnsRcode::kNxDomain} /* rcode */,
174 // "parsable" metric is omitted because the RCODE is not NOERROR.
175 std::nullopt /* parsable */, {true} /* record_with_error */,
176 {kResolveTimeHttps} /* resolve_time_https */,
177 {15} /* resolve_time_ratio */);
178 }
179
180 // This test simulates successful address queries and an HTTPS query that
181 // timed out.
TEST_P(HttpssvcMetricsTest,AddressAndHttpsTimedOut)182 TEST_P(HttpssvcMetricsTest, AddressAndHttpsTimedOut) {
183 const base::TimeDelta kResolveTime = base::Milliseconds(10);
184 const base::TimeDelta kResolveTimeHttps = base::Milliseconds(15);
185 auto metrics = std::make_optional<HttpssvcMetrics>(secure_);
186 metrics->SaveForHttps(HttpssvcDnsRcode::kTimedOut, {}, kResolveTimeHttps);
187 metrics->SaveForAddressQuery(kResolveTime, HttpssvcDnsRcode::kNoError);
188 metrics.reset(); // Record the metrics to UMA.
189
190 VerifyAddressResolveTimeMetric({kResolveTime} /* expect_noerror_time */);
191 VerifyHttpsMetricsForExpectNoerror(
192 {HttpssvcDnsRcode::kTimedOut} /* rcode */,
193 // "parsable" metric is omitted because the RCODE is not NOERROR.
194 std::nullopt /* parsable */, std::nullopt /* record_with_error */,
195 {kResolveTimeHttps} /* resolve_time_https */,
196 {15} /* resolve_time_ratio */);
197 }
198
199 } // namespace net
200