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 <string_view>
8
9 #include "base/containers/contains.h"
10 #include "base/feature_list.h"
11 #include "base/metrics/histogram.h"
12 #include "base/metrics/histogram_base.h"
13 #include "base/metrics/histogram_functions.h"
14 #include "base/notreached.h"
15 #include "base/numerics/clamped_math.h"
16 #include "base/strings/string_split.h"
17 #include "base/strings/string_util.h"
18 #include "net/base/features.h"
19 #include "net/dns/dns_util.h"
20 #include "net/dns/public/dns_protocol.h"
21
22 namespace net {
23
TranslateDnsRcodeForHttpssvcExperiment(uint8_t rcode)24 enum HttpssvcDnsRcode TranslateDnsRcodeForHttpssvcExperiment(uint8_t rcode) {
25 switch (rcode) {
26 case dns_protocol::kRcodeNOERROR:
27 return HttpssvcDnsRcode::kNoError;
28 case dns_protocol::kRcodeFORMERR:
29 return HttpssvcDnsRcode::kFormErr;
30 case dns_protocol::kRcodeSERVFAIL:
31 return HttpssvcDnsRcode::kServFail;
32 case dns_protocol::kRcodeNXDOMAIN:
33 return HttpssvcDnsRcode::kNxDomain;
34 case dns_protocol::kRcodeNOTIMP:
35 return HttpssvcDnsRcode::kNotImp;
36 case dns_protocol::kRcodeREFUSED:
37 return HttpssvcDnsRcode::kRefused;
38 default:
39 return HttpssvcDnsRcode::kUnrecognizedRcode;
40 }
41 NOTREACHED();
42 }
43
HttpssvcMetrics(bool secure)44 HttpssvcMetrics::HttpssvcMetrics(bool secure) : secure_(secure) {}
45
~HttpssvcMetrics()46 HttpssvcMetrics::~HttpssvcMetrics() {
47 RecordMetrics();
48 }
49
SaveForAddressQuery(base::TimeDelta resolve_time,enum HttpssvcDnsRcode rcode)50 void HttpssvcMetrics::SaveForAddressQuery(base::TimeDelta resolve_time,
51 enum HttpssvcDnsRcode rcode) {
52 address_resolve_times_.push_back(resolve_time);
53
54 if (rcode != HttpssvcDnsRcode::kNoError)
55 disqualified_ = true;
56 }
57
SaveAddressQueryFailure()58 void HttpssvcMetrics::SaveAddressQueryFailure() {
59 disqualified_ = true;
60 }
61
SaveForHttps(enum HttpssvcDnsRcode rcode,const std::vector<bool> & condensed_records,base::TimeDelta https_resolve_time)62 void HttpssvcMetrics::SaveForHttps(enum HttpssvcDnsRcode rcode,
63 const std::vector<bool>& condensed_records,
64 base::TimeDelta https_resolve_time) {
65 DCHECK(!rcode_https_.has_value());
66 rcode_https_ = rcode;
67
68 num_https_records_ = condensed_records.size();
69
70 // We only record one "parsable" sample per HTTPS query. In case multiple
71 // matching records are present in the response, we combine their parsable
72 // values with logical AND.
73 const bool parsable = !base::Contains(condensed_records, false);
74
75 DCHECK(!is_https_parsable_.has_value());
76 is_https_parsable_ = parsable;
77
78 DCHECK(!https_resolve_time_.has_value());
79 https_resolve_time_ = https_resolve_time;
80 }
81
BuildMetricName(std::string_view leaf_name) const82 std::string HttpssvcMetrics::BuildMetricName(std::string_view leaf_name) const {
83 std::string_view type_str = "RecordHttps";
84 std::string_view secure = secure_ ? "Secure" : "Insecure";
85 // This part is just a legacy from old experiments but now meaningless.
86 std::string_view expectation = "ExpectNoerror";
87
88 // Example metric name:
89 // Net.DNS.HTTPSSVC.RecordHttps.Secure.ExpectNoerror.DnsRcode
90 // TODO(crbug.com/1366422): Simplify the metric names.
91 return base::JoinString(
92 {"Net.DNS.HTTPSSVC", type_str, secure, expectation, leaf_name}, ".");
93 }
94
RecordMetrics()95 void HttpssvcMetrics::RecordMetrics() {
96 DCHECK(!already_recorded_);
97 already_recorded_ = true;
98
99 // We really have no metrics to record without an HTTPS query resolve time and
100 // `address_resolve_times_`. If this HttpssvcMetrics is in an inconsistent
101 // state, disqualify any metrics from being recorded.
102 if (!https_resolve_time_.has_value() || address_resolve_times_.empty()) {
103 disqualified_ = true;
104 }
105 if (disqualified_)
106 return;
107
108 base::UmaHistogramMediumTimes(BuildMetricName("ResolveTimeExperimental"),
109 *https_resolve_time_);
110
111 // Record the address resolve times.
112 const std::string kMetricResolveTimeAddressRecord =
113 BuildMetricName("ResolveTimeAddress");
114 for (base::TimeDelta resolve_time_other : address_resolve_times_) {
115 base::UmaHistogramMediumTimes(kMetricResolveTimeAddressRecord,
116 resolve_time_other);
117 }
118
119 // ResolveTimeRatio is the HTTPS query resolve time divided by the slower of
120 // the A or AAAA resolve times. Arbitrarily choosing precision at two decimal
121 // places.
122 std::vector<base::TimeDelta>::iterator slowest_address_resolve =
123 std::max_element(address_resolve_times_.begin(),
124 address_resolve_times_.end());
125 DCHECK(slowest_address_resolve != address_resolve_times_.end());
126
127 // It's possible to get here with a zero resolve time in tests. Avoid
128 // divide-by-zero below by returning early; this data point is invalid anyway.
129 if (slowest_address_resolve->is_zero())
130 return;
131
132 // Compute a percentage showing how much larger the HTTPS query resolve time
133 // was compared to the slowest A or AAAA query.
134 //
135 // Computation happens on TimeDelta objects, which use CheckedNumeric. This
136 // will crash if the system clock leaps forward several hundred millennia
137 // (numeric_limits<int64_t>::max() microseconds ~= 292,000 years).
138 //
139 // Then scale the value of the percent by dividing by `kPercentScale`. Sample
140 // values are bounded between 1 and 20. A recorded sample of 10 means that the
141 // HTTPS query resolve time took 100% of the slower A/AAAA resolve time. A
142 // sample of 20 means that the HTTPS query resolve time was 200% relative to
143 // the A/AAAA resolve time, twice as long.
144 constexpr int64_t kMaxRatio = 20;
145 constexpr int64_t kPercentScale = 10;
146 const int64_t resolve_time_percent = base::ClampFloor<int64_t>(
147 *https_resolve_time_ / *slowest_address_resolve * 100);
148 base::UmaHistogramExactLinear(BuildMetricName("ResolveTimeRatio"),
149 resolve_time_percent / kPercentScale,
150 kMaxRatio);
151
152 if (num_https_records_ > 0) {
153 DCHECK(rcode_https_.has_value());
154 if (*rcode_https_ == HttpssvcDnsRcode::kNoError) {
155 base::UmaHistogramBoolean(BuildMetricName("Parsable"),
156 is_https_parsable_.value_or(false));
157 } else {
158 // Record boolean indicating whether we received an HTTPS record and
159 // an error simultaneously.
160 base::UmaHistogramBoolean(BuildMetricName("RecordWithError"), true);
161 }
162 }
163
164 base::UmaHistogramEnumeration(BuildMetricName("DnsRcode"), *rcode_https_);
165 }
166
167 } // namespace net
168