Use AtomicFile in CarTelemetryService
All usages of File are replaced with AtomicFile, with the exception of
simple deletion, because AtomicFile uses the same method underneath.
Bug: 205049095
Test: atest CarServiceUnitTest:ResultStoreTest
Test: atest CarServiceUnitTest:MetricsConfigStoreTest
Test: atest CarServiceUnitTest:StatsPublisherTest
Test: atest CarServiceUnitTest:CarTelemetryServiceTest
Change-Id: Ib74202b5d69cfb158d502a579e9d953a34ffb75b
Merged-In: Ib74202b5d69cfb158d502a579e9d953a34ffb75b
(cherry picked from commit 3f1dce5dc692b717d960b7a9d7afe669c194fa05)
diff --git a/service/src/com/android/car/telemetry/CarTelemetryService.java b/service/src/com/android/car/telemetry/CarTelemetryService.java
index fc108be..f70cd74 100644
--- a/service/src/com/android/car/telemetry/CarTelemetryService.java
+++ b/service/src/com/android/car/telemetry/CarTelemetryService.java
@@ -60,6 +60,7 @@
public class CarTelemetryService extends ICarTelemetryService.Stub implements CarServiceBase {
private static final boolean DEBUG = false;
+ private static final String PUBLISHER_DIR = "publisher";
public static final String TELEMETRY_DIR = "telemetry";
private final Context mContext;
@@ -88,13 +89,15 @@
SystemInterface systemInterface = CarLocalServices.getService(SystemInterface.class);
// full root directory path is /data/system/car/telemetry
File rootDirectory = new File(systemInterface.getSystemCarDir(), TELEMETRY_DIR);
+ File publisherDirectory = new File(rootDirectory, PUBLISHER_DIR);
+ publisherDirectory.mkdirs();
// initialize all necessary components
mMetricsConfigStore = new MetricsConfigStore(rootDirectory);
mResultStore = new ResultStore(rootDirectory);
mStatsManagerProxy = new StatsManagerImpl(
mContext.getSystemService(StatsManager.class));
mPublisherFactory = new PublisherFactory(mCarPropertyService, mTelemetryHandler,
- mStatsManagerProxy, rootDirectory);
+ mStatsManagerProxy, publisherDirectory);
mDataBroker = new DataBrokerImpl(mContext, mPublisherFactory, mResultStore);
mSystemMonitor = SystemMonitor.create(mContext, mTelemetryHandler);
// controller starts metrics collection after boot complete
diff --git a/service/src/com/android/car/telemetry/MetricsConfigStore.java b/service/src/com/android/car/telemetry/MetricsConfigStore.java
index 5a6e1ca..d369085 100644
--- a/service/src/com/android/car/telemetry/MetricsConfigStore.java
+++ b/service/src/com/android/car/telemetry/MetricsConfigStore.java
@@ -23,12 +23,14 @@
import android.car.telemetry.MetricsConfigKey;
import android.util.ArrayMap;
+import android.util.AtomicFile;
import com.android.car.CarLog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.utils.Slogf;
import java.io.File;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
@@ -54,14 +56,14 @@
mActiveConfigs = new ArrayMap<>();
// TODO(b/197336485): Add expiration date check for MetricsConfig
for (File file : mConfigDirectory.listFiles()) {
+ AtomicFile atomicFile = new AtomicFile(file);
try {
- byte[] serializedConfig = Files.readAllBytes(file.toPath());
TelemetryProto.MetricsConfig config =
- TelemetryProto.MetricsConfig.parseFrom(serializedConfig);
+ TelemetryProto.MetricsConfig.parseFrom(atomicFile.readFully());
mActiveConfigs.put(config.getName(), config);
} catch (IOException e) {
// TODO(b/197336655): record failure
- file.delete();
+ atomicFile.delete();
}
}
}
@@ -94,12 +96,15 @@
}
}
mActiveConfigs.put(metricsConfig.getName(), metricsConfig);
+ AtomicFile atomicFile = new AtomicFile(new File(mConfigDirectory, metricsConfig.getName()));
+ FileOutputStream fos = null;
try {
- Files.write(
- new File(mConfigDirectory, metricsConfig.getName()).toPath(),
- metricsConfig.toByteArray());
+ fos = atomicFile.startWrite();
+ fos.write(metricsConfig.toByteArray());
+ atomicFile.finishWrite(fos);
} catch (IOException e) {
// TODO(b/197336655): record failure
+ atomicFile.failWrite(fos);
Slogf.w(CarLog.TAG_TELEMETRY, "Failed to write metrics config to disk", e);
return ERROR_METRICS_CONFIG_UNKNOWN;
}
diff --git a/service/src/com/android/car/telemetry/ResultStore.java b/service/src/com/android/car/telemetry/ResultStore.java
index 1f4311b..16c8fba 100644
--- a/service/src/com/android/car/telemetry/ResultStore.java
+++ b/service/src/com/android/car/telemetry/ResultStore.java
@@ -19,6 +19,7 @@
import android.car.telemetry.MetricsConfigKey;
import android.os.PersistableBundle;
import android.util.ArrayMap;
+import android.util.AtomicFile;
import com.android.car.CarLog;
import com.android.internal.annotations.VisibleForTesting;
@@ -28,7 +29,6 @@
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
-import java.nio.file.Files;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@@ -68,13 +68,9 @@
/** Reads interim results into memory for faster access. */
private void loadInterimResultsIntoMemory() {
for (File file : mInterimResultDirectory.listFiles()) {
- try (FileInputStream fis = new FileInputStream(file)) {
- mInterimResultCache.put(
- file.getName(),
- new InterimResult(PersistableBundle.readFromStream(fis)));
- } catch (IOException e) {
- Slogf.w(CarLog.TAG_TELEMETRY, "Failed to read result from disk.", e);
- // TODO(b/197153560): record failure
+ PersistableBundle interimResultBundle = readPersistableBundle(new AtomicFile(file));
+ if (interimResultBundle != null) {
+ mInterimResultCache.put(file.getName(), new InterimResult(interimResultBundle));
}
}
}
@@ -107,19 +103,14 @@
* @return the final result as PersistableBundle if exists, null otherwise
*/
public PersistableBundle getFinalResult(String metricsConfigName, boolean deleteResult) {
- File file = new File(mFinalResultDirectory, metricsConfigName);
+ AtomicFile atomicFile = new AtomicFile(new File(mFinalResultDirectory, metricsConfigName));
// if no final result exists for this metrics config, return immediately
- if (!file.exists()) {
+ if (!atomicFile.getBaseFile().exists()) {
return null;
}
- PersistableBundle result = null;
- try (FileInputStream fis = new FileInputStream(file)) {
- result = PersistableBundle.readFromStream(fis);
- } catch (IOException e) {
- Slogf.w(CarLog.TAG_TELEMETRY, "Failed to get final result from disk.", e);
- }
+ PersistableBundle result = readPersistableBundle(atomicFile);
if (deleteResult) {
- file.delete();
+ atomicFile.delete();
}
return result;
}
@@ -129,7 +120,7 @@
* {@link com.android.car.telemetry.TelemetryProto.MetricsConfig}.
*/
public void putFinalResult(String metricsConfigName, PersistableBundle result) {
- writePersistableBundleToFile(mFinalResultDirectory, metricsConfigName, result);
+ writePersistableBundle(mFinalResultDirectory, metricsConfigName, result);
deleteFileInDirectory(mInterimResultDirectory, metricsConfigName);
mInterimResultCache.remove(metricsConfigName);
}
@@ -137,34 +128,35 @@
/** Returns the error result produced by the metrics config if exists, null otherwise. */
public TelemetryProto.TelemetryError getError(
String metricsConfigName, boolean deleteResult) {
- File file = new File(mErrorResultDirectory, metricsConfigName);
+ AtomicFile atomicFile = new AtomicFile(new File(mErrorResultDirectory, metricsConfigName));
// if no error exists for this metrics config, return immediately
- if (!file.exists()) {
+ if (!atomicFile.getBaseFile().exists()) {
return null;
}
TelemetryProto.TelemetryError result = null;
try {
- byte[] serializedBytes = Files.readAllBytes(file.toPath());
- result = TelemetryProto.TelemetryError.parseFrom(serializedBytes);
+ result = TelemetryProto.TelemetryError.parseFrom(atomicFile.readFully());
} catch (IOException e) {
Slogf.w(CarLog.TAG_TELEMETRY, "Failed to get error result from disk.", e);
}
if (deleteResult) {
- file.delete();
+ atomicFile.delete();
}
return result;
}
/** Stores the error object produced by the script. */
public void putError(String metricsConfigName, TelemetryProto.TelemetryError error) {
- mInterimResultCache.remove(metricsConfigName);
+ AtomicFile errorFile = new AtomicFile(new File(mErrorResultDirectory, metricsConfigName));
+ FileOutputStream fos = null;
try {
- Files.write(
- new File(mErrorResultDirectory, metricsConfigName).toPath(),
- error.toByteArray());
+ fos = errorFile.startWrite();
+ fos.write(error.toByteArray());
+ errorFile.finishWrite(fos);
deleteFileInDirectory(mInterimResultDirectory, metricsConfigName);
mInterimResultCache.remove(metricsConfigName);
} catch (IOException e) {
+ errorFile.failWrite(fos);
Slogf.w(CarLog.TAG_TELEMETRY, "Failed to write data to file", e);
// TODO(b/197153560): record failure
}
@@ -205,7 +197,7 @@
if (!interimResult.isDirty()) {
return;
}
- writePersistableBundleToFile(
+ writePersistableBundle(
mInterimResultDirectory, metricsConfigName, interimResult.getBundle());
});
}
@@ -226,16 +218,34 @@
/**
* Converts a {@link PersistableBundle} into byte array and saves the results to a file.
*/
- private void writePersistableBundleToFile(
+ private void writePersistableBundle(
File dir, String metricsConfigName, PersistableBundle result) {
- try (FileOutputStream os = new FileOutputStream(new File(dir, metricsConfigName))) {
- result.writeToStream(os);
+ AtomicFile bundleFile = new AtomicFile(new File(dir, metricsConfigName));
+ FileOutputStream fos = null;
+ try {
+ fos = bundleFile.startWrite();
+ result.writeToStream(fos);
+ bundleFile.finishWrite(fos);
} catch (IOException e) {
+ bundleFile.failWrite(fos);
Slogf.w(CarLog.TAG_TELEMETRY, "Failed to write result to file", e);
// TODO(b/197153560): record failure
}
}
+ /**
+ * Reads a {@link PersistableBundle} from the file system.
+ */
+ private PersistableBundle readPersistableBundle(AtomicFile atomicFile) {
+ try (FileInputStream fis = atomicFile.openRead()) {
+ return PersistableBundle.readFromStream(fis);
+ } catch (IOException e) {
+ Slogf.w(CarLog.TAG_TELEMETRY, "Failed to read from disk.", e);
+ // TODO(b/197153560): record failure
+ }
+ return null;
+ }
+
/** Deletes a the given file in the given directory if it exists. */
private void deleteFileInDirectory(File interimResultDirectory, String metricsConfigName) {
File file = new File(interimResultDirectory, metricsConfigName);
diff --git a/service/src/com/android/car/telemetry/publisher/PublisherFactory.java b/service/src/com/android/car/telemetry/publisher/PublisherFactory.java
index 0c210b6..8931b86 100644
--- a/service/src/com/android/car/telemetry/publisher/PublisherFactory.java
+++ b/service/src/com/android/car/telemetry/publisher/PublisherFactory.java
@@ -35,7 +35,7 @@
public class PublisherFactory {
private final Object mLock = new Object();
private final CarPropertyService mCarPropertyService;
- private final File mRootDirectory;
+ private final File mPublisherDirectory;
private final Handler mTelemetryHandler;
private final StatsManagerProxy mStatsManager;
private VehiclePropertyPublisher mVehiclePropertyPublisher;
@@ -48,11 +48,11 @@
CarPropertyService carPropertyService,
Handler handler,
StatsManagerProxy statsManager,
- File rootDirectory) {
+ File publisherDirectory) {
mCarPropertyService = carPropertyService;
mTelemetryHandler = handler;
mStatsManager = statsManager;
- mRootDirectory = rootDirectory;
+ mPublisherDirectory = publisherDirectory;
}
/** Returns the publisher by given type. This method is thread-safe. */
@@ -75,7 +75,8 @@
case TelemetryProto.Publisher.STATS_FIELD_NUMBER:
if (mStatsPublisher == null) {
mStatsPublisher = new StatsPublisher(
- mFailureListener, mStatsManager, mRootDirectory, mTelemetryHandler);
+ mFailureListener, mStatsManager, mPublisherDirectory,
+ mTelemetryHandler);
}
return mStatsPublisher;
default:
diff --git a/service/src/com/android/car/telemetry/publisher/StatsPublisher.java b/service/src/com/android/car/telemetry/publisher/StatsPublisher.java
index 8570d6a..40e0261 100644
--- a/service/src/com/android/car/telemetry/publisher/StatsPublisher.java
+++ b/service/src/com/android/car/telemetry/publisher/StatsPublisher.java
@@ -27,6 +27,7 @@
import android.os.Handler;
import android.os.PersistableBundle;
import android.os.Process;
+import android.util.AtomicFile;
import android.util.LongSparseArray;
import com.android.car.CarLog;
@@ -48,7 +49,6 @@
import java.io.File;
import java.io.FileInputStream;
-import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.time.Duration;
@@ -130,7 +130,7 @@
.build();
private final StatsManagerProxy mStatsManager;
- private final File mSavedStatsConfigsFile;
+ private final AtomicFile mSavedStatsConfigsFile;
private final Handler mTelemetryHandler;
// True if the publisher is periodically pulling reports from StatsD.
@@ -150,25 +150,27 @@
StatsPublisher(
PublisherFailureListener failureListener,
StatsManagerProxy statsManager,
- File rootDirectory,
+ File publisherDirectory,
Handler telemetryHandler) {
super(failureListener);
mStatsManager = statsManager;
mTelemetryHandler = telemetryHandler;
- mSavedStatsConfigsFile = new File(rootDirectory, SAVED_STATS_CONFIGS_FILE);
+ mSavedStatsConfigsFile = new AtomicFile(
+ new File(publisherDirectory, SAVED_STATS_CONFIGS_FILE));
mSavedStatsConfigs = loadBundle();
}
/** Loads the PersistableBundle containing stats config keys and versions from disk. */
private PersistableBundle loadBundle() {
- try (FileInputStream fileInputStream = new FileInputStream(mSavedStatsConfigsFile)) {
- return PersistableBundle.readFromStream(fileInputStream);
- } catch (FileNotFoundException e) {
+ if (!mSavedStatsConfigsFile.getBaseFile().exists()) {
return new PersistableBundle();
+ }
+ try (FileInputStream fileInputStream = mSavedStatsConfigsFile.openRead()) {
+ return PersistableBundle.readFromStream(fileInputStream);
} catch (IOException e) {
// TODO(b/199947533): handle failure
- Slogf.e(CarLog.TAG_TELEMETRY,
- "Failed to read file " + mSavedStatsConfigsFile.getAbsolutePath(), e);
+ Slogf.e(CarLog.TAG_TELEMETRY, "Failed to read file "
+ + mSavedStatsConfigsFile.getBaseFile().getAbsolutePath(), e);
return new PersistableBundle();
}
}
@@ -179,12 +181,16 @@
mSavedStatsConfigsFile.delete();
return;
}
- try (FileOutputStream fileOutputStream = new FileOutputStream(mSavedStatsConfigsFile)) {
+ FileOutputStream fileOutputStream = null;
+ try {
+ fileOutputStream = mSavedStatsConfigsFile.startWrite();
mSavedStatsConfigs.writeToStream(fileOutputStream);
+ mSavedStatsConfigsFile.finishWrite(fileOutputStream);
} catch (IOException e) {
// TODO(b/199947533): handle failure
+ mSavedStatsConfigsFile.failWrite(fileOutputStream);
Slogf.e(CarLog.TAG_TELEMETRY,
- "Cannot write to " + mSavedStatsConfigsFile.getAbsolutePath()
+ "Cannot write to " + mSavedStatsConfigsFile.getBaseFile().getAbsolutePath()
+ ". Added stats config info is lost.", e);
}
}