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