// Copyright 2020 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "net/dns/httpssvc_metrics.h" #include #include "base/containers/contains.h" #include "base/feature_list.h" #include "base/metrics/histogram.h" #include "base/metrics/histogram_base.h" #include "base/metrics/histogram_functions.h" #include "base/notreached.h" #include "base/numerics/clamped_math.h" #include "base/strings/string_split.h" #include "base/strings/string_util.h" #include "net/base/features.h" #include "net/dns/dns_util.h" #include "net/dns/public/dns_protocol.h" namespace net { enum HttpssvcDnsRcode TranslateDnsRcodeForHttpssvcExperiment(uint8_t rcode) { switch (rcode) { case dns_protocol::kRcodeNOERROR: return HttpssvcDnsRcode::kNoError; case dns_protocol::kRcodeFORMERR: return HttpssvcDnsRcode::kFormErr; case dns_protocol::kRcodeSERVFAIL: return HttpssvcDnsRcode::kServFail; case dns_protocol::kRcodeNXDOMAIN: return HttpssvcDnsRcode::kNxDomain; case dns_protocol::kRcodeNOTIMP: return HttpssvcDnsRcode::kNotImp; case dns_protocol::kRcodeREFUSED: return HttpssvcDnsRcode::kRefused; default: return HttpssvcDnsRcode::kUnrecognizedRcode; } NOTREACHED(); } HttpssvcMetrics::HttpssvcMetrics(bool secure) : secure_(secure) {} HttpssvcMetrics::~HttpssvcMetrics() { RecordMetrics(); } void HttpssvcMetrics::SaveForAddressQuery(base::TimeDelta resolve_time, enum HttpssvcDnsRcode rcode) { address_resolve_times_.push_back(resolve_time); if (rcode != HttpssvcDnsRcode::kNoError) disqualified_ = true; } void HttpssvcMetrics::SaveAddressQueryFailure() { disqualified_ = true; } void HttpssvcMetrics::SaveForHttps(enum HttpssvcDnsRcode rcode, const std::vector& condensed_records, base::TimeDelta https_resolve_time) { DCHECK(!rcode_https_.has_value()); rcode_https_ = rcode; num_https_records_ = condensed_records.size(); // We only record one "parsable" sample per HTTPS query. In case multiple // matching records are present in the response, we combine their parsable // values with logical AND. const bool parsable = !base::Contains(condensed_records, false); DCHECK(!is_https_parsable_.has_value()); is_https_parsable_ = parsable; DCHECK(!https_resolve_time_.has_value()); https_resolve_time_ = https_resolve_time; } std::string HttpssvcMetrics::BuildMetricName(std::string_view leaf_name) const { std::string_view type_str = "RecordHttps"; std::string_view secure = secure_ ? "Secure" : "Insecure"; // This part is just a legacy from old experiments but now meaningless. std::string_view expectation = "ExpectNoerror"; // Example metric name: // Net.DNS.HTTPSSVC.RecordHttps.Secure.ExpectNoerror.DnsRcode // TODO(crbug.com/1366422): Simplify the metric names. return base::JoinString( {"Net.DNS.HTTPSSVC", type_str, secure, expectation, leaf_name}, "."); } void HttpssvcMetrics::RecordMetrics() { DCHECK(!already_recorded_); already_recorded_ = true; // We really have no metrics to record without an HTTPS query resolve time and // `address_resolve_times_`. If this HttpssvcMetrics is in an inconsistent // state, disqualify any metrics from being recorded. if (!https_resolve_time_.has_value() || address_resolve_times_.empty()) { disqualified_ = true; } if (disqualified_) return; base::UmaHistogramMediumTimes(BuildMetricName("ResolveTimeExperimental"), *https_resolve_time_); // Record the address resolve times. const std::string kMetricResolveTimeAddressRecord = BuildMetricName("ResolveTimeAddress"); for (base::TimeDelta resolve_time_other : address_resolve_times_) { base::UmaHistogramMediumTimes(kMetricResolveTimeAddressRecord, resolve_time_other); } // ResolveTimeRatio is the HTTPS query resolve time divided by the slower of // the A or AAAA resolve times. Arbitrarily choosing precision at two decimal // places. std::vector::iterator slowest_address_resolve = std::max_element(address_resolve_times_.begin(), address_resolve_times_.end()); DCHECK(slowest_address_resolve != address_resolve_times_.end()); // It's possible to get here with a zero resolve time in tests. Avoid // divide-by-zero below by returning early; this data point is invalid anyway. if (slowest_address_resolve->is_zero()) return; // Compute a percentage showing how much larger the HTTPS query resolve time // was compared to the slowest A or AAAA query. // // Computation happens on TimeDelta objects, which use CheckedNumeric. This // will crash if the system clock leaps forward several hundred millennia // (numeric_limits::max() microseconds ~= 292,000 years). // // Then scale the value of the percent by dividing by `kPercentScale`. Sample // values are bounded between 1 and 20. A recorded sample of 10 means that the // HTTPS query resolve time took 100% of the slower A/AAAA resolve time. A // sample of 20 means that the HTTPS query resolve time was 200% relative to // the A/AAAA resolve time, twice as long. constexpr int64_t kMaxRatio = 20; constexpr int64_t kPercentScale = 10; const int64_t resolve_time_percent = base::ClampFloor( *https_resolve_time_ / *slowest_address_resolve * 100); base::UmaHistogramExactLinear(BuildMetricName("ResolveTimeRatio"), resolve_time_percent / kPercentScale, kMaxRatio); if (num_https_records_ > 0) { DCHECK(rcode_https_.has_value()); if (*rcode_https_ == HttpssvcDnsRcode::kNoError) { base::UmaHistogramBoolean(BuildMetricName("Parsable"), is_https_parsable_.value_or(false)); } else { // Record boolean indicating whether we received an HTTPS record and // an error simultaneously. base::UmaHistogramBoolean(BuildMetricName("RecordWithError"), true); } } base::UmaHistogramEnumeration(BuildMetricName("DnsRcode"), *rcode_https_); } } // namespace net