Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 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 "components/domain_reliability/context.h" | 5 #include "components/domain_reliability/context.h" |
| 6 | 6 |
| 7 #include <algorithm> | 7 #include <algorithm> |
| 8 | 8 |
| 9 #include "base/bind.h" | 9 #include "base/bind.h" |
| 10 #include "base/json/json_writer.h" | 10 #include "base/json/json_writer.h" |
| 11 #include "base/logging.h" | 11 #include "base/logging.h" |
| 12 #include "base/metrics/histogram.h" | 12 #include "base/metrics/histogram.h" |
| 13 #include "base/metrics/sparse_histogram.h" | 13 #include "base/metrics/sparse_histogram.h" |
| 14 #include "base/values.h" | 14 #include "base/values.h" |
| 15 #include "components/domain_reliability/dispatcher.h" | 15 #include "components/domain_reliability/dispatcher.h" |
| 16 #include "components/domain_reliability/uploader.h" | 16 #include "components/domain_reliability/uploader.h" |
| 17 #include "components/domain_reliability/util.h" | 17 #include "components/domain_reliability/util.h" |
| 18 #include "net/base/net_errors.h" | 18 #include "net/base/net_errors.h" |
| 19 #include "net/url_request/url_request_context_getter.h" | 19 #include "net/url_request/url_request_context_getter.h" |
| 20 | 20 |
| 21 using base::DictionaryValue; | 21 using base::DictionaryValue; |
| 22 using base::ListValue; | 22 using base::ListValue; |
| 23 using base::Value; | 23 using base::Value; |
| 24 | 24 |
| 25 namespace domain_reliability { | 25 namespace domain_reliability { |
| 26 | 26 |
| 27 namespace { | |
| 28 typedef std::deque<DomainReliabilityBeacon> BeaconDeque; | |
| 29 typedef BeaconDeque::iterator BeaconIterator; | |
| 30 typedef BeaconDeque::const_iterator BeaconConstIterator; | |
| 31 } // namespace | |
| 32 | |
| 33 DomainReliabilityContext::Factory::~Factory() { | 27 DomainReliabilityContext::Factory::~Factory() { |
| 34 } | 28 } |
| 35 | 29 |
| 36 class DomainReliabilityContext::ResourceState { | |
| 37 public: | |
| 38 ResourceState(DomainReliabilityContext* context, | |
| 39 const DomainReliabilityConfig::Resource* config) | |
| 40 : context(context), | |
| 41 config(config), | |
| 42 successful_requests(0), | |
| 43 failed_requests(0), | |
| 44 uploading_successful_requests(0), | |
| 45 uploading_failed_requests(0) {} | |
| 46 ~ResourceState() {} | |
| 47 | |
| 48 // Serializes the resource state into a Value to be included in an upload. | |
| 49 // If there is nothing to report (no beacons and all request counters are 0), | |
| 50 // returns a scoped_ptr to NULL instead so the resource can be omitted. | |
| 51 scoped_ptr<base::Value> ToValue(base::TimeTicks upload_time) const { | |
| 52 if (successful_requests == 0 && failed_requests == 0) | |
| 53 return scoped_ptr<base::Value>(); | |
| 54 | |
| 55 DictionaryValue* resource_value = new DictionaryValue(); | |
| 56 resource_value->SetString("name", config->name); | |
| 57 resource_value->SetInteger("successful_requests", successful_requests); | |
| 58 resource_value->SetInteger("failed_requests", failed_requests); | |
| 59 | |
| 60 return scoped_ptr<Value>(resource_value); | |
| 61 } | |
| 62 | |
| 63 // Remembers the current state of the resource data when an upload starts. | |
| 64 void MarkUpload() { | |
| 65 DCHECK_EQ(0u, uploading_successful_requests); | |
| 66 DCHECK_EQ(0u, uploading_failed_requests); | |
| 67 uploading_successful_requests = successful_requests; | |
| 68 uploading_failed_requests = failed_requests; | |
| 69 } | |
| 70 | |
| 71 // Uses the state remembered by |MarkUpload| to remove successfully uploaded | |
| 72 // data but keep beacons and request counts added after the upload started. | |
| 73 void CommitUpload() { | |
| 74 successful_requests -= uploading_successful_requests; | |
| 75 failed_requests -= uploading_failed_requests; | |
| 76 uploading_successful_requests = 0; | |
| 77 uploading_failed_requests = 0; | |
| 78 } | |
| 79 | |
| 80 void RollbackUpload() { | |
| 81 uploading_successful_requests = 0; | |
| 82 uploading_failed_requests = 0; | |
| 83 } | |
| 84 | |
| 85 DomainReliabilityContext* context; | |
| 86 const DomainReliabilityConfig::Resource* config; | |
| 87 | |
| 88 uint32 successful_requests; | |
| 89 uint32 failed_requests; | |
| 90 | |
| 91 // State saved during uploads; if an upload succeeds, these are used to | |
| 92 // remove uploaded data from the beacon list and request counters. | |
| 93 uint32 uploading_successful_requests; | |
| 94 uint32 uploading_failed_requests; | |
| 95 | |
| 96 private: | |
| 97 DISALLOW_COPY_AND_ASSIGN(ResourceState); | |
| 98 }; | |
| 99 | |
| 100 // static | 30 // static |
| 101 const size_t DomainReliabilityContext::kMaxQueuedBeacons = 150; | 31 const size_t DomainReliabilityContext::kMaxQueuedBeacons = 150; |
| 102 | 32 |
| 103 DomainReliabilityContext::DomainReliabilityContext( | 33 DomainReliabilityContext::DomainReliabilityContext( |
| 104 MockableTime* time, | 34 MockableTime* time, |
| 105 const DomainReliabilityScheduler::Params& scheduler_params, | 35 const DomainReliabilityScheduler::Params& scheduler_params, |
| 106 const std::string& upload_reporter_string, | 36 const std::string& upload_reporter_string, |
| 107 const base::TimeTicks* last_network_change_time, | 37 const base::TimeTicks* last_network_change_time, |
| 108 DomainReliabilityDispatcher* dispatcher, | 38 DomainReliabilityDispatcher* dispatcher, |
| 109 DomainReliabilityUploader* uploader, | 39 DomainReliabilityUploader* uploader, |
| 110 scoped_ptr<const DomainReliabilityConfig> config) | 40 scoped_ptr<const DomainReliabilityConfig> config) |
| 111 : config_(config.Pass()), | 41 : config_(config.Pass()), |
| 112 time_(time), | 42 time_(time), |
| 113 upload_reporter_string_(upload_reporter_string), | 43 upload_reporter_string_(upload_reporter_string), |
| 114 scheduler_(time, | 44 scheduler_(time, |
| 115 config_->collectors.size(), | 45 config_->collectors.size(), |
| 116 scheduler_params, | 46 scheduler_params, |
| 117 base::Bind(&DomainReliabilityContext::ScheduleUpload, | 47 base::Bind(&DomainReliabilityContext::ScheduleUpload, |
| 118 base::Unretained(this))), | 48 base::Unretained(this))), |
| 119 dispatcher_(dispatcher), | 49 dispatcher_(dispatcher), |
| 120 uploader_(uploader), | 50 uploader_(uploader), |
| 121 uploading_beacons_size_(0), | 51 uploading_beacons_size_(0), |
| 122 last_network_change_time_(last_network_change_time), | 52 last_network_change_time_(last_network_change_time), |
| 123 weak_factory_(this) { | 53 weak_factory_(this) { |
| 124 InitializeResourceStates(); | |
| 125 } | 54 } |
| 126 | 55 |
| 127 DomainReliabilityContext::~DomainReliabilityContext() {} | 56 DomainReliabilityContext::~DomainReliabilityContext() { |
| 57 ClearBeacons(); | |
| 58 } | |
| 128 | 59 |
| 129 void DomainReliabilityContext::OnBeacon(const GURL& url, | 60 void DomainReliabilityContext::OnBeacon( |
| 130 const DomainReliabilityBeacon& beacon) { | 61 scoped_ptr<DomainReliabilityBeacon> beacon) { |
| 131 size_t index = config_->GetResourceIndexForUrl(url); | 62 bool success = (beacon->status == "ok"); |
| 132 if (index == DomainReliabilityConfig::kInvalidResourceIndex) | 63 |
| 64 bool reported = config().DecideIfShouldReportRequest(success); | |
| 65 UMA_HISTOGRAM_BOOLEAN("DomainReliability.BeaconReported", reported); | |
| 66 if (!reported) { | |
| 67 // If the beacon isn't queued to be reported, it definitely cannot evict | |
| 68 // an older beacon. (This histogram is also logged below based on whether | |
| 69 // an older beacon was actually evicted.) | |
| 70 UMA_HISTOGRAM_BOOLEAN("DomainReliability.OnBeaconDidEvict", false); | |
|
Alexei Svitkine (slow)
2015/11/20 17:40:58
Nit: Can you make a helper function to log this hi
Deprecated (see juliatuttle)
2015/11/20 17:46:56
Done.
| |
| 133 return; | 71 return; |
| 134 DCHECK_GT(states_.size(), index); | |
| 135 | |
| 136 bool success = (beacon.status == "ok"); | |
| 137 | |
| 138 ResourceState* state = states_[index]; | |
| 139 if (success) | |
| 140 ++state->successful_requests; | |
| 141 else | |
| 142 ++state->failed_requests; | |
| 143 | |
| 144 bool reported = false; | |
| 145 bool evicted = false; | |
| 146 if (state->config->DecideIfShouldReportRequest(success)) { | |
| 147 beacons_.push_back(beacon); | |
| 148 beacons_.back().resource = state->config->name; | |
| 149 if (beacons_.size() > kMaxQueuedBeacons) { | |
| 150 RemoveOldestBeacon(); | |
| 151 evicted = true; | |
| 152 } | |
| 153 scheduler_.OnBeaconAdded(); | |
| 154 reported = true; | |
| 155 UMA_HISTOGRAM_SPARSE_SLOWLY("DomainReliability.ReportedBeaconError", | |
| 156 -beacon.chrome_error); | |
| 157 if (!beacon.server_ip.empty()) { | |
| 158 UMA_HISTOGRAM_SPARSE_SLOWLY( | |
| 159 "DomainReliability.ReportedBeaconError_HasServerIP", | |
| 160 -beacon.chrome_error); | |
| 161 } | |
| 162 // TODO(ttuttle): Histogram HTTP response code? | |
| 163 } | 72 } |
| 164 | 73 |
| 165 UMA_HISTOGRAM_BOOLEAN("DomainReliability.BeaconReported", reported); | 74 bool evicted = false; |
| 75 | |
| 76 UMA_HISTOGRAM_SPARSE_SLOWLY("DomainReliability.ReportedBeaconError", | |
| 77 -beacon->chrome_error); | |
| 78 if (!beacon->server_ip.empty()) { | |
| 79 UMA_HISTOGRAM_SPARSE_SLOWLY( | |
| 80 "DomainReliability.ReportedBeaconError_HasServerIP", | |
| 81 -beacon->chrome_error); | |
| 82 } | |
| 83 // TODO(ttuttle): Histogram HTTP response code? | |
| 84 | |
| 85 beacons_.push_back(beacon.release()); | |
| 86 if (beacons_.size() > kMaxQueuedBeacons) { | |
| 87 RemoveOldestBeacon(); | |
| 88 evicted = true; | |
| 89 } | |
| 90 | |
| 91 scheduler_.OnBeaconAdded(); | |
| 92 | |
| 166 UMA_HISTOGRAM_BOOLEAN("DomainReliability.OnBeaconDidEvict", evicted); | 93 UMA_HISTOGRAM_BOOLEAN("DomainReliability.OnBeaconDidEvict", evicted); |
| 167 } | 94 } |
| 168 | 95 |
| 169 void DomainReliabilityContext::ClearBeacons() { | 96 void DomainReliabilityContext::ClearBeacons() { |
| 170 for (auto& state : states_) { | 97 STLDeleteElements(&beacons_); |
| 171 state->successful_requests = 0; | |
| 172 state->failed_requests = 0; | |
| 173 state->uploading_successful_requests = 0; | |
| 174 state->uploading_failed_requests = 0; | |
| 175 } | |
| 176 beacons_.clear(); | 98 beacons_.clear(); |
| 177 uploading_beacons_size_ = 0; | 99 uploading_beacons_size_ = 0; |
| 178 } | 100 } |
| 179 | 101 |
| 180 scoped_ptr<base::Value> DomainReliabilityContext::GetWebUIData() const { | 102 scoped_ptr<Value> DomainReliabilityContext::GetWebUIData() const { |
| 181 base::DictionaryValue* context_value = new base::DictionaryValue(); | 103 DictionaryValue* context_value = new DictionaryValue(); |
| 182 | 104 |
| 183 context_value->SetString("domain", config().domain); | 105 context_value->SetString("origin", config().origin.spec()); |
| 184 context_value->SetInteger("beacon_count", static_cast<int>(beacons_.size())); | 106 context_value->SetInteger("beacon_count", static_cast<int>(beacons_.size())); |
| 185 context_value->SetInteger("uploading_beacon_count", | 107 context_value->SetInteger("uploading_beacon_count", |
| 186 static_cast<int>(uploading_beacons_size_)); | 108 static_cast<int>(uploading_beacons_size_)); |
| 187 context_value->Set("scheduler", scheduler_.GetWebUIData()); | 109 context_value->Set("scheduler", scheduler_.GetWebUIData()); |
| 188 | 110 |
| 189 return scoped_ptr<base::Value>(context_value); | 111 return scoped_ptr<Value>(context_value); |
| 190 } | 112 } |
| 191 | 113 |
| 192 void DomainReliabilityContext::GetQueuedBeaconsForTesting( | 114 void DomainReliabilityContext::GetQueuedBeaconsForTesting( |
| 193 std::vector<DomainReliabilityBeacon>* beacons_out) const { | 115 std::vector<const DomainReliabilityBeacon*>* beacons_out) const { |
| 116 DCHECK(this); | |
| 117 DCHECK(beacons_out); | |
| 194 beacons_out->assign(beacons_.begin(), beacons_.end()); | 118 beacons_out->assign(beacons_.begin(), beacons_.end()); |
| 195 } | 119 } |
| 196 | 120 |
| 197 void DomainReliabilityContext::GetRequestCountsForTesting( | |
| 198 size_t resource_index, | |
| 199 uint32_t* successful_requests_out, | |
| 200 uint32_t* failed_requests_out) const { | |
| 201 DCHECK_NE(DomainReliabilityConfig::kInvalidResourceIndex, resource_index); | |
| 202 DCHECK_GT(states_.size(), resource_index); | |
| 203 | |
| 204 const ResourceState& state = *states_[resource_index]; | |
| 205 *successful_requests_out = state.successful_requests; | |
| 206 *failed_requests_out = state.failed_requests; | |
| 207 } | |
| 208 | |
| 209 void DomainReliabilityContext::InitializeResourceStates() { | |
| 210 for (auto& resource : config_->resources) | |
| 211 states_.push_back(new ResourceState(this, resource)); | |
| 212 } | |
| 213 | |
| 214 void DomainReliabilityContext::ScheduleUpload( | 121 void DomainReliabilityContext::ScheduleUpload( |
| 215 base::TimeDelta min_delay, | 122 base::TimeDelta min_delay, |
| 216 base::TimeDelta max_delay) { | 123 base::TimeDelta max_delay) { |
| 217 dispatcher_->ScheduleTask( | 124 dispatcher_->ScheduleTask( |
| 218 base::Bind( | 125 base::Bind( |
| 219 &DomainReliabilityContext::StartUpload, | 126 &DomainReliabilityContext::StartUpload, |
| 220 weak_factory_.GetWeakPtr()), | 127 weak_factory_.GetWeakPtr()), |
| 221 min_delay, | 128 min_delay, |
| 222 max_delay); | 129 max_delay); |
| 223 } | 130 } |
| 224 | 131 |
| 225 void DomainReliabilityContext::StartUpload() { | 132 void DomainReliabilityContext::StartUpload() { |
| 226 MarkUpload(); | 133 MarkUpload(); |
| 227 | 134 |
| 228 DCHECK(upload_time_.is_null()); | 135 DCHECK(upload_time_.is_null()); |
| 229 upload_time_ = time_->NowTicks(); | 136 upload_time_ = time_->NowTicks(); |
| 137 size_t collector_index = scheduler_.OnUploadStart(); | |
| 138 const GURL& collector_url = *config().collectors[collector_index]; | |
| 139 | |
| 230 std::string report_json; | 140 std::string report_json; |
| 231 base::JSONWriter::Write(*CreateReport(upload_time_), &report_json); | 141 scoped_ptr<const Value> report = CreateReport(upload_time_, collector_url); |
| 232 | 142 base::JSONWriter::Write(*report, &report_json); |
| 233 size_t collector_index = scheduler_.OnUploadStart(); | 143 report.reset(); |
| 234 | 144 |
| 235 uploader_->UploadReport( | 145 uploader_->UploadReport( |
| 236 report_json, | 146 report_json, collector_url, |
| 237 config_->collectors[collector_index]->upload_url, | 147 base::Bind(&DomainReliabilityContext::OnUploadComplete, |
| 238 base::Bind( | 148 weak_factory_.GetWeakPtr())); |
| 239 &DomainReliabilityContext::OnUploadComplete, | |
| 240 weak_factory_.GetWeakPtr())); | |
| 241 | 149 |
| 242 UMA_HISTOGRAM_SPARSE_SLOWLY("DomainReliability.UploadCollectorIndex", | 150 UMA_HISTOGRAM_SPARSE_SLOWLY("DomainReliability.UploadCollectorIndex", |
| 243 static_cast<int>(collector_index)); | 151 static_cast<int>(collector_index)); |
| 244 if (!last_upload_time_.is_null()) { | 152 if (!last_upload_time_.is_null()) { |
| 245 UMA_HISTOGRAM_LONG_TIMES("DomainReliability.UploadInterval", | 153 UMA_HISTOGRAM_LONG_TIMES("DomainReliability.UploadInterval", |
| 246 upload_time_ - last_upload_time_); | 154 upload_time_ - last_upload_time_); |
| 247 } | 155 } |
| 248 } | 156 } |
| 249 | 157 |
| 250 void DomainReliabilityContext::OnUploadComplete( | 158 void DomainReliabilityContext::OnUploadComplete( |
| (...skipping 12 matching lines...) Expand all Loading... | |
| 263 DCHECK(!upload_time_.is_null()); | 171 DCHECK(!upload_time_.is_null()); |
| 264 UMA_HISTOGRAM_MEDIUM_TIMES("DomainReliability.UploadDuration", | 172 UMA_HISTOGRAM_MEDIUM_TIMES("DomainReliability.UploadDuration", |
| 265 now - upload_time_); | 173 now - upload_time_); |
| 266 UMA_HISTOGRAM_LONG_TIMES("DomainReliability.UploadCollectorRetryDelay", | 174 UMA_HISTOGRAM_LONG_TIMES("DomainReliability.UploadCollectorRetryDelay", |
| 267 scheduler_.last_collector_retry_delay()); | 175 scheduler_.last_collector_retry_delay()); |
| 268 last_upload_time_ = upload_time_; | 176 last_upload_time_ = upload_time_; |
| 269 upload_time_ = base::TimeTicks(); | 177 upload_time_ = base::TimeTicks(); |
| 270 } | 178 } |
| 271 | 179 |
| 272 scoped_ptr<const Value> DomainReliabilityContext::CreateReport( | 180 scoped_ptr<const Value> DomainReliabilityContext::CreateReport( |
| 273 base::TimeTicks upload_time) const { | 181 base::TimeTicks upload_time, |
| 182 const GURL& collector_url) const { | |
| 274 scoped_ptr<ListValue> beacons_value(new ListValue()); | 183 scoped_ptr<ListValue> beacons_value(new ListValue()); |
| 275 for (const auto& beacon : beacons_) { | 184 for (const auto& beacon : beacons_) { |
| 276 beacons_value->Append( | 185 beacons_value->Append(beacon->ToValue(upload_time, |
| 277 beacon.ToValue(upload_time, *last_network_change_time_)); | 186 *last_network_change_time_, |
| 278 } | 187 collector_url, |
| 279 | 188 config().path_prefixes)); |
| 280 scoped_ptr<ListValue> resources_value(new ListValue()); | |
| 281 for (const auto& state : states_) { | |
| 282 scoped_ptr<Value> resource_report = state->ToValue(upload_time); | |
| 283 if (resource_report) | |
| 284 resources_value->Append(resource_report.release()); | |
| 285 } | 189 } |
| 286 | 190 |
| 287 scoped_ptr<DictionaryValue> report_value(new DictionaryValue()); | 191 scoped_ptr<DictionaryValue> report_value(new DictionaryValue()); |
| 288 if (!config().version.empty()) | |
| 289 report_value->SetString("config_version", config().version); | |
| 290 report_value->SetString("reporter", upload_reporter_string_); | 192 report_value->SetString("reporter", upload_reporter_string_); |
| 291 report_value->Set("entries", beacons_value.release()); | 193 report_value->Set("entries", beacons_value.release()); |
| 292 if (!resources_value->empty()) | |
| 293 report_value->Set("resources", resources_value.release()); | |
| 294 | 194 |
| 295 return report_value.Pass(); | 195 return report_value.Pass(); |
| 296 } | 196 } |
| 297 | 197 |
| 298 void DomainReliabilityContext::MarkUpload() { | 198 void DomainReliabilityContext::MarkUpload() { |
| 299 for (auto& state : states_) | |
| 300 state->MarkUpload(); | |
| 301 DCHECK_EQ(0u, uploading_beacons_size_); | 199 DCHECK_EQ(0u, uploading_beacons_size_); |
| 302 uploading_beacons_size_ = beacons_.size(); | 200 uploading_beacons_size_ = beacons_.size(); |
| 303 DCHECK_NE(0u, uploading_beacons_size_); | 201 DCHECK_NE(0u, uploading_beacons_size_); |
| 304 } | 202 } |
| 305 | 203 |
| 306 void DomainReliabilityContext::CommitUpload() { | 204 void DomainReliabilityContext::CommitUpload() { |
| 307 for (auto& state : states_) | 205 auto begin = beacons_.begin(); |
| 308 state->CommitUpload(); | 206 auto end = begin + uploading_beacons_size_; |
| 309 BeaconIterator begin = beacons_.begin(); | 207 STLDeleteContainerPointers(begin, end); |
| 310 BeaconIterator end = begin + uploading_beacons_size_; | |
| 311 beacons_.erase(begin, end); | 208 beacons_.erase(begin, end); |
| 312 DCHECK_NE(0u, uploading_beacons_size_); | 209 DCHECK_NE(0u, uploading_beacons_size_); |
| 313 uploading_beacons_size_ = 0; | 210 uploading_beacons_size_ = 0; |
| 314 } | 211 } |
| 315 | 212 |
| 316 void DomainReliabilityContext::RollbackUpload() { | 213 void DomainReliabilityContext::RollbackUpload() { |
| 317 for (auto& state : states_) | |
| 318 state->RollbackUpload(); | |
| 319 DCHECK_NE(0u, uploading_beacons_size_); | 214 DCHECK_NE(0u, uploading_beacons_size_); |
| 320 uploading_beacons_size_ = 0; | 215 uploading_beacons_size_ = 0; |
| 321 } | 216 } |
| 322 | 217 |
| 323 void DomainReliabilityContext::RemoveOldestBeacon() { | 218 void DomainReliabilityContext::RemoveOldestBeacon() { |
| 324 DCHECK(!beacons_.empty()); | 219 DCHECK(!beacons_.empty()); |
| 325 | 220 |
| 326 VLOG(1) << "Beacon queue for " << config().domain << " full; " | 221 VLOG(1) << "Beacon queue for " << config().origin << " full; " |
| 327 << "removing oldest beacon"; | 222 << "removing oldest beacon"; |
| 328 | 223 |
| 224 delete beacons_.front(); | |
| 329 beacons_.pop_front(); | 225 beacons_.pop_front(); |
| 330 | 226 |
| 331 // If that just removed a beacon counted in uploading_beacons_size_, decrement | 227 // If that just removed a beacon counted in uploading_beacons_size_, decrement |
| 332 // that. | 228 // that. |
| 333 if (uploading_beacons_size_ > 0) | 229 if (uploading_beacons_size_ > 0) |
| 334 --uploading_beacons_size_; | 230 --uploading_beacons_size_; |
| 335 } | 231 } |
| 336 | 232 |
| 337 } // namespace domain_reliability | 233 } // namespace domain_reliability |
| OLD | NEW |