1 /* 2 * Copyright (C) 2017 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.car; 18 19 import android.util.JsonReader; 20 import android.util.JsonWriter; 21 import android.util.Log; 22 23 import com.android.car.systeminterface.SystemInterface; 24 25 import com.android.car.systeminterface.TimeInterface; 26 import com.android.internal.annotations.VisibleForTesting; 27 28 import java.io.File; 29 import java.io.FileReader; 30 import java.io.FileWriter; 31 import java.io.IOException; 32 import java.util.Objects; 33 import java.util.Optional; 34 35 /** 36 * A class that can keep track of how long its instances are alive for. 37 * 38 * It can be used as a helper object to track the lifetime of system components, e.g. 39 * 40 * class InterestingService { 41 * private UptimeTracker mTracker; 42 * 43 * public void onCreate() { 44 * mTracker = new UptimeTracker( 45 * "/storage/emulated/0/Android/data/interestingservice.uptime", 1 hour); 46 * mTracker.onCreate(); 47 * } 48 * 49 * public void onDestroy() { 50 * mTracker.onDestroy(); 51 * } 52 * } 53 * 54 * Now it's possible to know how long InterestingService has been alive in the system by querying 55 * mTracker.getTotalUptime(). Because this data is stored to disk, the uptime is maintained across 56 * process and system reboot boundaries. It is possible to configure periodic snapshot points to 57 * ensure that crashes do not cause more than a certain amount of uptime to go untracked. 58 */ 59 public class UptimeTracker { 60 /** 61 * In order to prevent excessive wear-out of the storage, do not allow snapshots to happen 62 * more frequently than this value 63 */ 64 public static final long MINIMUM_SNAPSHOT_INTERVAL_MS = 60 * 60 * 1000; 65 66 /** 67 * The default snapshot interval if none is given 68 */ 69 private static long DEFAULT_SNAPSHOT_INTERVAL_MS = 5 * 60 * 60 * 1000; // 5 hours 70 71 private final Object mLock = new Object(); 72 73 /** 74 * The file that uptime metrics are stored to 75 */ 76 private File mUptimeFile; 77 78 /** 79 * The uptime value retrieved from mUptimeFile 80 */ 81 private Optional<Long> mHistoricalUptime; 82 83 /** 84 * Last value of elapsedRealTime read from the system 85 */ 86 private long mLastRealTimeSnapshot; 87 88 /** 89 * The source of real-time and scheduling 90 */ 91 private TimeInterface mTimeInterface; 92 UptimeTracker(File file)93 public UptimeTracker(File file) { 94 this(file, DEFAULT_SNAPSHOT_INTERVAL_MS); 95 } 96 UptimeTracker(File file, long snapshotInterval)97 public UptimeTracker(File file, long snapshotInterval) { 98 this(file, snapshotInterval, new TimeInterface.DefaultImpl()); 99 } 100 UptimeTracker(File file, long snapshotInterval, SystemInterface systemInterface)101 UptimeTracker(File file, long snapshotInterval, SystemInterface systemInterface) { 102 this(file, snapshotInterval, systemInterface.getTimeInterface()); 103 } 104 105 // This constructor allows one to replace the source of time-based truths with 106 // a mock version. This is mostly useful for testing purposes. 107 @VisibleForTesting UptimeTracker(File file, long snapshotInterval, TimeInterface timeInterface)108 UptimeTracker(File file, 109 long snapshotInterval, 110 TimeInterface timeInterface) { 111 snapshotInterval = Math.max(snapshotInterval, MINIMUM_SNAPSHOT_INTERVAL_MS); 112 mUptimeFile = Objects.requireNonNull(file); 113 mTimeInterface = timeInterface; 114 mLastRealTimeSnapshot = mTimeInterface.getUptime(TimeInterface.EXCLUDE_DEEP_SLEEP_TIME); 115 mHistoricalUptime = Optional.empty(); 116 117 mTimeInterface.scheduleAction(this::flushSnapshot, snapshotInterval); 118 } 119 onDestroy()120 void onDestroy() { 121 synchronized (mLock) { 122 if (mTimeInterface != null) { 123 mTimeInterface.cancelAllActions(); 124 } 125 flushSnapshot(); 126 mTimeInterface = null; 127 mUptimeFile = null; 128 } 129 } 130 131 /** 132 * Return the total amount of uptime that has been observed, in milliseconds. 133 * 134 * This is the sum of the uptime stored on disk + the uptime seen since the last snapshot. 135 */ getTotalUptime()136 long getTotalUptime() { 137 synchronized (mLock) { 138 if (mTimeInterface == null) { 139 return 0; 140 } 141 return getHistoricalUptimeLocked() + ( 142 mTimeInterface.getUptime(TimeInterface.EXCLUDE_DEEP_SLEEP_TIME) 143 - mLastRealTimeSnapshot); 144 } 145 } 146 getHistoricalUptimeLocked()147 private long getHistoricalUptimeLocked() { 148 if (!mHistoricalUptime.isPresent() && mUptimeFile != null && mUptimeFile.exists()) { 149 try { 150 JsonReader reader = new JsonReader(new FileReader(mUptimeFile)); 151 reader.beginObject(); 152 if (!reader.nextName().equals("uptime")) { 153 throw new IllegalArgumentException( 154 mUptimeFile + " is not in a valid format"); 155 } else { 156 mHistoricalUptime = Optional.of(reader.nextLong()); 157 } 158 reader.endObject(); 159 reader.close(); 160 } catch (IllegalArgumentException | IOException e) { 161 Log.w(CarLog.TAG_SERVICE, "unable to read historical uptime data", e); 162 mHistoricalUptime = Optional.empty(); 163 } 164 } 165 return mHistoricalUptime.orElse(0L); 166 } 167 flushSnapshot()168 private void flushSnapshot() { 169 synchronized (mLock) { 170 if (mUptimeFile == null) { 171 return; 172 } 173 try { 174 long newUptime = getTotalUptime(); 175 mHistoricalUptime = Optional.of(newUptime); 176 mLastRealTimeSnapshot = mTimeInterface.getUptime( 177 TimeInterface.EXCLUDE_DEEP_SLEEP_TIME); 178 179 JsonWriter writer = new JsonWriter(new FileWriter(mUptimeFile)); 180 writer.beginObject(); 181 writer.name("uptime"); 182 writer.value(newUptime); 183 writer.endObject(); 184 writer.close(); 185 } catch (IOException e) { 186 Log.w(CarLog.TAG_SERVICE, "unable to write historical uptime data", e); 187 } 188 } 189 } 190 } 191