Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d942f85e authored by Josh Wu's avatar Josh Wu
Browse files

Add LeAudioConnectionSession

* Add MetricsCollector for LeAudio
* Implement LogLeAudioConnectionSessionReported logging API
* Add MetricsCollectorTest under bluetooth_le_audio_test

Test: atest BluetoothInstrumentationTests
Tag: #feature
Bug: 207811438
Change-Id: Iaa50e0a7986bae8919cd2e7d280a5303dbcc6d1f
Merged-In: Iaa50e0a7986bae8919cd2e7d280a5303dbcc6d1f
parent 8744afea
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -105,6 +105,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",
@@ -564,6 +565,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",
@@ -628,6 +630,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",
+30 −1
Original line number Diff line number Diff line
@@ -40,6 +40,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"
@@ -914,6 +915,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);
@@ -1308,6 +1313,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;
    }

@@ -1354,6 +1362,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) {
@@ -1409,6 +1420,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;
    }
@@ -1457,6 +1471,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_);
@@ -2032,6 +2050,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) {
@@ -2888,8 +2909,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_
@@ -2987,6 +3010,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
@@ -3558,6 +3583,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;
@@ -3688,6 +3715,7 @@ class LeAudioClientImpl : public LeAudioClient {
      leAudioClientAudioSink->Release(audio_sink_instance_);
      audio_sink_instance_ = nullptr;
    }
    le_audio::MetricsCollector::Get()->OnStreamEnded(active_group_id_);
  }
};

@@ -3977,6 +4005,7 @@ void LeAudioClient::Cleanup(base::Callback<void()> cleanupCb) {
  ContentControlIdKeeper::GetInstance()->Stop();
  LeAudioGroupStateMachine::Cleanup();
  IsoManager::GetInstance()->Stop();
  le_audio::MetricsCollector::Get()->Flush();
}

void LeAudioClient::InitializeAudioClients(
+3 −0
Original line number Diff line number Diff line
@@ -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; }
+294 −0
Original line number Diff line number Diff line
/*
 * 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
+136 −0
Original line number Diff line number Diff line
/*
 * 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
Loading