| // |
| // Copyright (C) 2015 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/metrics_utils.h" |
| |
| #include <string> |
| |
| #include <base/time/time.h> |
| |
| #include "update_engine/common/clock_interface.h" |
| #include "update_engine/common/constants.h" |
| #include "update_engine/common/utils.h" |
| |
| using base::Time; |
| using base::TimeDelta; |
| |
| namespace chromeos_update_engine { |
| namespace metrics_utils { |
| |
| metrics::AttemptResult GetAttemptResult(ErrorCode code) { |
| ErrorCode base_code = static_cast<ErrorCode>( |
| static_cast<int>(code) & ~static_cast<int>(ErrorCode::kSpecialFlags)); |
| |
| switch (base_code) { |
| case ErrorCode::kSuccess: |
| return metrics::AttemptResult::kUpdateSucceeded; |
| |
| case ErrorCode::kUpdatedButNotActive: |
| return metrics::AttemptResult::kUpdateSucceededNotActive; |
| |
| case ErrorCode::kDownloadTransferError: |
| case ErrorCode::kInternalLibCurlError: |
| case ErrorCode::kUnresolvedHostError: |
| case ErrorCode::kUnresolvedHostRecovered: |
| return metrics::AttemptResult::kPayloadDownloadError; |
| |
| case ErrorCode::kDownloadInvalidMetadataSize: |
| case ErrorCode::kDownloadInvalidMetadataMagicString: |
| case ErrorCode::kDownloadMetadataSignatureError: |
| case ErrorCode::kDownloadMetadataSignatureVerificationError: |
| case ErrorCode::kPayloadMismatchedType: |
| case ErrorCode::kUnsupportedMajorPayloadVersion: |
| case ErrorCode::kUnsupportedMinorPayloadVersion: |
| case ErrorCode::kDownloadNewPartitionInfoError: |
| case ErrorCode::kDownloadSignatureMissingInManifest: |
| case ErrorCode::kDownloadManifestParseError: |
| case ErrorCode::kDownloadOperationHashMissingError: |
| return metrics::AttemptResult::kMetadataMalformed; |
| |
| case ErrorCode::kDownloadOperationHashMismatch: |
| case ErrorCode::kDownloadOperationHashVerificationError: |
| return metrics::AttemptResult::kOperationMalformed; |
| |
| case ErrorCode::kDownloadOperationExecutionError: |
| case ErrorCode::kInstallDeviceOpenError: |
| case ErrorCode::kKernelDeviceOpenError: |
| case ErrorCode::kDownloadWriteError: |
| case ErrorCode::kFilesystemCopierError: |
| case ErrorCode::kFilesystemVerifierError: |
| case ErrorCode::kVerityCalculationError: |
| case ErrorCode::kNotEnoughSpace: |
| case ErrorCode::kDeviceCorrupted: |
| return metrics::AttemptResult::kOperationExecutionError; |
| |
| case ErrorCode::kDownloadMetadataSignatureMismatch: |
| return metrics::AttemptResult::kMetadataVerificationFailed; |
| |
| case ErrorCode::kPayloadSizeMismatchError: |
| case ErrorCode::kPayloadHashMismatchError: |
| case ErrorCode::kDownloadPayloadVerificationError: |
| case ErrorCode::kSignedDeltaPayloadExpectedError: |
| case ErrorCode::kDownloadPayloadPubKeyVerificationError: |
| case ErrorCode::kPayloadTimestampError: |
| return metrics::AttemptResult::kPayloadVerificationFailed; |
| |
| case ErrorCode::kNewRootfsVerificationError: |
| case ErrorCode::kNewKernelVerificationError: |
| case ErrorCode::kRollbackNotPossible: |
| return metrics::AttemptResult::kVerificationFailed; |
| |
| case ErrorCode::kPostinstallRunnerError: |
| case ErrorCode::kPostinstallBootedFromFirmwareB: |
| case ErrorCode::kPostinstallFirmwareRONotUpdatable: |
| case ErrorCode::kPostInstallMountError: |
| return metrics::AttemptResult::kPostInstallFailed; |
| |
| case ErrorCode::kUserCanceled: |
| return metrics::AttemptResult::kUpdateCanceled; |
| |
| // We should never get these errors in the update-attempt stage so |
| // return internal error if this happens. |
| case ErrorCode::kError: |
| case ErrorCode::kOmahaRequestXMLParseError: |
| case ErrorCode::kOmahaRequestError: |
| case ErrorCode::kOmahaResponseHandlerError: |
| case ErrorCode::kDownloadStateInitializationError: |
| case ErrorCode::kOmahaRequestEmptyResponseError: |
| case ErrorCode::kDownloadInvalidMetadataSignature: |
| case ErrorCode::kOmahaResponseInvalid: |
| case ErrorCode::kOmahaUpdateIgnoredPerPolicy: |
| case ErrorCode::kOmahaErrorInHTTPResponse: |
| case ErrorCode::kDownloadMetadataSignatureMissingError: |
| case ErrorCode::kOmahaUpdateDeferredForBackoff: |
| case ErrorCode::kPostinstallPowerwashError: |
| case ErrorCode::kUpdateCanceledByChannelChange: |
| case ErrorCode::kOmahaRequestXMLHasEntityDecl: |
| case ErrorCode::kOmahaUpdateIgnoredOverCellular: |
| case ErrorCode::kNoUpdate: |
| case ErrorCode::kFirstActiveOmahaPingSentPersistenceError: |
| case ErrorCode::kPackageExcludedFromUpdate: |
| return metrics::AttemptResult::kInternalError; |
| |
| case ErrorCode::kOmahaUpdateDeferredPerPolicy: |
| case ErrorCode::kNonCriticalUpdateInOOBE: |
| return metrics::AttemptResult::kUpdateSkipped; |
| |
| // Special flags. These can't happen (we mask them out above) but |
| // the compiler doesn't know that. Just break out so we can warn and |
| // return |kInternalError|. |
| case ErrorCode::kUmaReportedMax: |
| case ErrorCode::kOmahaRequestHTTPResponseBase: |
| case ErrorCode::kDevModeFlag: |
| case ErrorCode::kResumedFlag: |
| case ErrorCode::kTestImageFlag: |
| case ErrorCode::kTestOmahaUrlFlag: |
| case ErrorCode::kSpecialFlags: |
| break; |
| } |
| |
| LOG(ERROR) << "Unexpected error code " << base_code; |
| return metrics::AttemptResult::kInternalError; |
| } |
| |
| metrics::DownloadErrorCode GetDownloadErrorCode(ErrorCode code) { |
| ErrorCode base_code = static_cast<ErrorCode>( |
| static_cast<int>(code) & ~static_cast<int>(ErrorCode::kSpecialFlags)); |
| |
| if (base_code >= ErrorCode::kOmahaRequestHTTPResponseBase) { |
| int http_status = |
| static_cast<int>(base_code) - |
| static_cast<int>(ErrorCode::kOmahaRequestHTTPResponseBase); |
| if (http_status >= 200 && http_status <= 599) { |
| return static_cast<metrics::DownloadErrorCode>( |
| static_cast<int>(metrics::DownloadErrorCode::kHttpStatus200) + |
| http_status - 200); |
| } else if (http_status == 0) { |
| // The code is using HTTP Status 0 for "Unable to get http |
| // response code." |
| return metrics::DownloadErrorCode::kDownloadError; |
| } |
| LOG(WARNING) << "Unexpected HTTP status code " << http_status; |
| return metrics::DownloadErrorCode::kHttpStatusOther; |
| } |
| |
| switch (base_code) { |
| // Unfortunately, ErrorCode::kDownloadTransferError is returned for a wide |
| // variety of errors (proxy errors, host not reachable, timeouts etc.). |
| // |
| // For now just map that to kDownloading. See http://crbug.com/355745 |
| // for how we plan to add more detail in the future. |
| case ErrorCode::kDownloadTransferError: |
| return metrics::DownloadErrorCode::kDownloadError; |
| |
| case ErrorCode::kInternalLibCurlError: |
| return metrics::DownloadErrorCode::kInternalLibCurlError; |
| case ErrorCode::kUnresolvedHostError: |
| return metrics::DownloadErrorCode::kUnresolvedHostError; |
| case ErrorCode::kUnresolvedHostRecovered: |
| return metrics::DownloadErrorCode::kUnresolvedHostRecovered; |
| |
| // All of these error codes are not related to downloading so break |
| // out so we can warn and return InputMalformed. |
| case ErrorCode::kSuccess: |
| case ErrorCode::kError: |
| case ErrorCode::kOmahaRequestError: |
| case ErrorCode::kOmahaResponseHandlerError: |
| case ErrorCode::kFilesystemCopierError: |
| case ErrorCode::kPostinstallRunnerError: |
| case ErrorCode::kPostInstallMountError: |
| case ErrorCode::kPayloadMismatchedType: |
| case ErrorCode::kInstallDeviceOpenError: |
| case ErrorCode::kKernelDeviceOpenError: |
| case ErrorCode::kPayloadHashMismatchError: |
| case ErrorCode::kPayloadSizeMismatchError: |
| case ErrorCode::kDownloadPayloadVerificationError: |
| case ErrorCode::kDownloadNewPartitionInfoError: |
| case ErrorCode::kDownloadWriteError: |
| case ErrorCode::kNewRootfsVerificationError: |
| case ErrorCode::kNewKernelVerificationError: |
| case ErrorCode::kSignedDeltaPayloadExpectedError: |
| case ErrorCode::kDownloadPayloadPubKeyVerificationError: |
| case ErrorCode::kPostinstallBootedFromFirmwareB: |
| case ErrorCode::kDownloadStateInitializationError: |
| 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::kOmahaRequestEmptyResponseError: |
| case ErrorCode::kOmahaRequestXMLParseError: |
| case ErrorCode::kDownloadInvalidMetadataSize: |
| case ErrorCode::kDownloadInvalidMetadataSignature: |
| case ErrorCode::kOmahaResponseInvalid: |
| case ErrorCode::kOmahaUpdateIgnoredPerPolicy: |
| case ErrorCode::kOmahaUpdateDeferredPerPolicy: |
| case ErrorCode::kNonCriticalUpdateInOOBE: |
| case ErrorCode::kOmahaErrorInHTTPResponse: |
| case ErrorCode::kDownloadOperationHashMissingError: |
| case ErrorCode::kDownloadMetadataSignatureMissingError: |
| case ErrorCode::kOmahaUpdateDeferredForBackoff: |
| case ErrorCode::kPostinstallPowerwashError: |
| case ErrorCode::kUpdateCanceledByChannelChange: |
| case ErrorCode::kPostinstallFirmwareRONotUpdatable: |
| case ErrorCode::kUnsupportedMajorPayloadVersion: |
| case ErrorCode::kUnsupportedMinorPayloadVersion: |
| case ErrorCode::kOmahaRequestXMLHasEntityDecl: |
| case ErrorCode::kFilesystemVerifierError: |
| case ErrorCode::kUserCanceled: |
| case ErrorCode::kOmahaUpdateIgnoredOverCellular: |
| case ErrorCode::kPayloadTimestampError: |
| case ErrorCode::kUpdatedButNotActive: |
| case ErrorCode::kNoUpdate: |
| case ErrorCode::kRollbackNotPossible: |
| case ErrorCode::kFirstActiveOmahaPingSentPersistenceError: |
| case ErrorCode::kVerityCalculationError: |
| case ErrorCode::kNotEnoughSpace: |
| case ErrorCode::kDeviceCorrupted: |
| case ErrorCode::kPackageExcludedFromUpdate: |
| break; |
| |
| // Special flags. These can't happen (we mask them out above) but |
| // the compiler doesn't know that. Just break out so we can warn and |
| // return |kInputMalformed|. |
| case ErrorCode::kUmaReportedMax: |
| case ErrorCode::kOmahaRequestHTTPResponseBase: |
| case ErrorCode::kDevModeFlag: |
| case ErrorCode::kResumedFlag: |
| case ErrorCode::kTestImageFlag: |
| case ErrorCode::kTestOmahaUrlFlag: |
| case ErrorCode::kSpecialFlags: |
| LOG(ERROR) << "Unexpected error code " << base_code; |
| break; |
| } |
| |
| return metrics::DownloadErrorCode::kInputMalformed; |
| } |
| |
| metrics::ConnectionType GetConnectionType(ConnectionType type, |
| ConnectionTethering tethering) { |
| switch (type) { |
| case ConnectionType::kUnknown: |
| return metrics::ConnectionType::kUnknown; |
| |
| case ConnectionType::kDisconnected: |
| return metrics::ConnectionType::kDisconnected; |
| |
| case ConnectionType::kEthernet: |
| if (tethering == ConnectionTethering::kConfirmed) |
| return metrics::ConnectionType::kTetheredEthernet; |
| else |
| return metrics::ConnectionType::kEthernet; |
| |
| case ConnectionType::kWifi: |
| if (tethering == ConnectionTethering::kConfirmed) |
| return metrics::ConnectionType::kTetheredWifi; |
| else |
| return metrics::ConnectionType::kWifi; |
| |
| case ConnectionType::kCellular: |
| return metrics::ConnectionType::kCellular; |
| } |
| |
| LOG(ERROR) << "Unexpected network connection type: type=" |
| << static_cast<int>(type) |
| << ", tethering=" << static_cast<int>(tethering); |
| |
| return metrics::ConnectionType::kUnknown; |
| } |
| |
| int64_t GetPersistedValue(std::string_view key, PrefsInterface* prefs) { |
| 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; |
| } |
| |
| void SetNumReboots(int64_t num_reboots, PrefsInterface* prefs) { |
| CHECK(prefs); |
| prefs->SetInt64(kPrefsNumReboots, num_reboots); |
| LOG(INFO) << "Number of Reboots during current update attempt = " |
| << num_reboots; |
| } |
| |
| void SetPayloadAttemptNumber(int64_t payload_attempt_number, |
| PrefsInterface* prefs) { |
| CHECK(prefs); |
| prefs->SetInt64(kPrefsPayloadAttemptNumber, payload_attempt_number); |
| LOG(INFO) << "Payload Attempt Number = " << payload_attempt_number; |
| } |
| |
| void SetSystemUpdatedMarker(ClockInterface* clock, PrefsInterface* prefs) { |
| CHECK(prefs); |
| CHECK(clock); |
| Time update_finish_time = clock->GetMonotonicTime(); |
| prefs->SetInt64(kPrefsSystemUpdatedMarker, |
| update_finish_time.ToInternalValue()); |
| LOG(INFO) << "Updated Marker = " << utils::ToString(update_finish_time); |
| } |
| |
| void SetUpdateTimestampStart(const Time& update_start_time, |
| PrefsInterface* prefs) { |
| CHECK(prefs); |
| prefs->SetInt64(kPrefsUpdateTimestampStart, |
| update_start_time.ToInternalValue()); |
| LOG(INFO) << "Update Monotonic Timestamp Start = " |
| << utils::ToString(update_start_time); |
| } |
| |
| void SetUpdateBootTimestampStart(const base::Time& update_start_boot_time, |
| PrefsInterface* prefs) { |
| CHECK(prefs); |
| prefs->SetInt64(kPrefsUpdateBootTimestampStart, |
| update_start_boot_time.ToInternalValue()); |
| LOG(INFO) << "Update Boot Timestamp Start = " |
| << utils::ToString(update_start_boot_time); |
| } |
| |
| bool LoadAndReportTimeToReboot(MetricsReporterInterface* metrics_reporter, |
| PrefsInterface* prefs, |
| ClockInterface* clock) { |
| CHECK(prefs); |
| CHECK(clock); |
| int64_t stored_value = GetPersistedValue(kPrefsSystemUpdatedMarker, prefs); |
| if (stored_value == 0) |
| return false; |
| |
| Time system_updated_at = Time::FromInternalValue(stored_value); |
| TimeDelta time_to_reboot = clock->GetMonotonicTime() - system_updated_at; |
| if (time_to_reboot.ToInternalValue() < 0) { |
| LOG(ERROR) << "time_to_reboot is negative - system_updated_at: " |
| << utils::ToString(system_updated_at); |
| return false; |
| } |
| metrics_reporter->ReportTimeToReboot(time_to_reboot.InMinutes()); |
| return true; |
| } |
| |
| } // namespace metrics_utils |
| } // namespace chromeos_update_engine |