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