1 /*
2 * Copyright (c) 2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "memmgr_log.h"
17 #include "memmgr_ptr_util.h"
18 #include "parameters.h"
19 #include "kernel_interface.h"
20 #include "nandlife_controller.h"
21
22 namespace OHOS {
23 namespace Memory {
24 namespace {
25 const std::string TAG = "NandLifeController";
26
27 constexpr int TIMER_PEROID_MIN = 15;
28 constexpr int TIMER_PEROID_MS = TIMER_PEROID_MIN * 60 * 1000;
29
30 const std::string PARAM_VALUE_ZERO = "0";
31 const std::string PARAM_VALUE_ONE = "1";
32 const std::string PARAM_VALUE_UNKOWN = "-1";
33
34 const std::string PERMANENTLY_CLOSED_STATUS_PARAM = "persist.resourceschedule.memmgr.eswap.permanently.closed";
35 const std::string PERMANENTLY_CLOSED = PARAM_VALUE_ONE;
36 const std::string NOT_PERMANENTLY_CLOSED = PARAM_VALUE_ZERO;
37
38 const std::string MINS_TODAY_PARAM = "persist.resourceschedule.memmgr.eswap.minsToday";
39
40 const std::string SWAP_OUT_KB_TODAY_PARAM = "persist.resourceschedule.memmgr.eswap.swapOutKBToday";
41
42 const std::string MINS_FROM_BIRTH_PARAM = "persist.resourceschedule.memmgr.eswap.minsFromBirth";
43
44 const std::string SWAP_OUT_KB_FROM_BIRTH_PARAM = "persist.resourceschedule.memmgr.eswap.swapOutKBFromBirth";
45
46 const std::string params[] = {
47 PERMANENTLY_CLOSED_STATUS_PARAM,
48 MINS_TODAY_PARAM,
49 SWAP_OUT_KB_TODAY_PARAM,
50 MINS_FROM_BIRTH_PARAM,
51 SWAP_OUT_KB_FROM_BIRTH_PARAM,
52 };
53
54 const std::string PSI_HEALTH_INFO_PATH = "/dev/memcg/memory.eswap_info";
55 const std::string SWAP_OUT_SIZE_TAG = "Total Swapout Size";
56
57 const std::string ESWAP_ENABLE_PATH = "/proc/sys/kernel/hyperhold/enable";
58 const std::string ENABLE_ESWAP = "enable";
59 const std::string DISABLE_ESWAP = "disable";
60
61 constexpr int RETRY_TIMES = 3;
62 }
63
64 IMPLEMENT_SINGLE_INSTANCE(NandLifeController);
65
NandLifeController()66 NandLifeController::NandLifeController()
67 {
68 timerFunc_ = std::bind(&NandLifeController::CheckSwapOut, this);
69 }
70
Init()71 bool NandLifeController::Init()
72 {
73 if (!GetEventHandler()) {
74 CloseSwapOutTemporarily("init handler failed, nandlife controller cannot set timmer");
75 return false;
76 }
77 HILOGI("init handler successed");
78
79 // read nandlife config from xml, then check and set it.
80 // if the config does not meet the requirements, eswap will be closed temporarily.
81 if (!GetAndValidateNandLifeConfig()) {
82 CloseSwapOutTemporarily("get or validate nandlife config failed, controller will not work properly.");
83 return false;
84 }
85 HILOGI("get and validate nandlife config success. dailyQuotaMB=%{public}llu, totalQuotaMB=%{public}llu",
86 config_.GetDailySwapOutQuotaMb(), config_.GetTotalSwapOutQuotaMb());
87 if (config_.GetDailySwapOutQuotaMb() == 0 && config_.GetTotalSwapOutQuotaMb() == 0) {
88 HILOGE("will not limit swap-out!");
89 OpenSwapOutPermanently();
90 OpenSwapOutTemporarily("not limit swap-out in xml");
91 return true;
92 } else {
93 DAILY_SWAP_OUT_QUOTA_KB = config_.GetDailySwapOutQuotaMb() * 1024; // 1024: MB to KB
94 TOTAL_SWAP_OUT_QUOTA_KB = config_.GetTotalSwapOutQuotaMb() * 1024; // 1024: MB to KB
95 }
96
97 if (!LoadNandLifeParam()) {
98 CloseSwapOutTemporarily("load nandlife info file failed, controller will not work properly.");
99 return false;
100 }
101 HILOGI("load nandlife sys param success");
102
103 PrintNandLifeParam();
104
105 if (IsSwapOutClosedPermently()) {
106 CloseSwapOutTemporarily("swap-out has benn closed permently, nandlife controller no need work!");
107 return false;
108 }
109
110 unsigned long long swapOutKBSinceKernelBoot = 0;
111 if (GetSwapOutKBSinceKernelBoot(swapOutKBSinceKernelBoot)) {
112 HILOGI("swapOutKBSinceKernelBoot=%{public}llu KB", swapOutKBSinceKernelBoot);
113 lastSwapOutKB_ = swapOutKBSinceKernelBoot;
114 nowSwapOutKB_ = swapOutKBSinceKernelBoot;
115 } else {
116 CloseSwapOutTemporarily("invalid swapOutKBSinceKernelBoot");
117 return false;
118 }
119
120 // start check loop
121 SetTimer();
122
123 // check limit
124 if (CheckReachedTotalLimit() || CheckReachedDailyLimit()) {
125 return false;
126 }
127
128 OpenSwapOutTemporarily("pass all check when init");
129 return true;
130 }
131
132 // may throw exception due to number format
ReadUnsignedLongLongParam(const std::string & paramName)133 unsigned long long ReadUnsignedLongLongParam(const std::string ¶mName)
134 {
135 std::string value = system::GetParameter(paramName, PARAM_VALUE_UNKOWN);
136 if (value == PARAM_VALUE_UNKOWN) {
137 HILOGI("param <%{public}s> not set", paramName.c_str());
138 }
139 return std::strtoull(value.c_str(), nullptr, 10); // 10:Decimal
140 }
141
LoadNandLifeParam()142 bool NandLifeController::LoadNandLifeParam()
143 {
144 minsToday_ = ReadUnsignedLongLongParam(MINS_TODAY_PARAM);
145 if (errno == ERANGE || minsToday_ == ULLONG_MAX) {
146 HILOGI("[%{public}llu] invalid value of minsToday_", iter_);
147 return false;
148 } else {
149 HILOGI("[%{public}llu] minsToday_=%{public}llu", iter_, minsToday_);
150 }
151
152 swapOutKBToday_ = ReadUnsignedLongLongParam(SWAP_OUT_KB_TODAY_PARAM);
153 if (errno == ERANGE || swapOutKBToday_ == ULLONG_MAX) {
154 HILOGI("[%{public}llu] invalid value of swapOutKBToday_", iter_);
155 return false;
156 } else {
157 HILOGI("[%{public}llu] swapOutKBToday_=%{public}llu", iter_, swapOutKBToday_);
158 }
159
160 minsSinceBirth_ = ReadUnsignedLongLongParam(MINS_FROM_BIRTH_PARAM);
161 if (errno == ERANGE || minsSinceBirth_ == ULLONG_MAX) {
162 HILOGI("[%{public}llu] invalid value of minsSinceBirth_", iter_);
163 return false;
164 } else {
165 HILOGI("[%{public}llu] minsSinceBirth_=%{public}llu", iter_, minsSinceBirth_);
166 }
167
168 swapOutKBSinceBirth_ = ReadUnsignedLongLongParam(SWAP_OUT_KB_FROM_BIRTH_PARAM);
169 if (errno == ERANGE || swapOutKBSinceBirth_ == ULLONG_MAX) {
170 HILOGI("[%{public}llu] invalid value of swapOutKBSinceBirth_", iter_);
171 return false;
172 } else {
173 HILOGI("[%{public}llu] swapOutKBSinceBirth_=%{public}llu", iter_, swapOutKBSinceBirth_);
174 }
175
176 return true;
177 }
178
PrintNandLifeParam()179 void NandLifeController::PrintNandLifeParam()
180 {
181 HILOGI("[%{public}llu] begin print nandlife param-------------", iter_);
182 for (auto param : params) {
183 HILOGI("[%{public}llu] %{public}s=%{public}s", iter_, param.c_str(),
184 system::GetParameter(param, PARAM_VALUE_UNKOWN).c_str());
185 }
186 HILOGI("[%{public}llu] end print nandlife param --------------", iter_);
187 }
188
IsSwapOutClosedPermently()189 bool NandLifeController::IsSwapOutClosedPermently()
190 {
191 return system::GetParameter(PERMANENTLY_CLOSED_STATUS_PARAM, PARAM_VALUE_UNKOWN) == PERMANENTLY_CLOSED;
192 }
193
GetAndValidateNandLifeConfig()194 bool NandLifeController::GetAndValidateNandLifeConfig()
195 {
196 config_ = MemmgrConfigManager::GetInstance().GetNandLifeConfig();
197 return config_.GetDailySwapOutQuotaMb() >= 0 && config_.GetTotalSwapOutQuotaMb() >=0;
198 }
199
GetEventHandler()200 bool NandLifeController::GetEventHandler()
201 {
202 if (handler_ == nullptr) {
203 MAKE_POINTER(handler_, shared, AppExecFwk::EventHandler, "failed to create event handler", return false,
204 AppExecFwk::EventRunner::Create());
205 }
206 return true;
207 }
208
GetSwapOutKBSinceKernelBoot(unsigned long long & ret)209 bool NandLifeController::GetSwapOutKBSinceKernelBoot(unsigned long long &ret)
210 {
211 for (auto i = 0; i < RETRY_TIMES; i ++) {
212 if (KernelInterface::GetInstance().ReadSwapOutKBSinceKernelBoot(PSI_HEALTH_INFO_PATH, SWAP_OUT_SIZE_TAG, ret)) {
213 return true;
214 }
215 }
216 return false;
217 }
218
SetTimer()219 void NandLifeController::SetTimer()
220 {
221 // set timer and call CheckSwapOut each TIMER_PEROID_MIN min.
222 handler_->PostTask(timerFunc_, TIMER_PEROID_MS, AppExecFwk::EventQueue::Priority::HIGH);
223 HILOGI("[%{public}llu] set timer after %{public}d mins", iter_, TIMER_PEROID_MIN);
224 }
225
CheckReachedDailyLimit()226 bool NandLifeController::CheckReachedDailyLimit()
227 {
228 bool reachedDailyLimit = swapOutKBToday_ >= DAILY_SWAP_OUT_QUOTA_KB;
229 HILOGI("[%{public}llu] swapOutKBToday_(%{public}llu) %{public}s DAILY_SWAP_OUT_QUOTA_KB(%{public}llu)",
230 iter_, swapOutKBToday_, (reachedDailyLimit ? ">=" : "<"), DAILY_SWAP_OUT_QUOTA_KB);
231 if (reachedDailyLimit) {
232 CloseSwapOutTemporarily("reach daily limit, close swap-out temporarily!");
233 } else {
234 HILOGI("[%{public}llu] unreach daily limit, swap-out is still opened!", iter_);
235 }
236 return reachedDailyLimit;
237 }
238
CheckReachedTotalLimit()239 bool NandLifeController::CheckReachedTotalLimit()
240 {
241 bool reachedTotalLimit = swapOutKBSinceBirth_ >= TOTAL_SWAP_OUT_QUOTA_KB;
242 HILOGI("[%{public}llu] swapOutKBSinceBirth_(%{public}llu) %{public}s TOTAL_SWAP_OUT_QUOTA_KB(%{public}llu)",
243 iter_, swapOutKBSinceBirth_, (reachedTotalLimit ? ">=" : "<"), TOTAL_SWAP_OUT_QUOTA_KB);
244 if (reachedTotalLimit) {
245 HILOGE("[%{public}llu] reached total limit, close swap-out forever!", iter_);
246 CloseSwapOutPermanently();
247 } else {
248 HILOGI("[%{public}llu] unreach total limit!", iter_);
249 }
250 return reachedTotalLimit;
251 }
252
CheckSwapOut()253 void NandLifeController::CheckSwapOut()
254 {
255 ++iter_;
256
257 HILOGE("[%{public}llu] called", iter_);
258
259 if (IsSwapOutClosedPermently()) {
260 CloseSwapOutTemporarily("swap-out has benn closed permently!");
261 SetTimer();
262 return;
263 }
264
265 PrintNandLifeParam();
266
267 minsToday_ += TIMER_PEROID_MIN;
268 minsSinceBirth_ += TIMER_PEROID_MIN;
269
270 if (GetSwapOutKBSinceKernelBoot(nowSwapOutKB_)) {
271 HILOGI("[%{public}llu] swapOutKBSinceKernelBoot=%{public}llu KB", iter_, nowSwapOutKB_);
272 } else {
273 CloseSwapOutTemporarily("invalid swapOutKBSinceKernelBoot");
274 SetTimer();
275 return;
276 }
277 if (nowSwapOutKB_ < lastSwapOutKB_) {
278 CloseSwapOutTemporarily("deltaSwapOutMB < 0");
279 SetTimer();
280 return;
281 }
282 unsigned long long increasedSwapOutKB = nowSwapOutKB_ - lastSwapOutKB_;
283 HILOGE("[%{public}llu] lastSwapOutKB_=%{public}llu, nowSwapOutKB_=%{public}llu, increasedSwapOutKB=%{public}llu",
284 iter_, lastSwapOutKB_, nowSwapOutKB_, increasedSwapOutKB);
285 lastSwapOutKB_ = nowSwapOutKB_;
286 swapOutKBToday_ += increasedSwapOutKB;
287 swapOutKBSinceBirth_ += increasedSwapOutKB;
288
289 CheckReachedDailyLimit();
290
291 if (minsToday_ >= 24 * 60) { // 24: a day has 24 hours, 60: one hour has 60 min
292 HILOGI("[%{public}llu] enter a new day", iter_);
293 minsToday_ = 0;
294 swapOutKBToday_ = 0;
295 if (swapOutKBSinceBirth_ < TOTAL_SWAP_OUT_QUOTA_KB) { // swap-out is allowed
296 HILOGI("[%{public}llu] open swap-out since a new day", iter_);
297 OpenSwapOutTemporarily("enter a new day");
298 }
299 }
300
301 if (!UpdateNandLifeParam()) {
302 CloseSwapOutTemporarily("UpdateNandLifeParam failed!");
303 }
304
305 PrintNandLifeParam();
306
307 CheckReachedTotalLimit();
308
309 // set next timer
310 SetTimer();
311 }
312
SetParameterRetry(const std::string & paramName,const std::string & paramValue,int retryTimes)313 bool NandLifeController::SetParameterRetry(const std::string ¶mName, const std::string ¶mValue, int retryTimes)
314 {
315 for (auto i = 0; i < retryTimes; i++) {
316 if (system::SetParameter(paramName, paramValue)) {
317 return true;
318 }
319 }
320 HILOGW("[%{public}llu] set [%{public}s] to [%{public}s] failed!", iter_, paramName.c_str(), paramValue.c_str());
321 return false;
322 }
323
UpdateNandLifeParam()324 bool NandLifeController::UpdateNandLifeParam()
325 {
326 if (!SetParameterRetry(MINS_TODAY_PARAM, std::to_string(minsToday_), RETRY_TIMES)) {
327 return false;
328 }
329 if (!SetParameterRetry(SWAP_OUT_KB_TODAY_PARAM, std::to_string(swapOutKBToday_), RETRY_TIMES)) {
330 return false;
331 }
332 if (!SetParameterRetry(MINS_FROM_BIRTH_PARAM, std::to_string(minsSinceBirth_), RETRY_TIMES)) {
333 return false;
334 }
335 if (!SetParameterRetry(SWAP_OUT_KB_FROM_BIRTH_PARAM, std::to_string(swapOutKBSinceBirth_), RETRY_TIMES)) {
336 return false;
337 }
338 HILOGW("[%{public}llu] all success!", iter_);
339 return true;
340 }
341
OpenSwapOutTemporarily(const std::string & reason)342 void NandLifeController::OpenSwapOutTemporarily(const std::string &reason)
343 {
344 HILOGW("[%{public}llu] %{public}s", iter_, reason.c_str());
345 for (auto i = 0; i < RETRY_TIMES; i++) {
346 if (KernelInterface::GetInstance().EchoToPath(ESWAP_ENABLE_PATH.c_str(), ENABLE_ESWAP.c_str())) {
347 HILOGI("[%{public}llu] open eswap temporarily success!", iter_);
348 return;
349 }
350 }
351 HILOGW("[%{public}llu] open eswap temporarily failed!", iter_);
352 }
353
CloseSwapOutTemporarily(const std::string & reason)354 void NandLifeController::CloseSwapOutTemporarily(const std::string &reason)
355 {
356 HILOGW("[%{public}llu] %{public}s", iter_, reason.c_str());
357 for (auto i = 0; i < RETRY_TIMES; i++) {
358 if (KernelInterface::GetInstance().EchoToPath(ESWAP_ENABLE_PATH.c_str(), DISABLE_ESWAP.c_str())) {
359 HILOGW("[%{public}llu] clsoe eswap temporarily success!", iter_);
360 return;
361 }
362 }
363 HILOGW("[%{public}llu] close eswap temporarily failed!", iter_);
364 }
365
OpenSwapOutPermanently()366 void NandLifeController::OpenSwapOutPermanently()
367 {
368 bool ret = SetParameterRetry(PERMANENTLY_CLOSED_STATUS_PARAM, NOT_PERMANENTLY_CLOSED, RETRY_TIMES);
369 HILOGW("[%{public}llu] open eswap permanently %{public}s!", iter_, ret ? "success" : "failed");
370 }
371
CloseSwapOutPermanently()372 void NandLifeController::CloseSwapOutPermanently()
373 {
374 CloseSwapOutTemporarily("CloseSwapOutPermanently close eswap temporarily first!");
375 bool ret = SetParameterRetry(PERMANENTLY_CLOSED_STATUS_PARAM, PERMANENTLY_CLOSED, RETRY_TIMES);
376 HILOGW("[%{public}llu] close eswap permanently %{public}s!", iter_, ret ? "success" : "failed");
377 }
378 } // namespace Memory
379 } // namespace OHOS
380