/* * 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.car.builtin.util.Slogf; import android.util.JsonReader; import android.util.JsonWriter; 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 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 snapShotIntervalMs, TimeInterface timeInterface) { long snapshotInterval = snapShotIntervalMs; 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 (!Objects.equals(reader.nextName(), "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) { Slogf.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) { Slogf.w(CarLog.TAG_SERVICE, "unable to write historical uptime data", e); } } } }