1 /*
2 * Copyright (C) 2017 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 specic language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <algorithm>
18 #include <thread>
19
20 #include <android-base/file.h>
21 #include <android-base/logging.h>
22 #include <android-base/test_utils.h>
23
24 #include <gtest/gtest.h>
25
26 #include "perfmgr/HintManager.h"
27
28 namespace android {
29 namespace perfmgr {
30
31 using namespace std::chrono_literals;
32
33 constexpr auto kSLEEP_TOLERANCE_MS = 50ms;
34
35 // JSON_CONFIG
36 // {
37 // "Nodes": [
38 // {
39 // "Name": "CPUCluster0MinFreq",
40 // "Path": "/sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq",
41 // "Values": [
42 // "1512000",
43 // "1134000",
44 // "384000"
45 // ],
46 // "DefaultIndex": 2,
47 // "ResetOnInit": true
48 // },
49 // {
50 // "Name": "CPUCluster1MinFreq",
51 // "Path": "/sys/devices/system/cpu/cpu4/cpufreq/scaling_min_freq",
52 // "Values": [
53 // "1512000",
54 // "1134000",
55 // "384000"
56 // ],
57 // "HoldFd": true
58 // }
59 // ],
60 // "Actions": [
61 // {
62 // "PowerHint": "INTERACTION",
63 // "Node": "CPUCluster1MinFreq",
64 // "Value": "1134000",
65 // "Duration": 800
66 // },
67 // {
68 // "PowerHint": "LAUNCH",
69 // "Node": "CPUCluster0MinFreq",
70 // "Value": "1134000",
71 // "Duration": 500
72 // },
73 // {
74 // "PowerHint": "LAUNCH",
75 // "Node": "CPUCluster1MinFreq",
76 // "Value": "1512000",
77 // "Duration": 2000
78 // }
79 // ]
80 // }
81 constexpr char kJSON_RAW[] =
82 "{\"Nodes\":[{\"Name\":\"CPUCluster0MinFreq\",\"Path\":\"/sys/devices/"
83 "system/cpu/cpu0/cpufreq/"
84 "scaling_min_freq\",\"Values\":[\"1512000\",\"1134000\",\"384000\"],"
85 "\"DefaultIndex\":2,\"ResetOnInit\":true},{\"Name\":\"CPUCluster1MinFreq\","
86 "\"Path\":\"/sys/devices/system/cpu/cpu4/cpufreq/"
87 "scaling_min_freq\",\"Values\":[\"1512000\",\"1134000\",\"384000\"],"
88 "\"HoldFd\":true}],\"Actions\":[{\"PowerHint\":\"INTERACTION\",\"Node\":"
89 "\"CPUCluster1MinFreq\",\"Value\":\"1134000\",\"Duration\":800},{"
90 "\"PowerHint\":"
91 "\"LAUNCH\",\"Node\":\"CPUCluster0MinFreq\",\"Value\":\"1134000\","
92 "\"Duration\":"
93 "500},{\"PowerHint\":\"LAUNCH\",\"Node\":\"CPUCluster1MinFreq\","
94 "\"Value\":\"1512000\",\"Duration\":2000}]}";
95
96 class HintManagerTest : public ::testing::Test, public HintManager {
97 protected:
HintManagerTest()98 HintManagerTest()
99 : HintManager(nullptr,
100 std::map<std::string, std::vector<NodeAction>>{}) {
101 android::base::SetMinimumLogSeverity(android::base::VERBOSE);
102 }
103
SetUp()104 virtual void SetUp() {
105 // Set up dummy nodes
106 std::unique_ptr<TemporaryFile> tf = std::make_unique<TemporaryFile>();
107 nodes_.emplace_back(
108 new Node("n0", tf->path,
109 {{"n0_value0"}, {"n0_value1"}, {"n0_value2"}}, 2, false));
110 files_.emplace_back(std::move(tf));
111 tf = std::make_unique<TemporaryFile>();
112 nodes_.emplace_back(
113 new Node("n1", tf->path,
114 {{"n1_value0"}, {"n1_value1"}, {"n1_value2"}}, 2, true));
115 files_.emplace_back(std::move(tf));
116 nm_ = new NodeLooperThread(std::move(nodes_));
117 // Set up dummy actions
118 // "INTERACTION"
119 // Node0, value1, 800ms
120 // Node1, value1, forever
121 // "LAUNCH"
122 // Node0, value0, forever
123 // Node1, value0, 400ms
124 actions_ = std::map<std::string, std::vector<NodeAction>>{
125 {"INTERACTION", {{0, 1, 800ms}, {1, 1, 0ms}}},
126 {"LAUNCH", {{0, 0, 0ms}, {1, 0, 400ms}}}};
127
128 // Prepare dummy files to replace the nodes' path in example json_doc
129 files_.emplace_back(std::make_unique<TemporaryFile>());
130 files_.emplace_back(std::make_unique<TemporaryFile>());
131 // replace filepath
132 json_doc_ = kJSON_RAW;
133 std::string from =
134 "/sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq";
135 size_t start_pos = json_doc_.find(from);
136 json_doc_.replace(start_pos, from.length(), files_[0 + 2]->path);
137 from = "/sys/devices/system/cpu/cpu4/cpufreq/scaling_min_freq";
138 start_pos = json_doc_.find(from);
139 json_doc_.replace(start_pos, from.length(), files_[1 + 2]->path);
140 }
141
TearDown()142 virtual void TearDown() {
143 actions_.clear();
144 nodes_.clear();
145 files_.clear();
146 nm_ = nullptr;
147 }
148 sp<NodeLooperThread> nm_;
149 std::map<std::string, std::vector<NodeAction>> actions_;
150 std::vector<std::unique_ptr<Node>> nodes_;
151 std::vector<std::unique_ptr<TemporaryFile>> files_;
152 std::string json_doc_;
153 };
154
_VerifyPathValue(const std::string & path,const std::string & value)155 static inline void _VerifyPathValue(const std::string& path,
156 const std::string& value) {
157 std::string s;
158 EXPECT_TRUE(android::base::ReadFileToString(path, &s)) << strerror(errno);
159 EXPECT_EQ(value, s);
160 }
161
162 // Test GetHints
TEST_F(HintManagerTest,GetHintsTest)163 TEST_F(HintManagerTest, GetHintsTest) {
164 HintManager hm(nm_, actions_);
165 std::vector<std::string> hints = hm.GetHints();
166 EXPECT_TRUE(hm.IsRunning());
167 EXPECT_EQ(2u, hints.size());
168 EXPECT_NE(std::find(hints.begin(), hints.end(), "INTERACTION"), hints.end());
169 EXPECT_NE(std::find(hints.begin(), hints.end(), "LAUNCH"), hints.end());
170 }
171
172 // Test initialization of default values
TEST_F(HintManagerTest,HintInitDefaultTest)173 TEST_F(HintManagerTest, HintInitDefaultTest) {
174 HintManager hm(nm_, actions_);
175 std::this_thread::sleep_for(kSLEEP_TOLERANCE_MS);
176 EXPECT_TRUE(hm.IsRunning());
177 _VerifyPathValue(files_[0]->path, "");
178 _VerifyPathValue(files_[1]->path, "n1_value2");
179 }
180
181 // Test hint/cancel/expire
TEST_F(HintManagerTest,HintTest)182 TEST_F(HintManagerTest, HintTest) {
183 HintManager hm(nm_, actions_);
184 EXPECT_TRUE(hm.IsRunning());
185 EXPECT_TRUE(hm.DoHint("INTERACTION"));
186 std::this_thread::sleep_for(kSLEEP_TOLERANCE_MS);
187 _VerifyPathValue(files_[0]->path, "n0_value1");
188 _VerifyPathValue(files_[1]->path, "n1_value1");
189 // this won't change the expire time of INTERACTION hint
190 EXPECT_TRUE(hm.DoHint("INTERACTION", 200ms));
191 // now place new hint
192 EXPECT_TRUE(hm.DoHint("LAUNCH"));
193 std::this_thread::sleep_for(kSLEEP_TOLERANCE_MS);
194 _VerifyPathValue(files_[0]->path, "n0_value0");
195 _VerifyPathValue(files_[1]->path, "n1_value0");
196 EXPECT_TRUE(hm.DoHint("LAUNCH", 500ms));
197 // no"LAUNCH" node1 not expired
198 std::this_thread::sleep_for(400ms);
199 _VerifyPathValue(files_[0]->path, "n0_value0");
200 _VerifyPathValue(files_[1]->path, "n1_value0");
201 // "LAUNCH" node1 expired
202 std::this_thread::sleep_for(100ms + kSLEEP_TOLERANCE_MS);
203 _VerifyPathValue(files_[0]->path, "n0_value0");
204 _VerifyPathValue(files_[1]->path, "n1_value1");
205 EXPECT_TRUE(hm.EndHint("LAUNCH"));
206 std::this_thread::sleep_for(kSLEEP_TOLERANCE_MS);
207 // "LAUNCH" canceled
208 _VerifyPathValue(files_[0]->path, "n0_value1");
209 _VerifyPathValue(files_[1]->path, "n1_value1");
210 std::this_thread::sleep_for(200ms);
211 // "INTERACTION" node0 expired
212 _VerifyPathValue(files_[0]->path, "n0_value2");
213 _VerifyPathValue(files_[1]->path, "n1_value1");
214 EXPECT_TRUE(hm.EndHint("INTERACTION"));
215 std::this_thread::sleep_for(kSLEEP_TOLERANCE_MS);
216 // "INTERACTION" canceled
217 _VerifyPathValue(files_[0]->path, "n0_value2");
218 _VerifyPathValue(files_[1]->path, "n1_value2");
219 }
220
221 // Test parsing nodes with duplicate name
TEST_F(HintManagerTest,ParseNodesTest)222 TEST_F(HintManagerTest, ParseNodesTest) {
223 std::vector<std::unique_ptr<Node>> nodes =
224 HintManager::ParseNodes(json_doc_);
225 EXPECT_EQ(2u, nodes.size());
226 EXPECT_EQ("CPUCluster0MinFreq", nodes[0]->GetName());
227 EXPECT_EQ("CPUCluster1MinFreq", nodes[1]->GetName());
228 EXPECT_EQ(files_[0 + 2]->path, nodes[0]->GetPath());
229 EXPECT_EQ(files_[1 + 2]->path, nodes[1]->GetPath());
230 EXPECT_EQ("1512000", nodes[0]->GetValues()[0]);
231 EXPECT_EQ("1134000", nodes[0]->GetValues()[1]);
232 EXPECT_EQ("384000", nodes[0]->GetValues()[2]);
233 EXPECT_EQ("1512000", nodes[1]->GetValues()[0]);
234 EXPECT_EQ("1134000", nodes[1]->GetValues()[1]);
235 EXPECT_EQ("384000", nodes[1]->GetValues()[2]);
236 EXPECT_EQ(2u, nodes[0]->GetDefaultIndex());
237 EXPECT_EQ(2u, nodes[1]->GetDefaultIndex());
238 EXPECT_TRUE(nodes[0]->GetResetOnInit());
239 EXPECT_FALSE(nodes[1]->GetResetOnInit());
240 EXPECT_FALSE(nodes[0]->GetHoldFd());
241 EXPECT_TRUE(nodes[1]->GetHoldFd());
242 }
243
244 // Test parsing actions
TEST_F(HintManagerTest,ParseNodesDuplicateNameTest)245 TEST_F(HintManagerTest, ParseNodesDuplicateNameTest) {
246 std::string from = "CPUCluster0MinFreq";
247 size_t start_pos = json_doc_.find(from);
248 json_doc_.replace(start_pos, from.length(), "CPUCluster1MinFreq");
249 std::vector<std::unique_ptr<Node>> nodes =
250 HintManager::ParseNodes(json_doc_);
251 EXPECT_EQ(0u, nodes.size());
252 }
253
254 // Test parsing nodes with duplicate path
TEST_F(HintManagerTest,ParseNodesDuplicatePathTest)255 TEST_F(HintManagerTest, ParseNodesDuplicatePathTest) {
256 std::string from = files_[0 + 2]->path;
257 size_t start_pos = json_doc_.find(from);
258 json_doc_.replace(start_pos, from.length(), files_[1 + 2]->path);
259 std::vector<std::unique_ptr<Node>> nodes =
260 HintManager::ParseNodes(json_doc_);
261 EXPECT_EQ(0u, nodes.size());
262 }
263
264 // Test parsing invalid json for nodes
TEST_F(HintManagerTest,ParseBadNodesTest)265 TEST_F(HintManagerTest, ParseBadNodesTest) {
266 std::vector<std::unique_ptr<Node>> nodes =
267 HintManager::ParseNodes("invalid json");
268 EXPECT_EQ(0u, nodes.size());
269 nodes = HintManager::ParseNodes(
270 "{\"devices\":{\"15\":[\"armeabi-v7a\"],\"16\":[\"armeabi-v7a\"],"
271 "\"26\":[\"armeabi-v7a\",\"arm64-v8a\",\"x86\",\"x86_64\"]}}");
272 EXPECT_EQ(0u, nodes.size());
273 }
274
275 // Test parsing actions
TEST_F(HintManagerTest,ParseActionsTest)276 TEST_F(HintManagerTest, ParseActionsTest) {
277 std::vector<std::unique_ptr<Node>> nodes =
278 HintManager::ParseNodes(json_doc_);
279 std::map<std::string, std::vector<NodeAction>> actions =
280 HintManager::ParseActions(json_doc_, nodes);
281 EXPECT_EQ(2u, actions.size());
282 EXPECT_EQ(1u, actions["INTERACTION"].size());
283
284 EXPECT_EQ(1u, actions["INTERACTION"][0].node_index);
285 EXPECT_EQ(1u, actions["INTERACTION"][0].value_index);
286 EXPECT_EQ(std::chrono::milliseconds(800).count(),
287 actions["INTERACTION"][0].timeout_ms.count());
288
289 EXPECT_EQ(2u, actions["LAUNCH"].size());
290
291 EXPECT_EQ(0u, actions["LAUNCH"][0].node_index);
292 EXPECT_EQ(1u, actions["LAUNCH"][0].value_index);
293 EXPECT_EQ(std::chrono::milliseconds(500).count(),
294 actions["LAUNCH"][0].timeout_ms.count());
295
296 EXPECT_EQ(1u, actions["LAUNCH"][1].node_index);
297 EXPECT_EQ(0u, actions["LAUNCH"][1].value_index);
298 EXPECT_EQ(std::chrono::milliseconds(2000).count(),
299 actions["LAUNCH"][1].timeout_ms.count());
300 }
301
302 // Test parsing actions with duplicate node
TEST_F(HintManagerTest,ParseActionDuplicateNodeTest)303 TEST_F(HintManagerTest, ParseActionDuplicateNodeTest) {
304 std::string from = "\"Node\":\"CPUCluster0MinFreq\"";
305 size_t start_pos = json_doc_.find(from);
306 json_doc_.replace(start_pos, from.length(),
307 "\"Node\": \"CPUCluster1MinFreq\"");
308 std::vector<std::unique_ptr<Node>> nodes =
309 HintManager::ParseNodes(json_doc_);
310 EXPECT_EQ(2u, nodes.size());
311 std::map<std::string, std::vector<NodeAction>> actions =
312 HintManager::ParseActions(json_doc_, nodes);
313 EXPECT_EQ(0u, actions.size());
314 }
315
316 // Test parsing invalid json for actions
TEST_F(HintManagerTest,ParseBadActionsTest)317 TEST_F(HintManagerTest, ParseBadActionsTest) {
318 std::vector<std::unique_ptr<Node>> nodes =
319 HintManager::ParseNodes(json_doc_);
320 std::map<std::string, std::vector<NodeAction>> actions =
321 HintManager::ParseActions("invalid json", nodes);
322 EXPECT_EQ(0u, actions.size());
323 actions = HintManager::ParseActions(
324 "{\"devices\":{\"15\":[\"armeabi-v7a\"],\"16\":[\"armeabi-v7a\"],"
325 "\"26\":[\"armeabi-v7a\",\"arm64-v8a\",\"x86\",\"x86_64\"]}}",
326 nodes);
327 EXPECT_EQ(0u, actions.size());
328 }
329
330 // Test hint/cancel/expire with json config
TEST_F(HintManagerTest,GetFromJSONTest)331 TEST_F(HintManagerTest, GetFromJSONTest) {
332 TemporaryFile json_file;
333 ASSERT_TRUE(android::base::WriteStringToFile(json_doc_, json_file.path))
334 << strerror(errno);
335 std::unique_ptr<HintManager> hm = HintManager::GetFromJSON(json_file.path);
336 EXPECT_NE(nullptr, hm.get());
337 std::this_thread::sleep_for(kSLEEP_TOLERANCE_MS);
338 EXPECT_TRUE(hm->IsRunning());
339 // Initial default value on Node0
340 _VerifyPathValue(files_[0 + 2]->path, "384000");
341 _VerifyPathValue(files_[1 + 2]->path, "");
342 // Do INTERACTION
343 EXPECT_TRUE(hm->DoHint("INTERACTION"));
344 std::this_thread::sleep_for(kSLEEP_TOLERANCE_MS);
345 _VerifyPathValue(files_[0 + 2]->path, "384000");
346 _VerifyPathValue(files_[1 + 2]->path, "1134000");
347 // Do LAUNCH
348 EXPECT_TRUE(hm->DoHint("LAUNCH"));
349 std::this_thread::sleep_for(kSLEEP_TOLERANCE_MS);
350 _VerifyPathValue(files_[0 + 2]->path, "1134000");
351 _VerifyPathValue(files_[1 + 2]->path, "1512000");
352 std::this_thread::sleep_for(500ms);
353 // "LAUNCH" node0 expired
354 _VerifyPathValue(files_[0 + 2]->path, "384000");
355 _VerifyPathValue(files_[1 + 2]->path, "1512000");
356 EXPECT_TRUE(hm->EndHint("LAUNCH"));
357 std::this_thread::sleep_for(kSLEEP_TOLERANCE_MS);
358 // "LAUNCH" canceled
359 _VerifyPathValue(files_[0 + 2]->path, "384000");
360 _VerifyPathValue(files_[1 + 2]->path, "1134000");
361 std::this_thread::sleep_for(300ms);
362 // "INTERACTION" node1 expired
363 _VerifyPathValue(files_[0 + 2]->path, "384000");
364 _VerifyPathValue(files_[1 + 2]->path, "384000");
365 }
366
367 } // namespace perfmgr
368 } // namespace android
369