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 }
69 
Init()70 bool NandLifeController::Init()
71 {
72     if (!GetEventHandler()) {
73         CloseSwapOutTemporarily("init handler failed, nandlife controller cannot set timmer");
74         return false;
75     }
76     HILOGI("init handler successed");
77 
78     // read nandlife config from xml, then check and set it.
79     // if the config does not meet the requirements, eswap will be closed temporarily.
80     if (!GetAndValidateNandLifeConfig()) {
81         CloseSwapOutTemporarily("get or validate nandlife config failed, controller will not work properly.");
82         return false;
83     }
84     HILOGI("get and validate nandlife config success. dailyQuotaMB=%{public}llu, totalQuotaMB=%{public}llu",
85         config_.GetDailySwapOutQuotaMb(), config_.GetTotalSwapOutQuotaMb());
86     if (config_.GetDailySwapOutQuotaMb() == 0 && config_.GetTotalSwapOutQuotaMb() == 0) {
87         HILOGE("will not limit swap-out!");
88         OpenSwapOutPermanently();
89         OpenSwapOutTemporarily("not limit swap-out in xml");
90         return true;
91     } else {
92         DAILY_SWAP_OUT_QUOTA_KB = config_.GetDailySwapOutQuotaMb() * 1024; // 1024: MB to KB
93         TOTAL_SWAP_OUT_QUOTA_KB = config_.GetTotalSwapOutQuotaMb() * 1024; // 1024: MB to KB
94     }
95 
96     if (!LoadNandLifeParam()) {
97         CloseSwapOutTemporarily("load nandlife info file failed, controller will not work properly.");
98         return false;
99     }
100     HILOGI("load nandlife sys param success");
101 
102     PrintNandLifeParam();
103 
104     if (IsSwapOutClosedPermently()) {
105         CloseSwapOutTemporarily("swap-out has benn closed permently, nandlife controller no need work!");
106         return false;
107     }
108 
109     unsigned long long swapOutKBSinceKernelBoot = 0;
110     if (GetSwapOutKBSinceKernelBoot(swapOutKBSinceKernelBoot)) {
111         HILOGI("swapOutKBSinceKernelBoot=%{public}llu KB", swapOutKBSinceKernelBoot);
112         lastSwapOutKB_ = swapOutKBSinceKernelBoot;
113         nowSwapOutKB_ = swapOutKBSinceKernelBoot;
114     } else {
115         CloseSwapOutTemporarily("invalid swapOutKBSinceKernelBoot");
116         return false;
117     }
118 
119     // start check loop
120     SetTimer();
121 
122     // check limit
123     if (CheckReachedTotalLimit() || CheckReachedDailyLimit()) {
124         return false;
125     }
126 
127     OpenSwapOutTemporarily("pass all check when init");
128     return true;
129 }
130 
131 // may throw exception due to number format
ReadUnsignedLongLongParam(const std::string & paramName)132 unsigned long long ReadUnsignedLongLongParam(const std::string &paramName)
133 {
134     std::string value = system::GetParameter(paramName, PARAM_VALUE_UNKOWN);
135     if (value == PARAM_VALUE_UNKOWN) {
136         HILOGI("param <%{public}s> not set", paramName.c_str());
137     }
138     return std::strtoull(value.c_str(), nullptr, 10); // 10:Decimal
139 }
140 
LoadNandLifeParam()141 bool NandLifeController::LoadNandLifeParam()
142 {
143     minsToday_ = ReadUnsignedLongLongParam(MINS_TODAY_PARAM);
144     if (errno == ERANGE || minsToday_ == ULLONG_MAX) {
145         HILOGI("[%{public}llu] invalid value of minsToday_", iter_);
146         return false;
147     } else {
148         HILOGI("[%{public}llu] minsToday_=%{public}llu", iter_, minsToday_);
149     }
150 
151     swapOutKBToday_ = ReadUnsignedLongLongParam(SWAP_OUT_KB_TODAY_PARAM);
152     if (errno == ERANGE || swapOutKBToday_ == ULLONG_MAX) {
153         HILOGI("[%{public}llu] invalid value of swapOutKBToday_", iter_);
154         return false;
155     } else {
156         HILOGI("[%{public}llu] swapOutKBToday_=%{public}llu", iter_, swapOutKBToday_);
157     }
158 
159     minsSinceBirth_ = ReadUnsignedLongLongParam(MINS_FROM_BIRTH_PARAM);
160     if (errno == ERANGE || minsSinceBirth_ == ULLONG_MAX) {
161         HILOGI("[%{public}llu] invalid value of minsSinceBirth_", iter_);
162         return false;
163     } else {
164         HILOGI("[%{public}llu] minsSinceBirth_=%{public}llu", iter_, minsSinceBirth_);
165     }
166 
167     swapOutKBSinceBirth_ = ReadUnsignedLongLongParam(SWAP_OUT_KB_FROM_BIRTH_PARAM);
168     if (errno == ERANGE || swapOutKBSinceBirth_ == ULLONG_MAX) {
169         HILOGI("[%{public}llu] invalid value of swapOutKBSinceBirth_", iter_);
170         return false;
171     } else {
172         HILOGI("[%{public}llu] swapOutKBSinceBirth_=%{public}llu", iter_, swapOutKBSinceBirth_);
173     }
174 
175     return true;
176 }
177 
PrintNandLifeParam()178 void NandLifeController::PrintNandLifeParam()
179 {
180     HILOGI("[%{public}llu] begin print nandlife param-------------", iter_);
181     for (auto param : params) {
182         HILOGI("[%{public}llu] %{public}s=%{public}s", iter_, param.c_str(),
183             system::GetParameter(param, PARAM_VALUE_UNKOWN).c_str());
184     }
185     HILOGI("[%{public}llu] end print nandlife param --------------", iter_);
186 }
187 
IsSwapOutClosedPermently()188 bool NandLifeController::IsSwapOutClosedPermently()
189 {
190     return system::GetParameter(PERMANENTLY_CLOSED_STATUS_PARAM, PARAM_VALUE_UNKOWN) == PERMANENTLY_CLOSED;
191 }
192 
GetAndValidateNandLifeConfig()193 bool NandLifeController::GetAndValidateNandLifeConfig()
194 {
195     config_ = MemmgrConfigManager::GetInstance().GetNandLifeConfig();
196     return config_.GetDailySwapOutQuotaMb() >= 0 && config_.GetTotalSwapOutQuotaMb() >=0;
197 }
198 
GetEventHandler()199 bool NandLifeController::GetEventHandler()
200 {
201     if (handler_ == nullptr) {
202         MAKE_POINTER(handler_, shared, AppExecFwk::EventHandler, "failed to create event handler", return false,
203             AppExecFwk::EventRunner::Create());
204     }
205     return true;
206 }
207 
GetSwapOutKBSinceKernelBoot(unsigned long long & ret)208 bool NandLifeController::GetSwapOutKBSinceKernelBoot(unsigned long long &ret)
209 {
210     for (auto i = 0; i < RETRY_TIMES; i ++) {
211         if (KernelInterface::GetInstance().ReadSwapOutKBSinceKernelBoot(PSI_HEALTH_INFO_PATH, SWAP_OUT_SIZE_TAG, ret)) {
212             return true;
213         }
214     }
215     return false;
216 }
217 
SetTimer()218 void NandLifeController::SetTimer()
219 {
220     // set timer and call CheckSwapOut each TIMER_PEROID_MIN min.
221     handler_->PostTask([this] { this->CheckSwapOut(); }, TIMER_PEROID_MS, AppExecFwk::EventQueue::Priority::HIGH);
222     HILOGI("[%{public}llu] set timer after %{public}d mins", iter_, TIMER_PEROID_MIN);
223 }
224 
CheckReachedDailyLimit()225 bool NandLifeController::CheckReachedDailyLimit()
226 {
227     bool reachedDailyLimit = swapOutKBToday_ >= DAILY_SWAP_OUT_QUOTA_KB;
228     HILOGI("[%{public}llu] swapOutKBToday_(%{public}llu) %{public}s DAILY_SWAP_OUT_QUOTA_KB(%{public}llu)",
229         iter_, swapOutKBToday_, (reachedDailyLimit ? ">=" : "<"), DAILY_SWAP_OUT_QUOTA_KB);
230     if (reachedDailyLimit) {
231         CloseSwapOutTemporarily("reach daily limit, close swap-out temporarily!");
232     } else {
233         HILOGI("[%{public}llu] unreach daily limit, swap-out is still opened!", iter_);
234     }
235     return reachedDailyLimit;
236 }
237 
CheckReachedTotalLimit()238 bool NandLifeController::CheckReachedTotalLimit()
239 {
240     bool reachedTotalLimit = swapOutKBSinceBirth_ >= TOTAL_SWAP_OUT_QUOTA_KB;
241     HILOGI("[%{public}llu] swapOutKBSinceBirth_(%{public}llu) %{public}s TOTAL_SWAP_OUT_QUOTA_KB(%{public}llu)",
242         iter_, swapOutKBSinceBirth_, (reachedTotalLimit ? ">=" : "<"), TOTAL_SWAP_OUT_QUOTA_KB);
243     if (reachedTotalLimit) {
244         HILOGE("[%{public}llu] reached total limit, close swap-out forever!", iter_);
245         CloseSwapOutPermanently();
246     } else {
247         HILOGI("[%{public}llu] unreach total limit!", iter_);
248     }
249     return reachedTotalLimit;
250 }
251 
CheckSwapOut()252 void NandLifeController::CheckSwapOut()
253 {
254     ++iter_;
255 
256     HILOGE("[%{public}llu] called", iter_);
257 
258     if (IsSwapOutClosedPermently()) {
259         CloseSwapOutTemporarily("swap-out has benn closed permently!");
260         SetTimer();
261         return;
262     }
263 
264     PrintNandLifeParam();
265 
266     minsToday_ += TIMER_PEROID_MIN;
267     minsSinceBirth_ += TIMER_PEROID_MIN;
268 
269     if (GetSwapOutKBSinceKernelBoot(nowSwapOutKB_)) {
270         HILOGI("[%{public}llu] swapOutKBSinceKernelBoot=%{public}llu KB", iter_, nowSwapOutKB_);
271     } else {
272         CloseSwapOutTemporarily("invalid swapOutKBSinceKernelBoot");
273         SetTimer();
274         return;
275     }
276     if (nowSwapOutKB_ < lastSwapOutKB_) {
277         CloseSwapOutTemporarily("deltaSwapOutMB < 0");
278         SetTimer();
279         return;
280     }
281     unsigned long long increasedSwapOutKB = nowSwapOutKB_ - lastSwapOutKB_;
282     HILOGE("[%{public}llu] lastSwapOutKB_=%{public}llu, nowSwapOutKB_=%{public}llu, increasedSwapOutKB=%{public}llu",
283         iter_, lastSwapOutKB_, nowSwapOutKB_, increasedSwapOutKB);
284     lastSwapOutKB_ = nowSwapOutKB_;
285     swapOutKBToday_ += increasedSwapOutKB;
286     swapOutKBSinceBirth_ += increasedSwapOutKB;
287 
288     CheckReachedDailyLimit();
289 
290     if (minsToday_ >= 24 * 60) { // 24: a day has 24 hours, 60: one hour has 60 min
291         HILOGI("[%{public}llu] enter a new day", iter_);
292         minsToday_ = 0;
293         swapOutKBToday_ = 0;
294         if (swapOutKBSinceBirth_ < TOTAL_SWAP_OUT_QUOTA_KB) { // swap-out is allowed
295             HILOGI("[%{public}llu] open swap-out since a new day", iter_);
296             OpenSwapOutTemporarily("enter a new day");
297         }
298     }
299 
300     if (!UpdateNandLifeParam()) {
301         CloseSwapOutTemporarily("UpdateNandLifeParam failed!");
302     }
303 
304     PrintNandLifeParam();
305 
306     CheckReachedTotalLimit();
307 
308     // set next timer
309     SetTimer();
310 }
311 
SetParameterRetry(const std::string & paramName,const std::string & paramValue,int retryTimes)312 bool NandLifeController::SetParameterRetry(const std::string &paramName, const std::string &paramValue, int retryTimes)
313 {
314     for (auto i = 0; i < retryTimes; i++) {
315         if (system::SetParameter(paramName, paramValue)) {
316             return true;
317         }
318     }
319     HILOGW("[%{public}llu] set [%{public}s] to [%{public}s] failed!", iter_, paramName.c_str(), paramValue.c_str());
320     return false;
321 }
322 
UpdateNandLifeParam()323 bool NandLifeController::UpdateNandLifeParam()
324 {
325     if (!SetParameterRetry(MINS_TODAY_PARAM, std::to_string(minsToday_), RETRY_TIMES)) {
326         return false;
327     }
328     if (!SetParameterRetry(SWAP_OUT_KB_TODAY_PARAM, std::to_string(swapOutKBToday_), RETRY_TIMES)) {
329         return false;
330     }
331     if (!SetParameterRetry(MINS_FROM_BIRTH_PARAM, std::to_string(minsSinceBirth_), RETRY_TIMES)) {
332         return false;
333     }
334     if (!SetParameterRetry(SWAP_OUT_KB_FROM_BIRTH_PARAM, std::to_string(swapOutKBSinceBirth_), RETRY_TIMES)) {
335         return false;
336     }
337     HILOGW("[%{public}llu] all success!", iter_);
338     return true;
339 }
340 
OpenSwapOutTemporarily(const std::string & reason)341 void NandLifeController::OpenSwapOutTemporarily(const std::string &reason)
342 {
343     HILOGW("[%{public}llu] %{public}s", iter_, reason.c_str());
344     for (auto  i = 0; i < RETRY_TIMES; i++) {
345         if (KernelInterface::GetInstance().EchoToPath(ESWAP_ENABLE_PATH.c_str(), ENABLE_ESWAP.c_str())) {
346             HILOGI("[%{public}llu] open eswap temporarily success!", iter_);
347             return;
348         }
349     }
350     HILOGW("[%{public}llu] open eswap temporarily failed!", iter_);
351 }
352 
CloseSwapOutTemporarily(const std::string & reason)353 void NandLifeController::CloseSwapOutTemporarily(const std::string &reason)
354 {
355     HILOGW("[%{public}llu] %{public}s", iter_, reason.c_str());
356     for (auto  i = 0; i < RETRY_TIMES; i++) {
357         if (KernelInterface::GetInstance().EchoToPath(ESWAP_ENABLE_PATH.c_str(), DISABLE_ESWAP.c_str())) {
358             HILOGW("[%{public}llu] clsoe eswap temporarily success!", iter_);
359             return;
360         }
361     }
362     HILOGW("[%{public}llu] close eswap temporarily failed!", iter_);
363 }
364 
OpenSwapOutPermanently()365 void NandLifeController::OpenSwapOutPermanently()
366 {
367     bool ret = SetParameterRetry(PERMANENTLY_CLOSED_STATUS_PARAM, NOT_PERMANENTLY_CLOSED, RETRY_TIMES);
368     HILOGW("[%{public}llu] open eswap permanently %{public}s!", iter_, ret ? "success" : "failed");
369 }
370 
CloseSwapOutPermanently()371 void NandLifeController::CloseSwapOutPermanently()
372 {
373     CloseSwapOutTemporarily("CloseSwapOutPermanently close eswap temporarily first!");
374     bool ret = SetParameterRetry(PERMANENTLY_CLOSED_STATUS_PARAM, PERMANENTLY_CLOSED, RETRY_TIMES);
375     HILOGW("[%{public}llu] close eswap permanently %{public}s!", iter_, ret ? "success" : "failed");
376 }
377 } // namespace Memory
378 } // namespace OHOS
379