• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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 package com.android.server;
18 
19 import android.app.job.JobInfo;
20 import android.app.job.JobParameters;
21 import android.app.job.JobScheduler;
22 import android.app.job.JobService;
23 import android.content.ComponentName;
24 import android.content.Context;
25 import android.os.FileUtils;
26 import android.os.SystemProperties;
27 import android.util.Slog;
28 
29 import java.io.File;
30 import java.io.IOException;
31 import java.util.concurrent.TimeUnit;
32 
33 /**
34  * Schedules jobs for triggering zram writeback.
35  */
36 public final class ZramWriteback extends JobService {
37     private static final String TAG = "ZramWriteback";
38     private static final boolean DEBUG = false;
39 
40     private static final ComponentName sZramWriteback =
41             new ComponentName("android", ZramWriteback.class.getName());
42 
43     private static final int MARK_IDLE_JOB_ID = 811;
44     private static final int WRITEBACK_IDLE_JOB_ID = 812;
45 
46     private static final int MAX_ZRAM_DEVICES = 256;
47     private static int sZramDeviceId = 0;
48 
49     private static final String IDLE_SYS = "/sys/block/zram%d/idle";
50     private static final String IDLE_SYS_ALL_PAGES = "all";
51 
52     private static final String WB_SYS = "/sys/block/zram%d/writeback";
53     private static final String WB_SYS_IDLE_PAGES = "idle";
54 
55     private static final String WB_STATS_SYS = "/sys/block/zram%d/bd_stat";
56     private static final int WB_STATS_MAX_FILE_SIZE = 128;
57 
58     private static final String BDEV_SYS = "/sys/block/zram%d/backing_dev";
59 
60     private static final String MARK_IDLE_DELAY_PROP = "ro.zram.mark_idle_delay_mins";
61     private static final String FIRST_WB_DELAY_PROP = "ro.zram.first_wb_delay_mins";
62     private static final String PERIODIC_WB_DELAY_PROP = "ro.zram.periodic_wb_delay_hours";
63 
markPagesAsIdle()64     private void markPagesAsIdle() {
65         String idlePath = String.format(IDLE_SYS, sZramDeviceId);
66         try {
67             FileUtils.stringToFile(new File(idlePath), IDLE_SYS_ALL_PAGES);
68         } catch (IOException e) {
69             Slog.e(TAG, "Failed to write to " + idlePath);
70         }
71     }
72 
flushIdlePages()73     private void flushIdlePages() {
74         if (DEBUG) Slog.d(TAG, "Start writing back idle pages to disk");
75         String wbPath = String.format(WB_SYS, sZramDeviceId);
76         try {
77             FileUtils.stringToFile(new File(wbPath), WB_SYS_IDLE_PAGES);
78         } catch (IOException e) {
79             Slog.e(TAG, "Failed to write to " + wbPath);
80         }
81         if (DEBUG) Slog.d(TAG, "Finished writeback back idle pages");
82     }
83 
getWrittenPageCount()84     private int getWrittenPageCount() {
85         String wbStatsPath = String.format(WB_STATS_SYS, sZramDeviceId);
86         try {
87             String wbStats = FileUtils
88                     .readTextFile(new File(wbStatsPath), WB_STATS_MAX_FILE_SIZE, "");
89             return Integer.parseInt(wbStats.trim().split("\\s+")[2], 10);
90         } catch (IOException e) {
91             Slog.e(TAG, "Failed to read writeback stats from " + wbStatsPath);
92         }
93 
94         return -1;
95     }
96 
markAndFlushPages()97     private void markAndFlushPages() {
98         int pageCount = getWrittenPageCount();
99 
100         flushIdlePages();
101         markPagesAsIdle();
102 
103         if (pageCount != -1) {
104             Slog.i(TAG, "Total pages written to disk is " + (getWrittenPageCount() - pageCount));
105         }
106     }
107 
isWritebackEnabled()108     private static boolean isWritebackEnabled() {
109         try {
110             String backingDev = FileUtils
111                     .readTextFile(new File(String.format(BDEV_SYS, sZramDeviceId)), 128, "");
112             if (!"none".equals(backingDev.trim())) {
113                 return true;
114             } else {
115                 Slog.w(TAG, "Writeback device is not set");
116             }
117         } catch (IOException e) {
118             Slog.w(TAG, "Writeback is not enabled on zram");
119         }
120         return false;
121     }
122 
schedNextWriteback(Context context)123     private static void schedNextWriteback(Context context) {
124         int nextWbDelay = SystemProperties.getInt(PERIODIC_WB_DELAY_PROP, 24);
125         JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
126 
127         js.schedule(new JobInfo.Builder(WRITEBACK_IDLE_JOB_ID, sZramWriteback)
128                         .setMinimumLatency(TimeUnit.HOURS.toMillis(nextWbDelay))
129                         .setRequiresDeviceIdle(true)
130                         .build());
131     }
132 
133     @Override
onStartJob(JobParameters params)134     public boolean onStartJob(JobParameters params) {
135 
136         if (!isWritebackEnabled()) {
137             jobFinished(params, false);
138             return false;
139         }
140 
141         if (params.getJobId() == MARK_IDLE_JOB_ID) {
142             markPagesAsIdle();
143             jobFinished(params, false);
144             return false;
145         } else {
146             new Thread("ZramWriteback_WritebackIdlePages") {
147                 @Override
148                 public void run() {
149                     markAndFlushPages();
150                     schedNextWriteback(ZramWriteback.this);
151                     jobFinished(params, false);
152                 }
153             }.start();
154         }
155         return true;
156     }
157 
158     @Override
onStopJob(JobParameters params)159     public boolean onStopJob(JobParameters params) {
160         // The thread that triggers the writeback is non-interruptible
161         return false;
162     }
163 
164     /**
165      * Schedule the zram writeback job to trigger a writeback when idle
166      */
scheduleZramWriteback(Context context)167     public static void scheduleZramWriteback(Context context) {
168         int markIdleDelay = SystemProperties.getInt(MARK_IDLE_DELAY_PROP, 20);
169         int firstWbDelay = SystemProperties.getInt(FIRST_WB_DELAY_PROP, 180);
170 
171         JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
172 
173         // Schedule a one time job to mark pages as idle. These pages will be written
174         // back at later point if they remain untouched.
175         js.schedule(new JobInfo.Builder(MARK_IDLE_JOB_ID, sZramWriteback)
176                         .setMinimumLatency(TimeUnit.MINUTES.toMillis(markIdleDelay))
177                         .setOverrideDeadline(TimeUnit.MINUTES.toMillis(markIdleDelay))
178                         .build());
179 
180         // Schedule a one time job to flush idle pages to disk.
181         // After the initial writeback, subsequent writebacks are done at interval set
182         // by ro.zram.periodic_wb_delay_hours.
183         js.schedule(new JobInfo.Builder(WRITEBACK_IDLE_JOB_ID, sZramWriteback)
184                         .setMinimumLatency(TimeUnit.MINUTES.toMillis(firstWbDelay))
185                         .setRequiresDeviceIdle(true)
186                         .build());
187     }
188 }
189