diff options
-rw-r--r-- | system/bta/Android.bp | 4 | ||||
-rw-r--r-- | system/bta/le_audio/client.cc | 31 | ||||
-rw-r--r-- | system/bta/le_audio/devices.cc | 3 | ||||
-rw-r--r-- | system/bta/le_audio/metrics_collector.cc | 294 | ||||
-rw-r--r-- | system/bta/le_audio/metrics_collector.h | 136 | ||||
-rw-r--r-- | system/bta/le_audio/metrics_collector_linux.cc | 45 | ||||
-rw-r--r-- | system/bta/le_audio/metrics_collector_test.cc | 370 | ||||
-rw-r--r-- | system/common/metrics.cc | 49 | ||||
-rw-r--r-- | system/common/metrics.h | 15 | ||||
-rw-r--r-- | system/common/metrics_linux.cc | 13 | ||||
-rw-r--r-- | system/test/mock/mock_common_metrics.cc | 15 |
11 files changed, 974 insertions, 1 deletions
diff --git a/system/bta/Android.bp b/system/bta/Android.bp index 020a06d9e0..1da4e04c6f 100644 --- a/system/bta/Android.bp +++ b/system/bta/Android.bp @@ -101,6 +101,7 @@ cc_library_static { "le_audio/le_audio_set_configuration_provider.cc", "le_audio/le_audio_set_configuration_provider_json.cc", "le_audio/le_audio_types.cc", + "le_audio/metrics_collector.cc", "has/has_client.cc", "has/has_ctp.cc", "has/has_journal.cc", @@ -550,6 +551,7 @@ cc_test { "le_audio/le_audio_set_configuration_provider_json.cc", "le_audio/le_audio_types.cc", "le_audio/le_audio_types_test.cc", + "le_audio/metrics_collector_linux.cc", "le_audio/mock_iso_manager.cc", "test/common/mock_controller.cc", "le_audio/state_machine.cc", @@ -611,6 +613,8 @@ cc_test { "le_audio/le_audio_client_test.cc", "le_audio/le_audio_set_configuration_provider_json.cc", "le_audio/le_audio_types.cc", + "le_audio/metrics_collector.cc", + "le_audio/metrics_collector_test.cc", "le_audio/mock_iso_manager.cc", "le_audio/mock_state_machine.cc", "test/common/btm_api_mock.cc", diff --git a/system/bta/le_audio/client.cc b/system/bta/le_audio/client.cc index 287a9acfa9..2f877f0097 100644 --- a/system/bta/le_audio/client.cc +++ b/system/bta/le_audio/client.cc @@ -39,6 +39,7 @@ #include "gd/common/strings.h" #include "le_audio_set_configuration_provider.h" #include "le_audio_types.h" +#include "metrics_collector.h" #include "osi/include/log.h" #include "osi/include/osi.h" #include "osi/include/properties.h" @@ -880,6 +881,10 @@ class LeAudioClientImpl : public LeAudioClient { leAudioDevices_.Add(address, true); } else { leAudioDevice->connecting_actively_ = true; + + le_audio::MetricsCollector::Get()->OnConnectionStateChanged( + leAudioDevice->group_id_, address, ConnectionState::CONNECTING, + le_audio::ConnectionStatus::SUCCESS); } BTA_GATTC_Open(gatt_if_, address, true, false); @@ -1253,6 +1258,9 @@ class LeAudioClientImpl : public LeAudioClient { LOG(ERROR) << "Failed to connect to LeAudio leAudioDevice, status: " << +status; callbacks_->OnConnectionState(ConnectionState::DISCONNECTED, address); + le_audio::MetricsCollector::Get()->OnConnectionStateChanged( + leAudioDevice->group_id_, address, ConnectionState::CONNECTED, + le_audio::ConnectionStatus::FAILED); return; } @@ -1303,6 +1311,9 @@ class LeAudioClientImpl : public LeAudioClient { } LOG(ERROR) << __func__ << " Encryption error"; + le_audio::MetricsCollector::Get()->OnConnectionStateChanged( + leAudioDevice->group_id_, address, ConnectionState::CONNECTED, + le_audio::ConnectionStatus::FAILED); } void RegisterKnownNotifications(LeAudioDevice* leAudioDevice) { @@ -1358,6 +1369,9 @@ class LeAudioClientImpl : public LeAudioClient { BTA_GATTC_Close(leAudioDevice->conn_id_); if (leAudioDevice->connecting_actively_) { callbacks_->OnConnectionState(ConnectionState::DISCONNECTED, address); + le_audio::MetricsCollector::Get()->OnConnectionStateChanged( + leAudioDevice->group_id_, address, ConnectionState::CONNECTED, + le_audio::ConnectionStatus::FAILED); } return; } @@ -1402,6 +1416,10 @@ class LeAudioClientImpl : public LeAudioClient { leAudioDevice->closing_stream_for_disconnection_ = false; leAudioDevice->encrypted_ = false; + le_audio::MetricsCollector::Get()->OnConnectionStateChanged( + leAudioDevice->group_id_, address, ConnectionState::DISCONNECTED, + le_audio::ConnectionStatus::SUCCESS); + if (leAudioDevice->removing_device_) { if (leAudioDevice->group_id_ != bluetooth::groups::kGroupUnknown) { auto group = aseGroups_.FindById(leAudioDevice->group_id_); @@ -1953,6 +1971,9 @@ class LeAudioClientImpl : public LeAudioClient { btif_storage_set_leaudio_autoconnect(leAudioDevice->address_, true); leAudioDevice->first_connection_ = false; } + le_audio::MetricsCollector::Get()->OnConnectionStateChanged( + leAudioDevice->group_id_, leAudioDevice->address_, + ConnectionState::CONNECTED, le_audio::ConnectionStatus::SUCCESS); } bool IsAseAcceptingAudioData(struct ase* ase) { @@ -2799,8 +2820,10 @@ class LeAudioClientImpl : public LeAudioClient { /* Last suspends group - triggers group stop */ if ((audio_receiver_state_ == AudioState::IDLE) || - (audio_receiver_state_ == AudioState::READY_TO_RELEASE)) + (audio_receiver_state_ == AudioState::READY_TO_RELEASE)) { OnAudioSuspend(); + le_audio::MetricsCollector::Get()->OnStreamEnded(active_group_id_); + } DLOG(INFO) << __func__ << " OUT: audio_receiver_state_: " << audio_receiver_state_ @@ -2898,6 +2921,8 @@ class LeAudioClientImpl : public LeAudioClient { if (alarm_is_scheduled(suspend_timeout_)) alarm_cancel(suspend_timeout_); leAudioClientAudioSource->ConfirmStreamingRequest(); + le_audio::MetricsCollector::Get()->OnStreamStarted( + active_group_id_, current_context_type_); break; case AudioState::RELEASING: /* Keep wainting. After release is done, Audio Hal will be notified @@ -3443,6 +3468,8 @@ class LeAudioClientImpl : public LeAudioClient { stream_setup_end_timestamp_ = bluetooth::common::time_get_os_boottime_us(); + le_audio::MetricsCollector::Get()->OnStreamStarted( + active_group_id_, current_context_type_); break; case GroupStreamStatus::SUSPENDED: stream_setup_end_timestamp_ = 0; @@ -3575,6 +3602,7 @@ class LeAudioClientImpl : public LeAudioClient { leAudioClientAudioSink->Release(audio_sink_instance_); audio_sink_instance_ = nullptr; } + le_audio::MetricsCollector::Get()->OnStreamEnded(active_group_id_); } }; @@ -3855,6 +3883,7 @@ void LeAudioClient::Cleanup(base::Callback<void()> cleanupCb) { CodecManager::GetInstance()->Stop(); LeAudioGroupStateMachine::Cleanup(); IsoManager::GetInstance()->Stop(); + le_audio::MetricsCollector::Get()->Flush(); } void LeAudioClient::InitializeAudioClients( diff --git a/system/bta/le_audio/devices.cc b/system/bta/le_audio/devices.cc index ba7f462cdd..6943f3efe7 100644 --- a/system/bta/le_audio/devices.cc +++ b/system/bta/le_audio/devices.cc @@ -30,6 +30,7 @@ #include "device/include/controller.h" #include "gd/common/strings.h" #include "le_audio_set_configuration_provider.h" +#include "metrics_collector.h" #include "osi/include/log.h" #include "stack/include/acl_api.h" @@ -57,6 +58,7 @@ void LeAudioDeviceGroup::AddNode( const std::shared_ptr<LeAudioDevice>& leAudioDevice) { leAudioDevice->group_id_ = group_id_; leAudioDevices_.push_back(std::weak_ptr<LeAudioDevice>(leAudioDevice)); + MetricsCollector::Get()->OnGroupSizeUpdate(group_id_, leAudioDevices_.size()); } void LeAudioDeviceGroup::RemoveNode( @@ -73,6 +75,7 @@ void LeAudioDeviceGroup::RemoveNode( leAudioDevices_.begin(), leAudioDevices_.end(), [&leAudioDevice](auto& d) { return d.lock() == leAudioDevice; }), leAudioDevices_.end()); + MetricsCollector::Get()->OnGroupSizeUpdate(group_id_, leAudioDevices_.size()); } bool LeAudioDeviceGroup::IsEmpty(void) { return leAudioDevices_.size() == 0; } diff --git a/system/bta/le_audio/metrics_collector.cc b/system/bta/le_audio/metrics_collector.cc new file mode 100644 index 0000000000..4a00d2da84 --- /dev/null +++ b/system/bta/le_audio/metrics_collector.cc @@ -0,0 +1,294 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "metrics_collector.h" + +#include <chrono> +#include <memory> +#include <vector> + +#include "common/metrics.h" + +namespace le_audio { + +using ClockTimePoint = + std::chrono::time_point<std::chrono::high_resolution_clock>; +using bluetooth::le_audio::ConnectionState; +using le_audio::types::LeAudioContextType; + +const static ClockTimePoint kInvalidTimePoint{}; + +MetricsCollector* MetricsCollector::instance = nullptr; + +inline int64_t get_timedelta_nanos(const ClockTimePoint& t1, + const ClockTimePoint& t2) { + if (t1 == kInvalidTimePoint || t2 == kInvalidTimePoint) { + return -1; + } + return std::abs( + std::chrono::duration_cast<std::chrono::nanoseconds>(t1 - t2).count()); +} + +const static std::unordered_map<LeAudioContextType, LeAudioMetricsContextType> + kContextTypeTable = { + {LeAudioContextType::UNINITIALIZED, LeAudioMetricsContextType::INVALID}, + {LeAudioContextType::UNSPECIFIED, + LeAudioMetricsContextType::UNSPECIFIED}, + {LeAudioContextType::CONVERSATIONAL, + LeAudioMetricsContextType::COMMUNICATION}, + {LeAudioContextType::MEDIA, LeAudioMetricsContextType::MEDIA}, + {LeAudioContextType::GAME, LeAudioMetricsContextType::GAME}, + {LeAudioContextType::INSTRUCTIONAL, + LeAudioMetricsContextType::INSTRUCTIONAL}, + {LeAudioContextType::VOICEASSISTANTS, + LeAudioMetricsContextType::MAN_MACHINE}, + {LeAudioContextType::LIVE, LeAudioMetricsContextType::LIVE}, + {LeAudioContextType::SOUNDEFFECTS, + LeAudioMetricsContextType::ATTENTION_SEEKING}, + {LeAudioContextType::NOTIFICATIONS, + LeAudioMetricsContextType::ATTENTION_SEEKING}, + {LeAudioContextType::RINGTONE, LeAudioMetricsContextType::RINGTONE}, + {LeAudioContextType::ALERTS, + LeAudioMetricsContextType::IMMEDIATE_ALERT}, + {LeAudioContextType::EMERGENCYALARM, + LeAudioMetricsContextType::EMERGENCY_ALERT}, + {LeAudioContextType::RFU, LeAudioMetricsContextType::RFU}, +}; + +inline int32_t to_atom_context_type(const LeAudioContextType stack_type) { + auto it = kContextTypeTable.find(stack_type); + if (it != kContextTypeTable.end()) { + return static_cast<int32_t>(it->second); + } + return static_cast<int32_t>(LeAudioMetricsContextType::INVALID); +} + +class DeviceMetrics { + public: + RawAddress address_; + ClockTimePoint connecting_timepoint_ = kInvalidTimePoint; + ClockTimePoint connected_timepoint_ = kInvalidTimePoint; + ClockTimePoint disconnected_timepoint_ = kInvalidTimePoint; + int32_t connection_status_ = 0; + int32_t disconnection_status_ = 0; + + DeviceMetrics(const RawAddress& address) : address_(address) {} + + void AddStateChangedEvent(ConnectionState state, ConnectionStatus status) { + switch (state) { + case ConnectionState::CONNECTING: + connecting_timepoint_ = std::chrono::high_resolution_clock::now(); + break; + case ConnectionState::CONNECTED: + connected_timepoint_ = std::chrono::high_resolution_clock::now(); + connection_status_ = static_cast<int32_t>(status); + break; + case ConnectionState::DISCONNECTED: + disconnected_timepoint_ = std::chrono::high_resolution_clock::now(); + disconnection_status_ = static_cast<int32_t>(status); + break; + case ConnectionState::DISCONNECTING: + // Ignore + break; + } + } +}; + +class GroupMetricsImpl : public GroupMetrics { + private: + static constexpr int32_t kInvalidGroupId = -1; + int32_t group_id_; + int32_t group_size_; + std::vector<std::unique_ptr<DeviceMetrics>> device_metrics_; + std::unordered_map<RawAddress, DeviceMetrics*> opened_devices_; + ClockTimePoint beginning_timepoint_; + std::vector<int64_t> streaming_offset_nanos_; + std::vector<int64_t> streaming_duration_nanos_; + std::vector<int32_t> streaming_context_type_; + + public: + GroupMetricsImpl() : group_id_(kInvalidGroupId), group_size_(0) { + beginning_timepoint_ = std::chrono::high_resolution_clock::now(); + } + GroupMetricsImpl(int32_t group_id, int32_t group_size) + : group_id_(group_id), group_size_(group_size) { + beginning_timepoint_ = std::chrono::high_resolution_clock::now(); + } + + void AddStateChangedEvent(const RawAddress& address, + le_audio::ConnectionState state, + ConnectionStatus status) override { + auto it = opened_devices_.find(address); + if (it == opened_devices_.end()) { + device_metrics_.push_back(std::make_unique<DeviceMetrics>(address)); + it = opened_devices_.insert(std::begin(opened_devices_), + {address, device_metrics_.back().get()}); + } + it->second->AddStateChangedEvent(state, status); + if (state == le_audio::ConnectionState::DISCONNECTED || + (state == le_audio::ConnectionState::CONNECTED && + status != ConnectionStatus::SUCCESS)) { + opened_devices_.erase(it); + } + } + + void AddStreamStartedEvent( + le_audio::types::LeAudioContextType context_type) override { + int32_t atom_context_type = to_atom_context_type(context_type); + // Make sure events aligned + if (streaming_offset_nanos_.size() - streaming_duration_nanos_.size() != + 0) { + // Allow type switching + if (!streaming_context_type_.empty() && + streaming_context_type_.back() != atom_context_type) { + AddStreamEndedEvent(); + } else { + return; + } + } + streaming_offset_nanos_.push_back(get_timedelta_nanos( + std::chrono::high_resolution_clock::now(), beginning_timepoint_)); + streaming_context_type_.push_back(atom_context_type); + } + + void AddStreamEndedEvent() override { + // Make sure events aligned + if (streaming_offset_nanos_.size() - streaming_duration_nanos_.size() != + 1) { + return; + } + streaming_duration_nanos_.push_back( + get_timedelta_nanos(std::chrono::high_resolution_clock::now(), + beginning_timepoint_) - + streaming_offset_nanos_.back()); + } + + void SetGroupSize(int32_t group_size) override { group_size_ = group_size; } + + bool IsClosed() override { return opened_devices_.empty(); } + + void WriteStats() override { + int64_t connection_duration_nanos = get_timedelta_nanos( + beginning_timepoint_, std::chrono::high_resolution_clock::now()); + + int len = device_metrics_.size(); + std::vector<int64_t> device_connecting_offset_nanos(len); + std::vector<int64_t> device_connected_offset_nanos(len); + std::vector<int64_t> device_connection_duration_nanos(len); + std::vector<int32_t> device_connection_statuses(len); + std::vector<int32_t> device_disconnection_statuses(len); + std::vector<RawAddress> device_address(len); + + while (streaming_duration_nanos_.size() < streaming_offset_nanos_.size()) { + AddStreamEndedEvent(); + } + + for (int i = 0; i < len; i++) { + auto device_metric = device_metrics_[i].get(); + device_connecting_offset_nanos[i] = get_timedelta_nanos( + device_metric->connecting_timepoint_, beginning_timepoint_); + device_connected_offset_nanos[i] = get_timedelta_nanos( + device_metric->connected_timepoint_, beginning_timepoint_); + device_connection_duration_nanos[i] = + get_timedelta_nanos(device_metric->disconnected_timepoint_, + device_metric->connected_timepoint_); + device_connection_statuses[i] = device_metric->connection_status_; + device_disconnection_statuses[i] = device_metric->disconnection_status_; + device_address[i] = device_metric->address_; + } + + bluetooth::common::LogLeAudioConnectionSessionReported( + group_size_, group_id_, connection_duration_nanos, + device_connecting_offset_nanos, device_connected_offset_nanos, + device_connection_duration_nanos, device_connection_statuses, + device_disconnection_statuses, device_address, streaming_offset_nanos_, + streaming_duration_nanos_, streaming_context_type_); + } + + void Flush() { + for (auto& p : opened_devices_) { + p.second->AddStateChangedEvent( + bluetooth::le_audio::ConnectionState::DISCONNECTED, + ConnectionStatus::SUCCESS); + } + WriteStats(); + } +}; + +/* Metrics Colloctor */ + +MetricsCollector* MetricsCollector::Get() { + if (MetricsCollector::instance == nullptr) { + MetricsCollector::instance = new MetricsCollector(); + } + return MetricsCollector::instance; +} + +void MetricsCollector::OnGroupSizeUpdate(int32_t group_id, int32_t group_size) { + group_size_table_[group_id] = group_size; + auto it = opened_groups_.find(group_id); + if (it != opened_groups_.end()) { + it->second->SetGroupSize(group_size); + } +} + +void MetricsCollector::OnConnectionStateChanged( + int32_t group_id, const RawAddress& address, + bluetooth::le_audio::ConnectionState state, ConnectionStatus status) { + if (address.IsEmpty() || group_id <= 0) { + return; + } + auto it = opened_groups_.find(group_id); + if (it == opened_groups_.end()) { + it = opened_groups_.insert( + std::begin(opened_groups_), + {group_id, std::make_unique<GroupMetricsImpl>( + group_id, group_size_table_[group_id])}); + } + it->second->AddStateChangedEvent(address, state, status); + + if (it->second->IsClosed()) { + it->second->WriteStats(); + opened_groups_.erase(it); + } +} + +void MetricsCollector::OnStreamStarted( + int32_t group_id, le_audio::types::LeAudioContextType context_type) { + if (group_id <= 0) return; + auto it = opened_groups_.find(group_id); + if (it != opened_groups_.end()) { + it->second->AddStreamStartedEvent(context_type); + } +} + +void MetricsCollector::OnStreamEnded(int32_t group_id) { + if (group_id <= 0) return; + auto it = opened_groups_.find(group_id); + if (it != opened_groups_.end()) { + it->second->AddStreamEndedEvent(); + } +} + +void MetricsCollector::Flush() { + LOG(INFO) << __func__; + for (auto& p : opened_groups_) { + p.second->Flush(); + } + opened_groups_.clear(); +} + +} // namespace le_audio diff --git a/system/bta/le_audio/metrics_collector.h b/system/bta/le_audio/metrics_collector.h new file mode 100644 index 0000000000..f988364734 --- /dev/null +++ b/system/bta/le_audio/metrics_collector.h @@ -0,0 +1,136 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <hardware/bt_le_audio.h> + +#include <cstdint> +#include <memory> +#include <unordered_map> + +#include "le_audio_types.h" +#include "types/raw_address.h" + +namespace le_audio { + +enum ConnectionStatus : int32_t { + UNKNOWN = 0, + SUCCESS = 1, + FAILED = 2, +}; + +/* android.bluetooth.leaudio.ContextType */ +enum class LeAudioMetricsContextType : int32_t { + INVALID = 0, + UNSPECIFIED = 1, + COMMUNICATION = 2, + MEDIA = 3, + INSTRUCTIONAL = 4, + ATTENTION_SEEKING = 5, + IMMEDIATE_ALERT = 6, + MAN_MACHINE = 7, + EMERGENCY_ALERT = 8, + RINGTONE = 9, + TV = 10, + LIVE = 11, + GAME = 12, + RFU = 13, +}; + +class GroupMetrics { + public: + GroupMetrics() {} + + virtual ~GroupMetrics() {} + + virtual void AddStateChangedEvent(const RawAddress& address, + bluetooth::le_audio::ConnectionState state, + ConnectionStatus status) = 0; + + virtual void AddStreamStartedEvent( + le_audio::types::LeAudioContextType context_type) = 0; + + virtual void AddStreamEndedEvent() = 0; + + virtual void SetGroupSize(int32_t group_size) = 0; + + virtual bool IsClosed() = 0; + + virtual void WriteStats() = 0; + + virtual void Flush() = 0; +}; + +class MetricsCollector { + public: + static MetricsCollector* Get(); + + /** + * Update the size of given group which will be used in the + * LogMetricBluetoothLeAudioConnectionStateChanged() + * + * @param group_id ID of target group + * @param group_size Size of target group + */ + void OnGroupSizeUpdate(int32_t group_id, int32_t group_size); + + /** + * When there is a change in Bluetooth LE Audio connection state + * + * @param group_id Group ID of the associated device. + * @param address Address of the associated device. + * @param state New LE Audio connetion state. + * @param status status or reason of the state transition. Ignored at + * CONNECTING states. + */ + void OnConnectionStateChanged(int32_t group_id, const RawAddress& address, + bluetooth::le_audio::ConnectionState state, + ConnectionStatus status); + + /** + * When there is a change in LE Audio stream started + * + * @param group_id Group ID of the associated stream. + */ + void OnStreamStarted(int32_t group_id, + le_audio::types::LeAudioContextType context_type); + + /** + * When there is a change in LE Audio stream started + * + * @param group_id Group ID of the associated stream. + */ + void OnStreamEnded(int32_t group_id); + + /** + * Flush all log to statsd + * + * @param group_id Group ID of the associated stream. + */ + void Flush(); + + protected: + MetricsCollector() {} + + private: + static MetricsCollector* instance; + + std::unordered_map<int32_t, std::unique_ptr<GroupMetrics>> opened_groups_; + std::unordered_map<int32_t, int32_t> group_size_table_; +}; + +} // namespace le_audio diff --git a/system/bta/le_audio/metrics_collector_linux.cc b/system/bta/le_audio/metrics_collector_linux.cc new file mode 100644 index 0000000000..2ac63a567b --- /dev/null +++ b/system/bta/le_audio/metrics_collector_linux.cc @@ -0,0 +1,45 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "metrics_collector.h" + +namespace le_audio { + +/* Metrics Colloctor */ +MetricsCollector* MetricsCollector::instance = nullptr; + +MetricsCollector* MetricsCollector::Get() { + if (MetricsCollector::instance == nullptr) { + MetricsCollector::instance = new MetricsCollector(); + } + return MetricsCollector::instance; +} + +void MetricsCollector::OnGroupSizeUpdate(int32_t group_id, int32_t group_size) { +} + +void MetricsCollector::OnConnectionStateChanged( + int32_t group_id, const RawAddress& address, + bluetooth::le_audio::ConnectionState state, ConnectionStatus status) {} + +void MetricsCollector::OnStreamStarted( + int32_t group_id, le_audio::types::LeAudioContextType context_type) {} + +void MetricsCollector::OnStreamEnded(int32_t group_id) {} + +void MetricsCollector::Flush() {} + +} // namespace le_audio diff --git a/system/bta/le_audio/metrics_collector_test.cc b/system/bta/le_audio/metrics_collector_test.cc new file mode 100644 index 0000000000..ee295d56d7 --- /dev/null +++ b/system/bta/le_audio/metrics_collector_test.cc @@ -0,0 +1,370 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "metrics_collector.h" + +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <unistd.h> + +#include <cstdint> +#include <vector> + +#include "types/raw_address.h" + +using testing::_; +using testing::AnyNumber; +using testing::AtLeast; +using testing::AtMost; +using testing::DoAll; +using testing::Invoke; +using testing::Mock; +using testing::MockFunction; +using testing::NotNull; +using testing::Return; +using testing::SaveArg; +using testing::SetArgPointee; +using testing::Test; +using testing::WithArg; + +int log_count = 0; +int32_t last_group_size; +int32_t last_group_metric_id; +int64_t last_connection_duration_nanos; +std::vector<int64_t> last_device_connecting_offset_nanos; +std::vector<int64_t> last_device_connected_offset_nanos; +std::vector<int64_t> last_device_connection_duration_nanos; +std::vector<int32_t> last_device_connection_status; +std::vector<int32_t> last_device_disconnection_status; +std::vector<RawAddress> last_device_address; +std::vector<int64_t> last_streaming_offset_nanos; +std::vector<int64_t> last_streaming_duration_nanos; +std::vector<int32_t> last_streaming_context_type; + +namespace bluetooth { +namespace common { + +void LogLeAudioConnectionSessionReported( + int32_t group_size, int32_t group_metric_id, + int64_t connection_duration_nanos, + std::vector<int64_t>& device_connecting_offset_nanos, + std::vector<int64_t>& device_connected_offset_nanos, + std::vector<int64_t>& device_connection_duration_nanos, + std::vector<int32_t>& device_connection_status, + std::vector<int32_t>& device_disconnection_status, + std::vector<RawAddress>& device_address, + std::vector<int64_t>& streaming_offset_nanos, + std::vector<int64_t>& streaming_duration_nanos, + std::vector<int32_t>& streaming_context_type) { + log_count++; + last_group_size = group_size; + last_group_metric_id = group_metric_id; + last_connection_duration_nanos = connection_duration_nanos; + last_device_connecting_offset_nanos = device_connecting_offset_nanos; + last_device_connected_offset_nanos = device_connected_offset_nanos; + last_device_connection_duration_nanos = device_connection_duration_nanos; + last_device_connection_status = device_connection_status; + last_device_disconnection_status = device_disconnection_status; + last_device_address = device_address; + last_streaming_offset_nanos = streaming_offset_nanos; + last_streaming_duration_nanos = streaming_duration_nanos; + last_streaming_context_type = streaming_context_type; +} + +} // namespace common +} // namespace bluetooth + +namespace le_audio { + +const int32_t group_id1 = 1; +const RawAddress device1 = RawAddress({0x11, 0x22, 0x33, 0x44, 0x55, 0x66}); + +const int32_t group_id2 = 2; +const RawAddress device2 = RawAddress({0x11, 0x22, 0x33, 0x44, 0x55, 0x67}); +const RawAddress device3 = RawAddress({0x11, 0x22, 0x33, 0x44, 0x55, 0x68}); + +class MockMetricsCollector : public MetricsCollector { + public: + MockMetricsCollector() {} +}; + +class MetricsCollectorTest : public Test { + protected: + std::unique_ptr<MetricsCollector> collector; + + void SetUp() override { + collector = std::make_unique<MockMetricsCollector>(); + + log_count = 0; + last_group_size = 0; + last_group_metric_id = 0; + last_connection_duration_nanos = 0; + last_device_connecting_offset_nanos = {}; + last_device_connected_offset_nanos = {}; + last_device_connection_duration_nanos = {}; + last_device_connection_status = {}; + last_device_disconnection_status = {}; + last_device_address = {}; + last_streaming_offset_nanos = {}; + last_streaming_duration_nanos = {}; + last_streaming_context_type = {}; + } + + void TearDown() override { collector = nullptr; } +}; + +TEST_F(MetricsCollectorTest, Initialize) { ASSERT_EQ(log_count, 0); } + +TEST_F(MetricsCollectorTest, ConnectionFailed) { + collector->OnConnectionStateChanged( + group_id1, device1, bluetooth::le_audio::ConnectionState::CONNECTING, + ConnectionStatus::UNKNOWN); + collector->OnConnectionStateChanged( + group_id1, device1, bluetooth::le_audio::ConnectionState::CONNECTED, + ConnectionStatus::FAILED); + + ASSERT_EQ(log_count, 1); + ASSERT_EQ(last_group_metric_id, group_id1); + ASSERT_EQ(last_device_connecting_offset_nanos.size(), 1UL); + ASSERT_EQ(last_device_connection_status.size(), 1UL); + ASSERT_EQ(last_device_connection_status.back(), ConnectionStatus::FAILED); +} + +TEST_F(MetricsCollectorTest, ConnectingConnectedDisconnected) { + collector->OnConnectionStateChanged( + group_id1, device1, bluetooth::le_audio::ConnectionState::CONNECTING, + ConnectionStatus::UNKNOWN); + collector->OnConnectionStateChanged( + group_id1, device1, bluetooth::le_audio::ConnectionState::CONNECTED, + ConnectionStatus::SUCCESS); + collector->OnConnectionStateChanged( + group_id1, device1, bluetooth::le_audio::ConnectionState::DISCONNECTED, + ConnectionStatus::SUCCESS); + + ASSERT_EQ(log_count, 1); + ASSERT_EQ(last_group_metric_id, group_id1); + ASSERT_EQ(last_device_connecting_offset_nanos.size(), 1UL); + ASSERT_EQ(last_device_connection_status.size(), 1UL); + ASSERT_EQ(last_device_disconnection_status.size(), 1UL); + ASSERT_EQ(last_device_connecting_offset_nanos.size(), 1UL); + ASSERT_EQ(last_device_connected_offset_nanos.size(), 1UL); + ASSERT_EQ(last_device_connection_duration_nanos.size(), 1UL); + ASSERT_EQ(last_device_connection_status.back(), ConnectionStatus::SUCCESS); + ASSERT_EQ(last_device_disconnection_status.back(), ConnectionStatus::SUCCESS); +} + +TEST_F(MetricsCollectorTest, SingleDeviceTwoConnections) { + collector->OnConnectionStateChanged( + group_id1, device1, bluetooth::le_audio::ConnectionState::CONNECTING, + ConnectionStatus::UNKNOWN); + collector->OnConnectionStateChanged( + group_id1, device1, bluetooth::le_audio::ConnectionState::CONNECTED, + ConnectionStatus::SUCCESS); + collector->OnConnectionStateChanged( + group_id1, device1, bluetooth::le_audio::ConnectionState::DISCONNECTED, + ConnectionStatus::SUCCESS); + + ASSERT_EQ(log_count, 1); + ASSERT_EQ(last_group_metric_id, group_id1); + ASSERT_EQ(last_device_connecting_offset_nanos.size(), 1UL); + ASSERT_EQ(last_device_connection_status.size(), 1UL); + ASSERT_EQ(last_device_disconnection_status.size(), 1UL); + ASSERT_EQ(last_device_connecting_offset_nanos.size(), 1UL); + ASSERT_EQ(last_device_connected_offset_nanos.size(), 1UL); + ASSERT_EQ(last_device_connection_duration_nanos.size(), 1UL); + ASSERT_EQ(last_device_connection_status.back(), ConnectionStatus::SUCCESS); + ASSERT_EQ(last_device_disconnection_status.back(), ConnectionStatus::SUCCESS); + + collector->OnConnectionStateChanged( + group_id1, device1, bluetooth::le_audio::ConnectionState::CONNECTING, + ConnectionStatus::UNKNOWN); + collector->OnConnectionStateChanged( + group_id1, device1, bluetooth::le_audio::ConnectionState::CONNECTED, + ConnectionStatus::SUCCESS); + collector->OnConnectionStateChanged( + group_id1, device1, bluetooth::le_audio::ConnectionState::DISCONNECTED, + ConnectionStatus::SUCCESS); + + ASSERT_EQ(log_count, 2); + ASSERT_EQ(last_group_metric_id, group_id1); + ASSERT_EQ(last_device_connecting_offset_nanos.size(), 1UL); + ASSERT_EQ(last_device_connection_status.size(), 1UL); + ASSERT_EQ(last_device_disconnection_status.size(), 1UL); + ASSERT_EQ(last_device_connecting_offset_nanos.size(), 1UL); + ASSERT_EQ(last_device_connected_offset_nanos.size(), 1UL); + ASSERT_EQ(last_device_connection_duration_nanos.size(), 1UL); + ASSERT_EQ(last_device_connection_status.back(), ConnectionStatus::SUCCESS); + ASSERT_EQ(last_device_disconnection_status.back(), ConnectionStatus::SUCCESS); +} + +TEST_F(MetricsCollectorTest, StereoGroupBasicTest) { + collector->OnConnectionStateChanged( + group_id2, device2, bluetooth::le_audio::ConnectionState::CONNECTING, + ConnectionStatus::UNKNOWN); + collector->OnConnectionStateChanged( + group_id2, device2, bluetooth::le_audio::ConnectionState::CONNECTED, + ConnectionStatus::SUCCESS); + collector->OnConnectionStateChanged( + group_id2, device3, bluetooth::le_audio::ConnectionState::CONNECTED, + ConnectionStatus::SUCCESS); + collector->OnConnectionStateChanged( + group_id2, device3, bluetooth::le_audio::ConnectionState::DISCONNECTED, + ConnectionStatus::SUCCESS); + collector->OnConnectionStateChanged( + group_id2, device2, bluetooth::le_audio::ConnectionState::DISCONNECTED, + ConnectionStatus::SUCCESS); + + ASSERT_EQ(log_count, 1); + ASSERT_EQ(last_group_metric_id, group_id2); + ASSERT_EQ(last_device_connecting_offset_nanos.size(), 2UL); + ASSERT_EQ(last_device_connection_status.size(), 2UL); + ASSERT_EQ(last_device_disconnection_status.size(), 2UL); + ASSERT_EQ(last_device_connecting_offset_nanos.size(), 2UL); + ASSERT_EQ(last_device_connected_offset_nanos.size(), 2UL); + ASSERT_EQ(last_device_connection_duration_nanos.size(), 2UL); +} + +TEST_F(MetricsCollectorTest, StereoGroupMultiReconnections) { + collector->OnConnectionStateChanged( + group_id2, device2, bluetooth::le_audio::ConnectionState::CONNECTING, + ConnectionStatus::UNKNOWN); + collector->OnConnectionStateChanged( + group_id2, device2, bluetooth::le_audio::ConnectionState::CONNECTED, + ConnectionStatus::SUCCESS); + collector->OnConnectionStateChanged( + group_id2, device3, bluetooth::le_audio::ConnectionState::CONNECTED, + ConnectionStatus::SUCCESS); + collector->OnConnectionStateChanged( + group_id2, device3, bluetooth::le_audio::ConnectionState::DISCONNECTED, + ConnectionStatus::SUCCESS); + collector->OnConnectionStateChanged( + group_id2, device3, bluetooth::le_audio::ConnectionState::CONNECTED, + ConnectionStatus::SUCCESS); + collector->OnConnectionStateChanged( + group_id2, device3, bluetooth::le_audio::ConnectionState::DISCONNECTED, + ConnectionStatus::SUCCESS); + collector->OnConnectionStateChanged( + group_id2, device2, bluetooth::le_audio::ConnectionState::DISCONNECTED, + ConnectionStatus::SUCCESS); + + ASSERT_EQ(log_count, 1); + ASSERT_EQ(last_group_metric_id, group_id2); + ASSERT_EQ(last_device_connecting_offset_nanos.size(), 3UL); + ASSERT_EQ(last_device_connection_status.size(), 3UL); + ASSERT_EQ(last_device_disconnection_status.size(), 3UL); + ASSERT_EQ(last_device_connecting_offset_nanos.size(), 3UL); + ASSERT_EQ(last_device_connected_offset_nanos.size(), 3UL); + ASSERT_EQ(last_device_connection_duration_nanos.size(), 3UL); +} + +TEST_F(MetricsCollectorTest, MixGroups) { + collector->OnConnectionStateChanged( + group_id1, device1, bluetooth::le_audio::ConnectionState::CONNECTING, + ConnectionStatus::UNKNOWN); + collector->OnConnectionStateChanged( + group_id1, device1, bluetooth::le_audio::ConnectionState::CONNECTED, + ConnectionStatus::SUCCESS); + collector->OnConnectionStateChanged( + group_id2, device2, bluetooth::le_audio::ConnectionState::CONNECTING, + ConnectionStatus::UNKNOWN); + collector->OnConnectionStateChanged( + group_id2, device2, bluetooth::le_audio::ConnectionState::CONNECTED, + ConnectionStatus::SUCCESS); + collector->OnConnectionStateChanged( + group_id2, device3, bluetooth::le_audio::ConnectionState::CONNECTED, + ConnectionStatus::SUCCESS); + collector->OnConnectionStateChanged( + group_id2, device3, bluetooth::le_audio::ConnectionState::DISCONNECTED, + ConnectionStatus::SUCCESS); + collector->OnConnectionStateChanged( + group_id2, device2, bluetooth::le_audio::ConnectionState::DISCONNECTED, + ConnectionStatus::SUCCESS); + + ASSERT_EQ(log_count, 1); + ASSERT_EQ(last_group_metric_id, group_id2); + ASSERT_EQ(last_device_connecting_offset_nanos.size(), 2UL); + ASSERT_EQ(last_device_connection_status.size(), 2UL); + ASSERT_EQ(last_device_disconnection_status.size(), 2UL); + ASSERT_EQ(last_device_connecting_offset_nanos.size(), 2UL); + ASSERT_EQ(last_device_connected_offset_nanos.size(), 2UL); + ASSERT_EQ(last_device_connection_duration_nanos.size(), 2UL); + + collector->OnConnectionStateChanged( + group_id1, device1, bluetooth::le_audio::ConnectionState::DISCONNECTED, + ConnectionStatus::SUCCESS); + + ASSERT_EQ(log_count, 2); + ASSERT_EQ(last_group_metric_id, group_id1); + ASSERT_EQ(last_device_connecting_offset_nanos.size(), 1UL); + ASSERT_EQ(last_device_connection_status.size(), 1UL); + ASSERT_EQ(last_device_disconnection_status.size(), 1UL); + ASSERT_EQ(last_device_connecting_offset_nanos.size(), 1UL); + ASSERT_EQ(last_device_connected_offset_nanos.size(), 1UL); + ASSERT_EQ(last_device_connection_duration_nanos.size(), 1UL); +} + +TEST_F(MetricsCollectorTest, GroupSizeUpdated) { + collector->OnGroupSizeUpdate(group_id2, 1); + collector->OnGroupSizeUpdate(group_id1, 2); + collector->OnConnectionStateChanged( + group_id1, device1, bluetooth::le_audio::ConnectionState::CONNECTING, + ConnectionStatus::UNKNOWN); + collector->OnConnectionStateChanged( + group_id1, device1, bluetooth::le_audio::ConnectionState::CONNECTED, + ConnectionStatus::SUCCESS); + collector->OnConnectionStateChanged( + group_id1, device1, bluetooth::le_audio::ConnectionState::DISCONNECTED, + ConnectionStatus::SUCCESS); + + ASSERT_EQ(log_count, 1); + ASSERT_EQ(last_group_metric_id, group_id1); + ASSERT_EQ(last_group_size, 2); +} + +TEST_F(MetricsCollectorTest, StreamingSessions) { + collector->OnConnectionStateChanged( + group_id1, device1, bluetooth::le_audio::ConnectionState::CONNECTING, + ConnectionStatus::UNKNOWN); + collector->OnConnectionStateChanged( + group_id1, device1, bluetooth::le_audio::ConnectionState::CONNECTED, + ConnectionStatus::SUCCESS); + collector->OnStreamStarted(group_id1, + le_audio::types::LeAudioContextType::MEDIA); + collector->OnStreamEnded(group_id1); + collector->OnStreamStarted( + group_id1, le_audio::types::LeAudioContextType::CONVERSATIONAL); + collector->OnStreamEnded(group_id1); + collector->OnConnectionStateChanged( + group_id1, device1, bluetooth::le_audio::ConnectionState::DISCONNECTED, + ConnectionStatus::SUCCESS); + + ASSERT_EQ(log_count, 1); + ASSERT_EQ(last_group_metric_id, group_id1); + ASSERT_EQ(last_streaming_offset_nanos.size(), 2UL); + ASSERT_EQ(last_streaming_duration_nanos.size(), 2UL); + ASSERT_EQ(last_streaming_context_type.size(), 2UL); + + ASSERT_GT(last_streaming_offset_nanos[0], 0L); + ASSERT_GT(last_streaming_offset_nanos[1], 0L); + ASSERT_GT(last_streaming_duration_nanos[0], 0L); + ASSERT_GT(last_streaming_duration_nanos[1], 0L); + ASSERT_EQ(last_streaming_context_type[0], + static_cast<int32_t>(LeAudioMetricsContextType::MEDIA)); + ASSERT_EQ(last_streaming_context_type[1], + static_cast<int32_t>(LeAudioMetricsContextType::COMMUNICATION)); +} + +} // namespace le_audio
\ No newline at end of file diff --git a/system/common/metrics.cc b/system/common/metrics.cc index 47870e0402..be1d1b3bac 100644 --- a/system/common/metrics.cc +++ b/system/common/metrics.cc @@ -908,6 +908,55 @@ void LogBluetoothHalCrashReason(const RawAddress& address, uint32_t error_code, } } +void LogLeAudioConnectionSessionReported( + int32_t group_size, int32_t group_metric_id, + int64_t connection_duration_nanos, + std::vector<int64_t>& device_connecting_offset_nanos, + std::vector<int64_t>& device_connected_offset_nanos, + std::vector<int64_t>& device_connection_duration_nanos, + std::vector<int32_t>& device_connection_status, + std::vector<int32_t>& device_disconnection_status, + std::vector<RawAddress>& device_address, + std::vector<int64_t>& streaming_offset_nanos, + std::vector<int64_t>& streaming_duration_nanos, + std::vector<int32_t>& streaming_context_type) { + std::vector<int32_t> device_metric_id(device_address.size()); + for (uint64_t i = 0; i < device_address.size(); i++) { + if (!device_address[i].IsEmpty()) { + device_metric_id[i] = + MetricIdAllocator::GetInstance().AllocateId(device_address[i]); + } else { + device_metric_id[i] = 0; + } + } + int ret = stats_write( + LE_AUDIO_CONNECTION_SESSION_REPORTED, group_size, group_metric_id, + connection_duration_nanos, device_connecting_offset_nanos, + device_connected_offset_nanos, device_connection_duration_nanos, + device_connection_status, device_disconnection_status, device_metric_id, + streaming_offset_nanos, streaming_duration_nanos, streaming_context_type); + if (ret < 0) { + LOG(WARNING) << __func__ << ": failed for group " << group_metric_id + << "device_connecting_offset_nanos[" + << device_connecting_offset_nanos.size() << "], " + << "device_connected_offset_nanos[" + << device_connected_offset_nanos.size() << "], " + << "device_connection_duration_nanos[" + << device_connection_duration_nanos.size() << "], " + << "device_connection_status[" + << device_connection_status.size() << "], " + << "device_disconnection_status[" + << device_disconnection_status.size() << "], " + << "device_metric_id[" << device_metric_id.size() << "], " + << "streaming_offset_nanos[" << streaming_offset_nanos.size() + << "], " + << "streaming_duration_nanos[" + << streaming_duration_nanos.size() << "], " + << "streaming_context_type[" << streaming_context_type.size() + << "]"; + } +} + } // namespace common } // namespace bluetooth diff --git a/system/common/metrics.h b/system/common/metrics.h index c224734b07..53cad5c3d0 100644 --- a/system/common/metrics.h +++ b/system/common/metrics.h @@ -25,6 +25,7 @@ #include <memory> #include <string> +#include <vector> #include "types/raw_address.h" @@ -502,6 +503,20 @@ void LogManufacturerInfo(const RawAddress& address, */ void LogBluetoothHalCrashReason(const RawAddress& address, uint32_t error_code, uint32_t vendor_error_code); + +void LogLeAudioConnectionSessionReported( + int32_t group_size, int32_t group_metric_id, + int64_t connection_duration_nanos, + std::vector<int64_t>& device_connecting_offset_nanos, + std::vector<int64_t>& device_connected_offset_nanos, + std::vector<int64_t>& device_connection_duration_nanos, + std::vector<int32_t>& device_connection_status, + std::vector<int32_t>& device_disconnection_status, + std::vector<RawAddress>& device_address, + std::vector<int64_t>& streaming_offset_nanos, + std::vector<int64_t>& streaming_duration_nanos, + std::vector<int32_t>& streaming_context_type); + } // namespace common } // namespace bluetooth diff --git a/system/common/metrics_linux.cc b/system/common/metrics_linux.cc index 46cab49f2a..fa8c6a0497 100644 --- a/system/common/metrics_linux.cc +++ b/system/common/metrics_linux.cc @@ -154,6 +154,19 @@ void LogSmpPairingEvent(const RawAddress& address, uint8_t smp_cmd, android::bluetooth::DirectionEnum direction, uint8_t smp_fail_reason) {} +void LogLeAudioConnectionSessionReported( + int32_t group_size, int32_t group_metric_id, + int64_t connection_duration_nanos, + std::vector<int64_t>& device_connecting_offset_nanos, + std::vector<int64_t>& device_connected_offset_nanos, + std::vector<int64_t>& device_connection_duration_nanos, + std::vector<int32_t>& device_connection_status, + std::vector<int32_t>& device_disconnection_status, + std::vector<RawAddress>& device_address, + std::vector<int64_t>& streaming_offset_nanos, + std::vector<int64_t>& streaming_duration_nanos, + std::vector<int32_t>& streaming_context_type) {} + } // namespace common } // namespace bluetooth diff --git a/system/test/mock/mock_common_metrics.cc b/system/test/mock/mock_common_metrics.cc index ce8a7fb686..766fec2747 100644 --- a/system/test/mock/mock_common_metrics.cc +++ b/system/test/mock/mock_common_metrics.cc @@ -204,5 +204,20 @@ void LogSocketConnectionState( mock_function_count_map[__func__]++; } +void LogLeAudioConnectionSessionReported( + int32_t group_size, int32_t group_metric_id, + int64_t connection_duration_nanos, + std::vector<int64_t>& device_connecting_offset_nanos, + std::vector<int64_t>& device_connected_offset_nanos, + std::vector<int64_t>& device_connection_duration_nanos, + std::vector<int32_t>& device_connection_status, + std::vector<int32_t>& device_disconnection_status, + std::vector<RawAddress>& device_address, + std::vector<int64_t>& streaming_offset_nanos, + std::vector<int64_t>& streaming_duration_nanos, + std::vector<int32_t>& streaming_context_type) { + mock_function_count_map[__func__]++; +} + } // namespace common } // namespace bluetooth |