Segregate UMA metrics for production scenarios from test scenarios.

Currently we separate the UMA metrics only by one category: whether the
device is in dev mode or not. In addition, we need to exclude the noise
from these two categories:
1. Most of our testing on MP-signed images which are performed
with autest.
2. All our hwlab tests run in non-dev mode but they use dev-signed images
with dev-firmware keys.

So this CL defines additional bit fields to represent these states and
if any of these three flags are set, the UMA metric is sent to a
DevModeErrorCodes bucket. Thus the NormalErrorCodes bucket will have only
the production errors and thus we can monitor more effectively.

BUG=chromium-os:37613
TEST=Updated unit tests, ran on ZGB for all scenarios.
Change-Id: Id9cce33f09d1cc50cb15e67c731f7548940cbc24
Reviewed-on: https://gerrit.chromium.org/gerrit/41103
Reviewed-by: Chris Sosa <sosa@chromium.org>
Commit-Queue: Jay Srinivasan <jaysri@chromium.org>
Tested-by: Jay Srinivasan <jaysri@chromium.org>
diff --git a/utils.cc b/utils.cc
index e5d7290..fee3439 100644
--- a/utils.cc
+++ b/utils.cc
@@ -34,6 +34,8 @@
 #include "update_engine/file_writer.h"
 #include "update_engine/omaha_request_params.h"
 #include "update_engine/subprocess.h"
+#include "update_engine/system_state.h"
+#include "update_engine/update_attempter.h"
 
 using base::Time;
 using base::TimeDelta;
@@ -715,8 +717,7 @@
   // Ignore the higher order bits in the code by applying the mask as
   // we want the enumerations to be in the small contiguous range
   // with values less than kActionCodeUmaReportedMax.
-  ActionExitCode base_code = static_cast<ActionExitCode>(
-      code & kActualCodeMask);
+  ActionExitCode base_code = static_cast<ActionExitCode>(code & ~kSpecialFlags);
 
   // Make additional adjustments required for UMA and error classification.
   // TODO(jaysri): Move this logic to UeErrorCode.cc when we fix
@@ -724,26 +725,168 @@
   if (base_code >= kActionCodeOmahaRequestHTTPResponseBase) {
     // Since we want to keep the enums to a small value, aggregate all HTTP
     // errors into this one bucket for UMA and error classification purposes.
+    LOG(INFO) << "Converting error code " << base_code
+              << " to kActionCodeOmahaErrorInHTTPResponse";
     base_code = kActionCodeOmahaErrorInHTTPResponse;
   }
 
   return base_code;
 }
 
+// Returns a printable version of the various flags denoted in the higher order
+// bits of the given code. Returns an empty string if none of those bits are
+// set.
+string GetFlagNames(uint32_t code) {
+  uint32_t flags = code & kSpecialFlags;
+  string flag_names;
+  string separator = "";
+  for(size_t i = 0; i < sizeof(flags) * 8; i++) {
+    uint32_t flag = flags & (1 << i);
+    if (flag) {
+      flag_names += separator + CodeToString(static_cast<ActionExitCode>(flag));
+      separator = ", ";
+    }
+  }
 
-
-void SendErrorCodeToUma(MetricsLibraryInterface* metrics_lib,
-                        ActionExitCode code) {
-  string metric = utils::IsNormalBootMode() ? "Installer.NormalErrorCodes" :
-                                              "Installer.DevModeErrorCodes";
-
-  ActionExitCode reported_code = GetBaseErrorCode(code);
-
-  LOG(INFO) << "Sending error code " << reported_code
-            << " to UMA metric: " << metric;
-  metrics_lib->SendEnumToUMA(metric, reported_code, kActionCodeUmaReportedMax);
+  return flag_names;
 }
 
+void SendErrorCodeToUma(SystemState* system_state, ActionExitCode code) {
+  if (!system_state)
+    return;
+
+  ActionExitCode uma_error_code = GetBaseErrorCode(code);
+
+  // If the code doesn't have flags computed already, compute them now based on
+  // the state of the current update attempt.
+  uint32_t flags = code & kSpecialFlags;
+  if (!flags)
+    flags = system_state->update_attempter()->GetErrorCodeFlags();
+
+  // Determine the UMA bucket depending on the flags. But, ignore the resumed
+  // flag, as it's perfectly normal for production devices to resume their
+  // downloads and so we want to record those cases also in NormalErrorCodes
+  // bucket.
+  string metric = (flags & ~kActionCodeResumedFlag) ?
+      "Installer.DevModeErrorCodes" : "Installer.NormalErrorCodes";
+
+  LOG(INFO) << "Sending error code " << uma_error_code
+            << " (" << CodeToString(uma_error_code) << ")"
+            << " to UMA metric: " << metric
+            << ". Flags = " << (flags ? GetFlagNames(flags) : "None");
+
+  system_state->metrics_lib()->SendEnumToUMA(metric,
+                                             uma_error_code,
+                                             kActionCodeUmaReportedMax);
+}
+
+string CodeToString(ActionExitCode code) {
+  // If the given code has both parts (i.e. the error code part and the flags
+  // part) then strip off the flags part since the switch statement below
+  // has case statements only for the base error code or a single flag but
+  // doesn't support any combinations of those.
+  if ((code & kSpecialFlags) && (code & ~kSpecialFlags))
+    code = static_cast<ActionExitCode>(code & ~kSpecialFlags);
+  switch (code) {
+    case kActionCodeSuccess: return "kActionCodeSuccess";
+    case kActionCodeError: return "kActionCodeError";
+    case kActionCodeOmahaRequestError: return "kActionCodeOmahaRequestError";
+    case kActionCodeOmahaResponseHandlerError:
+      return "kActionCodeOmahaResponseHandlerError";
+    case kActionCodeFilesystemCopierError:
+      return "kActionCodeFilesystemCopierError";
+    case kActionCodePostinstallRunnerError:
+      return "kActionCodePostinstallRunnerError";
+    case kActionCodeSetBootableFlagError:
+      return "kActionCodeSetBootableFlagError";
+    case kActionCodeInstallDeviceOpenError:
+      return "kActionCodeInstallDeviceOpenError";
+    case kActionCodeKernelDeviceOpenError:
+      return "kActionCodeKernelDeviceOpenError";
+    case kActionCodeDownloadTransferError:
+      return "kActionCodeDownloadTransferError";
+    case kActionCodePayloadHashMismatchError:
+      return "kActionCodePayloadHashMismatchError";
+    case kActionCodePayloadSizeMismatchError:
+      return "kActionCodePayloadSizeMismatchError";
+    case kActionCodeDownloadPayloadVerificationError:
+      return "kActionCodeDownloadPayloadVerificationError";
+    case kActionCodeDownloadNewPartitionInfoError:
+      return "kActionCodeDownloadNewPartitionInfoError";
+    case kActionCodeDownloadWriteError:
+      return "kActionCodeDownloadWriteError";
+    case kActionCodeNewRootfsVerificationError:
+      return "kActionCodeNewRootfsVerificationError";
+    case kActionCodeNewKernelVerificationError:
+      return "kActionCodeNewKernelVerificationError";
+    case kActionCodeSignedDeltaPayloadExpectedError:
+      return "kActionCodeSignedDeltaPayloadExpectedError";
+    case kActionCodeDownloadPayloadPubKeyVerificationError:
+      return "kActionCodeDownloadPayloadPubKeyVerificationError";
+    case kActionCodePostinstallBootedFromFirmwareB:
+      return "kActionCodePostinstallBootedFromFirmwareB";
+    case kActionCodeDownloadStateInitializationError:
+      return "kActionCodeDownloadStateInitializationError";
+    case kActionCodeDownloadInvalidMetadataMagicString:
+      return "kActionCodeDownloadInvalidMetadataMagicString";
+    case kActionCodeDownloadSignatureMissingInManifest:
+      return "kActionCodeDownloadSignatureMissingInManifest";
+    case kActionCodeDownloadManifestParseError:
+      return "kActionCodeDownloadManifestParseError";
+    case kActionCodeDownloadMetadataSignatureError:
+      return "kActionCodeDownloadMetadataSignatureError";
+    case kActionCodeDownloadMetadataSignatureVerificationError:
+      return "kActionCodeDownloadMetadataSignatureVerificationError";
+    case kActionCodeDownloadMetadataSignatureMismatch:
+      return "kActionCodeDownloadMetadataSignatureMismatch";
+    case kActionCodeDownloadOperationHashVerificationError:
+      return "kActionCodeDownloadOperationHashVerificationError";
+    case kActionCodeDownloadOperationExecutionError:
+      return "kActionCodeDownloadOperationExecutionError";
+    case kActionCodeDownloadOperationHashMismatch:
+      return "kActionCodeDownloadOperationHashMismatch";
+    case kActionCodeOmahaRequestEmptyResponseError:
+      return "kActionCodeOmahaRequestEmptyResponseError";
+    case kActionCodeOmahaRequestXMLParseError:
+      return "kActionCodeOmahaRequestXMLParseError";
+    case kActionCodeDownloadInvalidMetadataSize:
+      return "kActionCodeDownloadInvalidMetadataSize";
+    case kActionCodeDownloadInvalidMetadataSignature:
+      return "kActionCodeDownloadInvalidMetadataSignature";
+    case kActionCodeOmahaResponseInvalid:
+      return "kActionCodeOmahaResponseInvalid";
+    case kActionCodeOmahaUpdateIgnoredPerPolicy:
+      return "kActionCodeOmahaUpdateIgnoredPerPolicy";
+    case kActionCodeOmahaUpdateDeferredPerPolicy:
+      return "kActionCodeOmahaUpdateDeferredPerPolicy";
+    case kActionCodeOmahaErrorInHTTPResponse:
+      return "kActionCodeOmahaErrorInHTTPResponse";
+    case kActionCodeDownloadOperationHashMissingError:
+      return "kActionCodeDownloadOperationHashMissingError";
+    case kActionCodeDownloadMetadataSignatureMissingError:
+      return "kActionCodeDownloadMetadataSignatureMissingError";
+    case kActionCodeOmahaUpdateDeferredForBackoff:
+      return "kActionCodeOmahaUpdateDeferredForBackoff";
+    case kActionCodeUmaReportedMax:
+      return "kActionCodeUmaReportedMax";
+    case kActionCodeOmahaRequestHTTPResponseBase:
+      return "kActionCodeOmahaRequestHTTPResponseBase";
+    case kActionCodeResumedFlag:
+      return "Resumed";
+    case kActionCodeDevModeFlag:
+      return "DevMode";
+    case kActionCodeTestImageFlag:
+      return "TestImage";
+    case kActionCodeTestOmahaUrlFlag:
+      return "TestOmahaUrl";
+    case kSpecialFlags:
+      return "kSpecialFlags";
+    // Don't add a default case to let the compiler warn about newly added
+    // error codes which should be added here.
+  }
+
+  return "Unknown error: " + base::UintToString(static_cast<unsigned>(code));
+}
 
 }  // namespace utils