blob: 83ce5e957903baf55a0bda151b398cccb3b93f44 [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.car;
import android.util.JsonReader;
import android.util.JsonWriter;
import android.util.Slog;
import com.android.car.systeminterface.SystemInterface;
import com.android.car.systeminterface.TimeInterface;
import com.android.internal.annotations.VisibleForTesting;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Objects;
import java.util.Optional;
/**
* A class that can keep track of how long its instances are alive for.
*
* It can be used as a helper object to track the lifetime of system components, e.g.
*
* class InterestingService {
* private UptimeTracker mTracker;
*
* public void onCreate() {
* mTracker = new UptimeTracker(
* "/storage/emulated/0/Android/data/interestingservice.uptime", 1 hour);
* mTracker.onCreate();
* }
*
* public void onDestroy() {
* mTracker.onDestroy();
* }
* }
*
* Now it's possible to know how long InterestingService has been alive in the system by querying
* mTracker.getTotalUptime(). Because this data is stored to disk, the uptime is maintained across
* process and system reboot boundaries. It is possible to configure periodic snapshot points to
* ensure that crashes do not cause more than a certain amount of uptime to go untracked.
*/
public class UptimeTracker {
/**
* In order to prevent excessive wear-out of the storage, do not allow snapshots to happen
* more frequently than this value
*/
public static final long MINIMUM_SNAPSHOT_INTERVAL_MS = 60 * 60 * 1000;
/**
* The default snapshot interval if none is given
*/
private static long DEFAULT_SNAPSHOT_INTERVAL_MS = 5 * 60 * 60 * 1000; // 5 hours
private final Object mLock = new Object();
/**
* The file that uptime metrics are stored to
*/
private File mUptimeFile;
/**
* The uptime value retrieved from mUptimeFile
*/
private Optional<Long> mHistoricalUptime;
/**
* Last value of elapsedRealTime read from the system
*/
private long mLastRealTimeSnapshot;
/**
* The source of real-time and scheduling
*/
private TimeInterface mTimeInterface;
public UptimeTracker(File file) {
this(file, DEFAULT_SNAPSHOT_INTERVAL_MS);
}
public UptimeTracker(File file, long snapshotInterval) {
this(file, snapshotInterval, new TimeInterface.DefaultImpl());
}
UptimeTracker(File file, long snapshotInterval, SystemInterface systemInterface) {
this(file, snapshotInterval, systemInterface.getTimeInterface());
}
// This constructor allows one to replace the source of time-based truths with
// a mock version. This is mostly useful for testing purposes.
@VisibleForTesting
UptimeTracker(File file,
long snapshotInterval,
TimeInterface timeInterface) {
snapshotInterval = Math.max(snapshotInterval, MINIMUM_SNAPSHOT_INTERVAL_MS);
mUptimeFile = Objects.requireNonNull(file);
mTimeInterface = timeInterface;
mLastRealTimeSnapshot = mTimeInterface.getUptime(TimeInterface.EXCLUDE_DEEP_SLEEP_TIME);
mHistoricalUptime = Optional.empty();
mTimeInterface.scheduleAction(this::flushSnapshot, snapshotInterval);
}
void onDestroy() {
synchronized (mLock) {
if (mTimeInterface != null) {
mTimeInterface.cancelAllActions();
}
flushSnapshot();
mTimeInterface = null;
mUptimeFile = null;
}
}
/**
* Return the total amount of uptime that has been observed, in milliseconds.
*
* This is the sum of the uptime stored on disk + the uptime seen since the last snapshot.
*/
long getTotalUptime() {
synchronized (mLock) {
if (mTimeInterface == null) {
return 0;
}
return getHistoricalUptimeLocked() + (
mTimeInterface.getUptime(TimeInterface.EXCLUDE_DEEP_SLEEP_TIME)
- mLastRealTimeSnapshot);
}
}
private long getHistoricalUptimeLocked() {
if (!mHistoricalUptime.isPresent() && mUptimeFile != null && mUptimeFile.exists()) {
try {
JsonReader reader = new JsonReader(new FileReader(mUptimeFile));
reader.beginObject();
if (!reader.nextName().equals("uptime")) {
throw new IllegalArgumentException(
mUptimeFile + " is not in a valid format");
} else {
mHistoricalUptime = Optional.of(reader.nextLong());
}
reader.endObject();
reader.close();
} catch (IllegalArgumentException | IOException e) {
Slog.w(CarLog.TAG_SERVICE, "unable to read historical uptime data", e);
mHistoricalUptime = Optional.empty();
}
}
return mHistoricalUptime.orElse(0L);
}
private void flushSnapshot() {
synchronized (mLock) {
if (mUptimeFile == null) {
return;
}
try {
long newUptime = getTotalUptime();
mHistoricalUptime = Optional.of(newUptime);
mLastRealTimeSnapshot = mTimeInterface.getUptime(
TimeInterface.EXCLUDE_DEEP_SLEEP_TIME);
JsonWriter writer = new JsonWriter(new FileWriter(mUptimeFile));
writer.beginObject();
writer.name("uptime");
writer.value(newUptime);
writer.endObject();
writer.close();
} catch (IOException e) {
Slog.w(CarLog.TAG_SERVICE, "unable to write historical uptime data", e);
}
}
}
}