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