Put a delay between individual statsd metrics processing.
Also move metrics processing to a JobService.
This is a low-risk change which moves code from
DeferredMetricsReader to ProcessMetricsJobService.
Fixes: 136098269
Test: manual
Change-Id: Ia2f77c911aba5812d7835c3e685ec63581cf0b36
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index d123d48..efc8729 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -266,5 +266,9 @@
android:name=".finalization.FinalScreenActivity"
android:theme="@style/SudThemeGlifV3.Light"
android:immersive="true" />
+
+ <service
+ android:name=".analytics.ProcessMetricsJobService"
+ android:permission="android.permission.BIND_JOB_SERVICE"/>
</application>
</manifest>
diff --git a/src/com/android/managedprovisioning/analytics/DeferredMetricsReader.java b/src/com/android/managedprovisioning/analytics/DeferredMetricsReader.java
index 82204db..1b7b81b 100644
--- a/src/com/android/managedprovisioning/analytics/DeferredMetricsReader.java
+++ b/src/com/android/managedprovisioning/analytics/DeferredMetricsReader.java
@@ -20,26 +20,25 @@
import com.android.managedprovisioning.common.ProvisionLogger;
import static com.android.internal.util.Preconditions.checkNotNull;
+import static com.android.managedprovisioning.analytics.ProcessMetricsJobService.EXTRA_FILE_PATH;
-import android.annotation.Nullable;
-import android.app.admin.DevicePolicyEventLogger;
-import android.os.AsyncTask;
+import android.app.job.JobInfo;
+import android.app.job.JobScheduler;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.PersistableBundle;
import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
/**
- * Reads the logs from the {@link InputStream} written to by {@link DeferredMetricsWriter}
- * and writes them to another {@link MetricsWriter}.
- *
- * @see DeferredMetricsWriter
+ * Schedules the reading of the metrics written by {@link DeferredMetricsWriter}.
*/
public class DeferredMetricsReader {
- private final MetricsWriter mMetricsWriter;
+ private static final ComponentName PROCESS_METRICS_SERVICE_COMPONENT = new ComponentName(
+ "com.android.managedprovisioning", ProcessMetricsJobService.class.getName());
+ private static final int JOB_ID = 1;
+ private static final long MINIMUM_LATENCY = 10 * 60 * 1000;
private final File mFile;
/**
@@ -47,58 +46,22 @@
*
* <p>The specified {@link File} is deleted after everything has been read from it.
*/
- public DeferredMetricsReader(File file, MetricsWriter metricsWriter) {
- mMetricsWriter = checkNotNull(metricsWriter);
+ public DeferredMetricsReader(File file) {
mFile = checkNotNull(file);
}
- /**
- * Asynchronously reads the logs from the {@link File} specified in the constructor
- * and writes them to the specified {@link MetricsWriter}.
- *
- * <p>The {@link File} will be deleted after they are written to the {@link MetricsWriter}.
- */
- public void dumpMetricsAndClearFile() {
- new ReadDeferredMetricsAsyncTask(mFile, mMetricsWriter).execute();
- }
-
- private static class ReadDeferredMetricsAsyncTask extends AsyncTask<Void, Void, Void> {
- private final MetricsWriter mMetricsWriter;
- private final File mFile;
-
- ReadDeferredMetricsAsyncTask(File file,
- MetricsWriter metricsWriter) {
- mFile = checkNotNull(file);
- mMetricsWriter = checkNotNull(metricsWriter);
- }
-
- @Override
- protected Void doInBackground(Void... voids) {
- try (InputStream inputStream = new FileInputStream(mFile)) {
- DevicePolicyEvent event;
- while ((event = DevicePolicyEvent.parseDelimitedFrom(inputStream)) != null) {
- mMetricsWriter.write(devicePolicyEventToLogger(event));
- }
- } catch (IOException e) {
- ProvisionLogger.loge(
- "Could not parse DevicePolicyEvent while reading from stream.", e);
- } finally {
- mFile.delete();
- }
- return null;
- }
-
- private DevicePolicyEventLogger devicePolicyEventToLogger(DevicePolicyEvent event) {
- final DevicePolicyEventLogger eventLogger = DevicePolicyEventLogger
- .createEvent(event.getEventId())
- .setAdmin(event.getAdminPackageName())
- .setInt(event.getIntegerValue())
- .setBoolean(event.getBooleanValue())
- .setTimePeriod(event.getTimePeriodMillis());
- if (event.getStringListValueCount() > 0) {
- eventLogger.setStrings(event.getStringListValueList().toArray(new String[0]));
- }
- return eventLogger;
+ public void scheduleDumpMetrics(Context context) {
+ final JobInfo jobInfo = new JobInfo.Builder(JOB_ID, PROCESS_METRICS_SERVICE_COMPONENT)
+ .setExtras(PersistableBundle.forPair(EXTRA_FILE_PATH, mFile.getAbsolutePath()))
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+ .setMinimumLatency(MINIMUM_LATENCY)
+ .setPersisted(true)
+ .build();
+ final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+ if (jobScheduler != null) {
+ jobScheduler.schedule(jobInfo);
+ } else {
+ ProvisionLogger.logv("JobScheduler is null.");
}
}
}
diff --git a/src/com/android/managedprovisioning/analytics/ProcessMetricsJobService.java b/src/com/android/managedprovisioning/analytics/ProcessMetricsJobService.java
new file mode 100644
index 0000000..fd144a2
--- /dev/null
+++ b/src/com/android/managedprovisioning/analytics/ProcessMetricsJobService.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2019 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.managedprovisioning.analytics;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.app.admin.DevicePolicyEventLogger;
+import android.app.job.JobParameters;
+import android.app.job.JobService;
+import android.os.AsyncTask;
+import android.os.PersistableBundle;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.managedprovisioning.DevicePolicyProtos.DevicePolicyEvent;
+import com.android.managedprovisioning.common.ProvisionLogger;
+
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * A {@link JobService} that reads the logs from the {@link InputStream} written to by
+ * {@link DeferredMetricsWriter} and writes them using another {@link MetricsWriter}.
+ *
+ * @see DeferredMetricsWriter
+ */
+public class ProcessMetricsJobService extends JobService {
+
+ static String EXTRA_FILE_PATH = "extra_file_path";
+
+ private final MetricsWriter mMetricsWriter;
+
+ @VisibleForTesting
+ ProcessMetricsJobService(MetricsWriter metricsWriter) {
+ mMetricsWriter = metricsWriter;
+ }
+
+ public ProcessMetricsJobService() {
+ this(new InstantMetricsWriter());
+ }
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ final PersistableBundle extras = params.getExtras();
+ if (extras == null || !extras.containsKey(EXTRA_FILE_PATH)) {
+ return false;
+ }
+ final File metrics = new File(extras.getString(EXTRA_FILE_PATH));
+ if (!metrics.exists()) {
+ return false;
+ }
+ executeReadDeferredMetrics(params, metrics);
+ return true;
+ }
+
+ @VisibleForTesting
+ void executeReadDeferredMetrics(JobParameters params,
+ File metricsFile) {
+ new ReadDeferredMetricsAsyncTask(params, metricsFile, mMetricsWriter).execute();
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ return false;
+ }
+
+ /**
+ * An {@link AsyncTask} which reads the logs from the {@link File} specified in the constructor
+ * and writes them to the specified {@link MetricsWriter}.
+ *
+ * <p>The {@link File} will be deleted after they are written to the {@link MetricsWriter}.
+ */
+ private class ReadDeferredMetricsAsyncTask extends AsyncTask<Void, Void, Void> {
+ private static final int METRICS_INTERVAL_MILLIS = 10;
+ private final MetricsWriter mMetricsWriter;
+ private final File mFile;
+ private final JobParameters mJobParameters;
+
+ ReadDeferredMetricsAsyncTask(JobParameters params,
+ File file,
+ MetricsWriter metricsWriter) {
+ mFile = checkNotNull(file);
+ mMetricsWriter = metricsWriter;
+ mJobParameters = params;
+ }
+
+ @Override
+ protected Void doInBackground(Void... voids) {
+ try (InputStream inputStream = new FileInputStream(mFile)) {
+ DevicePolicyEvent event;
+ while ((event = DevicePolicyEvent.parseDelimitedFrom(inputStream)) != null) {
+ delayProcessMetric();
+ mMetricsWriter.write(devicePolicyEventToLogger(event));
+ }
+ } catch (IOException e) {
+ ProvisionLogger.loge(
+ "Could not parse DevicePolicyEvent while reading from stream.", e);
+ } finally {
+ mFile.delete();
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void aVoid) {
+ jobFinished(mJobParameters, false);
+ }
+
+ /**
+ * Waits for {@link #METRICS_INTERVAL_MILLIS}.
+ * <p>statsd cannot handle too many metrics at once, so we must wait between each
+ * {@link MetricsWriter#write(DevicePolicyEventLogger...)} call.
+ */
+ private void delayProcessMetric() {
+ try {
+ Thread.sleep(METRICS_INTERVAL_MILLIS);
+ } catch (InterruptedException e) {
+ ProvisionLogger.loge(
+ "Thread interrupted while waiting to log metric.", e);
+ }
+ }
+
+ private DevicePolicyEventLogger devicePolicyEventToLogger(DevicePolicyEvent event) {
+ final DevicePolicyEventLogger eventLogger = DevicePolicyEventLogger
+ .createEvent(event.getEventId())
+ .setAdmin(event.getAdminPackageName())
+ .setInt(event.getIntegerValue())
+ .setBoolean(event.getBooleanValue())
+ .setTimePeriod(event.getTimePeriodMillis());
+ if (event.getStringListValueCount() > 0) {
+ eventLogger.setStrings(event.getStringListValueList().toArray(new String[0]));
+ }
+ return eventLogger;
+ }
+ }
+}
diff --git a/src/com/android/managedprovisioning/finalization/FinalizationController.java b/src/com/android/managedprovisioning/finalization/FinalizationController.java
index 2948d2f..1ea806e 100644
--- a/src/com/android/managedprovisioning/finalization/FinalizationController.java
+++ b/src/com/android/managedprovisioning/finalization/FinalizationController.java
@@ -31,10 +31,7 @@
import android.os.UserHandle;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.managedprovisioning.R;
import com.android.managedprovisioning.analytics.DeferredMetricsReader;
-import com.android.managedprovisioning.analytics.InstantMetricsWriter;
-import com.android.managedprovisioning.analytics.MetricsWriterFactory;
import com.android.managedprovisioning.common.NotificationHelper;
import com.android.managedprovisioning.common.ProvisionLogger;
import com.android.managedprovisioning.common.SettingsFacade;
@@ -43,8 +40,6 @@
import com.android.managedprovisioning.provisioning.Constants;
import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
/**
* Controller for the finalization of managed provisioning.
@@ -74,8 +69,7 @@
userProvisioningStateHelper,
new NotificationHelper(context),
new DeferredMetricsReader(
- Constants.getDeferredMetricsFile(context),
- new InstantMetricsWriter()));
+ Constants.getDeferredMetricsFile(context)));
}
public FinalizationController(Context context) {
@@ -86,8 +80,7 @@
new UserProvisioningStateHelper(context),
new NotificationHelper(context),
new DeferredMetricsReader(
- Constants.getDeferredMetricsFile(context),
- new InstantMetricsWriter()));
+ Constants.getDeferredMetricsFile(context)));
}
@VisibleForTesting
@@ -181,7 +174,7 @@
* provisioning and sets the right user provisioning states.</p>
*/
void provisioningFinalized() {
- mDeferredMetricsReader.dumpMetricsAndClearFile();
+ mDeferredMetricsReader.scheduleDumpMetrics(mContext);
if (mUserProvisioningStateHelper.isStateUnmanagedOrFinalized()) {
ProvisionLogger.logw("provisioningInitiallyDone called, but state is finalized or "
diff --git a/tests/instrumentation/src/com/android/managedprovisioning/finalization/FinalizationControllerTest.java b/tests/instrumentation/src/com/android/managedprovisioning/finalization/FinalizationControllerTest.java
index 579050b..9611a62 100644
--- a/tests/instrumentation/src/com/android/managedprovisioning/finalization/FinalizationControllerTest.java
+++ b/tests/instrumentation/src/com/android/managedprovisioning/finalization/FinalizationControllerTest.java
@@ -33,8 +33,6 @@
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
-import android.app.Activity;
-import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -122,7 +120,7 @@
mController.provisioningFinalized();
// THEN deferred metrics are written
- verify(mDeferredMetricsReader).dumpMetricsAndClearFile();
+ verify(mDeferredMetricsReader).scheduleDumpMetrics(any(Context.class));
verifyNoMoreInteractions(mDeferredMetricsReader);
// THEN nothing should happen
@@ -139,7 +137,7 @@
mController.provisioningFinalized();
// THEN deferred metrics are written
- verify(mDeferredMetricsReader).dumpMetricsAndClearFile();
+ verify(mDeferredMetricsReader).scheduleDumpMetrics(any(Context.class));
verifyNoMoreInteractions(mDeferredMetricsReader);
// THEN nothing should happen
@@ -198,7 +196,7 @@
mController.provisioningFinalized();
// THEN deferred metrics are written
- verify(mDeferredMetricsReader).dumpMetricsAndClearFile();
+ verify(mDeferredMetricsReader).scheduleDumpMetrics(any(Context.class));
verifyNoMoreInteractions(mDeferredMetricsReader);
// THEN the user provisioning state is finalized
@@ -232,7 +230,7 @@
mController.provisioningFinalized();
// THEN deferred metrics are written
- verify(mDeferredMetricsReader).dumpMetricsAndClearFile();
+ verify(mDeferredMetricsReader).scheduleDumpMetrics(any(Context.class));
verifyNoMoreInteractions(mDeferredMetricsReader);
// THEN the user provisioning state is finalized
diff --git a/tests/robotests/src/com/android/managedprovisioning/analytics/DeferredMetricsWriteReadRoboTest.java b/tests/robotests/src/com/android/managedprovisioning/analytics/DeferredMetricsWriteReadRoboTest.java
index 4bff123..5a6ee5a 100644
--- a/tests/robotests/src/com/android/managedprovisioning/analytics/DeferredMetricsWriteReadRoboTest.java
+++ b/tests/robotests/src/com/android/managedprovisioning/analytics/DeferredMetricsWriteReadRoboTest.java
@@ -20,17 +20,12 @@
import android.app.admin.DevicePolicyEventLogger;
-import com.android.managedprovisioning.DevicePolicyProtos.DevicePolicyEvent;
-
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
import java.io.File;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -71,9 +66,9 @@
private DevicePolicyEventLogger[] readMetricsFromFile(File file) {
final List<DevicePolicyEventLogger> eventsList = new ArrayList<>();
- final DeferredMetricsReader reader = new DeferredMetricsReader(file,
+ final ProcessMetricsJobService processMetricsJobService = new ProcessMetricsJobService(
loggers -> eventsList.addAll(Arrays.asList(loggers)));
- reader.dumpMetricsAndClearFile();
+ processMetricsJobService.executeReadDeferredMetrics(/* params */ null, file);
Robolectric.flushBackgroundThreadScheduler();
return eventsList.toArray(new DevicePolicyEventLogger[0]);
}
diff --git a/tests/robotests/src/com/android/managedprovisioning/analytics/InstantMetricsWriterTest.java b/tests/robotests/src/com/android/managedprovisioning/analytics/InstantMetricsWriterTest.java
index ceab313..0c30508 100644
--- a/tests/robotests/src/com/android/managedprovisioning/analytics/InstantMetricsWriterTest.java
+++ b/tests/robotests/src/com/android/managedprovisioning/analytics/InstantMetricsWriterTest.java
@@ -36,7 +36,7 @@
final MetricsWriter writer = new InstantMetricsWriter();
final DevicePolicyEventLogger devicePolicyEventLogger = mock(DevicePolicyEventLogger.class);
- writer.write();
+ writer.write(devicePolicyEventLogger);
verify(devicePolicyEventLogger).write();
verifyNoMoreInteractions(devicePolicyEventLogger);