Home | History | Annotate | Download | only in server
      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