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