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