blob: 027254b22c1c621d404e64da7ac686c1ed5923b7 [file]
/*
* Copyright 2023 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 "le_audio_health_status.h"
#include <bluetooth/log.h>
#include <vector>
#include "bta/include/bta_groups.h"
#include "common/strings.h"
#include "main/shim/metrics_api.h"
#include "os/log.h"
#include "osi/include/properties.h"
using bluetooth::common::ToString;
using bluetooth::groups::kGroupUnknown;
using bluetooth::le_audio::LeAudioDevice;
using bluetooth::le_audio::LeAudioHealthStatus;
using bluetooth::le_audio::LeAudioRecommendationActionCb;
namespace bluetooth::le_audio {
class LeAudioHealthStatusImpl;
LeAudioHealthStatusImpl* instance;
class LeAudioHealthStatusImpl : public LeAudioHealthStatus {
public:
LeAudioHealthStatusImpl(void) { log::debug("Initiated"); }
~LeAudioHealthStatusImpl(void) { clear_module(); }
void RegisterCallback(LeAudioRecommendationActionCb cb) override {
register_callback(std::move(cb));
}
void RemoveStatistics(const RawAddress& address, int group_id) override {
log::debug("{}, group_id: {}", address, group_id);
remove_device(address);
remove_group(group_id);
}
void AddStatisticForDevice(const LeAudioDevice* device,
LeAudioHealthDeviceStatType type) override {
if (device == nullptr) {
log::error("device is null");
return;
}
const RawAddress& address = device->address_;
log::debug("{}, {}", address, ToString(type));
auto dev = find_device(address);
if (dev == nullptr) {
add_device(address);
dev = find_device(address);
if (dev == nullptr) {
log::error("Could not add device {}", address);
return;
}
}
// log counter metrics
log_counter_metrics_for_device(type, device->allowlist_flag_);
LeAudioHealthBasedAction action;
switch (type) {
case LeAudioHealthDeviceStatType::VALID_DB:
dev->is_valid_service_ = true;
action = LeAudioHealthBasedAction::NONE;
break;
case LeAudioHealthDeviceStatType::INVALID_DB:
dev->is_valid_service_ = false;
action = LeAudioHealthBasedAction::DISABLE;
break;
case LeAudioHealthDeviceStatType::INVALID_CSIS:
dev->is_valid_group_member_ = false;
action = LeAudioHealthBasedAction::DISABLE;
break;
case LeAudioHealthDeviceStatType::VALID_CSIS:
dev->is_valid_group_member_ = true;
action = LeAudioHealthBasedAction::NONE;
break;
}
if (dev->latest_recommendation_ != action) {
dev->latest_recommendation_ = action;
send_recommendation_for_device(address, action);
return;
}
}
void AddStatisticForGroup(const LeAudioDeviceGroup* device_group,
LeAudioHealthGroupStatType type) override {
if (device_group == nullptr) {
log::error("device_group is null");
return;
}
int group_id = device_group->group_id_;
log::debug("group_id: {}, {}", group_id, ToString(type));
auto group = find_group(group_id);
if (group == nullptr) {
add_group(group_id);
group = find_group(group_id);
if (group == nullptr) {
log::error("Could not add group {}", group_id);
return;
}
}
LeAudioDevice* device = device_group->GetFirstDevice();
if (device == nullptr) {
log::error("Front device is null. Number of devices: {}",
device_group->Size());
return;
}
// log counter metrics
log_counter_metrics_for_group(type, device->allowlist_flag_);
switch (type) {
case LeAudioHealthGroupStatType::STREAM_CREATE_SUCCESS:
group->stream_success_cnt_++;
if (group->latest_recommendation_ == LeAudioHealthBasedAction::NONE) {
return;
}
break;
case LeAudioHealthGroupStatType::STREAM_CREATE_CIS_FAILED:
group->stream_cis_failures_cnt_++;
group->stream_failures_cnt_++;
break;
case LeAudioHealthGroupStatType::STREAM_CREATE_SIGNALING_FAILED:
group->stream_signaling_failures_cnt_++;
group->stream_failures_cnt_++;
break;
case LeAudioHealthGroupStatType::STREAM_CONTEXT_NOT_AVAILABLE:
group->stream_context_not_avail_cnt_++;
break;
}
LeAudioHealthBasedAction action = LeAudioHealthBasedAction::NONE;
if (group->stream_success_cnt_ == 0) {
/* Never succeed in stream creation */
if ((group->stream_failures_cnt_ >=
MAX_ALLOWED_FAILURES_IN_A_ROW_WITHOUT_SUCCESS)) {
action = LeAudioHealthBasedAction::DISABLE;
} else if (group->stream_context_not_avail_cnt_ >=
MAX_ALLOWED_FAILURES_IN_A_ROW_WITHOUT_SUCCESS) {
action = LeAudioHealthBasedAction::INACTIVATE_GROUP;
group->stream_context_not_avail_cnt_ = 0;
}
} else {
/* Had some success before */
if ((100 * group->stream_failures_cnt_ / group->stream_success_cnt_) >=
THRESHOLD_FOR_DISABLE_CONSIDERATION) {
action = LeAudioHealthBasedAction::CONSIDER_DISABLING;
} else if (group->stream_context_not_avail_cnt_ >=
MAX_ALLOWED_FAILURES_IN_A_ROW_WITHOUT_SUCCESS) {
action = LeAudioHealthBasedAction::INACTIVATE_GROUP;
group->stream_context_not_avail_cnt_ = 0;
}
}
if (group->latest_recommendation_ != action) {
group->latest_recommendation_ = action;
send_recommendation_for_group(group_id, action);
}
}
void Dump(int fd) {
dprintf(fd, " LeAudioHealthStats: \n groups:");
for (const auto& g : group_stats_) {
dumpsys_group(fd, g);
}
dprintf(fd, "\n devices: ");
for (const auto& dev : devices_stats_) {
dumpsys_dev(fd, dev);
}
dprintf(fd, "\n");
}
private:
static constexpr int MAX_ALLOWED_FAILURES_IN_A_ROW_WITHOUT_SUCCESS = 3;
static constexpr int THRESHOLD_FOR_DISABLE_CONSIDERATION = 70;
std::vector<LeAudioRecommendationActionCb> callbacks_;
std::vector<device_stats> devices_stats_;
std::vector<group_stats> group_stats_;
void dumpsys_group(int fd, const group_stats& group) {
std::stringstream stream;
stream << "\n group_id: " << group.group_id_ << ": "
<< group.latest_recommendation_
<< ", success: " << group.stream_success_cnt_
<< ", fail total: " << group.stream_failures_cnt_
<< ", fail cis: " << group.stream_cis_failures_cnt_
<< ", fail signaling: " << group.stream_signaling_failures_cnt_
<< ", context not avail: " << group.stream_context_not_avail_cnt_;
dprintf(fd, "%s", stream.str().c_str());
}
void dumpsys_dev(int fd, const device_stats& dev) {
std::stringstream stream;
stream << "\n " << ADDRESS_TO_LOGGABLE_STR(dev.address_) << ": "
<< dev.latest_recommendation_
<< (dev.is_valid_service_ ? " service: OK" : " service : NOK")
<< (dev.is_valid_group_member_ ? " csis: OK" : " csis : NOK");
dprintf(fd, "%s", stream.str().c_str());
}
void clear_module(void) {
devices_stats_.clear();
group_stats_.clear();
callbacks_.clear();
}
void send_recommendation_for_device(const RawAddress& address,
LeAudioHealthBasedAction recommendation) {
log::debug("{}, {}", address, ToString(recommendation));
/* Notify new user about known groups */
for (auto& cb : callbacks_) {
cb.Run(address, kGroupUnknown, recommendation);
}
}
void send_recommendation_for_group(
int group_id, const LeAudioHealthBasedAction recommendation) {
log::debug("group_id: {}, {}", group_id, ToString(recommendation));
/* Notify new user about known groups */
for (auto& cb : callbacks_) {
cb.Run(RawAddress::kEmpty, group_id, recommendation);
}
}
void add_device(const RawAddress& address) {
devices_stats_.emplace_back(device_stats(address));
}
void add_group(int group_id) {
group_stats_.emplace_back(group_stats(group_id));
}
void remove_group(int group_id) {
if (group_id == kGroupUnknown) {
return;
}
auto iter = std::find_if(
group_stats_.begin(), group_stats_.end(),
[group_id](const auto& g) { return g.group_id_ == group_id; });
if (iter != group_stats_.end()) {
group_stats_.erase(iter);
}
}
void remove_device(const RawAddress& address) {
auto iter = std::find_if(
devices_stats_.begin(), devices_stats_.end(),
[address](const auto& d) { return d.address_ == address; });
if (iter != devices_stats_.end()) {
devices_stats_.erase(iter);
}
}
void register_callback(LeAudioRecommendationActionCb cb) {
callbacks_.push_back(std::move(cb));
}
device_stats* find_device(const RawAddress& address) {
auto iter = std::find_if(
devices_stats_.begin(), devices_stats_.end(),
[address](const auto& d) { return d.address_ == address; });
if (iter == devices_stats_.end()) return nullptr;
return &(*iter);
}
group_stats* find_group(int group_id) {
auto iter = std::find_if(
group_stats_.begin(), group_stats_.end(),
[group_id](const auto& g) { return g.group_id_ == group_id; });
if (iter == group_stats_.end()) return nullptr;
return &(*iter);
}
void log_counter_metrics_for_device(LeAudioHealthDeviceStatType type,
bool in_allowlist) {
log::debug("in_allowlist: {}, type: {}", in_allowlist, ToString(type));
android::bluetooth::CodePathCounterKeyEnum key;
if (in_allowlist) {
switch (type) {
case LeAudioHealthDeviceStatType::VALID_DB:
case LeAudioHealthDeviceStatType::VALID_CSIS:
key = android::bluetooth::CodePathCounterKeyEnum::
LE_AUDIO_ALLOWLIST_DEVICE_HEALTH_STATUS_GOOD;
break;
case LeAudioHealthDeviceStatType::INVALID_DB:
key = android::bluetooth::CodePathCounterKeyEnum::
LE_AUDIO_ALLOWLIST_DEVICE_HEALTH_STATUS_BAD_INVALID_DB;
break;
case LeAudioHealthDeviceStatType::INVALID_CSIS:
key = android::bluetooth::CodePathCounterKeyEnum::
LE_AUDIO_ALLOWLIST_DEVICE_HEALTH_STATUS_BAD_INVALID_CSIS;
break;
default:
log::error("Metric unhandled {}", type);
return;
}
} else {
switch (type) {
case LeAudioHealthDeviceStatType::VALID_DB:
case LeAudioHealthDeviceStatType::VALID_CSIS:
key = android::bluetooth::CodePathCounterKeyEnum::
LE_AUDIO_NONALLOWLIST_DEVICE_HEALTH_STATUS_GOOD;
break;
case LeAudioHealthDeviceStatType::INVALID_DB:
key = android::bluetooth::CodePathCounterKeyEnum::
LE_AUDIO_NONALLOWLIST_DEVICE_HEALTH_STATUS_BAD_INVALID_DB;
break;
case LeAudioHealthDeviceStatType::INVALID_CSIS:
key = android::bluetooth::CodePathCounterKeyEnum::
LE_AUDIO_NONALLOWLIST_DEVICE_HEALTH_STATUS_BAD_INVALID_CSIS;
break;
default:
log::error("Metric unhandled {}", type);
return;
}
}
bluetooth::shim::CountCounterMetrics(key, 1);
}
void log_counter_metrics_for_group(LeAudioHealthGroupStatType type,
bool in_allowlist) {
log::debug("in_allowlist: {}, type: {}", in_allowlist, ToString(type));
android::bluetooth::CodePathCounterKeyEnum key;
if (in_allowlist) {
switch (type) {
case LeAudioHealthGroupStatType::STREAM_CREATE_SUCCESS:
key = android::bluetooth::CodePathCounterKeyEnum::
LE_AUDIO_ALLOWLIST_GROUP_HEALTH_STATUS_GOOD;
break;
case LeAudioHealthGroupStatType::STREAM_CREATE_CIS_FAILED:
key = android::bluetooth::CodePathCounterKeyEnum::
LE_AUDIO_ALLOWLIST_GROUP_HEALTH_STATUS_BAD_ONCE_CIS_FAILED;
break;
case LeAudioHealthGroupStatType::STREAM_CREATE_SIGNALING_FAILED:
key = android::bluetooth::CodePathCounterKeyEnum::
LE_AUDIO_ALLOWLIST_GROUP_HEALTH_STATUS_BAD_ONCE_SIGNALING_FAILED;
break;
default:
log::error("Metric unhandled {}", type);
return;
}
} else {
switch (type) {
case LeAudioHealthGroupStatType::STREAM_CREATE_SUCCESS:
key = android::bluetooth::CodePathCounterKeyEnum::
LE_AUDIO_NONALLOWLIST_GROUP_HEALTH_STATUS_GOOD;
break;
case LeAudioHealthGroupStatType::STREAM_CREATE_CIS_FAILED:
key = android::bluetooth::CodePathCounterKeyEnum::
LE_AUDIO_NONALLOWLIST_GROUP_HEALTH_STATUS_BAD_ONCE_CIS_FAILED;
break;
case LeAudioHealthGroupStatType::STREAM_CREATE_SIGNALING_FAILED:
key = android::bluetooth::CodePathCounterKeyEnum::
LE_AUDIO_NONALLOWLIST_GROUP_HEALTH_STATUS_BAD_ONCE_SIGNALING_FAILED;
break;
default:
log::error("Metric unhandled {}", type);
return;
}
}
bluetooth::shim::CountCounterMetrics(key, 1);
}
};
} // namespace bluetooth::le_audio
LeAudioHealthStatus* LeAudioHealthStatus::Get(void) {
if (instance) {
return instance;
}
instance = new LeAudioHealthStatusImpl();
return instance;
}
void LeAudioHealthStatus::DebugDump(int fd) {
if (instance) {
instance->Dump(fd);
}
}
void LeAudioHealthStatus::Cleanup(void) {
if (!instance) {
return;
}
auto ptr = instance;
instance = nullptr;
delete ptr;
}