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