chromium/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordCheckupClientMetricsRecorder.java

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser.password_manager;

import android.os.SystemClock;

import org.chromium.base.metrics.RecordHistogram;
import org.chromium.chrome.browser.password_manager.CredentialManagerLauncher.CredentialManagerError;
import org.chromium.chrome.browser.password_manager.PasswordManagerHelper.PasswordCheckOperation;

import java.util.Optional;

/**
 * Records metrics for an asynchronous job or a series of jobs. The job is expected to have started
 * when the {@link PasswordCheckupClientMetricsRecorder} instance is created. Latency is reported
 * in {@link #recordMetrics(Optional<Exception>) recordMetrics} under that assumption.
 */
class PasswordCheckupClientMetricsRecorder {
    private static final String PASSWORD_CHECKUP_HISTOGRAM_BASE = "PasswordManager.PasswordCheckup";
    private static final String RUN_PASSWORD_CHECKUP_OPERATION_SUFFIX = "RunPasswordCheckup";
    private static final String GET_BREACHED_CREDENTIALS_COUNT_OPERATION_SUFFIX =
            "GetBreachedCredentialsCount";
    private static final String GET_PASSWORD_CHECKUP_INTENT_OPERATION_SUFFIX = "GetIntent";

    private final @PasswordCheckOperation int mOperation;
    private final long mStartTimeMs;

    PasswordCheckupClientMetricsRecorder(@PasswordCheckOperation int operation) {
        mOperation = operation;
        mStartTimeMs = SystemClock.elapsedRealtime();
    }

    /**
     * Records the metrics depending on {@link Exception} provided.
     * Success metric is always reported. Latency is reported separately for
     * successful and failed operations.
     * Error codes are reported for failed operations only. For GMS errors, API error code is
     * additionally reported.
     *
     * @param exception {@link Optional<Exception>} instance corresponding to the occurred error
     */
    void recordMetrics(Optional<Exception> exception) {
        RecordHistogram.recordBooleanHistogram(getHistogramName("Success"), !exception.isPresent());
        RecordHistogram.recordTimesHistogram(
                getHistogramName(exception.isPresent() ? "ErrorLatency" : "Latency"),
                SystemClock.elapsedRealtime() - mStartTimeMs);
        if (exception.isPresent()) {
            recordErrorMetrics(exception.get());
        }
    }

    private void recordErrorMetrics(Exception exception) {
        @CredentialManagerError
        int error = PasswordManagerAndroidBackendUtil.getPasswordCheckupBackendError(exception);
        RecordHistogram.recordEnumeratedHistogram(
                getHistogramName("Error"), error, CredentialManagerError.COUNT);

        if (error == CredentialManagerError.API_ERROR) {
            int apiErrorCode = PasswordManagerAndroidBackendUtil.getApiErrorCode(exception);
            RecordHistogram.recordSparseHistogram(getHistogramName("APIError"), apiErrorCode);
        }
    }

    private String getHistogramName(String metric) {
        return PASSWORD_CHECKUP_HISTOGRAM_BASE
                + "."
                + getSuffixForOperation(mOperation)
                + "."
                + metric;
    }

    /** Returns histogram suffix (infix in real) for a given operation. */
    private static String getSuffixForOperation(@PasswordCheckOperation int operation) {
        switch (operation) {
            case PasswordCheckOperation.RUN_PASSWORD_CHECKUP:
                return RUN_PASSWORD_CHECKUP_OPERATION_SUFFIX;
            case PasswordCheckOperation.GET_BREACHED_CREDENTIALS_COUNT:
                return GET_BREACHED_CREDENTIALS_COUNT_OPERATION_SUFFIX;
            case PasswordCheckOperation.GET_PASSWORD_CHECKUP_INTENT:
                return GET_PASSWORD_CHECKUP_INTENT_OPERATION_SUFFIX;
            default:
                throw new AssertionError("All operations need to be handled.");
        }
    }
}