blob: d6368e6548d42f22aafa7aca85647b8da7596445 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/bluetooth/bluetooth_metrics.h"
#include <stdint.h>
#include <algorithm>
#include <map>
#include <set>
#include <unordered_set>
#include "base/hash.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "device/bluetooth/bluetooth_uuid.h"
using device::BluetoothUUID;
namespace {
// Generates a hash from a canonical UUID string suitable for
// base::UmaHistogramSparse(positive int).
//
// Hash values can be produced manually using tool: bluetooth_metrics_hash.
int HashUUID(const std::string& canonical_uuid) {
DCHECK(canonical_uuid.size() == 36) << "HashUUID requires 128 bit UUID "
"strings in canonical format to "
"ensure consistent hash results.";
// TODO(520284): Other than verifying that |uuid| contains a value, this logic
// should be migrated to a dedicated histogram macro for hashed strings.
uint32_t data = base::PersistentHash(canonical_uuid);
// Strip off the sign bit because UMA doesn't support negative values,
// but takes a signed int as input.
return static_cast<int>(data & 0x7fffffff);
}
int HashUUID(const base::Optional<BluetoothUUID>& uuid) {
return uuid ? HashUUID(uuid->canonical_value()) : 0;
}
// The maximum number of devices that needs to be recorded.
const size_t kMaxNumOfDevices = 100;
} // namespace
namespace content {
// General
// requestDevice()
void RecordRequestDeviceOutcome(UMARequestDeviceOutcome outcome) {
UMA_HISTOGRAM_ENUMERATION("Bluetooth.Web.RequestDevice.Outcome",
static_cast<int>(outcome),
static_cast<int>(UMARequestDeviceOutcome::COUNT));
}
static void RecordRequestDeviceFilters(
const std::vector<blink::mojom::WebBluetoothLeScanFilterPtr>& filters) {
UMA_HISTOGRAM_COUNTS_100("Bluetooth.Web.RequestDevice.Filters.Count",
filters.size());
for (const auto& filter : filters) {
if (!filter->services) {
continue;
}
UMA_HISTOGRAM_COUNTS_100("Bluetooth.Web.RequestDevice.FilterSize",
filter->services->size());
for (const BluetoothUUID& service : filter->services.value()) {
// TODO(ortuno): Use a macro to histogram strings.
// http://crbug.com/520284
base::UmaHistogramSparse("Bluetooth.Web.RequestDevice.Filters.Services",
HashUUID(service));
}
}
}
static void RecordRequestDeviceOptionalServices(
const std::vector<BluetoothUUID>& optional_services) {
UMA_HISTOGRAM_COUNTS_100("Bluetooth.Web.RequestDevice.OptionalServices.Count",
optional_services.size());
for (const BluetoothUUID& service : optional_services) {
// TODO(ortuno): Use a macro to histogram strings.
// http://crbug.com/520284
base::UmaHistogramSparse(
"Bluetooth.Web.RequestDevice.OptionalServices.Services",
HashUUID(service));
}
}
static void RecordUnionOfServices(
const blink::mojom::WebBluetoothRequestDeviceOptionsPtr& options) {
std::unordered_set<std::string> union_of_services;
for (const BluetoothUUID& service : options->optional_services) {
union_of_services.insert(service.canonical_value());
}
if (options->filters) {
for (const auto& filter : options->filters.value()) {
if (!filter->services) {
continue;
}
for (const BluetoothUUID& service : filter->services.value()) {
union_of_services.insert(service.canonical_value());
}
}
}
UMA_HISTOGRAM_COUNTS_100("Bluetooth.Web.RequestDevice.UnionOfServices.Count",
union_of_services.size());
for (const std::string& service : union_of_services) {
// TODO(ortuno): Use a macro to histogram strings.
// http://crbug.com/520284
base::UmaHistogramSparse(
"Bluetooth.Web.RequestDevice.UnionOfServices.Services",
HashUUID(service));
}
}
void RecordRequestDeviceOptions(
const blink::mojom::WebBluetoothRequestDeviceOptionsPtr& options) {
UMA_HISTOGRAM_BOOLEAN("Bluetooth.Web.RequestDevice.Options.AcceptAllDevices",
options->accept_all_devices);
if (options->filters) {
RecordRequestDeviceFilters(options->filters.value());
}
RecordRequestDeviceOptionalServices(options->optional_services);
RecordUnionOfServices(options);
}
// GATTServer.Connect
void RecordConnectGATTOutcome(UMAConnectGATTOutcome outcome) {
UMA_HISTOGRAM_ENUMERATION("Bluetooth.Web.ConnectGATT.Outcome",
static_cast<int>(outcome),
static_cast<int>(UMAConnectGATTOutcome::COUNT));
}
void RecordConnectGATTOutcome(CacheQueryOutcome outcome) {
DCHECK(outcome == CacheQueryOutcome::NO_DEVICE);
RecordConnectGATTOutcome(UMAConnectGATTOutcome::NO_DEVICE);
}
void RecordConnectGATTTimeSuccess(const base::TimeDelta& duration) {
UMA_HISTOGRAM_MEDIUM_TIMES("Bluetooth.Web.ConnectGATT.TimeSuccess", duration);
}
void RecordConnectGATTTimeFailed(const base::TimeDelta& duration) {
UMA_HISTOGRAM_MEDIUM_TIMES("Bluetooth.Web.ConnectGATT.TimeFailed", duration);
}
// getPrimaryService & getPrimaryServices
void RecordGetPrimaryServicesOutcome(
blink::mojom::WebBluetoothGATTQueryQuantity quantity,
UMAGetPrimaryServiceOutcome outcome) {
switch (quantity) {
case blink::mojom::WebBluetoothGATTQueryQuantity::SINGLE:
UMA_HISTOGRAM_ENUMERATION(
"Bluetooth.Web.GetPrimaryService.Outcome", static_cast<int>(outcome),
static_cast<int>(UMAGetPrimaryServiceOutcome::COUNT));
return;
case blink::mojom::WebBluetoothGATTQueryQuantity::MULTIPLE:
UMA_HISTOGRAM_ENUMERATION(
"Bluetooth.Web.GetPrimaryServices.Outcome", static_cast<int>(outcome),
static_cast<int>(UMAGetPrimaryServiceOutcome::COUNT));
return;
}
}
void RecordGetPrimaryServicesOutcome(
blink::mojom::WebBluetoothGATTQueryQuantity quantity,
CacheQueryOutcome outcome) {
DCHECK(outcome == CacheQueryOutcome::NO_DEVICE);
RecordGetPrimaryServicesOutcome(quantity,
UMAGetPrimaryServiceOutcome::NO_DEVICE);
}
void RecordGetPrimaryServicesServices(
blink::mojom::WebBluetoothGATTQueryQuantity quantity,
const base::Optional<BluetoothUUID>& service) {
// TODO(ortuno): Use a macro to histogram strings.
// http://crbug.com/520284
switch (quantity) {
case blink::mojom::WebBluetoothGATTQueryQuantity::SINGLE:
base::UmaHistogramSparse("Bluetooth.Web.GetPrimaryService.Services",
HashUUID(service));
return;
case blink::mojom::WebBluetoothGATTQueryQuantity::MULTIPLE:
base::UmaHistogramSparse("Bluetooth.Web.GetPrimaryServices.Services",
HashUUID(service));
return;
}
}
// getCharacteristic & getCharacteristics
void RecordGetCharacteristicsOutcome(
blink::mojom::WebBluetoothGATTQueryQuantity quantity,
UMAGetCharacteristicOutcome outcome) {
switch (quantity) {
case blink::mojom::WebBluetoothGATTQueryQuantity::SINGLE:
UMA_HISTOGRAM_ENUMERATION(
"Bluetooth.Web.GetCharacteristic.Outcome", static_cast<int>(outcome),
static_cast<int>(UMAGetCharacteristicOutcome::COUNT));
return;
case blink::mojom::WebBluetoothGATTQueryQuantity::MULTIPLE:
UMA_HISTOGRAM_ENUMERATION(
"Bluetooth.Web.GetCharacteristics.Outcome", static_cast<int>(outcome),
static_cast<int>(UMAGetCharacteristicOutcome::COUNT));
return;
}
}
void RecordGetCharacteristicsOutcome(
blink::mojom::WebBluetoothGATTQueryQuantity quantity,
CacheQueryOutcome outcome) {
switch (outcome) {
case CacheQueryOutcome::SUCCESS:
case CacheQueryOutcome::BAD_RENDERER:
// No need to record a success or renderer crash.
NOTREACHED();
return;
case CacheQueryOutcome::NO_DEVICE:
RecordGetCharacteristicsOutcome(quantity,
UMAGetCharacteristicOutcome::NO_DEVICE);
return;
case CacheQueryOutcome::NO_SERVICE:
RecordGetCharacteristicsOutcome(quantity,
UMAGetCharacteristicOutcome::NO_SERVICE);
return;
case CacheQueryOutcome::NO_CHARACTERISTIC:
case CacheQueryOutcome::NO_DESCRIPTOR:
NOTREACHED();
return;
}
}
void RecordGetCharacteristicsCharacteristic(
blink::mojom::WebBluetoothGATTQueryQuantity quantity,
const base::Optional<BluetoothUUID>& characteristic) {
switch (quantity) {
case blink::mojom::WebBluetoothGATTQueryQuantity::SINGLE:
base::UmaHistogramSparse("Bluetooth.Web.GetCharacteristic.Characteristic",
HashUUID(characteristic));
return;
case blink::mojom::WebBluetoothGATTQueryQuantity::MULTIPLE:
base::UmaHistogramSparse(
"Bluetooth.Web.GetCharacteristics.Characteristic",
HashUUID(characteristic));
return;
}
}
void RecordGetDescriptorsDescriptor(
blink::mojom::WebBluetoothGATTQueryQuantity quantity,
const base::Optional<BluetoothUUID>& descriptor) {
switch (quantity) {
case blink::mojom::WebBluetoothGATTQueryQuantity::SINGLE:
base::UmaHistogramSparse("Bluetooth.Web.GetDescriptor.Descriptor",
HashUUID(descriptor));
return;
case blink::mojom::WebBluetoothGATTQueryQuantity::MULTIPLE:
base::UmaHistogramSparse("Bluetooth.Web.GetDescriptors.Descriptor",
HashUUID(descriptor));
return;
}
}
void RecordGetDescriptorsOutcome(
blink::mojom::WebBluetoothGATTQueryQuantity quantity,
UMAGetDescriptorOutcome outcome) {
switch (quantity) {
case blink::mojom::WebBluetoothGATTQueryQuantity::SINGLE:
UMA_HISTOGRAM_ENUMERATION(
"Bluetooth.Web.GetDescriptor.Outcome", static_cast<int>(outcome),
static_cast<int>(UMAGetDescriptorOutcome::COUNT));
return;
case blink::mojom::WebBluetoothGATTQueryQuantity::MULTIPLE:
UMA_HISTOGRAM_ENUMERATION(
"Bluetooth.Web.GetDescriptors.Outcome", static_cast<int>(outcome),
static_cast<int>(UMAGetDescriptorOutcome::COUNT));
return;
}
}
void RecordGetDescriptorsOutcome(
blink::mojom::WebBluetoothGATTQueryQuantity quantity,
CacheQueryOutcome outcome) {
switch (outcome) {
case CacheQueryOutcome::SUCCESS:
case CacheQueryOutcome::BAD_RENDERER:
// No need to record a success or renderer crash.
NOTREACHED();
return;
case CacheQueryOutcome::NO_DEVICE:
RecordGetDescriptorsOutcome(quantity, UMAGetDescriptorOutcome::NO_DEVICE);
return;
case CacheQueryOutcome::NO_SERVICE:
RecordGetDescriptorsOutcome(quantity,
UMAGetDescriptorOutcome::NO_SERVICE);
return;
case CacheQueryOutcome::NO_CHARACTERISTIC:
RecordGetDescriptorsOutcome(quantity,
UMAGetDescriptorOutcome::NO_CHARACTERISTIC);
return;
case CacheQueryOutcome::NO_DESCRIPTOR:
NOTREACHED();
return;
}
}
// GATT Operations
void RecordGATTOperationOutcome(UMAGATTOperation operation,
UMAGATTOperationOutcome outcome) {
switch (operation) {
case UMAGATTOperation::CHARACTERISTIC_READ:
RecordCharacteristicReadValueOutcome(outcome);
return;
case UMAGATTOperation::CHARACTERISTIC_WRITE:
RecordCharacteristicWriteValueOutcome(outcome);
return;
case UMAGATTOperation::START_NOTIFICATIONS:
RecordStartNotificationsOutcome(outcome);
return;
case UMAGATTOperation::DESCRIPTOR_READ:
RecordDescriptorReadValueOutcome(outcome);
return;
case UMAGATTOperation::DESCRIPTOR_WRITE:
RecordDescriptorWriteValueOutcome(outcome);
return;
case UMAGATTOperation::COUNT:
NOTREACHED();
return;
}
NOTREACHED();
}
static UMAGATTOperationOutcome TranslateCacheQueryOutcomeToGATTOperationOutcome(
CacheQueryOutcome outcome) {
switch (outcome) {
case CacheQueryOutcome::SUCCESS:
case CacheQueryOutcome::BAD_RENDERER:
// No need to record a success or renderer crash.
NOTREACHED();
return UMAGATTOperationOutcome::NOT_SUPPORTED;
case CacheQueryOutcome::NO_DEVICE:
return UMAGATTOperationOutcome::NO_DEVICE;
case CacheQueryOutcome::NO_SERVICE:
return UMAGATTOperationOutcome::NO_SERVICE;
case CacheQueryOutcome::NO_CHARACTERISTIC:
return UMAGATTOperationOutcome::NO_CHARACTERISTIC;
case CacheQueryOutcome::NO_DESCRIPTOR:
return UMAGATTOperationOutcome::NO_DESCRIPTOR;
}
NOTREACHED() << "No need to record success or renderer crash";
return UMAGATTOperationOutcome::NOT_SUPPORTED;
}
// Characteristic.readValue
void RecordCharacteristicReadValueOutcome(UMAGATTOperationOutcome outcome) {
UMA_HISTOGRAM_ENUMERATION("Bluetooth.Web.Characteristic.ReadValue.Outcome",
static_cast<int>(outcome),
static_cast<int>(UMAGATTOperationOutcome::COUNT));
}
void RecordCharacteristicReadValueOutcome(CacheQueryOutcome outcome) {
RecordCharacteristicReadValueOutcome(
TranslateCacheQueryOutcomeToGATTOperationOutcome(outcome));
}
// Characteristic.writeValue
void RecordCharacteristicWriteValueOutcome(UMAGATTOperationOutcome outcome) {
UMA_HISTOGRAM_ENUMERATION("Bluetooth.Web.Characteristic.WriteValue.Outcome",
static_cast<int>(outcome),
static_cast<int>(UMAGATTOperationOutcome::COUNT));
}
void RecordCharacteristicWriteValueOutcome(CacheQueryOutcome outcome) {
RecordCharacteristicWriteValueOutcome(
TranslateCacheQueryOutcomeToGATTOperationOutcome(outcome));
}
// Characteristic.startNotifications
void RecordStartNotificationsOutcome(UMAGATTOperationOutcome outcome) {
UMA_HISTOGRAM_ENUMERATION(
"Bluetooth.Web.Characteristic.StartNotifications.Outcome",
static_cast<int>(outcome),
static_cast<int>(UMAGATTOperationOutcome::COUNT));
}
void RecordStartNotificationsOutcome(CacheQueryOutcome outcome) {
RecordStartNotificationsOutcome(
TranslateCacheQueryOutcomeToGATTOperationOutcome(outcome));
}
void RecordRSSISignalStrength(int rssi) {
base::UmaHistogramSparse("Bluetooth.Web.RequestDevice.RSSISignalStrength",
rssi);
}
// Descriptor.readValue
void RecordDescriptorReadValueOutcome(UMAGATTOperationOutcome outcome) {
UMA_HISTOGRAM_ENUMERATION("Bluetooth.Web.Descriptor.ReadValue.Outcome",
static_cast<int>(outcome),
static_cast<int>(UMAGATTOperationOutcome::COUNT));
}
void RecordDescriptorReadValueOutcome(CacheQueryOutcome outcome) {
RecordDescriptorReadValueOutcome(
TranslateCacheQueryOutcomeToGATTOperationOutcome(outcome));
}
// Descriptor.writeValue
void RecordDescriptorWriteValueOutcome(UMAGATTOperationOutcome outcome) {
UMA_HISTOGRAM_ENUMERATION("Bluetooth.Web.Descriptor.WriteValue.Outcome",
static_cast<int>(outcome),
static_cast<int>(UMAGATTOperationOutcome::COUNT));
}
void RecordDescriptorWriteValueOutcome(CacheQueryOutcome outcome) {
RecordDescriptorWriteValueOutcome(
TranslateCacheQueryOutcomeToGATTOperationOutcome(outcome));
}
void RecordRSSISignalStrengthLevel(UMARSSISignalStrengthLevel level) {
UMA_HISTOGRAM_ENUMERATION(
"Bluetooth.Web.RequestDevice.RSSISignalStrengthLevel",
static_cast<int>(level),
static_cast<int>(UMARSSISignalStrengthLevel::COUNT));
}
void RecordNumOfDevices(bool accept_all_devices, size_t num_of_devices) {
if (!accept_all_devices) {
base::UmaHistogramSparse(
"Bluetooth.Web.RequestDevice."
"NumOfDevicesInChooserWhenNotAcceptingAllDevices",
std::min(num_of_devices, kMaxNumOfDevices));
}
}
} // namespace content