You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1441 lines
52 KiB
1441 lines
52 KiB
// |
|
// Copyright (C) 2012 The Android Open Source Project |
|
// |
|
// Licensed under the Apache License, Version 2.0 (the "License"); |
|
// you may not use this file except in compliance with the License. |
|
// You may obtain a copy of the License at |
|
// |
|
// http://www.apache.org/licenses/LICENSE-2.0 |
|
// |
|
// Unless required by applicable law or agreed to in writing, software |
|
// distributed under the License is distributed on an "AS IS" BASIS, |
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
// See the License for the specific language governing permissions and |
|
// limitations under the License. |
|
// |
|
|
|
#include "update_engine/payload_state.h" |
|
|
|
#include <algorithm> |
|
#include <string> |
|
|
|
#include <base/logging.h> |
|
#include <base/strings/string_util.h> |
|
#include <base/strings/stringprintf.h> |
|
#include <metrics/metrics_library.h> |
|
#include <policy/device_policy.h> |
|
|
|
#include "update_engine/common/clock.h" |
|
#include "update_engine/common/constants.h" |
|
#include "update_engine/common/error_code_utils.h" |
|
#include "update_engine/common/hardware_interface.h" |
|
#include "update_engine/common/prefs.h" |
|
#include "update_engine/common/utils.h" |
|
#include "update_engine/connection_manager_interface.h" |
|
#include "update_engine/metrics_utils.h" |
|
#include "update_engine/omaha_request_params.h" |
|
#include "update_engine/payload_consumer/install_plan.h" |
|
#include "update_engine/system_state.h" |
|
|
|
using base::Time; |
|
using base::TimeDelta; |
|
using std::min; |
|
using std::string; |
|
|
|
namespace chromeos_update_engine { |
|
|
|
const TimeDelta PayloadState::kDurationSlack = TimeDelta::FromSeconds(600); |
|
|
|
// We want to upperbound backoffs to 16 days |
|
static const int kMaxBackoffDays = 16; |
|
|
|
// We want to randomize retry attempts after the backoff by +/- 6 hours. |
|
static const uint32_t kMaxBackoffFuzzMinutes = 12 * 60; |
|
|
|
PayloadState::PayloadState() |
|
: prefs_(nullptr), |
|
using_p2p_for_downloading_(false), |
|
p2p_num_attempts_(0), |
|
payload_attempt_number_(0), |
|
full_payload_attempt_number_(0), |
|
url_index_(0), |
|
url_failure_count_(0), |
|
url_switch_count_(0), |
|
attempt_num_bytes_downloaded_(0), |
|
attempt_connection_type_(metrics::ConnectionType::kUnknown), |
|
attempt_error_code_(ErrorCode::kSuccess), |
|
attempt_type_(AttemptType::kUpdate) { |
|
for (int i = 0; i <= kNumDownloadSources; i++) |
|
total_bytes_downloaded_[i] = current_bytes_downloaded_[i] = 0; |
|
} |
|
|
|
bool PayloadState::Initialize(SystemState* system_state) { |
|
system_state_ = system_state; |
|
prefs_ = system_state_->prefs(); |
|
powerwash_safe_prefs_ = system_state_->powerwash_safe_prefs(); |
|
LoadResponseSignature(); |
|
LoadPayloadAttemptNumber(); |
|
LoadFullPayloadAttemptNumber(); |
|
LoadUrlIndex(); |
|
LoadUrlFailureCount(); |
|
LoadUrlSwitchCount(); |
|
LoadBackoffExpiryTime(); |
|
LoadUpdateTimestampStart(); |
|
// The LoadUpdateDurationUptime() method relies on LoadUpdateTimestampStart() |
|
// being called before it. Don't reorder. |
|
LoadUpdateDurationUptime(); |
|
for (int i = 0; i < kNumDownloadSources; i++) { |
|
DownloadSource source = static_cast<DownloadSource>(i); |
|
LoadCurrentBytesDownloaded(source); |
|
LoadTotalBytesDownloaded(source); |
|
} |
|
LoadNumReboots(); |
|
LoadNumResponsesSeen(); |
|
LoadRollbackVersion(); |
|
LoadP2PFirstAttemptTimestamp(); |
|
LoadP2PNumAttempts(); |
|
return true; |
|
} |
|
|
|
void PayloadState::SetResponse(const OmahaResponse& omaha_response) { |
|
// Always store the latest response. |
|
response_ = omaha_response; |
|
|
|
// Compute the candidate URLs first as they are used to calculate the |
|
// response signature so that a change in enterprise policy for |
|
// HTTP downloads being enabled or not could be honored as soon as the |
|
// next update check happens. |
|
ComputeCandidateUrls(); |
|
|
|
// Check if the "signature" of this response (i.e. the fields we care about) |
|
// has changed. |
|
string new_response_signature = CalculateResponseSignature(); |
|
bool has_response_changed = (response_signature_ != new_response_signature); |
|
|
|
// If the response has changed, we should persist the new signature and |
|
// clear away all the existing state. |
|
if (has_response_changed) { |
|
LOG(INFO) << "Resetting all persisted state as this is a new response"; |
|
SetNumResponsesSeen(num_responses_seen_ + 1); |
|
SetResponseSignature(new_response_signature); |
|
ResetPersistedState(); |
|
return; |
|
} |
|
|
|
// Always start from payload index 0, even for resume, to download partition |
|
// info from previous payloads. |
|
payload_index_ = 0; |
|
|
|
// This is the earliest point at which we can validate whether the URL index |
|
// we loaded from the persisted state is a valid value. If the response |
|
// hasn't changed but the URL index is invalid, it's indicative of some |
|
// tampering of the persisted state. |
|
if (payload_index_ >= candidate_urls_.size() || |
|
url_index_ >= candidate_urls_[payload_index_].size()) { |
|
LOG(INFO) << "Resetting all payload state as the url index seems to have " |
|
"been tampered with"; |
|
ResetPersistedState(); |
|
return; |
|
} |
|
|
|
// Update the current download source which depends on the latest value of |
|
// the response. |
|
UpdateCurrentDownloadSource(); |
|
} |
|
|
|
void PayloadState::SetUsingP2PForDownloading(bool value) { |
|
using_p2p_for_downloading_ = value; |
|
// Update the current download source which depends on whether we are |
|
// using p2p or not. |
|
UpdateCurrentDownloadSource(); |
|
} |
|
|
|
void PayloadState::DownloadComplete() { |
|
LOG(INFO) << "Payload downloaded successfully"; |
|
IncrementPayloadAttemptNumber(); |
|
IncrementFullPayloadAttemptNumber(); |
|
} |
|
|
|
void PayloadState::DownloadProgress(size_t count) { |
|
if (count == 0) |
|
return; |
|
|
|
CalculateUpdateDurationUptime(); |
|
UpdateBytesDownloaded(count); |
|
|
|
// We've received non-zero bytes from a recent download operation. Since our |
|
// URL failure count is meant to penalize a URL only for consecutive |
|
// failures, downloading bytes successfully means we should reset the failure |
|
// count (as we know at least that the URL is working). In future, we can |
|
// design this to be more sophisticated to check for more intelligent failure |
|
// patterns, but right now, even 1 byte downloaded will mark the URL to be |
|
// good unless it hits 10 (or configured number of) consecutive failures |
|
// again. |
|
|
|
if (GetUrlFailureCount() == 0) |
|
return; |
|
|
|
LOG(INFO) << "Resetting failure count of Url" << GetUrlIndex() |
|
<< " to 0 as we received " << count << " bytes successfully"; |
|
SetUrlFailureCount(0); |
|
} |
|
|
|
void PayloadState::AttemptStarted(AttemptType attempt_type) { |
|
// Flush previous state from abnormal attempt failure, if any. |
|
ReportAndClearPersistedAttemptMetrics(); |
|
|
|
attempt_type_ = attempt_type; |
|
|
|
ClockInterface *clock = system_state_->clock(); |
|
attempt_start_time_boot_ = clock->GetBootTime(); |
|
attempt_start_time_monotonic_ = clock->GetMonotonicTime(); |
|
attempt_num_bytes_downloaded_ = 0; |
|
|
|
metrics::ConnectionType type; |
|
ConnectionType network_connection_type; |
|
ConnectionTethering tethering; |
|
ConnectionManagerInterface* connection_manager = |
|
system_state_->connection_manager(); |
|
if (!connection_manager->GetConnectionProperties(&network_connection_type, |
|
&tethering)) { |
|
LOG(ERROR) << "Failed to determine connection type."; |
|
type = metrics::ConnectionType::kUnknown; |
|
} else { |
|
type = metrics_utils::GetConnectionType(network_connection_type, tethering); |
|
} |
|
attempt_connection_type_ = type; |
|
|
|
if (attempt_type == AttemptType::kUpdate) |
|
PersistAttemptMetrics(); |
|
} |
|
|
|
void PayloadState::UpdateResumed() { |
|
LOG(INFO) << "Resuming an update that was previously started."; |
|
UpdateNumReboots(); |
|
AttemptStarted(AttemptType::kUpdate); |
|
} |
|
|
|
void PayloadState::UpdateRestarted() { |
|
LOG(INFO) << "Starting a new update"; |
|
ResetDownloadSourcesOnNewUpdate(); |
|
SetNumReboots(0); |
|
AttemptStarted(AttemptType::kUpdate); |
|
} |
|
|
|
void PayloadState::UpdateSucceeded() { |
|
// Send the relevant metrics that are tracked in this class to UMA. |
|
CalculateUpdateDurationUptime(); |
|
SetUpdateTimestampEnd(system_state_->clock()->GetWallclockTime()); |
|
|
|
switch (attempt_type_) { |
|
case AttemptType::kUpdate: |
|
CollectAndReportAttemptMetrics(ErrorCode::kSuccess); |
|
CollectAndReportSuccessfulUpdateMetrics(); |
|
ClearPersistedAttemptMetrics(); |
|
break; |
|
|
|
case AttemptType::kRollback: |
|
metrics::ReportRollbackMetrics(system_state_, |
|
metrics::RollbackResult::kSuccess); |
|
break; |
|
} |
|
attempt_error_code_ = ErrorCode::kSuccess; |
|
|
|
// Reset the number of responses seen since it counts from the last |
|
// successful update, e.g. now. |
|
SetNumResponsesSeen(0); |
|
SetPayloadIndex(0); |
|
|
|
CreateSystemUpdatedMarkerFile(); |
|
} |
|
|
|
void PayloadState::UpdateFailed(ErrorCode error) { |
|
ErrorCode base_error = utils::GetBaseErrorCode(error); |
|
LOG(INFO) << "Updating payload state for error code: " << base_error |
|
<< " (" << utils::ErrorCodeToString(base_error) << ")"; |
|
attempt_error_code_ = base_error; |
|
|
|
if (candidate_urls_.size() == 0) { |
|
// This means we got this error even before we got a valid Omaha response |
|
// or don't have any valid candidates in the Omaha response. |
|
// So we should not advance the url_index_ in such cases. |
|
LOG(INFO) << "Ignoring failures until we get a valid Omaha response."; |
|
return; |
|
} |
|
|
|
switch (attempt_type_) { |
|
case AttemptType::kUpdate: |
|
CollectAndReportAttemptMetrics(base_error); |
|
ClearPersistedAttemptMetrics(); |
|
break; |
|
|
|
case AttemptType::kRollback: |
|
metrics::ReportRollbackMetrics(system_state_, |
|
metrics::RollbackResult::kFailed); |
|
break; |
|
} |
|
|
|
|
|
switch (base_error) { |
|
// Errors which are good indicators of a problem with a particular URL or |
|
// the protocol used in the URL or entities in the communication channel |
|
// (e.g. proxies). We should try the next available URL in the next update |
|
// check to quickly recover from these errors. |
|
case ErrorCode::kPayloadHashMismatchError: |
|
case ErrorCode::kPayloadSizeMismatchError: |
|
case ErrorCode::kDownloadPayloadVerificationError: |
|
case ErrorCode::kDownloadPayloadPubKeyVerificationError: |
|
case ErrorCode::kSignedDeltaPayloadExpectedError: |
|
case ErrorCode::kDownloadInvalidMetadataMagicString: |
|
case ErrorCode::kDownloadSignatureMissingInManifest: |
|
case ErrorCode::kDownloadManifestParseError: |
|
case ErrorCode::kDownloadMetadataSignatureError: |
|
case ErrorCode::kDownloadMetadataSignatureVerificationError: |
|
case ErrorCode::kDownloadMetadataSignatureMismatch: |
|
case ErrorCode::kDownloadOperationHashVerificationError: |
|
case ErrorCode::kDownloadOperationExecutionError: |
|
case ErrorCode::kDownloadOperationHashMismatch: |
|
case ErrorCode::kDownloadInvalidMetadataSize: |
|
case ErrorCode::kDownloadInvalidMetadataSignature: |
|
case ErrorCode::kDownloadOperationHashMissingError: |
|
case ErrorCode::kDownloadMetadataSignatureMissingError: |
|
case ErrorCode::kPayloadMismatchedType: |
|
case ErrorCode::kUnsupportedMajorPayloadVersion: |
|
case ErrorCode::kUnsupportedMinorPayloadVersion: |
|
case ErrorCode::kPayloadTimestampError: |
|
IncrementUrlIndex(); |
|
break; |
|
|
|
// Errors which seem to be just transient network/communication related |
|
// failures and do not indicate any inherent problem with the URL itself. |
|
// So, we should keep the current URL but just increment the |
|
// failure count to give it more chances. This way, while we maximize our |
|
// chances of downloading from the URLs that appear earlier in the response |
|
// (because download from a local server URL that appears earlier in a |
|
// response is preferable than downloading from the next URL which could be |
|
// a internet URL and thus could be more expensive). |
|
|
|
case ErrorCode::kError: |
|
case ErrorCode::kDownloadTransferError: |
|
case ErrorCode::kDownloadWriteError: |
|
case ErrorCode::kDownloadStateInitializationError: |
|
case ErrorCode::kOmahaErrorInHTTPResponse: // Aggregate for HTTP errors. |
|
IncrementFailureCount(); |
|
break; |
|
|
|
// Errors which are not specific to a URL and hence shouldn't result in |
|
// the URL being penalized. This can happen in two cases: |
|
// 1. We haven't started downloading anything: These errors don't cost us |
|
// anything in terms of actual payload bytes, so we should just do the |
|
// regular retries at the next update check. |
|
// 2. We have successfully downloaded the payload: In this case, the |
|
// payload attempt number would have been incremented and would take care |
|
// of the backoff at the next update check. |
|
// In either case, there's no need to update URL index or failure count. |
|
case ErrorCode::kOmahaRequestError: |
|
case ErrorCode::kOmahaResponseHandlerError: |
|
case ErrorCode::kPostinstallRunnerError: |
|
case ErrorCode::kFilesystemCopierError: |
|
case ErrorCode::kInstallDeviceOpenError: |
|
case ErrorCode::kKernelDeviceOpenError: |
|
case ErrorCode::kDownloadNewPartitionInfoError: |
|
case ErrorCode::kNewRootfsVerificationError: |
|
case ErrorCode::kNewKernelVerificationError: |
|
case ErrorCode::kPostinstallBootedFromFirmwareB: |
|
case ErrorCode::kPostinstallFirmwareRONotUpdatable: |
|
case ErrorCode::kOmahaRequestEmptyResponseError: |
|
case ErrorCode::kOmahaRequestXMLParseError: |
|
case ErrorCode::kOmahaResponseInvalid: |
|
case ErrorCode::kOmahaUpdateIgnoredPerPolicy: |
|
case ErrorCode::kOmahaUpdateDeferredPerPolicy: |
|
case ErrorCode::kNonCriticalUpdateInOOBE: |
|
case ErrorCode::kOmahaUpdateDeferredForBackoff: |
|
case ErrorCode::kPostinstallPowerwashError: |
|
case ErrorCode::kUpdateCanceledByChannelChange: |
|
case ErrorCode::kOmahaRequestXMLHasEntityDecl: |
|
case ErrorCode::kFilesystemVerifierError: |
|
case ErrorCode::kUserCanceled: |
|
LOG(INFO) << "Not incrementing URL index or failure count for this error"; |
|
break; |
|
|
|
case ErrorCode::kSuccess: // success code |
|
case ErrorCode::kUmaReportedMax: // not an error code |
|
case ErrorCode::kOmahaRequestHTTPResponseBase: // aggregated already |
|
case ErrorCode::kDevModeFlag: // not an error code |
|
case ErrorCode::kResumedFlag: // not an error code |
|
case ErrorCode::kTestImageFlag: // not an error code |
|
case ErrorCode::kTestOmahaUrlFlag: // not an error code |
|
case ErrorCode::kSpecialFlags: // not an error code |
|
// These shouldn't happen. Enumerating these explicitly here so that we |
|
// can let the compiler warn about new error codes that are added to |
|
// action_processor.h but not added here. |
|
LOG(WARNING) << "Unexpected error code for UpdateFailed"; |
|
break; |
|
|
|
// Note: Not adding a default here so as to let the compiler warn us of |
|
// any new enums that were added in the .h but not listed in this switch. |
|
} |
|
} |
|
|
|
bool PayloadState::ShouldBackoffDownload() { |
|
if (response_.disable_payload_backoff) { |
|
LOG(INFO) << "Payload backoff logic is disabled. " |
|
"Can proceed with the download"; |
|
return false; |
|
} |
|
if (GetUsingP2PForDownloading() && !GetP2PUrl().empty()) { |
|
LOG(INFO) << "Payload backoff logic is disabled because download " |
|
<< "will happen from local peer (via p2p)."; |
|
return false; |
|
} |
|
if (system_state_->request_params()->interactive()) { |
|
LOG(INFO) << "Payload backoff disabled for interactive update checks."; |
|
return false; |
|
} |
|
for (const auto& package : response_.packages) { |
|
if (package.is_delta) { |
|
// If delta payloads fail, we want to fallback quickly to full payloads as |
|
// they are more likely to succeed. Exponential backoffs would greatly |
|
// slow down the fallback to full payloads. So we don't backoff for delta |
|
// payloads. |
|
LOG(INFO) << "No backoffs for delta payloads. " |
|
<< "Can proceed with the download"; |
|
return false; |
|
} |
|
} |
|
|
|
if (!system_state_->hardware()->IsOfficialBuild()) { |
|
// Backoffs are needed only for official builds. We do not want any delays |
|
// or update failures due to backoffs during testing or development. |
|
LOG(INFO) << "No backoffs for test/dev images. " |
|
<< "Can proceed with the download"; |
|
return false; |
|
} |
|
|
|
if (backoff_expiry_time_.is_null()) { |
|
LOG(INFO) << "No backoff expiry time has been set. " |
|
<< "Can proceed with the download"; |
|
return false; |
|
} |
|
|
|
if (backoff_expiry_time_ < Time::Now()) { |
|
LOG(INFO) << "The backoff expiry time (" |
|
<< utils::ToString(backoff_expiry_time_) |
|
<< ") has elapsed. Can proceed with the download"; |
|
return false; |
|
} |
|
|
|
LOG(INFO) << "Cannot proceed with downloads as we need to backoff until " |
|
<< utils::ToString(backoff_expiry_time_); |
|
return true; |
|
} |
|
|
|
void PayloadState::Rollback() { |
|
SetRollbackVersion(system_state_->request_params()->app_version()); |
|
AttemptStarted(AttemptType::kRollback); |
|
} |
|
|
|
void PayloadState::IncrementPayloadAttemptNumber() { |
|
// Update the payload attempt number for both payload types: full and delta. |
|
SetPayloadAttemptNumber(GetPayloadAttemptNumber() + 1); |
|
} |
|
|
|
void PayloadState::IncrementFullPayloadAttemptNumber() { |
|
// Update the payload attempt number for full payloads and the backoff time. |
|
if (response_.packages[payload_index_].is_delta) { |
|
LOG(INFO) << "Not incrementing payload attempt number for delta payloads"; |
|
return; |
|
} |
|
|
|
LOG(INFO) << "Incrementing the full payload attempt number"; |
|
SetFullPayloadAttemptNumber(GetFullPayloadAttemptNumber() + 1); |
|
UpdateBackoffExpiryTime(); |
|
} |
|
|
|
void PayloadState::IncrementUrlIndex() { |
|
size_t next_url_index = url_index_ + 1; |
|
size_t max_url_size = 0; |
|
for (const auto& urls : candidate_urls_) |
|
max_url_size = std::max(max_url_size, urls.size()); |
|
if (next_url_index < max_url_size) { |
|
LOG(INFO) << "Incrementing the URL index for next attempt"; |
|
SetUrlIndex(next_url_index); |
|
} else { |
|
LOG(INFO) << "Resetting the current URL index (" << url_index_ << ") to " |
|
<< "0 as we only have " << max_url_size << " candidate URL(s)"; |
|
SetUrlIndex(0); |
|
IncrementPayloadAttemptNumber(); |
|
IncrementFullPayloadAttemptNumber(); |
|
} |
|
|
|
// If we have multiple URLs, record that we just switched to another one |
|
if (max_url_size > 1) |
|
SetUrlSwitchCount(url_switch_count_ + 1); |
|
|
|
// Whenever we update the URL index, we should also clear the URL failure |
|
// count so we can start over fresh for the new URL. |
|
SetUrlFailureCount(0); |
|
} |
|
|
|
void PayloadState::IncrementFailureCount() { |
|
uint32_t next_url_failure_count = GetUrlFailureCount() + 1; |
|
if (next_url_failure_count < response_.max_failure_count_per_url) { |
|
LOG(INFO) << "Incrementing the URL failure count"; |
|
SetUrlFailureCount(next_url_failure_count); |
|
} else { |
|
LOG(INFO) << "Reached max number of failures for Url" << GetUrlIndex() |
|
<< ". Trying next available URL"; |
|
IncrementUrlIndex(); |
|
} |
|
} |
|
|
|
void PayloadState::UpdateBackoffExpiryTime() { |
|
if (response_.disable_payload_backoff) { |
|
LOG(INFO) << "Resetting backoff expiry time as payload backoff is disabled"; |
|
SetBackoffExpiryTime(Time()); |
|
return; |
|
} |
|
|
|
if (GetFullPayloadAttemptNumber() == 0) { |
|
SetBackoffExpiryTime(Time()); |
|
return; |
|
} |
|
|
|
// Since we're doing left-shift below, make sure we don't shift more |
|
// than this. E.g. if int is 4-bytes, don't left-shift more than 30 bits, |
|
// since we don't expect value of kMaxBackoffDays to be more than 100 anyway. |
|
int num_days = 1; // the value to be shifted. |
|
const int kMaxShifts = (sizeof(num_days) * 8) - 2; |
|
|
|
// Normal backoff days is 2 raised to (payload_attempt_number - 1). |
|
// E.g. if payload_attempt_number is over 30, limit power to 30. |
|
int power = min(GetFullPayloadAttemptNumber() - 1, kMaxShifts); |
|
|
|
// The number of days is the minimum of 2 raised to (payload_attempt_number |
|
// - 1) or kMaxBackoffDays. |
|
num_days = min(num_days << power, kMaxBackoffDays); |
|
|
|
// We don't want all retries to happen exactly at the same time when |
|
// retrying after backoff. So add some random minutes to fuzz. |
|
int fuzz_minutes = utils::FuzzInt(0, kMaxBackoffFuzzMinutes); |
|
TimeDelta next_backoff_interval = TimeDelta::FromDays(num_days) + |
|
TimeDelta::FromMinutes(fuzz_minutes); |
|
LOG(INFO) << "Incrementing the backoff expiry time by " |
|
<< utils::FormatTimeDelta(next_backoff_interval); |
|
SetBackoffExpiryTime(Time::Now() + next_backoff_interval); |
|
} |
|
|
|
void PayloadState::UpdateCurrentDownloadSource() { |
|
current_download_source_ = kNumDownloadSources; |
|
|
|
if (using_p2p_for_downloading_) { |
|
current_download_source_ = kDownloadSourceHttpPeer; |
|
} else if (payload_index_ < candidate_urls_.size() && |
|
candidate_urls_[payload_index_].size() != 0) { |
|
const string& current_url = candidate_urls_[payload_index_][GetUrlIndex()]; |
|
if (base::StartsWith( |
|
current_url, "https://", base::CompareCase::INSENSITIVE_ASCII)) { |
|
current_download_source_ = kDownloadSourceHttpsServer; |
|
} else if (base::StartsWith(current_url, |
|
"http://", |
|
base::CompareCase::INSENSITIVE_ASCII)) { |
|
current_download_source_ = kDownloadSourceHttpServer; |
|
} |
|
} |
|
|
|
LOG(INFO) << "Current download source: " |
|
<< utils::ToString(current_download_source_); |
|
} |
|
|
|
void PayloadState::UpdateBytesDownloaded(size_t count) { |
|
SetCurrentBytesDownloaded( |
|
current_download_source_, |
|
GetCurrentBytesDownloaded(current_download_source_) + count, |
|
false); |
|
SetTotalBytesDownloaded( |
|
current_download_source_, |
|
GetTotalBytesDownloaded(current_download_source_) + count, |
|
false); |
|
|
|
attempt_num_bytes_downloaded_ += count; |
|
} |
|
|
|
PayloadType PayloadState::CalculatePayloadType() { |
|
for (const auto& package : response_.packages) { |
|
if (package.is_delta) { |
|
return kPayloadTypeDelta; |
|
} |
|
} |
|
OmahaRequestParams* params = system_state_->request_params(); |
|
if (params->delta_okay()) { |
|
return kPayloadTypeFull; |
|
} |
|
// Full payload, delta was not allowed by request. |
|
return kPayloadTypeForcedFull; |
|
} |
|
|
|
// TODO(zeuthen): Currently we don't report the UpdateEngine.Attempt.* |
|
// metrics if the attempt ends abnormally, e.g. if the update_engine |
|
// process crashes or the device is rebooted. See |
|
// http://crbug.com/357676 |
|
void PayloadState::CollectAndReportAttemptMetrics(ErrorCode code) { |
|
int attempt_number = GetPayloadAttemptNumber(); |
|
|
|
PayloadType payload_type = CalculatePayloadType(); |
|
|
|
int64_t payload_size = GetPayloadSize(); |
|
|
|
int64_t payload_bytes_downloaded = attempt_num_bytes_downloaded_; |
|
|
|
ClockInterface *clock = system_state_->clock(); |
|
TimeDelta duration = clock->GetBootTime() - attempt_start_time_boot_; |
|
TimeDelta duration_uptime = clock->GetMonotonicTime() - |
|
attempt_start_time_monotonic_; |
|
|
|
int64_t payload_download_speed_bps = 0; |
|
int64_t usec = duration_uptime.InMicroseconds(); |
|
if (usec > 0) { |
|
double sec = static_cast<double>(usec) / Time::kMicrosecondsPerSecond; |
|
double bps = static_cast<double>(payload_bytes_downloaded) / sec; |
|
payload_download_speed_bps = static_cast<int64_t>(bps); |
|
} |
|
|
|
DownloadSource download_source = current_download_source_; |
|
|
|
metrics::DownloadErrorCode payload_download_error_code = |
|
metrics::DownloadErrorCode::kUnset; |
|
ErrorCode internal_error_code = ErrorCode::kSuccess; |
|
metrics::AttemptResult attempt_result = metrics_utils::GetAttemptResult(code); |
|
|
|
// Add additional detail to AttemptResult |
|
switch (attempt_result) { |
|
case metrics::AttemptResult::kPayloadDownloadError: |
|
payload_download_error_code = metrics_utils::GetDownloadErrorCode(code); |
|
break; |
|
|
|
case metrics::AttemptResult::kInternalError: |
|
internal_error_code = code; |
|
break; |
|
|
|
// Explicit fall-through for cases where we do not have additional |
|
// detail. We avoid the default keyword to force people adding new |
|
// AttemptResult values to visit this code and examine whether |
|
// additional detail is needed. |
|
case metrics::AttemptResult::kUpdateSucceeded: |
|
case metrics::AttemptResult::kMetadataMalformed: |
|
case metrics::AttemptResult::kOperationMalformed: |
|
case metrics::AttemptResult::kOperationExecutionError: |
|
case metrics::AttemptResult::kMetadataVerificationFailed: |
|
case metrics::AttemptResult::kPayloadVerificationFailed: |
|
case metrics::AttemptResult::kVerificationFailed: |
|
case metrics::AttemptResult::kPostInstallFailed: |
|
case metrics::AttemptResult::kAbnormalTermination: |
|
case metrics::AttemptResult::kUpdateCanceled: |
|
case metrics::AttemptResult::kNumConstants: |
|
case metrics::AttemptResult::kUnset: |
|
break; |
|
} |
|
|
|
metrics::ReportUpdateAttemptMetrics(system_state_, |
|
attempt_number, |
|
payload_type, |
|
duration, |
|
duration_uptime, |
|
payload_size, |
|
payload_bytes_downloaded, |
|
payload_download_speed_bps, |
|
download_source, |
|
attempt_result, |
|
internal_error_code, |
|
payload_download_error_code, |
|
attempt_connection_type_); |
|
} |
|
|
|
void PayloadState::PersistAttemptMetrics() { |
|
// TODO(zeuthen): For now we only persist whether an attempt was in |
|
// progress and not values/metrics related to the attempt. This |
|
// means that when this happens, of all the UpdateEngine.Attempt.* |
|
// metrics, only UpdateEngine.Attempt.Result is reported (with the |
|
// value |kAbnormalTermination|). In the future we might want to |
|
// persist more data so we can report other metrics in the |
|
// UpdateEngine.Attempt.* namespace when this happens. |
|
prefs_->SetBoolean(kPrefsAttemptInProgress, true); |
|
} |
|
|
|
void PayloadState::ClearPersistedAttemptMetrics() { |
|
prefs_->Delete(kPrefsAttemptInProgress); |
|
} |
|
|
|
void PayloadState::ReportAndClearPersistedAttemptMetrics() { |
|
bool attempt_in_progress = false; |
|
if (!prefs_->GetBoolean(kPrefsAttemptInProgress, &attempt_in_progress)) |
|
return; |
|
if (!attempt_in_progress) |
|
return; |
|
|
|
metrics::ReportAbnormallyTerminatedUpdateAttemptMetrics(system_state_); |
|
|
|
ClearPersistedAttemptMetrics(); |
|
} |
|
|
|
void PayloadState::CollectAndReportSuccessfulUpdateMetrics() { |
|
string metric; |
|
|
|
// Report metrics collected from all known download sources to UMA. |
|
int64_t total_bytes_by_source[kNumDownloadSources]; |
|
int64_t successful_bytes = 0; |
|
int64_t total_bytes = 0; |
|
int64_t successful_mbs = 0; |
|
int64_t total_mbs = 0; |
|
|
|
for (int i = 0; i < kNumDownloadSources; i++) { |
|
DownloadSource source = static_cast<DownloadSource>(i); |
|
int64_t bytes; |
|
|
|
// Only consider this download source (and send byte counts) as |
|
// having been used if we downloaded a non-trivial amount of bytes |
|
// (e.g. at least 1 MiB) that contributed to the final success of |
|
// the update. Otherwise we're going to end up with a lot of |
|
// zero-byte events in the histogram. |
|
|
|
bytes = GetCurrentBytesDownloaded(source); |
|
successful_bytes += bytes; |
|
successful_mbs += bytes / kNumBytesInOneMiB; |
|
SetCurrentBytesDownloaded(source, 0, true); |
|
|
|
bytes = GetTotalBytesDownloaded(source); |
|
total_bytes_by_source[i] = bytes; |
|
total_bytes += bytes; |
|
total_mbs += bytes / kNumBytesInOneMiB; |
|
SetTotalBytesDownloaded(source, 0, true); |
|
} |
|
|
|
int download_overhead_percentage = 0; |
|
if (successful_bytes > 0) { |
|
download_overhead_percentage = (total_bytes - successful_bytes) * 100ULL / |
|
successful_bytes; |
|
} |
|
|
|
int url_switch_count = static_cast<int>(url_switch_count_); |
|
|
|
int reboot_count = GetNumReboots(); |
|
|
|
SetNumReboots(0); |
|
|
|
TimeDelta duration = GetUpdateDuration(); |
|
|
|
prefs_->Delete(kPrefsUpdateTimestampStart); |
|
prefs_->Delete(kPrefsUpdateDurationUptime); |
|
|
|
PayloadType payload_type = CalculatePayloadType(); |
|
|
|
int64_t payload_size = GetPayloadSize(); |
|
|
|
int attempt_count = GetPayloadAttemptNumber(); |
|
|
|
int updates_abandoned_count = num_responses_seen_ - 1; |
|
|
|
metrics::ReportSuccessfulUpdateMetrics(system_state_, |
|
attempt_count, |
|
updates_abandoned_count, |
|
payload_type, |
|
payload_size, |
|
total_bytes_by_source, |
|
download_overhead_percentage, |
|
duration, |
|
reboot_count, |
|
url_switch_count); |
|
} |
|
|
|
void PayloadState::UpdateNumReboots() { |
|
// We only update the reboot count when the system has been detected to have |
|
// been rebooted. |
|
if (!system_state_->system_rebooted()) { |
|
return; |
|
} |
|
|
|
SetNumReboots(GetNumReboots() + 1); |
|
} |
|
|
|
void PayloadState::SetNumReboots(uint32_t num_reboots) { |
|
CHECK(prefs_); |
|
num_reboots_ = num_reboots; |
|
prefs_->SetInt64(kPrefsNumReboots, num_reboots); |
|
LOG(INFO) << "Number of Reboots during current update attempt = " |
|
<< num_reboots_; |
|
} |
|
|
|
void PayloadState::ResetPersistedState() { |
|
SetPayloadAttemptNumber(0); |
|
SetFullPayloadAttemptNumber(0); |
|
SetPayloadIndex(0); |
|
SetUrlIndex(0); |
|
SetUrlFailureCount(0); |
|
SetUrlSwitchCount(0); |
|
UpdateBackoffExpiryTime(); // This will reset the backoff expiry time. |
|
SetUpdateTimestampStart(system_state_->clock()->GetWallclockTime()); |
|
SetUpdateTimestampEnd(Time()); // Set to null time |
|
SetUpdateDurationUptime(TimeDelta::FromSeconds(0)); |
|
ResetDownloadSourcesOnNewUpdate(); |
|
ResetRollbackVersion(); |
|
SetP2PNumAttempts(0); |
|
SetP2PFirstAttemptTimestamp(Time()); // Set to null time |
|
SetScatteringWaitPeriod(TimeDelta()); |
|
} |
|
|
|
void PayloadState::ResetRollbackVersion() { |
|
CHECK(powerwash_safe_prefs_); |
|
rollback_version_ = ""; |
|
powerwash_safe_prefs_->Delete(kPrefsRollbackVersion); |
|
} |
|
|
|
void PayloadState::ResetDownloadSourcesOnNewUpdate() { |
|
for (int i = 0; i < kNumDownloadSources; i++) { |
|
DownloadSource source = static_cast<DownloadSource>(i); |
|
SetCurrentBytesDownloaded(source, 0, true); |
|
// Note: Not resetting the TotalBytesDownloaded as we want that metric |
|
// to count the bytes downloaded across various update attempts until |
|
// we have successfully applied the update. |
|
} |
|
} |
|
|
|
int64_t PayloadState::GetPersistedValue(const string& key) { |
|
CHECK(prefs_); |
|
if (!prefs_->Exists(key)) |
|
return 0; |
|
|
|
int64_t stored_value; |
|
if (!prefs_->GetInt64(key, &stored_value)) |
|
return 0; |
|
|
|
if (stored_value < 0) { |
|
LOG(ERROR) << key << ": Invalid value (" << stored_value |
|
<< ") in persisted state. Defaulting to 0"; |
|
return 0; |
|
} |
|
|
|
return stored_value; |
|
} |
|
|
|
string PayloadState::CalculateResponseSignature() { |
|
string response_sign; |
|
for (size_t i = 0; i < response_.packages.size(); i++) { |
|
const auto& package = response_.packages[i]; |
|
response_sign += base::StringPrintf( |
|
"Payload %zu:\n" |
|
" Size = %ju\n" |
|
" Sha256 Hash = %s\n" |
|
" Metadata Size = %ju\n" |
|
" Metadata Signature = %s\n" |
|
" Is Delta = %d\n" |
|
" NumURLs = %zu\n", |
|
i, |
|
static_cast<uintmax_t>(package.size), |
|
package.hash.c_str(), |
|
static_cast<uintmax_t>(package.metadata_size), |
|
package.metadata_signature.c_str(), |
|
package.is_delta, |
|
candidate_urls_[i].size()); |
|
|
|
for (size_t j = 0; j < candidate_urls_[i].size(); j++) |
|
response_sign += base::StringPrintf( |
|
" Candidate Url%zu = %s\n", j, candidate_urls_[i][j].c_str()); |
|
} |
|
|
|
response_sign += base::StringPrintf( |
|
"Max Failure Count Per Url = %d\n" |
|
"Disable Payload Backoff = %d\n", |
|
response_.max_failure_count_per_url, |
|
response_.disable_payload_backoff); |
|
return response_sign; |
|
} |
|
|
|
void PayloadState::LoadResponseSignature() { |
|
CHECK(prefs_); |
|
string stored_value; |
|
if (prefs_->Exists(kPrefsCurrentResponseSignature) && |
|
prefs_->GetString(kPrefsCurrentResponseSignature, &stored_value)) { |
|
SetResponseSignature(stored_value); |
|
} |
|
} |
|
|
|
void PayloadState::SetResponseSignature(const string& response_signature) { |
|
CHECK(prefs_); |
|
response_signature_ = response_signature; |
|
LOG(INFO) << "Current Response Signature = \n" << response_signature_; |
|
prefs_->SetString(kPrefsCurrentResponseSignature, response_signature_); |
|
} |
|
|
|
void PayloadState::LoadPayloadAttemptNumber() { |
|
SetPayloadAttemptNumber(GetPersistedValue(kPrefsPayloadAttemptNumber)); |
|
} |
|
|
|
void PayloadState::LoadFullPayloadAttemptNumber() { |
|
SetFullPayloadAttemptNumber(GetPersistedValue( |
|
kPrefsFullPayloadAttemptNumber)); |
|
} |
|
|
|
void PayloadState::SetPayloadAttemptNumber(int payload_attempt_number) { |
|
CHECK(prefs_); |
|
payload_attempt_number_ = payload_attempt_number; |
|
LOG(INFO) << "Payload Attempt Number = " << payload_attempt_number_; |
|
prefs_->SetInt64(kPrefsPayloadAttemptNumber, payload_attempt_number_); |
|
} |
|
|
|
void PayloadState::SetFullPayloadAttemptNumber( |
|
int full_payload_attempt_number) { |
|
CHECK(prefs_); |
|
full_payload_attempt_number_ = full_payload_attempt_number; |
|
LOG(INFO) << "Full Payload Attempt Number = " << full_payload_attempt_number_; |
|
prefs_->SetInt64(kPrefsFullPayloadAttemptNumber, |
|
full_payload_attempt_number_); |
|
} |
|
|
|
void PayloadState::SetPayloadIndex(size_t payload_index) { |
|
CHECK(prefs_); |
|
payload_index_ = payload_index; |
|
LOG(INFO) << "Payload Index = " << payload_index_; |
|
prefs_->SetInt64(kPrefsUpdateStatePayloadIndex, payload_index_); |
|
} |
|
|
|
bool PayloadState::NextPayload() { |
|
if (payload_index_ + 1 >= candidate_urls_.size()) |
|
return false; |
|
SetPayloadIndex(payload_index_ + 1); |
|
return true; |
|
} |
|
|
|
void PayloadState::LoadUrlIndex() { |
|
SetUrlIndex(GetPersistedValue(kPrefsCurrentUrlIndex)); |
|
} |
|
|
|
void PayloadState::SetUrlIndex(uint32_t url_index) { |
|
CHECK(prefs_); |
|
url_index_ = url_index; |
|
LOG(INFO) << "Current URL Index = " << url_index_; |
|
prefs_->SetInt64(kPrefsCurrentUrlIndex, url_index_); |
|
|
|
// Also update the download source, which is purely dependent on the |
|
// current URL index alone. |
|
UpdateCurrentDownloadSource(); |
|
} |
|
|
|
void PayloadState::LoadScatteringWaitPeriod() { |
|
SetScatteringWaitPeriod( |
|
TimeDelta::FromSeconds(GetPersistedValue(kPrefsWallClockWaitPeriod))); |
|
} |
|
|
|
void PayloadState::SetScatteringWaitPeriod(TimeDelta wait_period) { |
|
CHECK(prefs_); |
|
scattering_wait_period_ = wait_period; |
|
LOG(INFO) << "Scattering Wait Period (seconds) = " |
|
<< scattering_wait_period_.InSeconds(); |
|
if (scattering_wait_period_.InSeconds() > 0) { |
|
prefs_->SetInt64(kPrefsWallClockWaitPeriod, |
|
scattering_wait_period_.InSeconds()); |
|
} else { |
|
prefs_->Delete(kPrefsWallClockWaitPeriod); |
|
} |
|
} |
|
|
|
void PayloadState::LoadUrlSwitchCount() { |
|
SetUrlSwitchCount(GetPersistedValue(kPrefsUrlSwitchCount)); |
|
} |
|
|
|
void PayloadState::SetUrlSwitchCount(uint32_t url_switch_count) { |
|
CHECK(prefs_); |
|
url_switch_count_ = url_switch_count; |
|
LOG(INFO) << "URL Switch Count = " << url_switch_count_; |
|
prefs_->SetInt64(kPrefsUrlSwitchCount, url_switch_count_); |
|
} |
|
|
|
void PayloadState::LoadUrlFailureCount() { |
|
SetUrlFailureCount(GetPersistedValue(kPrefsCurrentUrlFailureCount)); |
|
} |
|
|
|
void PayloadState::SetUrlFailureCount(uint32_t url_failure_count) { |
|
CHECK(prefs_); |
|
url_failure_count_ = url_failure_count; |
|
LOG(INFO) << "Current URL (Url" << GetUrlIndex() |
|
<< ")'s Failure Count = " << url_failure_count_; |
|
prefs_->SetInt64(kPrefsCurrentUrlFailureCount, url_failure_count_); |
|
} |
|
|
|
void PayloadState::LoadBackoffExpiryTime() { |
|
CHECK(prefs_); |
|
int64_t stored_value; |
|
if (!prefs_->Exists(kPrefsBackoffExpiryTime)) |
|
return; |
|
|
|
if (!prefs_->GetInt64(kPrefsBackoffExpiryTime, &stored_value)) |
|
return; |
|
|
|
Time stored_time = Time::FromInternalValue(stored_value); |
|
if (stored_time > Time::Now() + TimeDelta::FromDays(kMaxBackoffDays)) { |
|
LOG(ERROR) << "Invalid backoff expiry time (" |
|
<< utils::ToString(stored_time) |
|
<< ") in persisted state. Resetting."; |
|
stored_time = Time(); |
|
} |
|
SetBackoffExpiryTime(stored_time); |
|
} |
|
|
|
void PayloadState::SetBackoffExpiryTime(const Time& new_time) { |
|
CHECK(prefs_); |
|
backoff_expiry_time_ = new_time; |
|
LOG(INFO) << "Backoff Expiry Time = " |
|
<< utils::ToString(backoff_expiry_time_); |
|
prefs_->SetInt64(kPrefsBackoffExpiryTime, |
|
backoff_expiry_time_.ToInternalValue()); |
|
} |
|
|
|
TimeDelta PayloadState::GetUpdateDuration() { |
|
Time end_time = update_timestamp_end_.is_null() |
|
? system_state_->clock()->GetWallclockTime() : |
|
update_timestamp_end_; |
|
return end_time - update_timestamp_start_; |
|
} |
|
|
|
void PayloadState::LoadUpdateTimestampStart() { |
|
int64_t stored_value; |
|
Time stored_time; |
|
|
|
CHECK(prefs_); |
|
|
|
Time now = system_state_->clock()->GetWallclockTime(); |
|
|
|
if (!prefs_->Exists(kPrefsUpdateTimestampStart)) { |
|
// The preference missing is not unexpected - in that case, just |
|
// use the current time as start time |
|
stored_time = now; |
|
} else if (!prefs_->GetInt64(kPrefsUpdateTimestampStart, &stored_value)) { |
|
LOG(ERROR) << "Invalid UpdateTimestampStart value. Resetting."; |
|
stored_time = now; |
|
} else { |
|
stored_time = Time::FromInternalValue(stored_value); |
|
} |
|
|
|
// Sanity check: If the time read from disk is in the future |
|
// (modulo some slack to account for possible NTP drift |
|
// adjustments), something is fishy and we should report and |
|
// reset. |
|
TimeDelta duration_according_to_stored_time = now - stored_time; |
|
if (duration_according_to_stored_time < -kDurationSlack) { |
|
LOG(ERROR) << "The UpdateTimestampStart value (" |
|
<< utils::ToString(stored_time) |
|
<< ") in persisted state is " |
|
<< utils::FormatTimeDelta(duration_according_to_stored_time) |
|
<< " in the future. Resetting."; |
|
stored_time = now; |
|
} |
|
|
|
SetUpdateTimestampStart(stored_time); |
|
} |
|
|
|
void PayloadState::SetUpdateTimestampStart(const Time& value) { |
|
CHECK(prefs_); |
|
update_timestamp_start_ = value; |
|
prefs_->SetInt64(kPrefsUpdateTimestampStart, |
|
update_timestamp_start_.ToInternalValue()); |
|
LOG(INFO) << "Update Timestamp Start = " |
|
<< utils::ToString(update_timestamp_start_); |
|
} |
|
|
|
void PayloadState::SetUpdateTimestampEnd(const Time& value) { |
|
update_timestamp_end_ = value; |
|
LOG(INFO) << "Update Timestamp End = " |
|
<< utils::ToString(update_timestamp_end_); |
|
} |
|
|
|
TimeDelta PayloadState::GetUpdateDurationUptime() { |
|
return update_duration_uptime_; |
|
} |
|
|
|
void PayloadState::LoadUpdateDurationUptime() { |
|
int64_t stored_value; |
|
TimeDelta stored_delta; |
|
|
|
CHECK(prefs_); |
|
|
|
if (!prefs_->Exists(kPrefsUpdateDurationUptime)) { |
|
// The preference missing is not unexpected - in that case, just |
|
// we'll use zero as the delta |
|
} else if (!prefs_->GetInt64(kPrefsUpdateDurationUptime, &stored_value)) { |
|
LOG(ERROR) << "Invalid UpdateDurationUptime value. Resetting."; |
|
stored_delta = TimeDelta::FromSeconds(0); |
|
} else { |
|
stored_delta = TimeDelta::FromInternalValue(stored_value); |
|
} |
|
|
|
// Sanity-check: Uptime can never be greater than the wall-clock |
|
// difference (modulo some slack). If it is, report and reset |
|
// to the wall-clock difference. |
|
TimeDelta diff = GetUpdateDuration() - stored_delta; |
|
if (diff < -kDurationSlack) { |
|
LOG(ERROR) << "The UpdateDurationUptime value (" |
|
<< utils::FormatTimeDelta(stored_delta) |
|
<< ") in persisted state is " |
|
<< utils::FormatTimeDelta(diff) |
|
<< " larger than the wall-clock delta. Resetting."; |
|
stored_delta = update_duration_current_; |
|
} |
|
|
|
SetUpdateDurationUptime(stored_delta); |
|
} |
|
|
|
void PayloadState::LoadNumReboots() { |
|
SetNumReboots(GetPersistedValue(kPrefsNumReboots)); |
|
} |
|
|
|
void PayloadState::LoadRollbackVersion() { |
|
CHECK(powerwash_safe_prefs_); |
|
string rollback_version; |
|
if (powerwash_safe_prefs_->GetString(kPrefsRollbackVersion, |
|
&rollback_version)) { |
|
SetRollbackVersion(rollback_version); |
|
} |
|
} |
|
|
|
void PayloadState::SetRollbackVersion(const string& rollback_version) { |
|
CHECK(powerwash_safe_prefs_); |
|
LOG(INFO) << "Blacklisting version "<< rollback_version; |
|
rollback_version_ = rollback_version; |
|
powerwash_safe_prefs_->SetString(kPrefsRollbackVersion, rollback_version); |
|
} |
|
|
|
void PayloadState::SetUpdateDurationUptimeExtended(const TimeDelta& value, |
|
const Time& timestamp, |
|
bool use_logging) { |
|
CHECK(prefs_); |
|
update_duration_uptime_ = value; |
|
update_duration_uptime_timestamp_ = timestamp; |
|
prefs_->SetInt64(kPrefsUpdateDurationUptime, |
|
update_duration_uptime_.ToInternalValue()); |
|
if (use_logging) { |
|
LOG(INFO) << "Update Duration Uptime = " |
|
<< utils::FormatTimeDelta(update_duration_uptime_); |
|
} |
|
} |
|
|
|
void PayloadState::SetUpdateDurationUptime(const TimeDelta& value) { |
|
Time now = system_state_->clock()->GetMonotonicTime(); |
|
SetUpdateDurationUptimeExtended(value, now, true); |
|
} |
|
|
|
void PayloadState::CalculateUpdateDurationUptime() { |
|
Time now = system_state_->clock()->GetMonotonicTime(); |
|
TimeDelta uptime_since_last_update = now - update_duration_uptime_timestamp_; |
|
TimeDelta new_uptime = update_duration_uptime_ + uptime_since_last_update; |
|
// We're frequently called so avoid logging this write |
|
SetUpdateDurationUptimeExtended(new_uptime, now, false); |
|
} |
|
|
|
string PayloadState::GetPrefsKey(const string& prefix, DownloadSource source) { |
|
return prefix + "-from-" + utils::ToString(source); |
|
} |
|
|
|
void PayloadState::LoadCurrentBytesDownloaded(DownloadSource source) { |
|
string key = GetPrefsKey(kPrefsCurrentBytesDownloaded, source); |
|
SetCurrentBytesDownloaded(source, GetPersistedValue(key), true); |
|
} |
|
|
|
void PayloadState::SetCurrentBytesDownloaded( |
|
DownloadSource source, |
|
uint64_t current_bytes_downloaded, |
|
bool log) { |
|
CHECK(prefs_); |
|
|
|
if (source >= kNumDownloadSources) |
|
return; |
|
|
|
// Update the in-memory value. |
|
current_bytes_downloaded_[source] = current_bytes_downloaded; |
|
|
|
string prefs_key = GetPrefsKey(kPrefsCurrentBytesDownloaded, source); |
|
prefs_->SetInt64(prefs_key, current_bytes_downloaded); |
|
LOG_IF(INFO, log) << "Current bytes downloaded for " |
|
<< utils::ToString(source) << " = " |
|
<< GetCurrentBytesDownloaded(source); |
|
} |
|
|
|
void PayloadState::LoadTotalBytesDownloaded(DownloadSource source) { |
|
string key = GetPrefsKey(kPrefsTotalBytesDownloaded, source); |
|
SetTotalBytesDownloaded(source, GetPersistedValue(key), true); |
|
} |
|
|
|
void PayloadState::SetTotalBytesDownloaded( |
|
DownloadSource source, |
|
uint64_t total_bytes_downloaded, |
|
bool log) { |
|
CHECK(prefs_); |
|
|
|
if (source >= kNumDownloadSources) |
|
return; |
|
|
|
// Update the in-memory value. |
|
total_bytes_downloaded_[source] = total_bytes_downloaded; |
|
|
|
// Persist. |
|
string prefs_key = GetPrefsKey(kPrefsTotalBytesDownloaded, source); |
|
prefs_->SetInt64(prefs_key, total_bytes_downloaded); |
|
LOG_IF(INFO, log) << "Total bytes downloaded for " |
|
<< utils::ToString(source) << " = " |
|
<< GetTotalBytesDownloaded(source); |
|
} |
|
|
|
void PayloadState::LoadNumResponsesSeen() { |
|
SetNumResponsesSeen(GetPersistedValue(kPrefsNumResponsesSeen)); |
|
} |
|
|
|
void PayloadState::SetNumResponsesSeen(int num_responses_seen) { |
|
CHECK(prefs_); |
|
num_responses_seen_ = num_responses_seen; |
|
LOG(INFO) << "Num Responses Seen = " << num_responses_seen_; |
|
prefs_->SetInt64(kPrefsNumResponsesSeen, num_responses_seen_); |
|
} |
|
|
|
void PayloadState::ComputeCandidateUrls() { |
|
bool http_url_ok = true; |
|
|
|
if (system_state_->hardware()->IsOfficialBuild()) { |
|
const policy::DevicePolicy* policy = system_state_->device_policy(); |
|
if (policy && policy->GetHttpDownloadsEnabled(&http_url_ok) && !http_url_ok) |
|
LOG(INFO) << "Downloads via HTTP Url are not enabled by device policy"; |
|
} else { |
|
LOG(INFO) << "Allowing HTTP downloads for unofficial builds"; |
|
http_url_ok = true; |
|
} |
|
|
|
candidate_urls_.clear(); |
|
for (const auto& package : response_.packages) { |
|
candidate_urls_.emplace_back(); |
|
for (const string& candidate_url : package.payload_urls) { |
|
if (base::StartsWith( |
|
candidate_url, "http://", base::CompareCase::INSENSITIVE_ASCII) && |
|
!http_url_ok) { |
|
continue; |
|
} |
|
candidate_urls_.back().push_back(candidate_url); |
|
LOG(INFO) << "Candidate Url" << (candidate_urls_.back().size() - 1) |
|
<< ": " << candidate_url; |
|
} |
|
LOG(INFO) << "Found " << candidate_urls_.back().size() << " candidate URLs " |
|
<< "out of " << package.payload_urls.size() |
|
<< " URLs supplied in package " << candidate_urls_.size() - 1; |
|
} |
|
} |
|
|
|
void PayloadState::CreateSystemUpdatedMarkerFile() { |
|
CHECK(prefs_); |
|
int64_t value = system_state_->clock()->GetWallclockTime().ToInternalValue(); |
|
prefs_->SetInt64(kPrefsSystemUpdatedMarker, value); |
|
} |
|
|
|
void PayloadState::BootedIntoUpdate(TimeDelta time_to_reboot) { |
|
// Send |time_to_reboot| as a UMA stat. |
|
string metric = metrics::kMetricTimeToRebootMinutes; |
|
system_state_->metrics_lib()->SendToUMA(metric, |
|
time_to_reboot.InMinutes(), |
|
0, // min: 0 minute |
|
30*24*60, // max: 1 month (approx) |
|
kNumDefaultUmaBuckets); |
|
LOG(INFO) << "Uploading " << utils::FormatTimeDelta(time_to_reboot) |
|
<< " for metric " << metric; |
|
} |
|
|
|
void PayloadState::UpdateEngineStarted() { |
|
// Flush previous state from abnormal attempt failure, if any. |
|
ReportAndClearPersistedAttemptMetrics(); |
|
|
|
// Avoid the UpdateEngineStarted actions if this is not the first time we |
|
// run the update engine since reboot. |
|
if (!system_state_->system_rebooted()) |
|
return; |
|
|
|
// Figure out if we just booted into a new update |
|
if (prefs_->Exists(kPrefsSystemUpdatedMarker)) { |
|
int64_t stored_value; |
|
if (prefs_->GetInt64(kPrefsSystemUpdatedMarker, &stored_value)) { |
|
Time system_updated_at = Time::FromInternalValue(stored_value); |
|
if (!system_updated_at.is_null()) { |
|
TimeDelta time_to_reboot = |
|
system_state_->clock()->GetWallclockTime() - system_updated_at; |
|
if (time_to_reboot.ToInternalValue() < 0) { |
|
LOG(ERROR) << "time_to_reboot is negative - system_updated_at: " |
|
<< utils::ToString(system_updated_at); |
|
} else { |
|
BootedIntoUpdate(time_to_reboot); |
|
} |
|
} |
|
} |
|
prefs_->Delete(kPrefsSystemUpdatedMarker); |
|
} |
|
// Check if it is needed to send metrics about a failed reboot into a new |
|
// version. |
|
ReportFailedBootIfNeeded(); |
|
} |
|
|
|
void PayloadState::ReportFailedBootIfNeeded() { |
|
// If the kPrefsTargetVersionInstalledFrom is present, a successfully applied |
|
// payload was marked as ready immediately before the last reboot, and we |
|
// need to check if such payload successfully rebooted or not. |
|
if (prefs_->Exists(kPrefsTargetVersionInstalledFrom)) { |
|
int64_t installed_from = 0; |
|
if (!prefs_->GetInt64(kPrefsTargetVersionInstalledFrom, &installed_from)) { |
|
LOG(ERROR) << "Error reading TargetVersionInstalledFrom on reboot."; |
|
return; |
|
} |
|
// Old Chrome OS devices will write 2 or 4 in this setting, with the |
|
// partition number. We are now using slot numbers (0 or 1) instead, so |
|
// the following comparison will not match if we are comparing an old |
|
// partition number against a new slot number, which is the correct outcome |
|
// since we successfully booted the new update in that case. If the boot |
|
// failed, we will read this value from the same version, so it will always |
|
// be compatible. |
|
if (installed_from == system_state_->boot_control()->GetCurrentSlot()) { |
|
// A reboot was pending, but the chromebook is again in the same |
|
// BootDevice where the update was installed from. |
|
int64_t target_attempt; |
|
if (!prefs_->GetInt64(kPrefsTargetVersionAttempt, &target_attempt)) { |
|
LOG(ERROR) << "Error reading TargetVersionAttempt when " |
|
"TargetVersionInstalledFrom was present."; |
|
target_attempt = 1; |
|
} |
|
|
|
// Report the UMA metric of the current boot failure. |
|
string metric = metrics::kMetricFailedUpdateCount; |
|
LOG(INFO) << "Uploading " << target_attempt |
|
<< " (count) for metric " << metric; |
|
system_state_->metrics_lib()->SendToUMA( |
|
metric, |
|
target_attempt, |
|
1, // min value |
|
50, // max value |
|
kNumDefaultUmaBuckets); |
|
} else { |
|
prefs_->Delete(kPrefsTargetVersionAttempt); |
|
prefs_->Delete(kPrefsTargetVersionUniqueId); |
|
} |
|
prefs_->Delete(kPrefsTargetVersionInstalledFrom); |
|
} |
|
} |
|
|
|
void PayloadState::ExpectRebootInNewVersion(const string& target_version_uid) { |
|
// Expect to boot into the new partition in the next reboot setting the |
|
// TargetVersion* flags in the Prefs. |
|
string stored_target_version_uid; |
|
string target_version_id; |
|
string target_partition; |
|
int64_t target_attempt; |
|
|
|
if (prefs_->Exists(kPrefsTargetVersionUniqueId) && |
|
prefs_->GetString(kPrefsTargetVersionUniqueId, |
|
&stored_target_version_uid) && |
|
stored_target_version_uid == target_version_uid) { |
|
if (!prefs_->GetInt64(kPrefsTargetVersionAttempt, &target_attempt)) |
|
target_attempt = 0; |
|
} else { |
|
prefs_->SetString(kPrefsTargetVersionUniqueId, target_version_uid); |
|
target_attempt = 0; |
|
} |
|
prefs_->SetInt64(kPrefsTargetVersionAttempt, target_attempt + 1); |
|
|
|
prefs_->SetInt64(kPrefsTargetVersionInstalledFrom, |
|
system_state_->boot_control()->GetCurrentSlot()); |
|
} |
|
|
|
void PayloadState::ResetUpdateStatus() { |
|
// Remove the TargetVersionInstalledFrom pref so that if the machine is |
|
// rebooted the next boot is not flagged as failed to rebooted into the |
|
// new applied payload. |
|
prefs_->Delete(kPrefsTargetVersionInstalledFrom); |
|
|
|
// Also decrement the attempt number if it exists. |
|
int64_t target_attempt; |
|
if (prefs_->GetInt64(kPrefsTargetVersionAttempt, &target_attempt)) |
|
prefs_->SetInt64(kPrefsTargetVersionAttempt, target_attempt - 1); |
|
} |
|
|
|
int PayloadState::GetP2PNumAttempts() { |
|
return p2p_num_attempts_; |
|
} |
|
|
|
void PayloadState::SetP2PNumAttempts(int value) { |
|
p2p_num_attempts_ = value; |
|
LOG(INFO) << "p2p Num Attempts = " << p2p_num_attempts_; |
|
CHECK(prefs_); |
|
prefs_->SetInt64(kPrefsP2PNumAttempts, value); |
|
} |
|
|
|
void PayloadState::LoadP2PNumAttempts() { |
|
SetP2PNumAttempts(GetPersistedValue(kPrefsP2PNumAttempts)); |
|
} |
|
|
|
Time PayloadState::GetP2PFirstAttemptTimestamp() { |
|
return p2p_first_attempt_timestamp_; |
|
} |
|
|
|
void PayloadState::SetP2PFirstAttemptTimestamp(const Time& time) { |
|
p2p_first_attempt_timestamp_ = time; |
|
LOG(INFO) << "p2p First Attempt Timestamp = " |
|
<< utils::ToString(p2p_first_attempt_timestamp_); |
|
CHECK(prefs_); |
|
int64_t stored_value = time.ToInternalValue(); |
|
prefs_->SetInt64(kPrefsP2PFirstAttemptTimestamp, stored_value); |
|
} |
|
|
|
void PayloadState::LoadP2PFirstAttemptTimestamp() { |
|
int64_t stored_value = GetPersistedValue(kPrefsP2PFirstAttemptTimestamp); |
|
Time stored_time = Time::FromInternalValue(stored_value); |
|
SetP2PFirstAttemptTimestamp(stored_time); |
|
} |
|
|
|
void PayloadState::P2PNewAttempt() { |
|
CHECK(prefs_); |
|
// Set timestamp, if it hasn't been set already |
|
if (p2p_first_attempt_timestamp_.is_null()) { |
|
SetP2PFirstAttemptTimestamp(system_state_->clock()->GetWallclockTime()); |
|
} |
|
// Increase number of attempts |
|
SetP2PNumAttempts(GetP2PNumAttempts() + 1); |
|
} |
|
|
|
bool PayloadState::P2PAttemptAllowed() { |
|
if (p2p_num_attempts_ > kMaxP2PAttempts) { |
|
LOG(INFO) << "Number of p2p attempts is " << p2p_num_attempts_ |
|
<< " which is greater than " |
|
<< kMaxP2PAttempts |
|
<< " - disallowing p2p."; |
|
return false; |
|
} |
|
|
|
if (!p2p_first_attempt_timestamp_.is_null()) { |
|
Time now = system_state_->clock()->GetWallclockTime(); |
|
TimeDelta time_spent_attempting_p2p = now - p2p_first_attempt_timestamp_; |
|
if (time_spent_attempting_p2p.InSeconds() < 0) { |
|
LOG(ERROR) << "Time spent attempting p2p is negative" |
|
<< " - disallowing p2p."; |
|
return false; |
|
} |
|
if (time_spent_attempting_p2p.InSeconds() > kMaxP2PAttemptTimeSeconds) { |
|
LOG(INFO) << "Time spent attempting p2p is " |
|
<< utils::FormatTimeDelta(time_spent_attempting_p2p) |
|
<< " which is greater than " |
|
<< utils::FormatTimeDelta(TimeDelta::FromSeconds( |
|
kMaxP2PAttemptTimeSeconds)) |
|
<< " - disallowing p2p."; |
|
return false; |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
int64_t PayloadState::GetPayloadSize() { |
|
int64_t payload_size = 0; |
|
for (const auto& package : response_.packages) |
|
payload_size += package.size; |
|
return payload_size; |
|
} |
|
|
|
} // namespace chromeos_update_engine
|
|
|