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