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 private static final String FORCE_WRITEBACK_PROP = "zram.force_writeback"; 64 markPagesAsIdle()65 private void markPagesAsIdle() { 66 String idlePath = String.format(IDLE_SYS, sZramDeviceId); 67 try { 68 FileUtils.stringToFile(new File(idlePath), IDLE_SYS_ALL_PAGES); 69 } catch (IOException e) { 70 Slog.e(TAG, "Failed to write to " + idlePath); 71 } 72 } 73 flushIdlePages()74 private void flushIdlePages() { 75 if (DEBUG) Slog.d(TAG, "Start writing back idle pages to disk"); 76 String wbPath = String.format(WB_SYS, sZramDeviceId); 77 try { 78 FileUtils.stringToFile(new File(wbPath), WB_SYS_IDLE_PAGES); 79 } catch (IOException e) { 80 Slog.e(TAG, "Failed to write to " + wbPath); 81 } 82 if (DEBUG) Slog.d(TAG, "Finished writeback back idle pages"); 83 } 84 getWrittenPageCount()85 private int getWrittenPageCount() { 86 String wbStatsPath = String.format(WB_STATS_SYS, sZramDeviceId); 87 try { 88 String wbStats = FileUtils 89 .readTextFile(new File(wbStatsPath), WB_STATS_MAX_FILE_SIZE, ""); 90 return Integer.parseInt(wbStats.trim().split("\\s+")[2], 10); 91 } catch (IOException e) { 92 Slog.e(TAG, "Failed to read writeback stats from " + wbStatsPath); 93 } 94 95 return -1; 96 } 97 markAndFlushPages()98 private void markAndFlushPages() { 99 int pageCount = getWrittenPageCount(); 100 101 flushIdlePages(); 102 markPagesAsIdle(); 103 104 if (pageCount != -1) { 105 Slog.i(TAG, "Total pages written to disk is " + (getWrittenPageCount() - pageCount)); 106 } 107 } 108 isWritebackEnabled()109 private static boolean isWritebackEnabled() { 110 try { 111 String backingDev = FileUtils 112 .readTextFile(new File(String.format(BDEV_SYS, sZramDeviceId)), 128, ""); 113 if (!"none".equals(backingDev.trim())) { 114 return true; 115 } else { 116 Slog.w(TAG, "Writeback device is not set"); 117 } 118 } catch (IOException e) { 119 Slog.w(TAG, "Writeback is not enabled on zram"); 120 } 121 return false; 122 } 123 schedNextWriteback(Context context)124 private static void schedNextWriteback(Context context) { 125 int nextWbDelay = SystemProperties.getInt(PERIODIC_WB_DELAY_PROP, 24); 126 boolean forceWb = SystemProperties.getBoolean(FORCE_WRITEBACK_PROP, false); 127 JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); 128 129 js.schedule(new JobInfo.Builder(WRITEBACK_IDLE_JOB_ID, sZramWriteback) 130 .setMinimumLatency(TimeUnit.HOURS.toMillis(nextWbDelay)) 131 .setRequiresDeviceIdle(!forceWb) 132 .build()); 133 } 134 135 @Override onStartJob(JobParameters params)136 public boolean onStartJob(JobParameters params) { 137 138 if (!isWritebackEnabled()) { 139 jobFinished(params, false); 140 return false; 141 } 142 143 if (params.getJobId() == MARK_IDLE_JOB_ID) { 144 markPagesAsIdle(); 145 jobFinished(params, false); 146 return false; 147 } else { 148 new Thread("ZramWriteback_WritebackIdlePages") { 149 @Override 150 public void run() { 151 markAndFlushPages(); 152 schedNextWriteback(ZramWriteback.this); 153 jobFinished(params, false); 154 } 155 }.start(); 156 } 157 return true; 158 } 159 160 @Override onStopJob(JobParameters params)161 public boolean onStopJob(JobParameters params) { 162 // The thread that triggers the writeback is non-interruptible 163 return false; 164 } 165 166 /** 167 * Schedule the zram writeback job to trigger a writeback when idle 168 */ scheduleZramWriteback(Context context)169 public static void scheduleZramWriteback(Context context) { 170 int markIdleDelay = SystemProperties.getInt(MARK_IDLE_DELAY_PROP, 20); 171 int firstWbDelay = SystemProperties.getInt(FIRST_WB_DELAY_PROP, 180); 172 boolean forceWb = SystemProperties.getBoolean(FORCE_WRITEBACK_PROP, false); 173 174 JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); 175 176 // Schedule a one time job to mark pages as idle. These pages will be written 177 // back at later point if they remain untouched. 178 js.schedule(new JobInfo.Builder(MARK_IDLE_JOB_ID, sZramWriteback) 179 .setMinimumLatency(TimeUnit.MINUTES.toMillis(markIdleDelay)) 180 .setOverrideDeadline(TimeUnit.MINUTES.toMillis(markIdleDelay)) 181 .build()); 182 183 // Schedule a one time job to flush idle pages to disk. 184 // After the initial writeback, subsequent writebacks are done at interval set 185 // by ro.zram.periodic_wb_delay_hours. 186 js.schedule(new JobInfo.Builder(WRITEBACK_IDLE_JOB_ID, sZramWriteback) 187 .setMinimumLatency(TimeUnit.MINUTES.toMillis(firstWbDelay)) 188 .setRequiresDeviceIdle(!forceWb) 189 .build()); 190 } 191 } 192