• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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