1 /*
2 * Copyright (C) 2023, 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 <binder/ProcessState.h>
18 #include <gmock/gmock.h>
19 #include <gtest/gtest.h>
20 #include <gtest_matchers.h>
21 #include <stats_subscription.h>
22 #include <stdint.h>
23 #include <utils/Looper.h>
24
25 #include <chrono>
26 #include <string>
27 #include <thread>
28 #include <vector>
29
30 #include "packages/modules/StatsD/statsd/src/shell/shell_config.pb.h"
31 #include "packages/modules/StatsD/statsd/src/shell/shell_data.pb.h"
32 #include "statslog_statsdtest.h"
33
34 #ifdef __ANDROID__
35
36 using namespace testing;
37 using android::Looper;
38 using android::ProcessState;
39 using android::sp;
40 using android::os::statsd::Atom;
41 using android::os::statsd::ShellData;
42 using android::os::statsd::ShellSubscription;
43 using android::os::statsd::TestAtomReported_State_OFF;
44 using android::os::statsd::TrainExperimentIds;
45 using android::os::statsd::util::BytesField;
46 using android::os::statsd::util::SCREEN_BRIGHTNESS_CHANGED;
47 using android::os::statsd::util::stats_write;
48 using android::os::statsd::util::TEST_ATOM_REPORTED;
49 using android::os::statsd::util::TEST_ATOM_REPORTED__REPEATED_ENUM_FIELD__OFF;
50 using std::string;
51 using std::vector;
52 using std::this_thread::sleep_for;
53
54 namespace {
55
56 class SubscriptionTest : public Test {
57 public:
SubscriptionTest()58 SubscriptionTest() : looper(Looper::prepare(/*opts=*/0)) {
59 const TestInfo* const test_info = UnitTest::GetInstance()->current_test_info();
60 ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
61
62 *trainExpIds.mutable_experiment_id() = {expIds.begin(), expIds.end()};
63 trainExpIds.SerializeToString(&trainExpIdsBytes);
64 }
65
~SubscriptionTest()66 ~SubscriptionTest() {
67 const TestInfo* const test_info = UnitTest::GetInstance()->current_test_info();
68 ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
69 }
70
71 protected:
SetUp()72 void SetUp() override {
73 // Start the Binder thread pool.
74 ProcessState::self()->startThreadPool();
75 }
76
TearDown()77 void TearDown() {
78 // Clear any dangling subscriptions from statsd.
79 if (__builtin_available(android __STATSD_SUBS_MIN_API__, *)) {
80 AStatsManager_removeSubscription(subId);
81 }
82 }
83
LogTestAtomReported(int32_t intFieldValue)84 void LogTestAtomReported(int32_t intFieldValue) {
85 const BytesField bytesField(trainExpIdsBytes.data(), trainExpIdsBytes.size());
86 stats_write(TEST_ATOM_REPORTED, uids.data(), uids.size(), tags, intFieldValue,
87 /*long_field=*/2LL, /*float_field=*/3.0F,
88 /*string_field=*/string1.c_str(),
89 /*boolean_field=*/false, /*state=*/TEST_ATOM_REPORTED__REPEATED_ENUM_FIELD__OFF,
90 bytesField, repeatedInts, repeatedLongs, repeatedFloats, repeatedStrings,
91 &(repeatedBool[0]), /*repeatedBoolSize=*/2, repeatedEnums);
92 }
93
94 int32_t subId;
95
96 // TestAtomReported fields.
97 const vector<int32_t> uids = {1};
98 const string tag = "test";
99 const vector<char const*> tags = {tag.c_str()};
100
101 // 100 int64s for the MODE_BYTES field to push atom size to over 1K.
102 const vector<int64_t> expIds = vector<int64_t>(100, INT64_MAX);
103
104 const vector<int32_t> repeatedInts{1};
105 const vector<int64_t> repeatedLongs{2LL};
106 const vector<float> repeatedFloats{3.0F};
107 const string string1 = "ABC";
108 const vector<char const*> repeatedStrings = {string1.c_str()};
109 const bool repeatedBool[2] = {false, true};
110 const vector<int32_t> repeatedEnums = {TEST_ATOM_REPORTED__REPEATED_ENUM_FIELD__OFF};
111 TrainExperimentIds trainExpIds;
112 string trainExpIdsBytes;
113
114 private:
115 sp<Looper> looper;
116 };
117
118 // Stores arguments passed in subscription callback.
119 struct CallbackData {
120 int32_t subId;
121 AStatsManager_SubscriptionCallbackReason reason;
122 vector<uint8_t> payload;
123 int count; // Stores number of times the callback is invoked.
124 };
125
callback(int32_t subscription_id,AStatsManager_SubscriptionCallbackReason reason,uint8_t * _Nonnull payload,size_t num_bytes,void * _Nullable cookie)126 static void callback(int32_t subscription_id, AStatsManager_SubscriptionCallbackReason reason,
127 uint8_t* _Nonnull payload, size_t num_bytes, void* _Nullable cookie) {
128 CallbackData* data = static_cast<CallbackData*>(cookie);
129 data->subId = subscription_id;
130 data->reason = reason;
131 data->payload.assign(payload, payload + num_bytes);
132 data->count++;
133 }
134
135 constexpr static int WAIT_MS = 500;
136
TEST_F(SubscriptionTest,TestSubscription)137 TEST_F(SubscriptionTest, TestSubscription) {
138 if (__builtin_available(android __STATSD_SUBS_MIN_API__, *)) {
139 ShellSubscription config;
140 config.add_pushed()->set_atom_id(TEST_ATOM_REPORTED);
141 config.add_pushed()->set_atom_id(SCREEN_BRIGHTNESS_CHANGED);
142
143 string configBytes;
144 config.SerializeToString(&configBytes);
145
146 CallbackData callbackData{/*subId=*/0,
147 ASTATSMANAGER_SUBSCRIPTION_CALLBACK_REASON_SUBSCRIPTION_ENDED,
148 /*payload=*/{},
149 /*count=*/0};
150
151 // Add subscription.
152 subId = AStatsManager_addSubscription(reinterpret_cast<const uint8_t*>(configBytes.data()),
153 configBytes.size(), &callback, &callbackData);
154 ASSERT_GT(subId, 0);
155 sleep_for(std::chrono::milliseconds(WAIT_MS));
156
157 // Log events without exceeding statsd cache.
158 stats_write(SCREEN_BRIGHTNESS_CHANGED, 100);
159 LogTestAtomReported(1);
160 sleep_for(std::chrono::milliseconds(WAIT_MS));
161
162 // Verify no callback occurred yet.
163 EXPECT_EQ(callbackData.subId, 0);
164 EXPECT_EQ(callbackData.reason,
165 ASTATSMANAGER_SUBSCRIPTION_CALLBACK_REASON_SUBSCRIPTION_ENDED);
166 EXPECT_EQ(callbackData.count, 0);
167 ASSERT_TRUE(callbackData.payload.empty());
168
169 // Log another TestAtomReported to overflow cache.
170 LogTestAtomReported(2);
171 sleep_for(std::chrono::milliseconds(WAIT_MS));
172
173 // Verify callback occurred.
174 EXPECT_EQ(callbackData.subId, subId);
175 EXPECT_EQ(callbackData.reason, ASTATSMANAGER_SUBSCRIPTION_CALLBACK_REASON_STATSD_INITIATED);
176 EXPECT_EQ(callbackData.count, 1);
177 ASSERT_GT(callbackData.payload.size(), 0);
178
179 ShellData actualShellData;
180 ASSERT_TRUE(actualShellData.ParseFromArray(callbackData.payload.data(),
181 callbackData.payload.size()));
182
183 ASSERT_GE(actualShellData.elapsed_timestamp_nanos_size(), 3);
184 EXPECT_THAT(actualShellData.elapsed_timestamp_nanos(), Each(Gt(0LL)));
185
186 ASSERT_GE(actualShellData.atom_size(), 3);
187
188 // Verify atom 1.
189 Atom expectedAtom;
190 expectedAtom.mutable_screen_brightness_changed()->set_level(100);
191 EXPECT_THAT(actualShellData.atom(0), EqAtom(expectedAtom));
192
193 // Verify atom 2.
194 expectedAtom.Clear();
195 auto* testAtomReported = expectedAtom.mutable_test_atom_reported();
196 auto* attributionNode = testAtomReported->add_attribution_node();
197 attributionNode->set_uid(uids[0]);
198 attributionNode->set_tag(tag);
199 testAtomReported->set_int_field(1);
200 testAtomReported->set_long_field(2LL);
201 testAtomReported->set_float_field(3.0F);
202 testAtomReported->set_string_field(string1);
203 testAtomReported->set_boolean_field(false);
204 testAtomReported->set_state(TestAtomReported_State_OFF);
205 *testAtomReported->mutable_bytes_field() = trainExpIds;
206 *testAtomReported->mutable_repeated_int_field() = {repeatedInts.begin(),
207 repeatedInts.end()};
208 *testAtomReported->mutable_repeated_long_field() = {repeatedLongs.begin(),
209 repeatedLongs.end()};
210 *testAtomReported->mutable_repeated_float_field() = {repeatedFloats.begin(),
211 repeatedFloats.end()};
212 *testAtomReported->mutable_repeated_string_field() = {repeatedStrings.begin(),
213 repeatedStrings.end()};
214 *testAtomReported->mutable_repeated_boolean_field() = {&repeatedBool[0],
215 &repeatedBool[0] + 2};
216 *testAtomReported->mutable_repeated_enum_field() = {repeatedEnums.begin(),
217 repeatedEnums.end()};
218 EXPECT_THAT(actualShellData.atom(1), EqAtom(expectedAtom));
219
220 // Verify atom 3.
221 testAtomReported->set_int_field(2);
222 EXPECT_THAT(actualShellData.atom(2), EqAtom(expectedAtom));
223
224 // Log another ScreenBrightnessChanged atom. No callback should occur.
225 stats_write(SCREEN_BRIGHTNESS_CHANGED, 99);
226 sleep_for(std::chrono::milliseconds(WAIT_MS));
227 EXPECT_EQ(callbackData.count, 1);
228
229 // Flush subscription. Callback should occur.
230 AStatsManager_flushSubscription(subId);
231 sleep_for(std::chrono::milliseconds(WAIT_MS));
232
233 EXPECT_EQ(callbackData.subId, subId);
234 EXPECT_EQ(callbackData.reason, ASTATSMANAGER_SUBSCRIPTION_CALLBACK_REASON_FLUSH_REQUESTED);
235 EXPECT_EQ(callbackData.count, 2);
236 ASSERT_GT(callbackData.payload.size(), 0);
237
238 ASSERT_TRUE(actualShellData.ParseFromArray(callbackData.payload.data(),
239 callbackData.payload.size()));
240
241 ASSERT_GE(actualShellData.elapsed_timestamp_nanos_size(), 1);
242 EXPECT_THAT(actualShellData.elapsed_timestamp_nanos(), Each(Gt(0LL)));
243
244 ASSERT_GE(actualShellData.atom_size(), 1);
245
246 // Verify atom 1.
247 expectedAtom.Clear();
248 expectedAtom.mutable_screen_brightness_changed()->set_level(99);
249 EXPECT_THAT(actualShellData.atom(0), EqAtom(expectedAtom));
250
251 // Log another ScreenBrightnessChanged atom. No callback should occur.
252 stats_write(SCREEN_BRIGHTNESS_CHANGED, 98);
253 sleep_for(std::chrono::milliseconds(WAIT_MS));
254 EXPECT_EQ(callbackData.count, 2);
255
256 // Trigger callback through cache timeout.
257 // Two 500 ms sleeps have occurred already so the total sleep is 71000 ms since last
258 // callback invocation.
259 sleep_for(std::chrono::milliseconds(70'000));
260 EXPECT_EQ(callbackData.subId, subId);
261 EXPECT_EQ(callbackData.reason, ASTATSMANAGER_SUBSCRIPTION_CALLBACK_REASON_STATSD_INITIATED);
262 EXPECT_EQ(callbackData.count, 3);
263 ASSERT_GT(callbackData.payload.size(), 0);
264
265 ASSERT_TRUE(actualShellData.ParseFromArray(callbackData.payload.data(),
266 callbackData.payload.size()));
267
268 ASSERT_GE(actualShellData.elapsed_timestamp_nanos_size(), 1);
269 EXPECT_THAT(actualShellData.elapsed_timestamp_nanos(), Each(Gt(0LL)));
270
271 ASSERT_GE(actualShellData.atom_size(), 1);
272
273 // Verify atom 1.
274 expectedAtom.Clear();
275 expectedAtom.mutable_screen_brightness_changed()->set_level(98);
276 EXPECT_THAT(actualShellData.atom(0), EqAtom(expectedAtom));
277
278 // Log another ScreenBrightnessChanged atom. No callback should occur.
279 stats_write(SCREEN_BRIGHTNESS_CHANGED, 97);
280 sleep_for(std::chrono::milliseconds(WAIT_MS));
281 EXPECT_EQ(callbackData.count, 3);
282
283 // End subscription. Final callback should occur.
284 AStatsManager_removeSubscription(subId);
285 sleep_for(std::chrono::milliseconds(WAIT_MS));
286
287 EXPECT_EQ(callbackData.subId, subId);
288 EXPECT_EQ(callbackData.reason,
289 ASTATSMANAGER_SUBSCRIPTION_CALLBACK_REASON_SUBSCRIPTION_ENDED);
290 EXPECT_EQ(callbackData.count, 4);
291 ASSERT_GT(callbackData.payload.size(), 0);
292
293 ASSERT_TRUE(actualShellData.ParseFromArray(callbackData.payload.data(),
294 callbackData.payload.size()));
295
296 ASSERT_GE(actualShellData.elapsed_timestamp_nanos_size(), 1);
297 EXPECT_THAT(actualShellData.elapsed_timestamp_nanos(), Each(Gt(0LL)));
298
299 ASSERT_GE(actualShellData.atom_size(), 1);
300
301 // Verify atom 1.
302 expectedAtom.Clear();
303 expectedAtom.mutable_screen_brightness_changed()->set_level(97);
304 EXPECT_THAT(actualShellData.atom(0), EqAtom(expectedAtom));
305 } else {
306 GTEST_SKIP();
307 }
308 }
309
310 } // namespace
311
312 #else
313 GTEST_LOG_(INFO) << "This test does nothing.\n";
314 #endif
315