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