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