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.car.builtin.util.Slogf; 20 import android.util.JsonReader; 21 import android.util.JsonWriter; 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 snapShotIntervalMs, TimeInterface timeInterface)107 UptimeTracker(File file, 108 long snapShotIntervalMs, 109 TimeInterface timeInterface) { 110 long snapshotInterval = snapShotIntervalMs; 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 (!Objects.equals(reader.nextName(), "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 Slogf.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 Slogf.w(CarLog.TAG_SERVICE, "unable to write historical uptime data", e); 187 } 188 } 189 } 190 } 191