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 "types/raw_address.h"
35
36 using bluetooth::common::ToString;
37 using bluetooth::groups::kGroupUnknown;
38 using bluetooth::le_audio::LeAudioDevice;
39 using bluetooth::le_audio::LeAudioHealthStatus;
40 using bluetooth::le_audio::LeAudioRecommendationActionCb;
41
42 namespace bluetooth::le_audio {
43 class LeAudioHealthStatusImpl;
44 LeAudioHealthStatusImpl* instance;
45
46 class LeAudioHealthStatusImpl : public LeAudioHealthStatus {
47 public:
LeAudioHealthStatusImpl(void)48 LeAudioHealthStatusImpl(void) { log::debug("Initiated"); }
49
~LeAudioHealthStatusImpl(void)50 ~LeAudioHealthStatusImpl(void) { clear_module(); }
51
RegisterCallback(LeAudioRecommendationActionCb cb)52 void RegisterCallback(LeAudioRecommendationActionCb cb) override {
53 register_callback(std::move(cb));
54 }
55
RemoveStatistics(const RawAddress & address,int group_id)56 void RemoveStatistics(const RawAddress& address, int group_id) override {
57 log::debug("{}, group_id: {}", address, group_id);
58 remove_device(address);
59 remove_group(group_id);
60 }
61
AddStatisticForDevice(const LeAudioDevice * device,LeAudioHealthDeviceStatType type)62 void AddStatisticForDevice(const LeAudioDevice* device,
63 LeAudioHealthDeviceStatType type) override {
64 if (device == nullptr) {
65 log::error("device is null");
66 return;
67 }
68
69 const RawAddress& address = device->address_;
70 log::debug("{}, {}", address, ToString(type));
71
72 auto dev = find_device(address);
73 if (dev == nullptr) {
74 add_device(address);
75 dev = find_device(address);
76 if (dev == nullptr) {
77 log::error("Could not add device {}", address);
78 return;
79 }
80 }
81 // log counter metrics
82 log_counter_metrics_for_device(type, device->allowlist_flag_);
83
84 LeAudioHealthBasedAction action;
85 switch (type) {
86 case LeAudioHealthDeviceStatType::VALID_DB:
87 dev->is_valid_service_ = true;
88 action = LeAudioHealthBasedAction::NONE;
89 break;
90 case LeAudioHealthDeviceStatType::INVALID_DB:
91 dev->is_valid_service_ = false;
92 action = LeAudioHealthBasedAction::DISABLE;
93 break;
94 case LeAudioHealthDeviceStatType::INVALID_CSIS:
95 dev->is_valid_group_member_ = false;
96 action = LeAudioHealthBasedAction::DISABLE;
97 break;
98 case LeAudioHealthDeviceStatType::VALID_CSIS:
99 dev->is_valid_group_member_ = true;
100 action = LeAudioHealthBasedAction::NONE;
101 break;
102 }
103
104 if (dev->latest_recommendation_ != action) {
105 dev->latest_recommendation_ = action;
106 send_recommendation_for_device(address, action);
107 return;
108 }
109 }
110
AddStatisticForGroup(const LeAudioDeviceGroup * device_group,LeAudioHealthGroupStatType type)111 void AddStatisticForGroup(const LeAudioDeviceGroup* device_group,
112 LeAudioHealthGroupStatType type) override {
113 if (device_group == nullptr) {
114 log::error("device_group is null");
115 return;
116 }
117
118 int group_id = device_group->group_id_;
119 log::debug("group_id: {}, {}", group_id, ToString(type));
120
121 auto group = find_group(group_id);
122 if (group == nullptr) {
123 add_group(group_id);
124 group = find_group(group_id);
125 if (group == nullptr) {
126 log::error("Could not add group {}", group_id);
127 return;
128 }
129 }
130
131 LeAudioDevice* device = device_group->GetFirstDevice();
132 if (device == nullptr) {
133 log::error("Front device is null. Number of devices: {}", device_group->Size());
134 return;
135 }
136 // log counter metrics
137 log_counter_metrics_for_group(type, device->allowlist_flag_);
138
139 switch (type) {
140 case LeAudioHealthGroupStatType::STREAM_CREATE_SUCCESS:
141 group->stream_success_cnt_++;
142 if (group->latest_recommendation_ == LeAudioHealthBasedAction::NONE) {
143 return;
144 }
145 break;
146 case LeAudioHealthGroupStatType::STREAM_CREATE_CIS_FAILED:
147 group->stream_cis_failures_cnt_++;
148 group->stream_failures_cnt_++;
149 break;
150 case LeAudioHealthGroupStatType::STREAM_CREATE_SIGNALING_FAILED:
151 group->stream_signaling_failures_cnt_++;
152 group->stream_failures_cnt_++;
153 break;
154 case LeAudioHealthGroupStatType::STREAM_CONTEXT_NOT_AVAILABLE:
155 group->stream_context_not_avail_cnt_++;
156 break;
157 }
158
159 LeAudioHealthBasedAction action = LeAudioHealthBasedAction::NONE;
160 if (group->stream_success_cnt_ == 0) {
161 /* Never succeed in stream creation */
162 if (group->stream_failures_cnt_ >= MAX_ALLOWED_FAILURES_IN_A_ROW_WITHOUT_SUCCESS) {
163 action = LeAudioHealthBasedAction::DISABLE;
164 } else if (group->stream_context_not_avail_cnt_ >=
165 MAX_ALLOWED_FAILURES_IN_A_ROW_WITHOUT_SUCCESS) {
166 action = LeAudioHealthBasedAction::INACTIVATE_GROUP;
167 group->stream_context_not_avail_cnt_ = 0;
168 }
169 } else {
170 /* Had some success before */
171 if ((100 * group->stream_failures_cnt_ / group->stream_success_cnt_) >=
172 THRESHOLD_FOR_DISABLE_CONSIDERATION) {
173 action = LeAudioHealthBasedAction::CONSIDER_DISABLING;
174 } else if (group->stream_context_not_avail_cnt_ >=
175 MAX_ALLOWED_FAILURES_IN_A_ROW_WITHOUT_SUCCESS) {
176 action = LeAudioHealthBasedAction::INACTIVATE_GROUP;
177 group->stream_context_not_avail_cnt_ = 0;
178 }
179 }
180
181 if (group->latest_recommendation_ != action) {
182 group->latest_recommendation_ = action;
183 send_recommendation_for_group(group_id, action);
184 }
185 }
186
Dump(int fd)187 void Dump(int fd) {
188 dprintf(fd, " LeAudioHealthStats: \n groups:");
189 for (const auto& g : group_stats_) {
190 dumpsys_group(fd, g);
191 }
192 dprintf(fd, "\n devices: ");
193 for (const auto& dev : devices_stats_) {
194 dumpsys_dev(fd, dev);
195 }
196 dprintf(fd, "\n");
197 }
198
199 private:
200 static constexpr int MAX_ALLOWED_FAILURES_IN_A_ROW_WITHOUT_SUCCESS = 3;
201 static constexpr int THRESHOLD_FOR_DISABLE_CONSIDERATION = 70;
202
203 std::vector<LeAudioRecommendationActionCb> callbacks_;
204 std::vector<device_stats> devices_stats_;
205 std::vector<group_stats> group_stats_;
206
dumpsys_group(int fd,const group_stats & group)207 void dumpsys_group(int fd, const group_stats& group) {
208 std::stringstream stream;
209
210 stream << "\n group_id: " << group.group_id_ << ": " << group.latest_recommendation_
211 << ", success: " << group.stream_success_cnt_
212 << ", fail total: " << group.stream_failures_cnt_
213 << ", fail cis: " << group.stream_cis_failures_cnt_
214 << ", fail signaling: " << group.stream_signaling_failures_cnt_
215 << ", context not avail: " << group.stream_context_not_avail_cnt_;
216
217 dprintf(fd, "%s", stream.str().c_str());
218 }
219
dumpsys_dev(int fd,const device_stats & dev)220 void dumpsys_dev(int fd, const device_stats& dev) {
221 std::stringstream stream;
222
223 stream << "\n " << dev.address_.ToRedactedStringForLogging() << ": "
224 << 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