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