1 /*
2 * Copyright 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at:
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "le_audio_health_status.h"
18
19 #include <bluetooth/log.h>
20 #include <frameworks/proto_logging/stats/enums/bluetooth/enums.pb.h>
21 #include <stdio.h>
22
23 #include <algorithm>
24 #include <sstream>
25 #include <utility>
26 #include <vector>
27
28 #include "bta/include/bta_groups.h"
29 #include "common/strings.h"
30 #include "device_groups.h"
31 #include "devices.h"
32 #include "hardware/bt_le_audio.h"
33 #include "main/shim/metrics_api.h"
34 #include "os/logging/log_adapter.h"
35 #include "types/raw_address.h"
36
37 using bluetooth::common::ToString;
38 using bluetooth::groups::kGroupUnknown;
39 using bluetooth::le_audio::LeAudioDevice;
40 using bluetooth::le_audio::LeAudioHealthStatus;
41 using bluetooth::le_audio::LeAudioRecommendationActionCb;
42
43 namespace bluetooth::le_audio {
44 class LeAudioHealthStatusImpl;
45 LeAudioHealthStatusImpl* instance;
46
47 class LeAudioHealthStatusImpl : public LeAudioHealthStatus {
48 public:
LeAudioHealthStatusImpl(void)49 LeAudioHealthStatusImpl(void) { log::debug("Initiated"); }
50
~LeAudioHealthStatusImpl(void)51 ~LeAudioHealthStatusImpl(void) { clear_module(); }
52
RegisterCallback(LeAudioRecommendationActionCb cb)53 void RegisterCallback(LeAudioRecommendationActionCb cb) override {
54 register_callback(std::move(cb));
55 }
56
RemoveStatistics(const RawAddress & address,int group_id)57 void RemoveStatistics(const RawAddress& address, int group_id) override {
58 log::debug("{}, group_id: {}", address, group_id);
59 remove_device(address);
60 remove_group(group_id);
61 }
62
AddStatisticForDevice(const LeAudioDevice * device,LeAudioHealthDeviceStatType type)63 void AddStatisticForDevice(const LeAudioDevice* device,
64 LeAudioHealthDeviceStatType type) override {
65 if (device == nullptr) {
66 log::error("device is null");
67 return;
68 }
69
70 const RawAddress& address = device->address_;
71 log::debug("{}, {}", address, ToString(type));
72
73 auto dev = find_device(address);
74 if (dev == nullptr) {
75 add_device(address);
76 dev = find_device(address);
77 if (dev == nullptr) {
78 log::error("Could not add device {}", address);
79 return;
80 }
81 }
82 // log counter metrics
83 log_counter_metrics_for_device(type, device->allowlist_flag_);
84
85 LeAudioHealthBasedAction action;
86 switch (type) {
87 case LeAudioHealthDeviceStatType::VALID_DB:
88 dev->is_valid_service_ = true;
89 action = LeAudioHealthBasedAction::NONE;
90 break;
91 case LeAudioHealthDeviceStatType::INVALID_DB:
92 dev->is_valid_service_ = false;
93 action = LeAudioHealthBasedAction::DISABLE;
94 break;
95 case LeAudioHealthDeviceStatType::INVALID_CSIS:
96 dev->is_valid_group_member_ = false;
97 action = LeAudioHealthBasedAction::DISABLE;
98 break;
99 case LeAudioHealthDeviceStatType::VALID_CSIS:
100 dev->is_valid_group_member_ = true;
101 action = LeAudioHealthBasedAction::NONE;
102 break;
103 }
104
105 if (dev->latest_recommendation_ != action) {
106 dev->latest_recommendation_ = action;
107 send_recommendation_for_device(address, action);
108 return;
109 }
110 }
111
AddStatisticForGroup(const LeAudioDeviceGroup * device_group,LeAudioHealthGroupStatType type)112 void AddStatisticForGroup(const LeAudioDeviceGroup* device_group,
113 LeAudioHealthGroupStatType type) override {
114 if (device_group == nullptr) {
115 log::error("device_group is null");
116 return;
117 }
118
119 int group_id = device_group->group_id_;
120 log::debug("group_id: {}, {}", group_id, ToString(type));
121
122 auto group = find_group(group_id);
123 if (group == nullptr) {
124 add_group(group_id);
125 group = find_group(group_id);
126 if (group == nullptr) {
127 log::error("Could not add group {}", group_id);
128 return;
129 }
130 }
131
132 LeAudioDevice* device = device_group->GetFirstDevice();
133 if (device == nullptr) {
134 log::error("Front device is null. Number of devices: {}", device_group->Size());
135 return;
136 }
137 // log counter metrics
138 log_counter_metrics_for_group(type, device->allowlist_flag_);
139
140 switch (type) {
141 case LeAudioHealthGroupStatType::STREAM_CREATE_SUCCESS:
142 group->stream_success_cnt_++;
143 if (group->latest_recommendation_ == LeAudioHealthBasedAction::NONE) {
144 return;
145 }
146 break;
147 case LeAudioHealthGroupStatType::STREAM_CREATE_CIS_FAILED:
148 group->stream_cis_failures_cnt_++;
149 group->stream_failures_cnt_++;
150 break;
151 case LeAudioHealthGroupStatType::STREAM_CREATE_SIGNALING_FAILED:
152 group->stream_signaling_failures_cnt_++;
153 group->stream_failures_cnt_++;
154 break;
155 case LeAudioHealthGroupStatType::STREAM_CONTEXT_NOT_AVAILABLE:
156 group->stream_context_not_avail_cnt_++;
157 break;
158 }
159
160 LeAudioHealthBasedAction action = LeAudioHealthBasedAction::NONE;
161 if (group->stream_success_cnt_ == 0) {
162 /* Never succeed in stream creation */
163 if (group->stream_failures_cnt_ >= MAX_ALLOWED_FAILURES_IN_A_ROW_WITHOUT_SUCCESS) {
164 action = LeAudioHealthBasedAction::DISABLE;
165 } else if (group->stream_context_not_avail_cnt_ >=
166 MAX_ALLOWED_FAILURES_IN_A_ROW_WITHOUT_SUCCESS) {
167 action = LeAudioHealthBasedAction::INACTIVATE_GROUP;
168 group->stream_context_not_avail_cnt_ = 0;
169 }
170 } else {
171 /* Had some success before */
172 if ((100 * group->stream_failures_cnt_ / group->stream_success_cnt_) >=
173 THRESHOLD_FOR_DISABLE_CONSIDERATION) {
174 action = LeAudioHealthBasedAction::CONSIDER_DISABLING;
175 } else if (group->stream_context_not_avail_cnt_ >=
176 MAX_ALLOWED_FAILURES_IN_A_ROW_WITHOUT_SUCCESS) {
177 action = LeAudioHealthBasedAction::INACTIVATE_GROUP;
178 group->stream_context_not_avail_cnt_ = 0;
179 }
180 }
181
182 if (group->latest_recommendation_ != action) {
183 group->latest_recommendation_ = action;
184 send_recommendation_for_group(group_id, action);
185 }
186 }
187
Dump(int fd)188 void Dump(int fd) {
189 dprintf(fd, " LeAudioHealthStats: \n groups:");
190 for (const auto& g : group_stats_) {
191 dumpsys_group(fd, g);
192 }
193 dprintf(fd, "\n devices: ");
194 for (const auto& dev : devices_stats_) {
195 dumpsys_dev(fd, dev);
196 }
197 dprintf(fd, "\n");
198 }
199
200 private:
201 static constexpr int MAX_ALLOWED_FAILURES_IN_A_ROW_WITHOUT_SUCCESS = 3;
202 static constexpr int THRESHOLD_FOR_DISABLE_CONSIDERATION = 70;
203
204 std::vector<LeAudioRecommendationActionCb> callbacks_;
205 std::vector<device_stats> devices_stats_;
206 std::vector<group_stats> group_stats_;
207
dumpsys_group(int fd,const group_stats & group)208 void dumpsys_group(int fd, const group_stats& group) {
209 std::stringstream stream;
210
211 stream << "\n group_id: " << group.group_id_ << ": " << group.latest_recommendation_
212 << ", success: " << group.stream_success_cnt_
213 << ", fail total: " << group.stream_failures_cnt_
214 << ", fail cis: " << group.stream_cis_failures_cnt_
215 << ", fail signaling: " << group.stream_signaling_failures_cnt_
216 << ", context not avail: " << group.stream_context_not_avail_cnt_;
217
218 dprintf(fd, "%s", stream.str().c_str());
219 }
220
dumpsys_dev(int fd,const device_stats & dev)221 void dumpsys_dev(int fd, const device_stats& dev) {
222 std::stringstream stream;
223
224 stream << "\n " << ADDRESS_TO_LOGGABLE_STR(dev.address_) << ": " << dev.latest_recommendation_
225 << (dev.is_valid_service_ ? " service: OK" : " service : NOK")
226 << (dev.is_valid_group_member_ ? " csis: OK" : " csis : NOK");
227
228 dprintf(fd, "%s", stream.str().c_str());
229 }
230
clear_module(void)231 void clear_module(void) {
232 devices_stats_.clear();
233 group_stats_.clear();
234 callbacks_.clear();
235 }
236
send_recommendation_for_device(const RawAddress & address,LeAudioHealthBasedAction recommendation)237 void send_recommendation_for_device(const RawAddress& address,
238 LeAudioHealthBasedAction recommendation) {
239 log::debug("{}, {}", address, ToString(recommendation));
240 /* Notify new user about known groups */
241 for (auto& cb : callbacks_) {
242 cb.Run(address, kGroupUnknown, recommendation);
243 }
244 }
245
send_recommendation_for_group(int group_id,const LeAudioHealthBasedAction recommendation)246 void send_recommendation_for_group(int group_id, const LeAudioHealthBasedAction recommendation) {
247 log::debug("group_id: {}, {}", group_id, ToString(recommendation));
248 /* Notify new user about known groups */
249 for (auto& cb : callbacks_) {
250 cb.Run(RawAddress::kEmpty, group_id, recommendation);
251 }
252 }
253
add_device(const RawAddress & address)254 void add_device(const RawAddress& address) { devices_stats_.emplace_back(device_stats(address)); }
255
add_group(int group_id)256 void add_group(int group_id) { group_stats_.emplace_back(group_stats(group_id)); }
257
remove_group(int group_id)258 void remove_group(int group_id) {
259 if (group_id == kGroupUnknown) {
260 return;
261 }
262 auto iter = std::find_if(group_stats_.begin(), group_stats_.end(),
263 [group_id](const auto& g) { return g.group_id_ == group_id; });
264 if (iter != group_stats_.end()) {
265 group_stats_.erase(iter);
266 }
267 }
268
remove_device(const RawAddress & address)269 void remove_device(const RawAddress& address) {
270 auto iter = std::find_if(devices_stats_.begin(), devices_stats_.end(),
271 [address](const auto& d) { return d.address_ == address; });
272 if (iter != devices_stats_.end()) {
273 devices_stats_.erase(iter);
274 }
275 }
276
register_callback(LeAudioRecommendationActionCb cb)277 void register_callback(LeAudioRecommendationActionCb cb) { callbacks_.push_back(std::move(cb)); }
278
find_device(const RawAddress & address)279 device_stats* find_device(const RawAddress& address) {
280 auto iter = std::find_if(devices_stats_.begin(), devices_stats_.end(),
281 [address](const auto& d) { return d.address_ == address; });
282 if (iter == devices_stats_.end()) {
283 return nullptr;
284 }
285
286 return &(*iter);
287 }
288
find_group(int group_id)289 group_stats* find_group(int group_id) {
290 auto iter = std::find_if(group_stats_.begin(), group_stats_.end(),
291 [group_id](const auto& g) { return g.group_id_ == group_id; });
292 if (iter == group_stats_.end()) {
293 return nullptr;
294 }
295
296 return &(*iter);
297 }
298
log_counter_metrics_for_device(LeAudioHealthDeviceStatType type,bool in_allowlist)299 void log_counter_metrics_for_device(LeAudioHealthDeviceStatType type, bool in_allowlist) {
300 log::debug("in_allowlist: {}, type: {}", in_allowlist, ToString(type));
301 android::bluetooth::CodePathCounterKeyEnum key;
302 if (in_allowlist) {
303 switch (type) {
304 case LeAudioHealthDeviceStatType::VALID_DB:
305 case LeAudioHealthDeviceStatType::VALID_CSIS:
306 key = android::bluetooth::CodePathCounterKeyEnum::
307 LE_AUDIO_ALLOWLIST_DEVICE_HEALTH_STATUS_GOOD;
308 break;
309 case LeAudioHealthDeviceStatType::INVALID_DB:
310 key = android::bluetooth::CodePathCounterKeyEnum::
311 LE_AUDIO_ALLOWLIST_DEVICE_HEALTH_STATUS_BAD_INVALID_DB;
312 break;
313 case LeAudioHealthDeviceStatType::INVALID_CSIS:
314 key = android::bluetooth::CodePathCounterKeyEnum::
315 LE_AUDIO_ALLOWLIST_DEVICE_HEALTH_STATUS_BAD_INVALID_CSIS;
316 break;
317 default:
318 log::error("Metric unhandled {}", type);
319 return;
320 }
321 } else {
322 switch (type) {
323 case LeAudioHealthDeviceStatType::VALID_DB:
324 case LeAudioHealthDeviceStatType::VALID_CSIS:
325 key = android::bluetooth::CodePathCounterKeyEnum::
326 LE_AUDIO_NONALLOWLIST_DEVICE_HEALTH_STATUS_GOOD;
327 break;
328 case LeAudioHealthDeviceStatType::INVALID_DB:
329 key = android::bluetooth::CodePathCounterKeyEnum::
330 LE_AUDIO_NONALLOWLIST_DEVICE_HEALTH_STATUS_BAD_INVALID_DB;
331 break;
332 case LeAudioHealthDeviceStatType::INVALID_CSIS:
333 key = android::bluetooth::CodePathCounterKeyEnum::
334 LE_AUDIO_NONALLOWLIST_DEVICE_HEALTH_STATUS_BAD_INVALID_CSIS;
335 break;
336 default:
337 log::error("Metric unhandled {}", type);
338 return;
339 }
340 }
341 bluetooth::shim::CountCounterMetrics(key, 1);
342 }
343
log_counter_metrics_for_group(LeAudioHealthGroupStatType type,bool in_allowlist)344 void log_counter_metrics_for_group(LeAudioHealthGroupStatType type, bool in_allowlist) {
345 log::debug("in_allowlist: {}, type: {}", in_allowlist, ToString(type));
346 android::bluetooth::CodePathCounterKeyEnum key;
347 if (in_allowlist) {
348 switch (type) {
349 case LeAudioHealthGroupStatType::STREAM_CREATE_SUCCESS:
350 key = android::bluetooth::CodePathCounterKeyEnum::
351 LE_AUDIO_ALLOWLIST_GROUP_HEALTH_STATUS_GOOD;
352 break;
353 case LeAudioHealthGroupStatType::STREAM_CREATE_CIS_FAILED:
354 key = android::bluetooth::CodePathCounterKeyEnum::
355 LE_AUDIO_ALLOWLIST_GROUP_HEALTH_STATUS_BAD_ONCE_CIS_FAILED;
356 break;
357 case LeAudioHealthGroupStatType::STREAM_CREATE_SIGNALING_FAILED:
358 key = android::bluetooth::CodePathCounterKeyEnum::
359 LE_AUDIO_ALLOWLIST_GROUP_HEALTH_STATUS_BAD_ONCE_SIGNALING_FAILED;
360 break;
361 default:
362 log::error("Metric unhandled {}", type);
363 return;
364 }
365 } else {
366 switch (type) {
367 case LeAudioHealthGroupStatType::STREAM_CREATE_SUCCESS:
368 key = android::bluetooth::CodePathCounterKeyEnum::
369 LE_AUDIO_NONALLOWLIST_GROUP_HEALTH_STATUS_GOOD;
370 break;
371 case LeAudioHealthGroupStatType::STREAM_CREATE_CIS_FAILED:
372 key = android::bluetooth::CodePathCounterKeyEnum::
373 LE_AUDIO_NONALLOWLIST_GROUP_HEALTH_STATUS_BAD_ONCE_CIS_FAILED;
374 break;
375 case LeAudioHealthGroupStatType::STREAM_CREATE_SIGNALING_FAILED:
376 key = android::bluetooth::CodePathCounterKeyEnum::
377 LE_AUDIO_NONALLOWLIST_GROUP_HEALTH_STATUS_BAD_ONCE_SIGNALING_FAILED;
378 break;
379 default:
380 log::error("Metric unhandled {}", type);
381 return;
382 }
383 }
384 bluetooth::shim::CountCounterMetrics(key, 1);
385 }
386 };
387 } // namespace bluetooth::le_audio
388
Get(void)389 LeAudioHealthStatus* LeAudioHealthStatus::Get(void) {
390 if (instance) {
391 return instance;
392 }
393 instance = new LeAudioHealthStatusImpl();
394 return instance;
395 }
396
DebugDump(int fd)397 void LeAudioHealthStatus::DebugDump(int fd) {
398 if (instance) {
399 instance->Dump(fd);
400 }
401 }
402
Cleanup(void)403 void LeAudioHealthStatus::Cleanup(void) {
404 if (!instance) {
405 return;
406 }
407 auto ptr = instance;
408 instance = nullptr;
409 delete ptr;
410 }
411