1 /*
2  * Copyright 2022 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 "metrics_collector.h"
18 
19 #include <bluetooth/log.h>
20 
21 #include <chrono>
22 #include <cstdint>
23 #include <cstdlib>
24 #include <memory>
25 #include <unordered_map>
26 #include <vector>
27 
28 #include "common/metrics.h"
29 #include "hardware/bt_le_audio.h"
30 #include "le_audio_types.h"
31 #include "types/raw_address.h"
32 
33 namespace bluetooth::le_audio {
34 
35 using bluetooth::le_audio::ConnectionState;
36 using bluetooth::le_audio::types::LeAudioContextType;
37 
38 const static metrics::ClockTimePoint kInvalidTimePoint{};
39 
40 MetricsCollector* MetricsCollector::instance = nullptr;
41 
get_timedelta_nanos(const metrics::ClockTimePoint & t1,const metrics::ClockTimePoint & t2)42 inline int64_t get_timedelta_nanos(const metrics::ClockTimePoint& t1,
43                                    const metrics::ClockTimePoint& t2) {
44   if (t1 == kInvalidTimePoint || t2 == kInvalidTimePoint) {
45     return -1;
46   }
47   return std::abs(std::chrono::duration_cast<std::chrono::nanoseconds>(t1 - t2).count());
48 }
49 
50 const static std::unordered_map<LeAudioContextType, LeAudioMetricsContextType> kContextTypeTable = {
51         {LeAudioContextType::UNINITIALIZED, LeAudioMetricsContextType::INVALID},
52         {LeAudioContextType::UNSPECIFIED, LeAudioMetricsContextType::UNSPECIFIED},
53         {LeAudioContextType::CONVERSATIONAL, LeAudioMetricsContextType::COMMUNICATION},
54         {LeAudioContextType::MEDIA, LeAudioMetricsContextType::MEDIA},
55         {LeAudioContextType::GAME, LeAudioMetricsContextType::GAME},
56         {LeAudioContextType::INSTRUCTIONAL, LeAudioMetricsContextType::INSTRUCTIONAL},
57         {LeAudioContextType::VOICEASSISTANTS, LeAudioMetricsContextType::MAN_MACHINE},
58         {LeAudioContextType::LIVE, LeAudioMetricsContextType::LIVE},
59         {LeAudioContextType::SOUNDEFFECTS, LeAudioMetricsContextType::ATTENTION_SEEKING},
60         {LeAudioContextType::NOTIFICATIONS, LeAudioMetricsContextType::ATTENTION_SEEKING},
61         {LeAudioContextType::RINGTONE, LeAudioMetricsContextType::RINGTONE},
62         {LeAudioContextType::ALERTS, LeAudioMetricsContextType::IMMEDIATE_ALERT},
63         {LeAudioContextType::EMERGENCYALARM, LeAudioMetricsContextType::EMERGENCY_ALERT},
64         {LeAudioContextType::RFU, LeAudioMetricsContextType::RFU},
65 };
66 
to_atom_context_type(const LeAudioContextType stack_type)67 inline int32_t to_atom_context_type(const LeAudioContextType stack_type) {
68   auto it = kContextTypeTable.find(stack_type);
69   if (it != kContextTypeTable.end()) {
70     return static_cast<int32_t>(it->second);
71   }
72   return static_cast<int32_t>(LeAudioMetricsContextType::INVALID);
73 }
74 
75 class DeviceMetrics {
76 public:
77   RawAddress address_;
78   metrics::ClockTimePoint connecting_timepoint_ = kInvalidTimePoint;
79   metrics::ClockTimePoint connected_timepoint_ = kInvalidTimePoint;
80   metrics::ClockTimePoint disconnected_timepoint_ = kInvalidTimePoint;
81   int32_t connection_status_ = 0;
82   int32_t disconnection_status_ = 0;
83 
DeviceMetrics(const RawAddress & address)84   DeviceMetrics(const RawAddress& address) : address_(address) {}
85 
AddStateChangedEvent(ConnectionState state,ConnectionStatus status)86   void AddStateChangedEvent(ConnectionState state, ConnectionStatus status) {
87     switch (state) {
88       case ConnectionState::CONNECTING:
89         connecting_timepoint_ = std::chrono::high_resolution_clock::now();
90         break;
91       case ConnectionState::CONNECTED:
92         connected_timepoint_ = std::chrono::high_resolution_clock::now();
93         connection_status_ = static_cast<int32_t>(status);
94         break;
95       case ConnectionState::DISCONNECTED:
96         disconnected_timepoint_ = std::chrono::high_resolution_clock::now();
97         disconnection_status_ = static_cast<int32_t>(status);
98         break;
99       case ConnectionState::DISCONNECTING:
100         // Ignore
101         break;
102     }
103   }
104 };
105 
106 class GroupMetricsImpl : public GroupMetrics {
107 private:
108   static constexpr int32_t kInvalidGroupId = -1;
109   int32_t group_id_;
110   int32_t group_size_;
111   std::vector<std::unique_ptr<DeviceMetrics>> device_metrics_;
112   std::unordered_map<RawAddress, DeviceMetrics*> opened_devices_;
113   metrics::ClockTimePoint beginning_timepoint_;
114   std::vector<int64_t> streaming_offset_nanos_;
115   std::vector<int64_t> streaming_duration_nanos_;
116   std::vector<int32_t> streaming_context_type_;
117 
118 public:
GroupMetricsImpl()119   GroupMetricsImpl() : group_id_(kInvalidGroupId), group_size_(0) {
120     beginning_timepoint_ = std::chrono::high_resolution_clock::now();
121   }
GroupMetricsImpl(int32_t group_id,int32_t group_size)122   GroupMetricsImpl(int32_t group_id, int32_t group_size)
123       : group_id_(group_id), group_size_(group_size) {
124     beginning_timepoint_ = std::chrono::high_resolution_clock::now();
125   }
126 
AddStateChangedEvent(const RawAddress & address,bluetooth::le_audio::ConnectionState state,ConnectionStatus status)127   void AddStateChangedEvent(const RawAddress& address, bluetooth::le_audio::ConnectionState state,
128                             ConnectionStatus status) override {
129     auto it = opened_devices_.find(address);
130     if (it == opened_devices_.end()) {
131       device_metrics_.push_back(std::make_unique<DeviceMetrics>(address));
132       it = opened_devices_.insert(std::begin(opened_devices_),
133                                   {address, device_metrics_.back().get()});
134     }
135     it->second->AddStateChangedEvent(state, status);
136     if (state == bluetooth::le_audio::ConnectionState::DISCONNECTED ||
137         (state == bluetooth::le_audio::ConnectionState::CONNECTED &&
138          status != ConnectionStatus::SUCCESS)) {
139       opened_devices_.erase(it);
140     }
141   }
142 
AddStreamStartedEvent(bluetooth::le_audio::types::LeAudioContextType context_type)143   void AddStreamStartedEvent(bluetooth::le_audio::types::LeAudioContextType context_type) override {
144     int32_t atom_context_type = to_atom_context_type(context_type);
145     // Make sure events aligned
146     if (streaming_offset_nanos_.size() - streaming_duration_nanos_.size() != 0) {
147       // Allow type switching
148       if (!streaming_context_type_.empty() && streaming_context_type_.back() != atom_context_type) {
149         AddStreamEndedEvent();
150       } else {
151         return;
152       }
153     }
154     streaming_offset_nanos_.push_back(
155             get_timedelta_nanos(std::chrono::high_resolution_clock::now(), beginning_timepoint_));
156     streaming_context_type_.push_back(atom_context_type);
157   }
158 
AddStreamEndedEvent()159   void AddStreamEndedEvent() override {
160     // Make sure events aligned
161     if (streaming_offset_nanos_.size() - streaming_duration_nanos_.size() != 1) {
162       return;
163     }
164     streaming_duration_nanos_.push_back(
165             get_timedelta_nanos(std::chrono::high_resolution_clock::now(), beginning_timepoint_) -
166             streaming_offset_nanos_.back());
167   }
168 
SetGroupSize(int32_t group_size)169   void SetGroupSize(int32_t group_size) override { group_size_ = group_size; }
170 
IsClosed()171   bool IsClosed() override { return opened_devices_.empty(); }
172 
WriteStats()173   void WriteStats() override {
174     int64_t connection_duration_nanos =
175             get_timedelta_nanos(beginning_timepoint_, std::chrono::high_resolution_clock::now());
176 
177     int len = device_metrics_.size();
178     std::vector<int64_t> device_connecting_offset_nanos(len);
179     std::vector<int64_t> device_connected_offset_nanos(len);
180     std::vector<int64_t> device_connection_duration_nanos(len);
181     std::vector<int32_t> device_connection_statuses(len);
182     std::vector<int32_t> device_disconnection_statuses(len);
183     std::vector<RawAddress> device_address(len);
184 
185     while (streaming_duration_nanos_.size() < streaming_offset_nanos_.size()) {
186       AddStreamEndedEvent();
187     }
188 
189     for (int i = 0; i < len; i++) {
190       auto device_metric = device_metrics_[i].get();
191       device_connecting_offset_nanos[i] =
192               get_timedelta_nanos(device_metric->connecting_timepoint_, beginning_timepoint_);
193       device_connected_offset_nanos[i] =
194               get_timedelta_nanos(device_metric->connected_timepoint_, beginning_timepoint_);
195       device_connection_duration_nanos[i] = get_timedelta_nanos(
196               device_metric->disconnected_timepoint_, device_metric->connected_timepoint_);
197       device_connection_statuses[i] = device_metric->connection_status_;
198       device_disconnection_statuses[i] = device_metric->disconnection_status_;
199       device_address[i] = device_metric->address_;
200     }
201 
202     bluetooth::common::LogLeAudioConnectionSessionReported(
203             group_size_, group_id_, connection_duration_nanos, device_connecting_offset_nanos,
204             device_connected_offset_nanos, device_connection_duration_nanos,
205             device_connection_statuses, device_disconnection_statuses, device_address,
206             streaming_offset_nanos_, streaming_duration_nanos_, streaming_context_type_);
207   }
208 
Flush()209   void Flush() {
210     for (auto& p : opened_devices_) {
211       p.second->AddStateChangedEvent(bluetooth::le_audio::ConnectionState::DISCONNECTED,
212                                      ConnectionStatus::SUCCESS);
213     }
214     WriteStats();
215   }
216 };
217 
218 /* Metrics Colloctor */
219 
Get()220 MetricsCollector* MetricsCollector::Get() {
221   if (MetricsCollector::instance == nullptr) {
222     MetricsCollector::instance = new MetricsCollector();
223   }
224   return MetricsCollector::instance;
225 }
226 
OnGroupSizeUpdate(int32_t group_id,int32_t group_size)227 void MetricsCollector::OnGroupSizeUpdate(int32_t group_id, int32_t group_size) {
228   group_size_table_[group_id] = group_size;
229   auto it = opened_groups_.find(group_id);
230   if (it != opened_groups_.end()) {
231     it->second->SetGroupSize(group_size);
232   }
233 }
234 
OnConnectionStateChanged(int32_t group_id,const RawAddress & address,bluetooth::le_audio::ConnectionState state,ConnectionStatus status)235 void MetricsCollector::OnConnectionStateChanged(int32_t group_id, const RawAddress& address,
236                                                 bluetooth::le_audio::ConnectionState state,
237                                                 ConnectionStatus status) {
238   if (address.IsEmpty() || group_id <= 0) {
239     return;
240   }
241   auto it = opened_groups_.find(group_id);
242   if (it == opened_groups_.end()) {
243     it = opened_groups_.insert(
244             std::begin(opened_groups_),
245             {group_id, std::make_unique<GroupMetricsImpl>(group_id, group_size_table_[group_id])});
246   }
247   it->second->AddStateChangedEvent(address, state, status);
248 
249   if (it->second->IsClosed()) {
250     it->second->WriteStats();
251     opened_groups_.erase(it);
252   }
253 }
254 
OnStreamStarted(int32_t group_id,bluetooth::le_audio::types::LeAudioContextType context_type)255 void MetricsCollector::OnStreamStarted(
256         int32_t group_id, bluetooth::le_audio::types::LeAudioContextType context_type) {
257   if (group_id <= 0) {
258     return;
259   }
260   auto it = opened_groups_.find(group_id);
261   if (it != opened_groups_.end()) {
262     it->second->AddStreamStartedEvent(context_type);
263   }
264 }
265 
OnStreamEnded(int32_t group_id)266 void MetricsCollector::OnStreamEnded(int32_t group_id) {
267   if (group_id <= 0) {
268     return;
269   }
270   auto it = opened_groups_.find(group_id);
271   if (it != opened_groups_.end()) {
272     it->second->AddStreamEndedEvent();
273   }
274 }
275 
OnBroadcastStateChanged(bool started)276 void MetricsCollector::OnBroadcastStateChanged(bool started) {
277   if (started) {
278     broadcast_beginning_timepoint_ = std::chrono::high_resolution_clock::now();
279   } else {
280     auto broadcast_ending_timepoint_ = std::chrono::high_resolution_clock::now();
281     bluetooth::common::LogLeAudioBroadcastSessionReported(
282             get_timedelta_nanos(broadcast_beginning_timepoint_, broadcast_ending_timepoint_));
283     broadcast_beginning_timepoint_ = kInvalidTimePoint;
284   }
285 }
286 
Flush()287 void MetricsCollector::Flush() {
288   log::info("");
289   for (auto& p : opened_groups_) {
290     p.second->Flush();
291   }
292   opened_groups_.clear();
293 }
294 
295 }  // namespace bluetooth::le_audio
296