1 // Copyright 2020 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/nearby_sharing/nearby_share_metrics_logger.h"
6
7 #include "base/metrics/histogram_functions.h"
8 #include "base/numerics/safe_conversions.h"
9 #include "chrome/browser/nearby_sharing/common/nearby_share_prefs.h"
10 #include "components/policy/core/common/policy_service.h"
11 #include "components/policy/policy_constants.h"
12 #include "components/prefs/pref_service.h"
13
14 namespace {
15
16 const char kStartAdvertisingResultMetricPrefix[] =
17 "Nearby.Share.StartAdvertising.Result";
18 const char kStartAdvertisingResultFailureReasonMetricPrefix[] =
19 "Nearby.Share.StartAdvertising.Result.FailureReason";
20 const char kTransferMetricPrefix[] = "Nearby.Share.Transfer";
21 const size_t kBytesPerKilobyte = 1024;
22
23 // These values are persisted to logs. Entries should not be renumbered and
24 // numeric values should never be reused. If entries are added, kMaxValue should
25 // be updated.
26 enum class NearbyShareEnabledState {
27 kEnabledAndOnboarded = 0,
28 kEnabledAndNotOnboarded = 1,
29 kDisabledAndOnboarded = 2,
30 kDisabledAndNotOnboarded = 3,
31 kDisallowedByPolicy = 4,
32 kMaxValue = kDisallowedByPolicy
33 };
34
35 // These values are persisted to logs. Entries should not be renumbered and
36 // numeric values should never be reused. If entries are added, kMaxValue should
37 // be updated.
38 enum class TransferNotCompletedReason {
39 kUnknown = 0,
40 kAwaitingRemoteAcceptanceFailed = 1,
41 kFailed = 2,
42 kRejected = 3,
43 kCancelled = 4,
44 kTimedOut = 5,
45 kMediaUnavailable = 6,
46 kNotEnoughSpace = 7,
47 kUnsupportedAttachmentType = 8,
48 kMaxValue = kUnsupportedAttachmentType
49 };
50
51 // These values are persisted to logs. Entries should not be renumbered and
52 // numeric values should never be reused. If entries are added, kMaxValue should
53 // be updated.
54 enum class StartAdvertisingFailureReason {
55 kUnknown = 0,
56 kError = 1,
57 kOutOfOrderApiCall = 2,
58 kAlreadyHaveActiveStrategy = 3,
59 kAlreadyAdvertising = 4,
60 kBluetoothError = 5,
61 kBleError = 6,
62 kWifiLanError = 7,
63 kMaxValue = kWifiLanError
64 };
65
66 // These values are persisted to logs. Entries should not be renumbered and
67 // numeric values should never be reused. If entries are added, kMaxValue should
68 // be updated.
69 enum class FinalPayloadStatus {
70 kSuccess = 0,
71 kFailure = 1,
72 kCanceled = 2,
73 kMaxValue = kCanceled
74 };
75
TransferMetadataStatusToTransferNotCompletedReason(TransferMetadata::Status status)76 TransferNotCompletedReason TransferMetadataStatusToTransferNotCompletedReason(
77 TransferMetadata::Status status) {
78 switch (status) {
79 case TransferMetadata::Status::kAwaitingRemoteAcceptanceFailed:
80 return TransferNotCompletedReason::kAwaitingRemoteAcceptanceFailed;
81 case TransferMetadata::Status::kFailed:
82 return TransferNotCompletedReason::kFailed;
83 case TransferMetadata::Status::kRejected:
84 return TransferNotCompletedReason::kRejected;
85 case TransferMetadata::Status::kCancelled:
86 return TransferNotCompletedReason::kCancelled;
87 case TransferMetadata::Status::kTimedOut:
88 return TransferNotCompletedReason::kTimedOut;
89 case TransferMetadata::Status::kMediaUnavailable:
90 return TransferNotCompletedReason::kMediaUnavailable;
91 case TransferMetadata::Status::kNotEnoughSpace:
92 return TransferNotCompletedReason::kNotEnoughSpace;
93 case TransferMetadata::Status::kUnsupportedAttachmentType:
94 return TransferNotCompletedReason::kUnsupportedAttachmentType;
95 case TransferMetadata::Status::kUnknown:
96 case TransferMetadata::Status::kConnecting:
97 case TransferMetadata::Status::kAwaitingLocalConfirmation:
98 case TransferMetadata::Status::kAwaitingRemoteAcceptance:
99 case TransferMetadata::Status::kInProgress:
100 case TransferMetadata::Status::kComplete:
101 case TransferMetadata::Status::kMediaDownloading:
102 case TransferMetadata::Status::kExternalProviderLaunched:
103 NOTREACHED();
104 return TransferNotCompletedReason::kUnknown;
105 }
106 }
107
108 StartAdvertisingFailureReason
NearbyConnectionsStatusToStartAdvertisingFailureReason(location::nearby::connections::mojom::Status status)109 NearbyConnectionsStatusToStartAdvertisingFailureReason(
110 location::nearby::connections::mojom::Status status) {
111 switch (status) {
112 case location::nearby::connections::mojom::Status::kError:
113 return StartAdvertisingFailureReason::kError;
114 case location::nearby::connections::mojom::Status::kOutOfOrderApiCall:
115 return StartAdvertisingFailureReason::kOutOfOrderApiCall;
116 case location::nearby::connections::mojom::Status::
117 kAlreadyHaveActiveStrategy:
118 return StartAdvertisingFailureReason::kAlreadyHaveActiveStrategy;
119 case location::nearby::connections::mojom::Status::kAlreadyAdvertising:
120 return StartAdvertisingFailureReason::kAlreadyAdvertising;
121 case location::nearby::connections::mojom::Status::kBluetoothError:
122 return StartAdvertisingFailureReason::kBluetoothError;
123 case location::nearby::connections::mojom::Status::kBleError:
124 return StartAdvertisingFailureReason::kBleError;
125 case location::nearby::connections::mojom::Status::kWifiLanError:
126 return StartAdvertisingFailureReason::kWifiLanError;
127 case location::nearby::connections::mojom::Status::kSuccess:
128 NOTREACHED();
129 FALLTHROUGH;
130 case location::nearby::connections::mojom::Status::kAlreadyDiscovering:
131 case location::nearby::connections::mojom::Status::kEndpointIOError:
132 case location::nearby::connections::mojom::Status::kEndpointUnknown:
133 case location::nearby::connections::mojom::Status::kConnectionRejected:
134 case location::nearby::connections::mojom::Status::
135 kAlreadyConnectedToEndpoint:
136 case location::nearby::connections::mojom::Status::kNotConnectedToEndpoint:
137 case location::nearby::connections::mojom::Status::kPayloadUnknown:
138 return StartAdvertisingFailureReason::kUnknown;
139 }
140 }
141
PayloadStatusToFinalPayloadStatus(location::nearby::connections::mojom::PayloadStatus status)142 FinalPayloadStatus PayloadStatusToFinalPayloadStatus(
143 location::nearby::connections::mojom::PayloadStatus status) {
144 switch (status) {
145 case location::nearby::connections::mojom::PayloadStatus::kSuccess:
146 return FinalPayloadStatus::kSuccess;
147 case location::nearby::connections::mojom::PayloadStatus::kFailure:
148 return FinalPayloadStatus::kFailure;
149 case location::nearby::connections::mojom::PayloadStatus::kCanceled:
150 return FinalPayloadStatus::kCanceled;
151 case location::nearby::connections::mojom::PayloadStatus::kInProgress:
152 NOTREACHED();
153 return FinalPayloadStatus::kCanceled;
154 }
155 }
156
GetDirectionSubcategoryName(bool is_incoming)157 std::string GetDirectionSubcategoryName(bool is_incoming) {
158 return is_incoming ? ".Receive" : ".Send";
159 }
160
GetShareTargetTypeSubcategoryName(nearby_share::mojom::ShareTargetType type)161 std::string GetShareTargetTypeSubcategoryName(
162 nearby_share::mojom::ShareTargetType type) {
163 switch (type) {
164 case nearby_share::mojom::ShareTargetType::kUnknown:
165 return ".Unknown";
166 case nearby_share::mojom::ShareTargetType::kPhone:
167 return ".Phone";
168 case nearby_share::mojom::ShareTargetType::kTablet:
169 return ".Tablet";
170 case nearby_share::mojom::ShareTargetType::kLaptop:
171 return ".Laptop";
172 }
173 }
174
GetPayloadStatusSubcategoryName(location::nearby::connections::mojom::PayloadStatus status)175 std::string GetPayloadStatusSubcategoryName(
176 location::nearby::connections::mojom::PayloadStatus status) {
177 switch (status) {
178 case location::nearby::connections::mojom::PayloadStatus::kSuccess:
179 return ".Succeeded";
180 case location::nearby::connections::mojom::PayloadStatus::kFailure:
181 return ".Failed";
182 case location::nearby::connections::mojom::PayloadStatus::kCanceled:
183 return ".Cancelled";
184 case location::nearby::connections::mojom::PayloadStatus::kInProgress:
185 NOTREACHED();
186 return ".Cancelled";
187 }
188 }
189
GetUpgradedMediumSubcategoryName(base::Optional<location::nearby::connections::mojom::Medium> last_upgraded_medium)190 std::string GetUpgradedMediumSubcategoryName(
191 base::Optional<location::nearby::connections::mojom::Medium>
192 last_upgraded_medium) {
193 if (!last_upgraded_medium) {
194 return ".NoMediumUpgrade";
195 }
196
197 switch (*last_upgraded_medium) {
198 case location::nearby::connections::mojom::Medium::kWebRtc:
199 return ".WebRtcUpgrade";
200 case location::nearby::connections::mojom::Medium::kUnknown:
201 case location::nearby::connections::mojom::Medium::kMdns:
202 case location::nearby::connections::mojom::Medium::kBluetooth:
203 case location::nearby::connections::mojom::Medium::kWifiHotspot:
204 case location::nearby::connections::mojom::Medium::kBle:
205 case location::nearby::connections::mojom::Medium::kWifiLan:
206 case location::nearby::connections::mojom::Medium::kWifiAware:
207 case location::nearby::connections::mojom::Medium::kNfc:
208 case location::nearby::connections::mojom::Medium::kWifiDirect:
209 return ".UnknownMediumUpgrade";
210 }
211 }
212
213 } // namespace
214
RecordNearbyShareEnabledMetric(const PrefService * pref_service)215 void RecordNearbyShareEnabledMetric(const PrefService* pref_service) {
216 NearbyShareEnabledState state;
217
218 bool is_managed =
219 pref_service->IsManagedPreference(prefs::kNearbySharingEnabledPrefName);
220 bool is_enabled =
221 pref_service->GetBoolean(prefs::kNearbySharingEnabledPrefName);
222 bool is_onboarded =
223 pref_service->GetBoolean(prefs::kNearbySharingOnboardingCompletePrefName);
224
225 if (is_enabled) {
226 state = is_onboarded ? NearbyShareEnabledState::kEnabledAndOnboarded
227 : NearbyShareEnabledState::kEnabledAndNotOnboarded;
228 } else if (is_managed) {
229 state = NearbyShareEnabledState::kDisallowedByPolicy;
230 } else { // !is_enabled && !is_managed
231 state = is_onboarded ? NearbyShareEnabledState::kDisabledAndOnboarded
232 : NearbyShareEnabledState::kDisabledAndNotOnboarded;
233 }
234
235 base::UmaHistogramEnumeration("Nearby.Share.Enabled", state);
236 }
237
RecordNearbyShareTransferCompletionStatusMetric(bool is_incoming,nearby_share::mojom::ShareTargetType type,TransferMetadata::Status status)238 void RecordNearbyShareTransferCompletionStatusMetric(
239 bool is_incoming,
240 nearby_share::mojom::ShareTargetType type,
241 TransferMetadata::Status status) {
242 DCHECK(TransferMetadata::IsFinalStatus(status));
243
244 const std::string kPrefix =
245 kTransferMetricPrefix + std::string(".CompletionStatus");
246 std::string send_or_receive = GetDirectionSubcategoryName(is_incoming);
247 std::string share_target_type = GetShareTargetTypeSubcategoryName(type);
248
249 bool is_complete = status == TransferMetadata::Status::kComplete;
250 base::UmaHistogramBoolean(kPrefix, is_complete);
251 base::UmaHistogramBoolean(kPrefix + send_or_receive, is_complete);
252 base::UmaHistogramBoolean(kPrefix + share_target_type, is_complete);
253 base::UmaHistogramBoolean(kPrefix + send_or_receive + share_target_type,
254 is_complete);
255 if (!is_complete) {
256 const std::string kReasonInfix = ".NotCompletedReason";
257 TransferNotCompletedReason reason =
258 TransferMetadataStatusToTransferNotCompletedReason(status);
259 base::UmaHistogramEnumeration(kPrefix + kReasonInfix, reason);
260 base::UmaHistogramEnumeration(kPrefix + kReasonInfix + send_or_receive,
261 reason);
262 base::UmaHistogramEnumeration(kPrefix + kReasonInfix + share_target_type,
263 reason);
264 base::UmaHistogramEnumeration(
265 kPrefix + kReasonInfix + send_or_receive + share_target_type, reason);
266 }
267 }
268
RecordNearbyShareTransferSizeMetric(bool is_incoming,nearby_share::mojom::ShareTargetType type,base::Optional<location::nearby::connections::mojom::Medium> last_upgraded_medium,location::nearby::connections::mojom::PayloadStatus status,uint64_t payload_size_bytes)269 void RecordNearbyShareTransferSizeMetric(
270 bool is_incoming,
271 nearby_share::mojom::ShareTargetType type,
272 base::Optional<location::nearby::connections::mojom::Medium>
273 last_upgraded_medium,
274 location::nearby::connections::mojom::PayloadStatus status,
275 uint64_t payload_size_bytes) {
276 DCHECK_NE(status,
277 location::nearby::connections::mojom::PayloadStatus::kInProgress);
278
279 int kilobytes =
280 base::saturated_cast<int>(payload_size_bytes / kBytesPerKilobyte);
281 for (const std::string& direction_name :
282 {std::string(), GetDirectionSubcategoryName(is_incoming)}) {
283 for (const std::string& share_target_type_name :
284 {std::string(), GetShareTargetTypeSubcategoryName(type)}) {
285 for (const std::string& last_upgraded_medium_name :
286 {std::string(),
287 GetUpgradedMediumSubcategoryName(last_upgraded_medium)}) {
288 for (const std::string& payload_status_name :
289 {std::string(), GetPayloadStatusSubcategoryName(status)}) {
290 base::UmaHistogramCounts1M(
291 kTransferMetricPrefix + std::string(".TotalSize") +
292 direction_name + share_target_type_name +
293 last_upgraded_medium_name + payload_status_name,
294 kilobytes);
295 }
296 }
297 }
298 }
299 }
300
RecordNearbyShareTransferRateMetric(bool is_incoming,nearby_share::mojom::ShareTargetType type,base::Optional<location::nearby::connections::mojom::Medium> last_upgraded_medium,location::nearby::connections::mojom::PayloadStatus status,uint64_t transferred_payload_bytes,base::TimeDelta time_elapsed)301 void RecordNearbyShareTransferRateMetric(
302 bool is_incoming,
303 nearby_share::mojom::ShareTargetType type,
304 base::Optional<location::nearby::connections::mojom::Medium>
305 last_upgraded_medium,
306 location::nearby::connections::mojom::PayloadStatus status,
307 uint64_t transferred_payload_bytes,
308 base::TimeDelta time_elapsed) {
309 DCHECK_NE(status,
310 location::nearby::connections::mojom::PayloadStatus::kInProgress);
311
312 int kilobytes_per_second = base::saturated_cast<int>(base::ClampDiv(
313 base::ClampDiv(transferred_payload_bytes, time_elapsed.InSecondsF()),
314 kBytesPerKilobyte));
315 for (const std::string& direction_name :
316 {std::string(), GetDirectionSubcategoryName(is_incoming)}) {
317 for (const std::string& share_target_type_name :
318 {std::string(), GetShareTargetTypeSubcategoryName(type)}) {
319 for (const std::string& last_upgraded_medium_name :
320 {std::string(),
321 GetUpgradedMediumSubcategoryName(last_upgraded_medium)}) {
322 for (const std::string& payload_status_name :
323 {std::string(), GetPayloadStatusSubcategoryName(status)}) {
324 base::UmaHistogramCounts100000(
325 kTransferMetricPrefix + std::string(".Rate") + direction_name +
326 share_target_type_name + last_upgraded_medium_name +
327 payload_status_name,
328 kilobytes_per_second);
329 }
330 }
331 }
332 }
333 }
334
RecordNearbyShareTransferNumAttachmentsMetric(size_t num_text_attachments,size_t num_file_attachments)335 void RecordNearbyShareTransferNumAttachmentsMetric(
336 size_t num_text_attachments,
337 size_t num_file_attachments) {
338 const std::string kAttachmentInfix = ".NumAttachments";
339 base::UmaHistogramCounts100(kTransferMetricPrefix + kAttachmentInfix,
340 num_text_attachments + num_file_attachments);
341 base::UmaHistogramCounts100(
342 kTransferMetricPrefix + kAttachmentInfix + ".Text", num_text_attachments);
343 base::UmaHistogramCounts100(
344 kTransferMetricPrefix + kAttachmentInfix + ".File", num_file_attachments);
345 }
346
RecordNearbyShareStartAdvertisingResultMetric(bool is_high_visibility,location::nearby::connections::mojom::Status status)347 void RecordNearbyShareStartAdvertisingResultMetric(
348 bool is_high_visibility,
349 location::nearby::connections::mojom::Status status) {
350 const std::string mode_suffix =
351 is_high_visibility ? ".HighVisibility" : ".BLE";
352 const bool success =
353 status == location::nearby::connections::mojom::Status::kSuccess;
354
355 base::UmaHistogramBoolean(kStartAdvertisingResultMetricPrefix, success);
356 base::UmaHistogramBoolean(kStartAdvertisingResultMetricPrefix + mode_suffix,
357 success);
358 if (!success) {
359 StartAdvertisingFailureReason reason =
360 NearbyConnectionsStatusToStartAdvertisingFailureReason(status);
361 base::UmaHistogramEnumeration(
362 kStartAdvertisingResultFailureReasonMetricPrefix, reason);
363 base::UmaHistogramEnumeration(
364 kStartAdvertisingResultFailureReasonMetricPrefix + mode_suffix, reason);
365 }
366 }
367
RecordNearbyShareFinalPayloadStatusForUpgradedMedium(location::nearby::connections::mojom::PayloadStatus status,base::Optional<location::nearby::connections::mojom::Medium> medium)368 void RecordNearbyShareFinalPayloadStatusForUpgradedMedium(
369 location::nearby::connections::mojom::PayloadStatus status,
370 base::Optional<location::nearby::connections::mojom::Medium> medium) {
371 DCHECK_NE(status,
372 location::nearby::connections::mojom::PayloadStatus::kInProgress);
373 base::UmaHistogramEnumeration("Nearby.Share.Medium.FinalPayloadStatus" +
374 GetUpgradedMediumSubcategoryName(medium),
375 PayloadStatusToFinalPayloadStatus(status));
376 }
377