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