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 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 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 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 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 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 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 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 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 */ 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