Skip to content

Commit

Permalink
Printing: Create PrintPreview printing outcome metric
Browse files Browse the repository at this point in the history
This metric attempts to capture all expected outcomes when a user
goes to print on ChromeOS.

The metric gets recorded from two places: the Print Preview UI code and
the CrOS lower level printing code.

To capture whether a printer was manually selected, a new setting
`printer_manually_selected` is added to PrintSettings (but only set for
CrOS)

Tests: browser_test, ash_unittest
Change-Id: I1bb3e91cfd248727c724e338dc06b16db86dc57e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4220653
Reviewed-by: Rebekah Potter <rbpotter@chromium.org>
Commit-Queue: Gavin Williams <gavinwill@chromium.org>
Reviewed-by: Zentaro Kavanagh <zentaro@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1105485}
  • Loading branch information
Gavin Williams authored and Chromium LUCI CQ committed Feb 15, 2023
1 parent 371a2df commit 9324ed6
Show file tree
Hide file tree
Showing 20 changed files with 304 additions and 11 deletions.
88 changes: 84 additions & 4 deletions chrome/browser/ash/printing/cups_print_job_manager_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@
#include <utility>
#include <vector>

#include "base/check.h"
#include "base/check_op.h"
#include "base/compiler_specific.h"
#include "base/containers/flat_map.h"
#include "base/functional/bind.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/notreached.h"
#include "base/strings/utf_string_conversions.h"
Expand All @@ -32,6 +35,7 @@
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/grit/generated_resources.h"
#include "chromeos/printing/printing_constants.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
Expand Down Expand Up @@ -64,6 +68,10 @@ enum JobResultForHistogram {
RESULT_MAX
};

struct PrinterMetrics {
bool printer_manually_selected;
};

// Returns the appropriate JobResultForHistogram for a given |state|. Only
// FINISHED and PRINTER_CANCEL are derived from CupsPrintJob::State.
JobResultForHistogram ResultForHistogram(CupsPrintJob::State state) {
Expand Down Expand Up @@ -128,6 +136,15 @@ class CupsPrintJobManagerImpl : public CupsPrintJobManager {
void OnDocDone(::printing::PrintJob* job,
::printing::PrintedDocument* document,
int job_id) {
// Store the printer data for metrics to be recorded upon print job status
// updates.
const std::string printer_id =
base::UTF16ToUTF8(document->settings().device_name());
const std::string key = CupsPrintJob::CreateUniqueId(printer_id, job_id);
DCHECK(!printer_metrics_cache_.contains(key));
printer_metrics_cache_.emplace(
key, PrinterMetrics{job->settings().printer_manually_selected()});

// This event occurs after the print job has been successfully sent to the
// spooler which is when we begin tracking the print queue.
DCHECK(document);
Expand All @@ -136,9 +153,8 @@ class CupsPrintJobManagerImpl : public CupsPrintJobManager {
title = ::printing::SimplifyDocumentTitle(
l10n_util::GetStringUTF16(IDS_DEFAULT_PRINT_DOCUMENT_TITLE));
}
CreatePrintJob(base::UTF16ToUTF8(document->settings().device_name()),
base::UTF16ToUTF8(title), job_id, document->page_count(),
job->source(), job->source_id(),
CreatePrintJob(printer_id, base::UTF16ToUTF8(title), job_id,
document->page_count(), job->source(), job->source_id(),
PrintSettingsToProto(document->settings()));
}

Expand Down Expand Up @@ -205,7 +221,9 @@ class CupsPrintJobManagerImpl : public CupsPrintJobManager {
const std::string printer_id = job->printer().id();

// Stop monitoring jobs after we cancel them. The user no longer cares.
jobs_.erase(job->GetUniqueId());
const std::string unique_id = job->GetUniqueId();
jobs_.erase(unique_id);
printer_metrics_cache_.erase(unique_id);

cups_wrapper_->CancelJob(printer_id, job_id);
}
Expand Down Expand Up @@ -294,6 +312,7 @@ class CupsPrintJobManagerImpl : public CupsPrintJobManager {
VLOG(1) << "Removing Job " << print_job->document_title();
RecordJobResult(ResultForHistogram(print_job->state()));
jobs_.erase(entry);
printer_metrics_cache_.erase(key);
} else {
active_jobs.push_back(key);
}
Expand Down Expand Up @@ -325,6 +344,7 @@ class CupsPrintJobManagerImpl : public CupsPrintJobManager {
}

jobs_.clear();
printer_metrics_cache_.clear();
}

// Notify observers that a state update has occurred for |job|.
Expand Down Expand Up @@ -366,6 +386,61 @@ class CupsPrintJobManagerImpl : public CupsPrintJobManager {
NotifyJobUpdated(job);
break;
}

RecordPrinterMetricIfJobComplete(job);
}

// Only record metrics for print jobs with states that denote success complete
// or an error status.
void RecordPrinterMetricIfJobComplete(base::WeakPtr<CupsPrintJob> job) {
const std::string unique_id = job->GetUniqueId();
auto iter = printer_metrics_cache_.find(unique_id);
if (iter == printer_metrics_cache_.end()) {
return;
}

bool print_job_success;
switch (job->state()) {
// Consider these print job statuses as "in progress" jobs so don't record
// metrics yet.
case CupsPrintJob::State::STATE_NONE:
case CupsPrintJob::State::STATE_WAITING:
case CupsPrintJob::State::STATE_STARTED:
case CupsPrintJob::State::STATE_PAGE_DONE:
case CupsPrintJob::State::STATE_RESUMED:
return;
// The set of states for a print job considered "failed" for metrics
// recording.
case CupsPrintJob::State::STATE_SUSPENDED:
case CupsPrintJob::State::STATE_CANCELLED:
case CupsPrintJob::State::STATE_FAILED:
case CupsPrintJob::State::STATE_ERROR:
print_job_success = false;
break;
case CupsPrintJob::State::STATE_DOCUMENT_DONE:
print_job_success = true;
break;
}

const PrinterMetrics& metrics = iter->second;
chromeos::PrintAttemptOutcome print_attempt_outcome;
if (print_job_success && metrics.printer_manually_selected) {
print_attempt_outcome = chromeos::PrintAttemptOutcome::
kPrintJobSuccessManuallySelectedPrinter;
} else if (print_job_success && !metrics.printer_manually_selected) {
print_attempt_outcome =
chromeos::PrintAttemptOutcome::kPrintJobSuccessInitialPrinter;
} else if (!print_job_success && metrics.printer_manually_selected) {
print_attempt_outcome =
chromeos::PrintAttemptOutcome::kPrintJobFailManuallySelectedPrinter;
} else {
print_attempt_outcome =
chromeos::PrintAttemptOutcome::kPrintJobFailInitialPrinter;
}
base::UmaHistogramEnumeration("PrintPreview.PrintAttemptOutcome",
print_attempt_outcome);

printer_metrics_cache_.erase(unique_id);
}

// Ongoing print jobs.
Expand All @@ -374,6 +449,11 @@ class CupsPrintJobManagerImpl : public CupsPrintJobManager {
// Records the number of consecutive times the GetJobs query has failed.
int retry_count_ = 0;

// Stores the desired printer metrics in a map keyed by the CupsPrintJob
// `unique_id`. Once the corresponding print job either fails or completes,
// record the metrics entry to histograms and remove it from the map.
base::flat_map<std::string, PrinterMetrics> printer_metrics_cache_;

base::RepeatingTimer timer_;
std::unique_ptr<CupsWrapper> cups_wrapper_;
::printing::PrintJobManager::DocDoneCallbackList::Subscription subscription_;
Expand Down
14 changes: 14 additions & 0 deletions chrome/browser/resources/print_preview/data/destination.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,12 @@ export class Destination {
*/
private eulaUrl_: string = '';

/**
* True if the user opened the print preview dropdown and selected a different
* printer than the original destination.
*/
private printerManuallySelected_: boolean = false;

/**
* Stores the printer status reason for a local Chrome OS printer.
*/
Expand Down Expand Up @@ -333,6 +339,14 @@ export class Destination {
this.eulaUrl_ = eulaUrl;
}

get printerManuallySelected(): boolean {
return this.printerManuallySelected_;
}

set printerManuallySelected(printerManuallySelected: boolean) {
this.printerManuallySelected_ = printerManuallySelected;
}

/**
* @return The printer status reason for a local Chrome OS printer.
*/
Expand Down
16 changes: 14 additions & 2 deletions chrome/browser/resources/print_preview/data/destination_store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -562,9 +562,21 @@ export class DestinationStore extends EventTarget {
return new DestinationMatch(idRegExp, displayNameRegExp);
}

/** @param Key identifying the destination to select */
/**
* This function is only invoked when the user selects a new destination via
* the UI. Programmatic selection of a destination should not use this
* function.
* @param Key identifying the destination to select
*/
selectDestinationByKey(key: string) {
assert(this.tryToSelectDestinationByKey_(key));
const success = this.tryToSelectDestinationByKey_(key);
assert(success);
// <if expr="is_chromeos">
if (success && this.selectedDestination_ &&
this.selectedDestination_.type !== PrinterType.PDF_PRINTER) {
this.selectedDestination_.printerManuallySelected = true;
}
// </if>
}

/**
Expand Down
2 changes: 2 additions & 0 deletions chrome/browser/resources/print_preview/data/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ export type PrintTicket = Ticket&{
pageHeight: number,
pageWidth: number,
// <if expr="is_chromeos">
printerManuallySelected: boolean,
printToGoogleDrive: boolean,
// </if>
showSystemDialog: boolean,
Expand Down Expand Up @@ -1567,6 +1568,7 @@ export class PrintPreviewModelElement extends PolymerElement {
// <if expr="is_chromeos">
printToGoogleDrive:
destination.id === GooglePromotedDestinationId.SAVE_TO_DRIVE_CROS,
printerManuallySelected: destination.printerManuallySelected,
// </if>
};

Expand Down
14 changes: 14 additions & 0 deletions chrome/browser/resources/print_preview/data/printer_status_cros.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,20 @@ export enum PrinterState {
UNKNOWN = 2,
}

export enum PrintAttemptOutcome {
CANCELLED_PRINT_BUTTON_DISABLED = 0,
CANCELLED_NO_PRINTERS_AVAILABLE = 1,
CANCELLED_OTHER_PRINTERS_AVAILABLE = 2,
CANCELLED_PRINTER_ERROR_STATUS = 3,
CANCELLED_PRINTER_GOOD_STATUS = 4,
CANCELLED_PRINTER_UNKNOWN_STATUS = 5,
PDF_PRINT_ATTEMPTED = 6,
PRINT_JOB_SUCCESS_INITIAL_PRINTER = 7,
PRINT_JOB_SUCCESS_MANUALLY_SELECTED_PRINTER = 8,
PRINT_JOB_FAIL_INITIAL_PRINTER = 9,
PRINT_JOB_FAIL_MANUALLY_SELECTED_PRINTER = 10,
}

interface StatusReasonEntry {
reason: PrinterStatusReason;
severity: PrinterStatusSeverity;
Expand Down
12 changes: 11 additions & 1 deletion chrome/browser/resources/print_preview/native_layer_cros.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {sendWithPromise} from 'chrome://resources/js/cr.js';

import {Cdd} from './data/cdd.js';
import {ExtensionDestinationInfo} from './data/local_parsers.js';
import {PrinterStatus} from './data/printer_status_cros.js';
import {PrintAttemptOutcome, PrinterStatus} from './data/printer_status_cros.js';

export interface PrinterSetupResponse {
printerId: string;
Expand Down Expand Up @@ -71,6 +71,12 @@ export interface NativeLayerCros {
* fetching mode.
*/
getPrintServersConfig(): Promise<PrintServersConfig>;

/**
* Records the `PrintPreview.PrintAttemptOutcome` histogram for capturing
* the result from opening Print Preview.
*/
recordPrintAttemptOutcome(printAttemptOutcome: PrintAttemptOutcome): void;
}

export class NativeLayerCrosImpl implements NativeLayerCros {
Expand Down Expand Up @@ -105,6 +111,10 @@ export class NativeLayerCrosImpl implements NativeLayerCros {
return sendWithPromise('getPrintServersConfig');
}

recordPrintAttemptOutcome(printAttemptOutcome: PrintAttemptOutcome) {
chrome.send('recordPrintAttemptOutcome', [printAttemptOutcome]);
}

static getInstance(): NativeLayerCros {
return instance || (instance = new NativeLayerCrosImpl());
}
Expand Down
66 changes: 62 additions & 4 deletions chrome/browser/resources/print_preview/ui/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,29 @@ import '../data/document_info.js';
import './sidebar.js';

import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.js';
import {isMac, isWindows} from 'chrome://resources/js/platform.js';
import {FocusOutlineManager} from 'chrome://resources/js/focus_outline_manager.js';
import {WebUiListenerMixin} from 'chrome://resources/cr_elements/web_ui_listener_mixin.js';
import {EventTracker} from 'chrome://resources/js/event_tracker.js';
import {FocusOutlineManager} from 'chrome://resources/js/focus_outline_manager.js';
import {isMac, isWindows} from 'chrome://resources/js/platform.js';
import {hasKeyModifiers} from 'chrome://resources/js/util_ts.js';
import {WebUiListenerMixin} from 'chrome://resources/cr_elements/web_ui_listener_mixin.js';
import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';

import {Destination, PrinterType} from '../data/destination.js';
import {Destination, DestinationOrigin, PrinterType} from '../data/destination.js';
import {DocumentSettings, PrintPreviewDocumentInfoElement} from '../data/document_info.js';
import {Margins} from '../data/margins.js';
import {MeasurementSystem} from '../data/measurement_system.js';
import {DuplexMode, PrintPreviewModelElement, whenReady} from '../data/model.js';
import {PrintableArea} from '../data/printable_area.js';
// <if expr="is_chromeos">
import {computePrinterState, PrintAttemptOutcome, PrinterState} from '../data/printer_status_cros.js';
// </if>
import {Size} from '../data/size.js';
import {Error, PrintPreviewStateElement, State} from '../data/state.js';
import {NativeInitialSettings, NativeLayer, NativeLayerImpl} from '../native_layer.js';
// <if expr="is_chromeos">
import {NativeLayerCrosImpl} from '../native_layer_cros.js';

// </if>

import {getTemplate} from './app.html.js';
import {DestinationState} from './destination_settings.js';
Expand Down Expand Up @@ -209,6 +216,10 @@ export class PrintPreviewAppElement extends PrintPreviewAppElementBase {
e.preventDefault();
}

// <if expr="is_chromeos">
this.recordCancelMetricCros_();
// </if>

return;
}

Expand Down Expand Up @@ -401,6 +412,13 @@ export class PrintPreviewAppElement extends PrintPreviewAppElementBase {
this.nativeLayer_!.hidePreview();
}
} else if (this.state === State.PRINTING) {
// <if expr="is_chromeos">
if (this.destination_.type === PrinterType.PDF_PRINTER) {
NativeLayerCrosImpl.getInstance().recordPrintAttemptOutcome(
PrintAttemptOutcome.PDF_PRINT_ATTEMPTED);
}
// </if>

const whenPrintDone =
this.nativeLayer_!.print(this.$.model.createPrintTicket(
this.destination_, this.openPdfInPreview_,
Expand Down Expand Up @@ -430,10 +448,50 @@ export class PrintPreviewAppElement extends PrintPreviewAppElementBase {
}

private onCancelRequested_() {
// <if expr="is_chromeos">
this.recordCancelMetricCros_();
// </if>
this.cancelled_ = true;
this.$.state.transitTo(State.CLOSING);
}

// <if expr="is_chromeos">
/** Records the Print Preview state when cancel is requested. */
private recordCancelMetricCros_() {
let printAttemptOutcome = null;
if (this.state !== State.READY) {
// Print button is disabled when state !== READY.
printAttemptOutcome = PrintAttemptOutcome.CANCELLED_PRINT_BUTTON_DISABLED;
} else if (!this.$.sidebar.printerExistsInDisplayedDestinations()) {
printAttemptOutcome = PrintAttemptOutcome.CANCELLED_NO_PRINTERS_AVAILABLE;
} else if (this.destination_.origin === DestinationOrigin.CROS) {
// Fetch and record printer state.
switch (computePrinterState(this.destination_.printerStatusReason)) {
case PrinterState.GOOD:
printAttemptOutcome =
PrintAttemptOutcome.CANCELLED_PRINTER_GOOD_STATUS;
break;
case PrinterState.ERROR:
printAttemptOutcome =
PrintAttemptOutcome.CANCELLED_PRINTER_ERROR_STATUS;
break;
case PrinterState.UNKNOWN:
printAttemptOutcome =
PrintAttemptOutcome.CANCELLED_PRINTER_UNKNOWN_STATUS;
break;
}
} else {
printAttemptOutcome =
PrintAttemptOutcome.CANCELLED_OTHER_PRINTERS_AVAILABLE;
}

if (printAttemptOutcome !== null) {
NativeLayerCrosImpl.getInstance().recordPrintAttemptOutcome(
printAttemptOutcome);
}
}
// </if>

/**
* @param e The event containing the new validity.
*/
Expand Down

0 comments on commit 9324ed6

Please sign in to comment.