[automerger skipped] [Cherry-pick] Increase post-reboot sleep am: b96236cf75 am: 2b663bffda am: 1af36ac6f1 -s ours
am skip reason: Merged-In I978e2c37cb65afde3cf4b9d43297c48cb822aedb with SHA-1 8f4fc2b1db is already in history
Original change: https://android-review.googlesource.com/c/platform/packages/modules/StatsD/+/2806054
Change-Id: I0e72aae505daa27c95d05da56af10be669020bd7
Signed-off-by: Automerger Merge Worker <[email protected]>
diff --git a/.gitignore b/.gitignore
index ccff052..3c2d0cd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,8 @@
**/.idea
**/*.iml
**/*.ipr
+
+# VSCode
+**/.vscode/
+**/packages.modules.StatsD.code-workspace
+
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 73434a3..121ca17 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -27,7 +27,7 @@
"name" : "statsd_test"
}
],
- "hwasan-postsubmit" : [
+ "hwasan-presubmit" : [
{
"name" : "FrameworkStatsdTest"
},
diff --git a/aidl/Android.bp b/aidl/Android.bp
index 865c083..c79f0b8 100644
--- a/aidl/Android.bp
+++ b/aidl/Android.bp
@@ -26,11 +26,14 @@
name: "statsd-aidl",
unstable: true,
srcs: [
+ "android/os/StatsSubscriptionCallbackReason.aidl",
+ "android/os/IStatsSubscriptionCallback.aidl",
"android/os/IPendingIntentRef.aidl",
"android/os/IPullAtomCallback.aidl",
"android/os/IPullAtomResultReceiver.aidl",
"android/os/IStatsCompanionService.aidl",
"android/os/IStatsd.aidl",
+ "android/os/IStatsQueryCallback.aidl",
"android/os/StatsDimensionsValueParcel.aidl",
"android/util/PropertyParcel.aidl",
"android/util/StatsEventParcel.aidl",
diff --git a/aidl/android/os/IPendingIntentRef.aidl b/aidl/android/os/IPendingIntentRef.aidl
index 000a699..5a0f5d7 100644
--- a/aidl/android/os/IPendingIntentRef.aidl
+++ b/aidl/android/os/IPendingIntentRef.aidl
@@ -43,4 +43,10 @@
oneway void sendSubscriberBroadcast(long configUid, long configId, long subscriptionId,
long subscriptionRuleId, in String[] cookies,
in StatsDimensionsValueParcel dimensionsValueParcel);
+
+ /**
+ * Send a broadcast to the specified PendingIntent notifying it that the list of restricted
+ * metrics has changed.
+ */
+ oneway void sendRestrictedMetricsChangedBroadcast(in long[] metricIds);
}
diff --git a/aidl/android/os/IStatsManagerService.aidl b/aidl/android/os/IStatsManagerService.aidl
index b59a97e..ef1e4c6 100644
--- a/aidl/android/os/IStatsManagerService.aidl
+++ b/aidl/android/os/IStatsManagerService.aidl
@@ -18,6 +18,7 @@
import android.app.PendingIntent;
import android.os.IPullAtomCallback;
+import android.os.IStatsQueryCallback;
/**
* Binder interface to communicate with the Java-based statistics service helper.
@@ -131,6 +132,31 @@
oneway void registerPullAtomCallback(int atomTag, long coolDownMillis, long timeoutMillis,
in int[] additiveFields, IPullAtomCallback pullerCallback);
- /** Tell StatsManagerService to unregister the pulller for the given atom tag from statsd. */
+ /** Tell StatsManagerService to unregister the puller for the given atom tag from statsd. */
oneway void unregisterPullAtomCallback(int atomTag);
+
+ /** Section for restricted-logging methods. */
+
+ /** Queries data from underlying statsd sql store. */
+ oneway void querySql(in String sqlQuery, in int minSqlClientVersion,
+ in @nullable byte[] policyConfig, in IStatsQueryCallback queryCallback,
+ in long configKey, in String configPackage);
+
+ /**
+ * Registers the operation that is called whenever there is a change in the restricted metrics
+ * for a specified config that are present for this client. This operation allows statsd to
+ * inform the client about the current restricted metrics available to be queried for
+ * the specified config.
+ *
+ * Requires Manifest.permission.READ_RESTRICTED_STATS.
+ */
+ long[] setRestrictedMetricsChangedOperation(in PendingIntent pendingIntent, in long configKey,
+ in String configPackage);
+
+ /**
+ * Removes the restricted metrics changed operation for the specified config key/package.
+ *
+ * Requires Manifest.permission.READ_RESTRICTED_STATS.
+ */
+ void removeRestrictedMetricsChangedOperation(in long configKey, in String configPackage);
}
diff --git a/aidl/android/os/IStatsQueryCallback.aidl b/aidl/android/os/IStatsQueryCallback.aidl
new file mode 100644
index 0000000..911c816
--- /dev/null
+++ b/aidl/android/os/IStatsQueryCallback.aidl
@@ -0,0 +1,28 @@
+/* *
+ * Copyright (C) 2022 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 android.os;
+
+/**
+ * Binder interface to hold a Callback for Stats SQL queries.
+ * {@hide}
+ */
+interface IStatsQueryCallback {
+ oneway void sendResults(in String[] queryData, in String[] columnNames,
+ in int[] columnTypes, int rowCount);
+
+ oneway void sendFailure(String error);
+}
diff --git a/aidl/android/os/IStatsSubscriptionCallback.aidl b/aidl/android/os/IStatsSubscriptionCallback.aidl
new file mode 100644
index 0000000..abc2d35
--- /dev/null
+++ b/aidl/android/os/IStatsSubscriptionCallback.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 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 android.os;
+
+import android.os.StatsSubscriptionCallbackReason;
+
+/**
+ * Binder interface for callback that provides data for a subscription.
+ * {@hide}
+ */
+oneway interface IStatsSubscriptionCallback {
+ /**
+ * Send back subscription data.
+ */
+ void onSubscriptionData(StatsSubscriptionCallbackReason reason, in byte[] subscriptionPayload);
+}
diff --git a/aidl/android/os/IStatsd.aidl b/aidl/android/os/IStatsd.aidl
index 2a0fe69..2cbaf9d 100644
--- a/aidl/android/os/IStatsd.aidl
+++ b/aidl/android/os/IStatsd.aidl
@@ -16,10 +16,12 @@
package android.os;
+import android.os.IStatsSubscriptionCallback;
import android.os.IPendingIntentRef;
import android.os.IPullAtomCallback;
import android.os.ParcelFileDescriptor;
import android.util.PropertyParcel;
+import android.os.IStatsQueryCallback;
/**
* Binder interface to communicate with the statistics management service.
@@ -240,4 +242,64 @@
* Notifies of properties in statsd_java namespace.
*/
oneway void updateProperties(in PropertyParcel[] properties);
+
+ /** Section for restricted-logging methods. */
+ /**
+ * Queries data from underlying statsd sql store.
+ */
+ oneway void querySql(in String sqlQuery, in int minSqlClientVersion,
+ in @nullable byte[] policyConfig, in IStatsQueryCallback queryCallback,
+ in long configKey, in String configPackage, in int callingUid);
+
+ /**
+ * Registers the operation that is called whenever there is a change in the restricted metrics
+ * for a specified config that are present for this client. This operation allows statsd to
+ * inform the client about the current restricted metrics available to be queried for the
+ * specified config.
+ *
+ * Requires Manifest.permission.READ_RESTRICTED_STATS
+ */
+ long[] setRestrictedMetricsChangedOperation(in long configKey, in String configPackage,
+ in IPendingIntentRef pir, int callingUid);
+
+ /**
+ * Removes the restricted metrics changed operation for the specified config package/id.
+ *
+ * Requires Manifest.permission.READ_RESTRICTED_STATS.
+ */
+ void removeRestrictedMetricsChangedOperation(in long configKey, in String configPackage,
+ in int callingUid);
+
+ /** Section for atoms subscription methods. */
+ /**
+ * Adds a subscription for atom events.
+ *
+ * IStatsSubscriptionCallback Binder interface will be used to deliver subscription data back to
+ * the subscriber. IStatsSubscriptionCallback also uniquely identifies this subscription - it
+ * should not be reused for another subscription.
+ *
+ * Enforces caller is in the traced_probes selinux domain.
+ */
+ oneway void addSubscription(in byte[] subscriptionConfig,
+ IStatsSubscriptionCallback callback);
+
+ /**
+ * Unsubscribe from a given subscription identified by the IBinder token.
+ *
+ * This will subsequently trigger IStatsSubscriptionCallback with pending data
+ * for this subscription.
+ *
+ * Enforces caller is in the traced_probes selinux domain.
+ */
+ oneway void removeSubscription(IStatsSubscriptionCallback callback);
+
+ /**
+ * Flush data for a subscription.
+ *
+ * This will subsequently trigger IStatsSubscriptionCallback with pending data
+ * for this subscription.
+ *
+ * Enforces caller is in the traced_probes selinux domain.
+ */
+ oneway void flushSubscription(IStatsSubscriptionCallback callback);
}
diff --git a/aidl/android/os/StatsSubscriptionCallbackReason.aidl b/aidl/android/os/StatsSubscriptionCallbackReason.aidl
new file mode 100644
index 0000000..5e3a945
--- /dev/null
+++ b/aidl/android/os/StatsSubscriptionCallbackReason.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 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 android.os;
+
+
+/**
+ * @hide
+ */
+@Backing(type="int")
+enum StatsSubscriptionCallbackReason {
+ STATSD_INITIATED = 1,
+ FLUSH_REQUESTED = 2,
+ SUBSCRIPTION_ENDED = 3,
+}
diff --git a/framework/Android.bp b/framework/Android.bp
index 634d818..360801b 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -46,15 +46,26 @@
],
visibility: ["//packages/modules/StatsD/framework:__subpackages__"],
}
+
java_sdk_library {
name: "framework-statsd",
defaults: ["framework-module-defaults"],
installable: true,
+ jarjar_rules: "jarjar-rules.txt",
+
srcs: [
":framework-statsd-sources",
],
+ libs: [
+ "androidx.annotation_annotation",
+ ],
+
+ static_libs: [
+ "modules-utils-build",
+ ],
+
permitted_packages: [
"android.app",
"android.os",
diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt
index c432a7f..41b5891 100644
--- a/framework/api/system-current.txt
+++ b/framework/api/system-current.txt
@@ -1,6 +1,18 @@
// Signature format: 2.0
package android.app {
+ public class StatsCursor extends android.database.AbstractCursor {
+ method @NonNull public String[] getColumnNames();
+ method public int getCount();
+ method public double getDouble(int);
+ method public float getFloat(int);
+ method public int getInt(int);
+ method public long getLong(int);
+ method public short getShort(int);
+ method @NonNull public String getString(int);
+ method public boolean isNull(int);
+ }
+
public final class StatsManager {
method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void addConfig(long, byte[]) throws android.app.StatsManager.StatsUnavailableException;
method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public boolean addConfiguration(long, byte[]);
@@ -10,6 +22,7 @@
method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public long[] getRegisteredExperimentIds() throws android.app.StatsManager.StatsUnavailableException;
method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public byte[] getReports(long) throws android.app.StatsManager.StatsUnavailableException;
method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public byte[] getStatsMetadata() throws android.app.StatsManager.StatsUnavailableException;
+ method @RequiresPermission(android.Manifest.permission.READ_RESTRICTED_STATS) public void query(long, @NonNull String, @NonNull android.app.StatsQuery, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.StatsCursor,android.app.StatsManager.StatsQueryException>) throws android.app.StatsManager.StatsUnavailableException;
method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void removeConfig(long) throws android.app.StatsManager.StatsUnavailableException;
method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public boolean removeConfiguration(long);
method @NonNull @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public long[] setActiveConfigsChangedOperation(@Nullable android.app.PendingIntent) throws android.app.StatsManager.StatsUnavailableException;
@@ -18,12 +31,14 @@
method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public boolean setDataFetchOperation(long, android.app.PendingIntent);
method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void setFetchReportsOperation(android.app.PendingIntent, long) throws android.app.StatsManager.StatsUnavailableException;
method @RequiresPermission(android.Manifest.permission.REGISTER_STATS_PULL_ATOM) public void setPullAtomCallback(int, @Nullable android.app.StatsManager.PullAtomMetadata, @NonNull java.util.concurrent.Executor, @NonNull android.app.StatsManager.StatsPullAtomCallback);
+ method @NonNull @RequiresPermission(android.Manifest.permission.READ_RESTRICTED_STATS) public long[] setRestrictedMetricsChangedOperation(long, @NonNull String, @Nullable android.app.PendingIntent) throws android.app.StatsManager.StatsUnavailableException;
field public static final String ACTION_STATSD_STARTED = "android.app.action.STATSD_STARTED";
field public static final String EXTRA_STATS_ACTIVE_CONFIG_KEYS = "android.app.extra.STATS_ACTIVE_CONFIG_KEYS";
field public static final String EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES = "android.app.extra.STATS_BROADCAST_SUBSCRIBER_COOKIES";
field public static final String EXTRA_STATS_CONFIG_KEY = "android.app.extra.STATS_CONFIG_KEY";
field public static final String EXTRA_STATS_CONFIG_UID = "android.app.extra.STATS_CONFIG_UID";
field public static final String EXTRA_STATS_DIMENSIONS_VALUE = "android.app.extra.STATS_DIMENSIONS_VALUE";
+ field public static final String EXTRA_STATS_RESTRICTED_METRIC_IDS = "android.app.extra.STATS_RESTRICTED_METRIC_IDS";
field public static final String EXTRA_STATS_SUBSCRIPTION_ID = "android.app.extra.STATS_SUBSCRIPTION_ID";
field public static final String EXTRA_STATS_SUBSCRIPTION_RULE_ID = "android.app.extra.STATS_SUBSCRIPTION_RULE_ID";
field public static final int PULL_SKIP = 1; // 0x1
@@ -48,11 +63,33 @@
method public int onPullAtom(int, @NonNull java.util.List<android.util.StatsEvent>);
}
+ public static class StatsManager.StatsQueryException extends android.util.AndroidException {
+ ctor public StatsManager.StatsQueryException(@NonNull String);
+ ctor public StatsManager.StatsQueryException(@NonNull String, @NonNull Throwable);
+ }
+
public static class StatsManager.StatsUnavailableException extends android.util.AndroidException {
ctor public StatsManager.StatsUnavailableException(String);
ctor public StatsManager.StatsUnavailableException(String, Throwable);
}
+ public final class StatsQuery {
+ method @IntRange(from=0) public int getMinSqlClientVersion();
+ method @Nullable public byte[] getPolicyConfig();
+ method @NonNull public String getRawSql();
+ method public int getSqlDialect();
+ field public static final int DIALECT_SQLITE = 1; // 0x1
+ field public static final int DIALECT_UNKNOWN = 0; // 0x0
+ }
+
+ public static final class StatsQuery.Builder {
+ ctor public StatsQuery.Builder(@NonNull String);
+ method @NonNull public android.app.StatsQuery build();
+ method @NonNull public android.app.StatsQuery.Builder setMinSqlClientVersion(@IntRange(from=0) int);
+ method @NonNull public android.app.StatsQuery.Builder setPolicyConfig(@NonNull byte[]);
+ method @NonNull public android.app.StatsQuery.Builder setSqlDialect(int);
+ }
+
}
package android.os {
@@ -112,12 +149,26 @@
method @Deprecated public static void writeRaw(@NonNull byte[], int);
field public static final byte ANNOTATION_ID_DEFAULT_STATE = 6; // 0x6
field public static final byte ANNOTATION_ID_EXCLUSIVE_STATE = 4; // 0x4
+ field public static final byte ANNOTATION_ID_FIELD_RESTRICTION_ACCESSIBILITY = 14; // 0xe
+ field public static final byte ANNOTATION_ID_FIELD_RESTRICTION_AMBIENT_SENSING = 17; // 0x11
+ field public static final byte ANNOTATION_ID_FIELD_RESTRICTION_APP_ACTIVITY = 12; // 0xc
+ field public static final byte ANNOTATION_ID_FIELD_RESTRICTION_APP_USAGE = 11; // 0xb
+ field public static final byte ANNOTATION_ID_FIELD_RESTRICTION_DEMOGRAPHIC_CLASSIFICATION = 18; // 0x12
+ field public static final byte ANNOTATION_ID_FIELD_RESTRICTION_HEALTH_CONNECT = 13; // 0xd
+ field public static final byte ANNOTATION_ID_FIELD_RESTRICTION_PERIPHERAL_DEVICE_INFO = 10; // 0xa
+ field public static final byte ANNOTATION_ID_FIELD_RESTRICTION_SYSTEM_SEARCH = 15; // 0xf
+ field public static final byte ANNOTATION_ID_FIELD_RESTRICTION_USER_ENGAGEMENT = 16; // 0x10
field public static final byte ANNOTATION_ID_IS_UID = 1; // 0x1
field public static final byte ANNOTATION_ID_PRIMARY_FIELD = 3; // 0x3
field public static final byte ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID = 5; // 0x5
+ field public static final byte ANNOTATION_ID_RESTRICTION_CATEGORY = 9; // 0x9
field public static final byte ANNOTATION_ID_STATE_NESTED = 8; // 0x8
field public static final byte ANNOTATION_ID_TRIGGER_STATE_RESET = 7; // 0x7
field public static final byte ANNOTATION_ID_TRUNCATE_TIMESTAMP = 2; // 0x2
+ field public static final int RESTRICTION_CATEGORY_AUTHENTICATION = 3; // 0x3
+ field public static final int RESTRICTION_CATEGORY_DIAGNOSTIC = 1; // 0x1
+ field public static final int RESTRICTION_CATEGORY_FRAUD_AND_ABUSE = 4; // 0x4
+ field public static final int RESTRICTION_CATEGORY_SYSTEM_INTELLIGENCE = 2; // 0x2
}
}
diff --git a/framework/jarjar-rules.txt b/framework/jarjar-rules.txt
new file mode 100644
index 0000000..c78153d
--- /dev/null
+++ b/framework/jarjar-rules.txt
@@ -0,0 +1 @@
+rule com.android.modules.utils.** com.android.internal.statsd.@0
\ No newline at end of file
diff --git a/framework/java/android/app/StatsCursor.java b/framework/java/android/app/StatsCursor.java
new file mode 100644
index 0000000..29cd241
--- /dev/null
+++ b/framework/java/android/app/StatsCursor.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2023 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 android.app;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.annotation.SuppressLint;
+import android.database.AbstractCursor;
+import android.database.MatrixCursor;
+
+/**
+ * Custom cursor implementation to hold a cross-process cursor to pass data to caller.
+ *
+ * @hide
+ */
+@SystemApi
+public class StatsCursor extends AbstractCursor {
+ private final MatrixCursor mMatrixCursor;
+ private final int[] mColumnTypes;
+ private final String[] mColumnNames;
+ private final int mRowCount;
+
+ /**
+ * @hide
+ **/
+ public StatsCursor(String[] queryData, String[] columnNames, int[] columnTypes, int rowCount) {
+ mColumnTypes = columnTypes;
+ mColumnNames = columnNames;
+ mRowCount = rowCount;
+ mMatrixCursor = new MatrixCursor(columnNames);
+ for (int i = 0; i < rowCount; i++) {
+ MatrixCursor.RowBuilder builder = mMatrixCursor.newRow();
+ for (int j = 0; j < columnNames.length; j++) {
+ int dataIndex = i * columnNames.length + j;
+ builder.add(columnNames[j], queryData[dataIndex]);
+ }
+ }
+ }
+
+ /**
+ * Returns the numbers of rows in the cursor.
+ *
+ * @return the number of rows in the cursor.
+ */
+ @Override
+ public int getCount() {
+ return mRowCount;
+ }
+
+ /**
+ * Returns a string array holding the names of all of the columns in the
+ * result set in the order in which they were listed in the result.
+ *
+ * @return the names of the columns returned in this query.
+ */
+ @Override
+ @NonNull
+ public String[] getColumnNames() {
+ return mColumnNames;
+ }
+
+ /**
+ * Returns the value of the requested column as a String.
+ *
+ * @param column the zero-based index of the target column.
+ * @return the value of that column as a String.
+ */
+ @Override
+ @NonNull
+ public String getString(int column) {
+ return mMatrixCursor.getString(column);
+ }
+
+ /**
+ * Returns the value of the requested column as a short.
+ *
+ * @param column the zero-based index of the target column.
+ * @return the value of that column as a short.
+ */
+ @Override
+ @SuppressLint("NoByteOrShort")
+ public short getShort(int column) {
+ return mMatrixCursor.getShort(column);
+ }
+
+ /**
+ * Returns the value of the requested column as an int.
+ *
+ * @param column the zero-based index of the target column.
+ * @return the value of that column as an int.
+ */
+ @Override
+ public int getInt(int column) {
+ return mMatrixCursor.getInt(column);
+ }
+
+ /**
+ * Returns the value of the requested column as a long.
+ *
+ * @param column the zero-based index of the target column.
+ * @return the value of that column as a long.
+ */
+ @Override
+ public long getLong(int column) {
+ return mMatrixCursor.getLong(column);
+ }
+
+ /**
+ * Returns the value of the requested column as a float.
+ *
+ * @param column the zero-based index of the target column.
+ * @return the value of that column as a float.
+ */
+ @Override
+ public float getFloat(int column) {
+ return mMatrixCursor.getFloat(column);
+ }
+
+ /**
+ * Returns the value of the requested column as a double.
+ *
+ * @param column the zero-based index of the target column.
+ * @return the value of that column as a double.
+ */
+ @Override
+ public double getDouble(int column) {
+ return mMatrixCursor.getDouble(column);
+ }
+
+ /**
+ * Returns <code>true</code> if the value in the indicated column is null.
+ *
+ * @param column the zero-based index of the target column.
+ * @return whether the column value is null.
+ */
+ @Override
+ public boolean isNull(int column) {
+ return mMatrixCursor.isNull(column);
+ }
+
+ /**
+ * Returns the data type of the given column's value.
+ *
+ * @param column the zero-based index of the target column.
+ * @return column value type
+ */
+ @Override
+ public int getType(int column) {
+ return mColumnTypes[column];
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean onMove(int oldPosition, int newPosition) {
+ return mMatrixCursor.moveToPosition(newPosition);
+ }
+}
diff --git a/framework/java/android/app/StatsManager.java b/framework/java/android/app/StatsManager.java
index 92eb416..a0379fd 100644
--- a/framework/java/android/app/StatsManager.java
+++ b/framework/java/android/app/StatsManager.java
@@ -17,6 +17,7 @@
import static android.Manifest.permission.DUMP;
import static android.Manifest.permission.PACKAGE_USAGE_STATS;
+import static android.Manifest.permission.READ_RESTRICTED_STATS;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
@@ -25,9 +26,12 @@
import android.annotation.SystemApi;
import android.content.Context;
import android.os.Binder;
+import android.os.Build;
import android.os.IPullAtomCallback;
import android.os.IPullAtomResultReceiver;
import android.os.IStatsManagerService;
+import android.os.IStatsQueryCallback;
+import android.os.OutcomeReceiver;
import android.os.RemoteException;
import android.os.StatsFrameworkInitializer;
import android.util.AndroidException;
@@ -35,8 +39,11 @@
import android.util.StatsEvent;
import android.util.StatsEventParcel;
+import androidx.annotation.RequiresApi;
+
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.build.SdkLevel;
import java.util.ArrayList;
import java.util.List;
@@ -95,6 +102,12 @@
"android.app.extra.STATS_ACTIVE_CONFIG_KEYS";
/**
+ * Long array extra of the restricted metric ids present for the client.
+ */
+ public static final String EXTRA_STATS_RESTRICTED_METRIC_IDS =
+ "android.app.extra.STATS_RESTRICTED_METRIC_IDS";
+
+ /**
* Broadcast Action: Statsd has started.
* Configurations and PendingIntents can now be sent to it.
*/
@@ -120,7 +133,7 @@
/**
* @hide
**/
- @VisibleForTesting public static final long DEFAULT_TIMEOUT_MILLIS = 2_000L; // 2 seconds.
+ @VisibleForTesting public static final long DEFAULT_TIMEOUT_MILLIS = 1_500L; // 1.5 seconds.
/**
* Constructor for StatsManagerClient.
@@ -363,6 +376,115 @@
}
}
+ /**
+ * Registers the operation that is called whenever there is a change in the restricted metrics
+ * for a specified config that are present for this client. This operation allows statsd to
+ * inform the client about the current restricted metric ids available to be queried for the
+ * specified config. This call can block on statsd.
+ *
+ * If there is no config in statsd that matches the provided config package and key, an empty
+ * list is returned. The pending intent will be tracked, and the operation will be called
+ * whenever a matching config is added.
+ *
+ * @param configKey The configKey passed by the package that added the config in
+ * StatsManager#addConfig
+ * @param configPackage The package that added the config in StatsManager#addConfig
+ * @param pendingIntent the PendingIntent to use when broadcasting info to caller.
+ * May be null, in which case it removes any associated pending intent
+ * for this client.
+ * @return A list of metric ids identifying the restricted metrics that are currently available
+ * to be queried for the specified config.
+ * If the pendingIntent is null, this will be an empty list.
+ * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service
+ */
+ @RequiresPermission(READ_RESTRICTED_STATS)
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public @NonNull long[] setRestrictedMetricsChangedOperation(long configKey,
+ @NonNull String configPackage,
+ @Nullable PendingIntent pendingIntent)
+ throws StatsUnavailableException {
+ synchronized (sLock) {
+ try {
+ IStatsManagerService service = getIStatsManagerServiceLocked();
+ if (pendingIntent == null) {
+ service.removeRestrictedMetricsChangedOperation(configKey, configPackage);
+ return new long[0];
+ } else {
+ return service.setRestrictedMetricsChangedOperation(pendingIntent,
+ configKey, configPackage);
+ }
+
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to connect to statsmanager "
+ + "when registering restricted metrics listener.");
+ throw new StatsUnavailableException("could not connect", e);
+ } catch (SecurityException e) {
+ throw new StatsUnavailableException(e.getMessage(), e);
+ }
+ }
+ }
+
+ /**
+ * Queries the underlying service based on query received and populates the OutcomeReceiver via
+ * callback. This call is blocking on statsd being available, but is otherwise nonblocking.
+ * i.e. the call can return before the query processing is done.
+ * <p>
+ * Two types of tables are supported: Metric tables and the device information table.
+ * </p>
+ * <p>
+ * The device information table is named device_info and contains the following columns:
+ * sdkVersion, model, product, hardware, device, osBuild, fingerprint, brand, manufacturer, and
+ * board. These columns correspond to {@link Build.VERSION.SDK_INT}, {@link Build.MODEL},
+ * {@link Build.PRODUCT}, {@link Build.HARDWARE}, {@link Build.DEVICE}, {@link Build.ID},
+ * {@link Build.FINGERPRINT}, {@link Build.BRAND}, {@link Build.MANUFACTURER},
+ * {@link Build.BOARD} respectively.
+ * </p>
+ * <p>
+ * The metric tables are named metric_METRIC_ID where METRIC_ID is the metric id that is part
+ * of the wire encoded config passed to {@link #addConfig(long, byte[])}. If the metric id is
+ * negative, then the '-' character is replaced with 'n' in the table name. Each metric table
+ * contains the 3 columns followed by n columns of the following form: atomId,
+ * elapsedTimestampNs, wallTimestampNs, field_1, field_2, field_3 ... field_n. These
+ * columns correspond to to the id of the atom from frameworks/proto_logging/stats/atoms.proto,
+ * time when the atom is recorded, and the data fields within each atom.
+ * </p>
+ * @param configKey The configKey passed by the package that added
+ * the config being queried in StatsManager#addConfig
+ * @param configPackage The package that added the config being queried in
+ * StatsManager#addConfig
+ * @param query the query object encapsulating a sql-string and necessary config to query
+ * underlying sql-based data store.
+ * @param executor the executor on which outcomeReceiver will be invoked.
+ * @param outcomeReceiver the receiver to be populated with cursor pointing to result data.
+ */
+ @RequiresPermission(READ_RESTRICTED_STATS)
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public void query(long configKey, @NonNull String configPackage, @NonNull StatsQuery query,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<StatsCursor, StatsQueryException> outcomeReceiver)
+ throws StatsUnavailableException {
+ if(query.getSqlDialect() != StatsQuery.DIALECT_SQLITE) {
+ executor.execute(() -> {
+ outcomeReceiver.onError(new StatsQueryException("Unsupported Sql Dialect"));
+ });
+ return;
+ }
+
+ StatsQueryCallbackInternal callbackInternal =
+ new StatsQueryCallbackInternal(outcomeReceiver, executor);
+ synchronized (sLock) {
+ try {
+ IStatsManagerService service = getIStatsManagerServiceLocked();
+ service.querySql(query.getRawSql(), query.getMinSqlClientVersion(),
+ query.getPolicyConfig(), callbackInternal, configKey,
+ configPackage);
+ } catch (RemoteException | IllegalStateException e) {
+ throw new StatsUnavailableException("could not connect", e);
+ }
+ }
+ }
+
+
// TODO: Temporary for backwards compatibility. Remove.
/**
* @deprecated Use {@link #setFetchReportsOperation(PendingIntent, long)}
@@ -721,6 +843,52 @@
return mStatsManagerService;
}
+ private static class StatsQueryCallbackInternal extends IStatsQueryCallback.Stub {
+ OutcomeReceiver<StatsCursor, StatsQueryException> queryCallback;
+ Executor mExecutor;
+
+ StatsQueryCallbackInternal(OutcomeReceiver<StatsCursor, StatsQueryException> queryCallback,
+ @NonNull @CallbackExecutor Executor executor) {
+ this.queryCallback = queryCallback;
+ this.mExecutor = executor;
+ }
+
+ @Override
+ public void sendResults(String[] queryData, String[] columnNames, int[] columnTypes,
+ int rowCount) {
+ if (!SdkLevel.isAtLeastU()) {
+ throw new IllegalStateException(
+ "StatsManager#query is not available before Android U");
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> {
+ StatsCursor cursor = new StatsCursor(queryData, columnNames, columnTypes,
+ rowCount);
+ queryCallback.onResult(cursor);
+ });
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void sendFailure(String error) {
+ if (!SdkLevel.isAtLeastU()) {
+ throw new IllegalStateException(
+ "StatsManager#query is not available before Android U");
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> {
+ queryCallback.onError(new StatsQueryException(error));
+ });
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+
/**
* Exception thrown when communication with the stats service fails (eg if it is not available).
* This might be thrown early during boot before the stats service has started or if it crashed.
@@ -734,4 +902,18 @@
super("Failed to connect to statsd: " + reason, e);
}
}
+
+ /**
+ * Exception thrown when executing a query in statsd fails for any reason. This might be thrown
+ * if the query is malformed or if there is a database error when executing the query.
+ */
+ public static class StatsQueryException extends AndroidException {
+ public StatsQueryException(@NonNull String reason) {
+ super("Failed to query statsd: " + reason);
+ }
+
+ public StatsQueryException(@NonNull String reason, @NonNull Throwable e) {
+ super("Failed to query statsd: " + reason, e);
+ }
+ }
}
diff --git a/framework/java/android/app/StatsQuery.java b/framework/java/android/app/StatsQuery.java
new file mode 100644
index 0000000..a4a315c
--- /dev/null
+++ b/framework/java/android/app/StatsQuery.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2023 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 android.app;
+
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+/**
+ * Represents a query that contains information required for StatsManager to return relevant metric
+ * data.
+ *
+ * @hide
+ */
+@SystemApi
+public final class StatsQuery {
+ /**
+ * Default value for SQL dialect.
+ */
+ public static final int DIALECT_UNKNOWN = 0;
+
+ /**
+ * Query passed is of SQLite dialect.
+ */
+ public static final int DIALECT_SQLITE = 1;
+
+ /**
+ * @hide
+ */
+ @IntDef(prefix = {"DIALECT_"}, value = {DIALECT_UNKNOWN, DIALECT_SQLITE})
+ @interface SqlDialect {
+ }
+
+ private final int sqlDialect;
+ private final String rawSql;
+ private final int minClientSqlVersion;
+ private final byte[] policyConfig;
+ private StatsQuery(int sqlDialect, @NonNull String rawSql, int minClientSqlVersion,
+ @Nullable byte[] policyConfig) {
+ this.sqlDialect = sqlDialect;
+ this.rawSql = rawSql;
+ this.minClientSqlVersion = minClientSqlVersion;
+ this.policyConfig = policyConfig;
+ }
+
+ /**
+ * Returns the SQL dialect of the query.
+ */
+ public @SqlDialect int getSqlDialect() {
+ return sqlDialect;
+ }
+
+ /**
+ * Returns the raw SQL of the query.
+ */
+ @NonNull
+ public String getRawSql() {
+ return rawSql;
+ }
+
+ /**
+ * Returns the minimum SQL client library version required to execute the query.
+ */
+ @IntRange(from = 0)
+ public int getMinSqlClientVersion() {
+ return minClientSqlVersion;
+ }
+
+ /**
+ * Returns the wire-encoded StatsPolicyConfig proto that contains information to verify the
+ * query against a policy defined on the underlying data. Returns null if no policy was set.
+ */
+ @Nullable
+ public byte[] getPolicyConfig() {
+ return policyConfig;
+ }
+
+ /**
+ * Builder for constructing a StatsQuery object.
+ * <p>Usage:</p>
+ * <code>
+ * StatsQuery statsQuery = new StatsQuery.Builder("SELECT * from table")
+ * .setSqlDialect(StatsQuery.DIALECT_SQLITE)
+ * .setMinClientSqlVersion(1)
+ * .build();
+ * </code>
+ */
+ public static final class Builder {
+ private int sqlDialect;
+ private String rawSql;
+ private int minSqlClientVersion;
+ private byte[] policyConfig;
+
+ /**
+ * Returns a new StatsQuery.Builder object for constructing StatsQuery for
+ * StatsManager#query
+ */
+ public Builder(@NonNull final String rawSql) {
+ if (rawSql == null) {
+ throw new IllegalArgumentException("rawSql must not be null");
+ }
+ this.rawSql = rawSql;
+ this.sqlDialect = DIALECT_SQLITE;
+ this.minSqlClientVersion = 1;
+ this.policyConfig = null;
+ }
+
+ /**
+ * Sets the SQL dialect of the query.
+ *
+ * @param sqlDialect The SQL dialect of the query.
+ */
+ @NonNull
+ public Builder setSqlDialect(@SqlDialect final int sqlDialect) {
+ this.sqlDialect = sqlDialect;
+ return this;
+ }
+
+ /**
+ * Sets the minimum SQL client library version required to execute the query.
+ *
+ * @param minSqlClientVersion The minimum SQL client version required to execute the query.
+ */
+ @NonNull
+ public Builder setMinSqlClientVersion(@IntRange(from = 0) final int minSqlClientVersion) {
+ if (minSqlClientVersion < 0) {
+ throw new IllegalArgumentException("minSqlClientVersion must be a "
+ + "positive integer");
+ }
+ this.minSqlClientVersion = minSqlClientVersion;
+ return this;
+ }
+
+ /**
+ * Sets the wire-encoded StatsPolicyConfig proto that contains information to verify the
+ * query against a policy defined on the underlying data.
+ *
+ * @param policyConfig The wire-encoded StatsPolicyConfig proto.
+ */
+ @NonNull
+ public Builder setPolicyConfig(@NonNull final byte[] policyConfig) {
+ this.policyConfig = policyConfig;
+ return this;
+ }
+
+ /**
+ * Builds a new instance of {@link StatsQuery}.
+ *
+ * @return A new instance of {@link StatsQuery}.
+ */
+ @NonNull
+ public StatsQuery build() {
+ return new StatsQuery(sqlDialect, rawSql, minSqlClientVersion, policyConfig);
+ }
+ }
+}
diff --git a/framework/java/android/util/StatsLog.java b/framework/java/android/util/StatsLog.java
index 28884c1..f38751a 100644
--- a/framework/java/android/util/StatsLog.java
+++ b/framework/java/android/util/StatsLog.java
@@ -20,17 +20,24 @@
import static android.Manifest.permission.PACKAGE_USAGE_STATS;
import android.Manifest;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.content.Context;
+import android.os.Build;
import android.os.IStatsd;
import android.os.Process;
import android.util.proto.ProtoOutputStream;
+import androidx.annotation.RequiresApi;
+
import com.android.internal.statsd.StatsdStatsLog;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* StatsLog provides an API for developers to send events to statsd. The events can be used to
* define custom metrics inside statsd.
@@ -46,80 +53,290 @@
private static final int EXPERIMENT_IDS_FIELD_ID = 1;
/**
- * Annotation ID constant for logging UID field.
- *
- * @hide
- */
+ * Annotation ID constant for logging UID field.
+ *
+ * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+ * accept byte as the type for annotation ids to save space.
+ *
+ * @hide
+ */
@SuppressLint("NoByteOrShort")
@SystemApi
public static final byte ANNOTATION_ID_IS_UID = 1;
/**
- * Annotation ID constant to indicate logged atom event's timestamp should be truncated.
- *
- * @hide
- */
+ * Annotation ID constant to indicate logged atom event's timestamp should be truncated.
+ *
+ * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+ * accept byte as the type for annotation ids to save space.
+ *
+ * @hide
+ */
@SuppressLint("NoByteOrShort")
@SystemApi
public static final byte ANNOTATION_ID_TRUNCATE_TIMESTAMP = 2;
/**
- * Annotation ID constant for a state atom's primary field.
- *
- * @hide
- */
+ * Annotation ID constant for a state atom's primary field.
+ *
+ * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+ * accept byte as the type for annotation ids to save space.
+ *
+ * @hide
+ */
@SuppressLint("NoByteOrShort")
@SystemApi
public static final byte ANNOTATION_ID_PRIMARY_FIELD = 3;
/**
- * Annotation ID constant for state atom's state field.
- *
- * @hide
- */
+ * Annotation ID constant for state atom's state field.
+ *
+ * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+ * accept byte as the type for annotation ids to save space.
+ *
+ * @hide
+ */
@SuppressLint("NoByteOrShort")
@SystemApi
public static final byte ANNOTATION_ID_EXCLUSIVE_STATE = 4;
/**
- * Annotation ID constant to indicate the first UID in the attribution chain
- * is a primary field.
- * Should only be used for attribution chain fields.
- *
- * @hide
- */
+ * Annotation ID constant to indicate the first UID in the attribution chain
+ * is a primary field.
+ * Should only be used for attribution chain fields.
+ *
+ * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+ * accept byte as the type for annotation ids to save space.
+ *
+ * @hide
+ */
@SuppressLint("NoByteOrShort")
@SystemApi
public static final byte ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID = 5;
/**
- * Annotation ID constant to indicate which state is default for the state atom.
- *
- * @hide
- */
+ * Annotation ID constant to indicate which state is default for the state atom.
+ *
+ * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+ * accept byte as the type for annotation ids to save space.
+ *
+ * @hide
+ */
@SuppressLint("NoByteOrShort")
@SystemApi
public static final byte ANNOTATION_ID_DEFAULT_STATE = 6;
/**
- * Annotation ID constant to signal all states should be reset to the default state.
- *
- * @hide
- */
+ * Annotation ID constant to signal all states should be reset to the default state.
+ *
+ * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+ * accept byte as the type for annotation ids to save space.
+ *
+ * @hide
+ */
@SuppressLint("NoByteOrShort")
@SystemApi
public static final byte ANNOTATION_ID_TRIGGER_STATE_RESET = 7;
/**
- * Annotation ID constant to indicate state changes need to account for nesting.
- * This should only be used with binary state atoms.
- *
- * @hide
- */
+ * Annotation ID constant to indicate state changes need to account for nesting.
+ * This should only be used with binary state atoms.
+ *
+ * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+ * accept byte as the type for annotation ids to save space.
+ *
+ * @hide
+ */
@SuppressLint("NoByteOrShort")
@SystemApi
public static final byte ANNOTATION_ID_STATE_NESTED = 8;
+ /**
+ * Annotation ID constant to indicate the restriction category of an atom.
+ * This annotation must only be attached to the atom id. This is an int annotation.
+ *
+ * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+ * accept byte as the type for annotation ids to save space.
+ *
+ * @hide
+ */
+ @SuppressLint("NoByteOrShort")
+ @SystemApi
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final byte ANNOTATION_ID_RESTRICTION_CATEGORY = 9;
+
+ /**
+ * Annotation ID to indicate that a field of an atom contains peripheral device info.
+ * This is a bool annotation.
+ *
+ * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+ * accept byte as the type for annotation ids to save space.
+ *
+ * @hide
+ */
+ @SuppressLint("NoByteOrShort")
+ @SystemApi
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final byte ANNOTATION_ID_FIELD_RESTRICTION_PERIPHERAL_DEVICE_INFO = 10;
+
+ /**
+ * Annotation ID to indicate that a field of an atom contains app usage information.
+ * This is a bool annotation.
+ *
+ * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+ * accept byte as the type for annotation ids to save space.
+ *
+ * @hide
+ */
+ @SuppressLint("NoByteOrShort")
+ @SystemApi
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final byte ANNOTATION_ID_FIELD_RESTRICTION_APP_USAGE = 11;
+
+ /**
+ * Annotation ID to indicate that a field of an atom contains app activity information.
+ * This is a bool annotation.
+ *
+ * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+ * accept byte as the type for annotation ids to save space.
+ *
+ * @hide
+ */
+ @SuppressLint("NoByteOrShort")
+ @SystemApi
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final byte ANNOTATION_ID_FIELD_RESTRICTION_APP_ACTIVITY = 12;
+
+ /**
+ * Annotation ID to indicate that a field of an atom contains health connect information.
+ * This is a bool annotation.
+ *
+ * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+ * accept byte as the type for annotation ids to save space.
+ *
+ * @hide
+ */
+ @SuppressLint("NoByteOrShort")
+ @SystemApi
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final byte ANNOTATION_ID_FIELD_RESTRICTION_HEALTH_CONNECT = 13;
+
+ /**
+ * Annotation ID to indicate that a field of an atom contains accessibility information.
+ * This is a bool annotation.
+ *
+ * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+ * accept byte as the type for annotation ids to save space.
+ *
+ * @hide
+ */
+ @SuppressLint("NoByteOrShort")
+ @SystemApi
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final byte ANNOTATION_ID_FIELD_RESTRICTION_ACCESSIBILITY = 14;
+
+ /**
+ * Annotation ID to indicate that a field of an atom contains system search information.
+ * This is a bool annotation.
+ *
+ * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+ * accept byte as the type for annotation ids to save space.
+ *
+ * @hide
+ */
+ @SuppressLint("NoByteOrShort")
+ @SystemApi
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final byte ANNOTATION_ID_FIELD_RESTRICTION_SYSTEM_SEARCH = 15;
+
+ /**
+ * Annotation ID to indicate that a field of an atom contains user engagement information.
+ * This is a bool annotation.
+ *
+ * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+ * accept byte as the type for annotation ids to save space.
+ *
+ * @hide
+ */
+ @SuppressLint("NoByteOrShort")
+ @SystemApi
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final byte ANNOTATION_ID_FIELD_RESTRICTION_USER_ENGAGEMENT = 16;
+
+ /**
+ * Annotation ID to indicate that a field of an atom contains ambient sensing information.
+ * This is a bool annotation.
+ *
+ * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+ * accept byte as the type for annotation ids to save space.
+ *
+ * @hide
+ */
+ @SuppressLint("NoByteOrShort")
+ @SystemApi
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final byte ANNOTATION_ID_FIELD_RESTRICTION_AMBIENT_SENSING = 17;
+
+ /**
+ * Annotation ID to indicate that a field of an atom contains demographic classification
+ * information. This is a bool annotation.
+ *
+ * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+ * accept byte as the type for annotation ids to save space.
+ *
+ * @hide
+ */
+ @SuppressLint("NoByteOrShort")
+ @SystemApi
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final byte ANNOTATION_ID_FIELD_RESTRICTION_DEMOGRAPHIC_CLASSIFICATION = 18;
+
+
+ /** @hide */
+ @IntDef(prefix = { "RESTRICTION_CATEGORY_" }, value = {
+ RESTRICTION_CATEGORY_DIAGNOSTIC,
+ RESTRICTION_CATEGORY_SYSTEM_INTELLIGENCE,
+ RESTRICTION_CATEGORY_AUTHENTICATION,
+ RESTRICTION_CATEGORY_FRAUD_AND_ABUSE})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RestrictionCategory {}
+
+ /**
+ * Restriction category for atoms about diagnostics.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final int RESTRICTION_CATEGORY_DIAGNOSTIC = 1;
+
+ /**
+ * Restriction category for atoms about system intelligence.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final int RESTRICTION_CATEGORY_SYSTEM_INTELLIGENCE = 2;
+
+ /**
+ * Restriction category for atoms about authentication.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final int RESTRICTION_CATEGORY_AUTHENTICATION = 3;
+
+ /**
+ * Restriction category for atoms about fraud and abuse.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final int RESTRICTION_CATEGORY_FRAUD_AND_ABUSE = 4;
+
private StatsLog() {
}
diff --git a/lib/libstatsgtestmatchers/Android.bp b/lib/libstatsgtestmatchers/Android.bp
new file mode 100644
index 0000000..fe832fa
--- /dev/null
+++ b/lib/libstatsgtestmatchers/Android.bp
@@ -0,0 +1,53 @@
+//
+// Copyright (C) 2023 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.
+//
+
+// ==========================================================
+// Native library for custom GoogleTest matchers
+// ==========================================================
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test_library {
+ name: "libstatsgtestmatchers",
+ srcs: [
+ ":libstats_log_protos",
+ ":libstats_subscription_protos",
+ ],
+ export_include_dirs: ["include"],
+ proto: {
+ type: "lite",
+ include_dirs: [
+ "external/protobuf/src",
+ ],
+ static: true,
+ },
+ static_libs: [
+ "libgmock",
+ "libprotobuf-cpp-lite",
+ ],
+ shared: {
+ enabled: false,
+ },
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+ visibility: [
+ "//packages/modules/StatsD/lib/libstatspull",
+ "//packages/modules/StatsD/statsd",
+ ],
+}
diff --git a/lib/libstatsgtestmatchers/include/gtest_matchers.h b/lib/libstatsgtestmatchers/include/gtest_matchers.h
new file mode 100644
index 0000000..084346d
--- /dev/null
+++ b/lib/libstatsgtestmatchers/include/gtest_matchers.h
@@ -0,0 +1,231 @@
+// Copyright (C) 2023 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.
+
+#pragma once
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <ostream>
+
+#include "frameworks/proto_logging/stats/atoms.pb.h"
+#include "frameworks/proto_logging/stats/attribution_node.pb.h"
+#include "packages/modules/StatsD/statsd/src/shell/shell_data.pb.h"
+#include "packages/modules/StatsD/statsd/src/stats_log.pb.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using PackageInfo = UidMapping_PackageInfoSnapshot_PackageInfo;
+
+// clang-format off
+
+#define PROPERTY_MATCHER(typename, propname, matcher) \
+ testing::Property(#propname, &typename::propname, matcher(expected.propname()))
+
+#define REPEATED_PROPERTY_MATCHER(typename, propname, matcher) \
+ testing::Property(#propname, &typename::propname, \
+ testing::Pointwise(matcher(), expected.propname()))
+
+#define PROPERTY_EQ(typename, propname) PROPERTY_MATCHER(typename, propname, testing::Eq)
+
+#define REPEATED_PROPERTY_EQ(typename, propname) \
+ REPEATED_PROPERTY_MATCHER(typename, propname, testing::Eq)
+
+/**
+ * Generate a GoogleTest equality matcher for a custom type specified by typename with the given
+ * properties.
+ *
+ * Example: For the protos:
+ * message Bar {
+ * optional int32 aa = 1;
+ * }
+ * message Foo {
+ * optional int32 a = 1;
+ * repeated float b = 2;
+ * optional Bar bar = 3;
+ * repeated Bar repeated_bar = 4;
+ * }
+ * This will generate equality matchers for them called EqBar() and EqFoo():
+ * EQ_MATCHER(Bar, PROPERTY_EQ(Bar, aa));
+ * EQ_MATCHER(Foo,
+ * PROPERTY_EQ(Foo, a),
+ * PROPERTY_EQ(Foo, b),
+ * PROPERTY_MATCHER(Foo, bar, EqBar),
+ * REPEATED_PROPERTY_MATCHER(Foo, repeated_bar, EqBar)
+ * );
+ */
+#define EQ_MATCHER(typename, properties...) \
+ MATCHER(Eq##typename, " ") { \
+ return testing::Matches(Eq##typename(std::get<1>(arg)))(std::get<0>(arg)); \
+ } \
+ MATCHER_P(Eq##typename, expected, testing::PrintToString(expected)) { \
+ return testing::ExplainMatchResult(testing::AllOf(properties), arg, result_listener); \
+ }
+
+#define PROPERTY_PRINT(propname) \
+ if (obj.has_##propname()) { \
+ *os << #propname << ": " << testing::PrintToString(obj.propname()) << ", "; \
+ }
+
+#define REPEATED_PROPERTY_PRINT(propname) \
+ if (obj.propname##_size() > 0) { \
+ *os << #propname << ": " << testing::PrintToString(obj.propname()) << ", "; \
+ }
+
+/**
+ * Generates a method that teaches GoogleTest how to print a custom type specified by typename
+ * with the given properties.
+ * The equality matcher generated by EQ_MATCHER for the given typename will use this generated
+ * printer method to print the object when the matcher fails.
+ *
+ * Example: For the protos:
+ * message Bar {
+ * optional int32 aa = 1;
+ * }
+ * message Foo {
+ * optional int32 a = 1;
+ * repeated float b = 2;
+ * optional Bar bar = 3;
+ * repeated Bar repeated_bar = 4;
+ * }
+ * This will generate printer methods for them:
+ * TYPE_PRINTER(Bar, PROPERTY_PRINT(aa));
+ * TYPE_PRINTER(Foo,
+ * PROPERTY_PRINT(a)
+ * PROPERTY_PRINT(b)
+ * PROPERTY_PRINT(bar)
+ * REPEATED_PROPERTY_PRINT(repeated_bar)
+ * );
+ */
+#define TYPE_PRINTER(typename, properties) \
+ inline void PrintTo(const typename& obj, std::ostream* os) { \
+ *os << #typename << ": { "; \
+ properties \
+ *os << "}"; \
+ }
+
+EQ_MATCHER(PackageInfo,
+ PROPERTY_EQ(PackageInfo, version),
+ PROPERTY_EQ(PackageInfo, uid),
+ PROPERTY_EQ(PackageInfo, deleted),
+ PROPERTY_EQ(PackageInfo, truncated_certificate_hash),
+ PROPERTY_EQ(PackageInfo, name_hash),
+ PROPERTY_EQ(PackageInfo, version_string_hash),
+ PROPERTY_EQ(PackageInfo, name),
+ PROPERTY_EQ(PackageInfo, version_string),
+ PROPERTY_EQ(PackageInfo, installer_index),
+ PROPERTY_EQ(PackageInfo, installer_hash),
+ PROPERTY_EQ(PackageInfo, installer)
+);
+TYPE_PRINTER(PackageInfo,
+ PROPERTY_PRINT(version)
+ PROPERTY_PRINT(uid)
+ PROPERTY_PRINT(deleted)
+ PROPERTY_PRINT(truncated_certificate_hash)
+ PROPERTY_PRINT(name_hash)
+ PROPERTY_PRINT(version_string_hash)
+ PROPERTY_PRINT(name)
+ PROPERTY_PRINT(version_string)
+ PROPERTY_PRINT(installer_index)
+ PROPERTY_PRINT(installer_hash)
+ PROPERTY_PRINT(installer)
+);
+
+EQ_MATCHER(AttributionNode,
+ PROPERTY_EQ(AttributionNode, uid),
+ PROPERTY_EQ(AttributionNode, tag)
+);
+TYPE_PRINTER(AttributionNode,
+ PROPERTY_PRINT(uid)
+ PROPERTY_PRINT(tag)
+);
+
+EQ_MATCHER(ScreenStateChanged, PROPERTY_EQ(ScreenStateChanged, state));
+TYPE_PRINTER(ScreenStateChanged, PROPERTY_PRINT(state));
+
+EQ_MATCHER(TrainExperimentIds,
+ REPEATED_PROPERTY_EQ(TrainExperimentIds, experiment_id)
+);
+TYPE_PRINTER(TrainExperimentIds, REPEATED_PROPERTY_PRINT(experiment_id));
+
+EQ_MATCHER(TestAtomReported,
+ REPEATED_PROPERTY_MATCHER(TestAtomReported, attribution_node, EqAttributionNode),
+ PROPERTY_EQ(TestAtomReported, int_field),
+ PROPERTY_EQ(TestAtomReported, long_field),
+ PROPERTY_EQ(TestAtomReported, float_field),
+ PROPERTY_EQ(TestAtomReported, string_field),
+ PROPERTY_EQ(TestAtomReported, boolean_field),
+ PROPERTY_EQ(TestAtomReported, state),
+ PROPERTY_MATCHER(TestAtomReported, bytes_field, EqTrainExperimentIds),
+ REPEATED_PROPERTY_EQ(TestAtomReported, repeated_int_field),
+ REPEATED_PROPERTY_EQ(TestAtomReported, repeated_long_field),
+ REPEATED_PROPERTY_EQ(TestAtomReported, repeated_float_field),
+ REPEATED_PROPERTY_EQ(TestAtomReported, repeated_string_field),
+ REPEATED_PROPERTY_EQ(TestAtomReported, repeated_boolean_field),
+ REPEATED_PROPERTY_EQ(TestAtomReported, repeated_enum_field)
+);
+TYPE_PRINTER(TestAtomReported,
+ REPEATED_PROPERTY_PRINT(attribution_node)
+ PROPERTY_PRINT(int_field)
+ PROPERTY_PRINT(long_field)
+ PROPERTY_PRINT(float_field)
+ PROPERTY_PRINT(string_field)
+ PROPERTY_PRINT(boolean_field)
+ PROPERTY_PRINT(state)
+ PROPERTY_PRINT(bytes_field)
+ REPEATED_PROPERTY_PRINT(repeated_int_field)
+ REPEATED_PROPERTY_PRINT(repeated_long_field)
+ REPEATED_PROPERTY_PRINT(repeated_float_field)
+ REPEATED_PROPERTY_PRINT(repeated_string_field)
+ REPEATED_PROPERTY_PRINT(repeated_boolean_field)
+ REPEATED_PROPERTY_PRINT(repeated_enum_field)
+);
+
+EQ_MATCHER(CpuActiveTime,
+ PROPERTY_EQ(CpuActiveTime, uid),
+ PROPERTY_EQ(CpuActiveTime, time_millis)
+);
+TYPE_PRINTER(CpuActiveTime,
+ PROPERTY_PRINT(uid)
+ PROPERTY_PRINT(time_millis)
+);
+
+EQ_MATCHER(PluggedStateChanged, PROPERTY_EQ(PluggedStateChanged, state));
+TYPE_PRINTER(PluggedStateChanged, PROPERTY_PRINT(state));
+
+EQ_MATCHER(Atom,
+ PROPERTY_MATCHER(Atom, screen_state_changed, EqScreenStateChanged),
+ PROPERTY_MATCHER(Atom, test_atom_reported, EqTestAtomReported)
+);
+TYPE_PRINTER(Atom,
+ PROPERTY_PRINT(screen_state_changed)
+ PROPERTY_PRINT(test_atom_reported)
+);
+
+EQ_MATCHER(ShellData,
+ REPEATED_PROPERTY_MATCHER(ShellData, atom, EqAtom),
+ REPEATED_PROPERTY_EQ(ShellData, elapsed_timestamp_nanos)
+);
+TYPE_PRINTER(ShellData,
+ REPEATED_PROPERTY_PRINT(atom)
+ REPEATED_PROPERTY_PRINT(elapsed_timestamp_nanos)
+);
+
+// clang-format on
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/lib/libstatspull/Android.bp b/lib/libstatspull/Android.bp
index f8d2ff8..99c3e54 100644
--- a/lib/libstatspull/Android.bp
+++ b/lib/libstatspull/Android.bp
@@ -24,6 +24,8 @@
cc_defaults {
name: "libstatspull_defaults",
srcs: [
+ "stats_subscription.cpp",
+ "stats_provider.cpp",
"stats_pull_atom_callback.cpp",
],
cflags: [
@@ -105,16 +107,37 @@
],
}
-// Note: These unit tests only test PullAtomMetadata.
-// For full E2E tests of libstatspull, use LibStatsPullTests
+// Note: These unit tests only test PullAtomMetadata and subscriptions
+// For full E2E tests of pullers, use LibStatsPullTests
cc_test {
name: "libstatspull_test",
srcs: [
+ ":libprotobuf-internal-descriptor-proto",
+ ":libstats_log_protos",
+ ":libstats_subscription_protos",
"tests/pull_atom_metadata_test.cpp",
+ "tests/stats_subscription_test.cpp",
],
+ proto: {
+ type: "lite",
+ include_dirs: [
+ "external/protobuf/src",
+ ],
+ static: true,
+ },
shared_libs: [
"libstatspull",
"libstatssocket",
+ "libbase",
+ "libbinder",
+ "libutils",
+ "liblog",
+ ],
+ static_libs: [
+ "libgmock",
+ "libstatsgtestmatchers",
+ "libstatslog_statsdtest",
+ "libprotobuf-cpp-lite",
],
test_suites: [
"general-tests",
diff --git a/lib/libstatspull/include/stats_subscription.h b/lib/libstatspull/include/stats_subscription.h
new file mode 100644
index 0000000..6509c2b
--- /dev/null
+++ b/lib/libstatspull/include/stats_subscription.h
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2023, 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.
+ */
+#pragma once
+
+#include <stdint.h>
+#include <sys/cdefs.h>
+
+#ifndef __STATSD_SUBS_MIN_API__
+#define __STATSD_SUBS_MIN_API__ __ANDROID_API_U__
+#endif
+
+__BEGIN_DECLS
+
+/**
+ * Reason codes for why subscription callback was triggered.
+ */
+typedef enum AStatsManager_SubscriptionCallbackReason : uint32_t {
+ /**
+ * SubscriptionCallbackReason constant for subscription data transfer initiated by stats
+ * service.
+ *
+ * Introduced in API 34.
+ */
+ ASTATSMANAGER_SUBSCRIPTION_CALLBACK_REASON_STATSD_INITIATED = 1,
+
+ /**
+ * SubscriptionCallbackReason constant for subscriber requesting flush of pending data.
+ *
+ * Introduced in API 34.
+ */
+ ASTATSMANAGER_SUBSCRIPTION_CALLBACK_REASON_FLUSH_REQUESTED = 2,
+
+ /**
+ * SubscriptionCallbackReason constant for final stream of data for a subscription.
+ *
+ * Introduced in API 34.
+ */
+ ASTATSMANAGER_SUBSCRIPTION_CALLBACK_REASON_SUBSCRIPTION_ENDED = 3,
+} AStatsManager_SubscriptionCallbackReason;
+
+/**
+ * Callback interface for receiving subscription data by the stats service.
+ *
+ * This will be called on an arbitrary binder thread. There is a pool of such threads and there is a
+ * no guarantee a single thread will be used even for the same subscription. Clients must ensure it
+ * is safe to call callback from arbitrary threads.
+ *
+ * \param subscription_id the subscription id for which the callback is triggered.
+ * \param reason code for why the callback is triggered.
+ * \param payload encoded SubscriptionResults proto containing subscription data.
+ * Cannot be null.
+ * \param num_bytes size in bytes of the payload.
+ * \param cookie the opaque pointer passed in AStatsManager_addSubscription. Can be null.
+ *
+ * Introduced in API 34.
+ */
+typedef void (*AStatsManager_SubscriptionCallback)(int32_t subscription_id,
+ AStatsManager_SubscriptionCallbackReason reason,
+ uint8_t* _Nonnull payload, size_t num_bytes,
+ void* _Nullable cookie);
+
+/**
+ * Adds a new subscription.
+ *
+ * Requires caller is in the traced_probes selinux domain.
+ *
+ * \param subscription_config encoded ShellSubscription proto containing parameters for a new
+ * subscription. Cannot be null.
+ * \param num_bytes size in bytes of the subscription_config.
+ * \param callback function called to deliver subscription data back to the subscriber. Each
+ * callback can be used for more than one subscription. Cannot be null.
+ * \param cookie opaque pointer to associate with the subscription. The provided callback will be
+ * invoked with this cookie as an argument when delivering data for this subscription. Can be
+ * null.
+ * \return subscription ID for the new subscription. Subscription ID is a positive integer. A
+ * negative value indicates an error.
+ *
+ * Introduced in API 34.
+ */
+int32_t AStatsManager_addSubscription(const uint8_t* _Nonnull subscription_config, size_t num_bytes,
+ const AStatsManager_SubscriptionCallback _Nonnull callback,
+ void* _Nullable cookie)
+ __INTRODUCED_IN(__STATSD_SUBS_MIN_API__);
+
+/**
+ * Removes an existing subscription.
+ * This will trigger a flush of the remaining subscription data through
+ * AStatsManager_SubscriptionCallback with the reason as
+ * ASTATSMANAGER_SUBSCRIPTION_CALLBACK_REASON_SUBSCRIPTION_ENDED.
+ *
+ * Requires caller is in the traced_probes selinux domain.
+ *
+ * \param subscription_id subscription id of the subscription to terminate.
+ *
+ * Introduced in API 34.
+ */
+void AStatsManager_removeSubscription(int32_t subscription_id)
+ __INTRODUCED_IN(__STATSD_SUBS_MIN_API__);
+
+/**
+ * Request stats service to flush a subscription.
+ * This will trigger AStatsManager_SubscriptionCallback with the reason as
+ * ASTATSMANAGER_SUBSCRIPTION_CALLBACK_REASON_FLUSH_REQUESTED.
+ *
+ * Requires caller is in the traced_probes selinux domain.
+ *
+ * \param subscription_id ID of the subscription to be flushed.
+ *
+ * Introduced in API 34.
+ */
+void AStatsManager_flushSubscription(int32_t subscription_id)
+ __INTRODUCED_IN(__STATSD_SUBS_MIN_API__);
+
+__END_DECLS
diff --git a/lib/libstatspull/libstatspull.map.txt b/lib/libstatspull/libstatspull.map.txt
index e0e851a..30641e4 100644
--- a/lib/libstatspull/libstatspull.map.txt
+++ b/lib/libstatspull/libstatspull.map.txt
@@ -12,6 +12,10 @@
AStatsEventList_addStatsEvent; # apex # introduced=30
AStatsManager_setPullAtomCallback; # apex # introduced=30
AStatsManager_clearPullAtomCallback; # apex # introduced=30
+
+ AStatsManager_addSubscription; # apex # introduced=UpsideDownCake
+ AStatsManager_removeSubscription; # apex # introduced=UpsideDownCake
+ AStatsManager_flushSubscription; # apex # introduced=UpsideDownCake
local:
*;
};
diff --git a/lib/libstatspull/libstatspull_test.xml b/lib/libstatspull/libstatspull_test.xml
index 233fc1f..5b06f1e 100644
--- a/lib/libstatspull/libstatspull_test.xml
+++ b/lib/libstatspull/libstatspull_test.xml
@@ -29,6 +29,7 @@
<test class="com.android.tradefed.testtype.GTest" >
<option name="native-test-device-path" value="/data/local/tmp" />
<option name="module-name" value="libstatspull_test" />
+ <option name="native-test-timeout" value="180000"/>
</test>
<object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
diff --git a/lib/libstatspull/stats_provider.cpp b/lib/libstatspull/stats_provider.cpp
new file mode 100644
index 0000000..bee8e89
--- /dev/null
+++ b/lib/libstatspull/stats_provider.cpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023, 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.
+ */
+
+#include <android/binder_manager.h>
+#include <stats_provider.h>
+
+using aidl::android::os::IStatsd;
+
+StatsProvider::StatsProvider(StatsProviderBinderDiedCallback callback)
+ : mDeathRecipient(AIBinder_DeathRecipient_new(binderDied)), mCallback(callback) {
+}
+
+StatsProvider::~StatsProvider() {
+ resetStatsService();
+}
+
+std::shared_ptr<IStatsd> StatsProvider::getStatsService() {
+ std::lock_guard<std::mutex> lock(mMutex);
+ if (!mStatsd) {
+ // Fetch statsd
+ ::ndk::SpAIBinder binder(AServiceManager_getService("stats"));
+ mStatsd = IStatsd::fromBinder(binder);
+ if (mStatsd) {
+ AIBinder_linkToDeath(binder.get(), mDeathRecipient.get(), this);
+ }
+ }
+ return mStatsd;
+}
+
+void StatsProvider::resetStatsService() {
+ std::lock_guard<std::mutex> lock(mMutex);
+ mStatsd = nullptr;
+}
+
+void StatsProvider::binderDied(void* cookie) {
+ StatsProvider* statsProvider = static_cast<StatsProvider*>(cookie);
+ statsProvider->resetStatsService();
+ statsProvider->mCallback();
+}
diff --git a/lib/libstatspull/stats_provider.h b/lib/libstatspull/stats_provider.h
new file mode 100644
index 0000000..c9d9246
--- /dev/null
+++ b/lib/libstatspull/stats_provider.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023, 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.
+ */
+
+#include <aidl/android/os/IStatsd.h>
+#include <android/binder_auto_utils.h>
+
+using StatsProviderBinderDiedCallback = void (*)(void);
+
+/**
+ * Wrapper class for providing IStatsd Binder service.
+ * It handles Binder death and registers a callback for when the Binder service is restored after
+ * death.
+ */
+class StatsProvider {
+public:
+ StatsProvider(StatsProviderBinderDiedCallback callback);
+
+ ~StatsProvider();
+
+ std::shared_ptr<aidl::android::os::IStatsd> getStatsService();
+
+private:
+ static void binderDied(void* cookie);
+
+ void resetStatsService();
+
+ std::mutex mMutex;
+ std::shared_ptr<aidl::android::os::IStatsd> mStatsd;
+ const ::ndk::ScopedAIBinder_DeathRecipient mDeathRecipient;
+ const StatsProviderBinderDiedCallback mCallback;
+};
diff --git a/lib/libstatspull/stats_pull_atom_callback.cpp b/lib/libstatspull/stats_pull_atom_callback.cpp
index 8c72944..3fdf243 100644
--- a/lib/libstatspull/stats_pull_atom_callback.cpp
+++ b/lib/libstatspull/stats_pull_atom_callback.cpp
@@ -47,7 +47,7 @@
}
constexpr int64_t DEFAULT_COOL_DOWN_MILLIS = 1000LL; // 1 second.
-constexpr int64_t DEFAULT_TIMEOUT_MILLIS = 2000LL; // 2 seconds.
+constexpr int64_t DEFAULT_TIMEOUT_MILLIS = 1500LL; // 1.5 seconds.
struct AStatsManager_PullAtomMetadata {
int64_t cool_down_millis;
diff --git a/lib/libstatspull/stats_subscription.cpp b/lib/libstatspull/stats_subscription.cpp
new file mode 100644
index 0000000..3bc7e79
--- /dev/null
+++ b/lib/libstatspull/stats_subscription.cpp
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2023, 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.
+ */
+
+#include <aidl/android/os/BnStatsSubscriptionCallback.h>
+#include <aidl/android/os/IStatsd.h>
+#include <aidl/android/os/StatsSubscriptionCallbackReason.h>
+#include <android/binder_auto_utils.h>
+#include <stats_provider.h>
+#include <stats_subscription.h>
+
+#include <atomic>
+#include <map>
+#include <vector>
+
+using Status = ::ndk::ScopedAStatus;
+using aidl::android::os::BnStatsSubscriptionCallback;
+using aidl::android::os::IStatsd;
+using aidl::android::os::StatsSubscriptionCallbackReason;
+using ::ndk::SharedRefBase;
+
+class Subscription;
+
+// Mutex for accessing subscriptions map.
+static std::mutex subscriptionsMutex;
+
+// TODO(b/271039569): Store subscriptions in a singleton object.
+// Added subscriptions keyed by their subscription ID.
+static std::map</* subscription ID */ int32_t, std::shared_ptr<Subscription>> subscriptions;
+
+class Subscription : public BnStatsSubscriptionCallback {
+public:
+ Subscription(const int32_t subscriptionId, const std::vector<uint8_t>& subscriptionConfig,
+ const AStatsManager_SubscriptionCallback callback, void* cookie)
+ : mSubscriptionId(subscriptionId),
+ mSubscriptionParamsBytes(subscriptionConfig),
+ mCallback(callback),
+ mCookie(cookie) {
+ }
+
+ Status onSubscriptionData(const StatsSubscriptionCallbackReason reason,
+ const std::vector<uint8_t>& subscriptionPayload) override {
+ std::vector<uint8_t> mutablePayload = subscriptionPayload;
+ mCallback(mSubscriptionId, static_cast<AStatsManager_SubscriptionCallbackReason>(reason),
+ mutablePayload.data(), mutablePayload.size(), mCookie);
+
+ std::shared_ptr<Subscription> thisSubscription;
+ if (reason == StatsSubscriptionCallbackReason::SUBSCRIPTION_ENDED) {
+ std::lock_guard<std::mutex> lock(subscriptionsMutex);
+
+ auto subscriptionsIt = subscriptions.find(mSubscriptionId);
+ if (subscriptionsIt != subscriptions.end()) {
+ // Ensure this subscription's refcount doesn't hit 0 when we erase it from the
+ // subscriptions map by adding a local reference here.
+ thisSubscription = subscriptionsIt->second;
+
+ subscriptions.erase(subscriptionsIt);
+ }
+ }
+
+ return Status::ok();
+ }
+
+ const std::vector<uint8_t>& getSubscriptionParamsBytes() const {
+ return mSubscriptionParamsBytes;
+ }
+
+private:
+ const int32_t mSubscriptionId;
+ const std::vector<uint8_t> mSubscriptionParamsBytes;
+ const AStatsManager_SubscriptionCallback mCallback;
+ void* mCookie;
+};
+
+// forward declare so it can be referenced in StatsProvider constructor.
+static void onStatsBinderRestart();
+
+static std::shared_ptr<StatsProvider> statsProvider =
+ std::make_shared<StatsProvider>(onStatsBinderRestart);
+
+static void onStatsBinderRestart() {
+ const std::shared_ptr<IStatsd> statsService = statsProvider->getStatsService();
+ if (statsService == nullptr) {
+ return;
+ }
+
+ // Since we do not want to make an IPC with the lock held, we first create a
+ // copy of the data with the lock held before iterating through the map.
+ std::map<int32_t, std::shared_ptr<Subscription>> subscriptionsCopy;
+ {
+ std::lock_guard<std::mutex> lock(subscriptionsMutex);
+ subscriptionsCopy = subscriptions;
+ }
+ for (const auto& [_, subscription] : subscriptionsCopy) {
+ statsService->addSubscription(subscription->getSubscriptionParamsBytes(), subscription);
+ }
+}
+
+static int32_t getNextSubscriptionId() {
+ static std::atomic_int32_t nextSubscriptionId(0);
+ return ++nextSubscriptionId;
+}
+
+static std::shared_ptr<Subscription> getBinderCallbackForSubscription(
+ const int32_t subscription_id) {
+ std::lock_guard<std::mutex> lock(subscriptionsMutex);
+ auto subscriptionsIt = subscriptions.find(subscription_id);
+ if (subscriptionsIt == subscriptions.end()) {
+ return nullptr;
+ }
+ return subscriptionsIt->second;
+}
+
+int32_t AStatsManager_addSubscription(const uint8_t* subscription_config, const size_t num_bytes,
+ const AStatsManager_SubscriptionCallback callback,
+ void* cookie) {
+ const std::vector<uint8_t> subscriptionConfig(subscription_config,
+ subscription_config + num_bytes);
+ const int32_t subscriptionId(getNextSubscriptionId());
+ std::shared_ptr<Subscription> subscription =
+ SharedRefBase::make<Subscription>(subscriptionId, subscriptionConfig, callback, cookie);
+
+ {
+ std::lock_guard<std::mutex> lock(subscriptionsMutex);
+
+ subscriptions[subscriptionId] = subscription;
+ }
+
+ // TODO(b/270648168): Queue the binder call to not block on binder
+ const std::shared_ptr<IStatsd> statsService = statsProvider->getStatsService();
+ if (statsService != nullptr) {
+ statsService->addSubscription(subscriptionConfig, subscription);
+ }
+
+ return subscriptionId;
+}
+
+void AStatsManager_removeSubscription(const int32_t subscription_id) {
+ std::shared_ptr<Subscription> subscription = getBinderCallbackForSubscription(subscription_id);
+ if (subscription == nullptr) {
+ return;
+ }
+
+ // TODO(b/270648168): Queue the binder call to not block on binder
+ const std::shared_ptr<IStatsd> statsService = statsProvider->getStatsService();
+ if (statsService == nullptr) {
+ // Statsd not available.
+ // TODO(b/270656443): keep track of removeSubscription request and make the IPC call when
+ // statsd binder comes back up.
+ return;
+ }
+ statsService->removeSubscription(subscription);
+}
+
+void AStatsManager_flushSubscription(const int32_t subscription_id) {
+ std::shared_ptr<Subscription> subscription = getBinderCallbackForSubscription(subscription_id);
+ if (subscription == nullptr) {
+ return;
+ }
+
+ // TODO(b/270648168): Queue the binder call to not block on binder
+ const std::shared_ptr<IStatsd> statsService = statsProvider->getStatsService();
+ if (statsService == nullptr) {
+ // Statsd not available.
+ // TODO(b/270656443): keep track of flushSubscription request and make the IPC call when
+ // statsd binder comes back up.
+ return;
+ }
+
+ // TODO(b/273649282): Ensure the subscription is cleared in case the final Binder data
+ // callback fails.
+ statsService->flushSubscription(subscription);
+}
diff --git a/lib/libstatspull/tests/pull_atom_metadata_test.cpp b/lib/libstatspull/tests/pull_atom_metadata_test.cpp
index abc8e47..c3ebe5b 100644
--- a/lib/libstatspull/tests/pull_atom_metadata_test.cpp
+++ b/lib/libstatspull/tests/pull_atom_metadata_test.cpp
@@ -21,7 +21,7 @@
namespace {
static const int64_t DEFAULT_COOL_DOWN_MILLIS = 1000LL; // 1 second.
-static const int64_t DEFAULT_TIMEOUT_MILLIS = 2000LL; // 2 seconds.
+static const int64_t DEFAULT_TIMEOUT_MILLIS = 1500LL; // 1.5 seconds.
} // anonymous namespace
diff --git a/lib/libstatspull/tests/stats_subscription_test.cpp b/lib/libstatspull/tests/stats_subscription_test.cpp
new file mode 100644
index 0000000..301a937
--- /dev/null
+++ b/lib/libstatspull/tests/stats_subscription_test.cpp
@@ -0,0 +1,314 @@
+/*
+ * Copyright (C) 2023, 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.
+ */
+
+#include <binder/ProcessState.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <gtest_matchers.h>
+#include <stats_subscription.h>
+#include <stdint.h>
+#include <utils/Looper.h>
+
+#include <chrono>
+#include <string>
+#include <thread>
+#include <vector>
+
+#include "packages/modules/StatsD/statsd/src/shell/shell_config.pb.h"
+#include "packages/modules/StatsD/statsd/src/shell/shell_data.pb.h"
+#include "statslog_statsdtest.h"
+
+#ifdef __ANDROID__
+
+using namespace testing;
+using android::Looper;
+using android::ProcessState;
+using android::sp;
+using android::os::statsd::Atom;
+using android::os::statsd::ShellData;
+using android::os::statsd::ShellSubscription;
+using android::os::statsd::TestAtomReported_State_OFF;
+using android::os::statsd::TrainExperimentIds;
+using android::os::statsd::util::BytesField;
+using android::os::statsd::util::SCREEN_BRIGHTNESS_CHANGED;
+using android::os::statsd::util::stats_write;
+using android::os::statsd::util::TEST_ATOM_REPORTED;
+using android::os::statsd::util::TEST_ATOM_REPORTED__REPEATED_ENUM_FIELD__OFF;
+using std::string;
+using std::vector;
+using std::this_thread::sleep_for;
+
+namespace {
+
+class SubscriptionTest : public Test {
+public:
+ SubscriptionTest() : looper(Looper::prepare(/*opts=*/0)) {
+ const TestInfo* const test_info = UnitTest::GetInstance()->current_test_info();
+ ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
+
+ *trainExpIds.mutable_experiment_id() = {expIds.begin(), expIds.end()};
+ trainExpIds.SerializeToString(&trainExpIdsBytes);
+ }
+
+ ~SubscriptionTest() {
+ const TestInfo* const test_info = UnitTest::GetInstance()->current_test_info();
+ ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
+ }
+
+protected:
+ void SetUp() override {
+ // Start the Binder thread pool.
+ ProcessState::self()->startThreadPool();
+ }
+
+ void TearDown() {
+ // Clear any dangling subscriptions from statsd.
+ if (__builtin_available(android __STATSD_SUBS_MIN_API__, *)) {
+ AStatsManager_removeSubscription(subId);
+ }
+ }
+
+ void LogTestAtomReported(int32_t intFieldValue) {
+ const BytesField bytesField(trainExpIdsBytes.data(), trainExpIdsBytes.size());
+ stats_write(TEST_ATOM_REPORTED, uids.data(), uids.size(), tags, intFieldValue,
+ /*long_field=*/2LL, /*float_field=*/3.0F,
+ /*string_field=*/string1.c_str(),
+ /*boolean_field=*/false, /*state=*/TEST_ATOM_REPORTED__REPEATED_ENUM_FIELD__OFF,
+ bytesField, repeatedInts, repeatedLongs, repeatedFloats, repeatedStrings,
+ &(repeatedBool[0]), /*repeatedBoolSize=*/2, repeatedEnums);
+ }
+
+ int32_t subId;
+
+ // TestAtomReported fields.
+ const vector<int32_t> uids = {1};
+ const string tag = "test";
+ const vector<char const*> tags = {tag.c_str()};
+
+ // 100 int64s for the MODE_BYTES field to push atom size to over 1K.
+ const vector<int64_t> expIds = vector<int64_t>(100, INT64_MAX);
+
+ const vector<int32_t> repeatedInts{1};
+ const vector<int64_t> repeatedLongs{2LL};
+ const vector<float> repeatedFloats{3.0F};
+ const string string1 = "ABC";
+ const vector<char const*> repeatedStrings = {string1.c_str()};
+ const bool repeatedBool[2] = {false, true};
+ const vector<int32_t> repeatedEnums = {TEST_ATOM_REPORTED__REPEATED_ENUM_FIELD__OFF};
+ TrainExperimentIds trainExpIds;
+ string trainExpIdsBytes;
+
+private:
+ sp<Looper> looper;
+};
+
+// Stores arguments passed in subscription callback.
+struct CallbackData {
+ int32_t subId;
+ AStatsManager_SubscriptionCallbackReason reason;
+ vector<uint8_t> payload;
+ int count; // Stores number of times the callback is invoked.
+};
+
+static void callback(int32_t subscription_id, AStatsManager_SubscriptionCallbackReason reason,
+ uint8_t* _Nonnull payload, size_t num_bytes, void* _Nullable cookie) {
+ CallbackData* data = static_cast<CallbackData*>(cookie);
+ data->subId = subscription_id;
+ data->reason = reason;
+ data->payload.assign(payload, payload + num_bytes);
+ data->count++;
+}
+
+constexpr static int WAIT_MS = 500;
+
+TEST_F(SubscriptionTest, TestSubscription) {
+ if (__builtin_available(android __STATSD_SUBS_MIN_API__, *)) {
+ ShellSubscription config;
+ config.add_pushed()->set_atom_id(TEST_ATOM_REPORTED);
+ config.add_pushed()->set_atom_id(SCREEN_BRIGHTNESS_CHANGED);
+
+ string configBytes;
+ config.SerializeToString(&configBytes);
+
+ CallbackData callbackData{/*subId=*/0,
+ ASTATSMANAGER_SUBSCRIPTION_CALLBACK_REASON_SUBSCRIPTION_ENDED,
+ /*payload=*/{},
+ /*count=*/0};
+
+ // Add subscription.
+ subId = AStatsManager_addSubscription(reinterpret_cast<const uint8_t*>(configBytes.data()),
+ configBytes.size(), &callback, &callbackData);
+ ASSERT_GT(subId, 0);
+ sleep_for(std::chrono::milliseconds(WAIT_MS));
+
+ // Log events without exceeding statsd cache.
+ stats_write(SCREEN_BRIGHTNESS_CHANGED, 100);
+ LogTestAtomReported(1);
+ sleep_for(std::chrono::milliseconds(WAIT_MS));
+
+ // Verify no callback occurred yet.
+ EXPECT_EQ(callbackData.subId, 0);
+ EXPECT_EQ(callbackData.reason,
+ ASTATSMANAGER_SUBSCRIPTION_CALLBACK_REASON_SUBSCRIPTION_ENDED);
+ EXPECT_EQ(callbackData.count, 0);
+ ASSERT_TRUE(callbackData.payload.empty());
+
+ // Log another TestAtomReported to overflow cache.
+ LogTestAtomReported(2);
+ sleep_for(std::chrono::milliseconds(WAIT_MS));
+
+ // Verify callback occurred.
+ EXPECT_EQ(callbackData.subId, subId);
+ EXPECT_EQ(callbackData.reason, ASTATSMANAGER_SUBSCRIPTION_CALLBACK_REASON_STATSD_INITIATED);
+ EXPECT_EQ(callbackData.count, 1);
+ ASSERT_GT(callbackData.payload.size(), 0);
+
+ ShellData actualShellData;
+ ASSERT_TRUE(actualShellData.ParseFromArray(callbackData.payload.data(),
+ callbackData.payload.size()));
+
+ ASSERT_GE(actualShellData.elapsed_timestamp_nanos_size(), 3);
+ EXPECT_THAT(actualShellData.elapsed_timestamp_nanos(), Each(Gt(0LL)));
+
+ ASSERT_GE(actualShellData.atom_size(), 3);
+
+ // Verify atom 1.
+ Atom expectedAtom;
+ expectedAtom.mutable_screen_brightness_changed()->set_level(100);
+ EXPECT_THAT(actualShellData.atom(0), EqAtom(expectedAtom));
+
+ // Verify atom 2.
+ expectedAtom.Clear();
+ auto* testAtomReported = expectedAtom.mutable_test_atom_reported();
+ auto* attributionNode = testAtomReported->add_attribution_node();
+ attributionNode->set_uid(uids[0]);
+ attributionNode->set_tag(tag);
+ testAtomReported->set_int_field(1);
+ testAtomReported->set_long_field(2LL);
+ testAtomReported->set_float_field(3.0F);
+ testAtomReported->set_string_field(string1);
+ testAtomReported->set_boolean_field(false);
+ testAtomReported->set_state(TestAtomReported_State_OFF);
+ *testAtomReported->mutable_bytes_field() = trainExpIds;
+ *testAtomReported->mutable_repeated_int_field() = {repeatedInts.begin(),
+ repeatedInts.end()};
+ *testAtomReported->mutable_repeated_long_field() = {repeatedLongs.begin(),
+ repeatedLongs.end()};
+ *testAtomReported->mutable_repeated_float_field() = {repeatedFloats.begin(),
+ repeatedFloats.end()};
+ *testAtomReported->mutable_repeated_string_field() = {repeatedStrings.begin(),
+ repeatedStrings.end()};
+ *testAtomReported->mutable_repeated_boolean_field() = {&repeatedBool[0],
+ &repeatedBool[0] + 2};
+ *testAtomReported->mutable_repeated_enum_field() = {repeatedEnums.begin(),
+ repeatedEnums.end()};
+ EXPECT_THAT(actualShellData.atom(1), EqAtom(expectedAtom));
+
+ // Verify atom 3.
+ testAtomReported->set_int_field(2);
+ EXPECT_THAT(actualShellData.atom(2), EqAtom(expectedAtom));
+
+ // Log another ScreenBrightnessChanged atom. No callback should occur.
+ stats_write(SCREEN_BRIGHTNESS_CHANGED, 99);
+ sleep_for(std::chrono::milliseconds(WAIT_MS));
+ EXPECT_EQ(callbackData.count, 1);
+
+ // Flush subscription. Callback should occur.
+ AStatsManager_flushSubscription(subId);
+ sleep_for(std::chrono::milliseconds(WAIT_MS));
+
+ EXPECT_EQ(callbackData.subId, subId);
+ EXPECT_EQ(callbackData.reason, ASTATSMANAGER_SUBSCRIPTION_CALLBACK_REASON_FLUSH_REQUESTED);
+ EXPECT_EQ(callbackData.count, 2);
+ ASSERT_GT(callbackData.payload.size(), 0);
+
+ ASSERT_TRUE(actualShellData.ParseFromArray(callbackData.payload.data(),
+ callbackData.payload.size()));
+
+ ASSERT_GE(actualShellData.elapsed_timestamp_nanos_size(), 1);
+ EXPECT_THAT(actualShellData.elapsed_timestamp_nanos(), Each(Gt(0LL)));
+
+ ASSERT_GE(actualShellData.atom_size(), 1);
+
+ // Verify atom 1.
+ expectedAtom.Clear();
+ expectedAtom.mutable_screen_brightness_changed()->set_level(99);
+ EXPECT_THAT(actualShellData.atom(0), EqAtom(expectedAtom));
+
+ // Log another ScreenBrightnessChanged atom. No callback should occur.
+ stats_write(SCREEN_BRIGHTNESS_CHANGED, 98);
+ sleep_for(std::chrono::milliseconds(WAIT_MS));
+ EXPECT_EQ(callbackData.count, 2);
+
+ // Trigger callback through cache timeout.
+ // Two 500 ms sleeps have occurred already so the total sleep is 71000 ms since last
+ // callback invocation.
+ sleep_for(std::chrono::milliseconds(70'000));
+ EXPECT_EQ(callbackData.subId, subId);
+ EXPECT_EQ(callbackData.reason, ASTATSMANAGER_SUBSCRIPTION_CALLBACK_REASON_STATSD_INITIATED);
+ EXPECT_EQ(callbackData.count, 3);
+ ASSERT_GT(callbackData.payload.size(), 0);
+
+ ASSERT_TRUE(actualShellData.ParseFromArray(callbackData.payload.data(),
+ callbackData.payload.size()));
+
+ ASSERT_GE(actualShellData.elapsed_timestamp_nanos_size(), 1);
+ EXPECT_THAT(actualShellData.elapsed_timestamp_nanos(), Each(Gt(0LL)));
+
+ ASSERT_GE(actualShellData.atom_size(), 1);
+
+ // Verify atom 1.
+ expectedAtom.Clear();
+ expectedAtom.mutable_screen_brightness_changed()->set_level(98);
+ EXPECT_THAT(actualShellData.atom(0), EqAtom(expectedAtom));
+
+ // Log another ScreenBrightnessChanged atom. No callback should occur.
+ stats_write(SCREEN_BRIGHTNESS_CHANGED, 97);
+ sleep_for(std::chrono::milliseconds(WAIT_MS));
+ EXPECT_EQ(callbackData.count, 3);
+
+ // End subscription. Final callback should occur.
+ AStatsManager_removeSubscription(subId);
+ sleep_for(std::chrono::milliseconds(WAIT_MS));
+
+ EXPECT_EQ(callbackData.subId, subId);
+ EXPECT_EQ(callbackData.reason,
+ ASTATSMANAGER_SUBSCRIPTION_CALLBACK_REASON_SUBSCRIPTION_ENDED);
+ EXPECT_EQ(callbackData.count, 4);
+ ASSERT_GT(callbackData.payload.size(), 0);
+
+ ASSERT_TRUE(actualShellData.ParseFromArray(callbackData.payload.data(),
+ callbackData.payload.size()));
+
+ ASSERT_GE(actualShellData.elapsed_timestamp_nanos_size(), 1);
+ EXPECT_THAT(actualShellData.elapsed_timestamp_nanos(), Each(Gt(0LL)));
+
+ ASSERT_GE(actualShellData.atom_size(), 1);
+
+ // Verify atom 1.
+ expectedAtom.Clear();
+ expectedAtom.mutable_screen_brightness_changed()->set_level(97);
+ EXPECT_THAT(actualShellData.atom(0), EqAtom(expectedAtom));
+ } else {
+ GTEST_SKIP();
+ }
+}
+
+} // namespace
+
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/lib/libstatssocket/include/stats_annotations.h b/lib/libstatssocket/include/stats_annotations.h
index e812af0..2963db9 100644
--- a/lib/libstatssocket/include/stats_annotations.h
+++ b/lib/libstatssocket/include/stats_annotations.h
@@ -80,6 +80,116 @@
* Introduced in API 31.
*/
ASTATSLOG_ANNOTATION_ID_STATE_NESTED = 8,
+
+ /**
+ * Annotation ID constant to indicate the restriction category of an atom.
+ * This annotation must only be attached to the atom id. This is an int annotation.
+ *
+ * Introduced in API 34.
+ */
+ ASTATSLOG_ANNOTATION_ID_RESTRICTION_CATEGORY = 9,
+
+ /**
+ * Annotation ID to indicate that a field of an atom contains peripheral device info.
+ * This is a bool annotation.
+ *
+ * Introduced in API 34.
+ */
+ ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_PERIPHERAL_DEVICE_INFO = 10,
+
+ /**
+ * Annotation ID to indicate that a field of an atom contains app usage information.
+ * This is a bool annotation.
+ *
+ * Introduced in API 34.
+ */
+ ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_APP_USAGE = 11,
+
+ /**
+ * Annotation ID to indicate that a field of an atom contains app activity information.
+ * This is a bool annotation.
+ *
+ * Introduced in API 34.
+ */
+ ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_APP_ACTIVITY = 12,
+
+ /**
+ * Annotation ID to indicate that a field of an atom contains health connect information.
+ * This is a bool annotation.
+ *
+ * Introduced in API 34.
+ */
+ ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_HEALTH_CONNECT = 13,
+
+ /**
+ * Annotation ID to indicate that a field of an atom contains accessibility information.
+ * This is a bool annotation.
+ *
+ * Introduced in API 34.
+ */
+ ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_ACCESSIBILITY = 14,
+
+ /**
+ * Annotation ID to indicate that a field of an atom contains system search information.
+ * This is a bool annotation.
+ *
+ * Introduced in API 34.
+ */
+ ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_SYSTEM_SEARCH = 15,
+
+ /**
+ * Annotation ID to indicate that a field of an atom contains user engagement information.
+ * This is a bool annotation.
+ *
+ * Introduced in API 34.
+ */
+ ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_USER_ENGAGEMENT = 16,
+
+ /**
+ * Annotation ID to indicate that a field of an atom contains ambient sensing information.
+ * This is a bool annotation.
+ *
+ * Introduced in API 34.
+ */
+ ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_AMBIENT_SENSING = 17,
+
+ /**
+ * Annotation ID to indicate that a field of an atom contains demographic classification
+ * information. This is a bool annotation.
+ *
+ * Introduced in API 34.
+ */
+ ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_DEMOGRAPHIC_CLASSIFICATION = 18,
};
+enum AStatsLogRestrictionCategory : uint32_t {
+ /**
+ * Restriction category for atoms about diagnostics.
+ *
+ * Introduced in API 34.
+ */
+ ASTATSLOG_RESTRICTION_CATEGORY_DIAGNOSTIC = 1,
+
+ /**
+ * Restriction category for atoms about system intelligence.
+ *
+ * Introduced in API 34.
+ */
+ ASTATSLOG_RESTRICTION_CATEGORY_SYSTEM_INTELLIGENCE = 2,
+
+ /**
+ * Restriction category for atoms about authentication.
+ *
+ * Introduced in API 34.
+ */
+ ASTATSLOG_RESTRICTION_CATEGORY_AUTHENTICATION = 3,
+
+ /**
+ * Restriction category for atoms about fraud and abuse.
+ *
+ * Introduced in API 34.
+ */
+ ASTATSLOG_RESTRICTION_CATEGORY_FRAUD_AND_ABUSE = 4,
+
+};
__END_DECLS
diff --git a/lib/libstatssocket/statsd_writer.c b/lib/libstatssocket/statsd_writer.c
index 06695fe..2c9bb25 100644
--- a/lib/libstatssocket/statsd_writer.c
+++ b/lib/libstatssocket/statsd_writer.c
@@ -107,7 +107,7 @@
if (sock < 0) {
ret = -errno;
} else {
- int sndbuf = 1 * 1024 * 1024; // set max send buffer size 1MB
+ const int sndbuf = 2 * 1024 * 1024; // set max send buffer size 2MB
socklen_t bufLen = sizeof(sndbuf);
// SO_RCVBUF does not have an effect on unix domain socket, but SO_SNDBUF does.
// Proceed to connect even setsockopt fails.
diff --git a/service/java/com/android/server/stats/StatsCompanion.java b/service/java/com/android/server/stats/StatsCompanion.java
index dc477a5..d0954ea 100644
--- a/service/java/com/android/server/stats/StatsCompanion.java
+++ b/service/java/com/android/server/stats/StatsCompanion.java
@@ -112,6 +112,7 @@
private static final int CODE_DATA_BROADCAST = 1;
private static final int CODE_ACTIVE_CONFIGS_BROADCAST = 1;
private static final int CODE_SUBSCRIBER_BROADCAST = 1;
+ private static final int CODE_RESTRICTED_METRICS_BROADCAST = 1;
private final PendingIntent mPendingIntent;
private final Context mContext;
@@ -184,5 +185,24 @@
+ "; presumably it had been cancelled.");
}
}
+
+ @Override
+ public void sendRestrictedMetricsChangedBroadcast(long[] metricIds) {
+ enforceStatsdCallingUid();
+ Intent intent = new Intent();
+ intent.putExtra(StatsManager.EXTRA_STATS_RESTRICTED_METRIC_IDS, metricIds);
+ try {
+ mPendingIntent.send(mContext, CODE_RESTRICTED_METRICS_BROADCAST, intent, null,
+ null);
+ if (DEBUG) {
+ Log.d(TAG,
+ "Sent restricted metrics broadcast with metric ids " + Arrays.toString(
+ metricIds));
+ }
+ } catch (PendingIntent.CanceledException e) {
+ Log.w(TAG,
+ "Unable to send restricted metrics changed broadcast using PendingIntent");
+ }
+ }
}
}
diff --git a/service/java/com/android/server/stats/StatsCompanionService.java b/service/java/com/android/server/stats/StatsCompanionService.java
index c3771df..367bfdd 100644
--- a/service/java/com/android/server/stats/StatsCompanionService.java
+++ b/service/java/com/android/server/stats/StatsCompanionService.java
@@ -103,8 +103,6 @@
public static final int DEATH_THRESHOLD = 10;
- private static final String INCLUDE_CERTIFICATE_HASH = "include_certificate_hash";
-
private final Context mContext;
private final AlarmManager mAlarmManager;
@GuardedBy("sStatsdLock")
@@ -263,15 +261,12 @@
| ProtoOutputStream.FIELD_COUNT_SINGLE
| INSTALLER_FIELD_ID,
installer);
- if (DeviceConfig.getBoolean(
- NAMESPACE_STATSD_JAVA, INCLUDE_CERTIFICATE_HASH, false)) {
- final byte[] certHash = getPackageCertificateHash(
- packagesPlusApex.get(j).signingInfo);
- output.write(ProtoOutputStream.FIELD_TYPE_BYTES
- | ProtoOutputStream.FIELD_COUNT_SINGLE
- | CERTIFICATE_HASH_FIELD_ID,
- certHash);
- }
+ final byte[] certHash =
+ getPackageCertificateHash(packagesPlusApex.get(j).signingInfo);
+ output.write(ProtoOutputStream.FIELD_TYPE_BYTES
+ | ProtoOutputStream.FIELD_COUNT_SINGLE
+ | CERTIFICATE_HASH_FIELD_ID,
+ certHash);
numRecords++;
output.end(applicationInfoToken);
@@ -284,6 +279,7 @@
}
} finally {
if (DEBUG) Log.d(TAG, "End thread for sending uid map data.");
+ FileUtils.closeQuietly(fout);
backgroundThread.quit();
}
});
@@ -376,11 +372,7 @@
final String installer = getInstallerPackageName(pm, app);
// Get Package certificate hash.
- byte[] certHash = new byte[0];
- if (DeviceConfig.getBoolean(
- NAMESPACE_STATSD_JAVA, INCLUDE_CERTIFICATE_HASH, false)) {
- certHash = getPackageCertificateHash(pi.signingInfo);
- }
+ byte[] certHash = getPackageCertificateHash(pi.signingInfo);
sStatsd.informOnePackage(
app,
@@ -673,13 +665,6 @@
private void onPropertiesChanged(final Properties properties) {
updateProperties(properties);
-
- // Re-fetch package information with package certificates if include_certificate_hash
- // property changed.
- final Set<String> propertyNames = properties.getKeyset();
- if (propertyNames.contains(INCLUDE_CERTIFICATE_HASH)) {
- informAllUids(mContext);
- }
}
private void updateProperties(final Properties properties) {
@@ -710,7 +695,7 @@
try {
statsd.updateProperties(propertyParcels);
} catch (RemoteException e) {
- Log.w(TAG, "Failed to inform statsd of an include app certificate flag update", e);
+ Log.w(TAG, "Failed to inform statsd of updated statsd_java properties", e);
}
}
diff --git a/service/java/com/android/server/stats/StatsManagerService.java b/service/java/com/android/server/stats/StatsManagerService.java
index 1e3846b..c518f50 100644
--- a/service/java/com/android/server/stats/StatsManagerService.java
+++ b/service/java/com/android/server/stats/StatsManagerService.java
@@ -26,6 +26,7 @@
import android.os.Binder;
import android.os.IPullAtomCallback;
import android.os.IStatsManagerService;
+import android.os.IStatsQueryCallback;
import android.os.IStatsd;
import android.os.PowerManager;
import android.os.Process;
@@ -66,6 +67,9 @@
@GuardedBy("mLock")
private ArrayMap<ConfigKey, ArrayMap<Long, PendingIntentRef>> mBroadcastSubscriberPirMap =
new ArrayMap<>();
+ @GuardedBy("mLock")
+ private ArrayMap<ConfigKeyWithPackage, ArrayMap<Integer, PendingIntentRef>>
+ mRestrictedMetricsPirMap = new ArrayMap<>();
public StatsManagerService(Context context) {
super();
@@ -104,6 +108,39 @@
}
}
+ private static class ConfigKeyWithPackage {
+ private final String mConfigPackage;
+ private final long mConfigId;
+
+ ConfigKeyWithPackage(String configPackage, long configId) {
+ mConfigPackage = configPackage;
+ mConfigId = configId;
+ }
+
+ public String getConfigPackage() {
+ return mConfigPackage;
+ }
+
+ public long getConfigId() {
+ return mConfigId;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mConfigPackage, mConfigId);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof ConfigKeyWithPackage) {
+ ConfigKeyWithPackage other = (ConfigKeyWithPackage) obj;
+ return this.mConfigPackage.equals(other.getConfigPackage())
+ && this.mConfigId == other.getConfigId();
+ }
+ return false;
+ }
+ }
+
private static class PullerKey {
private final int mUid;
private final int mAtomTag;
@@ -477,10 +514,97 @@
throw new IllegalStateException("Failed to connect to statsd to removeConfig");
}
+ @Override
+ public long[] setRestrictedMetricsChangedOperation(PendingIntent pendingIntent,
+ long configId, String configPackage) {
+ enforceRestrictedStatsPermission();
+ int callingUid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+ PendingIntentRef pir = new PendingIntentRef(pendingIntent, mContext);
+ ConfigKeyWithPackage key = new ConfigKeyWithPackage(configPackage, configId);
+ // Add the PIR to a map so we can re-register if statsd is unavailable.
+ synchronized (mLock) {
+ ArrayMap<Integer, PendingIntentRef> innerMap = mRestrictedMetricsPirMap.getOrDefault(
+ key, new ArrayMap<>());
+ innerMap.put(callingUid, pir);
+ mRestrictedMetricsPirMap.put(key, innerMap);
+ }
+ try {
+ IStatsd statsd = getStatsdNonblocking();
+ if (statsd != null) {
+ return statsd.setRestrictedMetricsChangedOperation(configId, configPackage, pir,
+ callingUid);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to setRestrictedMetricsChangedOperation with statsd");
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ return new long[]{};
+ }
+
+ @Override
+ public void removeRestrictedMetricsChangedOperation(long configId, String configPackage) {
+ enforceRestrictedStatsPermission();
+ int callingUid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+ ConfigKeyWithPackage key = new ConfigKeyWithPackage(configPackage, configId);
+ synchronized (mLock) {
+ ArrayMap<Integer, PendingIntentRef> innerMap = mRestrictedMetricsPirMap.getOrDefault(
+ key, new ArrayMap<>());
+ innerMap.remove(callingUid);
+ if (innerMap.isEmpty()) {
+ mRestrictedMetricsPirMap.remove(key);
+ }
+ }
+ try {
+ IStatsd statsd = getStatsdNonblocking();
+ if (statsd != null) {
+ statsd.removeRestrictedMetricsChangedOperation(configId, configPackage, callingUid);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to removeRestrictedMetricsChangedOperation with statsd");
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void querySql(String sqlQuery, int minSqlClientVersion, byte[] policyConfig,
+ IStatsQueryCallback queryCallback, long configKey, String configPackage) {
+ int callingUid = Binder.getCallingUid();
+ enforceRestrictedStatsPermission();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ IStatsd statsd = waitForStatsd();
+ if (statsd != null) {
+ statsd.querySql(
+ sqlQuery,
+ minSqlClientVersion,
+ policyConfig,
+ queryCallback,
+ configKey,
+ configPackage,
+ callingUid);
+ } else {
+ queryCallback.sendFailure("Could not connect to statsd from system server");
+ }
+ } catch (RemoteException e) {
+ throw new IllegalStateException(e.getMessage(), e);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
void setStatsCompanionService(StatsCompanionService statsCompanionService) {
mStatsCompanionService = statsCompanionService;
}
+ /** Checks that the caller has READ_RESTRICTED_STATS permission. */
+ private void enforceRestrictedStatsPermission() {
+ mContext.enforceCallingPermission(Manifest.permission.READ_RESTRICTED_STATS, null);
+ }
+
/**
* Checks that the caller has both DUMP and PACKAGE_USAGE_STATS permissions. Also checks that
* the caller has USAGE_STATS_PERMISSION_OPS for the specified packageName if it is not null.
@@ -589,6 +713,8 @@
registerAllDataFetchOperations(statsd);
registerAllActiveConfigsChangedOperations(statsd);
registerAllBroadcastSubscribers(statsd);
+ registerAllRestrictedMetricsChangedOperations(statsd);
+ // TODO (b/269419485): register all restricted metric operations.
} catch (RemoteException e) {
Log.e(TAG, "StatsManager failed to (re-)register data with statsd");
} finally {
@@ -657,7 +783,7 @@
}
for (Map.Entry<ConfigKey, ArrayMap<Long, PendingIntentRef>> entry :
- mBroadcastSubscriberPirMap.entrySet()) {
+ broadcastSubscriberCopy.entrySet()) {
ConfigKey configKey = entry.getKey();
for (Map.Entry<Long, PendingIntentRef> subscriberEntry : entry.getValue().entrySet()) {
statsd.setBroadcastSubscriber(configKey.getConfigId(), subscriberEntry.getKey(),
@@ -665,4 +791,28 @@
}
}
}
+
+ // Pre-condition: the Binder calling identity has already been cleared
+ private void registerAllRestrictedMetricsChangedOperations(IStatsd statsd)
+ throws RemoteException {
+ // Since we do not want to make an IPC with the lock held, we first create a deep copy of
+ // the data with the lock held before iterating through the map.
+ ArrayMap<ConfigKeyWithPackage, ArrayMap<Integer, PendingIntentRef>> restrictedMetricsCopy =
+ new ArrayMap<>();
+ synchronized (mLock) {
+ for (Map.Entry<ConfigKeyWithPackage, ArrayMap<Integer, PendingIntentRef>> entry :
+ mRestrictedMetricsPirMap.entrySet()) {
+ restrictedMetricsCopy.put(entry.getKey(), new ArrayMap(entry.getValue()));
+ }
+ }
+
+ for (Map.Entry<ConfigKeyWithPackage, ArrayMap<Integer, PendingIntentRef>> entry :
+ restrictedMetricsCopy.entrySet()) {
+ ConfigKeyWithPackage configKey = entry.getKey();
+ for (Map.Entry<Integer, PendingIntentRef> uidEntry : entry.getValue().entrySet()) {
+ statsd.setRestrictedMetricsChangedOperation(configKey.getConfigId(),
+ configKey.getConfigPackage(), uidEntry.getValue(), uidEntry.getKey());
+ }
+ }
+ }
}
diff --git a/statsd/Android.bp b/statsd/Android.bp
index 8b34c4e..db3f28b 100644
--- a/statsd/Android.bp
+++ b/statsd/Android.bp
@@ -64,6 +64,7 @@
"src/metrics/duration_helper/OringDurationTracker.cpp",
"src/metrics/DurationMetricProducer.cpp",
"src/metrics/EventMetricProducer.cpp",
+ "src/metrics/RestrictedEventMetricProducer.cpp",
"src/metrics/GaugeMetricProducer.cpp",
"src/metrics/KllMetricProducer.cpp",
"src/metrics/MetricProducer.cpp",
@@ -80,6 +81,7 @@
"src/state/StateManager.cpp",
"src/state/StateTracker.cpp",
"src/stats_log_util.cpp",
+ "src/stats_policy_config.proto",
"src/statscompanion_util.cpp",
"src/statsd_config.proto",
"src/statsd_metadata.proto",
@@ -91,6 +93,8 @@
"src/subscriber/SubscriberReporter.cpp",
"src/uid_data.proto",
"src/utils/MultiConditionTrigger.cpp",
+ "src/utils/DbUtils.cpp",
+ "src/utils/RestrictedPolicyManager.cpp",
"src/utils/ShardOffsetProvider.cpp",
],
@@ -109,6 +113,7 @@
"libutils",
"server_configurable_flags",
"statsd-aidl-ndk",
+ "libsqlite_static_noicu",
],
shared_libs: [
"libbinder_ndk",
@@ -301,6 +306,7 @@
"tests/metrics/MaxDurationTracker_test.cpp",
"tests/metrics/NumericValueMetricProducer_test.cpp",
"tests/metrics/OringDurationTracker_test.cpp",
+ "tests/metrics/RestrictedEventMetricProducer_test.cpp",
"tests/MetricsManager_test.cpp",
"tests/metrics/parsing_utils/config_update_utils_test.cpp",
"tests/metrics/parsing_utils/metrics_manager_util_test.cpp",
@@ -311,6 +317,9 @@
],
srcs: [
+ // atom_field_options.proto needs field_options.proto, but that is
+ // not included in libprotobuf-cpp-lite, so compile it here.
+ ":libprotobuf-internal-protos",
":libstats_internal_protos",
"src/shell/shell_data.proto",
@@ -338,8 +347,10 @@
"tests/e2e/MetricActivation_e2e_test.cpp",
"tests/e2e/MetricConditionLink_e2e_test.cpp",
"tests/e2e/PartialBucket_e2e_test.cpp",
+ "tests/e2e/RestrictedConfig_e2e_test.cpp",
"tests/e2e/ValueMetric_pull_e2e_test.cpp",
"tests/e2e/WakelockDuration_e2e_test.cpp",
+ "tests/e2e/RestrictedEventMetric_e2e_test.cpp",
"tests/external/puller_util_test.cpp",
"tests/external/StatsCallbackPuller_test.cpp",
"tests/external/StatsPuller_test.cpp",
@@ -362,33 +373,39 @@
"tests/metrics/metrics_test_helper.cpp",
"tests/metrics/OringDurationTracker_test.cpp",
"tests/metrics/NumericValueMetricProducer_test.cpp",
+ "tests/metrics/RestrictedEventMetricProducer_test.cpp",
"tests/metrics/parsing_utils/config_update_utils_test.cpp",
"tests/metrics/parsing_utils/metrics_manager_util_test.cpp",
"tests/subscriber/SubscriberReporter_test.cpp",
+ "tests/LogEventFilter_test.cpp",
"tests/MetricsManager_test.cpp",
"tests/shell/ShellSubscriber_test.cpp",
"tests/state/StateTracker_test.cpp",
"tests/statsd_test_util.cpp",
"tests/statsd_test_util_test.cpp",
+ "tests/SocketListener_test.cpp",
"tests/StatsLogProcessor_test.cpp",
"tests/StatsService_test.cpp",
"tests/storage/StorageManager_test.cpp",
"tests/UidMap_test.cpp",
"tests/utils/MultiConditionTrigger_test.cpp",
+ "tests/utils/DbUtils_test.cpp",
],
static_libs: [
"libgmock",
- "libplatformprotos-test",
+ "libstatsgtestmatchers",
"libstatslog_statsdtest",
"libstatssocket_private",
],
proto: {
- type: "full",
+ type: "lite",
include_dirs: [
"external/protobuf/src",
+ "frameworks/proto_logging/stats",
],
+ static: true,
},
min_sdk_version: "30",
@@ -408,11 +425,13 @@
":libprotobuf-internal-protos",
":libstats_internal_protos",
+ "benchmark/db_benchmark.cpp",
"benchmark/duration_metric_benchmark.cpp",
"benchmark/filter_value_benchmark.cpp",
"benchmark/get_dimensions_for_condition_benchmark.cpp",
"benchmark/hello_world_benchmark.cpp",
"benchmark/log_event_benchmark.cpp",
+ "benchmark/log_event_filter_benchmark.cpp",
"benchmark/main.cpp",
"benchmark/metric_util.cpp",
"benchmark/stats_write_benchmark.cpp",
@@ -513,10 +532,19 @@
// Filegroup for statsd config proto definition.
filegroup {
- name: "statsd-config-proto-def",
+ name: "libstats_config_protos",
srcs: ["src/statsd_config.proto"],
}
+// Filegroup for statsd report protos.
+filegroup {
+ name: "libstats_log_protos",
+ srcs: [
+ "src/stats_log.proto",
+ "src/guardrail/invalid_config_reason_enum.proto",
+ ],
+}
+
// Filegroup for all statsd protos
filegroup {
name: "statsd_internal_protos",
@@ -532,3 +560,15 @@
"src/guardrail/invalid_config_reason_enum.proto",
],
}
+
+// Filegroup for subscription protos.
+filegroup {
+ name: "libstats_subscription_protos",
+ srcs: [
+ ":libstats_internal_protos",
+ ":libstats_config_protos",
+ "src/shell/shell_config.proto",
+ "src/shell/shell_data.proto",
+ ],
+}
+
diff --git a/statsd/benchmark/db_benchmark.cpp b/statsd/benchmark/db_benchmark.cpp
new file mode 100644
index 0000000..ebe751a
--- /dev/null
+++ b/statsd/benchmark/db_benchmark.cpp
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#include "benchmark/benchmark.h"
+#include "metric_util.h"
+#include "utils/DbUtils.h"
+
+using namespace std;
+
+namespace android {
+namespace os {
+namespace statsd {
+namespace dbutils {
+
+static void BM_insertAtomsIntoDbTablesNewConnection(benchmark::State& state) {
+ ConfigKey key = ConfigKey(111, 222);
+ int64_t metricId = 0;
+ int64_t bucketStartTimeNs = 10000000000;
+
+ unique_ptr<LogEvent> event =
+ CreateScreenStateChangedEvent(bucketStartTimeNs, android::view::DISPLAY_STATE_OFF);
+ vector<LogEvent> logEvents;
+ for (int j = 0; j < state.range(1); ++j) {
+ logEvents.push_back(*event.get());
+ }
+ string err;
+ for (auto s : state) {
+ for (int metricId = 0; metricId < state.range(0); ++metricId) {
+ state.PauseTiming();
+ deleteDb(key);
+ createTableIfNeeded(key, metricId, *event.get());
+ state.ResumeTiming();
+ insert(key, metricId, logEvents, err);
+ }
+ }
+ deleteDb(key);
+}
+
+BENCHMARK(BM_insertAtomsIntoDbTablesNewConnection)
+ ->Args({1, 10})
+ ->Args({1, 50})
+ ->Args({1, 100})
+ ->Args({1, 500})
+ ->Args({10, 10})
+ ->Args({10, 20});
+
+static void BM_insertAtomsIntoDbTablesReuseConnection(benchmark::State& state) {
+ ConfigKey key = ConfigKey(111, 222);
+ int64_t metricId = 0;
+ int64_t bucketStartTimeNs = 10000000000;
+
+ unique_ptr<LogEvent> event =
+ CreateScreenStateChangedEvent(bucketStartTimeNs, android::view::DISPLAY_STATE_OFF);
+ vector<LogEvent> logEvents;
+ for (int j = 0; j < state.range(1); ++j) {
+ logEvents.push_back(*event.get());
+ }
+ sqlite3* dbHandle = getDb(key);
+ string err;
+ for (auto s : state) {
+ for (int metricId = 0; metricId < state.range(0); ++metricId) {
+ state.PauseTiming();
+ deleteTable(key, metricId);
+ createTableIfNeeded(key, metricId, *event.get());
+ state.ResumeTiming();
+ insert(key, metricId, logEvents, err);
+ }
+ }
+ closeDb(dbHandle);
+ deleteDb(key);
+}
+
+BENCHMARK(BM_insertAtomsIntoDbTablesReuseConnection)
+ ->Args({1, 10})
+ ->Args({1, 50})
+ ->Args({1, 100})
+ ->Args({1, 500})
+ ->Args({10, 10})
+ ->Args({10, 20});
+
+static void BM_createDbTables(benchmark::State& state) {
+ ConfigKey key = ConfigKey(111, 222);
+ int64_t metricId = 0;
+ int64_t bucketStartTimeNs = 10000000000;
+
+ unique_ptr<LogEvent> event =
+ CreateScreenStateChangedEvent(bucketStartTimeNs, android::view::DISPLAY_STATE_OFF);
+ vector<LogEvent> logEvents{*event.get()};
+ string err;
+ for (auto s : state) {
+ state.PauseTiming();
+ deleteTable(key, metricId);
+ state.ResumeTiming();
+ createTableIfNeeded(key, metricId, *event.get());
+ insert(key, metricId, logEvents, err);
+ }
+ deleteDb(key);
+}
+
+BENCHMARK(BM_createDbTables);
+} // namespace dbutils
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/statsd/benchmark/log_event_benchmark.cpp b/statsd/benchmark/log_event_benchmark.cpp
index 057e00b..419f323 100644
--- a/statsd/benchmark/log_event_benchmark.cpp
+++ b/statsd/benchmark/log_event_benchmark.cpp
@@ -22,11 +22,19 @@
namespace os {
namespace statsd {
-static size_t createAndParseStatsEvent(uint8_t* msg) {
+static void writeEventTestFields(AStatsEvent& event) {
+ AStatsEvent_writeInt64(&event, 3L);
+ AStatsEvent_writeInt32(&event, 2);
+ AStatsEvent_writeFloat(&event, 2.0);
+ AStatsEvent_writeString(&event, "DemoStringValue");
+}
+
+static size_t createStatsEvent(uint8_t* msg, int numElements = 1) {
AStatsEvent* event = AStatsEvent_obtain();
AStatsEvent_setAtomId(event, 100);
- AStatsEvent_writeInt32(event, 2);
- AStatsEvent_writeFloat(event, 2.0);
+ for (int i = 0; i < numElements; i++) {
+ writeEventTestFields(*event);
+ }
AStatsEvent_build(event);
size_t size;
@@ -35,9 +43,21 @@
return size;
}
+static size_t createStatsEventMedium(uint8_t* msg) {
+ return createStatsEvent(msg, 5);
+}
+
+static size_t createStatsEventLarge(uint8_t* msg) {
+ return createStatsEvent(msg, 10);
+}
+
+static size_t createStatsEventExtraLarge(uint8_t* msg) {
+ return createStatsEvent(msg, 40);
+}
+
static void BM_LogEventCreation(benchmark::State& state) {
uint8_t msg[LOGGER_ENTRY_MAX_PAYLOAD];
- size_t size = createAndParseStatsEvent(msg);
+ size_t size = createStatsEvent(msg);
while (state.KeepRunning()) {
LogEvent event(/*uid=*/ 1000, /*pid=*/ 1001);
benchmark::DoNotOptimize(event.parseBuffer(msg, size));
@@ -45,6 +65,147 @@
}
BENCHMARK(BM_LogEventCreation);
+static void BM_LogEventCreationWithPrefetch(benchmark::State& state) {
+ uint8_t msg[LOGGER_ENTRY_MAX_PAYLOAD];
+ size_t size = createStatsEvent(msg);
+ while (state.KeepRunning()) {
+ LogEvent event(/*uid=*/1000, /*pid=*/1001);
+
+ // explicitly parse header first
+ const LogEvent::BodyBufferInfo header = event.parseHeader(msg, size);
+
+ // explicitly parse body using the header
+ benchmark::DoNotOptimize(event.parseBody(header));
+ }
+}
+BENCHMARK(BM_LogEventCreationWithPrefetch);
+
+static void BM_LogEventCreationWithPrefetchOnly(benchmark::State& state) {
+ uint8_t msg[LOGGER_ENTRY_MAX_PAYLOAD];
+ size_t size = createStatsEvent(msg);
+ while (state.KeepRunning()) {
+ LogEvent event(/*uid=*/1000, /*pid=*/1001);
+
+ // explicitly parse header only and skip the body
+ benchmark::DoNotOptimize(event.parseHeader(msg, size));
+ }
+}
+BENCHMARK(BM_LogEventCreationWithPrefetchOnly);
+
+static void BM_LogEventCreationMedium(benchmark::State& state) {
+ uint8_t msg[LOGGER_ENTRY_MAX_PAYLOAD];
+ size_t size = createStatsEventMedium(msg);
+ while (state.KeepRunning()) {
+ LogEvent event(/*uid=*/1000, /*pid=*/1001);
+
+ benchmark::DoNotOptimize(event.parseBuffer(msg, size));
+ }
+}
+BENCHMARK(BM_LogEventCreationMedium);
+
+static void BM_LogEventCreationMediumWithPrefetch(benchmark::State& state) {
+ uint8_t msg[LOGGER_ENTRY_MAX_PAYLOAD];
+ size_t size = createStatsEventMedium(msg);
+ while (state.KeepRunning()) {
+ LogEvent event(/*uid=*/1000, /*pid=*/1001);
+
+ // explicitly parse header first
+ const LogEvent::BodyBufferInfo header = event.parseHeader(msg, size);
+
+ // explicitly parse body using the header
+ benchmark::DoNotOptimize(event.parseBody(header));
+ }
+}
+BENCHMARK(BM_LogEventCreationMediumWithPrefetch);
+
+static void BM_LogEventCreationMediumWithPrefetchOnly(benchmark::State& state) {
+ uint8_t msg[LOGGER_ENTRY_MAX_PAYLOAD];
+ size_t size = createStatsEventMedium(msg);
+ while (state.KeepRunning()) {
+ LogEvent event(/*uid=*/1000, /*pid=*/1001);
+
+ // explicitly parse header only and skip the body
+ benchmark::DoNotOptimize(event.parseHeader(msg, size));
+ }
+}
+BENCHMARK(BM_LogEventCreationMediumWithPrefetchOnly);
+
+static void BM_LogEventCreationLarge(benchmark::State& state) {
+ uint8_t msg[LOGGER_ENTRY_MAX_PAYLOAD];
+ size_t size = createStatsEventLarge(msg);
+ while (state.KeepRunning()) {
+ LogEvent event(/*uid=*/1000, /*pid=*/1001);
+
+ benchmark::DoNotOptimize(event.parseBuffer(msg, size));
+ }
+}
+BENCHMARK(BM_LogEventCreationLarge);
+
+static void BM_LogEventCreationLargeWithPrefetch(benchmark::State& state) {
+ uint8_t msg[LOGGER_ENTRY_MAX_PAYLOAD];
+ size_t size = createStatsEventLarge(msg);
+ while (state.KeepRunning()) {
+ LogEvent event(/*uid=*/1000, /*pid=*/1001);
+
+ // explicitly parse header first
+ const LogEvent::BodyBufferInfo header = event.parseHeader(msg, size);
+
+ // explicitly parse body using the header
+ benchmark::DoNotOptimize(event.parseBody(header));
+ }
+}
+BENCHMARK(BM_LogEventCreationLargeWithPrefetch);
+
+static void BM_LogEventCreationLargeWithPrefetchOnly(benchmark::State& state) {
+ uint8_t msg[LOGGER_ENTRY_MAX_PAYLOAD];
+ size_t size = createStatsEventLarge(msg);
+ while (state.KeepRunning()) {
+ LogEvent event(/*uid=*/1000, /*pid=*/1001);
+
+ // explicitly parse header only and skip the body
+ benchmark::DoNotOptimize(event.parseHeader(msg, size));
+ }
+}
+BENCHMARK(BM_LogEventCreationLargeWithPrefetchOnly);
+
+static void BM_LogEventCreationExtraLarge(benchmark::State& state) {
+ uint8_t msg[LOGGER_ENTRY_MAX_PAYLOAD];
+ size_t size = createStatsEventExtraLarge(msg);
+ while (state.KeepRunning()) {
+ LogEvent event(/*uid=*/1000, /*pid=*/1001);
+
+ benchmark::DoNotOptimize(event.parseBuffer(msg, size));
+ }
+}
+BENCHMARK(BM_LogEventCreationExtraLarge);
+
+static void BM_LogEventCreationExtraLargeWithPrefetch(benchmark::State& state) {
+ uint8_t msg[LOGGER_ENTRY_MAX_PAYLOAD];
+ size_t size = createStatsEventExtraLarge(msg);
+ while (state.KeepRunning()) {
+ LogEvent event(/*uid=*/1000, /*pid=*/1001);
+
+ // explicitly parse header first
+ const LogEvent::BodyBufferInfo header = event.parseHeader(msg, size);
+
+ // explicitly parse body using the header
+ benchmark::DoNotOptimize(event.parseBody(header));
+ }
+}
+BENCHMARK(BM_LogEventCreationExtraLargeWithPrefetch);
+
+static void BM_LogEventCreationExtraLargeWithPrefetchOnly(benchmark::State& state) {
+ uint8_t msg[LOGGER_ENTRY_MAX_PAYLOAD];
+ size_t size = createStatsEventExtraLarge(msg);
+ while (state.KeepRunning()) {
+ LogEvent event(/*uid=*/1000, /*pid=*/1001);
+
+ // explicitly parse header only and skip the body
+ benchmark::DoNotOptimize(event.parseHeader(msg, size));
+ }
+}
+BENCHMARK(BM_LogEventCreationExtraLargeWithPrefetchOnly);
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/statsd/benchmark/log_event_filter_benchmark.cpp b/statsd/benchmark/log_event_filter_benchmark.cpp
new file mode 100644
index 0000000..b65e88f
--- /dev/null
+++ b/statsd/benchmark/log_event_filter_benchmark.cpp
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+#include <random>
+#include <vector>
+
+#include "benchmark/benchmark.h"
+#include "socket/LogEventFilter.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+namespace {
+
+constexpr int kAtomIdsCount = 500; // Filter size setup
+constexpr int kAtomIdsSampleCount = 3000; // Queries number
+
+std::vector<int> generateSampleAtomIdsList() {
+ std::vector<int> atomIds(kAtomIdsSampleCount);
+
+ std::default_random_engine generator;
+
+ // Get atoms ids which are not in the filter to test behavior when set is searched for an
+ // an absent key
+ // Expected atoms ids are in a range 1..3000, random & evenly distributes
+ std::uniform_int_distribution<int> distribution(1, kAtomIdsSampleCount);
+
+ for (int i = 0; i < kAtomIdsSampleCount; ++i) {
+ atomIds[i] = distribution(generator);
+ }
+
+ return atomIds;
+}
+
+template <typename T>
+T generateAtomIds() {
+ T atomIds;
+
+ std::default_random_engine generator;
+ std::uniform_int_distribution<int> distribution(1, kAtomIdsCount);
+
+ for (int i = 0; i < kAtomIdsCount; ++i) {
+ atomIds.insert(distribution(generator));
+ }
+
+ return atomIds;
+}
+
+// Used to setup filter
+const std::set<int> kAtomIdsSet = generateAtomIds<std::set<int>>();
+const std::unordered_set<int> kAtomIdsUnorderedSet = generateAtomIds<std::unordered_set<int>>();
+
+const std::set<int> kAtomIdsSet2 = generateAtomIds<std::set<int>>();
+const std::unordered_set<int> kAtomIdsUnorderedSet2 = generateAtomIds<std::unordered_set<int>>();
+
+const std::set<int> kAtomIdsSet3 = generateAtomIds<std::set<int>>();
+const std::unordered_set<int> kAtomIdsUnorderedSet3 = generateAtomIds<std::unordered_set<int>>();
+
+const std::set<int> kAtomIdsSet4 = generateAtomIds<std::set<int>>();
+const std::unordered_set<int> kAtomIdsUnorderedSet4 = generateAtomIds<std::unordered_set<int>>();
+
+// Used to perform sample quieries
+const std::vector<int> kSampleIdsList = generateSampleAtomIdsList();
+
+} // namespace
+
+static void BM_LogEventFilterUnorderedSet(benchmark::State& state) {
+ while (state.KeepRunning()) {
+ LogEventFilter eventFilter;
+ // populate
+ eventFilter.setAtomIds(kAtomIdsUnorderedSet, nullptr);
+ // many fetches
+ for (const auto& atomId : kSampleIdsList) {
+ benchmark::DoNotOptimize(eventFilter.isAtomInUse(atomId));
+ }
+ }
+}
+BENCHMARK(BM_LogEventFilterUnorderedSet);
+
+static void BM_LogEventFilterUnorderedSet2Consumers(benchmark::State& state) {
+ while (state.KeepRunning()) {
+ LogEventFilter eventFilter;
+ // populate
+ eventFilter.setAtomIds(kAtomIdsUnorderedSet, &kAtomIdsUnorderedSet);
+ eventFilter.setAtomIds(kAtomIdsUnorderedSet2, &kAtomIdsUnorderedSet2);
+ eventFilter.setAtomIds(kAtomIdsUnorderedSet3, &kAtomIdsUnorderedSet);
+ eventFilter.setAtomIds(kAtomIdsUnorderedSet4, &kAtomIdsUnorderedSet2);
+ // many fetches
+ for (const auto& atomId : kSampleIdsList) {
+ benchmark::DoNotOptimize(eventFilter.isAtomInUse(atomId));
+ }
+ }
+}
+BENCHMARK(BM_LogEventFilterUnorderedSet2Consumers);
+
+static void BM_LogEventFilterSet(benchmark::State& state) {
+ while (state.KeepRunning()) {
+ LogEventFilterGeneric<std::set<int>> eventFilter;
+ // populate
+ eventFilter.setAtomIds(kAtomIdsSet, nullptr);
+ // many fetches
+ for (const auto& atomId : kSampleIdsList) {
+ benchmark::DoNotOptimize(eventFilter.isAtomInUse(atomId));
+ }
+ }
+}
+BENCHMARK(BM_LogEventFilterSet);
+
+static void BM_LogEventFilterSet2Consumers(benchmark::State& state) {
+ while (state.KeepRunning()) {
+ LogEventFilterGeneric<std::set<int>> eventFilter;
+ // populate
+ eventFilter.setAtomIds(kAtomIdsSet, &kAtomIdsSet);
+ eventFilter.setAtomIds(kAtomIdsSet2, &kAtomIdsSet2);
+ eventFilter.setAtomIds(kAtomIdsSet3, &kAtomIdsSet);
+ eventFilter.setAtomIds(kAtomIdsSet4, &kAtomIdsSet2);
+ // many fetches
+ for (const auto& atomId : kSampleIdsList) {
+ benchmark::DoNotOptimize(eventFilter.isAtomInUse(atomId));
+ }
+ }
+}
+BENCHMARK(BM_LogEventFilterSet2Consumers);
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/statsd/benchmark/metric_util.cpp b/statsd/benchmark/metric_util.cpp
index 89fd3d9..4f18298 100644
--- a/statsd/benchmark/metric_util.cpp
+++ b/statsd/benchmark/metric_util.cpp
@@ -354,10 +354,12 @@
sp<StatsPullerManager> pullerManager = new StatsPullerManager();
sp<AlarmMonitor> anomalyAlarmMonitor;
sp<AlarmMonitor> periodicAlarmMonitor;
- sp<StatsLogProcessor> processor =
- new StatsLogProcessor(uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
- timeBaseSec * NS_PER_SEC, [](const ConfigKey&) { return true; },
- [](const int&, const vector<int64_t>&) { return true; });
+ sp<StatsLogProcessor> processor = new StatsLogProcessor(
+ uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
+ timeBaseSec * NS_PER_SEC, [](const ConfigKey&) { return true; },
+ [](const int&, const vector<int64_t>&) { return true; },
+ [](const ConfigKey&, const string&, const vector<int64_t>&) {},
+ /*logEventFilter=*/nullptr);
processor->OnConfigUpdated(timeBaseSec * NS_PER_SEC, key, config);
return processor;
}
diff --git a/statsd/src/FieldValue.h b/statsd/src/FieldValue.h
index 5906942..286005a 100644
--- a/statsd/src/FieldValue.h
+++ b/statsd/src/FieldValue.h
@@ -16,7 +16,6 @@
#pragma once
#include "src/statsd_config.pb.h"
-#include "annotations.h"
namespace android {
namespace os {
diff --git a/statsd/src/StatsLogProcessor.cpp b/statsd/src/StatsLogProcessor.cpp
index 56d5f61..85f0008 100644
--- a/statsd/src/StatsLogProcessor.cpp
+++ b/statsd/src/StatsLogProcessor.cpp
@@ -20,6 +20,7 @@
#include "StatsLogProcessor.h"
#include <android-base/file.h>
+#include <android-modules-utils/sdk_level.h>
#include <cutils/multiuser.h>
#include <src/active_config_list.pb.h>
#include <src/experiment_ids.pb.h>
@@ -39,6 +40,7 @@
using namespace android;
using android::base::StringPrintf;
+using android::modules::sdklevel::IsAtLeastU;
using android::util::FIELD_COUNT_REPEATED;
using android::util::FIELD_TYPE_BOOL;
using android::util::FIELD_TYPE_FLOAT;
@@ -53,6 +55,8 @@
namespace os {
namespace statsd {
+using aidl::android::os::IStatsQueryCallback;
+
// for ConfigMetricsReportList
const int FIELD_ID_CONFIG_KEY = 1;
const int FIELD_ID_REPORTS = 2;
@@ -84,25 +88,32 @@
// Cool down period for writing data to disk to avoid overwriting files.
#define WRITE_DATA_COOL_DOWN_SEC 15
-StatsLogProcessor::StatsLogProcessor(const sp<UidMap>& uidMap,
- const sp<StatsPullerManager>& pullerManager,
- const sp<AlarmMonitor>& anomalyAlarmMonitor,
- const sp<AlarmMonitor>& periodicAlarmMonitor,
- const int64_t timeBaseNs,
- const std::function<bool(const ConfigKey&)>& sendBroadcast,
- const std::function<bool(
- const int&, const vector<int64_t>&)>& activateBroadcast)
- : mUidMap(uidMap),
+StatsLogProcessor::StatsLogProcessor(
+ const sp<UidMap>& uidMap, const sp<StatsPullerManager>& pullerManager,
+ const sp<AlarmMonitor>& anomalyAlarmMonitor, const sp<AlarmMonitor>& periodicAlarmMonitor,
+ const int64_t timeBaseNs, const std::function<bool(const ConfigKey&)>& sendBroadcast,
+ const std::function<bool(const int&, const vector<int64_t>&)>& activateBroadcast,
+ const std::function<void(const ConfigKey&, const string&, const vector<int64_t>&)>&
+ sendRestrictedMetricsBroadcast,
+ const std::shared_ptr<LogEventFilter>& logEventFilter)
+ : mLastTtlTime(0),
+ mLastFlushRestrictedTime(0),
+ mLastDbGuardrailEnforcementTime(0),
+ mUidMap(uidMap),
mPullerManager(pullerManager),
mAnomalyAlarmMonitor(anomalyAlarmMonitor),
mPeriodicAlarmMonitor(periodicAlarmMonitor),
+ mLogEventFilter(logEventFilter),
mSendBroadcast(sendBroadcast),
mSendActivationBroadcast(activateBroadcast),
+ mSendRestrictedMetricsBroadcast(sendRestrictedMetricsBroadcast),
mTimeBaseNs(timeBaseNs),
mLargestTimestampSeen(0),
mLastTimestampSeen(0) {
mPullerManager->ForceClearPullerCache();
StateManager::getInstance().updateLogSources(uidMap);
+ // It is safe called locked version at constructor - no concurrent access possible
+ updateLogEventFilterLocked();
}
StatsLogProcessor::~StatsLogProcessor() {
@@ -384,8 +395,9 @@
// Tell StatsdStats about new event
const int64_t eventElapsedTimeNs = event->GetElapsedTimestampNs();
- int atomId = event->GetTagId();
- StatsdStats::getInstance().noteAtomLogged(atomId, eventElapsedTimeNs / NS_PER_SEC);
+ const int atomId = event->GetTagId();
+ StatsdStats::getInstance().noteAtomLogged(atomId, eventElapsedTimeNs / NS_PER_SEC,
+ event->isParsedHeaderOnly());
if (!event->isValid()) {
StatsdStats::getInstance().noteAtomError(atomId);
return;
@@ -437,16 +449,24 @@
informAnomalyAlarmFiredLocked(NanoToMillis(elapsedRealtimeNs));
}
- int64_t curTimeSec = getElapsedRealtimeSec();
+ const int64_t curTimeSec = getElapsedRealtimeSec();
if (curTimeSec - mLastPullerCacheClearTimeSec > StatsdStats::kPullerCacheClearIntervalSec) {
mPullerManager->ClearPullerCacheIfNecessary(curTimeSec * NS_PER_SEC);
mLastPullerCacheClearTimeSec = curTimeSec;
}
+ flushRestrictedDataIfNecessaryLocked(elapsedRealtimeNs);
+ enforceDataTtlsIfNecessaryLocked(getWallClockNs(), elapsedRealtimeNs);
+ enforceDbGuardrailsIfNecessaryLocked(getWallClockNs(), elapsedRealtimeNs);
+
std::unordered_set<int> uidsWithActiveConfigsChanged;
std::unordered_map<int, std::vector<int64_t>> activeConfigsPerUid;
+
// pass the event to metrics managers.
for (auto& pair : mMetricsManagers) {
+ if (event->isRestricted() && !pair.second->hasRestrictedMetricsDelegate()) {
+ continue;
+ }
int uid = pair.first.GetUid();
int64_t configId = pair.first.GetId();
bool isPrevActive = pair.second->isActive();
@@ -530,9 +550,21 @@
void StatsLogProcessor::OnConfigUpdatedLocked(const int64_t timestampNs, const ConfigKey& key,
const StatsdConfig& config, bool modularUpdate) {
VLOG("Updated configuration for key %s", key.ToString().c_str());
- // Create new config if this is not a modular update or if this is a new config.
const auto& it = mMetricsManagers.find(key);
bool configValid = false;
+ if (IsAtLeastU() && it != mMetricsManagers.end()) {
+ if (it->second->hasRestrictedMetricsDelegate() !=
+ config.has_restricted_metrics_delegate_package_name()) {
+ // Not a modular update if has_restricted_metrics_delegate changes
+ modularUpdate = false;
+ }
+ if (!modularUpdate && it->second->hasRestrictedMetricsDelegate()) {
+ // Always delete the old db if restricted metrics config is not a
+ // modular update.
+ dbutils::deleteDb(key);
+ }
+ }
+ // Create new config if this is not a modular update or if this is a new config.
if (!modularUpdate || it == mMetricsManagers.end()) {
sp<MetricsManager> newMetricsManager =
new MetricsManager(key, config, mTimeBaseNs, timestampNs, mUidMap, mPullerManager,
@@ -540,8 +572,23 @@
configValid = newMetricsManager->isConfigValid();
if (configValid) {
newMetricsManager->init();
- mUidMap->OnConfigUpdated(key);
newMetricsManager->refreshTtl(timestampNs);
+ // Sdk check for U+ is unnecessary because config with restricted metrics delegate
+ // will be invalid on non U+ devices.
+ if (newMetricsManager->hasRestrictedMetricsDelegate()) {
+ mSendRestrictedMetricsBroadcast(key,
+ newMetricsManager->getRestrictedMetricsDelegate(),
+ newMetricsManager->getAllMetricIds());
+ string err;
+ if (!dbutils::updateDeviceInfoTable(key, err)) {
+ ALOGE("Failed to create device_info table for configKey %s, err: %s",
+ key.ToString().c_str(), err.c_str());
+ StatsdStats::getInstance().noteDeviceInfoTableCreationFailed(key);
+ }
+ } else if (it != mMetricsManagers.end() && it->second->hasRestrictedMetricsDelegate()) {
+ mSendRestrictedMetricsBroadcast(key, it->second->getRestrictedMetricsDelegate(),
+ {});
+ }
mMetricsManagers[key] = newMetricsManager;
VLOG("StatsdConfig valid");
}
@@ -549,16 +596,34 @@
// Preserve the existing MetricsManager, update necessary components and metadata in place.
configValid = it->second->updateConfig(config, mTimeBaseNs, timestampNs,
mAnomalyAlarmMonitor, mPeriodicAlarmMonitor);
- if (configValid) {
- mUidMap->OnConfigUpdated(key);
+ if (configValid && it->second->hasRestrictedMetricsDelegate()) {
+ mSendRestrictedMetricsBroadcast(key, it->second->getRestrictedMetricsDelegate(),
+ it->second->getAllMetricIds());
}
}
+
+ if (configValid && !config.has_restricted_metrics_delegate_package_name()) {
+ // We do not need to track uid map changes for restricted metrics since the uidmap is not
+ // stored in the sqlite db.
+ mUidMap->OnConfigUpdated(key);
+ } else if (configValid && config.has_restricted_metrics_delegate_package_name()) {
+ mUidMap->OnConfigRemoved(key);
+ }
if (!configValid) {
// If there is any error in the config, don't use it.
// Remove any existing config with the same key.
ALOGE("StatsdConfig NOT valid");
+ // Send an empty restricted metrics broadcast if the previous config was restricted.
+ if (IsAtLeastU() && it != mMetricsManagers.end() &&
+ it->second->hasRestrictedMetricsDelegate()) {
+ mSendRestrictedMetricsBroadcast(key, it->second->getRestrictedMetricsDelegate(), {});
+ dbutils::deleteDb(key);
+ }
mMetricsManagers.erase(key);
+ mUidMap->OnConfigRemoved(key);
}
+
+ updateLogEventFilterLocked();
}
size_t StatsLogProcessor::GetMetricsSize(const ConfigKey& key) const {
@@ -595,6 +660,12 @@
const DumpLatency dumpLatency, ProtoOutputStream* proto) {
std::lock_guard<std::mutex> lock(mMetricsMutex);
+ auto it = mMetricsManagers.find(key);
+ if (it != mMetricsManagers.end() && it->second->hasRestrictedMetricsDelegate()) {
+ VLOG("Unexpected call to StatsLogProcessor::onDumpReport for restricted metrics.");
+ return;
+ }
+
// Start of ConfigKey.
uint64_t configKeyToken = proto->start(FIELD_TYPE_MESSAGE | FIELD_ID_CONFIG_KEY);
proto->write(FIELD_TYPE_INT32 | FIELD_ID_UID, key.GetUid());
@@ -603,7 +674,6 @@
// End of ConfigKey.
bool keepFile = false;
- auto it = mMetricsManagers.find(key);
if (it != mMetricsManagers.end() && it->second->shouldPersistLocalHistory()) {
keepFile = true;
}
@@ -677,6 +747,12 @@
if (it == mMetricsManagers.end()) {
return;
}
+ if (it->second->hasRestrictedMetricsDelegate()) {
+ VLOG("Unexpected call to StatsLogProcessor::onConfigMetricsReportLocked for restricted "
+ "metrics.");
+ // Do not call onDumpReport for restricted metrics.
+ return;
+ }
int64_t lastReportTimeNs = it->second->getLastReportTimeNs();
int64_t lastReportWallClockNs = it->second->getLastReportWallClockNs();
@@ -764,6 +840,10 @@
if (it != mMetricsManagers.end()) {
WriteDataToDiskLocked(key, getElapsedRealtimeNs(), getWallClockNs(), CONFIG_REMOVED,
NO_TIME_CONSTRAINTS);
+ if (IsAtLeastU() && it->second->hasRestrictedMetricsDelegate()) {
+ dbutils::deleteDb(key);
+ mSendRestrictedMetricsBroadcast(key, it->second->getRestrictedMetricsDelegate(), {});
+ }
mMetricsManagers.erase(it);
mUidMap->OnConfigRemoved(key);
}
@@ -786,6 +866,221 @@
if (mMetricsManagers.empty()) {
mPullerManager->ForceClearPullerCache();
}
+
+ updateLogEventFilterLocked();
+}
+
+// TODO(b/267501143): Add unit tests when metric producer is ready
+void StatsLogProcessor::enforceDataTtlsIfNecessaryLocked(const int64_t wallClockNs,
+ const int64_t elapsedRealtimeNs) {
+ if (!IsAtLeastU()) {
+ return;
+ }
+ if (elapsedRealtimeNs - mLastTtlTime < StatsdStats::kMinTtlCheckPeriodNs) {
+ return;
+ }
+ enforceDataTtlsLocked(wallClockNs, elapsedRealtimeNs);
+}
+
+void StatsLogProcessor::flushRestrictedDataIfNecessaryLocked(const int64_t elapsedRealtimeNs) {
+ if (!IsAtLeastU()) {
+ return;
+ }
+ if (elapsedRealtimeNs - mLastFlushRestrictedTime < StatsdStats::kMinFlushRestrictedPeriodNs) {
+ return;
+ }
+ flushRestrictedDataLocked(elapsedRealtimeNs);
+}
+
+void StatsLogProcessor::querySql(const string& sqlQuery, const int32_t minSqlClientVersion,
+ const optional<vector<uint8_t>>& policyConfig,
+ const shared_ptr<IStatsQueryCallback>& callback,
+ const int64_t configId, const string& configPackage,
+ const int32_t callingUid) {
+ std::lock_guard<std::mutex> lock(mMetricsMutex);
+ string err = "";
+
+ if (!IsAtLeastU()) {
+ ALOGW("Restricted metrics query invoked on U- device");
+ StatsdStats::getInstance().noteQueryRestrictedMetricFailed(
+ configId, configPackage, std::nullopt, callingUid,
+ InvalidQueryReason(FLAG_DISABLED));
+ return;
+ }
+
+ const int64_t elapsedRealtimeNs = getElapsedRealtimeNs();
+
+ // TODO(b/268416460): validate policyConfig here
+
+ if (minSqlClientVersion > dbutils::getDbVersion()) {
+ callback->sendFailure(StringPrintf(
+ "Unsupported sqlite version. Installed Version: %d, Requested Version: %d.",
+ dbutils::getDbVersion(), minSqlClientVersion));
+ StatsdStats::getInstance().noteQueryRestrictedMetricFailed(
+ configId, configPackage, std::nullopt, callingUid,
+ InvalidQueryReason(UNSUPPORTED_SQLITE_VERSION));
+ return;
+ }
+
+ set<int32_t> configPackageUids;
+ const auto& uidMapItr = UidMap::sAidToUidMapping.find(configPackage);
+ if (uidMapItr != UidMap::sAidToUidMapping.end()) {
+ configPackageUids.insert(uidMapItr->second);
+ } else {
+ configPackageUids = mUidMap->getAppUid(configPackage);
+ }
+
+ InvalidQueryReason invalidQueryReason;
+ set<ConfigKey> keysToQuery = getRestrictedConfigKeysToQueryLocked(
+ callingUid, configId, configPackageUids, err, invalidQueryReason);
+
+ if (keysToQuery.empty()) {
+ callback->sendFailure(err);
+ StatsdStats::getInstance().noteQueryRestrictedMetricFailed(
+ configId, configPackage, std::nullopt, callingUid,
+ InvalidQueryReason(invalidQueryReason));
+ return;
+ }
+
+ if (keysToQuery.size() > 1) {
+ err = "Ambiguous ConfigKey";
+ callback->sendFailure(err);
+ StatsdStats::getInstance().noteQueryRestrictedMetricFailed(
+ configId, configPackage, std::nullopt, callingUid,
+ InvalidQueryReason(AMBIGUOUS_CONFIG_KEY));
+ return;
+ }
+
+ flushRestrictedDataLocked(elapsedRealtimeNs);
+ enforceDataTtlsLocked(getWallClockNs(), elapsedRealtimeNs);
+
+ std::vector<std::vector<std::string>> rows;
+ std::vector<int32_t> columnTypes;
+ std::vector<string> columnNames;
+ if (!dbutils::query(*(keysToQuery.begin()), sqlQuery, rows, columnTypes, columnNames, err)) {
+ callback->sendFailure(StringPrintf("failed to query db %s:", err.c_str()));
+ StatsdStats::getInstance().noteQueryRestrictedMetricFailed(
+ configId, configPackage, keysToQuery.begin()->GetUid(), callingUid,
+ InvalidQueryReason(QUERY_FAILURE), err.c_str());
+ return;
+ }
+
+ vector<string> queryData;
+ queryData.reserve(rows.size() * columnNames.size());
+ // TODO(b/268415904): avoid this vector transformation.
+ if (columnNames.size() != columnTypes.size()) {
+ callback->sendFailure("Inconsistent row sizes");
+ StatsdStats::getInstance().noteQueryRestrictedMetricFailed(
+ configId, configPackage, keysToQuery.begin()->GetUid(), callingUid,
+ InvalidQueryReason(INCONSISTENT_ROW_SIZE));
+ }
+ for (size_t i = 0; i < rows.size(); ++i) {
+ if (rows[i].size() != columnNames.size()) {
+ callback->sendFailure("Inconsistent row sizes");
+ StatsdStats::getInstance().noteQueryRestrictedMetricFailed(
+ configId, configPackage, keysToQuery.begin()->GetUid(), callingUid,
+ InvalidQueryReason(INCONSISTENT_ROW_SIZE));
+ return;
+ }
+ queryData.insert(std::end(queryData), std::make_move_iterator(std::begin(rows[i])),
+ std::make_move_iterator(std::end(rows[i])));
+ }
+ callback->sendResults(queryData, columnNames, columnTypes, rows.size());
+ StatsdStats::getInstance().noteQueryRestrictedMetricSucceed(
+ configId, configPackage, keysToQuery.begin()->GetUid(), callingUid,
+ /*queryLatencyNs=*/getElapsedRealtimeNs() - elapsedRealtimeNs);
+}
+
+set<ConfigKey> StatsLogProcessor::getRestrictedConfigKeysToQueryLocked(
+ const int32_t callingUid, const int64_t configId, const set<int32_t>& configPackageUids,
+ string& err, InvalidQueryReason& invalidQueryReason) {
+ set<ConfigKey> matchedConfigKeys;
+ for (auto uid : configPackageUids) {
+ ConfigKey configKey(uid, configId);
+ if (mMetricsManagers.find(configKey) != mMetricsManagers.end()) {
+ matchedConfigKeys.insert(configKey);
+ }
+ }
+
+ set<ConfigKey> excludedKeys;
+ for (auto& configKey : matchedConfigKeys) {
+ auto it = mMetricsManagers.find(configKey);
+ if (!it->second->validateRestrictedMetricsDelegate(callingUid)) {
+ excludedKeys.insert(configKey);
+ };
+ }
+
+ set<ConfigKey> result;
+ std::set_difference(matchedConfigKeys.begin(), matchedConfigKeys.end(), excludedKeys.begin(),
+ excludedKeys.end(), std::inserter(result, result.end()));
+ if (matchedConfigKeys.empty()) {
+ err = "No configs found matching the config key";
+ invalidQueryReason = InvalidQueryReason(CONFIG_KEY_NOT_FOUND);
+ } else if (result.empty()) {
+ err = "No matching configs for restricted metrics delegate";
+ invalidQueryReason = InvalidQueryReason(CONFIG_KEY_WITH_UNMATCHED_DELEGATE);
+ }
+
+ return result;
+}
+
+void StatsLogProcessor::EnforceDataTtls(const int64_t wallClockNs,
+ const int64_t elapsedRealtimeNs) {
+ if (!IsAtLeastU()) {
+ return;
+ }
+ std::lock_guard<std::mutex> lock(mMetricsMutex);
+ enforceDataTtlsLocked(wallClockNs, elapsedRealtimeNs);
+}
+
+void StatsLogProcessor::enforceDataTtlsLocked(const int64_t wallClockNs,
+ const int64_t elapsedRealtimeNs) {
+ for (const auto& itr : mMetricsManagers) {
+ itr.second->enforceRestrictedDataTtls(wallClockNs);
+ }
+ mLastTtlTime = elapsedRealtimeNs;
+}
+
+void StatsLogProcessor::enforceDbGuardrailsIfNecessaryLocked(const int64_t wallClockNs,
+ const int64_t elapsedRealtimeNs) {
+ if (elapsedRealtimeNs - mLastDbGuardrailEnforcementTime <
+ StatsdStats::kMinDbGuardrailEnforcementPeriodNs) {
+ return;
+ }
+ StorageManager::enforceDbGuardrails(STATS_RESTRICTED_DATA_DIR, wallClockNs / NS_PER_SEC,
+ StatsdStats::kMaxFileSize);
+ mLastDbGuardrailEnforcementTime = elapsedRealtimeNs;
+}
+
+void StatsLogProcessor::fillRestrictedMetrics(const int64_t configId, const string& configPackage,
+ const int32_t delegateUid, vector<int64_t>* output) {
+ std::lock_guard<std::mutex> lock(mMetricsMutex);
+
+ set<int32_t> configPackageUids;
+ const auto& uidMapItr = UidMap::sAidToUidMapping.find(configPackage);
+ if (uidMapItr != UidMap::sAidToUidMapping.end()) {
+ configPackageUids.insert(uidMapItr->second);
+ } else {
+ configPackageUids = mUidMap->getAppUid(configPackage);
+ }
+ string err;
+ InvalidQueryReason invalidQueryReason;
+ set<ConfigKey> keysToGetMetrics = getRestrictedConfigKeysToQueryLocked(
+ delegateUid, configId, configPackageUids, err, invalidQueryReason);
+
+ for (const ConfigKey& key : keysToGetMetrics) {
+ vector<int64_t> metricIds = mMetricsManagers[key]->getAllMetricIds();
+ output->insert(output->end(), metricIds.begin(), metricIds.end());
+ }
+}
+
+void StatsLogProcessor::flushRestrictedDataLocked(const int64_t elapsedRealtimeNs) {
+ for (const auto& it : mMetricsManagers) {
+ // no-op if metricsManager is not restricted
+ it.second->flushRestrictedData();
+ }
+
+ mLastFlushRestrictedTime = elapsedRealtimeNs;
}
void StatsLogProcessor::flushIfNecessaryLocked(const ConfigKey& key,
@@ -800,22 +1095,31 @@
// We suspect that the byteSize() computation is expensive, so we set a rate limit.
size_t totalBytes = metricsManager.byteSize();
+
mLastByteSizeTimes[key] = elapsedRealtimeNs;
+ const size_t kBytesPerConfig = metricsManager.hasRestrictedMetricsDelegate()
+ ? StatsdStats::kBytesPerRestrictedConfigTriggerFlush
+ : StatsdStats::kBytesPerConfigTriggerGetData;
bool requestDump = false;
if (totalBytes > StatsdStats::kMaxMetricsBytesPerConfig) {
// Too late. We need to start clearing data.
metricsManager.dropData(elapsedRealtimeNs);
StatsdStats::getInstance().noteDataDropped(key, totalBytes);
VLOG("StatsD had to toss out metrics for %s", key.ToString().c_str());
- } else if ((totalBytes > StatsdStats::kBytesPerConfigTriggerGetData) ||
+ } else if ((totalBytes > kBytesPerConfig) ||
(mOnDiskDataConfigs.find(key) != mOnDiskDataConfigs.end())) {
- // Request to send a broadcast if:
+ // Request to dump if:
// 1. in memory data > threshold OR
// 2. config has old data report on disk.
requestDump = true;
}
if (requestDump) {
+ if (metricsManager.hasRestrictedMetricsDelegate()) {
+ metricsManager.flushRestrictedData();
+ // No need to send broadcast for restricted metrics.
+ return;
+ }
// Send broadcast so that receivers can pull data.
auto lastBroadcastTime = mLastBroadcastTimes.find(key);
if (lastBroadcastTime != mLastBroadcastTimes.end()) {
@@ -842,6 +1146,10 @@
!mMetricsManagers.find(key)->second->shouldWriteToDisk()) {
return;
}
+ if (mMetricsManagers.find(key)->second->hasRestrictedMetricsDelegate()) {
+ mMetricsManagers.find(key)->second->flushRestrictedData();
+ return;
+ }
vector<uint8_t> buffer;
onConfigMetricsReportLocked(key, timestampNs, wallClockNs,
true /* include_current_partial_bucket*/, true /* erase_data */,
@@ -1158,6 +1466,31 @@
}
}
+LogEventFilter::AtomIdSet StatsLogProcessor::getDefaultAtomIdSet() {
+ // populate hard-coded list of useful atoms
+ // we add also atoms which could be pushed by statsd itself to simplify the logic
+ // to handle metric configs update: APP_BREADCRUMB_REPORTED & ANOMALY_DETECTED
+ LogEventFilter::AtomIdSet allAtomIds{
+ util::BINARY_PUSH_STATE_CHANGED, util::DAVEY_OCCURRED,
+ util::ISOLATED_UID_CHANGED, util::APP_BREADCRUMB_REPORTED,
+ util::WATCHDOG_ROLLBACK_OCCURRED, util::ANOMALY_DETECTED};
+ return allAtomIds;
+}
+
+void StatsLogProcessor::updateLogEventFilterLocked() const {
+ VLOG("StatsLogProcessor: Updating allAtomIds");
+ if (!mLogEventFilter) {
+ return;
+ }
+ LogEventFilter::AtomIdSet allAtomIds = getDefaultAtomIdSet();
+ for (const auto& metricsManager : mMetricsManagers) {
+ metricsManager.second->addAllAtomIds(allAtomIds);
+ }
+ StateManager::getInstance().addAllAtomIds(allAtomIds);
+ VLOG("StatsLogProcessor: Updating allAtomIds done. Total atoms %d", (int)allAtomIds.size());
+ mLogEventFilter->setAtomIds(std::move(allAtomIds), this);
+}
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/statsd/src/StatsLogProcessor.h b/statsd/src/StatsLogProcessor.h
index 6681763..4841263 100644
--- a/statsd/src/StatsLogProcessor.h
+++ b/statsd/src/StatsLogProcessor.h
@@ -16,33 +16,37 @@
#pragma once
+#include <aidl/android/os/BnStatsd.h>
#include <gtest/gtest_prod.h>
+#include <stdio.h>
+
+#include <unordered_map>
+
#include "config/ConfigListener.h"
+#include "external/StatsPullerManager.h"
#include "logd/LogEvent.h"
#include "metrics/MetricsManager.h"
#include "packages/UidMap.h"
-#include "external/StatsPullerManager.h"
-
+#include "socket/LogEventFilter.h"
#include "src/statsd_config.pb.h"
#include "src/statsd_metadata.pb.h"
-#include <stdio.h>
-#include <unordered_map>
-
namespace android {
namespace os {
namespace statsd {
-
class StatsLogProcessor : public ConfigListener, public virtual PackageInfoListener {
public:
- StatsLogProcessor(const sp<UidMap>& uidMap, const sp<StatsPullerManager>& pullerManager,
- const sp<AlarmMonitor>& anomalyAlarmMonitor,
- const sp<AlarmMonitor>& subscriberTriggerAlarmMonitor,
- const int64_t timeBaseNs,
- const std::function<bool(const ConfigKey&)>& sendBroadcast,
- const std::function<bool(const int&,
- const vector<int64_t>&)>& sendActivationBroadcast);
+ StatsLogProcessor(
+ const sp<UidMap>& uidMap, const sp<StatsPullerManager>& pullerManager,
+ const sp<AlarmMonitor>& anomalyAlarmMonitor,
+ const sp<AlarmMonitor>& subscriberTriggerAlarmMonitor, const int64_t timeBaseNs,
+ const std::function<bool(const ConfigKey&)>& sendBroadcast,
+ const std::function<bool(const int&, const vector<int64_t>&)>& sendActivationBroadcast,
+ const std::function<void(const ConfigKey&, const string&, const vector<int64_t>&)>&
+ sendRestrictedMetricsBroadcast,
+ const std::shared_ptr<LogEventFilter>& logEventFilter);
+
virtual ~StatsLogProcessor();
void OnLogEvent(LogEvent* event);
@@ -108,6 +112,9 @@
int64_t currentWallClockTimeNs,
int64_t systemElapsedTimeNs);
+ /* Enforces ttls for restricted metrics */
+ void EnforceDataTtls(const int64_t wallClockNs, const int64_t elapsedRealtimeNs);
+
/* Sets the active status/ttl for all configs and metrics to the status in ActiveConfigList. */
void SetConfigsActiveState(const ActiveConfigList& activeConfigList, int64_t currentTimeNs);
@@ -142,6 +149,12 @@
inline void setPrintLogs(bool enabled) {
std::lock_guard<std::mutex> lock(mMetricsMutex);
mPrintAllLogs = enabled;
+
+ if (mLogEventFilter) {
+ // Turning on print logs turns off pushed event filtering to enforce
+ // complete log event buffer parsing
+ mLogEventFilter->setFilteringEnabled(!enabled);
+ }
}
// Add a specific config key to the possible configs to dump ASAP.
@@ -151,6 +164,17 @@
void cancelAnomalyAlarm();
+ void querySql(const string& sqlQuery, const int32_t minSqlClientVersion,
+ const optional<vector<uint8_t>>& policyConfig,
+ const shared_ptr<aidl::android::os::IStatsQueryCallback>& callback,
+ const int64_t configId, const string& configPackage, const int32_t callingUid);
+
+ void fillRestrictedMetrics(const int64_t configId, const string& configPackage,
+ const int32_t delegateUid, vector<int64_t>* output);
+
+ /* Returns pre-defined list of atoms to parse by LogEventFilter */
+ static LogEventFilter::AtomIdSet getDefaultAtomIdSet();
+
private:
// For testing only.
inline sp<AlarmMonitor> getAnomalyAlarmMonitor() const {
@@ -178,6 +202,15 @@
// Tracks when we last checked the bytes consumed for each config key.
std::unordered_map<ConfigKey, int64_t> mLastByteSizeTimes;
+ // Tracks when we last checked the ttl for restricted metrics.
+ int64_t mLastTtlTime;
+
+ // Tracks when we last flushed restricted metrics.
+ int64_t mLastFlushRestrictedTime;
+
+ // Tracks when we last checked db guardrails.
+ int64_t mLastDbGuardrailEnforcementTime;
+
// Tracks which config keys has metric reports on disk
std::set<ConfigKey> mOnDiskDataConfigs;
@@ -189,6 +222,8 @@
sp<AlarmMonitor> mPeriodicAlarmMonitor;
+ std::shared_ptr<LogEventFilter> mLogEventFilter;
+
void OnLogEvent(LogEvent* event, int64_t elapsedRealtimeNs);
void resetIfConfigTtlExpiredLocked(const int64_t eventTimeNs);
@@ -228,10 +263,28 @@
(e.g., before reboot). So no need to further persist local history.*/
const bool dataSavedToDisk, vector<uint8_t>* proto);
+ /* Check if it is time enforce data ttls for restricted metrics, and if it is, enforce ttls
+ * on all restricted metrics. */
+ void enforceDataTtlsIfNecessaryLocked(const int64_t wallClockNs,
+ const int64_t elapsedRealtimeNs);
+
+ // Enforces ttls on all restricted metrics.
+ void enforceDataTtlsLocked(const int64_t wallClockNs, const int64_t elapsedRealtimeNs);
+
+ // Enforces that dbs are within guardrail parameters.
+ void enforceDbGuardrailsIfNecessaryLocked(const int64_t wallClockNs,
+ const int64_t elapsedRealtimeNs);
+
/* Check if we should send a broadcast if approaching memory limits and if we're over, we
* actually delete the data. */
void flushIfNecessaryLocked(const ConfigKey& key, MetricsManager& metricsManager);
+ set<ConfigKey> getRestrictedConfigKeysToQueryLocked(const int32_t callingUid,
+ const int64_t configId,
+ const set<int32_t>& configPackageUids,
+ string& err,
+ InvalidQueryReason& invalidQueryReason);
+
// Maps the isolated uid in the log event to host uid if the log event contains uid fields.
void mapIsolatedUidToHostUidIfNecessaryLocked(LogEvent* event) const;
@@ -268,6 +321,13 @@
const int64_t& timestampNs,
unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>>& alarmSet);
+ void flushRestrictedDataLocked(const int64_t elapsedRealtimeNs);
+
+ void flushRestrictedDataIfNecessaryLocked(const int64_t elapsedRealtimeNs);
+
+ /* Tells LogEventFilter about atom ids to parse */
+ void updateLogEventFilterLocked() const;
+
// Function used to send a broadcast so that receiver for the config key can call getData
// to retrieve the stored data.
std::function<bool(const ConfigKey& key)> mSendBroadcast;
@@ -276,6 +336,12 @@
// are currently active.
std::function<bool(const int& uid, const vector<int64_t>& configIds)> mSendActivationBroadcast;
+ // Function used to send a broadcast if necessary so the receiver can be notified of the
+ // restricted metrics for the given config.
+ std::function<void(const ConfigKey& key, const string& delegatePackage,
+ const vector<int64_t>& restrictedMetricIds)>
+ mSendRestrictedMetricsBroadcast;
+
const int64_t mTimeBaseNs;
// Largest timestamp of the events that we have processed.
@@ -299,6 +365,7 @@
bool mPrintAllLogs = false;
+ friend class StatsLogProcessorTestRestricted;
FRIEND_TEST(StatsLogProcessorTest, TestOutOfOrderLogs);
FRIEND_TEST(StatsLogProcessorTest, TestRateLimitByteSize);
FRIEND_TEST(StatsLogProcessorTest, TestRateLimitBroadcast);
@@ -310,7 +377,22 @@
FRIEND_TEST(StatsLogProcessorTest,
TestActivationOnBootMultipleActivationsDifferentActivationTypes);
FRIEND_TEST(StatsLogProcessorTest, TestActivationsPersistAcrossSystemServerRestart);
-
+ FRIEND_TEST(StatsLogProcessorTest, LogEventFilterOnSetPrintLogs);
+ FRIEND_TEST(StatsLogProcessorTest, TestUidMapHasSnapshot);
+ FRIEND_TEST(StatsLogProcessorTest, TestEmptyConfigHasNoUidMap);
+ FRIEND_TEST(StatsLogProcessorTest, TestReportIncludesSubConfig);
+ FRIEND_TEST(StatsLogProcessorTest, TestPullUidProviderSetOnConfigUpdate);
+ FRIEND_TEST(StatsLogProcessorTestRestricted, TestInconsistentRestrictedMetricsConfigUpdate);
+ FRIEND_TEST(StatsLogProcessorTestRestricted, TestRestrictedLogEventPassed);
+ FRIEND_TEST(StatsLogProcessorTestRestricted, TestRestrictedLogEventNotPassed);
+ FRIEND_TEST(StatsLogProcessorTestRestricted, RestrictedMetricsManagerOnDumpReportNotCalled);
+ FRIEND_TEST(StatsLogProcessorTestRestricted, NonRestrictedMetricsManagerOnDumpReportCalled);
+ FRIEND_TEST(StatsLogProcessorTestRestricted, RestrictedMetricOnDumpReportEmpty);
+ FRIEND_TEST(StatsLogProcessorTestRestricted, NonRestrictedMetricOnDumpReportNotEmpty);
+ FRIEND_TEST(StatsLogProcessorTestRestricted, RestrictedMetricNotWriteToDisk);
+ FRIEND_TEST(StatsLogProcessorTestRestricted, NonRestrictedMetricWriteToDisk);
+ FRIEND_TEST(StatsLogProcessorTestRestricted, RestrictedMetricFlushIfReachMemoryLimit);
+ FRIEND_TEST(StatsLogProcessorTestRestricted, RestrictedMetricNotFlushIfNotReachMemoryLimit);
FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForSumDuration1);
FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForSumDuration2);
FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForSumDuration3);
@@ -330,6 +412,19 @@
FRIEND_TEST(GaugeMetricE2ePulledTest, TestRandomSamplePulledEventsWithActivation);
FRIEND_TEST(GaugeMetricE2ePulledTest, TestRandomSamplePulledEventsNoCondition);
FRIEND_TEST(GaugeMetricE2ePulledTest, TestConditionChangeToTrueSamplePulledEvents);
+ FRIEND_TEST(RestrictedEventMetricE2eTest, TestEnforceTtlRemovesOldEvents);
+ FRIEND_TEST(RestrictedEventMetricE2eTest, TestFlagDisabled);
+ FRIEND_TEST(RestrictedEventMetricE2eTest, TestLogEventsEnforceTtls);
+ FRIEND_TEST(RestrictedEventMetricE2eTest, TestQueryEnforceTtls);
+ FRIEND_TEST(RestrictedEventMetricE2eTest, TestLogEventsDoesNotEnforceTtls);
+ FRIEND_TEST(RestrictedEventMetricE2eTest, TestNotFlushed);
+ FRIEND_TEST(RestrictedEventMetricE2eTest, TestFlushInWriteDataToDisk);
+ FRIEND_TEST(RestrictedEventMetricE2eTest, TestFlushPeriodically);
+ FRIEND_TEST(RestrictedEventMetricE2eTest, TestTTlsEnforceDbGuardrails);
+ FRIEND_TEST(RestrictedEventMetricE2eTest, TestOnLogEventMalformedDbNameDeleted);
+ FRIEND_TEST(RestrictedEventMetricE2eTest, TestEnforceDbGuardrails);
+ FRIEND_TEST(RestrictedEventMetricE2eTest, TestEnforceDbGuardrailsDoesNotDeleteBeforeGuardrail);
+ FRIEND_TEST(RestrictedEventMetricE2eTest, TestRestrictedMetricLoadsTtlFromDisk);
FRIEND_TEST(AnomalyCountDetectionE2eTest, TestSlicedCountMetric_single_bucket);
FRIEND_TEST(AnomalyCountDetectionE2eTest, TestSlicedCountMetric_multiple_buckets);
@@ -389,6 +484,8 @@
FRIEND_TEST(ValueMetricE2eTest, TestInitWithValueFieldPositionALL);
FRIEND_TEST(KllMetricE2eTest, TestInitWithKllFieldPositionALL);
+
+ FRIEND_TEST(StatsServiceStatsdInitTest, StatsServiceStatsdInitTest);
};
} // namespace statsd
diff --git a/statsd/src/StatsService.cpp b/statsd/src/StatsService.cpp
index 302b81b..c34964d 100644
--- a/statsd/src/StatsService.cpp
+++ b/statsd/src/StatsService.cpp
@@ -18,20 +18,15 @@
#include "Log.h"
#include "StatsService.h"
-#include "stats_log_util.h"
-#include "android-base/stringprintf.h"
-#include "config/ConfigKey.h"
-#include "config/ConfigManager.h"
-#include "guardrail/StatsdStats.h"
-#include "storage/StorageManager.h"
-#include "subscriber/SubscriberReporter.h"
#include <android-base/file.h>
#include <android-base/strings.h>
+#include <android-modules-utils/sdk_level.h>
+#include <android/binder_ibinder_platform.h>
#include <cutils/multiuser.h>
+#include <private/android_filesystem_config.h>
#include <src/statsd_config.pb.h>
#include <src/uid_data.pb.h>
-#include <private/android_filesystem_config.h>
#include <statslog_statsd.h>
#include <stdio.h>
#include <stdlib.h>
@@ -39,9 +34,20 @@
#include <unistd.h>
#include <utils/String16.h>
+#include "android-base/stringprintf.h"
+#include "config/ConfigKey.h"
+#include "config/ConfigManager.h"
+#include "flags/FlagProvider.h"
+#include "guardrail/StatsdStats.h"
+#include "stats_log_util.h"
+#include "storage/StorageManager.h"
+#include "subscriber/SubscriberReporter.h"
+#include "utils/DbUtils.h"
+
using namespace android;
using android::base::StringPrintf;
+using android::modules::sdklevel::IsAtLeastU;
using android::util::FIELD_COUNT_REPEATED;
using android::util::FIELD_TYPE_MESSAGE;
@@ -53,6 +59,8 @@
constexpr const char* kPermissionDump = "android.permission.DUMP";
+constexpr const char* kTracedProbesSid = "u:r:traced_probes:s0";
+
constexpr const char* kPermissionRegisterPullAtom = "android.permission.REGISTER_STATS_PULL_ATOM";
#define STATS_SERVICE_DIR "/data/misc/stats-service"
@@ -88,7 +96,37 @@
} \
}
-StatsService::StatsService(const sp<UidMap>& uidMap, shared_ptr<LogEventQueue> queue)
+Status checkSid(const char* expectedSid) {
+ const char* sid = nullptr;
+ if (__builtin_available(android __ANDROID_API_U__, *)) {
+ sid = AIBinder_getCallingSid();
+ }
+
+ // root (which is the uid in tests for example) has all permissions.
+ uid_t uid = AIBinder_getCallingUid();
+ if (uid == AID_ROOT) {
+ return Status::ok();
+ }
+
+ if (sid != nullptr && strcmp(expectedSid, sid) == 0) {
+ return Status::ok();
+ } else {
+ return exception(EX_SECURITY,
+ StringPrintf("SID '%s' is not expected SID '%s'", sid, expectedSid));
+ }
+}
+
+#define ENFORCE_SID(sid) \
+ { \
+ Status status = checkSid((sid)); \
+ if (!status.isOk()) { \
+ return status; \
+ } \
+ }
+
+StatsService::StatsService(const sp<UidMap>& uidMap, shared_ptr<LogEventQueue> queue,
+ const std::shared_ptr<LogEventFilter>& logEventFilter,
+ int initEventDelaySecs)
: mUidMap(uidMap),
mAnomalyAlarmMonitor(new AlarmMonitor(
MIN_DIFF_TO_UPDATE_REGISTERED_ALARM_SECS,
@@ -115,10 +153,12 @@
}
})),
mEventQueue(std::move(queue)),
+ mLogEventFilter(logEventFilter),
mBootCompleteTrigger({kBootCompleteTag, kUidMapReceivedTag, kAllPullersRegisteredTag},
- [this]() { mProcessor->onStatsdInitCompleted(getElapsedRealtimeNs()); }),
+ [this]() { onStatsdInitCompleted(); }),
mStatsCompanionServiceDeathRecipient(
- AIBinder_DeathRecipient_new(StatsService::statsCompanionServiceDied)) {
+ AIBinder_DeathRecipient_new(StatsService::statsCompanionServiceDied)),
+ mInitEventDelaySecs(initEventDelaySecs) {
mPullerManager = new StatsPullerManager();
StatsPuller::SetUidMap(mUidMap);
mConfigManager = new ConfigManager();
@@ -160,7 +200,29 @@
}
VLOG("StatsService::active configs broadcast failed for uid %d", uid);
return false;
- });
+ },
+ [this](const ConfigKey& key, const string& delegatePackage,
+ const vector<int64_t>& restrictedMetrics) {
+ set<string> configPackages;
+ set<int32_t> delegateUids;
+ for (const auto& kv : UidMap::sAidToUidMapping) {
+ if (kv.second == static_cast<uint32_t>(key.GetUid())) {
+ configPackages.insert(kv.first);
+ }
+ if (kv.first == delegatePackage) {
+ delegateUids.insert(kv.second);
+ }
+ }
+ if (configPackages.empty()) {
+ configPackages = mUidMap->getAppNamesFromUid(key.GetUid(), true);
+ }
+ if (delegateUids.empty()) {
+ delegateUids = mUidMap->getAppUid(delegatePackage);
+ }
+ mConfigManager->SendRestrictedMetricsBroadcast(configPackages, key.GetId(),
+ delegateUids, restrictedMetrics);
+ },
+ logEventFilter);
mUidMap->setListener(mProcessor);
mConfigManager->AddListener(mProcessor);
@@ -365,12 +427,7 @@
}
if (!utf8Args[0].compare(String8("data-subscribe"))) {
- {
- std::lock_guard<std::mutex> lock(mShellSubscriberMutex);
- if (mShellSubscriber == nullptr) {
- mShellSubscriber = new ShellSubscriber(mUidMap, mPullerManager);
- }
- }
+ initShellSubscriber();
int timeoutSec = -1;
if (argc >= 2) {
timeoutSec = atoi(utf8Args[1].c_str());
@@ -1086,10 +1143,28 @@
return Status::ok();
}
+void StatsService::onStatsdInitCompleted() {
+ if (mInitEventDelaySecs > 0) {
+ // The hard-coded delay is determined based on perfetto traces evaluation
+ // for statsd during the boot.
+ // The delay is required to properly process event storm which often has place
+ // after device boot.
+ // This function is called from a dedicated thread without holding locks, so sleeping is ok.
+ // See MultiConditionTrigger::markComplete() executorThread for details
+ // For more details see http://b/277958338
+ std::this_thread::sleep_for(std::chrono::seconds(mInitEventDelaySecs));
+ }
+
+ mProcessor->onStatsdInitCompleted(getElapsedRealtimeNs());
+}
+
void StatsService::Startup() {
mConfigManager->Startup();
+ int64_t wallClockNs = getWallClockNs();
+ int64_t elapsedRealtimeNs = getElapsedRealtimeNs();
mProcessor->LoadActiveConfigsFromDisk();
- mProcessor->LoadMetadataFromDisk(getWallClockNs(), getElapsedRealtimeNs());
+ mProcessor->LoadMetadataFromDisk(wallClockNs, elapsedRealtimeNs);
+ mProcessor->EnforceDataTtls(wallClockNs, elapsedRealtimeNs);
}
void StatsService::Terminate() {
@@ -1314,11 +1389,8 @@
Status StatsService::updateProperties(const vector<PropertyParcel>& properties) {
ENFORCE_UID(AID_SYSTEM);
- for (const auto& [property, value] : properties) {
- if (property == kIncludeCertificateHash) {
- mUidMap->setIncludeCertificateHash(value == "true");
- }
- }
+ // TODO(b/281765292): Forward statsd_java properties received here to FlagProvider.
+
return Status::ok();
}
@@ -1356,6 +1428,90 @@
mPullerManager->SetStatsCompanionService(nullptr);
}
+Status StatsService::setRestrictedMetricsChangedOperation(const int64_t configId,
+ const string& configPackage,
+ const shared_ptr<IPendingIntentRef>& pir,
+ const int32_t callingUid,
+ vector<int64_t>* output) {
+ ENFORCE_UID(AID_SYSTEM);
+ if (!IsAtLeastU()) {
+ ALOGW("setRestrictedMetricsChangedOperation invoked on U- device");
+ return Status::ok();
+ }
+ mConfigManager->SetRestrictedMetricsChangedReceiver(configPackage, configId, callingUid, pir);
+ if (output != nullptr) {
+ mProcessor->fillRestrictedMetrics(configId, configPackage, callingUid, output);
+ } else {
+ ALOGW("StatsService::setRestrictedMetricsChangedOperation output was nullptr");
+ }
+ return Status::ok();
+}
+
+Status StatsService::removeRestrictedMetricsChangedOperation(const int64_t configId,
+ const string& configPackage,
+ const int32_t callingUid) {
+ ENFORCE_UID(AID_SYSTEM);
+ if (!IsAtLeastU()) {
+ ALOGW("removeRestrictedMetricsChangedOperation invoked on U- device");
+ return Status::ok();
+ }
+ mConfigManager->RemoveRestrictedMetricsChangedReceiver(configPackage, configId, callingUid);
+ return Status::ok();
+}
+
+Status StatsService::querySql(const string& sqlQuery, const int32_t minSqlClientVersion,
+ const optional<vector<uint8_t>>& policyConfig,
+ const shared_ptr<IStatsQueryCallback>& callback,
+ const int64_t configKey, const string& configPackage,
+ const int32_t callingUid) {
+ ENFORCE_UID(AID_SYSTEM);
+ if (callback == nullptr) {
+ ALOGW("querySql called with null callback.");
+ StatsdStats::getInstance().noteQueryRestrictedMetricFailed(
+ configKey, configPackage, std::nullopt, callingUid,
+ InvalidQueryReason(NULL_CALLBACK));
+ return Status::ok();
+ }
+ mProcessor->querySql(sqlQuery, minSqlClientVersion, policyConfig, callback, configKey,
+ configPackage, callingUid);
+ return Status::ok();
+}
+
+Status StatsService::addSubscription(const vector<uint8_t>& subscriptionConfig,
+ const shared_ptr<IStatsSubscriptionCallback>& callback) {
+ ENFORCE_SID(kTracedProbesSid);
+
+ initShellSubscriber();
+
+ mShellSubscriber->startNewSubscription(subscriptionConfig, callback);
+ return Status::ok();
+}
+
+Status StatsService::removeSubscription(const shared_ptr<IStatsSubscriptionCallback>& callback) {
+ ENFORCE_SID(kTracedProbesSid);
+
+ if (mShellSubscriber != nullptr) {
+ mShellSubscriber->unsubscribe(callback);
+ }
+ return Status::ok();
+}
+
+Status StatsService::flushSubscription(const shared_ptr<IStatsSubscriptionCallback>& callback) {
+ ENFORCE_SID(kTracedProbesSid);
+
+ if (mShellSubscriber != nullptr) {
+ mShellSubscriber->flushSubscription(callback);
+ }
+ return Status::ok();
+}
+
+void StatsService::initShellSubscriber() {
+ std::lock_guard<std::mutex> lock(mShellSubscriberMutex);
+ if (mShellSubscriber == nullptr) {
+ mShellSubscriber = new ShellSubscriber(mUidMap, mPullerManager, mLogEventFilter);
+ }
+}
+
void StatsService::stopReadingLogs() {
mIsStopRequested = true;
// Push this event so that readLogs will process and break out of the loop
diff --git a/statsd/src/StatsService.h b/statsd/src/StatsService.h
index f8fe03c..da1b4ab 100644
--- a/statsd/src/StatsService.h
+++ b/statsd/src/StatsService.h
@@ -20,6 +20,7 @@
#include <aidl/android/os/BnStatsd.h>
#include <aidl/android/os/IPendingIntentRef.h>
#include <aidl/android/os/IPullAtomCallback.h>
+#include <aidl/android/os/IStatsSubscriptionCallback.h>
#include <aidl/android/util/PropertyParcel.h>
#include <gtest/gtest_prod.h>
#include <utils/Looper.h>
@@ -40,10 +41,13 @@
using namespace android::os;
using namespace std;
+using ::ndk::SpAIBinder;
using Status = ::ndk::ScopedAStatus;
using aidl::android::os::BnStatsd;
using aidl::android::os::IPendingIntentRef;
using aidl::android::os::IPullAtomCallback;
+using aidl::android::os::IStatsQueryCallback;
+using aidl::android::os::IStatsSubscriptionCallback;
using aidl::android::util::PropertyParcel;
using ::ndk::ScopedAIBinder_DeathRecipient;
using ::ndk::ScopedFileDescriptor;
@@ -52,11 +56,11 @@
namespace os {
namespace statsd {
-constexpr const char* kIncludeCertificateHash = "include_certificate_hash";
-
class StatsService : public BnStatsd {
public:
- StatsService(const sp<UidMap>& uidMap, shared_ptr<LogEventQueue> queue);
+ StatsService(const sp<UidMap>& uidMap, shared_ptr<LogEventQueue> queue,
+ const std::shared_ptr<LogEventFilter>& logEventFilter,
+ int initEventDelaySecs = kStatsdInitDelaySecs);
virtual ~StatsService();
/** The anomaly alarm registered with AlarmManager won't be updated by less than this. */
@@ -206,6 +210,53 @@
*/
virtual Status updateProperties(const vector<PropertyParcel>& properties);
+ /**
+ * Binder call to let clients register the restricted metrics changed operation for the given
+ * config and calling uid.
+ */
+ virtual Status setRestrictedMetricsChangedOperation(const int64_t configKey,
+ const string& configPackage,
+ const shared_ptr<IPendingIntentRef>& pir,
+ const int32_t callingUid,
+ vector<int64_t>* output);
+
+ /**
+ * Binder call to remove the restricted metrics changed operation for the specified config
+ * and calling uid.
+ */
+ virtual Status removeRestrictedMetricsChangedOperation(const int64_t configKey,
+ const string& configPackage,
+ const int32_t callingUid);
+
+ /**
+ * Binder call to query data in statsd sql store.
+ */
+ virtual Status querySql(const string& sqlQuery, const int32_t minSqlClientVersion,
+ const optional<vector<uint8_t>>& policyConfig,
+ const shared_ptr<IStatsQueryCallback>& callback,
+ const int64_t configKey, const string& configPackage,
+ const int32_t callingUid);
+
+ /**
+ * Binder call to add a subscription.
+ */
+ virtual Status addSubscription(const vector<uint8_t>& subscriptionConfig,
+ const shared_ptr<IStatsSubscriptionCallback>& callback) override;
+
+ /**
+ * Binder call to remove a subscription.
+ */
+ virtual Status removeSubscription(
+ const shared_ptr<IStatsSubscriptionCallback>& callback) override;
+
+ /**
+ * Binder call to flush atom events for a subscription.
+ */
+ virtual Status flushSubscription(
+ const shared_ptr<IStatsSubscriptionCallback>& callback) override;
+
+ const static int kStatsdInitDelaySecs = 90;
+
private:
/**
* Load system properties at init.
@@ -348,6 +399,16 @@
void statsCompanionServiceDiedImpl();
/**
+ * Initialize ShellSubscriber
+ */
+ void initShellSubscriber();
+
+ /*
+ * Notify StatsLogProcessor of boot completed
+ */
+ void onStatsdInitCompleted();
+
+ /**
* This method is used to stop log reader thread.
*/
void stopReadingLogs();
@@ -396,6 +457,7 @@
*/
mutable mutex mShellSubscriberMutex;
shared_ptr<LogEventQueue> mEventQueue;
+ std::shared_ptr<LogEventFilter> mLogEventFilter;
std::unique_ptr<std::thread> mLogsReaderThread;
@@ -406,7 +468,11 @@
ScopedAIBinder_DeathRecipient mStatsCompanionServiceDeathRecipient;
+ const int mInitEventDelaySecs;
+
friend class StatsServiceConfigTest;
+ friend class StatsServiceStatsdInitTest;
+ friend class RestrictedConfigE2ETest;
FRIEND_TEST(StatsLogProcessorTest, TestActivationsPersistAcrossSystemServerRestart);
FRIEND_TEST(StatsServiceTest, TestAddConfig_simple);
@@ -426,13 +492,18 @@
FRIEND_TEST(PartialBucketE2eTest, TestGaugeMetricWithoutMinPartialBucket);
FRIEND_TEST(PartialBucketE2eTest, TestGaugeMetricWithMinPartialBucket);
FRIEND_TEST(PartialBucketE2eTest, TestCountMetricNoSplitByDefault);
-
+ FRIEND_TEST(RestrictedConfigE2ETest, NonRestrictedConfigGetReport);
+ FRIEND_TEST(RestrictedConfigE2ETest, RestrictedConfigNoReport);
+ FRIEND_TEST(RestrictedConfigE2ETest,
+ TestSendRestrictedMetricsChangedBroadcastMultipleMatchedConfigs);
FRIEND_TEST(ConfigUpdateE2eTest, TestAnomalyDurationMetric);
FRIEND_TEST(AnomalyDurationDetectionE2eTest, TestDurationMetric_SUM_single_bucket);
FRIEND_TEST(AnomalyDurationDetectionE2eTest, TestDurationMetric_SUM_partial_bucket);
FRIEND_TEST(AnomalyDurationDetectionE2eTest, TestDurationMetric_SUM_multiple_buckets);
FRIEND_TEST(AnomalyDurationDetectionE2eTest, TestDurationMetric_SUM_long_refractory_period);
+
+ FRIEND_TEST(StatsServiceStatsdInitTest, StatsServiceStatsdInitTest);
};
} // namespace statsd
diff --git a/statsd/src/annotations.h b/statsd/src/annotations.h
deleted file mode 100644
index cf7f543..0000000
--- a/statsd/src/annotations.h
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2020 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.
- */
-
-#pragma once
-
-namespace android {
-namespace os {
-namespace statsd {
-
-const uint8_t ANNOTATION_ID_IS_UID = 1;
-const uint8_t ANNOTATION_ID_TRUNCATE_TIMESTAMP = 2;
-const uint8_t ANNOTATION_ID_PRIMARY_FIELD = 3;
-const uint8_t ANNOTATION_ID_EXCLUSIVE_STATE = 4;
-const uint8_t ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID = 5;
-const uint8_t ANNOTATION_ID_TRIGGER_STATE_RESET = 7;
-const uint8_t ANNOTATION_ID_STATE_NESTED = 8;
-
-} // namespace statsd
-} // namespace os
-} // namespace android
diff --git a/statsd/src/config/ConfigKeyWithPackage.h b/statsd/src/config/ConfigKeyWithPackage.h
new file mode 100644
index 0000000..85e95d5
--- /dev/null
+++ b/statsd/src/config/ConfigKeyWithPackage.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#pragma once
+
+#include <string>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using std::hash;
+using std::string;
+
+/**
+ * A config key that uses a package name instead of a uid. Generally, ConfigKey which uses a uid
+ * should be used. This is currently only used for restricted metrics changed operation.
+ */
+class ConfigKeyWithPackage {
+public:
+ ConfigKeyWithPackage(const string& package, const int64_t id) : mPackage(package), mId(id) {
+ }
+
+ inline string GetPackage() const {
+ return mPackage;
+ }
+ inline int64_t GetId() const {
+ return mId;
+ }
+
+ inline bool operator<(const ConfigKeyWithPackage& that) const {
+ if (mPackage != that.mPackage) {
+ return mPackage < that.mPackage;
+ }
+ return mId < that.mId;
+ };
+
+ inline bool operator==(const ConfigKeyWithPackage& that) const {
+ return mPackage == that.mPackage && mId == that.mId;
+ };
+
+private:
+ string mPackage;
+ int64_t mId;
+};
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/statsd/src/config/ConfigManager.cpp b/statsd/src/config/ConfigManager.cpp
index 570cd45..16c4e75 100644
--- a/statsd/src/config/ConfigManager.cpp
+++ b/statsd/src/config/ConfigManager.cpp
@@ -37,6 +37,8 @@
using std::string;
using std::vector;
+using Status = ::ndk::ScopedAStatus;
+
#define STATS_SERVICE_DIR "/data/misc/stats-service"
using android::base::StringPrintf;
@@ -152,6 +154,83 @@
}
}
+void ConfigManager::SetRestrictedMetricsChangedReceiver(const string& configPackage,
+ const int64_t configId,
+ const int32_t callingUid,
+ const shared_ptr<IPendingIntentRef>& pir) {
+ lock_guard<mutex> lock(mMutex);
+ ConfigKeyWithPackage configKey(configPackage, configId);
+ mRestrictedMetricsChangedReceivers[configKey][callingUid] = pir;
+}
+
+void ConfigManager::RemoveRestrictedMetricsChangedReceiver(const string& configPackage,
+ const int64_t configId,
+ const int32_t callingUid) {
+ lock_guard<mutex> lock(mMutex);
+ ConfigKeyWithPackage configKey(configPackage, configId);
+ const auto& it = mRestrictedMetricsChangedReceivers.find(configKey);
+ if (it != mRestrictedMetricsChangedReceivers.end()) {
+ it->second.erase(callingUid);
+ if (it->second.empty()) {
+ mRestrictedMetricsChangedReceivers.erase(it);
+ }
+ }
+}
+
+void ConfigManager::RemoveRestrictedMetricsChangedReceiver(
+ const ConfigKeyWithPackage& key, const int32_t delegateUid,
+ const shared_ptr<IPendingIntentRef>& pir) {
+ lock_guard<mutex> lock(mMutex);
+ const auto& it = mRestrictedMetricsChangedReceivers.find(key);
+ if (it != mRestrictedMetricsChangedReceivers.end()) {
+ const auto& pirIt = it->second.find(delegateUid);
+ if (pirIt != it->second.end() && pirIt->second == pir) {
+ it->second.erase(delegateUid);
+ if (it->second.empty()) {
+ mRestrictedMetricsChangedReceivers.erase(it);
+ }
+ }
+ }
+}
+
+void ConfigManager::SendRestrictedMetricsBroadcast(const set<string>& configPackages,
+ const int64_t configId,
+ const set<int32_t>& delegateUids,
+ const vector<int64_t>& metricIds) {
+ map<ConfigKeyWithPackage, map<int32_t, shared_ptr<IPendingIntentRef>>> intentsToSend;
+ {
+ lock_guard<mutex> lock(mMutex);
+ // Invoke the pending intent for all matching configs, as long as the listening delegates
+ // match the allowed delegate uids specified by the config.
+ for (const string& configPackage : configPackages) {
+ ConfigKeyWithPackage key(configPackage, configId);
+ const auto& it = mRestrictedMetricsChangedReceivers.find(key);
+ if (it != mRestrictedMetricsChangedReceivers.end()) {
+ for (const auto& [delegateUid, pir] : it->second) {
+ if (delegateUids.find(delegateUid) != delegateUids.end()) {
+ intentsToSend[key][delegateUid] = pir;
+ }
+ }
+ }
+ }
+ }
+
+ // Invoke the pending intents without holding the lock.
+ for (const auto& [key, innerMap] : intentsToSend) {
+ for (const auto& [delegateUid, pir] : innerMap) {
+ Status status = pir->sendRestrictedMetricsChangedBroadcast(metricIds);
+ if (status.isOk()) {
+ VLOG("ConfigManager::SendRestrictedMetricsBroadcast succeeded");
+ }
+ if (status.getExceptionCode() == EX_TRANSACTION_FAILED &&
+ status.getStatus() == STATUS_DEAD_OBJECT) {
+ // Must also be called without the lock, since remove will acquire the lock.
+ RemoveRestrictedMetricsChangedReceiver(key, delegateUid, pir);
+ }
+ }
+ }
+}
+
void ConfigManager::RemoveConfig(const ConfigKey& key) {
vector<sp<ConfigListener>> broadcastList;
{
@@ -181,6 +260,7 @@
StorageManager::deleteSuffixedFiles(STATS_SERVICE_DIR, suffix.c_str());
}
+// TODO(b/xxx): consider removing all receivers associated with this uid.
void ConfigManager::RemoveConfigs(int uid) {
vector<ConfigKey> removed;
vector<sp<ConfigListener>> broadcastList;
diff --git a/statsd/src/config/ConfigManager.h b/statsd/src/config/ConfigManager.h
index 9a0f504..dd7d5bb 100644
--- a/statsd/src/config/ConfigManager.h
+++ b/statsd/src/config/ConfigManager.h
@@ -16,14 +16,15 @@
#pragma once
-#include "config/ConfigKey.h"
-#include "config/ConfigListener.h"
-
#include <aidl/android/os/IPendingIntentRef.h>
+#include <stdio.h>
+
#include <mutex>
#include <string>
-#include <stdio.h>
+#include "config/ConfigKey.h"
+#include "config/ConfigKeyWithPackage.h"
+#include "config/ConfigListener.h"
using aidl::android::os::IPendingIntentRef;
using std::shared_ptr;
@@ -113,6 +114,27 @@
const shared_ptr<IPendingIntentRef>& pir);
/**
+ * Sets the pending intent that is notified whenever the list of restricted metrics changes
+ */
+ void SetRestrictedMetricsChangedReceiver(const string& configPackage, const int64_t configId,
+ const int32_t callingUid,
+ const shared_ptr<IPendingIntentRef>& pir);
+
+ /**
+ * Erase any restricted metrics changed pending intents associated with this config key & uid.
+ */
+ void RemoveRestrictedMetricsChangedReceiver(const string& configPackage, const int64_t configId,
+ const int32_t callingUid);
+
+ /**
+ * Sends a restricted metrics broadcast for the valid config keys and delegate package
+ */
+ void SendRestrictedMetricsBroadcast(const std::set<string>& configPackages,
+ const int64_t configId,
+ const std::set<int32_t>& delegateUids,
+ const std::vector<int64_t>& metricIds);
+
+ /**
* A configuration was removed.
*
* Reports this to listeners.
@@ -166,9 +188,24 @@
std::map<int, shared_ptr<IPendingIntentRef>> mActiveConfigsChangedReceivers;
/**
+ * Each uid can subscribe up to one receiver for a particular config to receive the restricted
+ * metrics for that config. The receiver is specified as IPendingIntentRef.
+ */
+ std::map<ConfigKeyWithPackage, std::map<int32_t, shared_ptr<IPendingIntentRef>>>
+ mRestrictedMetricsChangedReceivers;
+
+ /**
* The ConfigListeners that will be told about changes.
*/
std::vector<sp<ConfigListener>> mListeners;
+
+ /**
+ * Erase the restricted metrics changed pending intents associated with this config key & uid if
+ * it is equal to the provided pending intent.
+ */
+ void RemoveRestrictedMetricsChangedReceiver(const ConfigKeyWithPackage& key,
+ const int32_t delegateUid,
+ const shared_ptr<IPendingIntentRef>& pir);
};
} // namespace statsd
diff --git a/statsd/src/external/StatsPullerManager.cpp b/statsd/src/external/StatsPullerManager.cpp
index ca5ee5b..8502d4f 100644
--- a/statsd/src/external/StatsPullerManager.cpp
+++ b/statsd/src/external/StatsPullerManager.cpp
@@ -32,7 +32,6 @@
#include "../statscompanion_util.h"
#include "StatsCallbackPuller.h"
#include "TrainInfoPuller.h"
-#include "flags/FlagProvider.h"
#include "statslog_statsd.h"
using std::shared_ptr;
@@ -50,8 +49,7 @@
// TrainInfo.
{{.atomTag = util::TRAIN_INFO, .uid = AID_STATSD}, new TrainInfoPuller()},
}),
- mNextPullTimeNs(NO_ALARM_UPDATE),
- mLimitPull(FlagProvider::getInstance().getBootFlagBool(LIMIT_PULL_FLAG, FLAG_FALSE)) {
+ mNextPullTimeNs(NO_ALARM_UPDATE) {
}
bool StatsPullerManager::Pull(int tagId, const ConfigKey& configKey, const int64_t eventTimeNs,
@@ -229,33 +227,22 @@
vector<ReceiverInfo*> receivers;
if (pair.second.size() != 0) {
for (ReceiverInfo& receiverInfo : pair.second) {
- // If mLimitPull is true, check if metric needs to pull data (pullNecessary).
// If pullNecessary and enough time has passed for the next bucket, then add
// receiver to the list that will pull on this alarm.
// If pullNecessary is false, check if next pull time needs to be updated.
- if (mLimitPull) {
- sp<PullDataReceiver> receiverPtr = receiverInfo.receiver.promote();
- const bool pullNecessary =
- receiverPtr != nullptr && receiverPtr->isPullNeeded();
- if (receiverInfo.nextPullTimeNs <= elapsedTimeNs && pullNecessary) {
- receivers.push_back(&receiverInfo);
- } else {
- if (receiverInfo.nextPullTimeNs <= elapsedTimeNs) {
- receiverPtr->onDataPulled({}, PullResult::PULL_NOT_NEEDED,
- elapsedTimeNs);
- int numBucketsAhead = (elapsedTimeNs - receiverInfo.nextPullTimeNs) /
- receiverInfo.intervalNs;
- receiverInfo.nextPullTimeNs +=
- (numBucketsAhead + 1) * receiverInfo.intervalNs;
- }
- minNextPullTimeNs = min(receiverInfo.nextPullTimeNs, minNextPullTimeNs);
- }
+ sp<PullDataReceiver> receiverPtr = receiverInfo.receiver.promote();
+ const bool pullNecessary = receiverPtr != nullptr && receiverPtr->isPullNeeded();
+ if (receiverInfo.nextPullTimeNs <= elapsedTimeNs && pullNecessary) {
+ receivers.push_back(&receiverInfo);
} else {
if (receiverInfo.nextPullTimeNs <= elapsedTimeNs) {
- receivers.push_back(&receiverInfo);
- } else {
- minNextPullTimeNs = min(receiverInfo.nextPullTimeNs, minNextPullTimeNs);
+ receiverPtr->onDataPulled({}, PullResult::PULL_NOT_NEEDED, elapsedTimeNs);
+ int numBucketsAhead = (elapsedTimeNs - receiverInfo.nextPullTimeNs) /
+ receiverInfo.intervalNs;
+ receiverInfo.nextPullTimeNs +=
+ (numBucketsAhead + 1) * receiverInfo.intervalNs;
}
+ minNextPullTimeNs = min(receiverInfo.nextPullTimeNs, minNextPullTimeNs);
}
}
if (receivers.size() > 0) {
diff --git a/statsd/src/external/StatsPullerManager.h b/statsd/src/external/StatsPullerManager.h
index f9db4c4..80a1331 100644
--- a/statsd/src/external/StatsPullerManager.h
+++ b/statsd/src/external/StatsPullerManager.h
@@ -164,8 +164,6 @@
int64_t mNextPullTimeNs;
- const bool mLimitPull;
-
FRIEND_TEST(GaugeMetricE2ePulledTest, TestFirstNSamplesPulledNoTrigger);
FRIEND_TEST(GaugeMetricE2ePulledTest, TestFirstNSamplesPulledNoTriggerWithActivation);
FRIEND_TEST(GaugeMetricE2ePulledTest, TestRandomSamplePulledEvents);
diff --git a/statsd/src/flags/FlagProvider.h b/statsd/src/flags/FlagProvider.h
index 7e11feb..8f24ac9 100644
--- a/statsd/src/flags/FlagProvider.h
+++ b/statsd/src/flags/FlagProvider.h
@@ -37,7 +37,8 @@
const std::string STATSD_NATIVE_NAMESPACE = "statsd_native";
const std::string STATSD_NATIVE_BOOT_NAMESPACE = "statsd_native_boot";
-const std::string LIMIT_PULL_FLAG = "limit_pull";
+const std::string OPTIMIZATION_SOCKET_PARSING_FLAG = "optimization_socket_parsing";
+const std::string STATSD_INIT_COMPLETED_NO_DELAY_FLAG = "statsd_init_completed_no_delay";
const std::string FLAG_TRUE = "true";
const std::string FLAG_FALSE = "false";
@@ -113,9 +114,15 @@
friend class KllMetricE2eAbTest;
friend class MetricsManagerTest;
friend class StatsLogProcessorTest;
+ friend class StatsLogProcessorTestRestricted;
+ friend class RestrictedEventMetricProducerTest;
+ friend class RestrictedConfigE2ETest;
+ friend class RestrictedEventMetricE2eTest;
+ friend class LogEvent_FieldRestrictionTest;
FRIEND_TEST(ConfigUpdateE2eTest, TestEventMetric);
FRIEND_TEST(ConfigUpdateE2eTest, TestGaugeMetric);
+ FRIEND_TEST(ConfigUpdateE2eTest, TestConfigUpdateRestrictedDelegateCleared);
FRIEND_TEST(EventMetricE2eTest, TestEventMetricDataAggregated);
FRIEND_TEST(EventMetricProducerTest, TestOneAtomTagAggregatedEvents);
FRIEND_TEST(EventMetricProducerTest, TestTwoAtomTagAggregatedEvents);
@@ -127,6 +134,15 @@
FRIEND_TEST(FlagProviderTest_SPlus, TestGetFlagBoolServerFlagEmptyDefaultTrue);
FRIEND_TEST(FlagProviderTest_SPlus_RealValues, TestGetBootFlagBoolServerFlagTrue);
FRIEND_TEST(FlagProviderTest_SPlus_RealValues, TestGetBootFlagBoolServerFlagFalse);
+ FRIEND_TEST(MetricsManagerTest_SPlus, TestRestrictedMetricsConfig);
+ FRIEND_TEST(RestrictedEventMetricE2eTest, TestFlagDisabled);
+ FRIEND_TEST(LogEventTest, TestRestrictionCategoryAnnotation);
+ FRIEND_TEST(LogEventTest, TestInvalidRestrictionCategoryAnnotation);
+ FRIEND_TEST(LogEventTest, TestRestrictionCategoryAnnotationFlagDisabled);
+ FRIEND_TEST(LogEvent_FieldRestrictionTest, TestFieldRestrictionAnnotation);
+ FRIEND_TEST(LogEvent_FieldRestrictionTest, TestInvalidAnnotationIntType);
+ FRIEND_TEST(LogEvent_FieldRestrictionTest, TestInvalidAnnotationAtomLevel);
+ FRIEND_TEST(LogEvent_FieldRestrictionTest, TestRestrictionCategoryAnnotationFlagDisabled);
};
} // namespace statsd
diff --git a/statsd/src/guardrail/StatsdStats.cpp b/statsd/src/guardrail/StatsdStats.cpp
index 4ed0089..7dd6d9a 100644
--- a/statsd/src/guardrail/StatsdStats.cpp
+++ b/statsd/src/guardrail/StatsdStats.cpp
@@ -19,9 +19,11 @@
#include "StatsdStats.h"
#include <android/util/ProtoOutputStream.h>
+
#include "../stats_log_util.h"
#include "statslog_statsd.h"
#include "storage/StorageManager.h"
+#include "utils/ShardOffsetProvider.h"
namespace android {
namespace os {
@@ -35,6 +37,7 @@
using android::util::FIELD_TYPE_INT64;
using android::util::FIELD_TYPE_MESSAGE;
using android::util::FIELD_TYPE_STRING;
+using android::util::FIELD_TYPE_UINT32;
using android::util::ProtoOutputStream;
using std::lock_guard;
using std::shared_ptr;
@@ -53,11 +56,24 @@
const int FIELD_ID_LOGGER_ERROR_STATS = 16;
const int FIELD_ID_OVERFLOW = 18;
const int FIELD_ID_ACTIVATION_BROADCAST_GUARDRAIL = 19;
+const int FIELD_ID_RESTRICTED_METRIC_QUERY_STATS = 20;
+const int FIELD_ID_SHARD_OFFSET = 21;
+
+const int FIELD_ID_RESTRICTED_METRIC_QUERY_STATS_CALLING_UID = 1;
+const int FIELD_ID_RESTRICTED_METRIC_QUERY_STATS_CONFIG_ID = 2;
+const int FIELD_ID_RESTRICTED_METRIC_QUERY_STATS_CONFIG_UID = 3;
+const int FIELD_ID_RESTRICTED_METRIC_QUERY_STATS_CONFIG_PACKAGE = 4;
+const int FIELD_ID_RESTRICTED_METRIC_QUERY_STATS_INVALID_QUERY_REASON = 5;
+const int FIELD_ID_RESTRICTED_METRIC_QUERY_STATS_QUERY_WALL_TIME_NS = 6;
+const int FIELD_ID_RESTRICTED_METRIC_QUERY_STATS_HAS_ERROR = 7;
+const int FIELD_ID_RESTRICTED_METRIC_QUERY_STATS_ERROR = 8;
+const int FIELD_ID_RESTRICTED_METRIC_QUERY_STATS_LATENCY_NS = 9;
const int FIELD_ID_ATOM_STATS_TAG = 1;
const int FIELD_ID_ATOM_STATS_COUNT = 2;
const int FIELD_ID_ATOM_STATS_ERROR_COUNT = 3;
const int FIELD_ID_ATOM_STATS_DROPS_COUNT = 4;
+const int FIELD_ID_ATOM_STATS_SKIP_COUNT = 5;
const int FIELD_ID_ANOMALY_ALARMS_REGISTERED = 1;
const int FIELD_ID_PERIODIC_ALARMS_REGISTERED = 1;
@@ -99,6 +115,12 @@
const int FIELD_ID_CONFIG_STATS_DEACTIVATION = 23;
const int FIELD_ID_CONFIG_STATS_ANNOTATION_INT64 = 1;
const int FIELD_ID_CONFIG_STATS_ANNOTATION_INT32 = 2;
+const int FIELD_ID_CONFIG_STATS_RESTRICTED_METRIC_STATS = 25;
+const int FIELD_ID_CONFIG_STATS_DEVICE_INFO_TABLE_CREATION_FAILED = 26;
+const int FIELD_ID_CONFIG_STATS_RESTRICTED_DB_CORRUPTED_COUNT = 27;
+const int FIELD_ID_CONFIG_STATS_RESTRICTED_CONFIG_FLUSH_LATENCY = 28;
+const int FIELD_ID_CONFIG_STATS_RESTRICTED_CONFIG_DB_SIZE_TIME_SEC = 29;
+const int FIELD_ID_CONFIG_STATS_RESTRICTED_CONFIG_DB_SIZE_BYTES = 30;
const int FIELD_ID_INVALID_CONFIG_REASON_ENUM = 1;
const int FIELD_ID_INVALID_CONFIG_REASON_METRIC_ID = 2;
@@ -126,6 +148,14 @@
const int FIELD_ID_ACTIVATION_BROADCAST_GUARDRAIL_UID = 1;
const int FIELD_ID_ACTIVATION_BROADCAST_GUARDRAIL_TIME = 2;
+// for RestrictedMetricStats proto
+const int FIELD_ID_RESTRICTED_STATS_METRIC_ID = 1;
+const int FIELD_ID_RESTRICTED_STATS_INSERT_ERROR = 2;
+const int FIELD_ID_RESTRICTED_STATS_TABLE_CREATION_ERROR = 3;
+const int FIELD_ID_RESTRICTED_STATS_TABLE_DELETION_ERROR = 4;
+const int FIELD_ID_RESTRICTED_STATS_FLUSH_LATENCY = 5;
+const int FIELD_ID_RESTRICTED_STATS_CATEGORY_CHANGED_COUNT = 6;
+
const std::map<int, std::pair<size_t, size_t>> StatsdStats::kAtomDimensionKeySizeLimitMap = {
{util::BINDER_CALLS, {6000, 10000}},
{util::LOOPER_STATS, {1500, 2500}},
@@ -271,7 +301,8 @@
noteDataDropped(key, totalBytes, getWallClockSec());
}
-void StatsdStats::noteEventQueueOverflow(int64_t oldestEventTimestampNs, int32_t atomId) {
+void StatsdStats::noteEventQueueOverflow(int64_t oldestEventTimestampNs, int32_t atomId,
+ bool isSkipped) {
lock_guard<std::mutex> lock(mLock);
mOverflowCount++;
@@ -286,7 +317,7 @@
mMinQueueHistoryNs = history;
}
- noteAtomLoggedLocked(atomId);
+ noteAtomLoggedLocked(atomId, isSkipped);
noteAtomDroppedLocked(atomId);
}
@@ -331,6 +362,26 @@
it->second->dump_report_stats.push_back(std::make_pair(timeSec, num_bytes));
}
+void StatsdStats::noteDeviceInfoTableCreationFailed(const ConfigKey& key) {
+ lock_guard<std::mutex> lock(mLock);
+ auto it = mConfigStats.find(key);
+ if (it == mConfigStats.end()) {
+ ALOGE("Config key %s not found!", key.ToString().c_str());
+ return;
+ }
+ it->second->device_info_table_creation_failed = true;
+}
+
+void StatsdStats::noteDbCorrupted(const ConfigKey& key) {
+ lock_guard<std::mutex> lock(mLock);
+ auto it = mConfigStats.find(key);
+ if (it == mConfigStats.end()) {
+ ALOGE("Config key %s not found!", key.ToString().c_str());
+ return;
+ }
+ it->second->db_corrupted_count++;
+}
+
void StatsdStats::noteUidMapDropped(int deltas) {
lock_guard<std::mutex> lock(mLock);
mUidMapStats.dropped_changes += mUidMapStats.dropped_changes + deltas;
@@ -480,22 +531,24 @@
mPulledAtomStats[pullAtomId].pullExceedMaxDelay++;
}
-void StatsdStats::noteAtomLogged(int atomId, int32_t /*timeSec*/) {
+void StatsdStats::noteAtomLogged(int atomId, int32_t /*timeSec*/, bool isSkipped) {
lock_guard<std::mutex> lock(mLock);
- noteAtomLoggedLocked(atomId);
+ noteAtomLoggedLocked(atomId, isSkipped);
}
-void StatsdStats::noteAtomLoggedLocked(int atomId) {
+void StatsdStats::noteAtomLoggedLocked(int atomId, bool isSkipped) {
if (atomId >= 0 && atomId <= kMaxPushedAtomId) {
- mPushedAtomStats[atomId]++;
+ mPushedAtomStats[atomId].logCount++;
+ mPushedAtomStats[atomId].skipCount += isSkipped;
} else {
if (atomId < 0) {
android_errorWriteLog(0x534e4554, "187957589");
}
if (mNonPlatformPushedAtomStats.size() < kMaxNonPlatformPushedAtoms ||
mNonPlatformPushedAtomStats.find(atomId) != mNonPlatformPushedAtomStats.end()) {
- mNonPlatformPushedAtomStats[atomId]++;
+ mNonPlatformPushedAtomStats[atomId].logCount++;
+ mNonPlatformPushedAtomStats[atomId].skipCount += isSkipped;
}
}
}
@@ -611,6 +664,137 @@
}
}
+void StatsdStats::noteQueryRestrictedMetricSucceed(const int64_t configId,
+ const string& configPackage,
+ const std::optional<int32_t> configUid,
+ const int32_t callingUid,
+ const int64_t latencyNs) {
+ lock_guard<std::mutex> lock(mLock);
+
+ if (mRestrictedMetricQueryStats.size() == kMaxRestrictedMetricQueryCount) {
+ mRestrictedMetricQueryStats.pop_front();
+ }
+ mRestrictedMetricQueryStats.emplace_back(RestrictedMetricQueryStats(
+ callingUid, configId, configPackage, configUid, getWallClockNs(),
+ /*invalidQueryReason=*/std::nullopt, /*error=*/"", latencyNs));
+}
+
+void StatsdStats::noteQueryRestrictedMetricFailed(const int64_t configId,
+ const string& configPackage,
+ const std::optional<int32_t> configUid,
+ const int32_t callingUid,
+ const InvalidQueryReason reason) {
+ lock_guard<std::mutex> lock(mLock);
+ noteQueryRestrictedMetricFailedLocked(configId, configPackage, configUid, callingUid, reason,
+ /*error=*/"");
+}
+
+void StatsdStats::noteQueryRestrictedMetricFailed(
+ const int64_t configId, const string& configPackage, const std::optional<int32_t> configUid,
+ const int32_t callingUid, const InvalidQueryReason reason, const string& error) {
+ lock_guard<std::mutex> lock(mLock);
+ noteQueryRestrictedMetricFailedLocked(configId, configPackage, configUid, callingUid, reason,
+ error);
+}
+
+void StatsdStats::noteQueryRestrictedMetricFailedLocked(
+ const int64_t configId, const string& configPackage, const std::optional<int32_t> configUid,
+ const int32_t callingUid, const InvalidQueryReason reason, const string& error) {
+ if (mRestrictedMetricQueryStats.size() == kMaxRestrictedMetricQueryCount) {
+ mRestrictedMetricQueryStats.pop_front();
+ }
+ mRestrictedMetricQueryStats.emplace_back(RestrictedMetricQueryStats(
+ callingUid, configId, configPackage, configUid, getWallClockNs(), reason, error,
+ /*queryLatencyNs=*/std::nullopt));
+}
+
+void StatsdStats::noteRestrictedMetricInsertError(const ConfigKey& configKey,
+ const int64_t metricId) {
+ lock_guard<std::mutex> lock(mLock);
+ auto it = mConfigStats.find(configKey);
+ if (it != mConfigStats.end()) {
+ it->second->restricted_metric_stats[metricId].insertError++;
+ }
+}
+
+void StatsdStats::noteRestrictedMetricTableCreationError(const ConfigKey& configKey,
+ const int64_t metricId) {
+ lock_guard<std::mutex> lock(mLock);
+ auto it = mConfigStats.find(configKey);
+ if (it != mConfigStats.end()) {
+ it->second->restricted_metric_stats[metricId].tableCreationError++;
+ }
+}
+
+void StatsdStats::noteRestrictedMetricTableDeletionError(const ConfigKey& configKey,
+ const int64_t metricId) {
+ lock_guard<std::mutex> lock(mLock);
+ auto it = mConfigStats.find(configKey);
+ if (it != mConfigStats.end()) {
+ it->second->restricted_metric_stats[metricId].tableDeletionError++;
+ }
+}
+
+void StatsdStats::noteRestrictedMetricFlushLatency(const ConfigKey& configKey,
+ const int64_t metricId,
+ const int64_t flushLatencyNs) {
+ lock_guard<std::mutex> lock(mLock);
+ auto it = mConfigStats.find(configKey);
+ if (it == mConfigStats.end()) {
+ ALOGE("Config key %s not found!", configKey.ToString().c_str());
+ return;
+ }
+ auto& restrictedMetricStats = it->second->restricted_metric_stats[metricId];
+ if (restrictedMetricStats.flushLatencyNs.size() == kMaxRestrictedMetricFlushLatencyCount) {
+ restrictedMetricStats.flushLatencyNs.pop_front();
+ }
+ restrictedMetricStats.flushLatencyNs.push_back(flushLatencyNs);
+}
+
+void StatsdStats::noteRestrictedConfigFlushLatency(const ConfigKey& configKey,
+ const int64_t totalFlushLatencyNs) {
+ lock_guard<std::mutex> lock(mLock);
+ auto it = mConfigStats.find(configKey);
+ if (it == mConfigStats.end()) {
+ ALOGE("Config key %s not found!", configKey.ToString().c_str());
+ return;
+ }
+ std::list<int64_t>& totalFlushLatencies = it->second->total_flush_latency_ns;
+ if (totalFlushLatencies.size() == kMaxRestrictedConfigFlushLatencyCount) {
+ totalFlushLatencies.pop_front();
+ }
+ totalFlushLatencies.push_back(totalFlushLatencyNs);
+}
+
+void StatsdStats::noteRestrictedConfigDbSize(const ConfigKey& configKey,
+ const int64_t elapsedTimeNs, const int64_t dbSize) {
+ lock_guard<std::mutex> lock(mLock);
+ auto it = mConfigStats.find(configKey);
+ if (it == mConfigStats.end()) {
+ ALOGE("Config key %s not found!", configKey.ToString().c_str());
+ return;
+ }
+ std::list<int64_t>& totalDbSizeTimestamps = it->second->total_db_size_timestamps;
+ std::list<int64_t>& totaDbSizes = it->second->total_db_sizes;
+ if (totalDbSizeTimestamps.size() == kMaxRestrictedConfigDbSizeCount) {
+ totalDbSizeTimestamps.pop_front();
+ totaDbSizes.pop_front();
+ }
+ totalDbSizeTimestamps.push_back(elapsedTimeNs);
+ totaDbSizes.push_back(dbSize);
+}
+
+void StatsdStats::noteRestrictedMetricCategoryChanged(const ConfigKey& configKey,
+ const int64_t metricId) {
+ lock_guard<std::mutex> lock(mLock);
+ auto it = mConfigStats.find(configKey);
+ if (it == mConfigStats.end()) {
+ ALOGE("Config key %s not found!", configKey.ToString().c_str());
+ return;
+ }
+ it->second->restricted_metric_stats[metricId].categoryChangedCount++;
+}
+
StatsdStats::AtomMetricStats& StatsdStats::getAtomMetricStats(int64_t metricId) {
auto atomMetricStatsIter = mAtomMetricStats.find(metricId);
if (atomMetricStatsIter != mAtomMetricStats.end()) {
@@ -629,7 +813,7 @@
// Reset the historical data, but keep the active ConfigStats
mStartTimeSec = getWallClockSec();
mIceBox.clear();
- std::fill(mPushedAtomStats.begin(), mPushedAtomStats.end(), 0);
+ std::fill(mPushedAtomStats.begin(), mPushedAtomStats.end(), PushedAtomStats());
mNonPlatformPushedAtomStats.clear();
mAnomalyAlarmRegisteredStats = 0;
mPeriodicAlarmRegisteredStats = 0;
@@ -651,6 +835,11 @@
config.second->metric_stats.clear();
config.second->metric_dimension_in_condition_stats.clear();
config.second->alert_stats.clear();
+ config.second->restricted_metric_stats.clear();
+ config.second->db_corrupted_count = 0;
+ config.second->total_flush_latency_ns.clear();
+ config.second->total_db_size_timestamps.clear();
+ config.second->total_db_sizes.clear();
}
for (auto& pullStats : mPulledAtomStats) {
pullStats.second.totalPull = 0;
@@ -678,6 +867,7 @@
mActivationBroadcastGuardrailStats.clear();
mPushedAtomErrorStats.clear();
mPushedAtomDropsStats.clear();
+ mRestrictedMetricQueryStats.clear();
}
string buildTimeString(int64_t timeSec) {
@@ -717,11 +907,13 @@
for (const auto& configStats : mIceBox) {
dprintf(out,
"Config {%d_%lld}: creation=%d, deletion=%d, reset=%d, #metric=%d, #condition=%d, "
- "#matcher=%d, #alert=%d, valid=%d\n",
+ "#matcher=%d, #alert=%d, valid=%d, device_info_table_creation_failed=%d, "
+ "db_corrupted_count=%d\n",
configStats->uid, (long long)configStats->id, configStats->creation_time_sec,
configStats->deletion_time_sec, configStats->reset_time_sec,
configStats->metric_count, configStats->condition_count, configStats->matcher_count,
- configStats->alert_count, configStats->is_valid);
+ configStats->alert_count, configStats->is_valid,
+ configStats->device_info_table_creation_failed, configStats->db_corrupted_count);
if (!configStats->is_valid) {
dprintf(out, "\tinvalid config reason: %s\n",
@@ -747,17 +939,27 @@
dprintf(out, "\tdata drop time: %d with size %lld", *dropTimePtr,
(long long)*dropBytesPtr);
}
+
+ for (const int64_t flushLatency : configStats->total_flush_latency_ns) {
+ dprintf(out, "\tflush latency time ns: %lld\n", (long long)flushLatency);
+ }
+
+ for (const int64_t dbSize : configStats->total_db_sizes) {
+ dprintf(out, "\tdb size: %lld\n", (long long)dbSize);
+ }
}
dprintf(out, "%lu Active Configs\n", (unsigned long)mConfigStats.size());
for (auto& pair : mConfigStats) {
auto& configStats = pair.second;
dprintf(out,
"Config {%d-%lld}: creation=%d, deletion=%d, #metric=%d, #condition=%d, "
- "#matcher=%d, #alert=%d, valid=%d\n",
+ "#matcher=%d, #alert=%d, valid=%d, device_info_table_creation_failed=%d, "
+ "db_corrupted_count=%d\n",
configStats->uid, (long long)configStats->id, configStats->creation_time_sec,
configStats->deletion_time_sec, configStats->metric_count,
configStats->condition_count, configStats->matcher_count, configStats->alert_count,
- configStats->is_valid);
+ configStats->is_valid, configStats->device_info_table_creation_failed,
+ configStats->db_corrupted_count);
if (!configStats->is_valid) {
dprintf(out, "\tinvalid config reason: %s\n",
@@ -814,22 +1016,43 @@
for (const auto& stats : pair.second->alert_stats) {
dprintf(out, "alert %lld declared %d times\n", (long long)stats.first, stats.second);
}
+
+ for (const auto& stats : configStats->restricted_metric_stats) {
+ dprintf(out, "Restricted MetricId %lld: ", (long long)stats.first);
+ dprintf(out, "Insert error %lld, ", (long long)stats.second.insertError);
+ dprintf(out, "Table creation error %lld, ", (long long)stats.second.tableCreationError);
+ dprintf(out, "Table deletion error %lld ", (long long)stats.second.tableDeletionError);
+ dprintf(out, "Category changed count %lld\n ",
+ (long long)stats.second.categoryChangedCount);
+ string flushLatencies = "Flush Latencies: ";
+ for (const int64_t latencyNs : stats.second.flushLatencyNs) {
+ flushLatencies.append(to_string(latencyNs).append(","));
+ }
+ flushLatencies.pop_back();
+ flushLatencies.push_back('\n');
+ dprintf(out, "%s", flushLatencies.c_str());
+ }
+
+ for (const int64_t flushLatency : configStats->total_flush_latency_ns) {
+ dprintf(out, "flush latency time ns: %lld\n", (long long)flushLatency);
+ }
}
dprintf(out, "********Disk Usage stats***********\n");
StorageManager::printStats(out);
dprintf(out, "********Pushed Atom stats***********\n");
const size_t atomCounts = mPushedAtomStats.size();
for (size_t i = 2; i < atomCounts; i++) {
- if (mPushedAtomStats[i] > 0) {
- dprintf(out, "Atom %zu->(total count)%d, (error count)%d, (drop count)%d\n", i,
- mPushedAtomStats[i], getPushedAtomErrorsLocked((int)i),
- getPushedAtomDropsLocked((int)i));
+ if (mPushedAtomStats[i].logCount > 0) {
+ dprintf(out,
+ "Atom %zu->(total count)%d, (error count)%d, (drop count)%d, (skip count)%d\n",
+ i, mPushedAtomStats[i].logCount, getPushedAtomErrorsLocked((int)i),
+ getPushedAtomDropsLocked((int)i), mPushedAtomStats[i].skipCount);
}
}
for (const auto& pair : mNonPlatformPushedAtomStats) {
- dprintf(out, "Atom %d->(total count)%d, (error count)%d, (drop count)%d\n", pair.first,
- pair.second, getPushedAtomErrorsLocked(pair.first),
- getPushedAtomDropsLocked((int)pair.first));
+ dprintf(out, "Atom %d->(total count)%d, (error count)%d, (drop count)%d, (skip count)%d\n",
+ pair.first, pair.second.logCount, getPushedAtomErrorsLocked(pair.first),
+ getPushedAtomDropsLocked((int)pair.first), pair.second.skipCount);
}
dprintf(out, "********Pulled Atom stats***********\n");
@@ -856,7 +1079,7 @@
string uptimeMillis = "(pull timeout system uptime millis) ";
string pullTimeoutMillis = "(pull timeout elapsed time millis) ";
for (const auto& stats : pair.second.pullTimeoutMetadata) {
- uptimeMillis.append(to_string(stats.pullTimeoutUptimeMillis)).append(",");;
+ uptimeMillis.append(to_string(stats.pullTimeoutUptimeMillis)).append(",");
pullTimeoutMillis.append(to_string(stats.pullTimeoutElapsedMillis)).append(",");
}
uptimeMillis.pop_back();
@@ -908,6 +1131,30 @@
}
dprintf(out, "\n");
}
+
+ if (mRestrictedMetricQueryStats.size() > 0) {
+ dprintf(out, "********Restricted Metric Query stats***********\n");
+ for (const auto& stat : mRestrictedMetricQueryStats) {
+ if (stat.mHasError) {
+ dprintf(out,
+ "Query with error type: %d - %lld (query time ns), "
+ "%d (calling uid), %lld (config id), %s (config package), %s (error)\n",
+ stat.mInvalidQueryReason.value(), (long long)stat.mQueryWallTimeNs,
+ stat.mCallingUid, (long long)stat.mConfigId, stat.mConfigPackage.c_str(),
+ stat.mError.c_str());
+ } else {
+ dprintf(out,
+ "Query succeed - %lld (query time ns), %d (calling uid), "
+ "%lld (config id), %s (config package), %d (config uid), "
+ "%lld (queryLatencyNs)\n",
+ (long long)stat.mQueryWallTimeNs, stat.mCallingUid,
+ (long long)stat.mConfigId, stat.mConfigPackage.c_str(),
+ stat.mConfigUid.value(), (long long)stat.mQueryLatencyNs.value());
+ }
+ }
+ }
+ dprintf(out, "********Shard Offset Provider stats***********\n");
+ dprintf(out, "Shard Offset: %u\n", ShardOffsetProvider::getInstance().getShardOffset());
}
void addConfigStatsToProto(const ConfigStats& configStats, ProtoOutputStream* proto) {
@@ -1054,6 +1301,47 @@
proto->end(tmpToken);
}
+ for (const auto& pair : configStats.restricted_metric_stats) {
+ uint64_t token =
+ proto->start(FIELD_TYPE_MESSAGE | FIELD_ID_CONFIG_STATS_RESTRICTED_METRIC_STATS |
+ FIELD_COUNT_REPEATED);
+
+ proto->write(FIELD_TYPE_INT64 | FIELD_ID_RESTRICTED_STATS_METRIC_ID, (long long)pair.first);
+ writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_RESTRICTED_STATS_INSERT_ERROR,
+ (long long)pair.second.insertError, proto);
+ writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_RESTRICTED_STATS_TABLE_CREATION_ERROR,
+ (long long)pair.second.tableCreationError, proto);
+ writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_RESTRICTED_STATS_TABLE_DELETION_ERROR,
+ (long long)pair.second.tableDeletionError, proto);
+ for (const int64_t flushLatencyNs : pair.second.flushLatencyNs) {
+ proto->write(FIELD_TYPE_INT64 | FIELD_ID_RESTRICTED_STATS_FLUSH_LATENCY |
+ FIELD_COUNT_REPEATED,
+ flushLatencyNs);
+ }
+ writeNonZeroStatToStream(
+ FIELD_TYPE_INT64 | FIELD_ID_RESTRICTED_STATS_CATEGORY_CHANGED_COUNT,
+ (long long)pair.second.categoryChangedCount, proto);
+ proto->end(token);
+ }
+ proto->write(FIELD_TYPE_BOOL | FIELD_ID_CONFIG_STATS_DEVICE_INFO_TABLE_CREATION_FAILED,
+ configStats.device_info_table_creation_failed);
+ proto->write(FIELD_TYPE_INT32 | FIELD_ID_CONFIG_STATS_RESTRICTED_DB_CORRUPTED_COUNT,
+ configStats.db_corrupted_count);
+ for (int64_t latency : configStats.total_flush_latency_ns) {
+ proto->write(FIELD_TYPE_INT64 | FIELD_ID_CONFIG_STATS_RESTRICTED_CONFIG_FLUSH_LATENCY |
+ FIELD_COUNT_REPEATED,
+ latency);
+ }
+ for (int64_t dbSizeTimestamp : configStats.total_db_size_timestamps) {
+ proto->write(FIELD_TYPE_INT64 | FIELD_ID_CONFIG_STATS_RESTRICTED_CONFIG_DB_SIZE_TIME_SEC |
+ FIELD_COUNT_REPEATED,
+ dbSizeTimestamp);
+ }
+ for (int64_t dbSize : configStats.total_db_sizes) {
+ proto->write(FIELD_TYPE_INT64 | FIELD_ID_CONFIG_STATS_RESTRICTED_CONFIG_DB_SIZE_BYTES |
+ FIELD_COUNT_REPEATED,
+ dbSize);
+ }
proto->end(token);
}
@@ -1074,17 +1362,19 @@
const size_t atomCounts = mPushedAtomStats.size();
for (size_t i = 2; i < atomCounts; i++) {
- if (mPushedAtomStats[i] > 0) {
+ if (mPushedAtomStats[i].logCount > 0) {
uint64_t token =
proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_ATOM_STATS | FIELD_COUNT_REPEATED);
proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_TAG, (int32_t)i);
- proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_COUNT, mPushedAtomStats[i]);
+ proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_COUNT, mPushedAtomStats[i].logCount);
const int errors = getPushedAtomErrorsLocked(i);
writeNonZeroStatToStream(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_ERROR_COUNT, errors,
&proto);
const int drops = getPushedAtomDropsLocked(i);
writeNonZeroStatToStream(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_DROPS_COUNT, drops,
&proto);
+ writeNonZeroStatToStream(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_SKIP_COUNT,
+ mPushedAtomStats[i].skipCount, &proto);
proto.end(token);
}
}
@@ -1093,12 +1383,14 @@
uint64_t token =
proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_ATOM_STATS | FIELD_COUNT_REPEATED);
proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_TAG, pair.first);
- proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_COUNT, pair.second);
+ proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_COUNT, pair.second.logCount);
const int errors = getPushedAtomErrorsLocked(pair.first);
writeNonZeroStatToStream(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_ERROR_COUNT, errors,
&proto);
const int drops = getPushedAtomDropsLocked(pair.first);
writeNonZeroStatToStream(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_DROPS_COUNT, drops, &proto);
+ writeNonZeroStatToStream(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_SKIP_COUNT,
+ pair.second.skipCount, &proto);
proto.end(token);
}
@@ -1172,6 +1464,42 @@
proto.end(token);
}
+ for (const auto& stat : mRestrictedMetricQueryStats) {
+ uint64_t token = proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_RESTRICTED_METRIC_QUERY_STATS |
+ FIELD_COUNT_REPEATED);
+ proto.write(FIELD_TYPE_INT32 | FIELD_ID_RESTRICTED_METRIC_QUERY_STATS_CALLING_UID,
+ stat.mCallingUid);
+ proto.write(FIELD_TYPE_INT64 | FIELD_ID_RESTRICTED_METRIC_QUERY_STATS_CONFIG_ID,
+ stat.mConfigId);
+ proto.write(FIELD_TYPE_STRING | FIELD_ID_RESTRICTED_METRIC_QUERY_STATS_CONFIG_PACKAGE,
+ stat.mConfigPackage);
+ if (stat.mConfigUid.has_value()) {
+ proto.write(FIELD_TYPE_INT32 | FIELD_ID_RESTRICTED_METRIC_QUERY_STATS_CONFIG_UID,
+ stat.mConfigUid.value());
+ }
+ if (stat.mInvalidQueryReason.has_value()) {
+ proto.write(
+ FIELD_TYPE_ENUM | FIELD_ID_RESTRICTED_METRIC_QUERY_STATS_INVALID_QUERY_REASON,
+ stat.mInvalidQueryReason.value());
+ }
+ proto.write(FIELD_TYPE_INT64 | FIELD_ID_RESTRICTED_METRIC_QUERY_STATS_QUERY_WALL_TIME_NS,
+ stat.mQueryWallTimeNs);
+ proto.write(FIELD_TYPE_BOOL | FIELD_ID_RESTRICTED_METRIC_QUERY_STATS_HAS_ERROR,
+ stat.mHasError);
+ if (stat.mHasError && !stat.mError.empty()) {
+ proto.write(FIELD_TYPE_STRING | FIELD_ID_RESTRICTED_METRIC_QUERY_STATS_ERROR,
+ stat.mError);
+ }
+ if (stat.mQueryLatencyNs.has_value()) {
+ proto.write(FIELD_TYPE_INT64 | FIELD_ID_RESTRICTED_METRIC_QUERY_STATS_LATENCY_NS,
+ stat.mQueryLatencyNs.value());
+ }
+ proto.end(token);
+ }
+
+ proto.write(FIELD_TYPE_UINT32 | FIELD_ID_SHARD_OFFSET,
+ static_cast<long>(ShardOffsetProvider::getInstance().getShardOffset()));
+
output->clear();
size_t bufferSize = proto.size();
output->resize(bufferSize);
diff --git a/statsd/src/guardrail/StatsdStats.h b/statsd/src/guardrail/StatsdStats.h
index a221d55..4bf6ffd 100644
--- a/statsd/src/guardrail/StatsdStats.h
+++ b/statsd/src/guardrail/StatsdStats.h
@@ -52,6 +52,27 @@
}
};
+// Keep this in sync with InvalidQueryReason enum in stats_log.proto
+enum InvalidQueryReason {
+ UNKNOWN_REASON = 0,
+ FLAG_DISABLED = 1,
+ UNSUPPORTED_SQLITE_VERSION = 2,
+ AMBIGUOUS_CONFIG_KEY = 3,
+ CONFIG_KEY_NOT_FOUND = 4,
+ CONFIG_KEY_WITH_UNMATCHED_DELEGATE = 5,
+ QUERY_FAILURE = 6,
+ INCONSISTENT_ROW_SIZE = 7,
+ NULL_CALLBACK = 8
+};
+
+typedef struct {
+ int64_t insertError = 0;
+ int64_t tableCreationError = 0;
+ int64_t tableDeletionError = 0;
+ std::list<int64_t> flushLatencyNs;
+ int64_t categoryChangedCount = 0;
+} RestrictedMetricStats;
+
struct ConfigStats {
int32_t uid;
int64_t id;
@@ -63,6 +84,8 @@
int32_t matcher_count;
int32_t alert_count;
bool is_valid;
+ bool device_info_table_creation_failed = false;
+ int32_t db_corrupted_count = 0;
// Stores reasons for why config is valid or not
std::optional<InvalidConfigReason> reason;
@@ -105,6 +128,17 @@
// Stores the config ID for each sub-config used.
std::list<std::pair<const int64_t, const int32_t>> annotations;
+
+ // Maps metric ID of restricted metric to its stats.
+ std::map<int64_t, RestrictedMetricStats> restricted_metric_stats;
+
+ std::list<int64_t> total_flush_latency_ns;
+
+ // Stores the last 20 timestamps for computing sqlite db size.
+ std::list<int64_t> total_db_size_timestamps;
+
+ // Stores the last 20 sizes of the sqlite db.
+ std::list<int64_t> total_db_sizes;
};
struct UidMapStats {
@@ -146,6 +180,14 @@
const static int kMaxPullAtomPackages = 100;
+ const static int kMaxRestrictedMetricQueryCount = 20;
+
+ const static int kMaxRestrictedMetricFlushLatencyCount = 20;
+
+ const static int kMaxRestrictedConfigFlushLatencyCount = 20;
+
+ const static int kMaxRestrictedConfigDbSizeCount = 20;
+
// Max memory allowed for storing metrics per configuration. If this limit is exceeded, statsd
// drops the metrics data in memory.
static const size_t kMaxMetricsBytesPerConfig = 2 * 1024 * 1024;
@@ -154,6 +196,10 @@
// data subscriber that it's time to call getData.
static const size_t kBytesPerConfigTriggerGetData = 192 * 1024;
+ // Soft memory limit per restricted configuration. Once this limit is exceeded,
+ // we begin flush in-memory restricted metrics to database.
+ static const size_t kBytesPerRestrictedConfigTriggerFlush = 25 * 1024;
+
// Cap the UID map's memory usage to this. This should be fairly high since the UID information
// is critical for understanding the metrics.
const static size_t kMaxBytesUsedUidMap = 50 * 1024;
@@ -167,6 +213,15 @@
/* Min period between two checks of byte size per config key in nanoseconds. */
static const int64_t kMinByteSizeCheckPeriodNs = 60 * NS_PER_SEC;
+ /* Min period between two checks of restricted metrics TTLs. */
+ static const int64_t kMinTtlCheckPeriodNs = 60 * 60 * NS_PER_SEC;
+
+ /* Min period between two flush operations of restricted metrics. */
+ static const int64_t kMinFlushRestrictedPeriodNs = 60 * 60 * NS_PER_SEC;
+
+ /* Min period between two db guardrail check operations of restricted metrics. */
+ static const int64_t kMinDbGuardrailEnforcementPeriodNs = 60 * 60 * NS_PER_SEC;
+
/* Minimum period between two activation broadcasts in nanoseconds. */
static const int64_t kMinActivationBroadcastPeriodNs = 10 * NS_PER_SEC;
@@ -191,9 +246,12 @@
// Maximum number of pushed atoms statsd stats will track above kMaxPushedAtomId.
static const int kMaxNonPlatformPushedAtoms = 600;
+ // Maximum number of pushed atoms error statsd stats will track.
+ static const int kMaxPushedAtomErrorStatsSize = 100;
+
// Maximum atom id value that we consider a platform pushed atom.
// This should be updated once highest pushed atom id in atoms.proto approaches this value.
- static const int kMaxPushedAtomId = 750;
+ static const int kMaxPushedAtomId = 900;
// Atom id that is the start of the pulled atoms.
static const int kPullAtomStartTag = 10000;
@@ -261,6 +319,16 @@
void noteMetricsReportSent(const ConfigKey& key, const size_t num_bytes);
/**
+ * Report failure in creating the device info metadata table for restricted configs.
+ */
+ void noteDeviceInfoTableCreationFailed(const ConfigKey& key);
+
+ /**
+ * Report db corruption for restricted configs.
+ */
+ void noteDbCorrupted(const ConfigKey& key);
+
+ /**
* Report the size of output tuple of a condition.
*
* Note: only report when the condition has an output dimension, and the tuple
@@ -315,7 +383,7 @@
/**
* Report an atom event has been logged.
*/
- void noteAtomLogged(int atomId, int32_t timeSec);
+ void noteAtomLogged(int atomId, int32_t timeSec, bool isSkipped);
/**
* Report that statsd modified the anomaly alarm registered with StatsCompanionService.
@@ -487,23 +555,63 @@
/* Reports one event id has been dropped due to queue overflow, and the oldest event timestamp
* in the queue */
- void noteEventQueueOverflow(int64_t oldestEventTimestampNs, int32_t atomId);
+ void noteEventQueueOverflow(int64_t oldestEventTimestampNs, int32_t atomId, bool isSkipped);
/**
* Reports that the activation broadcast guardrail was hit for this uid. Namely, the broadcast
* should have been sent, but instead was skipped due to hitting the guardrail.
*/
- void noteActivationBroadcastGuardrailHit(const int uid);
+ void noteActivationBroadcastGuardrailHit(const int uid);
- /**
- * Reports that an atom is erroneous or cannot be parsed successfully by
- * statsd. An atom tag of 0 indicates that the client did not supply the
- * atom id within the encoding.
- *
- * For pushed atoms only, this call should be preceded by a call to
- * noteAtomLogged.
- */
- void noteAtomError(int atomTag, bool pull=false);
+ /**
+ * Reports that an atom is erroneous or cannot be parsed successfully by
+ * statsd. An atom tag of 0 indicates that the client did not supply the
+ * atom id within the encoding.
+ *
+ * For pushed atoms only, this call should be preceded by a call to
+ * noteAtomLogged.
+ */
+ void noteAtomError(int atomTag, bool pull = false);
+
+ /** Report query of restricted metric succeed **/
+ void noteQueryRestrictedMetricSucceed(const int64_t configId, const string& configPackage,
+ const std::optional<int32_t> configUid,
+ const int32_t callingUid, const int64_t queryLatencyNs);
+
+ /** Report query of restricted metric failed **/
+ void noteQueryRestrictedMetricFailed(const int64_t configId, const string& configPackage,
+ const std::optional<int32_t> configUid,
+ const int32_t callingUid, const InvalidQueryReason reason);
+
+ /** Report query of restricted metric failed along with an error string **/
+ void noteQueryRestrictedMetricFailed(const int64_t configId, const string& configPackage,
+ const std::optional<int32_t> configUid,
+ const int32_t callingUid, const InvalidQueryReason reason,
+ const string& error);
+
+ // Reports that a restricted metric fails to be inserted to database.
+ void noteRestrictedMetricInsertError(const ConfigKey& configKey, int64_t metricId);
+
+ // Reports that a restricted metric fails to create table in database.
+ void noteRestrictedMetricTableCreationError(const ConfigKey& configKey, const int64_t metricId);
+
+ // Reports that a restricted metric fails to delete table in database.
+ void noteRestrictedMetricTableDeletionError(const ConfigKey& configKey, const int64_t metricId);
+
+ // Reports the time it takes for a restricted metric to flush the data to the database.
+ void noteRestrictedMetricFlushLatency(const ConfigKey& configKey, const int64_t metricId,
+ const int64_t flushLatencyNs);
+
+ // Reports that a restricted metric had a category change.
+ void noteRestrictedMetricCategoryChanged(const ConfigKey& configKey, const int64_t metricId);
+
+ // Reports the time is takes to flush a restricted config to the database.
+ void noteRestrictedConfigFlushLatency(const ConfigKey& configKey,
+ const int64_t totalFlushLatencyNs);
+
+ // Reports the size of the internal sqlite db.
+ void noteRestrictedConfigDbSize(const ConfigKey& configKey, const int64_t elapsedTimeNs,
+ const int64_t dbSize);
/**
* Reset the historical stats. Including all stats in icebox, and the tracked stats about
@@ -532,9 +640,10 @@
typedef struct PullTimeoutMetadata {
int64_t pullTimeoutUptimeMillis;
int64_t pullTimeoutElapsedMillis;
- PullTimeoutMetadata(int64_t uptimeMillis, int64_t elapsedMillis) :
- pullTimeoutUptimeMillis(uptimeMillis),
- pullTimeoutElapsedMillis(elapsedMillis) {/* do nothing */}
+ PullTimeoutMetadata(int64_t uptimeMillis, int64_t elapsedMillis)
+ : pullTimeoutUptimeMillis(uptimeMillis),
+ pullTimeoutElapsedMillis(elapsedMillis) { /* do nothing */
+ }
} PullTimeoutMetadata;
typedef struct {
@@ -593,17 +702,23 @@
// The size of the vector is capped by kMaxIceBoxSize.
std::list<const std::shared_ptr<ConfigStats>> mIceBox;
- // Stores the number of times a pushed atom is logged.
+ // Stores the number of times a pushed atom is logged and skipped (if skipped).
// The size of the vector is the largest pushed atom id in atoms.proto + 1. Atoms
// out of that range will be put in mNonPlatformPushedAtomStats.
// This is a vector, not a map because it will be accessed A LOT -- for each stats log.
- std::vector<int> mPushedAtomStats;
+ struct PushedAtomStats {
+ int logCount = 0;
+ int skipCount = 0;
+ };
- // Stores the number of times a pushed atom is logged for atom ids above kMaxPushedAtomId.
- // The max size of the map is kMaxNonPlatformPushedAtoms.
- std::unordered_map<int, int> mNonPlatformPushedAtomStats;
+ std::vector<PushedAtomStats> mPushedAtomStats;
+
+ // Stores the number of times a pushed atom is logged and skipped for atom ids above
+ // kMaxPushedAtomId. The max size of the map is kMaxNonPlatformPushedAtoms.
+ std::unordered_map<int, PushedAtomStats> mNonPlatformPushedAtomStats;
// Stores the number of times a pushed atom is dropped due to queue overflow event.
+ // We do not expect it will happen too often so the map is preferable vs pre-allocated vector
// The max size of the map is kMaxPushedAtomId + kMaxNonPlatformPushedAtoms.
std::unordered_map<int, int> mPushedAtomDropsStats;
@@ -612,9 +727,8 @@
// Stores the number of times a pushed atom was logged erroneously. The
// corresponding counts for pulled atoms are stored in PulledAtomStats.
- // The max size of this map is kMaxAtomErrorsStatsSize.
+ // The max size of this map is kMaxPushedAtomErrorStatsSize.
std::map<int, int> mPushedAtomErrorStats;
- int kMaxPushedAtomErrorStatsSize = 100;
// Maps metric ID to its stats. The size is capped by the number of metrics.
std::map<int64_t, AtomMetricStats> mAtomMetricStats;
@@ -658,6 +772,40 @@
std::list<int32_t> mSystemServerRestartSec;
+ struct RestrictedMetricQueryStats {
+ RestrictedMetricQueryStats(int32_t callingUid, int64_t configId,
+ const string& configPackage, std::optional<int32_t> configUid,
+ int64_t queryTimeNs,
+ std::optional<InvalidQueryReason> invalidQueryReason,
+ const string& error, std::optional<int64_t> queryLatencyNs)
+ : mCallingUid(callingUid),
+ mConfigId(configId),
+ mConfigPackage(configPackage),
+ mConfigUid(configUid),
+ mQueryWallTimeNs(queryTimeNs),
+ mInvalidQueryReason(invalidQueryReason),
+ mError(error),
+ mQueryLatencyNs(queryLatencyNs) {
+ mHasError = invalidQueryReason.has_value();
+ }
+ int32_t mCallingUid;
+ int64_t mConfigId;
+ string mConfigPackage;
+ std::optional<int32_t> mConfigUid;
+ int64_t mQueryWallTimeNs;
+ std::optional<InvalidQueryReason> mInvalidQueryReason;
+ bool mHasError;
+ string mError;
+ std::optional<int64_t> mQueryLatencyNs;
+ };
+ std::list<RestrictedMetricQueryStats> mRestrictedMetricQueryStats;
+
+ void noteQueryRestrictedMetricFailedLocked(const int64_t configId, const string& configPackage,
+ const std::optional<int32_t> configUid,
+ const int32_t callingUid,
+ const InvalidQueryReason reason,
+ const string& error);
+
// Stores the number of times statsd modified the anomaly alarm registered with
// StatsCompanionService.
int mAnomalyAlarmRegisteredStats = 0;
@@ -671,7 +819,7 @@
void resetInternalLocked();
- void noteAtomLoggedLocked(int atomId);
+ void noteAtomLoggedLocked(int atomId, bool isSkipped);
void noteAtomDroppedLocked(int atomId);
@@ -712,8 +860,13 @@
FRIEND_TEST(StatsdStatsTest, TestAtomMetricsStats);
FRIEND_TEST(StatsdStatsTest, TestActivationBroadcastGuardrailHit);
FRIEND_TEST(StatsdStatsTest, TestAtomErrorStats);
+ FRIEND_TEST(StatsdStatsTest, TestAtomSkippedStats);
+ FRIEND_TEST(StatsdStatsTest, TestRestrictedMetricsStats);
+ FRIEND_TEST(StatsdStatsTest, TestRestrictedMetricsQueryStats);
FRIEND_TEST(StatsdStatsTest, TestAtomDroppedStats);
- FRIEND_TEST(StatsdStatsTest, TestAtomDroppedAndLoggedStats);
+ FRIEND_TEST(StatsdStatsTest, TestAtomLoggedAndDroppedStats);
+ FRIEND_TEST(StatsdStatsTest, TestAtomLoggedAndDroppedAndSkippedStats);
+ FRIEND_TEST(StatsdStatsTest, TestShardOffsetProvider);
FRIEND_TEST(StatsLogProcessorTest, InvalidConfigRemoved);
};
diff --git a/statsd/src/guardrail/invalid_config_reason_enum.proto b/statsd/src/guardrail/invalid_config_reason_enum.proto
index 7df4d3e..23a462d 100644
--- a/statsd/src/guardrail/invalid_config_reason_enum.proto
+++ b/statsd/src/guardrail/invalid_config_reason_enum.proto
@@ -106,4 +106,6 @@
INVALID_CONFIG_REASON_METRIC_DIMENSIONAL_SAMPLING_INFO_MISSING_SAMPLED_FIELD = 81;
INVALID_CONFIG_REASON_METRIC_SAMPLED_FIELD_INCORRECT_SIZE = 82;
INVALID_CONFIG_REASON_METRIC_SAMPLED_FIELDS_NOT_SUBSET_DIM_IN_WHAT = 83;
+ INVALID_CONFIG_REASON_RESTRICTED_METRIC_NOT_ENABLED = 84;
+ INVALID_CONFIG_REASON_RESTRICTED_METRIC_NOT_SUPPORTED = 85;
};
diff --git a/statsd/src/logd/LogEvent.cpp b/statsd/src/logd/LogEvent.cpp
index f112937..8cdd79c 100644
--- a/statsd/src/logd/LogEvent.cpp
+++ b/statsd/src/logd/LogEvent.cpp
@@ -20,10 +20,12 @@
#include "logd/LogEvent.h"
#include <android-base/stringprintf.h>
+#include <android-modules-utils/sdk_level.h>
#include <android/binder_ibinder.h>
#include <private/android_filesystem_config.h>
-#include "annotations.h"
+#include "flags/FlagProvider.h"
+#include "stats_annotations.h"
#include "stats_log_util.h"
#include "statslog_statsd.h"
@@ -36,12 +38,25 @@
using namespace android::util;
using android::base::StringPrintf;
+using android::modules::sdklevel::IsAtLeastU;
using android::util::ProtoOutputStream;
using std::string;
using std::vector;
+namespace {
+
+uint8_t getTypeId(uint8_t typeInfo) {
+ return typeInfo & 0x0F; // type id in lower 4 bytes
+}
+
+uint8_t getNumAnnotations(uint8_t typeInfo) {
+ return (typeInfo >> 4) & 0x0F; // num annotations in upper 4 bytes
+}
+
+} // namespace
+
LogEvent::LogEvent(int32_t uid, int32_t pid)
- : mLogdTimestampNs(time(nullptr)), mLogUid(uid), mLogPid(pid) {
+ : mLogdTimestampNs(getWallClockNs()), mLogUid(uid), mLogPid(pid) {
}
LogEvent::LogEvent(const string& trainName, int64_t trainVersionCode, bool requiresStaging,
@@ -333,7 +348,7 @@
void LogEvent::parseExclusiveStateAnnotation(uint8_t annotationType,
std::optional<uint8_t> numElements) {
- // Allowed types: INT
+ // Allowed types: BOOL
if (mValues.empty() || annotationType != BOOL_TYPE || !checkPreviousValueType(INT) ||
numElements) {
VLOG("Atom ID %d error while parseExclusiveStateAnnotation()", mTagId);
@@ -361,7 +376,7 @@
void LogEvent::parseStateNestedAnnotation(uint8_t annotationType,
std::optional<uint8_t> numElements) {
- // Allowed types: INT
+ // Allowed types: BOOL
if (mValues.empty() || annotationType != BOOL_TYPE || !checkPreviousValueType(INT) ||
numElements) {
VLOG("Atom ID %d error while parseStateNestedAnnotation()", mTagId);
@@ -373,6 +388,38 @@
mValues[mValues.size() - 1].mAnnotations.setNested(nested);
}
+void LogEvent::parseRestrictionCategoryAnnotation(uint8_t annotationType) {
+ // Allowed types: INT, field value should be empty since this is atom-level annotation.
+ if (!mValues.empty() || annotationType != INT32_TYPE) {
+ mValid = false;
+ return;
+ }
+ int value = readNextValue<int32_t>();
+ // should be one of predefined category in StatsLog.java
+ switch (value) {
+ // Only diagnostic is currently supported for use.
+ case ASTATSLOG_RESTRICTION_CATEGORY_DIAGNOSTIC:
+ break;
+ default:
+ mValid = false;
+ return;
+ }
+ mRestrictionCategory = static_cast<StatsdRestrictionCategory>(value);
+ return;
+}
+
+void LogEvent::parseFieldRestrictionAnnotation(uint8_t annotationType) {
+ // Allowed types: BOOL
+ if (mValues.empty() || annotationType != BOOL_TYPE) {
+ mValid = false;
+ return;
+ }
+ // Read the value so that the rest of the event is correctly parsed
+ // TODO: store the field annotations once the metrics need to parse them.
+ readNextValue<uint8_t>();
+ return;
+}
+
// firstUidInChainIndex is a default parameter that is only needed when parsing
// annotations for attribution chains.
// numElements is a default param that is only needed when parsing annotations for repeated fields
@@ -383,27 +430,50 @@
uint8_t annotationType = readNextValue<uint8_t>();
switch (annotationId) {
- case ANNOTATION_ID_IS_UID:
+ case ASTATSLOG_ANNOTATION_ID_IS_UID:
parseIsUidAnnotation(annotationType, numElements);
break;
- case ANNOTATION_ID_TRUNCATE_TIMESTAMP:
+ case ASTATSLOG_ANNOTATION_ID_TRUNCATE_TIMESTAMP:
parseTruncateTimestampAnnotation(annotationType);
break;
- case ANNOTATION_ID_PRIMARY_FIELD:
+ case ASTATSLOG_ANNOTATION_ID_PRIMARY_FIELD:
parsePrimaryFieldAnnotation(annotationType, numElements, firstUidInChainIndex);
break;
- case ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID:
+ case ASTATSLOG_ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID:
parsePrimaryFieldFirstUidAnnotation(annotationType, firstUidInChainIndex);
break;
- case ANNOTATION_ID_EXCLUSIVE_STATE:
+ case ASTATSLOG_ANNOTATION_ID_EXCLUSIVE_STATE:
parseExclusiveStateAnnotation(annotationType, numElements);
break;
- case ANNOTATION_ID_TRIGGER_STATE_RESET:
+ case ASTATSLOG_ANNOTATION_ID_TRIGGER_STATE_RESET:
parseTriggerStateResetAnnotation(annotationType, numElements);
break;
- case ANNOTATION_ID_STATE_NESTED:
+ case ASTATSLOG_ANNOTATION_ID_STATE_NESTED:
parseStateNestedAnnotation(annotationType, numElements);
break;
+ case ASTATSLOG_ANNOTATION_ID_RESTRICTION_CATEGORY:
+ if (IsAtLeastU()) {
+ parseRestrictionCategoryAnnotation(annotationType);
+ } else {
+ mValid = false;
+ }
+ break;
+ // Currently field restrictions are ignored, so we parse but do not store them.
+ case ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_PERIPHERAL_DEVICE_INFO:
+ case ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_APP_USAGE:
+ case ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_APP_ACTIVITY:
+ case ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_HEALTH_CONNECT:
+ case ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_ACCESSIBILITY:
+ case ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_SYSTEM_SEARCH:
+ case ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_USER_ENGAGEMENT:
+ case ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_AMBIENT_SENSING:
+ case ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_DEMOGRAPHIC_CLASSIFICATION:
+ if (IsAtLeastU()) {
+ parseFieldRestrictionAnnotation(annotationType);
+ } else {
+ mValid = false;
+ }
+ break;
default:
VLOG("Atom ID %d error while parseAnnotations() - wrong annotationId(%d)", mTagId,
annotationId);
@@ -413,24 +483,34 @@
}
}
-uint8_t LogEvent::parseHeader() {
+LogEvent::BodyBufferInfo LogEvent::parseHeader(const uint8_t* buf, size_t len) {
+ BodyBufferInfo bodyInfo;
+
+ mParsedHeaderOnly = true;
+
+ mBuf = buf;
+ mRemainingLen = (uint32_t)len;
+
// Beginning of buffer is OBJECT_TYPE | NUM_FIELDS | TIMESTAMP | ATOM_ID
uint8_t typeInfo = readNextValue<uint8_t>();
if (getTypeId(typeInfo) != OBJECT_TYPE) {
mValid = false;
- return 0;
+ mBuf = nullptr;
+ return bodyInfo;
}
uint8_t numElements = readNextValue<uint8_t>();
if (numElements < 2 || numElements > INT8_MAX) {
mValid = false;
- return 0;
+ mBuf = nullptr;
+ return bodyInfo;
}
typeInfo = readNextValue<uint8_t>();
if (getTypeId(typeInfo) != INT64_TYPE) {
mValid = false;
- return 0;
+ mBuf = nullptr;
+ return bodyInfo;
}
mElapsedTimestampNs = readNextValue<int64_t>();
numElements--;
@@ -438,34 +518,33 @@
typeInfo = readNextValue<uint8_t>();
if (getTypeId(typeInfo) != INT32_TYPE) {
mValid = false;
- return 0;
+ mBuf = nullptr;
+ return bodyInfo;
}
mTagId = readNextValue<int32_t>();
numElements--;
parseAnnotations(getNumAnnotations(typeInfo)); // atom-level annotations
- return numElements;
+ bodyInfo.numElements = numElements;
+ bodyInfo.buffer = mBuf;
+ bodyInfo.bufferSize = mRemainingLen;
+
+ mBuf = nullptr;
+ return bodyInfo;
}
-// This parsing logic is tied to the encoding scheme used in StatsEvent.java and
-// stats_event.c
-bool LogEvent::parseBuffer(const uint8_t* buf, size_t len, bool fetchHeaderOnly) {
- mBuf = buf;
- mRemainingLen = (uint32_t)len;
+bool LogEvent::parseBody(const BodyBufferInfo& bodyInfo) {
+ mParsedHeaderOnly = false;
- const uint8_t numElements = parseHeader();
-
- if (!mValid || fetchHeaderOnly) {
- mBuf = nullptr;
- return mValid;
- }
+ mBuf = bodyInfo.buffer;
+ mRemainingLen = (uint32_t)bodyInfo.bufferSize;
int32_t pos[] = {1, 1, 1};
bool last[] = {false, false, false};
- for (pos[0] = 1; pos[0] <= numElements && mValid; pos[0]++) {
- last[0] = (pos[0] == numElements);
+ for (pos[0] = 1; pos[0] <= bodyInfo.numElements && mValid; pos[0]++) {
+ last[0] = (pos[0] == bodyInfo.numElements);
uint8_t typeInfo = readNextValue<uint8_t>();
uint8_t typeId = getTypeId(typeInfo);
@@ -513,12 +592,22 @@
return mValid;
}
-uint8_t LogEvent::getTypeId(uint8_t typeInfo) {
- return typeInfo & 0x0F; // type id in lower 4 bytes
-}
+// This parsing logic is tied to the encoding scheme used in StatsEvent.java and
+// stats_event.c
+bool LogEvent::parseBuffer(const uint8_t* buf, size_t len) {
+ BodyBufferInfo bodyInfo = parseHeader(buf, len);
-uint8_t LogEvent::getNumAnnotations(uint8_t typeInfo) {
- return (typeInfo >> 4) & 0x0F; // num annotations in upper 4 bytes
+ // emphasize intention to parse the body, however atom data could be incomplete
+ // if header/body parsing was failed due to invalid buffer content for example
+ mParsedHeaderOnly = false;
+
+ // early termination if header is invalid
+ if (!mValid) {
+ mBuf = nullptr;
+ return false;
+ }
+
+ return parseBody(bodyInfo);
}
int64_t LogEvent::GetLong(size_t key, status_t* err) const {
@@ -661,6 +750,11 @@
result += " [" + annotations + "] ";
}
+ if (isParsedHeaderOnly()) {
+ result += " ParsedHeaderOnly }";
+ return result;
+ }
+
for (const auto& value : mValues) {
result += StringPrintf("%#x", value.mField.getField()) + "->" + value.mValue.toString();
result += value.mAnnotations.toString() + " ";
diff --git a/statsd/src/logd/LogEvent.h b/statsd/src/logd/LogEvent.h
index c59cc73..d75b446 100644
--- a/statsd/src/logd/LogEvent.h
+++ b/statsd/src/logd/LogEvent.h
@@ -24,6 +24,7 @@
#include <vector>
#include "FieldValue.h"
+#include "utils/RestrictedPolicyManager.h"
namespace android {
namespace os {
@@ -88,12 +89,31 @@
* \param buf a buffer that begins at the start of the serialized atom (it
* should not include the android_log_header_t or the StatsEventTag)
* \param len size of the buffer
- * \param fetchHeaderOnly force to parse only event header with atomId,timestamp
- * and atom level annotations
*
- * \return success of the initialization
+ * \return success of the parsing
*/
- bool parseBuffer(const uint8_t* buf, size_t len, bool fetchHeaderOnly = false);
+ bool parseBuffer(const uint8_t* buf, size_t len);
+
+ struct BodyBufferInfo {
+ const uint8_t* buffer = nullptr;
+ size_t bufferSize = 0;
+ uint8_t numElements = 0;
+ };
+
+ /**
+ * @brief Parses atom header which consists of atom id, timestamp
+ * and atom level annotations
+ * Updates the value of isValid()
+ * @return BodyBufferInfo to be used for parseBody()
+ */
+ BodyBufferInfo parseHeader(const uint8_t* buf, size_t len);
+
+ /**
+ * @brief Parses atom body which consists of header.numElements elements
+ * Should be called only with BodyBufferInfo if when logEvent.isValid() == true
+ * \return success of the parsing
+ */
+ bool parseBody(const BodyBufferInfo& bodyInfo);
// Constructs a BinaryPushStateChanged LogEvent from API call.
explicit LogEvent(const std::string& trainName, int64_t trainVersionCode, bool requiresStaging,
@@ -232,17 +252,26 @@
}
/**
+ * @brief Returns true if only header was parsed
+ */
+ bool isParsedHeaderOnly() const {
+ return mParsedHeaderOnly;
+ }
+
+ /**
* Only use this if copy is absolutely needed.
*/
LogEvent(const LogEvent&) = default;
+ inline StatsdRestrictionCategory getRestrictionCategory() const {
+ return mRestrictionCategory;
+ }
+
+ inline bool isRestricted() const {
+ return mRestrictionCategory != CATEGORY_NO_RESTRICTION;
+ }
+
private:
- /**
- * @brief Parses atom header which consists of atom id, timestamp
- * and atom level annotations
- * @return amount of fields on the atom level
- */
- uint8_t parseHeader();
void parseInt32(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
void parseInt64(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
void parseString(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
@@ -265,7 +294,10 @@
void parseTriggerStateResetAnnotation(uint8_t annotationType,
std::optional<uint8_t> numElements);
void parseStateNestedAnnotation(uint8_t annotationType, std::optional<uint8_t> numElements);
+ void parseRestrictionCategoryAnnotation(uint8_t annotationType);
+ void parseFieldRestrictionAnnotation(uint8_t annotationType);
bool checkPreviousValueType(Type expected);
+ bool getRestrictedMetricsFlag();
/**
* The below two variables are only valid during the execution of
@@ -277,6 +309,8 @@
bool mValid = true; // stores whether the event we received from the socket is valid
+ bool mParsedHeaderOnly = false; // stores whether the only header was parsed skipping the body
+
/**
* Side-effects:
* If there is enough space in buffer to read value of type T
@@ -317,9 +351,6 @@
mValues.push_back(FieldValue(f, v));
}
- uint8_t getTypeId(uint8_t typeInfo);
- uint8_t getNumAnnotations(uint8_t typeInfo);
-
// The items are naturally sorted in DFS order as we read them. this allows us to do fast
// matching.
std::vector<FieldValue> mValues;
@@ -343,6 +374,7 @@
// Annotations
bool mTruncateTimestamp = false;
int mResetState = -1;
+ StatsdRestrictionCategory mRestrictionCategory = CATEGORY_NO_RESTRICTION;
size_t mNumUidFields = 0;
diff --git a/statsd/src/logd/LogEventQueue.h b/statsd/src/logd/LogEventQueue.h
index 9dda3d2..e0e2f4e 100644
--- a/statsd/src/logd/LogEventQueue.h
+++ b/statsd/src/logd/LogEventQueue.h
@@ -16,12 +16,14 @@
#pragma once
-#include "LogEvent.h"
+#include <gtest/gtest_prod.h>
#include <condition_variable>
#include <mutex>
#include <queue>
+#include "LogEvent.h"
+
namespace android {
namespace os {
namespace statsd {
@@ -50,6 +52,16 @@
std::condition_variable mCondition;
std::mutex mMutex;
std::queue<std::unique_ptr<LogEvent>> mQueue;
+
+ friend class SocketParseMessageTest;
+
+ FRIEND_TEST(SocketParseMessageTestNoFiltering, TestProcessMessageNoFiltering);
+ FRIEND_TEST(SocketParseMessageTestNoFiltering,
+ TestProcessMessageNoFilteringWithEmptySetExplicitSet);
+ FRIEND_TEST(SocketParseMessageTest, TestProcessMessageFilterEmptySet);
+ FRIEND_TEST(SocketParseMessageTest, TestProcessMessageFilterCompleteSet);
+ FRIEND_TEST(SocketParseMessageTest, TestProcessMessageFilterPartialSet);
+ FRIEND_TEST(SocketParseMessageTest, TestProcessMessageFilterToggle);
};
} // namespace statsd
diff --git a/statsd/src/main.cpp b/statsd/src/main.cpp
index 367ab73..14b31a0 100644
--- a/statsd/src/main.cpp
+++ b/statsd/src/main.cpp
@@ -17,6 +17,8 @@
#define STATSD_DEBUG false // STOPSHIP if true
#include "Log.h"
+#include <android/binder_ibinder.h>
+#include <android/binder_ibinder_platform.h>
#include <android/binder_interface_utils.h>
#include <android/binder_manager.h>
#include <android/binder_process.h>
@@ -73,18 +75,36 @@
ABinderProcess_startThreadPool();
std::shared_ptr<LogEventQueue> eventQueue =
- std::make_shared<LogEventQueue>(4000 /*buffer limit. Buffer is NOT pre-allocated*/);
+ std::make_shared<LogEventQueue>(8000 /*buffer limit. Buffer is NOT pre-allocated*/);
// Initialize boot flags
- FlagProvider::getInstance().initBootFlags({LIMIT_PULL_FLAG});
+ FlagProvider::getInstance().initBootFlags(
+ {OPTIMIZATION_SOCKET_PARSING_FLAG, STATSD_INIT_COMPLETED_NO_DELAY_FLAG});
sp<UidMap> uidMap = UidMap::getInstance();
+ const bool logsFilteringEnabled = FlagProvider::getInstance().getBootFlagBool(
+ OPTIMIZATION_SOCKET_PARSING_FLAG, FLAG_FALSE);
+ std::shared_ptr<LogEventFilter> logEventFilter =
+ logsFilteringEnabled ? std::make_shared<LogEventFilter>() : nullptr;
+
+ const int initEventDelay = FlagProvider::getInstance().getBootFlagBool(
+ STATSD_INIT_COMPLETED_NO_DELAY_FLAG, FLAG_FALSE)
+ ? 0
+ : StatsService::kStatsdInitDelaySecs;
// Create the service
- gStatsService = SharedRefBase::make<StatsService>(uidMap, eventQueue);
+ gStatsService =
+ SharedRefBase::make<StatsService>(uidMap, eventQueue, logEventFilter, initEventDelay);
+ auto binder = gStatsService->asBinder();
+
+ // We want to be able to ask for the selinux context of callers:
+ if (__builtin_available(android __ANDROID_API_U__, *)) {
+ AIBinder_setRequestingSid(binder.get(), true);
+ }
+
// TODO(b/149582373): Set DUMP_FLAG_PROTO once libbinder_ndk supports
// setting dumpsys priorities.
- binder_status_t status = AServiceManager_addService(gStatsService->asBinder().get(), "stats");
+ binder_status_t status = AServiceManager_addService(binder.get(), "stats");
if (status != STATUS_OK) {
ALOGE("Failed to add service as AIDL service");
return -1;
@@ -94,7 +114,7 @@
gStatsService->Startup();
- gSocketListener = new StatsSocketListener(eventQueue);
+ gSocketListener = new StatsSocketListener(eventQueue, logEventFilter);
ALOGI("Statsd starts to listen to socket.");
// Backlog and /proc/sys/net/unix/max_dgram_qlen set to large value
diff --git a/statsd/src/metrics/EventMetricProducer.h b/statsd/src/metrics/EventMetricProducer.h
index fc3e79d..c897305 100644
--- a/statsd/src/metrics/EventMetricProducer.h
+++ b/statsd/src/metrics/EventMetricProducer.h
@@ -50,6 +50,9 @@
return METRIC_TYPE_EVENT;
}
+protected:
+ size_t mTotalSize;
+
private:
void onMatchedLogEventInternalLocked(
const size_t matcherIndex, const MetricDimensionKey& eventKey,
@@ -95,8 +98,6 @@
// Maps the field/value pairs of an atom to a list of timestamps used to deduplicate atoms.
std::unordered_map<AtomDimensionKey, std::vector<int64_t>> mAggregatedAtoms;
-
- size_t mTotalSize;
};
} // namespace statsd
diff --git a/statsd/src/metrics/MetricProducer.h b/statsd/src/metrics/MetricProducer.h
index 0323523..ed3b217 100644
--- a/statsd/src/metrics/MetricProducer.h
+++ b/statsd/src/metrics/MetricProducer.h
@@ -31,8 +31,10 @@
#include "matchers/EventMatcherWizard.h"
#include "matchers/matcher_util.h"
#include "packages/PackageInfoListener.h"
+#include "src/statsd_metadata.pb.h" // MetricMetadata
#include "state/StateListener.h"
#include "state/StateManager.h"
+#include "utils/DbUtils.h"
#include "utils/ShardOffsetProvider.h"
namespace android {
@@ -335,6 +337,21 @@
void writeActiveMetricToProtoOutputStream(
int64_t currentTimeNs, const DumpReportReason reason, ProtoOutputStream* proto);
+ virtual void enforceRestrictedDataTtl(sqlite3* db, const int64_t wallClockNs){};
+
+ virtual bool writeMetricMetadataToProto(metadata::MetricMetadata* metricMetadata) {
+ return false;
+ }
+
+ virtual void loadMetricMetadataFromProto(const metadata::MetricMetadata& metricMetadata){};
+
+ /* Called when the metric is to about to be removed from config. */
+ virtual void onMetricRemove() {
+ }
+
+ virtual void flushRestrictedData() {
+ }
+
// Start: getters/setters
inline int64_t getMetricId() const {
return mMetricId;
diff --git a/statsd/src/metrics/MetricsManager.cpp b/statsd/src/metrics/MetricsManager.cpp
index 361cb1a..1822ddf 100644
--- a/statsd/src/metrics/MetricsManager.cpp
+++ b/statsd/src/metrics/MetricsManager.cpp
@@ -18,6 +18,7 @@
#include "MetricsManager.h"
+#include <android-modules-utils/sdk_level.h>
#include <private/android_filesystem_config.h>
#include "CountMetricProducer.h"
@@ -33,6 +34,9 @@
#include "stats_log_util.h"
#include "stats_util.h"
#include "statslog_statsd.h"
+#include "utils/DbUtils.h"
+
+using android::modules::sdklevel::IsAtLeastU;
using android::util::FIELD_COUNT_REPEATED;
using android::util::FIELD_TYPE_INT32;
@@ -77,9 +81,16 @@
mWhitelistedAtomIds(config.whitelisted_atom_ids().begin(),
config.whitelisted_atom_ids().end()),
mShouldPersistHistory(config.persist_locally()) {
+ if (!IsAtLeastU() && config.has_restricted_metrics_delegate_package_name()) {
+ mInvalidConfigReason =
+ InvalidConfigReason(INVALID_CONFIG_REASON_RESTRICTED_METRIC_NOT_ENABLED);
+ return;
+ }
+ if (config.has_restricted_metrics_delegate_package_name()) {
+ mRestrictedMetricsDelegatePackageName = config.restricted_metrics_delegate_package_name();
+ }
// Init the ttl end timestamp.
refreshTtl(timeBaseNs);
-
mInvalidConfigReason = initStatsdConfig(
key, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
timeBaseNs, currentTimeNs, mTagIdsToMatchersMap, mAllAtomMatchingTrackers,
@@ -119,6 +130,16 @@
const int64_t currentTimeNs,
const sp<AlarmMonitor>& anomalyAlarmMonitor,
const sp<AlarmMonitor>& periodicAlarmMonitor) {
+ if (!IsAtLeastU() && config.has_restricted_metrics_delegate_package_name()) {
+ mInvalidConfigReason =
+ InvalidConfigReason(INVALID_CONFIG_REASON_RESTRICTED_METRIC_NOT_ENABLED);
+ return false;
+ }
+ if (config.has_restricted_metrics_delegate_package_name()) {
+ mRestrictedMetricsDelegatePackageName = config.restricted_metrics_delegate_package_name();
+ } else {
+ mRestrictedMetricsDelegatePackageName = nullopt;
+ }
vector<sp<AtomMatchingTracker>> newAtomMatchingTrackers;
unordered_map<int64_t, int> newAtomMatchingTrackerMap;
vector<sp<ConditionTracker>> newConditionTrackers;
@@ -421,6 +442,11 @@
const bool include_current_partial_bucket, const bool erase_data,
const DumpLatency dumpLatency, std::set<string>* str_set,
ProtoOutputStream* protoOutput) {
+ if (hasRestrictedMetricsDelegate()) {
+ // TODO(b/268150038): report error to statsdstats
+ VLOG("Unexpected call to onDumpReport in restricted metricsmanager.");
+ return;
+ }
VLOG("=========================Metric Reports Start==========================");
// one StatsLogReport per MetricProduer
for (const auto& producer : mAllMetricProducers) {
@@ -541,12 +567,13 @@
return;
}
+ // TODO(b/212755214): this check could be done once on the StatsLogProcessor level
if (!eventSanityCheck(event)) {
return;
}
- int tagId = event.GetTagId();
- int64_t eventTimeNs = event.GetElapsedTimestampNs();
+ const int tagId = event.GetTagId();
+ const int64_t eventTimeNs = event.GetElapsedTimestampNs();
bool isActive = mIsAlwaysActive;
@@ -573,6 +600,15 @@
return;
}
+ if (event.isParsedHeaderOnly()) {
+ // This should not happen if metric config is defined for certain atom id
+ const int64_t firstMatcherId =
+ mAllAtomMatchingTrackers[*matchersIt->second.begin()]->getId();
+ ALOGW("Atom %d is mistakenly skipped - there is a matcher %lld for it", tagId,
+ (long long)firstMatcherId);
+ return;
+ }
+
vector<MatchingState> matcherCache(mAllAtomMatchingTrackers.size(),
MatchingState::kNotComputed);
@@ -666,7 +702,6 @@
}
}
}
-
// For matched AtomMatchers, tell relevant metrics that a matched event has come.
for (size_t i = 0; i < mAllAtomMatchingTrackers.size(); i++) {
if (matcherCache[i] == MatchingState::kMatched) {
@@ -761,6 +796,15 @@
}
metadataWritten |= alertWritten;
}
+
+ for (const auto& metricProducer : mAllMetricProducers) {
+ metadata::MetricMetadata* metricMetadata = statsMetadata->add_metric_metadata();
+ bool metricWritten = metricProducer->writeMetricMetadataToProto(metricMetadata);
+ if (!metricWritten) {
+ statsMetadata->mutable_metric_metadata()->RemoveLast();
+ }
+ metadataWritten |= metricWritten;
+ }
return metadataWritten;
}
@@ -769,7 +813,7 @@
int64_t systemElapsedTimeNs) {
for (const metadata::AlertMetadata& alertMetadata : metadata.alert_metadata()) {
int64_t alertId = alertMetadata.alert_id();
- auto it = mAlertTrackerMap.find(alertId);
+ const auto& it = mAlertTrackerMap.find(alertId);
if (it == mAlertTrackerMap.end()) {
ALOGE("No anomalyTracker found for alertId %lld", (long long) alertId);
continue;
@@ -778,6 +822,67 @@
currentWallClockTimeNs,
systemElapsedTimeNs);
}
+ for (const metadata::MetricMetadata& metricMetadata : metadata.metric_metadata()) {
+ int64_t metricId = metricMetadata.metric_id();
+ const auto& it = mMetricProducerMap.find(metricId);
+ if (it == mMetricProducerMap.end()) {
+ ALOGE("No metricProducer found for metricId %lld", (long long)metricId);
+ }
+ mAllMetricProducers[it->second]->loadMetricMetadataFromProto(metricMetadata);
+ }
+}
+
+void MetricsManager::enforceRestrictedDataTtls(const int64_t wallClockNs) {
+ if (!hasRestrictedMetricsDelegate()) {
+ return;
+ }
+ sqlite3* db = dbutils::getDb(mConfigKey);
+ if (db == nullptr) {
+ ALOGE("Failed to open sqlite db");
+ dbutils::closeDb(db);
+ return;
+ }
+ for (const auto& producer : mAllMetricProducers) {
+ producer->enforceRestrictedDataTtl(db, wallClockNs);
+ }
+ dbutils::closeDb(db);
+}
+
+bool MetricsManager::validateRestrictedMetricsDelegate(const int32_t callingUid) {
+ if (!hasRestrictedMetricsDelegate()) {
+ return false;
+ }
+
+ set<int32_t> possibleUids = mUidMap->getAppUid(mRestrictedMetricsDelegatePackageName.value());
+
+ return possibleUids.find(callingUid) != possibleUids.end();
+}
+
+void MetricsManager::flushRestrictedData() {
+ if (!hasRestrictedMetricsDelegate()) {
+ return;
+ }
+ int64_t flushStartNs = getElapsedRealtimeNs();
+ for (const auto& producer : mAllMetricProducers) {
+ producer->flushRestrictedData();
+ }
+ StatsdStats::getInstance().noteRestrictedConfigFlushLatency(
+ mConfigKey, getElapsedRealtimeNs() - flushStartNs);
+}
+
+vector<int64_t> MetricsManager::getAllMetricIds() const {
+ vector<int64_t> metricIds;
+ metricIds.reserve(mMetricProducerMap.size());
+ for (const auto& [metricId, _] : mMetricProducerMap) {
+ metricIds.push_back(metricId);
+ }
+ return metricIds;
+}
+
+void MetricsManager::addAllAtomIds(LogEventFilter::AtomIdSet& allIds) const {
+ for (const auto& [atomId, _] : mTagIdsToMatchersMap) {
+ allIds.insert(atomId);
+ }
}
} // namespace statsd
diff --git a/statsd/src/metrics/MetricsManager.h b/statsd/src/metrics/MetricsManager.h
index 5b9731e..8b7869a 100644
--- a/statsd/src/metrics/MetricsManager.h
+++ b/statsd/src/metrics/MetricsManager.h
@@ -58,7 +58,7 @@
bool eventSanityCheck(const LogEvent& event);
- void onLogEvent(const LogEvent& event);
+ virtual void onLogEvent(const LogEvent& event);
void onAnomalyAlarmFired(
const int64_t& timestampNs,
@@ -161,6 +161,31 @@
void loadMetadata(const metadata::StatsMetadata& metadata,
int64_t currentWallClockTimeNs,
int64_t systemElapsedTimeNs);
+
+ inline bool hasRestrictedMetricsDelegate() const {
+ return mRestrictedMetricsDelegatePackageName.has_value();
+ }
+
+ inline string getRestrictedMetricsDelegate() const {
+ return hasRestrictedMetricsDelegate() ? mRestrictedMetricsDelegatePackageName.value() : "";
+ }
+
+ inline ConfigKey getConfigKey() const {
+ return mConfigKey;
+ }
+
+ void enforceRestrictedDataTtls(const int64_t wallClockNs);
+
+ bool validateRestrictedMetricsDelegate(const int32_t callingUid);
+
+ virtual void flushRestrictedData();
+
+ // Slow, should not be called in a hotpath.
+ vector<int64_t> getAllMetricIds() const;
+
+ // Adds all atom ids referenced by matchers in the MetricsManager's config
+ void addAllAtomIds(LogEventFilter::AtomIdSet& allIds) const;
+
private:
// For test only.
inline int64_t getTtlEndNs() const { return mTtlEndNs; }
@@ -317,6 +342,10 @@
// Hashes of the States used in this config, keyed by the state id, used in config updates.
std::map<int64_t, uint64_t> mStateProtoHashes;
+ // Optional package name of the delegate that processes restricted metrics
+ // If set, restricted metrics are only uploaded to the delegate.
+ optional<string> mRestrictedMetricsDelegatePackageName = nullopt;
+
FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions);
FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks);
FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSliceByFirstUid);
@@ -352,6 +381,9 @@
FRIEND_TEST(MetricsManagerTest, TestLogSources);
FRIEND_TEST(MetricsManagerTest, TestLogSourcesOnConfigUpdate);
+ FRIEND_TEST(MetricsManagerTest, TestOnMetricRemoveCalled);
+ FRIEND_TEST(MetricsManagerTest_SPlus, TestRestrictedMetricsConfig);
+ FRIEND_TEST(MetricsManagerTest_SPlus, TestRestrictedMetricsConfigUpdate);
FRIEND_TEST(MetricsManagerUtilTest, TestSampledMetrics);
FRIEND_TEST(StatsLogProcessorTest, TestActiveConfigMetricDiskWriteRead);
diff --git a/statsd/src/metrics/RestrictedEventMetricProducer.cpp b/statsd/src/metrics/RestrictedEventMetricProducer.cpp
new file mode 100644
index 0000000..f38e835
--- /dev/null
+++ b/statsd/src/metrics/RestrictedEventMetricProducer.cpp
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2023, 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.
+ */
+#define STATSD_DEBUG true
+#include "Log.h"
+
+#include "RestrictedEventMetricProducer.h"
+
+#include "stats_annotations.h"
+#include "stats_log_util.h"
+#include "utils/DbUtils.h"
+
+using std::lock_guard;
+using std::vector;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+#define NS_PER_DAY (24 * 3600 * NS_PER_SEC)
+
+RestrictedEventMetricProducer::RestrictedEventMetricProducer(
+ const ConfigKey& key, const EventMetric& metric, const int conditionIndex,
+ const vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& wizard,
+ const uint64_t protoHash, const int64_t startTimeNs,
+ const unordered_map<int, shared_ptr<Activation>>& eventActivationMap,
+ const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap,
+ const vector<int>& slicedStateAtoms,
+ const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap)
+ : EventMetricProducer(key, metric, conditionIndex, initialConditionCache, wizard, protoHash,
+ startTimeNs, eventActivationMap, eventDeactivationMap, slicedStateAtoms,
+ stateGroupMap),
+ mRestrictedDataCategory(CATEGORY_UNKNOWN) {
+}
+
+void RestrictedEventMetricProducer::onMatchedLogEventInternalLocked(
+ const size_t matcherIndex, const MetricDimensionKey& eventKey,
+ const ConditionKey& conditionKey, bool condition, const LogEvent& event,
+ const std::map<int, HashableDimensionKey>& statePrimaryKeys) {
+ if (!condition) {
+ return;
+ }
+ if (mRestrictedDataCategory != CATEGORY_UNKNOWN &&
+ mRestrictedDataCategory != event.getRestrictionCategory()) {
+ StatsdStats::getInstance().noteRestrictedMetricCategoryChanged(mConfigKey, mMetricId);
+ deleteMetricTable();
+ mLogEvents.clear();
+ mTotalSize = 0;
+ }
+ mRestrictedDataCategory = event.getRestrictionCategory();
+ mLogEvents.push_back(event);
+ mTotalSize += getSize(event.getValues()) + sizeof(event);
+}
+
+void RestrictedEventMetricProducer::onDumpReportLocked(
+ const int64_t dumpTimeNs, const bool include_current_partial_bucket, const bool erase_data,
+ const DumpLatency dumpLatency, std::set<string>* str_set,
+ android::util::ProtoOutputStream* protoOutput) {
+ VLOG("Unexpected call to onDumpReportLocked() in RestrictedEventMetricProducer");
+}
+
+void RestrictedEventMetricProducer::onMetricRemove() {
+ std::lock_guard<std::mutex> lock(mMutex);
+ if (!mIsMetricTableCreated) {
+ return;
+ }
+ deleteMetricTable();
+}
+
+void RestrictedEventMetricProducer::enforceRestrictedDataTtl(sqlite3* db,
+ const int64_t wallClockNs) {
+ int32_t ttlInDays = RestrictedPolicyManager::getInstance().getRestrictedCategoryTtl(
+ mRestrictedDataCategory);
+ int64_t ttlTime = wallClockNs - ttlInDays * NS_PER_DAY;
+ dbutils::flushTtl(db, mMetricId, ttlTime);
+}
+
+void RestrictedEventMetricProducer::clearPastBucketsLocked(const int64_t dumpTimeNs) {
+ VLOG("Unexpected call to clearPastBucketsLocked in RestrictedEventMetricProducer");
+}
+
+void RestrictedEventMetricProducer::dropDataLocked(const int64_t dropTimeNs) {
+ mLogEvents.clear();
+ mTotalSize = 0;
+ StatsdStats::getInstance().noteBucketDropped(mMetricId);
+}
+
+void RestrictedEventMetricProducer::flushRestrictedData() {
+ std::lock_guard<std::mutex> lock(mMutex);
+ if (mLogEvents.empty()) {
+ return;
+ }
+ int64_t flushStartNs = getElapsedRealtimeNs();
+ if (!mIsMetricTableCreated) {
+ if (!dbutils::isEventCompatible(mConfigKey, mMetricId, mLogEvents[0])) {
+ // Delete old data if schema changes
+ // TODO(b/268150038): report error to statsdstats
+ ALOGD("Detected schema change for metric %lld", (long long)mMetricId);
+ deleteMetricTable();
+ }
+ // TODO(b/271481944): add retry.
+ if (!dbutils::createTableIfNeeded(mConfigKey, mMetricId, mLogEvents[0])) {
+ ALOGE("Failed to create table for metric %lld", (long long)mMetricId);
+ StatsdStats::getInstance().noteRestrictedMetricTableCreationError(mConfigKey,
+ mMetricId);
+ return;
+ }
+ mIsMetricTableCreated = true;
+ }
+ string err;
+ if (!dbutils::insert(mConfigKey, mMetricId, mLogEvents, err)) {
+ ALOGE("Failed to insert logEvent to table for metric %lld. err=%s", (long long)mMetricId,
+ err.c_str());
+ StatsdStats::getInstance().noteRestrictedMetricInsertError(mConfigKey, mMetricId);
+ } else {
+ StatsdStats::getInstance().noteRestrictedMetricFlushLatency(
+ mConfigKey, mMetricId, getElapsedRealtimeNs() - flushStartNs);
+ }
+ mLogEvents.clear();
+ mTotalSize = 0;
+}
+
+bool RestrictedEventMetricProducer::writeMetricMetadataToProto(
+ metadata::MetricMetadata* metricMetadata) {
+ metricMetadata->set_metric_id(mMetricId);
+ metricMetadata->set_restricted_category(mRestrictedDataCategory);
+ return true;
+}
+
+void RestrictedEventMetricProducer::loadMetricMetadataFromProto(
+ const metadata::MetricMetadata& metricMetadata) {
+ mRestrictedDataCategory =
+ static_cast<StatsdRestrictionCategory>(metricMetadata.restricted_category());
+}
+
+void RestrictedEventMetricProducer::deleteMetricTable() {
+ if (!dbutils::deleteTable(mConfigKey, mMetricId)) {
+ StatsdStats::getInstance().noteRestrictedMetricTableDeletionError(mConfigKey, mMetricId);
+ VLOG("Failed to delete table for metric %lld", (long long)mMetricId);
+ }
+ mIsMetricTableCreated = false;
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/statsd/src/metrics/RestrictedEventMetricProducer.h b/statsd/src/metrics/RestrictedEventMetricProducer.h
new file mode 100644
index 0000000..74b0b80
--- /dev/null
+++ b/statsd/src/metrics/RestrictedEventMetricProducer.h
@@ -0,0 +1,67 @@
+#ifndef RESTRICTED_EVENT_METRIC_PRODUCER_H
+#define RESTRICTED_EVENT_METRIC_PRODUCER_H
+
+#include <gtest/gtest_prod.h>
+
+#include "EventMetricProducer.h"
+#include "utils/RestrictedPolicyManager.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class RestrictedEventMetricProducer : public EventMetricProducer {
+public:
+ RestrictedEventMetricProducer(
+ const ConfigKey& key, const EventMetric& eventMetric, const int conditionIndex,
+ const vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& wizard,
+ const uint64_t protoHash, const int64_t startTimeNs,
+ const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap = {},
+ const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
+ eventDeactivationMap = {},
+ const vector<int>& slicedStateAtoms = {},
+ const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap = {});
+
+ void onMetricRemove() override;
+
+ void enforceRestrictedDataTtl(sqlite3* db, const int64_t wallClockNs);
+
+ void flushRestrictedData() override;
+
+ bool writeMetricMetadataToProto(metadata::MetricMetadata* metricMetadata) override;
+
+ void loadMetricMetadataFromProto(const metadata::MetricMetadata& metricMetadata) override;
+
+ inline StatsdRestrictionCategory getRestrictionCategory() {
+ std::lock_guard<std::mutex> lock(mMutex);
+ return mRestrictedDataCategory;
+ }
+
+private:
+ void onMatchedLogEventInternalLocked(
+ const size_t matcherIndex, const MetricDimensionKey& eventKey,
+ const ConditionKey& conditionKey, bool condition, const LogEvent& event,
+ const std::map<int, HashableDimensionKey>& statePrimaryKeys) override;
+
+ void onDumpReportLocked(const int64_t dumpTimeNs, const bool include_current_partial_bucket,
+ const bool erase_data, const DumpLatency dumpLatency,
+ std::set<string>* str_set,
+ android::util::ProtoOutputStream* protoOutput) override;
+
+ void clearPastBucketsLocked(const int64_t dumpTimeNs) override;
+
+ void dropDataLocked(const int64_t dropTimeNs) override;
+
+ void deleteMetricTable();
+
+ bool mIsMetricTableCreated = false;
+
+ StatsdRestrictionCategory mRestrictedDataCategory;
+
+ vector<LogEvent> mLogEvents;
+};
+
+} // namespace statsd
+} // namespace os
+} // namespace android
+#endif // RESTRICTED_EVENT_METRIC_PRODUCER_H
diff --git a/statsd/src/metrics/parsing_utils/config_update_utils.cpp b/statsd/src/metrics/parsing_utils/config_update_utils.cpp
index c3972d9..e7f30bc 100644
--- a/statsd/src/metrics/parsing_utils/config_update_utils.cpp
+++ b/statsd/src/metrics/parsing_utils/config_update_utils.cpp
@@ -744,6 +744,12 @@
newMetricProducers.reserve(allMetricsCount);
optional<InvalidConfigReason> invalidConfigReason;
+ if (config.has_restricted_metrics_delegate_package_name() &&
+ allMetricsCount != config.event_metric_size()) {
+ ALOGE("Restricted metrics only support event metric");
+ return InvalidConfigReason(INVALID_CONFIG_REASON_RESTRICTED_METRIC_NOT_SUPPORTED);
+ }
+
// Construct map from metric id to metric activation index. The map will be used to determine
// the metric activation corresponding to a metric.
unordered_map<int64_t, int> metricToActivationMap;
@@ -1058,6 +1064,15 @@
newMetricProducers[i]->prepareFirstBucket();
}
}
+
+ for (const sp<MetricProducer>& oldMetricProducer : oldMetricProducers) {
+ const auto& it = newMetricProducerMap.find(oldMetricProducer->getMetricId());
+ // Consider metric removed if it's not present in newMetricProducerMap or it's replaced.
+ if (it == newMetricProducerMap.end() ||
+ replacedMetrics.find(oldMetricProducer->getMetricId()) != replacedMetrics.end()) {
+ oldMetricProducer->onMetricRemove();
+ }
+ }
return nullopt;
}
diff --git a/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp b/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
index be41c90..7d2ed03 100644
--- a/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
+++ b/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
@@ -37,6 +37,7 @@
#include "metrics/KllMetricProducer.h"
#include "metrics/MetricProducer.h"
#include "metrics/NumericValueMetricProducer.h"
+#include "metrics/RestrictedEventMetricProducer.h"
#include "state/StateManager.h"
#include "stats_util.h"
@@ -788,6 +789,11 @@
return nullopt;
}
+ if (config.has_restricted_metrics_delegate_package_name()) {
+ return {new RestrictedEventMetricProducer(
+ key, metric, conditionIndex, initialConditionCache, wizard, metricHash, timeBaseNs,
+ eventActivationMap, eventDeactivationMap)};
+ }
return {new EventMetricProducer(key, metric, conditionIndex, initialConditionCache, wizard,
metricHash, timeBaseNs, eventActivationMap,
eventDeactivationMap)};
@@ -1440,6 +1446,12 @@
allMetricProducers.reserve(allMetricsCount);
optional<InvalidConfigReason> invalidConfigReason;
+ if (config.has_restricted_metrics_delegate_package_name() &&
+ allMetricsCount != config.event_metric_size()) {
+ ALOGE("Restricted metrics only support event metric");
+ return InvalidConfigReason(INVALID_CONFIG_REASON_RESTRICTED_METRIC_NOT_SUPPORTED);
+ }
+
// Construct map from metric id to metric activation index. The map will be used to determine
// the metric activation corresponding to a metric.
unordered_map<int64_t, int> metricToActivationMap;
diff --git a/statsd/src/packages/UidMap.cpp b/statsd/src/packages/UidMap.cpp
index a6aef5b..a483247 100644
--- a/statsd/src/packages/UidMap.cpp
+++ b/statsd/src/packages/UidMap.cpp
@@ -71,7 +71,7 @@
const int FIELD_ID_CHANGE_NEW_VERSION_STRING_HASH = 10;
const int FIELD_ID_CHANGE_PREV_VERSION_STRING_HASH = 11;
-UidMap::UidMap() : mBytesUsed(0), mIncludeCertificateHash(false) {
+UidMap::UidMap() : mBytesUsed(0) {
}
UidMap::~UidMap() {}
@@ -351,7 +351,7 @@
FIELD_ID_SNAPSHOT_PACKAGE_INFO);
// Get installer index.
int installerIndex = -1;
- if (includeInstaller && mIncludeCertificateHash && installerIndices != nullptr) {
+ if (includeInstaller && installerIndices != nullptr) {
const auto& it = installerIndices->find(appData.installer);
if (it == installerIndices->end()) {
// We have not encountered this installer yet; add it to installerIndices.
@@ -400,17 +400,13 @@
}
}
- if (mIncludeCertificateHash) {
- const size_t dumpHashSize =
- truncatedCertificateHashSize <= appData.certificateHash.size()
- ? truncatedCertificateHashSize
- : appData.certificateHash.size();
- if (dumpHashSize > 0) {
- proto->write(
- FIELD_TYPE_BYTES | FIELD_ID_SNAPSHOT_PACKAGE_TRUNCATED_CERTIFICATE_HASH,
- reinterpret_cast<const char*>(appData.certificateHash.data()),
- dumpHashSize);
- }
+ const size_t dumpHashSize = truncatedCertificateHashSize <= appData.certificateHash.size()
+ ? truncatedCertificateHashSize
+ : appData.certificateHash.size();
+ if (dumpHashSize > 0) {
+ proto->write(FIELD_TYPE_BYTES | FIELD_ID_SNAPSHOT_PACKAGE_TRUNCATED_CERTIFICATE_HASH,
+ reinterpret_cast<const char*>(appData.certificateHash.data()),
+ dumpHashSize);
}
proto->write(FIELD_TYPE_INT64 | FIELD_ID_SNAPSHOT_PACKAGE_VERSION,
@@ -481,7 +477,7 @@
installers[index] = installer;
}
- if (includeInstaller && mIncludeCertificateHash) {
+ if (includeInstaller) {
// Write installer list; either strings or hashes.
for (const string& installerName : installers) {
if (str_set == nullptr) { // Strings not hashed
@@ -554,11 +550,6 @@
return results;
}
-void UidMap::setIncludeCertificateHash(const bool include) {
- lock_guard<mutex> lock(mMutex);
- mIncludeCertificateHash = include;
-}
-
// Note not all the following AIDs are used as uids. Some are used only for gids.
// It's ok to leave them in the map, but we won't ever see them in the log's uid field.
// App's uid starts from 10000, and will not overlap with the following AIDs.
diff --git a/statsd/src/packages/UidMap.h b/statsd/src/packages/UidMap.h
index e5cd224..43f9967 100644
--- a/statsd/src/packages/UidMap.h
+++ b/statsd/src/packages/UidMap.h
@@ -169,8 +169,6 @@
std::map<string, int>* installerIndices, std::set<string>* str_set,
ProtoOutputStream* proto) const;
- void setIncludeCertificateHash(const bool include);
-
private:
std::set<string> getAppNamesFromUidLocked(const int32_t& uid, bool returnNormalized) const;
string normalizeAppName(const string& appName) const;
@@ -227,9 +225,10 @@
// Cache the size of mOutput;
size_t mBytesUsed;
- bool mIncludeCertificateHash;
-
// Allows unit-test to access private methods.
+ FRIEND_TEST(RestrictedEventMetricE2eTest, TestRestrictedConfigUpdateDoesNotUpdateUidMap);
+ FRIEND_TEST(RestrictedEventMetricE2eTest,
+ TestRestrictedConfigUpdateAddsDelegateRemovesUidMapEntry);
FRIEND_TEST(UidMapTest, TestClearingOutput);
FRIEND_TEST(UidMapTest, TestRemovedAppRetained);
FRIEND_TEST(UidMapTest, TestRemovedAppOverGuardrail);
diff --git a/statsd/src/shell/ShellSubscriber.cpp b/statsd/src/shell/ShellSubscriber.cpp
index 8c9f57a..3b0c606 100644
--- a/statsd/src/shell/ShellSubscriber.cpp
+++ b/statsd/src/shell/ShellSubscriber.cpp
@@ -20,9 +20,12 @@
#include <android-base/file.h>
#include <inttypes.h>
+#include <utils/Timers.h>
#include "stats_log_util.h"
+using aidl::android::os::IStatsSubscriptionCallback;
+
namespace android {
namespace os {
namespace statsd {
@@ -31,6 +34,7 @@
{
std::unique_lock<std::mutex> lock(mMutex);
mClientSet.clear();
+ updateLogEventFilterLocked();
}
mThreadSleepCV.notify_one();
if (mThread.joinable()) {
@@ -38,10 +42,9 @@
}
}
-// Create new ShellSubscriberClient to manage a new subscription
bool ShellSubscriber::startNewSubscription(int in, int out, int64_t timeoutSec) {
std::unique_lock<std::mutex> lock(mMutex);
- ALOGD("ShellSubscriber: new subscription has come in");
+ VLOG("ShellSubscriber: new subscription has come in");
if (mClientSet.size() >= kMaxSubscriptions) {
ALOGE("ShellSubscriber: cannot have another active subscription. Current Subscriptions: "
"%zu. Limit: %zu",
@@ -49,12 +52,31 @@
return false;
}
- unique_ptr<ShellSubscriberClient> client = make_unique<ShellSubscriberClient>(
- in, out, timeoutSec, getElapsedRealtimeSec(), mUidMap, mPullerMgr);
- if (!client->readConfig()) return false;
+ return startNewSubscriptionLocked(ShellSubscriberClient::create(
+ in, out, timeoutSec, getElapsedRealtimeSec(), mUidMap, mPullerMgr));
+}
+
+bool ShellSubscriber::startNewSubscription(const vector<uint8_t>& subscriptionConfig,
+ const shared_ptr<IStatsSubscriptionCallback>& callback) {
+ std::unique_lock<std::mutex> lock(mMutex);
+ VLOG("ShellSubscriber: new subscription has come in");
+ if (mClientSet.size() >= kMaxSubscriptions) {
+ ALOGE("ShellSubscriber: cannot have another active subscription. Current Subscriptions: "
+ "%zu. Limit: %zu",
+ mClientSet.size(), kMaxSubscriptions);
+ return false;
+ }
+
+ return startNewSubscriptionLocked(ShellSubscriberClient::create(
+ subscriptionConfig, callback, getElapsedRealtimeSec(), mUidMap, mPullerMgr));
+}
+
+bool ShellSubscriber::startNewSubscriptionLocked(unique_ptr<ShellSubscriberClient> client) {
+ if (client == nullptr) return false;
// Add new valid client to the client set
mClientSet.insert(std::move(client));
+ updateLogEventFilterLocked();
// Only spawn one thread to manage pulling atoms and sending
// heartbeats.
@@ -65,18 +87,19 @@
}
mThread = thread([this] { pullAndSendHeartbeats(); });
}
+
return true;
}
// Sends heartbeat signals and sleeps between doing work
void ShellSubscriber::pullAndSendHeartbeats() {
- ALOGD("ShellSubscriber: helper thread starting");
+ VLOG("ShellSubscriber: helper thread starting");
std::unique_lock<std::mutex> lock(mMutex);
while (true) {
- int64_t sleepTimeMs = INT_MAX;
- int64_t nowSecs = getElapsedRealtimeSec();
- int64_t nowMillis = getElapsedRealtimeMillis();
- int64_t nowNanos = getElapsedRealtimeNs();
+ int64_t sleepTimeMs = 24 * 60 * 60 * 1000; // 24 hours.
+ const int64_t nowNanos = getElapsedRealtimeNs();
+ const int64_t nowMillis = nanoseconds_to_milliseconds(nowNanos);
+ const int64_t nowSecs = nanoseconds_to_seconds(nowNanos);
for (auto clientIt = mClientSet.begin(); clientIt != mClientSet.end();) {
int64_t subscriptionSleepMs =
(*clientIt)->pullAndSendHeartbeatsIfNeeded(nowSecs, nowMillis, nowNanos);
@@ -84,13 +107,14 @@
if ((*clientIt)->isAlive()) {
++clientIt;
} else {
- ALOGD("ShellSubscriber: removing client!");
+ VLOG("ShellSubscriber: removing client!");
clientIt = mClientSet.erase(clientIt);
+ updateLogEventFilterLocked();
}
}
if (mClientSet.empty()) {
mThreadAlive = false;
- ALOGD("ShellSubscriber: helper thread done!");
+ VLOG("ShellSubscriber: helper thread done!");
return;
}
VLOG("ShellSubscriber: helper thread sleeping for %" PRId64 "ms", sleepTimeMs);
@@ -99,18 +123,85 @@
}
void ShellSubscriber::onLogEvent(const LogEvent& event) {
+ // Skip if event is skipped
+ if (event.isParsedHeaderOnly()) {
+ return;
+ }
+ // Skip RestrictedLogEvents
+ if (event.isRestricted()) {
+ return;
+ }
std::unique_lock<std::mutex> lock(mMutex);
for (auto clientIt = mClientSet.begin(); clientIt != mClientSet.end();) {
(*clientIt)->onLogEvent(event);
if ((*clientIt)->isAlive()) {
++clientIt;
} else {
- ALOGD("ShellSubscriber: removing client!");
+ VLOG("ShellSubscriber: removing client!");
clientIt = mClientSet.erase(clientIt);
+ updateLogEventFilterLocked();
}
}
}
+void ShellSubscriber::flushSubscription(const shared_ptr<IStatsSubscriptionCallback>& callback) {
+ std::unique_lock<std::mutex> lock(mMutex);
+
+ // TODO(b/268822860): Consider storing callback clients in a map keyed by
+ // IStatsSubscriptionCallback to avoid this linear search.
+ for (auto clientIt = mClientSet.begin(); clientIt != mClientSet.end(); ++clientIt) {
+ if ((*clientIt)->hasCallback(callback)) {
+ if ((*clientIt)->isAlive()) {
+ (*clientIt)->flush();
+ } else {
+ VLOG("ShellSubscriber: removing client!");
+
+ // Erasing a value moves the iterator to the next value. The update expression also
+ // moves the iterator, skipping a value. This is fine because we do an early return
+ // before next iteration of the loop.
+ clientIt = mClientSet.erase(clientIt);
+ updateLogEventFilterLocked();
+ }
+ return;
+ }
+ }
+}
+
+void ShellSubscriber::unsubscribe(const shared_ptr<IStatsSubscriptionCallback>& callback) {
+ std::unique_lock<std::mutex> lock(mMutex);
+
+ // TODO(b/268822860): Consider storing callback clients in a map keyed by
+ // IStatsSubscriptionCallback to avoid this linear search.
+ for (auto clientIt = mClientSet.begin(); clientIt != mClientSet.end(); ++clientIt) {
+ if ((*clientIt)->hasCallback(callback)) {
+ if ((*clientIt)->isAlive()) {
+ (*clientIt)->onUnsubscribe();
+ }
+ VLOG("ShellSubscriber: removing client!");
+
+ // Erasing a value moves the iterator to the next value. The update expression also
+ // moves the iterator, skipping a value. This is fine because we do an early return
+ // before next iteration of the loop.
+ clientIt = mClientSet.erase(clientIt);
+ updateLogEventFilterLocked();
+ return;
+ }
+ }
+}
+
+void ShellSubscriber::updateLogEventFilterLocked() const {
+ VLOG("ShellSubscriber: Updating allAtomIds");
+ if (!mLogEventFilter) {
+ return;
+ }
+ LogEventFilter::AtomIdSet allAtomIds;
+ for (const auto& client : mClientSet) {
+ client->addAllAtomIds(allAtomIds);
+ }
+ VLOG("ShellSubscriber: Updating allAtomIds done. Total atoms %d", (int)allAtomIds.size());
+ mLogEventFilter->setAtomIds(std::move(allAtomIds), this);
+}
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/statsd/src/shell/ShellSubscriber.h b/statsd/src/shell/ShellSubscriber.h
index 3b25281..4ff8861 100644
--- a/statsd/src/shell/ShellSubscriber.h
+++ b/statsd/src/shell/ShellSubscriber.h
@@ -16,13 +16,15 @@
#pragma once
+#include <aidl/android/os/IStatsSubscriptionCallback.h>
+
#include <condition_variable>
#include <mutex>
#include <thread>
#include "external/StatsPullerManager.h"
#include "packages/UidMap.h"
-#include "src/shell/ShellSubscriberClient.h"
+#include "shell/ShellSubscriberClient.h"
#include "src/shell/shell_config.pb.h"
#include "src/statsd_config.pb.h"
@@ -54,15 +56,27 @@
*/
class ShellSubscriber : public virtual RefBase {
public:
- ShellSubscriber(sp<UidMap> uidMap, sp<StatsPullerManager> pullerMgr)
- : mUidMap(uidMap), mPullerMgr(pullerMgr){};
+ ShellSubscriber(sp<UidMap> uidMap, sp<StatsPullerManager> pullerMgr,
+ const std::shared_ptr<LogEventFilter>& logEventFilter)
+ : mUidMap(uidMap), mPullerMgr(pullerMgr), mLogEventFilter(logEventFilter){};
~ShellSubscriber();
+ // Create new ShellSubscriberClient with file descriptors to manage a new subscription.
bool startNewSubscription(int inFd, int outFd, int64_t timeoutSec);
+ // Create new ShellSubscriberClient with Binder callback to manage a new subscription.
+ bool startNewSubscription(
+ const vector<uint8_t>& subscriptionConfig,
+ const shared_ptr<aidl::android::os::IStatsSubscriptionCallback>& callback);
+
void onLogEvent(const LogEvent& event);
+ void flushSubscription(
+ const shared_ptr<aidl::android::os::IStatsSubscriptionCallback>& callback);
+
+ void unsubscribe(const shared_ptr<aidl::android::os::IStatsSubscriptionCallback>& callback);
+
static size_t getMaxSizeKb() {
return ShellSubscriberClient::getMaxSizeKb();
}
@@ -72,12 +86,19 @@
}
private:
+ bool startNewSubscriptionLocked(unique_ptr<ShellSubscriberClient> client);
+
void pullAndSendHeartbeats();
+ /* Tells LogEventFilter about atom ids to parse */
+ void updateLogEventFilterLocked() const;
+
sp<UidMap> mUidMap;
sp<StatsPullerManager> mPullerMgr;
+ std::shared_ptr<LogEventFilter> mLogEventFilter;
+
// Protects mClientSet, mThreadAlive, and ShellSubscriberClient
mutable std::mutex mMutex;
diff --git a/statsd/src/shell/ShellSubscriberClient.cpp b/statsd/src/shell/ShellSubscriberClient.cpp
index eb93772..546ed16 100644
--- a/statsd/src/shell/ShellSubscriberClient.cpp
+++ b/statsd/src/shell/ShellSubscriberClient.cpp
@@ -18,65 +18,41 @@
#include "ShellSubscriberClient.h"
+#include "FieldValue.h"
#include "matchers/matcher_util.h"
#include "stats_log_util.h"
using android::base::unique_fd;
using android::util::ProtoOutputStream;
+using Status = ::ndk::ScopedAStatus;
namespace android {
namespace os {
namespace statsd {
-const static int FIELD_ID_ATOM = 1;
+const static int FIELD_ID_SHELL_DATA__ATOM = 1;
+const static int FIELD_ID_SHELL_DATA__ELAPSED_TIMESTAMP_NANOS = 2;
-// Called by ShellSubscriber when a pushed event occurs
-void ShellSubscriberClient::onLogEvent(const LogEvent& event) {
- mProto.clear();
- for (const auto& matcher : mPushedMatchers) {
- if (matchesSimple(mUidMap, matcher, event)) {
- uint64_t atomToken = mProto.start(util::FIELD_TYPE_MESSAGE |
- util::FIELD_COUNT_REPEATED | FIELD_ID_ATOM);
- event.ToProto(mProto);
- mProto.end(atomToken);
- attemptWriteToPipeLocked(mProto.size());
- }
- }
-}
+struct ReadConfigResult {
+ vector<SimpleAtomMatcher> pushedMatchers;
+ vector<ShellSubscriberClient::PullInfo> pullInfo;
+};
-// Read and parse single config. There should only one config per input.
-bool ShellSubscriberClient::readConfig() {
- // Read the size of the config.
- size_t bufferSize;
- if (!android::base::ReadFully(mDupIn.get(), &bufferSize, sizeof(bufferSize))) {
- return false;
- }
-
- // Check bufferSize
- if (bufferSize > (kMaxSizeKb * 1024)) {
- ALOGE("ShellSubscriberClient: received config (%zu bytes) is larger than the max size (%zu "
- "bytes)",
- bufferSize, (kMaxSizeKb * 1024));
- return false;
- }
-
- // Read the config.
- vector<uint8_t> buffer(bufferSize);
- if (!android::base::ReadFully(mDupIn.get(), buffer.data(), bufferSize)) {
- return false;
- }
-
+// Read and parse single config. There should only one config in the input.
+static optional<ReadConfigResult> readConfig(const vector<uint8_t>& configBytes,
+ int64_t startTimeMs, int64_t minPullIntervalMs) {
// Parse the config.
ShellSubscription config;
- if (!config.ParseFromArray(buffer.data(), bufferSize)) {
- return false;
+ if (!config.ParseFromArray(configBytes.data(), configBytes.size())) {
+ ALOGE("ShellSubscriberClient: failed to parse the config");
+ return nullopt;
}
- // Update SubscriptionInfo with state from config
- for (const auto& pushed : config.pushed()) {
- mPushedMatchers.push_back(pushed);
- }
+ ReadConfigResult result;
+ result.pushedMatchers.assign(config.pushed().begin(), config.pushed().end());
+
+ vector<ShellSubscriberClient::PullInfo> pullInfo;
for (const auto& pulled : config.pulled()) {
vector<string> packages;
vector<int32_t> uids;
@@ -89,89 +65,245 @@
}
}
- mPulledInfo.emplace_back(pulled.matcher(), pulled.freq_millis(), packages, uids);
+ const int64_t pullIntervalMs = max(pulled.freq_millis(), minPullIntervalMs);
+ result.pullInfo.emplace_back(pulled.matcher(), startTimeMs, pullIntervalMs, packages, uids);
ALOGD("ShellSubscriberClient: adding matcher for pulled atom %d",
pulled.matcher().atom_id());
}
+ return result;
+}
+
+ShellSubscriberClient::PullInfo::PullInfo(const SimpleAtomMatcher& matcher, int64_t startTimeMs,
+ int64_t intervalMs,
+ const std::vector<std::string>& packages,
+ const std::vector<int32_t>& uids)
+ : mPullerMatcher(matcher),
+ mIntervalMs(intervalMs),
+ mPrevPullElapsedRealtimeMs(startTimeMs),
+ mPullPackages(packages),
+ mPullUids(uids) {
+}
+
+ShellSubscriberClient::ShellSubscriberClient(
+ int out, const std::shared_ptr<IStatsSubscriptionCallback>& callback,
+ const std::vector<SimpleAtomMatcher>& pushedMatchers,
+ const std::vector<PullInfo>& pulledInfo, int64_t timeoutSec, int64_t startTimeSec,
+ const sp<UidMap>& uidMap, const sp<StatsPullerManager>& pullerMgr)
+ : mUidMap(uidMap),
+ mPullerMgr(pullerMgr),
+ mDupOut(fcntl(out, F_DUPFD_CLOEXEC, 0)),
+ mPushedMatchers(pushedMatchers),
+ mPulledInfo(pulledInfo),
+ mCallback(callback),
+ mTimeoutSec(timeoutSec),
+ mStartTimeSec(startTimeSec),
+ mLastWriteMs(startTimeSec * 1000),
+ mCacheSize(0){};
+
+unique_ptr<ShellSubscriberClient> ShellSubscriberClient::create(
+ int in, int out, int64_t timeoutSec, int64_t startTimeSec, const sp<UidMap>& uidMap,
+ const sp<StatsPullerManager>& pullerMgr) {
+ // Read the size of the config.
+ size_t bufferSize;
+ if (!android::base::ReadFully(in, &bufferSize, sizeof(bufferSize))) {
+ return nullptr;
+ }
+
+ // Check bufferSize
+ if (bufferSize > (kMaxSizeKb * 1024)) {
+ ALOGE("ShellSubscriberClient: received config (%zu bytes) is larger than the max size (%zu "
+ "bytes)",
+ bufferSize, (kMaxSizeKb * 1024));
+ return nullptr;
+ }
+
+ // Read the config.
+ vector<uint8_t> buffer(bufferSize);
+ if (!android::base::ReadFully(in, buffer.data(), bufferSize)) {
+ ALOGE("ShellSubscriberClient: failed to read the config from file descriptor");
+ return nullptr;
+ }
+
+ const optional<ReadConfigResult> readConfigResult =
+ readConfig(buffer, startTimeSec * 1000, /* minPullIntervalMs */ 0);
+ if (!readConfigResult.has_value()) {
+ return nullptr;
+ }
+
+ return make_unique<ShellSubscriberClient>(
+ out, /*callback=*/nullptr, readConfigResult->pushedMatchers, readConfigResult->pullInfo,
+ timeoutSec, startTimeSec, uidMap, pullerMgr);
+}
+
+unique_ptr<ShellSubscriberClient> ShellSubscriberClient::create(
+ const vector<uint8_t>& subscriptionConfig,
+ const shared_ptr<IStatsSubscriptionCallback>& callback, int64_t startTimeSec,
+ const sp<UidMap>& uidMap, const sp<StatsPullerManager>& pullerMgr) {
+ if (callback == nullptr) {
+ ALOGE("ShellSubscriberClient: received nullptr callback");
+ return nullptr;
+ }
+
+ if (subscriptionConfig.size() > (kMaxSizeKb * 1024)) {
+ ALOGE("ShellSubscriberClient: received config (%zu bytes) is larger than the max size (%zu "
+ "bytes)",
+ subscriptionConfig.size(), (kMaxSizeKb * 1024));
+ return nullptr;
+ }
+
+ const optional<ReadConfigResult> readConfigResult =
+ readConfig(subscriptionConfig, startTimeSec * 1000,
+ ShellSubscriberClient::kMinCallbackPullIntervalMs);
+ if (!readConfigResult.has_value()) {
+ return nullptr;
+ }
+
+ return make_unique<ShellSubscriberClient>(
+ /*out=*/-1, callback, readConfigResult->pushedMatchers, readConfigResult->pullInfo,
+ /*timeoutSec=*/-1, startTimeSec, uidMap, pullerMgr);
+}
+
+bool ShellSubscriberClient::writeEventToProtoIfMatched(const LogEvent& event,
+ const SimpleAtomMatcher& matcher,
+ const sp<UidMap>& uidMap) {
+ if (!matchesSimple(uidMap, matcher, event)) {
+ return false;
+ }
+
+ // Cache atom event in mProtoOut.
+ uint64_t atomToken = mProtoOut.start(util::FIELD_TYPE_MESSAGE | util::FIELD_COUNT_REPEATED |
+ FIELD_ID_SHELL_DATA__ATOM);
+ event.ToProto(mProtoOut);
+ mProtoOut.end(atomToken);
+
+ const int64_t timestampNs = truncateTimestampIfNecessary(event);
+ mProtoOut.write(util::FIELD_TYPE_INT64 | util::FIELD_COUNT_REPEATED |
+ FIELD_ID_SHELL_DATA__ELAPSED_TIMESTAMP_NANOS,
+ static_cast<long long>(timestampNs));
+
+ // Update byte size of cached data.
+ mCacheSize += getSize(event.getValues()) + sizeof(timestampNs);
+
return true;
}
+// Called by ShellSubscriber when a pushed event occurs
+void ShellSubscriberClient::onLogEvent(const LogEvent& event) {
+ for (const auto& matcher : mPushedMatchers) {
+ if (writeEventToProtoIfMatched(event, matcher, mUidMap)) {
+ flushProtoIfNeeded();
+ break;
+ }
+ }
+}
+
+void ShellSubscriberClient::flushProtoIfNeeded() {
+ if (mCallback == nullptr) { // Using file descriptor.
+ triggerFdFlush();
+ } else if (mCacheSize >= kMaxCacheSizeBytes) { // Using callback.
+ // Flush data if cache is full.
+ triggerCallback(StatsSubscriptionCallbackReason::STATSD_INITIATED);
+ }
+}
+
+int64_t ShellSubscriberClient::pullIfNeeded(int64_t nowSecs, int64_t nowMillis, int64_t nowNanos) {
+ int64_t sleepTimeMs = 24 * 60 * 60 * 1000; // 24 hours.
+ for (PullInfo& pullInfo : mPulledInfo) {
+ if (pullInfo.mPrevPullElapsedRealtimeMs + pullInfo.mIntervalMs <= nowMillis) {
+ vector<int32_t> uids;
+ getUidsForPullAtom(&uids, pullInfo);
+
+ vector<shared_ptr<LogEvent>> data;
+ mPullerMgr->Pull(pullInfo.mPullerMatcher.atom_id(), uids, nowNanos, &data);
+ VLOG("ShellSubscriberClient: pulled %zu atoms with id %d", data.size(),
+ pullInfo.mPullerMatcher.atom_id());
+
+ writePulledAtomsLocked(data, pullInfo.mPullerMatcher);
+ pullInfo.mPrevPullElapsedRealtimeMs = nowMillis;
+ }
+
+ // Determine how long to sleep before doing more work.
+ const int64_t nextPullTimeMs = pullInfo.mPrevPullElapsedRealtimeMs + pullInfo.mIntervalMs;
+
+ const int64_t timeBeforePullMs =
+ nextPullTimeMs - nowMillis; // guaranteed to be non-negative
+ sleepTimeMs = min(sleepTimeMs, timeBeforePullMs);
+ }
+ return sleepTimeMs;
+}
+
// The pullAndHeartbeat threads sleep for the minimum time
// among all clients' input
int64_t ShellSubscriberClient::pullAndSendHeartbeatsIfNeeded(int64_t nowSecs, int64_t nowMillis,
int64_t nowNanos) {
- int64_t sleepTimeMs = kMsBetweenHeartbeats;
-
- if ((nowSecs - mStartTimeSec >= mTimeoutSec) && (mTimeoutSec > 0)) {
- mClientAlive = false;
- return sleepTimeMs;
- }
-
- for (PullInfo& pullInfo : mPulledInfo) {
- if (pullInfo.mPrevPullElapsedRealtimeMs + pullInfo.mInterval >= nowMillis) {
- continue;
+ int64_t sleepTimeMs;
+ if (mCallback == nullptr) { // File descriptor subscription
+ if ((nowSecs - mStartTimeSec >= mTimeoutSec) && (mTimeoutSec > 0)) {
+ mClientAlive = false;
+ return kMsBetweenHeartbeats;
}
- vector<int32_t> uids;
- getUidsForPullAtom(&uids, pullInfo);
+ sleepTimeMs = min(kMsBetweenHeartbeats, pullIfNeeded(nowSecs, nowMillis, nowNanos));
- vector<std::shared_ptr<LogEvent>> data;
- mPullerMgr->Pull(pullInfo.mPullerMatcher.atom_id(), uids, nowNanos, &data);
- VLOG("ShellSubscriberClient: pulled %zu atoms with id %d", data.size(),
- pullInfo.mPullerMatcher.atom_id());
- writePulledAtomsLocked(data, pullInfo.mPullerMatcher);
+ // Send a heartbeat consisting of data size of 0, if
+ // the user hasn't recently received data from statsd. When it receives the data size of 0,
+ // the user will not expect any atoms and recheck whether the subscription should end.
+ if (nowMillis - mLastWriteMs >= kMsBetweenHeartbeats) {
+ triggerFdFlush();
+ if (!mClientAlive) return kMsBetweenHeartbeats;
+ }
- pullInfo.mPrevPullElapsedRealtimeMs = nowMillis;
+ int64_t timeBeforeHeartbeat = mLastWriteMs + kMsBetweenHeartbeats - nowMillis;
+ sleepTimeMs = min(sleepTimeMs, timeBeforeHeartbeat);
+ } else { // Callback subscription.
+ sleepTimeMs = min(kMsBetweenCallbacks, pullIfNeeded(nowSecs, nowMillis, nowNanos));
+
+ if (mCacheSize > 0 && nowMillis - mLastWriteMs >= kMsBetweenCallbacks) {
+ // Flush data if cache has kept data for longer than kMsBetweenCallbacks.
+ triggerCallback(StatsSubscriptionCallbackReason::STATSD_INITIATED);
+ }
+
+ // Cache should be flushed kMsBetweenCallbacks after mLastWrite.
+ const int64_t timeToCallbackMs = mLastWriteMs + kMsBetweenCallbacks - nowMillis;
+
+ // For callback subscriptions, ensure minimum sleep time is at least
+ // kMinCallbackSleepIntervalMs. Even if there is less than kMinCallbackSleepIntervalMs left
+ // before next pull time, sleep for at least kMinCallbackSleepIntervalMs. This has the
+ // effect of multiple pulled atoms that have a pull within kMinCallbackSleepIntervalMs from
+ // now to have their pulls batched together, mitigating frequent wakeups of the puller
+ // thread.
+ sleepTimeMs = max(kMinCallbackSleepIntervalMs, min(sleepTimeMs, timeToCallbackMs));
}
-
- // Send a heartbeat, consisting of a data size of 0, if the user hasn't recently received
- // data from statsd. When it receives the data size of 0, the user will not expect any
- // atoms and recheck whether the subscription should end.
- if (nowMillis - mLastWriteMs >= kMsBetweenHeartbeats) {
- attemptWriteToPipeLocked(/*dataSize=*/0);
- if (!mClientAlive) return kMsBetweenHeartbeats;
- }
-
- // Determine how long to sleep before doing more work.
- for (PullInfo& pullInfo : mPulledInfo) {
- int64_t nextPullTime = pullInfo.mPrevPullElapsedRealtimeMs + pullInfo.mInterval;
- int64_t timeBeforePull = nextPullTime - nowMillis; // guaranteed to be non-negative
- sleepTimeMs = std::min(sleepTimeMs, timeBeforePull);
- }
- int64_t timeBeforeHeartbeat = (mLastWriteMs + kMsBetweenHeartbeats) - nowMillis;
- sleepTimeMs = std::min(sleepTimeMs, timeBeforeHeartbeat);
return sleepTimeMs;
}
-void ShellSubscriberClient::writePulledAtomsLocked(const vector<std::shared_ptr<LogEvent>>& data,
+void ShellSubscriberClient::writePulledAtomsLocked(const vector<shared_ptr<LogEvent>>& data,
const SimpleAtomMatcher& matcher) {
- mProto.clear();
- int count = 0;
- for (const auto& event : data) {
- if (matchesSimple(mUidMap, matcher, *event)) {
- count++;
- uint64_t atomToken = mProto.start(util::FIELD_TYPE_MESSAGE |
- util::FIELD_COUNT_REPEATED | FIELD_ID_ATOM);
- event->ToProto(mProto);
- mProto.end(atomToken);
+ bool hasData = false;
+ for (const shared_ptr<LogEvent>& event : data) {
+ if (writeEventToProtoIfMatched(*event, matcher, mUidMap)) {
+ hasData = true;
}
}
- if (count > 0) attemptWriteToPipeLocked(mProto.size());
+ if (hasData) {
+ flushProtoIfNeeded();
+ }
}
-// Tries to write the atom encoded in mProto to the pipe. If the write fails
+// Tries to write the atom encoded in mProtoOut to the pipe. If the write fails
// because the read end of the pipe has closed, change the client status so
// the manager knows the subscription is no longer active
-void ShellSubscriberClient::attemptWriteToPipeLocked(size_t dataSize) {
+void ShellSubscriberClient::attemptWriteToPipeLocked() {
+ const size_t dataSize = mProtoOut.size();
// First, write the payload size.
if (!android::base::WriteFully(mDupOut, &dataSize, sizeof(dataSize))) {
mClientAlive = false;
return;
}
// Then, write the payload if this is not just a heartbeat.
- if (dataSize > 0 && !mProto.flush(mDupOut.get())) {
+ if (dataSize > 0 && !mProtoOut.flush(mDupOut.get())) {
mClientAlive = false;
return;
}
@@ -188,6 +320,45 @@
uids->push_back(DEFAULT_PULL_UID);
}
+void ShellSubscriberClient::clearCache() {
+ mProtoOut.clear();
+ mCacheSize = 0;
+}
+
+void ShellSubscriberClient::triggerFdFlush() {
+ attemptWriteToPipeLocked();
+ clearCache();
+}
+
+void ShellSubscriberClient::triggerCallback(StatsSubscriptionCallbackReason reason) {
+ // Invoke Binder callback with cached event data.
+ vector<uint8_t> payloadBytes;
+ mProtoOut.serializeToVector(&payloadBytes);
+ const Status status = mCallback->onSubscriptionData(reason, payloadBytes);
+ if (status.getStatus() == STATUS_DEAD_OBJECT &&
+ status.getExceptionCode() == EX_TRANSACTION_FAILED) {
+ mClientAlive = false;
+ return;
+ }
+
+ mLastWriteMs = getElapsedRealtimeMillis();
+ clearCache();
+}
+
+void ShellSubscriberClient::flush() {
+ triggerCallback(StatsSubscriptionCallbackReason::FLUSH_REQUESTED);
+}
+
+void ShellSubscriberClient::onUnsubscribe() {
+ triggerCallback(StatsSubscriptionCallbackReason::SUBSCRIPTION_ENDED);
+}
+
+void ShellSubscriberClient::addAllAtomIds(LogEventFilter::AtomIdSet& allAtomIds) const {
+ for (const auto& matcher : mPushedMatchers) {
+ allAtomIds.insert(matcher.atom_id());
+ }
+}
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/statsd/src/shell/ShellSubscriberClient.h b/statsd/src/shell/ShellSubscriberClient.h
index 31ec1d4..9d03cb4 100644
--- a/statsd/src/shell/ShellSubscriberClient.h
+++ b/statsd/src/shell/ShellSubscriberClient.h
@@ -16,16 +16,24 @@
#pragma once
+#include <aidl/android/os/IStatsSubscriptionCallback.h>
+#include <aidl/android/os/StatsSubscriptionCallbackReason.h>
#include <android-base/file.h>
#include <android/util/ProtoOutputStream.h>
#include <private/android_filesystem_config.h>
+#include <memory>
+
#include "external/StatsPullerManager.h"
#include "logd/LogEvent.h"
#include "packages/UidMap.h"
+#include "socket/LogEventFilter.h"
#include "src/shell/shell_config.pb.h"
#include "src/statsd_config.pb.h"
+using aidl::android::os::IStatsSubscriptionCallback;
+using aidl::android::os::StatsSubscriptionCallbackReason;
+
namespace android {
namespace os {
namespace statsd {
@@ -34,81 +42,123 @@
// guarded by the mutex in ShellSubscriber.h
class ShellSubscriberClient {
public:
- ShellSubscriberClient(int in, int out, int64_t timeoutSec, int64_t startTimeSec,
- sp<UidMap> uidMap, sp<StatsPullerManager> pullerMgr)
- : mUidMap(uidMap),
- mPullerMgr(pullerMgr),
- mDupIn(dup(in)),
- mDupOut(dup(out)),
- mTimeoutSec(timeoutSec),
- mStartTimeSec(startTimeSec){};
+ struct PullInfo {
+ PullInfo(const SimpleAtomMatcher& matcher, int64_t startTimeMs, int64_t interval,
+ const std::vector<std::string>& packages, const std::vector<int32_t>& uids);
+
+ const SimpleAtomMatcher mPullerMatcher;
+ const int64_t mIntervalMs;
+ int64_t mPrevPullElapsedRealtimeMs;
+ const std::vector<std::string> mPullPackages;
+ const std::vector<int32_t> mPullUids;
+ };
+
+ static std::unique_ptr<ShellSubscriberClient> create(int in, int out, int64_t timeoutSec,
+ int64_t startTimeSec,
+ const sp<UidMap>& uidMap,
+ const sp<StatsPullerManager>& pullerMgr);
+
+ static std::unique_ptr<ShellSubscriberClient> create(
+ const std::vector<uint8_t>& subscriptionConfig,
+ const std::shared_ptr<IStatsSubscriptionCallback>& callback, int64_t startTimeSec,
+ const sp<UidMap>& uidMap, const sp<StatsPullerManager>& pullerMgr);
+
+ // Should only be called by the create() factory.
+ explicit ShellSubscriberClient(int out,
+ const std::shared_ptr<IStatsSubscriptionCallback>& callback,
+ const std::vector<SimpleAtomMatcher>& pushedMatchers,
+ const std::vector<PullInfo>& pulledInfo, int64_t timeoutSec,
+ int64_t startTimeSec, const sp<UidMap>& uidMap,
+ const sp<StatsPullerManager>& pullerMgr);
void onLogEvent(const LogEvent& event);
- bool readConfig();
-
int64_t pullAndSendHeartbeatsIfNeeded(int64_t nowSecs, int64_t nowMillis, int64_t nowNanos);
+ // Should only be called when mCallback is not nullptr.
+ void flush();
+
+ // Should only be called when mCallback is not nullptr.
+ void onUnsubscribe();
+
bool isAlive() const {
return mClientAlive;
}
+ bool hasCallback(const std::shared_ptr<IStatsSubscriptionCallback>& callback) const {
+ return mCallback != nullptr && callback != nullptr &&
+ callback->asBinder() == mCallback->asBinder();
+ }
+
static size_t getMaxSizeKb() {
return kMaxSizeKb;
}
+ void addAllAtomIds(LogEventFilter::AtomIdSet& allAtomIds) const;
+
+ // Minimum pull interval for callback subscriptions.
+ static constexpr int64_t kMinCallbackPullIntervalMs = 60'000; // 60 seconds.
+
+ // Minimum sleep for the pull thread for callback subscriptions.
+ static constexpr int64_t kMinCallbackSleepIntervalMs = 2000; // 2 seconds.
private:
- struct PullInfo {
- PullInfo(const SimpleAtomMatcher& matcher, int64_t interval,
- const std::vector<std::string>& packages, const std::vector<int32_t>& uids)
- : mPullerMatcher(matcher),
- mInterval(interval),
- mPrevPullElapsedRealtimeMs(0),
- mPullPackages(packages),
- mPullUids(uids) {
- }
- SimpleAtomMatcher mPullerMatcher;
- int64_t mInterval;
- int64_t mPrevPullElapsedRealtimeMs;
- std::vector<std::string> mPullPackages;
- std::vector<int32_t> mPullUids;
- };
+ int64_t pullIfNeeded(int64_t nowSecs, int64_t nowMillis, int64_t nowNanos);
void writePulledAtomsLocked(const vector<std::shared_ptr<LogEvent>>& data,
const SimpleAtomMatcher& matcher);
- void attemptWriteToPipeLocked(size_t dataSize);
+ void attemptWriteToPipeLocked();
void getUidsForPullAtom(vector<int32_t>* uids, const PullInfo& pullInfo);
+ void flushProtoIfNeeded();
+
+ bool writeEventToProtoIfMatched(const LogEvent& event, const SimpleAtomMatcher& matcher,
+ const sp<UidMap>& uidMap);
+
+ void clearCache();
+
+ void triggerFdFlush();
+
+ void triggerCallback(StatsSubscriptionCallbackReason reason);
+
const int32_t DEFAULT_PULL_UID = AID_SYSTEM;
- sp<UidMap> mUidMap;
+ const sp<UidMap> mUidMap;
- sp<StatsPullerManager> mPullerMgr;
-
- android::base::unique_fd mDupIn;
+ const sp<StatsPullerManager> mPullerMgr;
android::base::unique_fd mDupOut;
- android::util::ProtoOutputStream mProto;
-
- std::vector<SimpleAtomMatcher> mPushedMatchers;
+ const std::vector<SimpleAtomMatcher> mPushedMatchers;
std::vector<PullInfo> mPulledInfo;
- int64_t mTimeoutSec;
+ std::shared_ptr<IStatsSubscriptionCallback> mCallback;
- int64_t mStartTimeSec;
+ const int64_t mTimeoutSec;
+
+ const int64_t mStartTimeSec;
bool mClientAlive = true;
- int64_t mLastWriteMs = 0;
+ int64_t mLastWriteMs;
+
+ // Stores Atom proto messages for events along with their respective timestamps.
+ ProtoOutputStream mProtoOut;
+
+ // Stores the total approximate encoded proto byte-size for cached Atom events in
+ // mEventTimestampNs and mProtoOut.
+ size_t mCacheSize;
static constexpr int64_t kMsBetweenHeartbeats = 1000;
// Cap the buffer size of configs to guard against bad allocations
static constexpr size_t kMaxSizeKb = 50;
+
+ static constexpr size_t kMaxCacheSizeBytes = 2 * 1024; // 2 KB
+
+ static constexpr int64_t kMsBetweenCallbacks = 70'000; // 70 seconds.
};
} // namespace statsd
diff --git a/statsd/src/shell/shell_config.proto b/statsd/src/shell/shell_config.proto
index 9c18c4f..6b2890c 100644
--- a/statsd/src/shell/shell_config.proto
+++ b/statsd/src/shell/shell_config.proto
@@ -27,7 +27,7 @@
optional SimpleAtomMatcher matcher = 1;
/* gap between two pulls in milliseconds */
- optional int32 freq_millis = 2;
+ optional int64 freq_millis = 2;
/* Packages that the pull is requested from */
repeated string packages = 3;
@@ -36,4 +36,4 @@
message ShellSubscription {
repeated SimpleAtomMatcher pushed = 1;
repeated PulledAtomSubscription pulled = 2;
-}
\ No newline at end of file
+}
diff --git a/statsd/src/shell/shell_data.proto b/statsd/src/shell/shell_data.proto
index ec41cbc..fdfbcc3 100644
--- a/statsd/src/shell/shell_data.proto
+++ b/statsd/src/shell/shell_data.proto
@@ -26,4 +26,5 @@
// The output of shell subscription, including both pulled and pushed subscriptions.
message ShellData {
repeated Atom atom = 1;
+ repeated int64 elapsed_timestamp_nanos = 2 [packed = true];
}
diff --git a/statsd/src/socket/LogEventFilter.h b/statsd/src/socket/LogEventFilter.h
new file mode 100644
index 0000000..aec91ef
--- /dev/null
+++ b/statsd/src/socket/LogEventFilter.h
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#pragma once
+
+#include <gtest/gtest_prod.h>
+
+#include <atomic>
+#include <mutex>
+#include <unordered_map>
+#include <unordered_set>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+/**
+ * Templating is for benchmarks only
+ *
+ * Based on benchmarks the more fast container to be used for atom ids filtering
+ * is unordered_set<int>
+ * #BM_LogEventFilterUnorderedSet 391208 ns 390086 ns 1793
+ * #BM_LogEventFilterUnorderedSet2Consumers 1293527 ns 1289326 ns 543
+ * #BM_LogEventFilterSet 613362 ns 611259 ns 1146
+ * #BM_LogEventFilterSet2Consumers 1859397 ns 1854193 ns 378
+ *
+ * See @LogEventFilter definition below
+ */
+template <typename T>
+class LogEventFilterGeneric {
+public:
+ virtual ~LogEventFilterGeneric() = default;
+
+ virtual void setFilteringEnabled(bool isEnabled) {
+ mLogsFilteringEnabled = isEnabled;
+ }
+
+ bool getFilteringEnabled() const {
+ return mLogsFilteringEnabled;
+ }
+
+ /**
+ * @brief Tests atom id with list of interesting atoms
+ * If Logs filtering is disabled - assume all atoms in use
+ * Most of the time should be non-blocking call - only in case when setAtomIds() was
+ * called the call will be blocking due to atom list needs to be synced up
+ * @param atomId
+ * @return true if atom is used by any of consumer or filtering is disabled
+ */
+ virtual bool isAtomInUse(int atomId) const {
+ if (!mLogsFilteringEnabled) {
+ return true;
+ }
+
+ // check if there is an updated set of interesting atom ids
+ if (mLocalSetUpdateCounter != mSetUpdateCounter.load(std::memory_order_relaxed)) {
+ std::lock_guard<std::mutex> guard(mTagIdsMutex);
+ mLocalSetUpdateCounter = mSetUpdateCounter.load(std::memory_order_relaxed);
+ mLocalTagIds.swap(mTagIds);
+ }
+ return mLocalTagIds.find(atomId) != mLocalTagIds.end();
+ }
+
+ typedef const void* ConsumerId;
+
+ typedef T AtomIdSet;
+ /**
+ * @brief Set the Atom Ids object
+ *
+ * @param tagIds set of atoms ids
+ * @param consumer used to differentiate the consumers to form proper superset of ids
+ */
+ virtual void setAtomIds(AtomIdSet tagIds, ConsumerId consumer) {
+ std::lock_guard lock(mTagIdsMutex);
+ // update ids list from consumer
+ if (tagIds.size() == 0) {
+ mTagIdsPerConsumer.erase(consumer);
+ } else {
+ mTagIdsPerConsumer[consumer].swap(tagIds);
+ }
+ // populate the superset incorporating list of distinct atom ids from all consumers
+ mTagIds.clear();
+ for (const auto& [_, atomIds] : mTagIdsPerConsumer) {
+ mTagIds.insert(atomIds.begin(), atomIds.end());
+ }
+ mSetUpdateCounter.fetch_add(1, std::memory_order_relaxed);
+ }
+
+private:
+ std::atomic_bool mLogsFilteringEnabled = true;
+ std::atomic_int mSetUpdateCounter;
+ mutable int mLocalSetUpdateCounter;
+
+ mutable std::mutex mTagIdsMutex;
+ std::unordered_map<ConsumerId, AtomIdSet> mTagIdsPerConsumer;
+ mutable AtomIdSet mTagIds;
+ mutable AtomIdSet mLocalTagIds;
+
+ friend class LogEventFilterTest;
+
+ FRIEND_TEST(LogEventFilterTest, TestEmptyFilter);
+ FRIEND_TEST(LogEventFilterTest, TestRemoveNonExistingEmptyFilter);
+ FRIEND_TEST(LogEventFilterTest, TestEmptyFilterDisabled);
+ FRIEND_TEST(LogEventFilterTest, TestEmptyFilterDisabledSetter);
+ FRIEND_TEST(LogEventFilterTest, TestNonEmptyFilterFullOverlap);
+ FRIEND_TEST(LogEventFilterTest, TestNonEmptyFilterPartialOverlap);
+ FRIEND_TEST(LogEventFilterTest, TestNonEmptyFilterDisabled);
+ FRIEND_TEST(LogEventFilterTest, TestNonEmptyFilterDisabledPartialOverlap);
+ FRIEND_TEST(LogEventFilterTest, TestMultipleConsumerOverlapIds);
+ FRIEND_TEST(LogEventFilterTest, TestMultipleConsumerNonOverlapIds);
+ FRIEND_TEST(LogEventFilterTest, TestMultipleConsumerOverlapIdsRemoved);
+ FRIEND_TEST(LogEventFilterTest, TestMultipleConsumerNonOverlapIdsRemoved);
+ FRIEND_TEST(LogEventFilterTest, TestMultipleConsumerEmptyFilter);
+};
+
+typedef LogEventFilterGeneric<std::unordered_set<int>> LogEventFilter;
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/statsd/src/socket/StatsSocketListener.cpp b/statsd/src/socket/StatsSocketListener.cpp
index 5764753..5873df3 100644
--- a/statsd/src/socket/StatsSocketListener.cpp
+++ b/statsd/src/socket/StatsSocketListener.cpp
@@ -36,11 +36,11 @@
namespace os {
namespace statsd {
-StatsSocketListener::StatsSocketListener(std::shared_ptr<LogEventQueue> queue)
- : SocketListener(getLogSocket(), false /*start listen*/), mQueue(std::move(queue)) {
-}
-
-StatsSocketListener::~StatsSocketListener() {
+StatsSocketListener::StatsSocketListener(std::shared_ptr<LogEventQueue> queue,
+ const std::shared_ptr<LogEventFilter>& logEventFilter)
+ : SocketListener(getLogSocket(), false /*start listen*/),
+ mQueue(std::move(queue)),
+ mLogEventFilter(logEventFilter) {
}
bool StatsSocketListener::onDataAvailable(SocketClient* cli) {
@@ -120,22 +120,38 @@
}
// move past the 4-byte StatsEventTag
- uint8_t* msg = ptr + sizeof(uint32_t);
- uint32_t len = n - sizeof(uint32_t);
- uint32_t uid = cred->uid;
- uint32_t pid = cred->pid;
+ const uint8_t* msg = ptr + sizeof(uint32_t);
+ const uint32_t len = n - sizeof(uint32_t);
+ const uint32_t uid = cred->uid;
+ const uint32_t pid = cred->pid;
- int64_t oldestTimestamp;
- std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(uid, pid);
- logEvent->parseBuffer(msg, len);
- const int32_t atomId = logEvent->GetTagId();
- if (!mQueue->push(std::move(logEvent), &oldestTimestamp)) {
- StatsdStats::getInstance().noteEventQueueOverflow(oldestTimestamp, atomId);
- }
+ processMessage(msg, len, uid, pid, mQueue, mLogEventFilter);
return true;
}
+void StatsSocketListener::processMessage(const uint8_t* msg, uint32_t len, uint32_t uid,
+ uint32_t pid, const std::shared_ptr<LogEventQueue>& queue,
+ const std::shared_ptr<LogEventFilter>& filter) {
+ std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(uid, pid);
+
+ if (filter && filter->getFilteringEnabled()) {
+ const LogEvent::BodyBufferInfo bodyInfo = logEvent->parseHeader(msg, len);
+ if (filter->isAtomInUse(logEvent->GetTagId())) {
+ logEvent->parseBody(bodyInfo);
+ }
+ } else {
+ logEvent->parseBuffer(msg, len);
+ }
+
+ const int32_t atomId = logEvent->GetTagId();
+ const bool isAtomSkipped = logEvent->isParsedHeaderOnly();
+ int64_t oldestTimestamp;
+ if (!queue->push(std::move(logEvent), &oldestTimestamp)) {
+ StatsdStats::getInstance().noteEventQueueOverflow(oldestTimestamp, atomId, isAtomSkipped);
+ }
+}
+
int StatsSocketListener::getLogSocket() {
static const char socketName[] = "statsdw";
int sock = android_get_control_socket(socketName);
diff --git a/statsd/src/socket/StatsSocketListener.h b/statsd/src/socket/StatsSocketListener.h
index 43822f3..74a338e 100644
--- a/statsd/src/socket/StatsSocketListener.h
+++ b/statsd/src/socket/StatsSocketListener.h
@@ -15,8 +15,11 @@
*/
#pragma once
+#include <gtest/gtest_prod.h>
#include <sysutils/SocketListener.h>
#include <utils/RefBase.h>
+
+#include "LogEventFilter.h"
#include "logd/LogEventQueue.h"
// DEFAULT_OVERFLOWUID is defined in linux/highuid.h, which is not part of
@@ -35,20 +38,53 @@
class StatsSocketListener : public SocketListener, public virtual RefBase {
public:
- explicit StatsSocketListener(std::shared_ptr<LogEventQueue> queue);
+ explicit StatsSocketListener(std::shared_ptr<LogEventQueue> queue,
+ const std::shared_ptr<LogEventFilter>& logEventFilter);
- virtual ~StatsSocketListener();
+ virtual ~StatsSocketListener() = default;
protected:
- virtual bool onDataAvailable(SocketClient* cli);
+ bool onDataAvailable(SocketClient* cli) override;
private:
static int getLogSocket();
+
+ /**
+ * @brief Helper API to parse buffer, make the LogEvent & submit it into the queue
+ * Created as a separate API to be easily tested without StatsSocketListener instance
+ *
+ * @param msg buffer to parse
+ * @param len size of buffer in bytes
+ * @param uid arguments for LogEvent constructor
+ * @param pid arguments for LogEvent constructor
+ * @param queue queue to submit the event
+ * @param filter to be used for event evaluation
+ */
+ static void processMessage(const uint8_t* msg, uint32_t len, uint32_t uid, uint32_t pid,
+ const std::shared_ptr<LogEventQueue>& queue,
+ const std::shared_ptr<LogEventFilter>& filter);
+
/**
* Who is going to get the events when they're read.
*/
std::shared_ptr<LogEventQueue> mQueue;
+
+ std::shared_ptr<LogEventFilter> mLogEventFilter;
+
+ friend class SocketParseMessageTest;
+ friend void generateAtomLogging(const std::shared_ptr<LogEventQueue>& queue,
+ const std::shared_ptr<LogEventFilter>& filter, int eventCount,
+ int startAtomId);
+
+ FRIEND_TEST(SocketParseMessageTestNoFiltering, TestProcessMessageNoFiltering);
+ FRIEND_TEST(SocketParseMessageTestNoFiltering,
+ TestProcessMessageNoFilteringWithEmptySetExplicitSet);
+ FRIEND_TEST(SocketParseMessageTest, TestProcessMessageFilterEmptySet);
+ FRIEND_TEST(SocketParseMessageTest, TestProcessMessageFilterCompleteSet);
+ FRIEND_TEST(SocketParseMessageTest, TestProcessMessageFilterPartialSet);
+ FRIEND_TEST(SocketParseMessageTest, TestProcessMessageFilterToggle);
};
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/statsd/src/state/StateManager.cpp b/statsd/src/state/StateManager.cpp
index c488d04..e75bd97 100644
--- a/statsd/src/state/StateManager.cpp
+++ b/statsd/src/state/StateManager.cpp
@@ -108,6 +108,12 @@
}
}
+void StateManager::addAllAtomIds(LogEventFilter::AtomIdSet& allIds) const {
+ for (const auto& stateTracker : mStateTrackers) {
+ allIds.insert(stateTracker.first);
+ }
+}
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/statsd/src/state/StateManager.h b/statsd/src/state/StateManager.h
index 8437279..68b5c90 100644
--- a/statsd/src/state/StateManager.h
+++ b/statsd/src/state/StateManager.h
@@ -24,6 +24,7 @@
#include "HashableDimensionKey.h"
#include "packages/UidMap.h"
+#include "socket/LogEventFilter.h"
#include "state/StateListener.h"
#include "state/StateTracker.h"
@@ -84,6 +85,8 @@
return -1;
}
+ void addAllAtomIds(LogEventFilter::AtomIdSet& allIds) const;
+
private:
mutable std::mutex mMutex;
diff --git a/statsd/src/stats_log.proto b/statsd/src/stats_log.proto
index a820197..44add93 100644
--- a/statsd/src/stats_log.proto
+++ b/statsd/src/stats_log.proto
@@ -504,6 +504,12 @@
repeated Annotation annotation = 18;
repeated int32 activation_time_sec = 22;
repeated int32 deactivation_time_sec = 23;
+ repeated RestrictedMetricStats restricted_metric_stats = 25;
+ optional bool device_info_table_creation_failed = 26;
+ optional int32 restricted_db_corrupted_count = 27;
+ repeated int64 restricted_flush_latency = 28;
+ repeated int64 restricted_db_size_time_sec = 29;
+ repeated int64 restricted_db_size_bytes = 30;
}
repeated ConfigStats config_stats = 3;
@@ -513,6 +519,7 @@
optional int32 count = 2;
optional int32 error_count = 3;
optional int32 dropped_count = 4;
+ optional int32 skip_count = 5;
}
repeated AtomStats atom_stats = 7;
@@ -622,6 +629,42 @@
}
repeated ActivationBroadcastGuardrail activation_guardrail_stats = 19;
+
+ message RestrictedMetricStats {
+ optional int64 restricted_metric_id = 1;
+ optional int64 insert_error = 2;
+ optional int64 table_creation_error = 3;
+ optional int64 table_deletion_error = 4;
+ repeated int64 flush_latency_ns = 5;
+ optional int64 category_changed_count = 6;
+ }
+
+ message RestrictedMetricQueryStats {
+ optional int32 calling_uid = 1;
+ optional int64 config_id = 2;
+ optional int32 config_uid = 3;
+ optional string config_package = 4;
+
+ enum InvalidQueryReason {
+ UNKNOWN_REASON = 0;
+ FLAG_DISABLED = 1;
+ UNSUPPORTED_SQLITE_VERSION = 2;
+ AMBIGUOUS_CONFIG_KEY = 3;
+ CONFIG_KEY_NOT_FOUND = 4;
+ CONFIG_KEY_WITH_UNMATCHED_DELEGATE = 5;
+ QUERY_FAILURE = 6;
+ INCONSISTENT_ROW_SIZE = 7;
+ NULL_CALLBACK = 8;
+ }
+
+ optional InvalidQueryReason invalid_query_reason = 5;
+ optional int64 query_wall_time_ns = 6;
+ optional bool has_error = 7;
+ optional string query_error = 8;
+ optional int64 query_latency_ns = 9;
+ }
+ repeated RestrictedMetricQueryStats restricted_metric_query_stats = 20;
+ optional uint32 shard_offset = 21;
}
message AlertTriggerDetails {
diff --git a/statsd/src/stats_policy_config.proto b/statsd/src/stats_policy_config.proto
new file mode 100644
index 0000000..c1ea446
--- /dev/null
+++ b/statsd/src/stats_policy_config.proto
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+syntax = "proto2";
+
+package android.os.statsd;
+
+option java_package = "com.android.internal.os";
+option java_outer_classname = "StatsPolicyConfig";
+
+message StatsPolicyConfig {
+ optional int32 minimumClientsInAggregateResult = 1;
+}
\ No newline at end of file
diff --git a/statsd/src/statsd_config.proto b/statsd/src/statsd_config.proto
index 758a8d1..36ca44f 100644
--- a/statsd/src/statsd_config.proto
+++ b/statsd/src/statsd_config.proto
@@ -591,6 +591,8 @@
optional uint32 package_certificate_hash_size_bytes = 26;
+ optional string restricted_metrics_delegate_package_name = 27;
+
// Do not use.
reserved 1000, 1001;
}
diff --git a/statsd/src/statsd_metadata.proto b/statsd/src/statsd_metadata.proto
index 200b392..7d18894 100644
--- a/statsd/src/statsd_metadata.proto
+++ b/statsd/src/statsd_metadata.proto
@@ -56,12 +56,18 @@
repeated AlertDimensionKeyedData alert_dim_keyed_data = 2;
}
+message MetricMetadata {
+ optional int64 metric_id = 1;
+ optional int32 restricted_category = 2;
+}
+
// All metadata for a config in statsd
message StatsMetadata {
optional ConfigKey config_key = 1;
repeated AlertMetadata alert_metadata = 2;
+ repeated MetricMetadata metric_metadata = 3;
}
message StatsMetadataList {
repeated StatsMetadata stats_metadata = 1;
-}
\ No newline at end of file
+}
diff --git a/statsd/src/storage/StorageManager.cpp b/statsd/src/storage/StorageManager.cpp
index 1c669eb..a0d13f9 100644
--- a/statsd/src/storage/StorageManager.cpp
+++ b/statsd/src/storage/StorageManager.cpp
@@ -17,19 +17,25 @@
#define STATSD_DEBUG false // STOPSHIP if true
#include "Log.h"
-#include "android-base/stringprintf.h"
-#include "guardrail/StatsdStats.h"
#include "storage/StorageManager.h"
-#include "stats_log_util.h"
#include <android-base/file.h>
+#include <android-modules-utils/sdk_level.h>
#include <private/android_filesystem_config.h>
+#include <sys/stat.h>
+
#include <fstream>
+#include "android-base/stringprintf.h"
+#include "guardrail/StatsdStats.h"
+#include "stats_log_util.h"
+#include "utils/DbUtils.h"
+
namespace android {
namespace os {
namespace statsd {
+using android::modules::sdklevel::IsAtLeastU;
using android::util::FIELD_COUNT_REPEATED;
using android::util::FIELD_TYPE_MESSAGE;
using std::map;
@@ -118,6 +124,16 @@
output->mIsHistory = (substr != nullptr && strcmp("history", substr) == 0);
}
+// Returns array of int64_t which contains a sqlite db's uid and configId
+static ConfigKey parseDbName(char* name) {
+ char* uid = strtok(name, "_");
+ char* configId = strtok(nullptr, ".");
+ if (uid == nullptr || configId == nullptr) {
+ return ConfigKey(-1, -1);
+ }
+ return ConfigKey(StrToInt64(uid), StrToInt64(configId));
+}
+
void StorageManager::writeFile(const char* file, const void* buffer, int numBytes) {
int fd = open(file, O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR);
if (fd == -1) {
@@ -807,6 +823,49 @@
totalFileSize);
}
+void StorageManager::enforceDbGuardrails(const char* path, const int64_t currWallClockSec,
+ const int64_t maxBytes) {
+ if (!IsAtLeastU()) {
+ return;
+ }
+ unique_ptr<DIR, decltype(&closedir)> dir(opendir(path), closedir);
+ if (dir == NULL) {
+ VLOG("Path %s does not exist", path);
+ return;
+ }
+
+ dirent* de;
+ int64_t deleteThresholdSec = currWallClockSec - StatsdStats::kMaxAgeSecond;
+ while ((de = readdir(dir.get()))) {
+ char* name = de->d_name;
+ if (name[0] == '.' || de->d_type == DT_DIR) continue;
+ string fullPathName = StringPrintf("%s/%s", path, name);
+ struct stat fileInfo;
+ const ConfigKey key = parseDbName(name);
+ if (stat(fullPathName.c_str(), &fileInfo) != 0) {
+ // Remove file if stat fails.
+ remove(fullPathName.c_str());
+ continue;
+ }
+ StatsdStats::getInstance().noteRestrictedConfigDbSize(key, currWallClockSec,
+ fileInfo.st_size);
+ if (fileInfo.st_mtime <= deleteThresholdSec || fileInfo.st_size >= maxBytes) {
+ remove(fullPathName.c_str());
+ }
+ if (hasFile(dbutils::getDbName(key).c_str())) {
+ dbutils::verifyIntegrityAndDeleteIfNecessary(key);
+ } else {
+ // Remove file if the file name fails to parse.
+ remove(fullPathName.c_str());
+ }
+ }
+}
+
+bool StorageManager::hasFile(const char* file) {
+ struct stat fileInfo;
+ return stat(file, &fileInfo) == 0;
+}
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/statsd/src/storage/StorageManager.h b/statsd/src/storage/StorageManager.h
index b0840ae..9a25bf0 100644
--- a/statsd/src/storage/StorageManager.h
+++ b/statsd/src/storage/StorageManager.h
@@ -162,6 +162,11 @@
static void sortFiles(vector<FileInfo>* fileNames);
+ static void enforceDbGuardrails(const char* path, const int64_t wallClockSec,
+ const int64_t maxBytes);
+
+ static bool hasFile(const char* file);
+
private:
/**
* Prints disk usage statistics about a directory related to statsd.
diff --git a/statsd/src/utils/DbUtils.cpp b/statsd/src/utils/DbUtils.cpp
new file mode 100644
index 0000000..35ad150
--- /dev/null
+++ b/statsd/src/utils/DbUtils.cpp
@@ -0,0 +1,502 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#define STATSD_DEBUG false // STOPSHIP if true
+
+#include "Log.h"
+
+#include "utils/DbUtils.h"
+
+#include <android/api-level.h>
+
+#include "FieldValue.h"
+#include "android-base/properties.h"
+#include "android-base/stringprintf.h"
+#include "stats_log_util.h"
+#include "storage/StorageManager.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+namespace dbutils {
+
+using ::android::os::statsd::FLOAT;
+using ::android::os::statsd::INT;
+using ::android::os::statsd::LONG;
+using ::android::os::statsd::StorageManager;
+using ::android::os::statsd::STRING;
+using base::GetProperty;
+using base::StringPrintf;
+
+const string TABLE_NAME_PREFIX = "metric_";
+const string COLUMN_NAME_ATOM_TAG = "atomId";
+const string COLUMN_NAME_EVENT_ELAPSED_CLOCK_NS = "elapsedTimestampNs";
+const string COLUMN_NAME_EVENT_WALL_CLOCK_NS = "wallTimestampNs";
+
+const string COLUMN_NAME_SDK_VERSION = "sdkVersion";
+const string COLUMN_NAME_MODEL = "model";
+const string COLUMN_NAME_PRODUCT = "product";
+const string COLUMN_NAME_HARDWARE = "hardware";
+const string COLUMN_NAME_DEVICE = "device";
+const string COLUMN_NAME_BUILD = "osBuild";
+const string COLUMN_NAME_FINGERPRINT = "fingerprint";
+const string COLUMN_NAME_BRAND = "brand";
+const string COLUMN_NAME_MANUFACTURER = "manufacturer";
+const string COLUMN_NAME_BOARD = "board";
+
+static std::vector<std::string> getExpectedTableSchema(const LogEvent& logEvent) {
+ vector<std::string> result;
+ for (const FieldValue& fieldValue : logEvent.getValues()) {
+ if (fieldValue.mField.getDepth() > 0) {
+ // Repeated fields are not supported.
+ continue;
+ }
+ switch (fieldValue.mValue.getType()) {
+ case INT:
+ case LONG:
+ result.push_back("INTEGER");
+ break;
+ case STRING:
+ result.push_back("TEXT");
+ break;
+ case FLOAT:
+ result.push_back("REAL");
+ break;
+ default:
+ // Byte array fields are not supported.
+ break;
+ }
+ }
+ return result;
+}
+
+static int integrityCheckCallback(void*, int colCount, char** queryResults, char**) {
+ if (colCount == 0 || strcmp(queryResults[0], "ok") != 0) {
+ // Returning 1 is an error code that causes exec to stop and error.
+ return 1;
+ }
+ return 0;
+}
+
+string getDbName(const ConfigKey& key) {
+ return StringPrintf("%s/%d_%lld.db", STATS_RESTRICTED_DATA_DIR, key.GetUid(),
+ (long long)key.GetId());
+}
+
+static string getCreateSqlString(const int64_t metricId, const LogEvent& event) {
+ string result = StringPrintf("CREATE TABLE IF NOT EXISTS %s%s", TABLE_NAME_PREFIX.c_str(),
+ reformatMetricId(metricId).c_str());
+ result += StringPrintf("(%s INTEGER,%s INTEGER,%s INTEGER,", COLUMN_NAME_ATOM_TAG.c_str(),
+ COLUMN_NAME_EVENT_ELAPSED_CLOCK_NS.c_str(),
+ COLUMN_NAME_EVENT_WALL_CLOCK_NS.c_str());
+ for (size_t fieldId = 1; fieldId <= event.getValues().size(); ++fieldId) {
+ const FieldValue& fieldValue = event.getValues()[fieldId - 1];
+ if (fieldValue.mField.getDepth() > 0) {
+ // Repeated fields are not supported.
+ continue;
+ }
+ switch (fieldValue.mValue.getType()) {
+ case INT:
+ case LONG:
+ result += StringPrintf("field_%d INTEGER,", fieldValue.mField.getPosAtDepth(0));
+ break;
+ case STRING:
+ result += StringPrintf("field_%d TEXT,", fieldValue.mField.getPosAtDepth(0));
+ break;
+ case FLOAT:
+ result += StringPrintf("field_%d REAL,", fieldValue.mField.getPosAtDepth(0));
+ break;
+ default:
+ // Byte array fields are not supported.
+ break;
+ }
+ }
+ result.pop_back();
+ result += ") STRICT;";
+ return result;
+}
+
+string reformatMetricId(const int64_t metricId) {
+ return metricId < 0 ? StringPrintf("n%lld", (long long)metricId * -1)
+ : StringPrintf("%lld", (long long)metricId);
+}
+
+bool createTableIfNeeded(const ConfigKey& key, const int64_t metricId, const LogEvent& event) {
+ const string dbName = getDbName(key);
+ sqlite3* db;
+ if (sqlite3_open(dbName.c_str(), &db) != SQLITE_OK) {
+ sqlite3_close(db);
+ return false;
+ }
+
+ char* error = nullptr;
+ string zSql = getCreateSqlString(metricId, event);
+ sqlite3_exec(db, zSql.c_str(), nullptr, nullptr, &error);
+ sqlite3_close(db);
+ if (error) {
+ ALOGW("Failed to create table to db: %s", error);
+ return false;
+ }
+ return true;
+}
+
+bool isEventCompatible(const ConfigKey& key, const int64_t metricId, const LogEvent& event) {
+ const string dbName = getDbName(key);
+ sqlite3* db;
+ if (sqlite3_open(dbName.c_str(), &db) != SQLITE_OK) {
+ sqlite3_close(db);
+ return false;
+ }
+ string zSql = StringPrintf("PRAGMA table_info(metric_%s);", reformatMetricId(metricId).c_str());
+ string err;
+ std::vector<int32_t> columnTypes;
+ std::vector<string> columnNames;
+ std::vector<std::vector<std::string>> rows;
+ if (!query(key, zSql, rows, columnTypes, columnNames, err)) {
+ ALOGE("Failed to check table schema for metric %lld: %s", (long long)metricId, err.c_str());
+ sqlite3_close(db);
+ return false;
+ }
+ // Sample query result
+ // cid name type notnull dflt_value pk
+ // --- ----------------- ------- ------- ---------- --
+ // 0 atomId INTEGER 0 (null) 0
+ // 1 elapsedTimestampNs INTEGER 0 (null) 0
+ // 2 wallTimestampNs INTEGER 0 (null) 0
+ // 3 field_1 INTEGER 0 (null) 0
+ // 4 field_2 TEXT 0 (null) 0
+ std::vector<string> tableSchema;
+ for (size_t i = 3; i < rows.size(); ++i) { // Atom fields start at the third row
+ tableSchema.push_back(rows[i][2]); // The third column stores the data type for the column
+ }
+ sqlite3_close(db);
+ // An empty rows vector implies the table has not yet been created.
+ return rows.size() == 0 || getExpectedTableSchema(event) == tableSchema;
+}
+
+bool deleteTable(const ConfigKey& key, const int64_t metricId) {
+ const string dbName = getDbName(key);
+ sqlite3* db;
+ if (sqlite3_open(dbName.c_str(), &db) != SQLITE_OK) {
+ sqlite3_close(db);
+ return false;
+ }
+ string zSql = StringPrintf("DROP TABLE metric_%s", reformatMetricId(metricId).c_str());
+ char* error = nullptr;
+ sqlite3_exec(db, zSql.c_str(), nullptr, nullptr, &error);
+ sqlite3_close(db);
+ if (error) {
+ ALOGW("Failed to drop table from db: %s", error);
+ return false;
+ }
+ return true;
+}
+
+void deleteDb(const ConfigKey& key) {
+ const string dbName = getDbName(key);
+ StorageManager::deleteFile(dbName.c_str());
+}
+
+sqlite3* getDb(const ConfigKey& key) {
+ const string dbName = getDbName(key);
+ sqlite3* db;
+ if (sqlite3_open(dbName.c_str(), &db) == SQLITE_OK) {
+ return db;
+ }
+ return nullptr;
+}
+
+void closeDb(sqlite3* db) {
+ sqlite3_close(db);
+}
+
+static bool getInsertSqlStmt(sqlite3* db, sqlite3_stmt** stmt, const int64_t metricId,
+ const vector<LogEvent>& events, string& err) {
+ string result =
+ StringPrintf("INSERT INTO metric_%s VALUES", reformatMetricId(metricId).c_str());
+ for (auto& logEvent : events) {
+ result += StringPrintf("(%d, %lld, %lld,", logEvent.GetTagId(),
+ (long long)logEvent.GetElapsedTimestampNs(),
+ (long long)logEvent.GetLogdTimestampNs());
+ for (auto& fieldValue : logEvent.getValues()) {
+ if (fieldValue.mField.getDepth() > 0 || fieldValue.mValue.getType() == STORAGE) {
+ // Repeated fields and byte fields are not supported.
+ continue;
+ }
+ result += "?,";
+ }
+ result.pop_back();
+ result += "),";
+ }
+ result.pop_back();
+ result += ";";
+ if (sqlite3_prepare_v2(db, result.c_str(), -1, stmt, nullptr) != SQLITE_OK) {
+ err = sqlite3_errmsg(db);
+ return false;
+ }
+ // ? parameters start with an index of 1 from start of query string to the
+ // end.
+ int32_t index = 1;
+ for (auto& logEvent : events) {
+ for (auto& fieldValue : logEvent.getValues()) {
+ if (fieldValue.mField.getDepth() > 0 || fieldValue.mValue.getType() == STORAGE) {
+ // Repeated fields and byte fields are not supported.
+ continue;
+ }
+ switch (fieldValue.mValue.getType()) {
+ case INT:
+ sqlite3_bind_int(*stmt, index, fieldValue.mValue.int_value);
+ break;
+ case LONG:
+ sqlite3_bind_int64(*stmt, index, fieldValue.mValue.long_value);
+ break;
+ case STRING:
+ sqlite3_bind_text(*stmt, index, fieldValue.mValue.str_value.c_str(), -1,
+ SQLITE_STATIC);
+ break;
+ case FLOAT:
+ sqlite3_bind_double(*stmt, index, fieldValue.mValue.float_value);
+ break;
+ default:
+ // Byte array fields are not supported.
+ break;
+ }
+ ++index;
+ }
+ }
+ return true;
+}
+
+bool insert(const ConfigKey& key, const int64_t metricId, const vector<LogEvent>& events,
+ string& error) {
+ const string dbName = getDbName(key);
+ sqlite3* db;
+ if (sqlite3_open(dbName.c_str(), &db) != SQLITE_OK) {
+ error = sqlite3_errmsg(db);
+ sqlite3_close(db);
+ return false;
+ }
+ bool success = insert(db, metricId, events, error);
+ sqlite3_close(db);
+ return success;
+}
+
+bool insert(sqlite3* db, const int64_t metricId, const vector<LogEvent>& events, string& error) {
+ sqlite3_stmt* stmt = nullptr;
+ if (!getInsertSqlStmt(db, &stmt, metricId, events, error)) {
+ ALOGW("Failed to generate prepared sql insert query %s", error.c_str());
+ sqlite3_finalize(stmt);
+ return false;
+ }
+ if (sqlite3_step(stmt) != SQLITE_DONE) {
+ error = sqlite3_errmsg(db);
+ ALOGW("Failed to insert data to db: %s", error.c_str());
+ sqlite3_finalize(stmt);
+ return false;
+ }
+ sqlite3_finalize(stmt);
+ return true;
+}
+
+bool query(const ConfigKey& key, const string& zSql, vector<vector<string>>& rows,
+ vector<int32_t>& columnTypes, vector<string>& columnNames, string& err) {
+ const string dbName = getDbName(key);
+ sqlite3* db;
+ if (sqlite3_open_v2(dbName.c_str(), &db, SQLITE_OPEN_READONLY, nullptr) != SQLITE_OK) {
+ err = sqlite3_errmsg(db);
+ sqlite3_close(db);
+ return false;
+ }
+ sqlite3_stmt* stmt;
+ if (sqlite3_prepare_v2(db, zSql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) {
+ err = sqlite3_errmsg(db);
+ sqlite3_finalize(stmt);
+ sqlite3_close(db);
+ return false;
+ }
+ int result = sqlite3_step(stmt);
+ bool firstIter = true;
+ while (result == SQLITE_ROW) {
+ int colCount = sqlite3_column_count(stmt);
+ vector<string> rowData(colCount);
+ for (int i = 0; i < colCount; ++i) {
+ if (firstIter) {
+ int32_t columnType = sqlite3_column_type(stmt, i);
+ // Needed to convert to java compatible cursor types. See AbstractCursor#getType()
+ if (columnType == 5) {
+ columnType = 0; // Remap 5 (null type) to 0 for java cursor
+ }
+ columnTypes.push_back(columnType);
+ columnNames.push_back(reinterpret_cast<const char*>(sqlite3_column_name(stmt, i)));
+ }
+ const unsigned char* textResult = sqlite3_column_text(stmt, i);
+ string colData =
+ textResult != nullptr ? string(reinterpret_cast<const char*>(textResult)) : "";
+ rowData[i] = std::move(colData);
+ }
+ rows.push_back(std::move(rowData));
+ firstIter = false;
+ result = sqlite3_step(stmt);
+ }
+ sqlite3_finalize(stmt);
+ if (result != SQLITE_DONE) {
+ err = sqlite3_errmsg(db);
+ sqlite3_close(db);
+ return false;
+ }
+ sqlite3_close(db);
+ return true;
+}
+
+bool flushTtl(sqlite3* db, const int64_t metricId, const int64_t ttlWallClockNs) {
+ string zSql = StringPrintf("DELETE FROM %s%s WHERE %s <= %lld", TABLE_NAME_PREFIX.c_str(),
+ reformatMetricId(metricId).c_str(),
+ COLUMN_NAME_EVENT_WALL_CLOCK_NS.c_str(), (long long)ttlWallClockNs);
+
+ char* error = nullptr;
+ sqlite3_exec(db, zSql.c_str(), nullptr, nullptr, &error);
+ if (error) {
+ ALOGW("Failed to enforce ttl: %s", error);
+ return false;
+ }
+ return true;
+}
+
+void verifyIntegrityAndDeleteIfNecessary(const ConfigKey& configKey) {
+ const string dbName = getDbName(configKey);
+ sqlite3* db;
+ if (sqlite3_open(dbName.c_str(), &db) != SQLITE_OK) {
+ sqlite3_close(db);
+ return;
+ }
+ string zSql = "PRAGMA integrity_check";
+
+ char* error = nullptr;
+ sqlite3_exec(db, zSql.c_str(), integrityCheckCallback, nullptr, &error);
+ if (error) {
+ StatsdStats::getInstance().noteDbCorrupted(configKey);
+ ALOGW("Integrity Check failed %s", error);
+ sqlite3_close(db);
+ deleteDb(configKey);
+ return;
+ }
+ sqlite3_close(db);
+}
+
+static bool getDeviceInfoInsertStmt(sqlite3* db, sqlite3_stmt** stmt, string error) {
+ string insertSql = StringPrintf("INSERT INTO device_info VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
+ if (sqlite3_prepare_v2(db, insertSql.c_str(), -1, stmt, nullptr) != SQLITE_OK) {
+ error = sqlite3_errmsg(db);
+ return false;
+ }
+
+ // ? parameters start with an index of 1 from start of query string to the end.
+ int32_t index = 1;
+
+ int32_t sdkVersion = android_get_device_api_level();
+ sqlite3_bind_int(*stmt, index, sdkVersion);
+ ++index;
+
+ string model = GetProperty("ro.product.model", "(unknown)");
+ sqlite3_bind_text(*stmt, index, model.c_str(), -1, SQLITE_TRANSIENT);
+ ++index;
+
+ string product = GetProperty("ro.product.name", "(unknown)");
+ sqlite3_bind_text(*stmt, index, product.c_str(), -1, SQLITE_TRANSIENT);
+ ++index;
+
+ string hardware = GetProperty("ro.hardware", "(unknown)");
+ sqlite3_bind_text(*stmt, index, hardware.c_str(), -1, SQLITE_TRANSIENT);
+ ++index;
+
+ string device = GetProperty("ro.product.device", "(unknown)");
+ sqlite3_bind_text(*stmt, index, device.c_str(), -1, SQLITE_TRANSIENT);
+ ++index;
+
+ string osBuild = GetProperty("ro.build.id", "(unknown)");
+ sqlite3_bind_text(*stmt, index, osBuild.c_str(), -1, SQLITE_TRANSIENT);
+ ++index;
+
+ string fingerprint = GetProperty("ro.build.fingerprint", "(unknown)");
+ sqlite3_bind_text(*stmt, index, fingerprint.c_str(), -1, SQLITE_TRANSIENT);
+ ++index;
+
+ string brand = GetProperty("ro.product.brand", "(unknown)");
+ sqlite3_bind_text(*stmt, index, brand.c_str(), -1, SQLITE_TRANSIENT);
+ ++index;
+
+ string manufacturer = GetProperty("ro.product.manufacturer", "(unknown)");
+ sqlite3_bind_text(*stmt, index, manufacturer.c_str(), -1, SQLITE_TRANSIENT);
+ ++index;
+
+ string board = GetProperty("ro.product.board", "(unknown)");
+ sqlite3_bind_text(*stmt, index, board.c_str(), -1, SQLITE_TRANSIENT);
+ ++index;
+
+ return true;
+}
+
+bool updateDeviceInfoTable(const ConfigKey& key, string& error) {
+ const string dbName = getDbName(key);
+ sqlite3* db;
+ if (sqlite3_open(dbName.c_str(), &db) != SQLITE_OK) {
+ error = sqlite3_errmsg(db);
+ sqlite3_close(db);
+ return false;
+ }
+
+ string dropTableSql = "DROP TABLE device_info";
+ // Ignore possible error result code if table has not yet been created.
+ sqlite3_exec(db, dropTableSql.c_str(), nullptr, nullptr, nullptr);
+
+ string createTableSql = StringPrintf(
+ "CREATE TABLE device_info(%s INTEGER, %s TEXT, %s TEXT, %s TEXT, %s TEXT, %s TEXT, %s "
+ "TEXT, %s TEXT, %s TEXT, %s TEXT) "
+ "STRICT",
+ COLUMN_NAME_SDK_VERSION.c_str(), COLUMN_NAME_MODEL.c_str(), COLUMN_NAME_PRODUCT.c_str(),
+ COLUMN_NAME_HARDWARE.c_str(), COLUMN_NAME_DEVICE.c_str(), COLUMN_NAME_BUILD.c_str(),
+ COLUMN_NAME_FINGERPRINT.c_str(), COLUMN_NAME_BRAND.c_str(),
+ COLUMN_NAME_MANUFACTURER.c_str(), COLUMN_NAME_BOARD.c_str());
+ if (sqlite3_exec(db, createTableSql.c_str(), nullptr, nullptr, nullptr) != SQLITE_OK) {
+ error = sqlite3_errmsg(db);
+ ALOGW("Failed to create device info table %s", error.c_str());
+ sqlite3_close(db);
+ return false;
+ }
+
+ sqlite3_stmt* stmt = nullptr;
+ if (!getDeviceInfoInsertStmt(db, &stmt, error)) {
+ ALOGW("Failed to generate device info prepared sql insert query %s", error.c_str());
+ sqlite3_finalize(stmt);
+ sqlite3_close(db);
+ return false;
+ }
+
+ if (sqlite3_step(stmt) != SQLITE_DONE) {
+ error = sqlite3_errmsg(db);
+ ALOGW("Failed to insert data to device info table: %s", error.c_str());
+ sqlite3_finalize(stmt);
+ sqlite3_close(db);
+ return false;
+ }
+ sqlite3_finalize(stmt);
+ sqlite3_close(db);
+ return true;
+}
+} // namespace dbutils
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/statsd/src/utils/DbUtils.h b/statsd/src/utils/DbUtils.h
new file mode 100644
index 0000000..8a561e6
--- /dev/null
+++ b/statsd/src/utils/DbUtils.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+#pragma once
+
+#include <sqlite3.h>
+
+#include "config/ConfigKey.h"
+#include "logd/LogEvent.h"
+
+using std::string;
+using std::vector;
+
+namespace android {
+namespace os {
+namespace statsd {
+namespace dbutils {
+
+#define STATS_RESTRICTED_DATA_DIR "/data/misc/stats-data/restricted-data"
+
+inline int32_t getDbVersion() {
+ return SQLITE_VERSION_NUMBER;
+};
+
+string getDbName(const ConfigKey& key);
+
+string reformatMetricId(const int64_t metricId);
+
+/* Creates a new data table for a specified metric if one does not yet exist. */
+bool createTableIfNeeded(const ConfigKey& key, const int64_t metricId, const LogEvent& event);
+
+/* Checks whether the table schema for the given metric matches the event.
+ * Returns true if the table has not yet been created.
+ */
+bool isEventCompatible(const ConfigKey& key, const int64_t metricId, const LogEvent& event);
+
+/* Deletes a data table for the specified metric. */
+bool deleteTable(const ConfigKey& key, const int64_t metricId);
+
+/* Deletes the SQLite db data file. */
+void deleteDb(const ConfigKey& key);
+
+/* Gets a handle to the sqlite db. You must call closeDb to free the allocated memory.
+ * Returns a nullptr if an error occurs.
+ */
+sqlite3* getDb(const ConfigKey& key);
+
+/* Closes the handle to the sqlite db. */
+void closeDb(sqlite3* db);
+
+/* Inserts new data into the specified metric data table.
+ * A temp sqlite handle is created using the ConfigKey.
+ */
+bool insert(const ConfigKey& key, const int64_t metricId, const vector<LogEvent>& events,
+ string& error);
+
+/* Inserts new data into the specified sqlite db handle. */
+bool insert(sqlite3* db, const int64_t metricId, const vector<LogEvent>& events, string& error);
+
+/* Executes a sql query on the specified SQLite db.
+ * A temp sqlite handle is created using the ConfigKey.
+ */
+bool query(const ConfigKey& key, const string& zSql, vector<vector<string>>& rows,
+ vector<int32_t>& columnTypes, vector<string>& columnNames, string& err);
+
+bool flushTtl(sqlite3* db, const int64_t metricId, const int64_t ttlWallClockNs);
+
+/* Checks for database corruption and deletes the db if it is corrupted. */
+void verifyIntegrityAndDeleteIfNecessary(const ConfigKey& key);
+
+/* Creates and updates the device info table for the given configKey. */
+bool updateDeviceInfoTable(const ConfigKey& key, string& error);
+
+} // namespace dbutils
+} // namespace statsd
+} // namespace os
+} // namespace android
\ No newline at end of file
diff --git a/statsd/src/utils/RestrictedPolicyManager.cpp b/statsd/src/utils/RestrictedPolicyManager.cpp
new file mode 100644
index 0000000..3ae75a4
--- /dev/null
+++ b/statsd/src/utils/RestrictedPolicyManager.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#include "utils/RestrictedPolicyManager.h"
+
+#include "stats_annotations.h"
+
+#define STATSD_DEBUG false // STOPSHIP if true
+
+namespace android {
+namespace os {
+namespace statsd {
+
+RestrictedPolicyManager& RestrictedPolicyManager::getInstance() {
+ static RestrictedPolicyManager policyInstance;
+ return policyInstance;
+}
+
+int32_t RestrictedPolicyManager::getRestrictedCategoryTtl(
+ const StatsdRestrictionCategory categoryId) {
+ return mRestrictionCategoryTtlInDaysMap.find(categoryId) !=
+ mRestrictionCategoryTtlInDaysMap.end()
+ ? mRestrictionCategoryTtlInDaysMap.at(categoryId)
+ : DEFAULT_RESTRICTED_CATEGORY_TTL_DAYS;
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
\ No newline at end of file
diff --git a/statsd/src/utils/RestrictedPolicyManager.h b/statsd/src/utils/RestrictedPolicyManager.h
new file mode 100644
index 0000000..34d1fa3
--- /dev/null
+++ b/statsd/src/utils/RestrictedPolicyManager.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+#pragma once
+
+#include <stdlib.h>
+
+#include <map>
+
+#include "stats_annotations.h"
+
+using std::map;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+#define DEFAULT_RESTRICTED_CATEGORY_TTL_DAYS 7
+
+// Restricted categories used internally by statsd.
+enum StatsdRestrictionCategory : int32_t {
+ CATEGORY_UNKNOWN = -1,
+ CATEGORY_NO_RESTRICTION = 0,
+ CATEGORY_DIAGNOSTIC = ASTATSLOG_RESTRICTION_CATEGORY_DIAGNOSTIC,
+ CATEGORY_SYSTEM_INTELLIGENCE = ASTATSLOG_RESTRICTION_CATEGORY_SYSTEM_INTELLIGENCE,
+ CATEGORY_AUTHENTICATION = ASTATSLOG_RESTRICTION_CATEGORY_AUTHENTICATION,
+ CATEGORY_FRAUD_AND_ABUSE = ASTATSLOG_RESTRICTION_CATEGORY_FRAUD_AND_ABUSE,
+};
+
+// Single instance shared across the process.
+class RestrictedPolicyManager {
+public:
+ static RestrictedPolicyManager& getInstance();
+ ~RestrictedPolicyManager(){};
+
+ // Gets the TTL in days for a particular restricted category. Returns the default for unknown
+ // categories.
+ int32_t getRestrictedCategoryTtl(const StatsdRestrictionCategory categoryId);
+
+private:
+ std::map<StatsdRestrictionCategory, int32_t> mRestrictionCategoryTtlInDaysMap;
+};
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/statsd/src/utils/ShardOffsetProvider.cpp b/statsd/src/utils/ShardOffsetProvider.cpp
index 6d847df..4a0df97 100644
--- a/statsd/src/utils/ShardOffsetProvider.cpp
+++ b/statsd/src/utils/ShardOffsetProvider.cpp
@@ -16,15 +16,31 @@
#include "ShardOffsetProvider.h"
+#include <errno.h>
+#include <sys/random.h>
+#include <unistd.h>
+
+#include <chrono>
+
namespace android {
namespace os {
namespace statsd {
-ShardOffsetProvider::ShardOffsetProvider(const int shardOffset) : mShardOffset(shardOffset) {
+ShardOffsetProvider::ShardOffsetProvider() {
+ unsigned int seed = 0;
+ // getrandom() reads bytes from urandom source into buf. If getrandom()
+ // is unable to read from urandom source, then it returns -1 and we set
+ // our seed to be time(nullptr) as a fallback.
+ if (TEMP_FAILURE_RETRY(
+ getrandom(static_cast<void*>(&seed), sizeof(unsigned int), GRND_NONBLOCK)) < 0) {
+ seed = time(nullptr);
+ }
+ srand(seed);
+ mShardOffset = rand();
}
ShardOffsetProvider& ShardOffsetProvider::getInstance() {
- static ShardOffsetProvider sShardOffsetProvider(rand());
+ static ShardOffsetProvider sShardOffsetProvider;
return sShardOffsetProvider;
}
diff --git a/statsd/src/utils/ShardOffsetProvider.h b/statsd/src/utils/ShardOffsetProvider.h
index 0623e0f..dd6a5a7 100644
--- a/statsd/src/utils/ShardOffsetProvider.h
+++ b/statsd/src/utils/ShardOffsetProvider.h
@@ -32,21 +32,21 @@
public:
~ShardOffsetProvider(){};
- int getShardOffset() const {
+ uint32_t getShardOffset() const {
return mShardOffset;
}
static ShardOffsetProvider& getInstance();
private:
- ShardOffsetProvider(const int shardOffset);
+ ShardOffsetProvider();
// Only used for testing.
- void setShardOffset(const int shardOffset) {
+ void setShardOffset(const uint32_t shardOffset) {
mShardOffset = shardOffset;
}
- int mShardOffset;
+ uint32_t mShardOffset;
FRIEND_TEST(CountMetricE2eTest, TestDimensionalSampling);
FRIEND_TEST(DurationMetricE2eTest, TestDimensionalSampling);
@@ -54,6 +54,7 @@
FRIEND_TEST(GaugeMetricProducerTest, TestPullDimensionalSampling);
FRIEND_TEST(KllMetricE2eTest, TestDimensionalSampling);
FRIEND_TEST(NumericValueMetricProducerTest, TestDimensionalSampling);
+ FRIEND_TEST(StatsdStatsTest, TestShardOffsetProvider);
};
} // namespace statsd
diff --git a/statsd/tests/ConfigManager_test.cpp b/statsd/tests/ConfigManager_test.cpp
index bfc8968..f03ccf5 100644
--- a/statsd/tests/ConfigManager_test.cpp
+++ b/statsd/tests/ConfigManager_test.cpp
@@ -161,3 +161,96 @@
manager->RemoveConfigs(1);
manager->RemoveConfigs(3);
}
+
+TEST(ConfigManagerTest, TestSendRestrictedMetricsChangedBroadcast) {
+ const string configPackage = "pkg";
+ const int64_t configId = 123;
+ const int32_t callingUid = 456;
+ const vector<int64_t> metricIds = {100, 200, 999};
+
+ shared_ptr<MockPendingIntentRef> pir = SharedRefBase::make<StrictMock<MockPendingIntentRef>>();
+ EXPECT_CALL(*pir, sendRestrictedMetricsChangedBroadcast(metricIds))
+ .Times(1)
+ .WillRepeatedly(Invoke([](const vector<int64_t>&) { return Status::ok(); }));
+
+ sp<ConfigManager> manager = new ConfigManager();
+ manager->SetRestrictedMetricsChangedReceiver(configPackage, configId, callingUid, pir);
+
+ manager->SendRestrictedMetricsBroadcast({configPackage}, configId, {callingUid}, metricIds);
+}
+
+TEST(ConfigManagerTest, TestSendRestrictedMetricsChangedBroadcastMultipleConfigs) {
+ const string package1 = "pkg1", package2 = "pkg2", package3 = "pkg3";
+ const int64_t configId = 123;
+ const int32_t callingUid1 = 456, callingUid2 = 789, callingUid3 = 1111;
+ const vector<int64_t> metricIds1 = {100, 200, 999}, metricIds2 = {101}, metricIds3 = {202, 101};
+
+ shared_ptr<MockPendingIntentRef> pir1 = SharedRefBase::make<StrictMock<MockPendingIntentRef>>();
+ EXPECT_CALL(*pir1, sendRestrictedMetricsChangedBroadcast(metricIds1))
+ .Times(3)
+ .WillRepeatedly(Invoke([](const vector<int64_t>&) { return Status::ok(); }));
+ EXPECT_CALL(*pir1, sendRestrictedMetricsChangedBroadcast(metricIds2))
+ .Times(1)
+ .WillRepeatedly(Invoke([](const vector<int64_t>&) { return Status::ok(); }));
+
+ shared_ptr<MockPendingIntentRef> pir2 = SharedRefBase::make<StrictMock<MockPendingIntentRef>>();
+ EXPECT_CALL(*pir2, sendRestrictedMetricsChangedBroadcast(metricIds1))
+ .Times(1)
+ .WillRepeatedly(Invoke([](const vector<int64_t>&) { return Status::ok(); }));
+
+ shared_ptr<MockPendingIntentRef> pir3 = SharedRefBase::make<StrictMock<MockPendingIntentRef>>();
+ EXPECT_CALL(*pir3, sendRestrictedMetricsChangedBroadcast(metricIds1))
+ .Times(1)
+ .WillRepeatedly(Invoke([](const vector<int64_t>&) { return Status::ok(); }));
+ EXPECT_CALL(*pir3, sendRestrictedMetricsChangedBroadcast(metricIds2))
+ .Times(1)
+ .WillRepeatedly(Invoke([](const vector<int64_t>&) { return Status::ok(); }));
+
+ shared_ptr<MockPendingIntentRef> pir4 = SharedRefBase::make<StrictMock<MockPendingIntentRef>>();
+ EXPECT_CALL(*pir4, sendRestrictedMetricsChangedBroadcast(metricIds1))
+ .Times(2)
+ .WillRepeatedly(Invoke([](const vector<int64_t>&) { return Status::ok(); }));
+
+ shared_ptr<MockPendingIntentRef> pir5 = SharedRefBase::make<StrictMock<MockPendingIntentRef>>();
+ EXPECT_CALL(*pir5, sendRestrictedMetricsChangedBroadcast(metricIds3))
+ .Times(1)
+ .WillRepeatedly(Invoke([](const vector<int64_t>&) { return Status::ok(); }));
+
+ sp<ConfigManager> manager = new ConfigManager();
+ manager->SetRestrictedMetricsChangedReceiver(package1, configId, callingUid1, pir1);
+ manager->SetRestrictedMetricsChangedReceiver(package2, configId, callingUid2, pir2);
+ manager->SetRestrictedMetricsChangedReceiver(package1, configId, callingUid2, pir3);
+ manager->SetRestrictedMetricsChangedReceiver(package2, configId, callingUid1, pir4);
+ manager->SetRestrictedMetricsChangedReceiver(package3, configId, callingUid3, pir5);
+
+ // Invoke pir 1, 2, 3, 4.
+ manager->SendRestrictedMetricsBroadcast({package1, package2}, configId,
+ {callingUid1, callingUid2}, metricIds1);
+ // Invoke pir 1 only
+ manager->SendRestrictedMetricsBroadcast({package1}, configId, {callingUid1}, metricIds1);
+ // Invoke pir 1, 4.
+ manager->SendRestrictedMetricsBroadcast({package1, package2}, configId, {callingUid1},
+ metricIds1);
+ // Invoke pir 1, 3
+ manager->SendRestrictedMetricsBroadcast({package1}, configId, {callingUid1, callingUid2},
+ metricIds2);
+ // Invoke pir 5
+ manager->SendRestrictedMetricsBroadcast({package3}, configId, {callingUid1, callingUid3},
+ metricIds3);
+}
+
+TEST(ConfigManagerTest, TestRemoveRestrictedMetricsChangedBroadcast) {
+ const string configPackage = "pkg";
+ const int64_t configId = 123;
+ const int32_t callingUid = 456;
+ const vector<int64_t> metricIds = {100, 200, 999};
+
+ shared_ptr<MockPendingIntentRef> pir = SharedRefBase::make<StrictMock<MockPendingIntentRef>>();
+ EXPECT_CALL(*pir, sendRestrictedMetricsChangedBroadcast(metricIds)).Times(0);
+
+ sp<ConfigManager> manager = new ConfigManager();
+ manager->SetRestrictedMetricsChangedReceiver(configPackage, configId, callingUid, pir);
+ manager->RemoveRestrictedMetricsChangedReceiver(configPackage, configId, callingUid);
+
+ manager->SendRestrictedMetricsBroadcast({configPackage}, configId, {callingUid}, metricIds);
+}
\ No newline at end of file
diff --git a/statsd/tests/LogEntryMatcher_test.cpp b/statsd/tests/LogEntryMatcher_test.cpp
index d0a063f..c91a443 100644
--- a/statsd/tests/LogEntryMatcher_test.cpp
+++ b/statsd/tests/LogEntryMatcher_test.cpp
@@ -15,9 +15,9 @@
#include <gtest/gtest.h>
#include <stdio.h>
-#include "annotations.h"
-#include "src/statsd_config.pb.h"
#include "matchers/matcher_util.h"
+#include "src/statsd_config.pb.h"
+#include "stats_annotations.h"
#include "stats_event.h"
#include "stats_log_util.h"
#include "stats_util.h"
@@ -120,7 +120,7 @@
AStatsEvent* statsEvent = AStatsEvent_obtain();
AStatsEvent_setAtomId(statsEvent, atomId);
AStatsEvent_writeInt32Array(statsEvent, intArray.data(), intArray.size());
- AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true);
+ AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
parseStatsEventToLogEvent(statsEvent, logEvent);
}
@@ -412,7 +412,8 @@
// Make event with is_uid annotation.
LogEvent event2(/*uid=*/0, /*pid=*/0);
- makeIntWithBoolAnnotationLogEvent(&event2, TAG_ID_2, 1111, ANNOTATION_ID_IS_UID, true);
+ makeIntWithBoolAnnotationLogEvent(&event2, TAG_ID_2, 1111, ASTATSLOG_ANNOTATION_ID_IS_UID,
+ true);
// Event has is_uid annotation, so mapping from uid to package name occurs.
simpleMatcher->set_atom_id(TAG_ID_2);
@@ -1231,12 +1232,12 @@
// Event where mapping from uid to package name occurs.
LogEvent event2(/*uid=*/0, /*pid=*/0);
- makeIntWithBoolAnnotationLogEvent(&event2, TAG_ID, 1111, ANNOTATION_ID_IS_UID, true);
+ makeIntWithBoolAnnotationLogEvent(&event2, TAG_ID, 1111, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2));
// Event where uid maps to package names that don't fit wildcard pattern.
LogEvent event3(/*uid=*/0, /*pid=*/0);
- makeIntWithBoolAnnotationLogEvent(&event3, TAG_ID, 3333, ANNOTATION_ID_IS_UID, true);
+ makeIntWithBoolAnnotationLogEvent(&event3, TAG_ID, 3333, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event3));
// Update matcher to match one AID
@@ -1245,12 +1246,12 @@
// Event where mapping from uid to aid doesn't fit wildcard pattern.
LogEvent event4(/*uid=*/0, /*pid=*/0);
- makeIntWithBoolAnnotationLogEvent(&event4, TAG_ID, 1005, ANNOTATION_ID_IS_UID, true);
+ makeIntWithBoolAnnotationLogEvent(&event4, TAG_ID, 1005, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event4));
// Event where mapping from uid to aid does fit wildcard pattern.
LogEvent event5(/*uid=*/0, /*pid=*/0);
- makeIntWithBoolAnnotationLogEvent(&event5, TAG_ID, 1000, ANNOTATION_ID_IS_UID, true);
+ makeIntWithBoolAnnotationLogEvent(&event5, TAG_ID, 1000, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event5));
// Update matcher to match multiple AIDs
@@ -1258,16 +1259,16 @@
// Event where mapping from uid to aid doesn't fit wildcard pattern.
LogEvent event6(/*uid=*/0, /*pid=*/0);
- makeIntWithBoolAnnotationLogEvent(&event6, TAG_ID, 1036, ANNOTATION_ID_IS_UID, true);
+ makeIntWithBoolAnnotationLogEvent(&event6, TAG_ID, 1036, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event6));
// Event where mapping from uid to aid does fit wildcard pattern.
LogEvent event7(/*uid=*/0, /*pid=*/0);
- makeIntWithBoolAnnotationLogEvent(&event7, TAG_ID, 1034, ANNOTATION_ID_IS_UID, true);
+ makeIntWithBoolAnnotationLogEvent(&event7, TAG_ID, 1034, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event7));
LogEvent event8(/*uid=*/0, /*pid=*/0);
- makeIntWithBoolAnnotationLogEvent(&event8, TAG_ID, 1035, ANNOTATION_ID_IS_UID, true);
+ makeIntWithBoolAnnotationLogEvent(&event8, TAG_ID, 1035, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event8));
}
diff --git a/statsd/tests/LogEventFilter_test.cpp b/statsd/tests/LogEventFilter_test.cpp
new file mode 100644
index 0000000..037f02d
--- /dev/null
+++ b/statsd/tests/LogEventFilter_test.cpp
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2023, 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.
+ */
+#include "socket/LogEventFilter.h"
+
+#include <gtest/gtest.h>
+
+#include <algorithm>
+
+#ifdef __ANDROID__
+
+namespace android {
+namespace os {
+namespace statsd {
+
+namespace {
+
+constexpr int kAtomIdsCount = 100; // Filter size setup
+
+LogEventFilter::AtomIdSet generateAtomIds(int rangeStart, int rangeEndInclusive) {
+ LogEventFilter::AtomIdSet atomIds;
+ for (int i = rangeStart; i <= rangeEndInclusive; ++i) {
+ atomIds.insert(i);
+ }
+ return atomIds;
+}
+
+bool testGuaranteedUnusedAtomsNotInUse(const LogEventFilter& filter) {
+ const auto sampleIds = generateAtomIds(10000, 11000);
+ bool atLeastOneInUse = false;
+ for (const auto& atomId : sampleIds) {
+ atLeastOneInUse |= filter.isAtomInUse(atomId);
+ }
+ return !atLeastOneInUse;
+}
+
+} // namespace
+
+TEST(LogEventFilterTest, TestEmptyFilter) {
+ LogEventFilter filter;
+ const auto sampleIds = generateAtomIds(1, kAtomIdsCount);
+ for (const auto& atomId : sampleIds) {
+ EXPECT_FALSE(filter.isAtomInUse(atomId));
+ }
+}
+
+TEST(LogEventFilterTest, TestRemoveNonExistingEmptyFilter) {
+ LogEventFilter filter;
+ EXPECT_FALSE(filter.isAtomInUse(1));
+ LogEventFilter::AtomIdSet emptyAtomIdsSet;
+ EXPECT_EQ(0, filter.mTagIdsPerConsumer.size());
+ EXPECT_EQ(0, filter.mLocalTagIds.size());
+ filter.setAtomIds(std::move(emptyAtomIdsSet), reinterpret_cast<LogEventFilter::ConsumerId>(0));
+ EXPECT_FALSE(filter.isAtomInUse(1));
+ EXPECT_EQ(0, filter.mLocalTagIds.size());
+ EXPECT_EQ(0, filter.mTagIdsPerConsumer.size());
+}
+
+TEST(LogEventFilterTest, TestEmptyFilterDisabled) {
+ LogEventFilter filter;
+ filter.setFilteringEnabled(false);
+ const auto sampleIds = generateAtomIds(1, kAtomIdsCount);
+ for (const auto& atomId : sampleIds) {
+ EXPECT_TRUE(filter.isAtomInUse(atomId));
+ }
+}
+
+TEST(LogEventFilterTest, TestNonEmptyFilterFullOverlap) {
+ LogEventFilter filter;
+ auto filterIds = generateAtomIds(1, kAtomIdsCount);
+ filter.setAtomIds(std::move(filterIds), reinterpret_cast<LogEventFilter::ConsumerId>(0));
+ EXPECT_EQ(1, filter.mTagIdsPerConsumer.size());
+
+ // inner copy updated only during fetch if required
+ EXPECT_EQ(0, filter.mLocalTagIds.size());
+ const auto sampleIds = generateAtomIds(1, kAtomIdsCount);
+ for (const auto& atomId : sampleIds) {
+ EXPECT_TRUE(filter.isAtomInUse(atomId));
+ }
+ EXPECT_EQ(kAtomIdsCount, filter.mLocalTagIds.size());
+}
+
+TEST(LogEventFilterTest, TestNonEmptyFilterPartialOverlap) {
+ LogEventFilter filter;
+ auto filterIds = generateAtomIds(1, kAtomIdsCount);
+ filter.setAtomIds(std::move(filterIds), reinterpret_cast<LogEventFilter::ConsumerId>(0));
+ // extra 100 atom ids should be filtered out
+ const auto sampleIds = generateAtomIds(1, kAtomIdsCount + 100);
+ for (const auto& atomId : sampleIds) {
+ bool const atomInUse = atomId <= kAtomIdsCount;
+ EXPECT_EQ(atomInUse, filter.isAtomInUse(atomId));
+ }
+}
+
+TEST(LogEventFilterTest, TestNonEmptyFilterDisabledPartialOverlap) {
+ LogEventFilter filter;
+ auto filterIds = generateAtomIds(1, kAtomIdsCount);
+ filter.setAtomIds(std::move(filterIds), reinterpret_cast<LogEventFilter::ConsumerId>(0));
+ filter.setFilteringEnabled(false);
+ // extra 100 atom ids should be in use due to filter is disabled
+ const auto sampleIds = generateAtomIds(1, kAtomIdsCount + 100);
+ for (const auto& atomId : sampleIds) {
+ EXPECT_TRUE(filter.isAtomInUse(atomId));
+ }
+}
+
+TEST(LogEventFilterTest, TestMultipleConsumerOverlapIdsRemoved) {
+ LogEventFilter filter;
+ auto filterIds1 = generateAtomIds(1, kAtomIdsCount);
+ // half of filterIds1 atom ids overlaps with filterIds2
+ auto filterIds2 = generateAtomIds(kAtomIdsCount / 2, kAtomIdsCount * 2);
+ filter.setAtomIds(std::move(filterIds1), reinterpret_cast<LogEventFilter::ConsumerId>(0));
+ filter.setAtomIds(std::move(filterIds2), reinterpret_cast<LogEventFilter::ConsumerId>(1));
+ // inner copy updated only during fetch if required
+ EXPECT_EQ(0, filter.mLocalTagIds.size());
+ const auto sampleIds = generateAtomIds(1, kAtomIdsCount * 2);
+ for (const auto& atomId : sampleIds) {
+ EXPECT_TRUE(filter.isAtomInUse(atomId));
+ }
+ EXPECT_EQ(kAtomIdsCount * 2, filter.mLocalTagIds.size());
+ EXPECT_TRUE(testGuaranteedUnusedAtomsNotInUse(filter));
+
+ // set empty filter for second consumer
+ LogEventFilter::AtomIdSet emptyAtomIdsSet;
+ filter.setAtomIds(std::move(emptyAtomIdsSet), reinterpret_cast<LogEventFilter::ConsumerId>(1));
+ EXPECT_EQ(kAtomIdsCount * 2, filter.mLocalTagIds.size());
+ for (const auto& atomId : sampleIds) {
+ bool const atomInUse = atomId <= kAtomIdsCount;
+ EXPECT_EQ(atomInUse, filter.isAtomInUse(atomId));
+ }
+ EXPECT_EQ(kAtomIdsCount, filter.mLocalTagIds.size());
+ EXPECT_TRUE(testGuaranteedUnusedAtomsNotInUse(filter));
+}
+
+TEST(LogEventFilterTest, TestMultipleConsumerEmptyFilter) {
+ LogEventFilter filter;
+ auto filterIds1 = generateAtomIds(1, kAtomIdsCount);
+ auto filterIds2 = generateAtomIds(kAtomIdsCount + 1, kAtomIdsCount * 2);
+ filter.setAtomIds(std::move(filterIds1), reinterpret_cast<LogEventFilter::ConsumerId>(0));
+ filter.setAtomIds(std::move(filterIds2), reinterpret_cast<LogEventFilter::ConsumerId>(1));
+ EXPECT_EQ(2, filter.mTagIdsPerConsumer.size());
+ // inner copy updated only during fetch if required
+ EXPECT_EQ(0, filter.mLocalTagIds.size());
+ const auto sampleIds = generateAtomIds(1, kAtomIdsCount * 2);
+ for (const auto& atomId : sampleIds) {
+ EXPECT_TRUE(filter.isAtomInUse(atomId));
+ }
+ EXPECT_EQ(kAtomIdsCount * 2, filter.mLocalTagIds.size());
+ EXPECT_TRUE(testGuaranteedUnusedAtomsNotInUse(filter));
+
+ // set empty filter for first consumer
+ LogEventFilter::AtomIdSet emptyAtomIdsSet;
+ filter.setAtomIds(emptyAtomIdsSet, reinterpret_cast<LogEventFilter::ConsumerId>(0));
+ EXPECT_EQ(1, filter.mTagIdsPerConsumer.size());
+ EXPECT_EQ(kAtomIdsCount * 2, filter.mLocalTagIds.size());
+ for (const auto& atomId : sampleIds) {
+ bool const atomInUse = atomId > kAtomIdsCount;
+ EXPECT_EQ(atomInUse, filter.isAtomInUse(atomId));
+ }
+ EXPECT_EQ(kAtomIdsCount, filter.mLocalTagIds.size());
+ EXPECT_TRUE(testGuaranteedUnusedAtomsNotInUse(filter));
+
+ // set empty filter for second consumer
+ filter.setAtomIds(emptyAtomIdsSet, reinterpret_cast<LogEventFilter::ConsumerId>(1));
+ EXPECT_EQ(0, filter.mTagIdsPerConsumer.size());
+ EXPECT_EQ(kAtomIdsCount, filter.mLocalTagIds.size());
+ for (const auto& atomId : sampleIds) {
+ EXPECT_FALSE(filter.isAtomInUse(atomId));
+ }
+ EXPECT_EQ(0, filter.mLocalTagIds.size());
+ EXPECT_TRUE(testGuaranteedUnusedAtomsNotInUse(filter));
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/statsd/tests/LogEvent_test.cpp b/statsd/tests/LogEvent_test.cpp
index f0d090d..1a9b920 100644
--- a/statsd/tests/LogEvent_test.cpp
+++ b/statsd/tests/LogEvent_test.cpp
@@ -14,12 +14,16 @@
#include "src/logd/LogEvent.h"
+#include <android-modules-utils/sdk_level.h>
#include <gtest/gtest.h>
+#include "flags/FlagProvider.h"
#include "frameworks/proto_logging/stats/atoms.pb.h"
#include "frameworks/proto_logging/stats/enums/stats/launcher/launcher.pb.h"
#include "log/log_event_list.h"
+#include "stats_annotations.h"
#include "stats_event.h"
+#include "statsd_test_util.h"
#ifdef __ANDROID__
@@ -27,10 +31,11 @@
namespace os {
namespace statsd {
+using android::modules::sdklevel::IsAtLeastU;
using std::string;
using std::vector;
-using util::ProtoOutputStream;
-using util::ProtoReader;
+using ::util::ProtoOutputStream;
+using ::util::ProtoReader;
namespace {
@@ -43,94 +48,117 @@
return f;
}
-void createStatsEvent(AStatsEvent* statsEvent, uint8_t typeId) {
- AStatsEvent_setAtomId(statsEvent, /*atomId=*/100);
-
- int int32Array[2] = {3, 6};
- uint32_t uids[] = {1001, 1002};
- const char* tags[] = {"tag1", "tag2"};
-
- switch (typeId) {
- case INT32_TYPE:
- AStatsEvent_writeInt32(statsEvent, 10);
- break;
- case INT64_TYPE:
- AStatsEvent_writeInt64(statsEvent, 1000L);
- break;
- case STRING_TYPE:
- AStatsEvent_writeString(statsEvent, "test");
- break;
- case LIST_TYPE:
- AStatsEvent_writeInt32Array(statsEvent, int32Array, 2);
- break;
- case FLOAT_TYPE:
- AStatsEvent_writeFloat(statsEvent, 1.3f);
- break;
- case BOOL_TYPE:
- AStatsEvent_writeBool(statsEvent, 1);
- break;
- case BYTE_ARRAY_TYPE:
- AStatsEvent_writeByteArray(statsEvent, (uint8_t*)"test", strlen("test"));
- break;
- case ATTRIBUTION_CHAIN_TYPE:
- AStatsEvent_writeAttributionChain(statsEvent, uids, tags, 2);
- break;
- default:
- break;
- }
-}
-
-void createFieldWithBoolAnnotationLogEvent(LogEvent* logEvent, uint8_t typeId, uint8_t annotationId,
- bool annotationValue, bool parseBufferResult) {
+bool createFieldWithBoolAnnotationLogEvent(LogEvent* logEvent, uint8_t typeId, uint8_t annotationId,
+ bool annotationValue, bool doHeaderPrefetch) {
AStatsEvent* statsEvent = AStatsEvent_obtain();
- createStatsEvent(statsEvent, typeId);
+ createStatsEvent(statsEvent, typeId, /*atomId=*/100);
AStatsEvent_addBoolAnnotation(statsEvent, annotationId, annotationValue);
AStatsEvent_build(statsEvent);
size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
- EXPECT_EQ(parseBufferResult, logEvent->parseBuffer(buf, size));
-
+ const uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+ if (doHeaderPrefetch) {
+ // Testing LogEvent header prefetch logic
+ const LogEvent::BodyBufferInfo bodyInfo = logEvent->parseHeader(buf, size);
+ logEvent->parseBody(bodyInfo);
+ } else {
+ logEvent->parseBuffer(buf, size);
+ }
AStatsEvent_release(statsEvent);
+
+ return logEvent->isValid();
}
-void createFieldWithIntAnnotationLogEvent(LogEvent* logEvent, uint8_t typeId, uint8_t annotationId,
- int annotationValue, bool parseBufferResult) {
+bool createFieldWithIntAnnotationLogEvent(LogEvent* logEvent, uint8_t typeId, uint8_t annotationId,
+ int annotationValue, bool doHeaderPrefetch) {
AStatsEvent* statsEvent = AStatsEvent_obtain();
- createStatsEvent(statsEvent, typeId);
+ createStatsEvent(statsEvent, typeId, /*atomId=*/100);
AStatsEvent_addInt32Annotation(statsEvent, annotationId, annotationValue);
AStatsEvent_build(statsEvent);
size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
- EXPECT_EQ(parseBufferResult, logEvent->parseBuffer(buf, size));
-
+ const uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+ if (doHeaderPrefetch) {
+ // Testing LogEvent header prefetch logic
+ const LogEvent::BodyBufferInfo bodyInfo = logEvent->parseHeader(buf, size);
+ logEvent->parseBody(bodyInfo);
+ } else {
+ logEvent->parseBuffer(buf, size);
+ }
AStatsEvent_release(statsEvent);
+
+ return logEvent->isValid();
+}
+
+bool createAtomLevelIntAnnotationLogEvent(LogEvent* logEvent, uint8_t typeId, uint8_t annotationId,
+ int annotationValue, bool doHeaderPrefetch) {
+ AStatsEvent* statsEvent = AStatsEvent_obtain();
+ AStatsEvent_setAtomId(statsEvent, /*atomId=*/100);
+ AStatsEvent_addInt32Annotation(statsEvent, annotationId, annotationValue);
+ fillStatsEventWithSampleValue(statsEvent, typeId);
+ AStatsEvent_build(statsEvent);
+
+ size_t size;
+ const uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+ if (doHeaderPrefetch) {
+ // Testing LogEvent header prefetch logic
+ const LogEvent::BodyBufferInfo bodyInfo = logEvent->parseHeader(buf, size);
+ logEvent->parseBody(bodyInfo);
+ } else {
+ logEvent->parseBuffer(buf, size);
+ }
+ AStatsEvent_release(statsEvent);
+
+ return logEvent->isValid();
+}
+
+bool createAtomLevelBoolAnnotationLogEvent(LogEvent* logEvent, uint8_t typeId, uint8_t annotationId,
+ bool annotationValue, bool doHeaderPrefetch) {
+ AStatsEvent* statsEvent = AStatsEvent_obtain();
+ AStatsEvent_setAtomId(statsEvent, /*atomId=*/100);
+ AStatsEvent_addBoolAnnotation(statsEvent, annotationId, annotationValue);
+ fillStatsEventWithSampleValue(statsEvent, typeId);
+ AStatsEvent_build(statsEvent);
+
+ size_t size;
+ const uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+ if (doHeaderPrefetch) {
+ // Testing LogEvent header prefetch logic
+ const LogEvent::BodyBufferInfo bodyInfo = logEvent->parseHeader(buf, size);
+ logEvent->parseBody(bodyInfo);
+ } else {
+ logEvent->parseBuffer(buf, size);
+ }
+ AStatsEvent_release(statsEvent);
+
+ return logEvent->isValid();
}
} // anonymous namespace
// Setup for parameterized tests.
-class LogEventTestBadAnnotationFieldTypes : public testing::TestWithParam<int> {
+class LogEventTestBadAnnotationFieldTypes : public testing::TestWithParam<std::tuple<int, bool>> {
public:
- static std::string ToString(testing::TestParamInfo<int> info) {
- switch (info.param) {
+ static std::string ToString(testing::TestParamInfo<std::tuple<int, bool>> info) {
+ const std::string boolName = std::get<1>(info.param) ? "_prefetchTrue" : "_prefetchFalse";
+
+ switch (std::get<0>(info.param)) {
case INT32_TYPE:
- return "Int32";
+ return "Int32" + boolName;
case INT64_TYPE:
- return "Int64";
+ return "Int64" + boolName;
case STRING_TYPE:
- return "String";
+ return "String" + boolName;
case LIST_TYPE:
- return "List";
+ return "List" + boolName;
case FLOAT_TYPE:
- return "Float";
+ return "Float" + boolName;
case BYTE_ARRAY_TYPE:
- return "ByteArray";
+ return "ByteArray" + boolName;
case ATTRIBUTION_CHAIN_TYPE:
- return "AttributionChain";
+ return "AttributionChain" + boolName;
default:
- return "Unknown";
+ return "Unknown" + boolName;
}
}
};
@@ -138,11 +166,40 @@
// TODO(b/222539899): Add BOOL_TYPE value once parseAnnotations is updated to check specific
// typeIds. BOOL_TYPE should be a bad field type for is_uid, nested, and reset state annotations.
INSTANTIATE_TEST_SUITE_P(BadAnnotationFieldTypes, LogEventTestBadAnnotationFieldTypes,
- testing::Values(INT32_TYPE, INT64_TYPE, STRING_TYPE, LIST_TYPE, FLOAT_TYPE,
- BYTE_ARRAY_TYPE, ATTRIBUTION_CHAIN_TYPE),
+ testing::Combine(testing::Values(INT32_TYPE, INT64_TYPE, STRING_TYPE,
+ LIST_TYPE, FLOAT_TYPE, BYTE_ARRAY_TYPE,
+ ATTRIBUTION_CHAIN_TYPE),
+ testing::Bool()),
LogEventTestBadAnnotationFieldTypes::ToString);
-TEST(LogEventTest, TestPrimitiveParsing) {
+class LogEventTest : public testing::TestWithParam<bool> {
+public:
+ bool ParseBuffer(LogEvent& logEvent, const uint8_t* buf, size_t size) {
+ size_t bufferOffset = 0;
+ if (GetParam()) {
+ // Testing LogEvent header prefetch logic
+ const LogEvent::BodyBufferInfo bodyInfo = logEvent.parseHeader(buf, size);
+ EXPECT_TRUE(logEvent.isParsedHeaderOnly());
+ const bool parseResult = logEvent.parseBody(bodyInfo);
+ EXPECT_EQ(parseResult, logEvent.isValid());
+ EXPECT_FALSE(logEvent.isParsedHeaderOnly());
+ } else {
+ const bool parseResult = logEvent.parseBuffer(buf, size);
+ EXPECT_EQ(parseResult, logEvent.isValid());
+ EXPECT_FALSE(logEvent.isParsedHeaderOnly());
+ }
+ return logEvent.isValid();
+ }
+
+ static std::string ToString(testing::TestParamInfo<bool> info) {
+ return info.param ? "PrefetchTrue" : "PrefetchFalse";
+ }
+};
+
+INSTANTIATE_TEST_SUITE_P(LogEventTestBufferParsing, LogEventTest, testing::Bool(),
+ LogEventTest::ToString);
+
+TEST_P(LogEventTest, TestPrimitiveParsing) {
AStatsEvent* event = AStatsEvent_obtain();
AStatsEvent_setAtomId(event, 100);
AStatsEvent_writeInt32(event, 10);
@@ -152,10 +209,10 @@
AStatsEvent_build(event);
size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(event, &size);
+ const uint8_t* buf = AStatsEvent_getBuffer(event, &size);
LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
- EXPECT_TRUE(logEvent.parseBuffer(buf, size));
+ EXPECT_TRUE(ParseBuffer(logEvent, buf, size));
EXPECT_EQ(100, logEvent.GetTagId());
EXPECT_EQ(1000, logEvent.GetUid());
@@ -192,7 +249,33 @@
AStatsEvent_release(event);
}
-TEST(LogEventTest, TestFetchHeaderOnly) {
+TEST_P(LogEventTest, TestEventWithInvalidHeaderParsing) {
+ AStatsEvent* event = AStatsEvent_obtain();
+ AStatsEvent_setAtomId(event, 100);
+ AStatsEvent_writeInt32(event, 10);
+ AStatsEvent_writeInt64(event, 0x123456789);
+ AStatsEvent_writeFloat(event, 2.0);
+ AStatsEvent_writeBool(event, true);
+ AStatsEvent_build(event);
+
+ size_t size;
+ const uint8_t* buf = AStatsEvent_getBuffer(event, &size);
+
+ // Corrupt LogEvent header info
+ // OBJECT_TYPE | NUM_FIELDS | TIMESTAMP | ATOM_ID
+ // Corrupting first 4 bytes will be sufficient
+ uint8_t* bufMod = const_cast<uint8_t*>(buf);
+ memset(static_cast<void*>(bufMod), 4, ERROR_TYPE);
+
+ LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
+ EXPECT_FALSE(ParseBuffer(logEvent, buf, size));
+ EXPECT_FALSE(logEvent.isValid());
+ EXPECT_FALSE(logEvent.isParsedHeaderOnly());
+
+ AStatsEvent_release(event);
+}
+
+TEST(LogEventTestParsing, TestFetchHeaderOnly) {
AStatsEvent* event = AStatsEvent_obtain();
AStatsEvent_setAtomId(event, 100);
AStatsEvent_writeInt32(event, 10);
@@ -205,7 +288,9 @@
const uint8_t* buf = AStatsEvent_getBuffer(event, &size);
LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
- EXPECT_TRUE(logEvent.parseBuffer(buf, size, /*fetchHeaderOnly=*/true));
+ const LogEvent::BodyBufferInfo bodyInfo = logEvent.parseHeader(buf, size);
+ EXPECT_TRUE(logEvent.isValid());
+ EXPECT_TRUE(logEvent.isParsedHeaderOnly());
AStatsEvent_release(event);
@@ -213,11 +298,10 @@
EXPECT_EQ(1000, logEvent.GetUid());
EXPECT_EQ(1001, logEvent.GetPid());
EXPECT_FALSE(logEvent.hasAttributionChain());
-
- ASSERT_EQ(logEvent.getValues().size(), 0);
+ ASSERT_EQ(0, logEvent.getValues().size());
}
-TEST(LogEventTest, TestStringAndByteArrayParsing) {
+TEST_P(LogEventTest, TestStringAndByteArrayParsing) {
AStatsEvent* event = AStatsEvent_obtain();
AStatsEvent_setAtomId(event, 100);
string str = "test";
@@ -226,10 +310,10 @@
AStatsEvent_build(event);
size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(event, &size);
+ const uint8_t* buf = AStatsEvent_getBuffer(event, &size);
LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
- EXPECT_TRUE(logEvent.parseBuffer(buf, size));
+ EXPECT_TRUE(ParseBuffer(logEvent, buf, size));
EXPECT_EQ(100, logEvent.GetTagId());
EXPECT_EQ(1000, logEvent.GetUid());
@@ -255,7 +339,7 @@
AStatsEvent_release(event);
}
-TEST(LogEventTest, TestEmptyString) {
+TEST_P(LogEventTest, TestEmptyString) {
AStatsEvent* event = AStatsEvent_obtain();
AStatsEvent_setAtomId(event, 100);
string empty = "";
@@ -263,10 +347,10 @@
AStatsEvent_build(event);
size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(event, &size);
+ const uint8_t* buf = AStatsEvent_getBuffer(event, &size);
LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
- EXPECT_TRUE(logEvent.parseBuffer(buf, size));
+ EXPECT_TRUE(ParseBuffer(logEvent, buf, size));
EXPECT_EQ(100, logEvent.GetTagId());
EXPECT_EQ(1000, logEvent.GetUid());
@@ -285,7 +369,7 @@
AStatsEvent_release(event);
}
-TEST(LogEventTest, TestByteArrayWithNullCharacter) {
+TEST_P(LogEventTest, TestByteArrayWithNullCharacter) {
AStatsEvent* event = AStatsEvent_obtain();
AStatsEvent_setAtomId(event, 100);
uint8_t message[] = {'\t', 'e', '\0', 's', 't'};
@@ -293,10 +377,10 @@
AStatsEvent_build(event);
size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(event, &size);
+ const uint8_t* buf = AStatsEvent_getBuffer(event, &size);
LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
- EXPECT_TRUE(logEvent.parseBuffer(buf, size));
+ EXPECT_TRUE(ParseBuffer(logEvent, buf, size));
EXPECT_EQ(100, logEvent.GetTagId());
EXPECT_EQ(1000, logEvent.GetUid());
@@ -315,7 +399,7 @@
AStatsEvent_release(event);
}
-TEST(LogEventTest, TestTooManyTopLevelElements) {
+TEST_P(LogEventTest, TestTooManyTopLevelElements) {
int32_t numElements = 128;
AStatsEvent* event = AStatsEvent_obtain();
AStatsEvent_setAtomId(event, 100);
@@ -327,14 +411,14 @@
AStatsEvent_build(event);
size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(event, &size);
+ const uint8_t* buf = AStatsEvent_getBuffer(event, &size);
LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
- EXPECT_FALSE(logEvent.parseBuffer(buf, size));
+ EXPECT_FALSE(ParseBuffer(logEvent, buf, size));
AStatsEvent_release(event);
}
-TEST(LogEventTest, TestAttributionChain) {
+TEST_P(LogEventTest, TestAttributionChain) {
AStatsEvent* event = AStatsEvent_obtain();
AStatsEvent_setAtomId(event, 100);
@@ -348,10 +432,10 @@
AStatsEvent_build(event);
size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(event, &size);
+ const uint8_t* buf = AStatsEvent_getBuffer(event, &size);
LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
- EXPECT_TRUE(logEvent.parseBuffer(buf, size));
+ EXPECT_TRUE(ParseBuffer(logEvent, buf, size));
EXPECT_EQ(100, logEvent.GetTagId());
EXPECT_EQ(1000, logEvent.GetUid());
@@ -394,7 +478,7 @@
AStatsEvent_release(event);
}
-TEST(LogEventTest, TestEmptyAttributionChain) {
+TEST_P(LogEventTest, TestEmptyAttributionChain) {
AStatsEvent* event = AStatsEvent_obtain();
AStatsEvent_setAtomId(event, 100);
@@ -403,15 +487,15 @@
AStatsEvent_build(event);
size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(event, &size);
+ const uint8_t* buf = AStatsEvent_getBuffer(event, &size);
LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
- EXPECT_FALSE(logEvent.parseBuffer(buf, size));
+ EXPECT_FALSE(ParseBuffer(logEvent, buf, size));
AStatsEvent_release(event);
}
-TEST(LogEventTest, TestAttributionChainTooManyElements) {
+TEST_P(LogEventTest, TestAttributionChainTooManyElements) {
int32_t numNodes = 128;
uint32_t uids[numNodes];
vector<string> tags(numNodes); // storage that cTag elements point to
@@ -429,14 +513,14 @@
AStatsEvent_build(event);
size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(event, &size);
+ const uint8_t* buf = AStatsEvent_getBuffer(event, &size);
LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
- EXPECT_FALSE(logEvent.parseBuffer(buf, size));
+ EXPECT_FALSE(ParseBuffer(logEvent, buf, size));
AStatsEvent_release(event);
}
-TEST(LogEventTest, TestArrayParsing) {
+TEST_P(LogEventTest, TestArrayParsing) {
size_t numElements = 2;
int32_t int32Array[2] = {3, 6};
int64_t int64Array[2] = {1000L, 1002L};
@@ -459,10 +543,10 @@
AStatsEvent_build(event);
size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(event, &size);
+ const uint8_t* buf = AStatsEvent_getBuffer(event, &size);
LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
- EXPECT_TRUE(logEvent.parseBuffer(buf, size));
+ EXPECT_TRUE(ParseBuffer(logEvent, buf, size));
EXPECT_EQ(100, logEvent.GetTagId());
EXPECT_EQ(1000, logEvent.GetUid());
@@ -535,7 +619,7 @@
EXPECT_EQ("str2", stringArrayItem2.mValue.str_value);
}
-TEST(LogEventTest, TestEmptyStringArray) {
+TEST_P(LogEventTest, TestEmptyStringArray) {
const char* cStringArray[2];
string empty = "";
cStringArray[0] = empty.c_str();
@@ -547,10 +631,10 @@
AStatsEvent_build(event);
size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(event, &size);
+ const uint8_t* buf = AStatsEvent_getBuffer(event, &size);
LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
- EXPECT_TRUE(logEvent.parseBuffer(buf, size));
+ EXPECT_TRUE(ParseBuffer(logEvent, buf, size));
EXPECT_EQ(100, logEvent.GetTagId());
EXPECT_EQ(1000, logEvent.GetUid());
@@ -574,7 +658,7 @@
AStatsEvent_release(event);
}
-TEST(LogEventTest, TestArrayTooManyElements) {
+TEST_P(LogEventTest, TestArrayTooManyElements) {
int32_t numElements = 128;
int32_t int32Array[numElements];
@@ -588,15 +672,15 @@
AStatsEvent_build(event);
size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(event, &size);
+ const uint8_t* buf = AStatsEvent_getBuffer(event, &size);
LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
- EXPECT_FALSE(logEvent.parseBuffer(buf, size));
+ EXPECT_FALSE(ParseBuffer(logEvent, buf, size));
AStatsEvent_release(event);
}
-TEST(LogEventTest, TestEmptyArray) {
+TEST_P(LogEventTest, TestEmptyArray) {
int32_t int32Array[0] = {};
AStatsEvent* event = AStatsEvent_obtain();
@@ -605,10 +689,10 @@
AStatsEvent_build(event);
size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(event, &size);
+ const uint8_t* buf = AStatsEvent_getBuffer(event, &size);
LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
- EXPECT_TRUE(logEvent.parseBuffer(buf, size));
+ EXPECT_TRUE(ParseBuffer(logEvent, buf, size));
EXPECT_EQ(100, logEvent.GetTagId());
EXPECT_EQ(1000, logEvent.GetUid());
@@ -619,10 +703,11 @@
AStatsEvent_release(event);
}
-TEST(LogEventTest, TestAnnotationIdIsUid) {
+TEST_P(LogEventTest, TestAnnotationIdIsUid) {
LogEvent event(/*uid=*/0, /*pid=*/0);
- createFieldWithBoolAnnotationLogEvent(&event, INT32_TYPE, ANNOTATION_ID_IS_UID, true,
- /*parseBufferResult*/ true);
+ EXPECT_TRUE(createFieldWithBoolAnnotationLogEvent(&event, INT32_TYPE,
+ ASTATSLOG_ANNOTATION_ID_IS_UID, true,
+ /*doHeaderPrefetch=*/GetParam()));
ASSERT_EQ(event.getNumUidFields(), 1);
@@ -631,7 +716,7 @@
EXPECT_TRUE(isUidField(values.at(0)));
}
-TEST(LogEventTest, TestAnnotationIdIsUid_RepeatedIntAndOtherFields) {
+TEST_P(LogEventTest, TestAnnotationIdIsUid_RepeatedIntAndOtherFields) {
size_t numElements = 2;
int32_t int32Array[2] = {3, 6};
@@ -645,14 +730,14 @@
AStatsEvent_setAtomId(statsEvent, 100);
AStatsEvent_writeInt32(statsEvent, 5);
AStatsEvent_writeInt32Array(statsEvent, int32Array, numElements);
- AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true);
+ AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
AStatsEvent_writeStringArray(statsEvent, cStringArray, numElements);
AStatsEvent_build(statsEvent);
size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+ const uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
- EXPECT_TRUE(logEvent.parseBuffer(buf, size));
+ EXPECT_TRUE(ParseBuffer(logEvent, buf, size));
EXPECT_EQ(2, logEvent.getNumUidFields());
const vector<FieldValue>& values = logEvent.getValues();
@@ -664,20 +749,20 @@
EXPECT_FALSE(isUidField(values.at(4)));
}
-TEST(LogEventTest, TestAnnotationIdIsUid_RepeatedIntOneEntry) {
+TEST_P(LogEventTest, TestAnnotationIdIsUid_RepeatedIntOneEntry) {
size_t numElements = 1;
int32_t int32Array[1] = {3};
AStatsEvent* statsEvent = AStatsEvent_obtain();
AStatsEvent_setAtomId(statsEvent, 100);
AStatsEvent_writeInt32Array(statsEvent, int32Array, numElements);
- AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true);
+ AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
AStatsEvent_build(statsEvent);
size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+ const uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
- EXPECT_TRUE(logEvent.parseBuffer(buf, size));
+ EXPECT_TRUE(ParseBuffer(logEvent, buf, size));
EXPECT_EQ(1, logEvent.getNumUidFields());
const vector<FieldValue>& values = logEvent.getValues();
@@ -685,46 +770,46 @@
EXPECT_TRUE(isUidField(values.at(0)));
}
-TEST(LogEventTest, TestAnnotationIdIsUid_EmptyIntArray) {
+TEST_P(LogEventTest, TestAnnotationIdIsUid_EmptyIntArray) {
int32_t int32Array[0] = {};
AStatsEvent* statsEvent = AStatsEvent_obtain();
AStatsEvent_setAtomId(statsEvent, 100);
AStatsEvent_writeInt32Array(statsEvent, int32Array, /*numElements*/ 0);
- AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true);
+ AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
AStatsEvent_writeInt32(statsEvent, 5);
AStatsEvent_build(statsEvent);
size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+ const uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
- EXPECT_TRUE(logEvent.parseBuffer(buf, size));
+ EXPECT_TRUE(ParseBuffer(logEvent, buf, size));
EXPECT_EQ(0, logEvent.getNumUidFields());
const vector<FieldValue>& values = logEvent.getValues();
EXPECT_EQ(values.size(), 1);
}
-TEST(LogEventTest, TestAnnotationIdIsUid_BadRepeatedInt64) {
+TEST_P(LogEventTest, TestAnnotationIdIsUid_BadRepeatedInt64) {
int64_t int64Array[2] = {1000L, 1002L};
AStatsEvent* statsEvent = AStatsEvent_obtain();
AStatsEvent_setAtomId(statsEvent, /*atomId=*/100);
AStatsEvent_writeInt64Array(statsEvent, int64Array, /*numElements*/ 2);
- AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true);
+ AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
AStatsEvent_build(statsEvent);
size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+ const uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
- EXPECT_FALSE(logEvent.parseBuffer(buf, size));
+ EXPECT_FALSE(ParseBuffer(logEvent, buf, size));
EXPECT_EQ(0, logEvent.getNumUidFields());
AStatsEvent_release(statsEvent);
}
-TEST(LogEventTest, TestAnnotationIdIsUid_BadRepeatedString) {
+TEST_P(LogEventTest, TestAnnotationIdIsUid_BadRepeatedString) {
size_t numElements = 2;
vector<string> stringArray = {"str1", "str2"};
const char* cStringArray[2];
@@ -735,14 +820,14 @@
AStatsEvent* statsEvent = AStatsEvent_obtain();
AStatsEvent_setAtomId(statsEvent, /*atomId=*/100);
AStatsEvent_writeStringArray(statsEvent, cStringArray, /*numElements*/ 2);
- AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true);
+ AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
AStatsEvent_build(statsEvent);
size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+ const uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
- EXPECT_FALSE(logEvent.parseBuffer(buf, size));
+ EXPECT_FALSE(ParseBuffer(logEvent, buf, size));
EXPECT_EQ(0, logEvent.getNumUidFields());
AStatsEvent_release(statsEvent);
@@ -751,22 +836,25 @@
TEST_P(LogEventTestBadAnnotationFieldTypes, TestAnnotationIdIsUid) {
LogEvent event(/*uid=*/0, /*pid=*/0);
- if (GetParam() != INT32_TYPE && GetParam() != LIST_TYPE) {
- createFieldWithBoolAnnotationLogEvent(&event, GetParam(), ANNOTATION_ID_IS_UID, true,
- /*parseBufferResult*/ false);
+ if (std::get<0>(GetParam()) != INT32_TYPE && std::get<0>(GetParam()) != LIST_TYPE) {
+ EXPECT_FALSE(createFieldWithBoolAnnotationLogEvent(
+ &event, std::get<0>(GetParam()), ASTATSLOG_ANNOTATION_ID_IS_UID, true,
+ /*doHeaderPrefetch=*/std::get<1>(GetParam())));
}
}
-TEST(LogEventTest, TestAnnotationIdIsUid_NotIntAnnotation) {
+TEST_P(LogEventTest, TestAnnotationIdIsUid_NotIntAnnotation) {
LogEvent event(/*uid=*/0, /*pid=*/0);
- createFieldWithIntAnnotationLogEvent(&event, INT32_TYPE, ANNOTATION_ID_IS_UID, 10,
- /*parseBufferResult*/ false);
+ EXPECT_FALSE(createFieldWithIntAnnotationLogEvent(&event, INT32_TYPE,
+ ASTATSLOG_ANNOTATION_ID_IS_UID, 10,
+ /*doHeaderPrefetch=*/GetParam()));
}
-TEST(LogEventTest, TestAnnotationIdStateNested) {
+TEST_P(LogEventTest, TestAnnotationIdStateNested) {
LogEvent event(/*uid=*/0, /*pid=*/0);
- createFieldWithBoolAnnotationLogEvent(&event, INT32_TYPE, ANNOTATION_ID_STATE_NESTED, true,
- /*parseBufferResult*/ true);
+ EXPECT_TRUE(createFieldWithBoolAnnotationLogEvent(&event, INT32_TYPE,
+ ASTATSLOG_ANNOTATION_ID_STATE_NESTED, true,
+ /*doHeaderPrefetch=*/GetParam()));
const vector<FieldValue>& values = event.getValues();
ASSERT_EQ(values.size(), 1);
@@ -776,22 +864,25 @@
TEST_P(LogEventTestBadAnnotationFieldTypes, TestAnnotationIdStateNested) {
LogEvent event(/*uid=*/0, /*pid=*/0);
- if (GetParam() != INT32_TYPE) {
- createFieldWithBoolAnnotationLogEvent(&event, GetParam(), ANNOTATION_ID_STATE_NESTED, true,
- /*parseBufferResult*/ false);
+ if (std::get<0>(GetParam()) != INT32_TYPE) {
+ EXPECT_FALSE(createFieldWithBoolAnnotationLogEvent(
+ &event, std::get<0>(GetParam()), ASTATSLOG_ANNOTATION_ID_STATE_NESTED, true,
+ /*doHeaderPrefetch=*/std::get<1>(GetParam())));
}
}
-TEST(LogEventTest, TestAnnotationIdStateNested_NotIntAnnotation) {
+TEST_P(LogEventTest, TestAnnotationIdStateNested_NotIntAnnotation) {
LogEvent event(/*uid=*/0, /*pid=*/0);
- createFieldWithIntAnnotationLogEvent(&event, INT32_TYPE, ANNOTATION_ID_STATE_NESTED, 10,
- /*parseBufferResult*/ false);
+ EXPECT_FALSE(createFieldWithIntAnnotationLogEvent(&event, INT32_TYPE,
+ ASTATSLOG_ANNOTATION_ID_STATE_NESTED, 10,
+ /*doHeaderPrefetch=*/GetParam()));
}
-TEST(LogEventTest, TestPrimaryFieldAnnotation) {
+TEST_P(LogEventTest, TestPrimaryFieldAnnotation) {
LogEvent event(/*uid=*/0, /*pid=*/0);
- createFieldWithBoolAnnotationLogEvent(&event, INT32_TYPE, ANNOTATION_ID_PRIMARY_FIELD, true,
- /*parseBufferResult*/ true);
+ EXPECT_TRUE(createFieldWithBoolAnnotationLogEvent(&event, INT32_TYPE,
+ ASTATSLOG_ANNOTATION_ID_PRIMARY_FIELD, true,
+ /*doHeaderPrefetch=*/GetParam()));
const vector<FieldValue>& values = event.getValues();
ASSERT_EQ(values.size(), 1);
@@ -801,22 +892,25 @@
TEST_P(LogEventTestBadAnnotationFieldTypes, TestPrimaryFieldAnnotation) {
LogEvent event(/*uid=*/0, /*pid=*/0);
- if (GetParam() == LIST_TYPE || GetParam() == ATTRIBUTION_CHAIN_TYPE) {
- createFieldWithBoolAnnotationLogEvent(&event, GetParam(), ANNOTATION_ID_PRIMARY_FIELD, true,
- /*parseBufferResult*/ false);
+ if (std::get<0>(GetParam()) == LIST_TYPE || std::get<0>(GetParam()) == ATTRIBUTION_CHAIN_TYPE) {
+ EXPECT_FALSE(createFieldWithBoolAnnotationLogEvent(
+ &event, std::get<0>(GetParam()), ASTATSLOG_ANNOTATION_ID_PRIMARY_FIELD, true,
+ /*doHeaderPrefetch=*/std::get<1>(GetParam())));
}
}
-TEST(LogEventTest, TestPrimaryFieldAnnotation_NotIntAnnotation) {
+TEST_P(LogEventTest, TestPrimaryFieldAnnotation_NotIntAnnotation) {
LogEvent event(/*uid=*/0, /*pid=*/0);
- createFieldWithIntAnnotationLogEvent(&event, INT32_TYPE, ANNOTATION_ID_PRIMARY_FIELD, 10,
- /*parseBufferResult*/ false);
+ EXPECT_FALSE(createFieldWithIntAnnotationLogEvent(&event, INT32_TYPE,
+ ASTATSLOG_ANNOTATION_ID_PRIMARY_FIELD, 10,
+ /*doHeaderPrefetch=*/GetParam()));
}
-TEST(LogEventTest, TestExclusiveStateAnnotation) {
+TEST_P(LogEventTest, TestExclusiveStateAnnotation) {
LogEvent event(/*uid=*/0, /*pid=*/0);
- createFieldWithBoolAnnotationLogEvent(&event, INT32_TYPE, ANNOTATION_ID_EXCLUSIVE_STATE, true,
- /*parseBufferResult*/ true);
+ EXPECT_TRUE(createFieldWithBoolAnnotationLogEvent(&event, INT32_TYPE,
+ ASTATSLOG_ANNOTATION_ID_EXCLUSIVE_STATE, true,
+ /*doHeaderPrefetch=*/GetParam()));
const vector<FieldValue>& values = event.getValues();
ASSERT_EQ(values.size(), 1);
@@ -826,20 +920,21 @@
TEST_P(LogEventTestBadAnnotationFieldTypes, TestExclusiveStateAnnotation) {
LogEvent event(/*uid=*/0, /*pid=*/0);
- if (GetParam() != INT32_TYPE) {
- createFieldWithBoolAnnotationLogEvent(&event, GetParam(), ANNOTATION_ID_EXCLUSIVE_STATE,
- true,
- /*parseBufferResult*/ false);
+ if (std::get<0>(GetParam()) != INT32_TYPE) {
+ EXPECT_FALSE(createFieldWithBoolAnnotationLogEvent(
+ &event, std::get<0>(GetParam()), ASTATSLOG_ANNOTATION_ID_EXCLUSIVE_STATE, true,
+ /*doHeaderPrefetch=*/std::get<1>(GetParam())));
}
}
-TEST(LogEventTest, TestExclusiveStateAnnotation_NotIntAnnotation) {
+TEST_P(LogEventTest, TestExclusiveStateAnnotation_NotIntAnnotation) {
LogEvent event(/*uid=*/0, /*pid=*/0);
- createFieldWithIntAnnotationLogEvent(&event, INT32_TYPE, ANNOTATION_ID_EXCLUSIVE_STATE, 10,
- /*parseBufferResult*/ false);
+ EXPECT_FALSE(createFieldWithIntAnnotationLogEvent(&event, INT32_TYPE,
+ ASTATSLOG_ANNOTATION_ID_EXCLUSIVE_STATE, 10,
+ /*doHeaderPrefetch=*/GetParam()));
}
-TEST(LogEventTest, TestPrimaryFieldFirstUidAnnotation) {
+TEST_P(LogEventTest, TestPrimaryFieldFirstUidAnnotation) {
// Event has 10 ints and then an attribution chain
int numInts = 10;
int firstUidInChainIndex = numInts;
@@ -855,14 +950,15 @@
AStatsEvent_writeInt32(statsEvent, 10);
}
AStatsEvent_writeAttributionChain(statsEvent, uids, tags, 2);
- AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID, true);
+ AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID,
+ true);
AStatsEvent_build(statsEvent);
// Construct LogEvent
size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+ const uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
LogEvent logEvent(/*uid=*/0, /*pid=*/0);
- EXPECT_TRUE(logEvent.parseBuffer(buf, size));
+ EXPECT_TRUE(ParseBuffer(logEvent, buf, size));
AStatsEvent_release(statsEvent);
// Check annotation
@@ -874,50 +970,88 @@
TEST_P(LogEventTestBadAnnotationFieldTypes, TestPrimaryFieldFirstUidAnnotation) {
LogEvent event(/*uid=*/0, /*pid=*/0);
- if (GetParam() != ATTRIBUTION_CHAIN_TYPE) {
- createFieldWithBoolAnnotationLogEvent(&event, GetParam(),
- ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID, true,
- /*parseBufferResult*/ false);
+ if (std::get<0>(GetParam()) != ATTRIBUTION_CHAIN_TYPE) {
+ EXPECT_FALSE(createFieldWithBoolAnnotationLogEvent(
+ &event, std::get<0>(GetParam()), ASTATSLOG_ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID,
+ true,
+ /*doHeaderPrefetch=*/std::get<1>(GetParam())));
}
}
-TEST(LogEventTest, TestPrimaryFieldFirstUidAnnotation_NotIntAnnotation) {
+TEST_P(LogEventTest, TestPrimaryFieldFirstUidAnnotation_NotIntAnnotation) {
LogEvent event(/*uid=*/0, /*pid=*/0);
- createFieldWithIntAnnotationLogEvent(&event, ATTRIBUTION_CHAIN_TYPE,
- ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID, 10,
- /*parseBufferResult*/ false);
+ EXPECT_FALSE(createFieldWithIntAnnotationLogEvent(
+ &event, ATTRIBUTION_CHAIN_TYPE, ASTATSLOG_ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID, 10,
+ /*doHeaderPrefetch=*/GetParam()));
}
-TEST(LogEventTest, TestResetStateAnnotation) {
+TEST_P(LogEventTest, TestResetStateAnnotation) {
int32_t resetState = 10;
LogEvent event(/*uid=*/0, /*pid=*/0);
- createFieldWithIntAnnotationLogEvent(&event, INT32_TYPE, ANNOTATION_ID_TRIGGER_STATE_RESET,
- resetState, /*parseBufferResult*/ true);
+ EXPECT_TRUE(createFieldWithIntAnnotationLogEvent(
+ &event, INT32_TYPE, ASTATSLOG_ANNOTATION_ID_TRIGGER_STATE_RESET, resetState,
+ /*doHeaderPrefetch=*/GetParam()));
const vector<FieldValue>& values = event.getValues();
ASSERT_EQ(values.size(), 1);
EXPECT_EQ(event.getResetState(), resetState);
}
+TEST_P(LogEventTest, TestRestrictionCategoryAnnotation) {
+ if (!IsAtLeastU()) {
+ GTEST_SKIP();
+ }
+ int32_t restrictionCategory = ASTATSLOG_RESTRICTION_CATEGORY_DIAGNOSTIC;
+ LogEvent event(/*uid=*/0, /*pid=*/0);
+ EXPECT_TRUE(createAtomLevelIntAnnotationLogEvent(
+ &event, INT32_TYPE, ASTATSLOG_ANNOTATION_ID_RESTRICTION_CATEGORY, restrictionCategory,
+ /*doHeaderPrefetch=*/GetParam()));
+
+ ASSERT_EQ(event.getRestrictionCategory(), restrictionCategory);
+}
+
+TEST_P(LogEventTest, TestInvalidRestrictionCategoryAnnotation) {
+ if (!IsAtLeastU()) {
+ GTEST_SKIP();
+ }
+ int32_t restrictionCategory = 619; // unknown category
+ LogEvent event(/*uid=*/0, /*pid=*/0);
+ EXPECT_FALSE(createAtomLevelIntAnnotationLogEvent(
+ &event, INT32_TYPE, ASTATSLOG_ANNOTATION_ID_RESTRICTION_CATEGORY, restrictionCategory,
+ /*doHeaderPrefetch=*/GetParam()));
+}
+
+TEST_P(LogEventTest, TestRestrictionCategoryAnnotationBelowUDevice) {
+ if (IsAtLeastU()) {
+ GTEST_SKIP();
+ }
+ int32_t restrictionCategory = ASTATSLOG_RESTRICTION_CATEGORY_DIAGNOSTIC;
+ LogEvent event(/*uid=*/0, /*pid=*/0);
+ EXPECT_FALSE(createAtomLevelIntAnnotationLogEvent(
+ &event, INT32_TYPE, ASTATSLOG_ANNOTATION_ID_RESTRICTION_CATEGORY, restrictionCategory,
+ /*doHeaderPrefetch=*/GetParam()));
+}
+
TEST_P(LogEventTestBadAnnotationFieldTypes, TestResetStateAnnotation) {
LogEvent event(/*uid=*/0, /*pid=*/0);
int32_t resetState = 10;
- if (GetParam() != INT32_TYPE) {
- createFieldWithIntAnnotationLogEvent(&event, GetParam(), ANNOTATION_ID_TRIGGER_STATE_RESET,
- resetState,
- /*parseBufferResult*/ false);
+ if (std::get<0>(GetParam()) != INT32_TYPE) {
+ EXPECT_FALSE(createFieldWithIntAnnotationLogEvent(
+ &event, std::get<0>(GetParam()), ASTATSLOG_ANNOTATION_ID_TRIGGER_STATE_RESET,
+ resetState,
+ /*doHeaderPrefetch=*/std::get<1>(GetParam())));
}
}
-TEST(LogEventTest, TestResetStateAnnotation_NotBoolAnnotation) {
+TEST_P(LogEventTest, TestResetStateAnnotation_NotBoolAnnotation) {
LogEvent event(/*uid=*/0, /*pid=*/0);
- createFieldWithBoolAnnotationLogEvent(&event, INT32_TYPE, ANNOTATION_ID_TRIGGER_STATE_RESET,
- true,
- /*parseBufferResult*/ false);
+ EXPECT_FALSE(createFieldWithBoolAnnotationLogEvent(
+ &event, INT32_TYPE, ASTATSLOG_ANNOTATION_ID_TRIGGER_STATE_RESET, true,
+ /*doHeaderPrefetch=*/GetParam()));
}
-TEST(LogEventTest, TestUidAnnotationWithInt8MaxValues) {
+TEST_P(LogEventTest, TestUidAnnotationWithInt8MaxValues) {
int32_t numElements = INT8_MAX;
int32_t int32Array[numElements];
@@ -930,18 +1064,18 @@
AStatsEvent_writeInt32Array(event, int32Array, numElements);
AStatsEvent_writeInt32(event, 10);
AStatsEvent_writeInt32(event, 11);
- AStatsEvent_addBoolAnnotation(event, ANNOTATION_ID_IS_UID, true);
+ AStatsEvent_addBoolAnnotation(event, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
AStatsEvent_build(event);
size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(event, &size);
+ const uint8_t* buf = AStatsEvent_getBuffer(event, &size);
LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
- EXPECT_TRUE(logEvent.parseBuffer(buf, size));
+ EXPECT_TRUE(ParseBuffer(logEvent, buf, size));
AStatsEvent_release(event);
}
-TEST(LogEventTest, TestEmptyAttributionChainWithPrimaryFieldFirstUidAnnotation) {
+TEST_P(LogEventTest, TestEmptyAttributionChainWithPrimaryFieldFirstUidAnnotation) {
AStatsEvent* event = AStatsEvent_obtain();
AStatsEvent_setAtomId(event, 100);
@@ -950,19 +1084,116 @@
AStatsEvent_writeInt32(event, 10);
AStatsEvent_writeAttributionChain(event, uids, tags, 0);
- AStatsEvent_addBoolAnnotation(event, ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID, true);
+ AStatsEvent_addBoolAnnotation(event, ASTATSLOG_ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID, true);
AStatsEvent_build(event);
size_t size;
- uint8_t* buf = AStatsEvent_getBuffer(event, &size);
+ const uint8_t* buf = AStatsEvent_getBuffer(event, &size);
LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
- EXPECT_FALSE(logEvent.parseBuffer(buf, size));
+ EXPECT_FALSE(ParseBuffer(logEvent, buf, size));
AStatsEvent_release(event);
}
+// Setup for parameterized tests.
+class LogEvent_FieldRestrictionTest : public testing::TestWithParam<std::tuple<int, bool>> {
+public:
+ static std::string ToString(testing::TestParamInfo<std::tuple<int, bool>> info) {
+ const std::string boolName = std::get<1>(info.param) ? "_prefetchTrue" : "_prefetchFalse";
+
+ switch (std::get<0>(info.param)) {
+ case ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_PERIPHERAL_DEVICE_INFO:
+ return "PeripheralDeviceInfo" + boolName;
+ case ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_APP_USAGE:
+ return "AppUsage" + boolName;
+ case ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_APP_ACTIVITY:
+ return "AppActivity" + boolName;
+ case ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_HEALTH_CONNECT:
+ return "HealthConnect" + boolName;
+ case ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_ACCESSIBILITY:
+ return "Accessibility" + boolName;
+ case ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_SYSTEM_SEARCH:
+ return "SystemSearch" + boolName;
+ case ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_USER_ENGAGEMENT:
+ return "UserEngagement" + boolName;
+ case ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_AMBIENT_SENSING:
+ return "AmbientSensing" + boolName;
+ case ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_DEMOGRAPHIC_CLASSIFICATION:
+ return "DemographicClassification" + boolName;
+ default:
+ return "Unknown" + boolName;
+ }
+ }
+ void TearDown() override {
+ FlagProvider::getInstance().resetOverrides();
+ }
+};
+
+// TODO(b/222539899): Add BOOL_TYPE value once parseAnnotations is updated to check specific
+// typeIds. BOOL_TYPE should be a bad field type for is_uid, nested, and reset state annotations.
+INSTANTIATE_TEST_SUITE_P(
+ LogEvent_FieldRestrictionTest, LogEvent_FieldRestrictionTest,
+ testing::Combine(
+ testing::Values(
+ ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_PERIPHERAL_DEVICE_INFO,
+ ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_APP_USAGE,
+ ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_APP_ACTIVITY,
+ ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_HEALTH_CONNECT,
+ ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_ACCESSIBILITY,
+ ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_SYSTEM_SEARCH,
+ ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_USER_ENGAGEMENT,
+ ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_AMBIENT_SENSING,
+ ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_DEMOGRAPHIC_CLASSIFICATION),
+ testing::Bool()),
+ LogEvent_FieldRestrictionTest::ToString);
+
+TEST_P(LogEvent_FieldRestrictionTest, TestFieldRestrictionAnnotation) {
+ if (!IsAtLeastU()) {
+ GTEST_SKIP();
+ }
+ LogEvent event(/*uid=*/0, /*pid=*/0);
+ EXPECT_TRUE(
+ createFieldWithBoolAnnotationLogEvent(&event, INT32_TYPE, std::get<0>(GetParam()), true,
+ /*doHeaderPrefetch=*/std::get<1>(GetParam())));
+ // Some basic checks to make sure the event is parsed correctly.
+ EXPECT_EQ(event.GetTagId(), 100);
+ ASSERT_EQ(event.getValues().size(), 1);
+ EXPECT_EQ(event.getValues()[0].mValue.getType(), Type::INT);
+}
+
+TEST_P(LogEvent_FieldRestrictionTest, TestInvalidAnnotationIntType) {
+ if (!IsAtLeastU()) {
+ GTEST_SKIP();
+ }
+ LogEvent event(/*uid=*/0, /*pid=*/0);
+ EXPECT_FALSE(createFieldWithIntAnnotationLogEvent(
+ &event, STRING_TYPE, std::get<0>(GetParam()),
+ /*random int*/ 15, /*doHeaderPrefetch=*/std::get<1>(GetParam())));
+}
+
+TEST_P(LogEvent_FieldRestrictionTest, TestInvalidAnnotationAtomLevel) {
+ if (!IsAtLeastU()) {
+ GTEST_SKIP();
+ }
+ LogEvent event(/*uid=*/0, /*pid=*/0);
+ EXPECT_FALSE(createAtomLevelBoolAnnotationLogEvent(
+ &event, STRING_TYPE, std::get<0>(GetParam()), true,
+ /*doHeaderPrefetch=*/std::get<1>(GetParam())));
+}
+
+TEST_P(LogEvent_FieldRestrictionTest, TestRestrictionCategoryAnnotationBelowUDevice) {
+ if (IsAtLeastU()) {
+ GTEST_SKIP();
+ }
+ int32_t restrictionCategory = ASTATSLOG_RESTRICTION_CATEGORY_DIAGNOSTIC;
+ LogEvent event(/*uid=*/0, /*pid=*/0);
+ EXPECT_FALSE(
+ createFieldWithBoolAnnotationLogEvent(&event, INT32_TYPE, std::get<0>(GetParam()), true,
+ /*doHeaderPrefetch=*/std::get<1>(GetParam())));
+}
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/statsd/tests/MetricsManager_test.cpp b/statsd/tests/MetricsManager_test.cpp
index 73e3d3c..16e9ab7 100644
--- a/statsd/tests/MetricsManager_test.cpp
+++ b/statsd/tests/MetricsManager_test.cpp
@@ -36,6 +36,7 @@
using namespace testing;
using android::sp;
using android::modules::sdklevel::IsAtLeastS;
+using android::modules::sdklevel::IsAtLeastU;
using android::os::statsd::Predicate;
using std::map;
using std::set;
@@ -94,6 +95,22 @@
return config;
}
+StatsdConfig buildGoodRestrictedConfig() {
+ StatsdConfig config;
+ config.set_id(12345);
+ config.set_restricted_metrics_delegate_package_name("delegate");
+
+ AtomMatcher* eventMatcher = config.add_atom_matcher();
+ eventMatcher->set_id(StringToId("SCREEN_IS_ON"));
+ SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
+ simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/);
+
+ EventMetric* metric = config.add_event_metric();
+ metric->set_id(3);
+ metric->set_what(StringToId("SCREEN_IS_ON"));
+ return config;
+}
+
set<int32_t> unionSet(const vector<set<int32_t>> sets) {
set<int32_t> toRet;
for (const set<int32_t>& s : sets) {
@@ -265,6 +282,43 @@
UnorderedElementsAreArray(unionSet({defaultPullUids, app2Uids, {AID_ADB}})));
}
+struct MetricsManagerServerFlagParam {
+ string flagValue;
+ string label;
+};
+
+class MetricsManagerTest_SPlus
+ : public ::testing::Test,
+ public ::testing::WithParamInterface<MetricsManagerServerFlagParam> {
+protected:
+ void SetUp() override {
+ if (shouldSkipTest()) {
+ GTEST_SKIP() << skipReason();
+ }
+ }
+
+ bool shouldSkipTest() const {
+ return !IsAtLeastS();
+ }
+
+ string skipReason() const {
+ return "Skipping MetricsManagerTest_SPlus because device is not S+";
+ }
+
+ std::optional<string> originalFlagValue;
+};
+
+INSTANTIATE_TEST_SUITE_P(
+ MetricsManagerTest_SPlus, MetricsManagerTest_SPlus,
+ testing::ValuesIn<MetricsManagerServerFlagParam>({
+ // Server flag values
+ {FLAG_TRUE, "ServerFlagTrue"},
+ {FLAG_FALSE, "ServerFlagFalse"},
+ }),
+ [](const testing::TestParamInfo<MetricsManagerTest_SPlus::ParamType>& info) {
+ return info.param.label;
+ });
+
TEST(MetricsManagerTest, TestCheckLogCredentialsWhitelistedAtom) {
sp<UidMap> uidMap;
sp<StatsPullerManager> pullerManager = new StatsPullerManager();
@@ -317,6 +371,54 @@
EXPECT_FALSE(metricsManager.isConfigValid());
}
+TEST_P(MetricsManagerTest_SPlus, TestRestrictedMetricsConfig) {
+ sp<UidMap> uidMap;
+ sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+ sp<AlarmMonitor> anomalyAlarmMonitor;
+ sp<AlarmMonitor> periodicAlarmMonitor;
+
+ StatsdConfig config = buildGoodRestrictedConfig();
+ config.add_allowed_log_source("AID_SYSTEM");
+ config.set_restricted_metrics_delegate_package_name("rm");
+
+ MetricsManager metricsManager(kConfigKey, config, timeBaseSec, timeBaseSec, uidMap,
+ pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor);
+
+ if (IsAtLeastU()) {
+ EXPECT_TRUE(metricsManager.isConfigValid());
+ } else {
+ EXPECT_EQ(metricsManager.mInvalidConfigReason,
+ INVALID_CONFIG_REASON_RESTRICTED_METRIC_NOT_ENABLED);
+ ASSERT_FALSE(metricsManager.isConfigValid());
+ }
+}
+
+TEST_P(MetricsManagerTest_SPlus, TestRestrictedMetricsConfigUpdate) {
+ sp<UidMap> uidMap;
+ sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+ sp<AlarmMonitor> anomalyAlarmMonitor;
+ sp<AlarmMonitor> periodicAlarmMonitor;
+
+ StatsdConfig config = buildGoodRestrictedConfig();
+ config.add_allowed_log_source("AID_SYSTEM");
+ config.set_restricted_metrics_delegate_package_name("rm");
+
+ MetricsManager metricsManager(kConfigKey, config, timeBaseSec, timeBaseSec, uidMap,
+ pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor);
+
+ StatsdConfig config2 = buildGoodRestrictedConfig();
+ metricsManager.updateConfig(config, timeBaseSec, timeBaseSec, anomalyAlarmMonitor,
+ periodicAlarmMonitor);
+
+ if (IsAtLeastU()) {
+ EXPECT_TRUE(metricsManager.isConfigValid());
+ } else {
+ EXPECT_EQ(metricsManager.mInvalidConfigReason,
+ INVALID_CONFIG_REASON_RESTRICTED_METRIC_NOT_ENABLED);
+ ASSERT_FALSE(metricsManager.isConfigValid());
+ }
+}
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/statsd/tests/SocketListener_test.cpp b/statsd/tests/SocketListener_test.cpp
new file mode 100644
index 0000000..42c084b
--- /dev/null
+++ b/statsd/tests/SocketListener_test.cpp
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2023, 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.
+ */
+#include <gtest/gtest.h>
+
+#include "socket/StatsSocketListener.h"
+#include "tests/statsd_test_util.h"
+
+#ifdef __ANDROID__
+
+namespace android {
+namespace os {
+namespace statsd {
+
+namespace {
+
+constexpr uint32_t kTestUid = 1001;
+constexpr uint32_t kTestPid = 1002;
+constexpr int kEventCount = 1000;
+constexpr int kEventFilteredCount = 500;
+constexpr int kAtomId = 1000;
+
+class AStatsEventWrapper final {
+ AStatsEvent* statsEvent = nullptr;
+
+public:
+ AStatsEventWrapper(int atomId) {
+ statsEvent = AStatsEvent_obtain();
+ createStatsEvent(statsEvent, INT64_TYPE, /*atomId=*/atomId);
+ AStatsEvent_build(statsEvent);
+ }
+
+ std::pair<const uint8_t*, size_t> getBuffer() const {
+ size_t size;
+ const uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+ return std::make_pair(buf, size);
+ }
+
+ ~AStatsEventWrapper() {
+ AStatsEvent_release(statsEvent);
+ }
+};
+
+} // namespace
+
+void generateAtomLogging(const std::shared_ptr<LogEventQueue>& queue,
+ const std::shared_ptr<LogEventFilter>& filter, int eventCount,
+ int startAtomId) {
+ // create number of AStatsEvent
+ for (int i = 0; i < eventCount; i++) {
+ AStatsEventWrapper event(startAtomId + i);
+ auto [buf, size] = event.getBuffer();
+ StatsSocketListener::processMessage(buf, size, kTestUid, kTestPid, queue, filter);
+ }
+}
+
+class SocketParseMessageTestNoFiltering : public testing::TestWithParam<bool> {
+protected:
+ std::shared_ptr<LogEventQueue> mEventQueue;
+ std::shared_ptr<LogEventFilter> mLogEventFilter;
+
+public:
+ SocketParseMessageTestNoFiltering()
+ : mEventQueue(std::make_shared<LogEventQueue>(kEventCount /*buffer limit*/)),
+ mLogEventFilter(GetParam() ? std::make_shared<LogEventFilter>() : nullptr) {
+ }
+
+ static std::string ToString(testing::TestParamInfo<bool> info) {
+ return info.param ? "WithEventFilter" : "NoEventFilter";
+ }
+};
+
+INSTANTIATE_TEST_SUITE_P(SocketParseMessageTestNoFiltering, SocketParseMessageTestNoFiltering,
+ testing::Bool(), SocketParseMessageTestNoFiltering::ToString);
+
+TEST_P(SocketParseMessageTestNoFiltering, TestProcessMessageNoFiltering) {
+ if (GetParam()) {
+ mLogEventFilter->setFilteringEnabled(false);
+ }
+
+ generateAtomLogging(mEventQueue, mLogEventFilter, kEventCount, kAtomId);
+
+ // check content of the queue
+ EXPECT_EQ(kEventCount, mEventQueue->mQueue.size());
+ for (int i = 0; i < kEventCount; i++) {
+ auto logEvent = mEventQueue->waitPop();
+ EXPECT_TRUE(logEvent->isValid());
+ EXPECT_EQ(kAtomId + i, logEvent->GetTagId());
+ EXPECT_FALSE(logEvent->isParsedHeaderOnly());
+ }
+}
+
+TEST_P(SocketParseMessageTestNoFiltering, TestProcessMessageNoFilteringWithEmptySetExplicitSet) {
+ if (GetParam()) {
+ mLogEventFilter->setFilteringEnabled(false);
+ LogEventFilter::AtomIdSet idsList;
+ mLogEventFilter->setAtomIds(idsList, nullptr);
+ }
+
+ generateAtomLogging(mEventQueue, mLogEventFilter, kEventCount, kAtomId);
+
+ // check content of the queue
+ EXPECT_EQ(kEventCount, mEventQueue->mQueue.size());
+ for (int i = 0; i < kEventCount; i++) {
+ auto logEvent = mEventQueue->waitPop();
+ EXPECT_TRUE(logEvent->isValid());
+ EXPECT_EQ(kAtomId + i, logEvent->GetTagId());
+ EXPECT_FALSE(logEvent->isParsedHeaderOnly());
+ }
+}
+
+TEST(SocketParseMessageTest, TestProcessMessageFilterEmptySet) {
+ std::shared_ptr<LogEventQueue> eventQueue =
+ std::make_shared<LogEventQueue>(kEventCount /*buffer limit*/);
+
+ std::shared_ptr<LogEventFilter> logEventFilter = std::make_shared<LogEventFilter>();
+
+ generateAtomLogging(eventQueue, logEventFilter, kEventCount, kAtomId);
+
+ // check content of the queue
+ for (int i = 0; i < kEventCount; i++) {
+ auto logEvent = eventQueue->waitPop();
+ EXPECT_TRUE(logEvent->isValid());
+ EXPECT_EQ(kAtomId + i, logEvent->GetTagId());
+ EXPECT_TRUE(logEvent->isParsedHeaderOnly());
+ }
+}
+
+TEST(SocketParseMessageTest, TestProcessMessageFilterEmptySetExplicitSet) {
+ std::shared_ptr<LogEventQueue> eventQueue =
+ std::make_shared<LogEventQueue>(kEventCount /*buffer limit*/);
+
+ std::shared_ptr<LogEventFilter> logEventFilter = std::make_shared<LogEventFilter>();
+
+ LogEventFilter::AtomIdSet idsList;
+ logEventFilter->setAtomIds(idsList, nullptr);
+
+ generateAtomLogging(eventQueue, logEventFilter, kEventCount, kAtomId);
+
+ // check content of the queue
+ for (int i = 0; i < kEventCount; i++) {
+ auto logEvent = eventQueue->waitPop();
+ EXPECT_TRUE(logEvent->isValid());
+ EXPECT_EQ(kAtomId + i, logEvent->GetTagId());
+ EXPECT_TRUE(logEvent->isParsedHeaderOnly());
+ }
+}
+
+TEST(SocketParseMessageTest, TestProcessMessageFilterCompleteSet) {
+ std::shared_ptr<LogEventQueue> eventQueue =
+ std::make_shared<LogEventQueue>(kEventCount /*buffer limit*/);
+
+ std::shared_ptr<LogEventFilter> logEventFilter = std::make_shared<LogEventFilter>();
+
+ LogEventFilter::AtomIdSet idsList;
+ for (int i = 0; i < kEventCount; i++) {
+ idsList.insert(kAtomId + i);
+ }
+ logEventFilter->setAtomIds(idsList, nullptr);
+
+ generateAtomLogging(eventQueue, logEventFilter, kEventCount, kAtomId);
+
+ // check content of the queue
+ EXPECT_EQ(kEventCount, eventQueue->mQueue.size());
+ for (int i = 0; i < kEventCount; i++) {
+ auto logEvent = eventQueue->waitPop();
+ EXPECT_TRUE(logEvent->isValid());
+ EXPECT_EQ(kAtomId + i, logEvent->GetTagId());
+ EXPECT_FALSE(logEvent->isParsedHeaderOnly());
+ }
+}
+
+TEST(SocketParseMessageTest, TestProcessMessageFilterPartialSet) {
+ std::shared_ptr<LogEventQueue> eventQueue =
+ std::make_shared<LogEventQueue>(kEventCount /*buffer limit*/);
+
+ std::shared_ptr<LogEventFilter> logEventFilter = std::make_shared<LogEventFilter>();
+
+ LogEventFilter::AtomIdSet idsList;
+ for (int i = 0; i < kEventFilteredCount; i++) {
+ idsList.insert(kAtomId + i);
+ }
+ logEventFilter->setAtomIds(idsList, nullptr);
+
+ generateAtomLogging(eventQueue, logEventFilter, kEventCount, kAtomId);
+
+ // check content of the queue
+ EXPECT_EQ(kEventCount, eventQueue->mQueue.size());
+ for (int i = 0; i < kEventFilteredCount; i++) {
+ auto logEvent = eventQueue->waitPop();
+ EXPECT_TRUE(logEvent->isValid());
+ EXPECT_EQ(kAtomId + i, logEvent->GetTagId());
+ EXPECT_FALSE(logEvent->isParsedHeaderOnly());
+ }
+
+ for (int i = kEventFilteredCount; i < kEventCount; i++) {
+ auto logEvent = eventQueue->waitPop();
+ EXPECT_TRUE(logEvent->isValid());
+ EXPECT_EQ(kAtomId + i, logEvent->GetTagId());
+ EXPECT_TRUE(logEvent->isParsedHeaderOnly());
+ }
+}
+
+TEST(SocketParseMessageTest, TestProcessMessageFilterToggle) {
+ std::shared_ptr<LogEventQueue> eventQueue =
+ std::make_shared<LogEventQueue>(kEventCount * 3 /*buffer limit*/);
+
+ std::shared_ptr<LogEventFilter> logEventFilter = std::make_shared<LogEventFilter>();
+
+ LogEventFilter::AtomIdSet idsList;
+ for (int i = 0; i < kEventFilteredCount; i++) {
+ idsList.insert(kAtomId + i);
+ }
+ // events with ids from kAtomId to kAtomId + kEventFilteredCount should not be skipped
+ logEventFilter->setAtomIds(idsList, nullptr);
+
+ generateAtomLogging(eventQueue, logEventFilter, kEventCount, kAtomId);
+
+ logEventFilter->setFilteringEnabled(false);
+ // since filtering is disabled - events with any ids should not be skipped
+ // will generate events with ids [kAtomId + kEventCount, kAtomId + kEventCount * 2]
+ generateAtomLogging(eventQueue, logEventFilter, kEventCount, kAtomId + kEventCount);
+
+ logEventFilter->setFilteringEnabled(true);
+ LogEventFilter::AtomIdSet idsList2;
+ for (int i = kEventFilteredCount; i < kEventCount; i++) {
+ idsList2.insert(kAtomId + kEventCount * 2 + i);
+ }
+ // events with idsList2 ids should not be skipped
+ logEventFilter->setAtomIds(idsList2, nullptr);
+
+ // will generate events with ids [kAtomId + kEventCount * 2, kAtomId + kEventCount * 3]
+ generateAtomLogging(eventQueue, logEventFilter, kEventCount, kAtomId + kEventCount * 2);
+
+ // check content of the queue
+ EXPECT_EQ(kEventCount * 3, eventQueue->mQueue.size());
+ // events with ids from kAtomId to kAtomId + kEventFilteredCount should not be skipped
+ for (int i = 0; i < kEventFilteredCount; i++) {
+ auto logEvent = eventQueue->waitPop();
+ EXPECT_TRUE(logEvent->isValid());
+ EXPECT_EQ(kAtomId + i, logEvent->GetTagId());
+ EXPECT_FALSE(logEvent->isParsedHeaderOnly());
+ }
+
+ // all events above kAtomId + kEventFilteredCount to kAtomId + kEventCount should be skipped
+ for (int i = kEventFilteredCount; i < kEventCount; i++) {
+ auto logEvent = eventQueue->waitPop();
+ EXPECT_TRUE(logEvent->isValid());
+ EXPECT_EQ(kAtomId + i, logEvent->GetTagId());
+ EXPECT_TRUE(logEvent->isParsedHeaderOnly());
+ }
+
+ // events with ids [kAtomId + kEventCount, kAtomId + kEventCount * 2] should not be skipped
+ // since wiltering was disabled at that time
+ for (int i = 0; i < kEventCount; i++) {
+ auto logEvent = eventQueue->waitPop();
+ EXPECT_TRUE(logEvent->isValid());
+ EXPECT_EQ(kAtomId + kEventCount + i, logEvent->GetTagId());
+ EXPECT_FALSE(logEvent->isParsedHeaderOnly());
+ }
+
+ // first half events with ids [kAtomId + kEventCount * 2, kAtomId + kEventCount * 3]
+ // should be skipped
+ for (int i = 0; i < kEventFilteredCount; i++) {
+ auto logEvent = eventQueue->waitPop();
+ EXPECT_TRUE(logEvent->isValid());
+ EXPECT_EQ(kAtomId + kEventCount * 2 + i, logEvent->GetTagId());
+ EXPECT_TRUE(logEvent->isParsedHeaderOnly());
+ }
+
+ // second half events with ids [kAtomId + kEventCount * 2, kAtomId + kEventCount * 3]
+ // should be processed
+ for (int i = kEventFilteredCount; i < kEventCount; i++) {
+ auto logEvent = eventQueue->waitPop();
+ EXPECT_TRUE(logEvent->isValid());
+ EXPECT_EQ(kAtomId + kEventCount * 2 + i, logEvent->GetTagId());
+ EXPECT_FALSE(logEvent->isParsedHeaderOnly());
+ }
+}
+
+// TODO: tests for setAtomIds() with multiple consumers
+// TODO: use MockLogEventFilter to test different sets from different consumers
+
+} // namespace statsd
+} // namespace os
+} // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/statsd/tests/StatsLogProcessor_test.cpp b/statsd/tests/StatsLogProcessor_test.cpp
index 019584e..d29b5e6 100644
--- a/statsd/tests/StatsLogProcessor_test.cpp
+++ b/statsd/tests/StatsLogProcessor_test.cpp
@@ -15,6 +15,7 @@
#include "StatsLogProcessor.h"
#include <android-base/stringprintf.h>
+#include <android-modules-utils/sdk_level.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <stdio.h>
@@ -26,9 +27,11 @@
#include "packages/UidMap.h"
#include "src/stats_log.pb.h"
#include "src/statsd_config.pb.h"
+#include "state/StateManager.h"
#include "statslog_statsdtest.h"
#include "storage/StorageManager.h"
#include "tests/statsd_test_util.h"
+#include "utils/DbUtils.h"
using namespace android;
using namespace testing;
@@ -40,8 +43,11 @@
namespace statsd {
using android::base::StringPrintf;
+using android::modules::sdklevel::IsAtLeastU;
using android::util::ProtoOutputStream;
+using ::testing::Expectation;
+
#ifdef __ANDROID__
#define STATS_DATA_DIR "/data/misc/stats-data"
@@ -50,20 +56,29 @@
*/
class MockMetricsManager : public MetricsManager {
public:
- MockMetricsManager()
- : MetricsManager(ConfigKey(1, 12345), StatsdConfig(), 1000, 1000, new UidMap(),
+ MockMetricsManager(ConfigKey configKey = ConfigKey(1, 12345))
+ : MetricsManager(configKey, StatsdConfig(), 1000, 1000, new UidMap(),
new StatsPullerManager(),
- new AlarmMonitor(10,
- [](const shared_ptr<IStatsCompanionService>&, int64_t) {},
- [](const shared_ptr<IStatsCompanionService>&) {}),
- new AlarmMonitor(10,
- [](const shared_ptr<IStatsCompanionService>&, int64_t) {},
- [](const shared_ptr<IStatsCompanionService>&) {})) {
+ new AlarmMonitor(
+ 10, [](const shared_ptr<IStatsCompanionService>&, int64_t) {},
+ [](const shared_ptr<IStatsCompanionService>&) {}),
+ new AlarmMonitor(
+ 10, [](const shared_ptr<IStatsCompanionService>&, int64_t) {},
+ [](const shared_ptr<IStatsCompanionService>&) {})) {
}
MOCK_METHOD0(byteSize, size_t());
MOCK_METHOD1(dropData, void(const int64_t dropTimeNs));
+
+ MOCK_METHOD(void, onLogEvent, (const LogEvent& event), (override));
+
+ MOCK_METHOD(void, onDumpReport,
+ (const int64_t dumpTimeNs, const int64_t wallClockNs,
+ const bool include_current_partial_bucket, const bool erase_data,
+ const DumpLatency dumpLatency, std::set<string>* str_set,
+ android::util::ProtoOutputStream* protoOutput),
+ (override));
};
TEST(StatsLogProcessorTest, TestRateLimitByteSize) {
@@ -72,9 +87,11 @@
sp<AlarmMonitor> anomalyAlarmMonitor;
sp<AlarmMonitor> periodicAlarmMonitor;
// Construct the processor with a no-op sendBroadcast function that does nothing.
- StatsLogProcessor p(m, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, 0,
- [](const ConfigKey& key) { return true; },
- [](const int&, const vector<int64_t>&) {return true;});
+ StatsLogProcessor p(
+ m, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, 0,
+ [](const ConfigKey& key) { return true; },
+ [](const int&, const vector<int64_t>&) { return true; },
+ [](const ConfigKey&, const string&, const vector<int64_t>&) {}, nullptr);
MockMetricsManager mockMetricsManager;
@@ -93,12 +110,14 @@
sp<AlarmMonitor> anomalyAlarmMonitor;
sp<AlarmMonitor> subscriberAlarmMonitor;
int broadcastCount = 0;
- StatsLogProcessor p(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
- [&broadcastCount](const ConfigKey& key) {
- broadcastCount++;
- return true;
- },
- [](const int&, const vector<int64_t>&) {return true;});
+ StatsLogProcessor p(
+ m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
+ [&broadcastCount](const ConfigKey& key) {
+ broadcastCount++;
+ return true;
+ },
+ [](const int&, const vector<int64_t>&) { return true; },
+ [](const ConfigKey&, const string&, const vector<int64_t>&) {}, nullptr);
MockMetricsManager mockMetricsManager;
@@ -125,12 +144,14 @@
sp<AlarmMonitor> anomalyAlarmMonitor;
sp<AlarmMonitor> subscriberAlarmMonitor;
int broadcastCount = 0;
- StatsLogProcessor p(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
- [&broadcastCount](const ConfigKey& key) {
- broadcastCount++;
- return true;
- },
- [](const int&, const vector<int64_t>&) {return true;});
+ StatsLogProcessor p(
+ m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
+ [&broadcastCount](const ConfigKey& key) {
+ broadcastCount++;
+ return true;
+ },
+ [](const int&, const vector<int64_t>&) { return true; },
+ [](const ConfigKey&, const string&, const vector<int64_t>&) {}, nullptr);
MockMetricsManager mockMetricsManager;
@@ -161,8 +182,49 @@
return config;
}
+StatsdConfig makeRestrictedConfig(bool includeMetric = false) {
+ StatsdConfig config;
+ config.add_allowed_log_source("AID_ROOT");
+ config.set_restricted_metrics_delegate_package_name("delegate");
+
+ if (includeMetric) {
+ auto appCrashMatcher = CreateProcessCrashAtomMatcher();
+ *config.add_atom_matcher() = appCrashMatcher;
+ auto eventMetric = config.add_event_metric();
+ eventMetric->set_id(StringToId("EventAppCrashes"));
+ eventMetric->set_what(appCrashMatcher.id());
+ }
+ return config;
+}
+
+class MockRestrictedMetricsManager : public MetricsManager {
+public:
+ MockRestrictedMetricsManager(ConfigKey configKey = ConfigKey(1, 12345))
+ : MetricsManager(configKey, makeRestrictedConfig(), 1000, 1000, new UidMap(),
+ new StatsPullerManager(),
+ new AlarmMonitor(
+ 10, [](const shared_ptr<IStatsCompanionService>&, int64_t) {},
+ [](const shared_ptr<IStatsCompanionService>&) {}),
+ new AlarmMonitor(
+ 10, [](const shared_ptr<IStatsCompanionService>&, int64_t) {},
+ [](const shared_ptr<IStatsCompanionService>&) {})) {
+ }
+
+ MOCK_METHOD(void, onLogEvent, (const LogEvent& event), (override));
+ MOCK_METHOD(void, onDumpReport,
+ (const int64_t dumpTimeNs, const int64_t wallClockNs,
+ const bool include_current_partial_bucket, const bool erase_data,
+ const DumpLatency dumpLatency, std::set<string>* str_set,
+ android::util::ProtoOutputStream* protoOutput),
+ (override));
+ MOCK_METHOD(size_t, byteSize, (), (override));
+ MOCK_METHOD(void, flushRestrictedData, (), (override));
+};
+
TEST(StatsLogProcessorTest, TestUidMapHasSnapshot) {
- // Setup simple config key corresponding to empty config.
+ ConfigKey key(3, 4);
+ StatsdConfig config = MakeConfig(true);
+
sp<UidMap> m = new UidMap();
sp<StatsPullerManager> pullerManager = new StatsPullerManager();
m->updateMap(1, {1, 2}, {1, 2}, {String16("v1"), String16("v2")},
@@ -171,14 +233,21 @@
sp<AlarmMonitor> anomalyAlarmMonitor;
sp<AlarmMonitor> subscriberAlarmMonitor;
int broadcastCount = 0;
- StatsLogProcessor p(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
- [&broadcastCount](const ConfigKey& key) {
- broadcastCount++;
- return true;
- },
- [](const int&, const vector<int64_t>&) {return true;});
- ConfigKey key(3, 4);
- StatsdConfig config = MakeConfig(true);
+ std::shared_ptr<MockLogEventFilter> mockLogEventFilter = std::make_shared<MockLogEventFilter>();
+ EXPECT_CALL(*mockLogEventFilter, setAtomIds(StatsLogProcessor::getDefaultAtomIdSet(), _))
+ .Times(1);
+ StatsLogProcessor p(
+ m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
+ [&broadcastCount](const ConfigKey& key) {
+ broadcastCount++;
+ return true;
+ },
+ [](const int&, const vector<int64_t>&) { return true; },
+ [](const ConfigKey&, const string&, const vector<int64_t>&) {}, mockLogEventFilter);
+
+ const LogEventFilter::AtomIdSet atomIdsList = CreateAtomIdSetFromConfig(config);
+ EXPECT_CALL(*mockLogEventFilter, setAtomIds(atomIdsList, &p)).Times(1);
+
p.OnConfigUpdated(0, key, config);
// Expect to get no metrics, but snapshot specified above in uidmap.
@@ -195,6 +264,9 @@
TEST(StatsLogProcessorTest, TestEmptyConfigHasNoUidMap) {
// Setup simple config key corresponding to empty config.
+ ConfigKey key(3, 4);
+ StatsdConfig config = MakeConfig(false);
+
sp<UidMap> m = new UidMap();
sp<StatsPullerManager> pullerManager = new StatsPullerManager();
m->updateMap(1, {1, 2}, {1, 2}, {String16("v1"), String16("v2")},
@@ -203,14 +275,21 @@
sp<AlarmMonitor> anomalyAlarmMonitor;
sp<AlarmMonitor> subscriberAlarmMonitor;
int broadcastCount = 0;
- StatsLogProcessor p(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
- [&broadcastCount](const ConfigKey& key) {
- broadcastCount++;
- return true;
- },
- [](const int&, const vector<int64_t>&) {return true;});
- ConfigKey key(3, 4);
- StatsdConfig config = MakeConfig(false);
+ std::shared_ptr<MockLogEventFilter> mockLogEventFilter = std::make_shared<MockLogEventFilter>();
+ EXPECT_CALL(*mockLogEventFilter, setAtomIds(StatsLogProcessor::getDefaultAtomIdSet(), _))
+ .Times(1);
+ StatsLogProcessor p(
+ m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
+ [&broadcastCount](const ConfigKey& key) {
+ broadcastCount++;
+ return true;
+ },
+ [](const int&, const vector<int64_t>&) { return true; },
+ [](const ConfigKey&, const string&, const vector<int64_t>&) {}, mockLogEventFilter);
+
+ const LogEventFilter::AtomIdSet atomIdsList = CreateAtomIdSetFromConfig(config);
+ EXPECT_CALL(*mockLogEventFilter, setAtomIds(atomIdsList, &p)).Times(1);
+
p.OnConfigUpdated(0, key, config);
// Expect to get no metrics, but snapshot specified above in uidmap.
@@ -225,23 +304,33 @@
TEST(StatsLogProcessorTest, TestReportIncludesSubConfig) {
// Setup simple config key corresponding to empty config.
- sp<UidMap> m = new UidMap();
- sp<StatsPullerManager> pullerManager = new StatsPullerManager();
- sp<AlarmMonitor> anomalyAlarmMonitor;
- sp<AlarmMonitor> subscriberAlarmMonitor;
- int broadcastCount = 0;
- StatsLogProcessor p(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
- [&broadcastCount](const ConfigKey& key) {
- broadcastCount++;
- return true;
- },
- [](const int&, const vector<int64_t>&) {return true;});
ConfigKey key(3, 4);
StatsdConfig config;
auto annotation = config.add_annotation();
annotation->set_field_int64(1);
annotation->set_field_int32(2);
config.add_allowed_log_source("AID_ROOT");
+
+ sp<UidMap> m = new UidMap();
+ sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+ sp<AlarmMonitor> anomalyAlarmMonitor;
+ sp<AlarmMonitor> subscriberAlarmMonitor;
+ int broadcastCount = 0;
+ std::shared_ptr<MockLogEventFilter> mockLogEventFilter = std::make_shared<MockLogEventFilter>();
+ EXPECT_CALL(*mockLogEventFilter, setAtomIds(StatsLogProcessor::getDefaultAtomIdSet(), _))
+ .Times(1);
+ StatsLogProcessor p(
+ m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
+ [&broadcastCount](const ConfigKey& key) {
+ broadcastCount++;
+ return true;
+ },
+ [](const int&, const vector<int64_t>&) { return true; },
+ [](const ConfigKey&, const string&, const vector<int64_t>&) {}, mockLogEventFilter);
+
+ const LogEventFilter::AtomIdSet atomIdsList = CreateAtomIdSetFromConfig(config);
+ EXPECT_CALL(*mockLogEventFilter, setAtomIds(atomIdsList, &p)).Times(1);
+
p.OnConfigUpdated(1, key, config);
// Expect to get no metrics, but snapshot specified above in uidmap.
@@ -307,16 +396,25 @@
TEST(StatsLogProcessorTest, TestPullUidProviderSetOnConfigUpdate) {
// Setup simple config key corresponding to empty config.
+ ConfigKey key(3, 4);
+ StatsdConfig config = MakeConfig(false);
+
sp<UidMap> m = new UidMap();
sp<StatsPullerManager> pullerManager = new StatsPullerManager();
sp<AlarmMonitor> anomalyAlarmMonitor;
sp<AlarmMonitor> subscriberAlarmMonitor;
+ std::shared_ptr<MockLogEventFilter> mockLogEventFilter = std::make_shared<MockLogEventFilter>();
+ EXPECT_CALL(*mockLogEventFilter, setAtomIds(StatsLogProcessor::getDefaultAtomIdSet(), _))
+ .Times(1);
StatsLogProcessor p(
m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
[](const ConfigKey& key) { return true; },
- [](const int&, const vector<int64_t>&) { return true; });
- ConfigKey key(3, 4);
- StatsdConfig config = MakeConfig(false);
+ [](const int&, const vector<int64_t>&) { return true; },
+ [](const ConfigKey&, const string&, const vector<int64_t>&) {}, mockLogEventFilter);
+
+ const LogEventFilter::AtomIdSet atomIdsList = CreateAtomIdSetFromConfig(config);
+ EXPECT_CALL(*mockLogEventFilter, setAtomIds(atomIdsList, &p)).Times(3);
+
p.OnConfigUpdated(0, key, config);
EXPECT_NE(pullerManager->mPullUidProviders.find(key), pullerManager->mPullUidProviders.end());
@@ -329,7 +427,9 @@
}
TEST(StatsLogProcessorTest, InvalidConfigRemoved) {
- // Setup simple config key corresponding to empty config.
+ ConfigKey key(3, 4);
+ StatsdConfig config = MakeConfig(true);
+
sp<UidMap> m = new UidMap();
sp<StatsPullerManager> pullerManager = new StatsPullerManager();
m->updateMap(1, {1, 2}, {1, 2}, {String16("v1"), String16("v2")},
@@ -337,11 +437,22 @@
/* certificateHash */ {{}, {}});
sp<AlarmMonitor> anomalyAlarmMonitor;
sp<AlarmMonitor> subscriberAlarmMonitor;
- StatsLogProcessor p(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
- [](const ConfigKey& key) { return true; },
- [](const int&, const vector<int64_t>&) {return true;});
- ConfigKey key(3, 4);
- StatsdConfig config = MakeConfig(true);
+ std::shared_ptr<MockLogEventFilter> mockLogEventFilter = std::make_shared<MockLogEventFilter>();
+ EXPECT_CALL(*mockLogEventFilter, setAtomIds(StatsLogProcessor::getDefaultAtomIdSet(), _))
+ .Times(1);
+ StatsLogProcessor p(
+ m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
+ [](const ConfigKey& key) { return true; },
+ [](const int&, const vector<int64_t>&) { return true; },
+ [](const ConfigKey&, const string&, const vector<int64_t>&) {}, mockLogEventFilter);
+
+ EXPECT_CALL(*mockLogEventFilter, setAtomIds(CreateAtomIdSetDefault(), &p)).Times(1);
+ // atom used by matcher defined in MakeConfig() API
+ Expectation exp =
+ EXPECT_CALL(*mockLogEventFilter, setAtomIds(CreateAtomIdSetFromConfig(config), &p))
+ .Times(1);
+ EXPECT_CALL(*mockLogEventFilter, setAtomIds(CreateAtomIdSetDefault(), &p)).Times(1).After(exp);
+
// Remove the config mConfigStats so that the Icebox starts at 0 configs.
p.OnConfigRemoved(key);
StatsdStats::getInstance().reset();
@@ -463,6 +574,9 @@
long timeBase1 = 1;
int broadcastCount = 0;
+ std::shared_ptr<MockLogEventFilter> mockLogEventFilter = std::make_shared<MockLogEventFilter>();
+ EXPECT_CALL(*mockLogEventFilter, setAtomIds(StatsLogProcessor::getDefaultAtomIdSet(), _))
+ .Times(1);
StatsLogProcessor processor(
m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, timeBase1,
[](const ConfigKey& key) { return true; },
@@ -474,7 +588,12 @@
activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), activeConfigs.begin(),
activeConfigs.end());
return true;
- });
+ },
+ [](const ConfigKey&, const string&, const vector<int64_t>&) {}, mockLogEventFilter);
+
+ // config1,config2,config3 use the same atom
+ const LogEventFilter::AtomIdSet atomIdsList = CreateAtomIdSetFromConfig(config1);
+ EXPECT_CALL(*mockLogEventFilter, setAtomIds(atomIdsList, &processor)).Times(3);
processor.OnConfigUpdated(1, cfgKey1, config1);
processor.OnConfigUpdated(2, cfgKey2, config2);
@@ -1561,7 +1680,7 @@
// Send the config.
const sp<UidMap> uidMap = new UidMap();
const shared_ptr<StatsService> service =
- SharedRefBase::make<StatsService>(uidMap, /* queue */ nullptr);
+ SharedRefBase::make<StatsService>(uidMap, /* queue */ nullptr, nullptr);
string serialized = config1.SerializeAsString();
service->addConfigurationChecked(uid, configId, {serialized.begin(), serialized.end()});
@@ -1740,6 +1859,29 @@
ADB_DUMP, NO_TIME_CONSTRAINTS, nullptr);
}
+TEST(StatsLogProcessorTest, LogEventFilterOnSetPrintLogs) {
+ sp<UidMap> m = new UidMap();
+ sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+ sp<AlarmMonitor> anomalyAlarmMonitor;
+ sp<AlarmMonitor> periodicAlarmMonitor;
+
+ std::shared_ptr<MockLogEventFilter> mockLogEventFilter = std::make_shared<MockLogEventFilter>();
+ EXPECT_CALL(*mockLogEventFilter, setAtomIds(StatsLogProcessor::getDefaultAtomIdSet(), _))
+ .Times(1);
+ StatsLogProcessor p(
+ m, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, 0,
+ [](const ConfigKey& key) { return true; },
+ [](const int&, const vector<int64_t>&) { return true; },
+ [](const ConfigKey&, const string&, const vector<int64_t>&) {}, mockLogEventFilter);
+
+ Expectation filterSetFalse =
+ EXPECT_CALL(*mockLogEventFilter, setFilteringEnabled(false)).Times(1);
+ EXPECT_CALL(*mockLogEventFilter, setFilteringEnabled(true)).Times(1).After(filterSetFalse);
+
+ p.setPrintLogs(true);
+ p.setPrintLogs(false);
+}
+
TEST(StatsLogProcessorTest_mapIsolatedUidToHostUid, LogHostUid) {
int hostUid = 20;
int isolatedUid = 30;
@@ -2006,6 +2148,201 @@
EXPECT_EQ(output.reports(0).last_report_elapsed_nanos(), dumpTime1Ns);
}
+class StatsLogProcessorTestRestricted : public Test {
+protected:
+ const ConfigKey mConfigKey = ConfigKey(1, 12345);
+ void SetUp() override {
+ if (!IsAtLeastU()) {
+ GTEST_SKIP();
+ }
+ }
+ void TearDown() override {
+ if (!IsAtLeastU()) {
+ GTEST_SKIP();
+ }
+ FlagProvider::getInstance().resetOverrides();
+ StorageManager::deleteAllFiles(STATS_DATA_DIR);
+ dbutils::deleteDb(mConfigKey);
+ }
+};
+
+TEST_F(StatsLogProcessorTestRestricted, TestInconsistentRestrictedMetricsConfigUpdate) {
+ ConfigKey key(3, 4);
+ StatsdConfig config = makeRestrictedConfig(true);
+ config.set_restricted_metrics_delegate_package_name("rm_package");
+
+ sp<UidMap> m = new UidMap();
+ sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+ m->updateMap(1, {1, 2}, {1, 2}, {String16("v1"), String16("v2")},
+ {String16("p1"), String16("p2")}, {String16(""), String16("")},
+ /* certificateHash */ {{}, {}});
+ sp<AlarmMonitor> anomalyAlarmMonitor;
+ sp<AlarmMonitor> subscriberAlarmMonitor;
+ std::shared_ptr<MockLogEventFilter> mockLogEventFilter = std::make_shared<MockLogEventFilter>();
+ EXPECT_CALL(*mockLogEventFilter, setAtomIds(StatsLogProcessor::getDefaultAtomIdSet(), _))
+ .Times(1);
+ StatsLogProcessor p(
+ m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
+ [](const ConfigKey& key) { return true; },
+ [](const int&, const vector<int64_t>&) { return true; },
+ [](const ConfigKey&, const string&, const vector<int64_t>&) {}, mockLogEventFilter);
+
+ // new newConfig will be the same as config
+ const LogEventFilter::AtomIdSet atomIdsList = CreateAtomIdSetFromConfig(config);
+ EXPECT_CALL(*mockLogEventFilter, setAtomIds(atomIdsList, &p)).Times(2);
+
+ p.OnConfigUpdated(0, key, config);
+
+ EXPECT_EQ(1, p.mMetricsManagers.size());
+ EXPECT_NE(p.mMetricsManagers.find(key), p.mMetricsManagers.end());
+ sp<MetricsManager> oldMetricsManager = p.mMetricsManagers.find(key)->second;
+
+ StatsdConfig newConfig = makeRestrictedConfig(true);
+ newConfig.clear_restricted_metrics_delegate_package_name();
+ p.OnConfigUpdated(/*timestampNs=*/0, key, newConfig);
+
+ ASSERT_NE(p.mMetricsManagers.find(key)->second, oldMetricsManager);
+}
+
+TEST_F(StatsLogProcessorTestRestricted, TestRestrictedLogEventNotPassed) {
+ sp<StatsLogProcessor> processor = CreateStatsLogProcessor(
+ /*timeBaseNs=*/1, /*currentTimeNs=*/1, StatsdConfig(), mConfigKey);
+ ConfigKey key(3, 4);
+ sp<MockMetricsManager> metricsManager = new MockMetricsManager(mConfigKey);
+ EXPECT_CALL(*metricsManager, onLogEvent).Times(0);
+
+ processor->mMetricsManagers[mConfigKey] = metricsManager;
+ EXPECT_FALSE(processor->mMetricsManagers[mConfigKey]->hasRestrictedMetricsDelegate());
+
+ unique_ptr<LogEvent> event = CreateRestrictedLogEvent(123);
+ EXPECT_TRUE(event->isValid());
+ EXPECT_TRUE(event->isRestricted());
+ processor->OnLogEvent(event.get());
+}
+
+TEST_F(StatsLogProcessorTestRestricted, TestRestrictedLogEventPassed) {
+ sp<StatsLogProcessor> processor = CreateStatsLogProcessor(
+ /*timeBaseNs=*/1, /*currentTimeNs=*/1, StatsdConfig(), mConfigKey);
+ sp<MockRestrictedMetricsManager> metricsManager = new MockRestrictedMetricsManager(mConfigKey);
+ EXPECT_CALL(*metricsManager, onLogEvent).Times(1);
+
+ processor->mMetricsManagers[mConfigKey] = metricsManager;
+ EXPECT_TRUE(processor->mMetricsManagers[mConfigKey]->hasRestrictedMetricsDelegate());
+
+ unique_ptr<LogEvent> event = CreateRestrictedLogEvent(123);
+ EXPECT_TRUE(event->isValid());
+ EXPECT_TRUE(event->isRestricted());
+ processor->OnLogEvent(event.get());
+}
+
+TEST_F(StatsLogProcessorTestRestricted, RestrictedMetricsManagerOnDumpReportNotCalled) {
+ sp<StatsLogProcessor> processor = CreateStatsLogProcessor(
+ /*timeBaseNs=*/1, /*currentTimeNs=*/1, makeRestrictedConfig(/*includeMetric=*/true),
+ mConfigKey);
+ sp<MockRestrictedMetricsManager> metricsManager = new MockRestrictedMetricsManager(mConfigKey);
+ EXPECT_CALL(*metricsManager, onDumpReport).Times(0);
+
+ processor->mMetricsManagers[mConfigKey] = metricsManager;
+ EXPECT_TRUE(processor->mMetricsManagers[mConfigKey]->hasRestrictedMetricsDelegate());
+
+ vector<uint8_t> buffer;
+ processor->onConfigMetricsReportLocked(mConfigKey, /*dumpTimeStampNs=*/1, /*wallClockNs=*/0,
+ /*include_current_partial_bucket=*/true,
+ /*erase_data=*/true, GET_DATA_CALLED, FAST,
+ /*dataSavedToDisk=*/true, &buffer);
+}
+
+TEST_F(StatsLogProcessorTestRestricted, RestrictedMetricFlushIfReachMemoryLimit) {
+ sp<StatsLogProcessor> processor = CreateStatsLogProcessor(
+ /*timeBaseNs=*/1, /*currentTimeNs=*/1, makeRestrictedConfig(/*includeMetric=*/true),
+ mConfigKey);
+ sp<MockRestrictedMetricsManager> metricsManager = new MockRestrictedMetricsManager(mConfigKey);
+ EXPECT_CALL(*metricsManager, flushRestrictedData).Times(1);
+ EXPECT_CALL(*metricsManager, byteSize)
+ .Times(1)
+ .WillOnce(Return(StatsdStats::kBytesPerRestrictedConfigTriggerFlush + 1));
+
+ processor->mMetricsManagers[mConfigKey] = metricsManager;
+ EXPECT_TRUE(processor->mMetricsManagers[mConfigKey]->hasRestrictedMetricsDelegate());
+
+ processor->flushIfNecessaryLocked(mConfigKey, *metricsManager);
+}
+
+TEST_F(StatsLogProcessorTestRestricted, RestrictedMetricNotFlushIfNotReachMemoryLimit) {
+ sp<StatsLogProcessor> processor = CreateStatsLogProcessor(
+ /*timeBaseNs=*/1, /*currentTimeNs=*/1, makeRestrictedConfig(/*includeMetric=*/true),
+ mConfigKey);
+ sp<MockRestrictedMetricsManager> metricsManager = new MockRestrictedMetricsManager(mConfigKey);
+ EXPECT_CALL(*metricsManager, flushRestrictedData).Times(0);
+ EXPECT_CALL(*metricsManager, byteSize)
+ .Times(1)
+ .WillOnce(Return(StatsdStats::kBytesPerRestrictedConfigTriggerFlush - 1));
+
+ processor->mMetricsManagers[mConfigKey] = metricsManager;
+ EXPECT_TRUE(processor->mMetricsManagers[mConfigKey]->hasRestrictedMetricsDelegate());
+
+ processor->flushIfNecessaryLocked(mConfigKey, *metricsManager);
+}
+
+TEST_F(StatsLogProcessorTestRestricted, NonRestrictedMetricsManagerOnDumpReportCalled) {
+ sp<StatsLogProcessor> processor = CreateStatsLogProcessor(
+ /*timeBaseNs=*/1, /*currentTimeNs=*/1, MakeConfig(/*includeMetric=*/true), mConfigKey);
+ sp<MockMetricsManager> metricsManager = new MockMetricsManager(mConfigKey);
+ EXPECT_CALL(*metricsManager, onDumpReport).Times(1);
+
+ processor->mMetricsManagers[mConfigKey] = metricsManager;
+ EXPECT_FALSE(processor->mMetricsManagers[mConfigKey]->hasRestrictedMetricsDelegate());
+
+ vector<uint8_t> buffer;
+ processor->onConfigMetricsReportLocked(mConfigKey, /*dumpTimeStampNs=*/1, /*wallClockNs=*/0,
+ /*include_current_partial_bucket=*/true,
+ /*erase_data=*/true, GET_DATA_CALLED, FAST,
+ /*dataSavedToDisk=*/true, &buffer);
+}
+
+TEST_F(StatsLogProcessorTestRestricted, RestrictedMetricOnDumpReportEmpty) {
+ sp<StatsLogProcessor> processor = CreateStatsLogProcessor(
+ /*timeBaseNs=*/1, /*currentTimeNs=*/1, makeRestrictedConfig(/*includeMetric=*/true),
+ mConfigKey);
+ ProtoOutputStream proto;
+ processor->onDumpReport(mConfigKey, /*dumpTimeStampNs=*/1, /*wallClockNs=*/2,
+ /*include_current_partial_bucket=*/true, /*erase_data=*/true,
+ DEVICE_SHUTDOWN, FAST, &proto);
+ ASSERT_EQ(proto.size(), 0);
+}
+
+TEST_F(StatsLogProcessorTestRestricted, NonRestrictedMetricOnDumpReportNotEmpty) {
+ sp<StatsLogProcessor> processor = CreateStatsLogProcessor(
+ /*timeBaseNs=*/1, /*currentTimeNs=*/1, MakeConfig(/*includeMetric=*/true), mConfigKey);
+
+ ProtoOutputStream proto;
+ processor->onDumpReport(mConfigKey, /*dumpTimeStampNs=*/1, /*wallClockNs=*/2,
+ /*include_current_partial_bucket=*/true, /*erase_data=*/true,
+ DEVICE_SHUTDOWN, FAST, &proto);
+ ASSERT_NE(proto.size(), 0);
+}
+
+TEST_F(StatsLogProcessorTestRestricted, RestrictedMetricNotWriteToDisk) {
+ sp<StatsLogProcessor> processor = CreateStatsLogProcessor(
+ /*timeBaseNs=*/1, /*currentTimeNs=*/1, makeRestrictedConfig(/*includeMetric=*/true),
+ mConfigKey);
+
+ processor->WriteDataToDiskLocked(mConfigKey, /*timestampNs=*/0, /*wallClockNs=*/0,
+ CONFIG_UPDATED, FAST);
+
+ ASSERT_FALSE(StorageManager::hasConfigMetricsReport(mConfigKey));
+}
+
+TEST_F(StatsLogProcessorTestRestricted, NonRestrictedMetricWriteToDisk) {
+ sp<StatsLogProcessor> processor = CreateStatsLogProcessor(
+ /*timeBaseNs=*/1, /*currentTimeNs=*/1, MakeConfig(true), mConfigKey);
+
+ processor->WriteDataToDiskLocked(mConfigKey, /*timestampNs=*/0, /*wallClockNs=*/0,
+ CONFIG_UPDATED, FAST);
+
+ ASSERT_TRUE(StorageManager::hasConfigMetricsReport(mConfigKey));
+}
+
#else
GTEST_LOG_(INFO) << "This test does nothing.\n";
#endif
diff --git a/statsd/tests/StatsService_test.cpp b/statsd/tests/StatsService_test.cpp
index bd76081..ed964a3 100644
--- a/statsd/tests/StatsService_test.cpp
+++ b/statsd/tests/StatsService_test.cpp
@@ -31,15 +31,45 @@
namespace os {
namespace statsd {
+using android::modules::sdklevel::IsAtLeastU;
using android::util::ProtoOutputStream;
using ::ndk::SharedRefBase;
#ifdef __ANDROID__
+namespace {
+
+const int64_t metricId = 123456;
+const int32_t ATOM_TAG = util::SUBSYSTEM_SLEEP_STATE;
+
+StatsdConfig CreateStatsdConfig(const GaugeMetric::SamplingType samplingType) {
+ StatsdConfig config;
+ config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
+ config.add_default_pull_packages("AID_ROOT"); // Fake puller is registered with root.
+ auto atomMatcher = CreateSimpleAtomMatcher("TestMatcher", ATOM_TAG);
+ *config.add_atom_matcher() = atomMatcher;
+ *config.add_gauge_metric() =
+ createGaugeMetric("GAUGE1", atomMatcher.id(), samplingType, nullopt, nullopt);
+ config.set_hash_strings_in_metric_report(false);
+ return config;
+}
+
+class FakeSubsystemSleepCallbackWithTiming : public FakeSubsystemSleepCallback {
+public:
+ Status onPullAtom(int atomTag,
+ const shared_ptr<IPullAtomResultReceiver>& resultReceiver) override {
+ mPullTimeNs = getElapsedRealtimeNs();
+ return FakeSubsystemSleepCallback::onPullAtom(atomTag, resultReceiver);
+ }
+ int64_t mPullTimeNs = 0;
+};
+
+} // namespace
+
TEST(StatsServiceTest, TestAddConfig_simple) {
const sp<UidMap> uidMap = new UidMap();
- shared_ptr<StatsService> service =
- SharedRefBase::make<StatsService>(uidMap, /* queue */ nullptr);
+ shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(
+ uidMap, /* queue */ nullptr, /* LogEventFilter */ nullptr);
const int kConfigKey = 12345;
const int kCallingUid = 123;
StatsdConfig config;
@@ -57,8 +87,8 @@
TEST(StatsServiceTest, TestAddConfig_empty) {
const sp<UidMap> uidMap = new UidMap();
- shared_ptr<StatsService> service =
- SharedRefBase::make<StatsService>(uidMap, /* queue */ nullptr);
+ shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(
+ uidMap, /* queue */ nullptr, /* LogEventFilter */ nullptr);
string serialized = "";
const int kConfigKey = 12345;
const int kCallingUid = 123;
@@ -73,8 +103,8 @@
TEST(StatsServiceTest, TestAddConfig_invalid) {
const sp<UidMap> uidMap = new UidMap();
- shared_ptr<StatsService> service =
- SharedRefBase::make<StatsService>(uidMap, /* queue */ nullptr);
+ shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(
+ uidMap, /* queue */ nullptr, /* LogEventFilter */ nullptr);
string serialized = "Invalid config!";
EXPECT_FALSE(
@@ -92,8 +122,8 @@
int32_t uid;
const sp<UidMap> uidMap = new UidMap();
- shared_ptr<StatsService> service =
- SharedRefBase::make<StatsService>(uidMap, /* queue */ nullptr);
+ shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(
+ uidMap, /* queue */ nullptr, /* LogEventFilter */ nullptr);
service->mEngBuild = true;
// "-1"
@@ -118,6 +148,114 @@
EXPECT_FALSE(service->getUidFromArgs(args, 2, uid));
}
+class StatsServiceStatsdInitTest : public StatsServiceConfigTest,
+ public testing::WithParamInterface<bool> {
+public:
+ StatsServiceStatsdInitTest() : kInitDelaySec(GetParam() ? 0 : 3) {
+ }
+
+ static std::string ToString(testing::TestParamInfo<bool> info) {
+ return info.param ? "NoDelay" : "WithDelay";
+ }
+
+protected:
+ const int kInitDelaySec = 0;
+
+ shared_ptr<StatsService> createStatsService() override {
+ return SharedRefBase::make<StatsService>(new UidMap(), /*queue=*/nullptr,
+ /*LogEventFilter=*/nullptr,
+ /*initEventDelaySecs=*/kInitDelaySec);
+ }
+};
+
+INSTANTIATE_TEST_SUITE_P(StatsServiceStatsdInitTest, StatsServiceStatsdInitTest, testing::Bool(),
+ StatsServiceStatsdInitTest::ToString);
+
+TEST_P(StatsServiceStatsdInitTest, StatsServiceStatsdInitTest) {
+ // used for error threshold tolerance due to sleep() is involved
+ const int64_t ERROR_THRESHOLD_NS = GetParam() ? 1000000 : 5 * 1000000;
+
+ auto pullAtomCallback = SharedRefBase::make<FakeSubsystemSleepCallbackWithTiming>();
+
+ // TODO: evaluate to use service->registerNativePullAtomCallback() API
+ service->mPullerManager->RegisterPullAtomCallback(/*uid=*/0, ATOM_TAG, NS_PER_SEC,
+ NS_PER_SEC * 10, {}, pullAtomCallback);
+
+ const int64_t createConfigTimeNs = getElapsedRealtimeNs();
+ StatsdConfig config = CreateStatsdConfig(GaugeMetric::RANDOM_ONE_SAMPLE);
+ config.set_id(kConfigKey);
+ ASSERT_TRUE(sendConfig(config));
+ ASSERT_EQ(2, pullAtomCallback->pullNum);
+
+ service->mProcessor->mPullerManager->ForceClearPullerCache();
+
+ const int64_t initCompletedTimeNs = getElapsedRealtimeNs();
+ service->onStatsdInitCompleted();
+ ASSERT_EQ(3, pullAtomCallback->pullNum);
+
+ // Checking pull with or without delay according to the flag value
+ const int64_t lastPullNs = pullAtomCallback->mPullTimeNs;
+
+ if (GetParam()) {
+ // when flag is defined - should be small delay between init & pull
+ // expect delay smaller than 1 second
+ EXPECT_GE(lastPullNs, initCompletedTimeNs);
+ EXPECT_LE(lastPullNs, initCompletedTimeNs + ERROR_THRESHOLD_NS);
+ } else {
+ // when flag is not defined - big delay is expected (kInitDelaySec)
+ EXPECT_GE(lastPullNs, initCompletedTimeNs + kInitDelaySec * NS_PER_SEC);
+ EXPECT_LE(lastPullNs,
+ initCompletedTimeNs + kInitDelaySec * NS_PER_SEC + ERROR_THRESHOLD_NS);
+ }
+
+ const int64_t bucketSizeNs =
+ TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000;
+ const int64_t dumpReportTsNanos = createConfigTimeNs + bucketSizeNs + NS_PER_SEC;
+
+ vector<uint8_t> output;
+ ConfigKey configKey(kCallingUid, kConfigKey);
+ service->mProcessor->onDumpReport(configKey, dumpReportTsNanos,
+ /*include_current_bucket=*/false, /*erase_data=*/true,
+ ADB_DUMP, FAST, &output);
+ ConfigMetricsReportList reports;
+ reports.ParseFromArray(output.data(), output.size());
+ ASSERT_EQ(1, reports.reports_size());
+
+ backfillDimensionPath(&reports);
+ backfillStartEndTimestamp(&reports);
+ backfillAggregatedAtoms(&reports);
+ StatsLogReport::GaugeMetricDataWrapper gaugeMetrics =
+ reports.reports(0).metrics(0).gauge_metrics();
+ ASSERT_EQ(gaugeMetrics.skipped_size(), 0);
+ ASSERT_GT((int)gaugeMetrics.data_size(), 0);
+ const auto data = gaugeMetrics.data(0);
+ ASSERT_EQ(2, data.bucket_info_size());
+
+ const auto bucketInfo0 = data.bucket_info(0);
+ const auto bucketInfo1 = data.bucket_info(1);
+
+ EXPECT_GE(NanoToMillis(bucketInfo0.start_bucket_elapsed_nanos()),
+ NanoToMillis(createConfigTimeNs));
+ EXPECT_LE(NanoToMillis(bucketInfo0.start_bucket_elapsed_nanos()),
+ NanoToMillis(createConfigTimeNs + ERROR_THRESHOLD_NS));
+
+ EXPECT_EQ(NanoToMillis(bucketInfo0.end_bucket_elapsed_nanos()),
+ NanoToMillis(bucketInfo1.start_bucket_elapsed_nanos()));
+
+ ASSERT_EQ(1, bucketInfo1.atom_size());
+ ASSERT_GT(bucketInfo1.atom(0).subsystem_sleep_state().time_millis(), 0);
+
+ EXPECT_GE(NanoToMillis(bucketInfo1.start_bucket_elapsed_nanos()),
+ NanoToMillis(createConfigTimeNs + kInitDelaySec * NS_PER_SEC));
+ EXPECT_LE(NanoToMillis(bucketInfo1.start_bucket_elapsed_nanos()),
+ NanoToMillis(createConfigTimeNs + kInitDelaySec * NS_PER_SEC + ERROR_THRESHOLD_NS));
+
+ EXPECT_GE(NanoToMillis(createConfigTimeNs + bucketSizeNs),
+ NanoToMillis(bucketInfo1.end_bucket_elapsed_nanos()));
+ EXPECT_LE(NanoToMillis(createConfigTimeNs + bucketSizeNs),
+ NanoToMillis(bucketInfo1.end_bucket_elapsed_nanos() + ERROR_THRESHOLD_NS));
+}
+
#else
GTEST_LOG_(INFO) << "This test does nothing.\n";
#endif
diff --git a/statsd/tests/UidMap_test.cpp b/statsd/tests/UidMap_test.cpp
index a303ac6..6d3dd7e 100644
--- a/statsd/tests/UidMap_test.cpp
+++ b/statsd/tests/UidMap_test.cpp
@@ -22,6 +22,7 @@
#include "StatsLogProcessor.h"
#include "StatsService.h"
#include "config/ConfigKey.h"
+#include "gtest_matchers.h"
#include "guardrail/StatsdStats.h"
#include "hash.h"
#include "logd/LogEvent.h"
@@ -53,13 +54,6 @@
const vector<vector<uint8_t>> kCertificateHashes{{'a', 'z'}, {'b', 'c'}, {'d', 'e'}};
const vector<bool> kDeleted(3, false);
-void sendIncludeCertificateHashFlagToStatsd(shared_ptr<StatsService> service, bool flag) {
- PropertyParcel certHashParcel;
- certHashParcel.property = kIncludeCertificateHash;
- certHashParcel.value = flag ? "true" : "false";
- service->updateProperties({certHashParcel});
-}
-
void sendPackagesToStatsd(shared_ptr<StatsService> service, const vector<int32_t>& uids,
const vector<int64_t>& versions, const vector<string>& versionStrings,
const vector<string>& apps, const vector<string>& installers,
@@ -114,7 +108,8 @@
StatsLogProcessor p(
m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
[](const ConfigKey& key) { return true; },
- [](const int&, const vector<int64_t>&) { return true; });
+ [](const int&, const vector<int64_t>&) { return true; },
+ [](const ConfigKey&, const string&, const vector<int64_t>&) {}, nullptr);
std::unique_ptr<LogEvent> addEvent = CreateIsolatedUidChangedEvent(
1 /*timestamp*/, 100 /*hostUid*/, 101 /*isolatedUid*/, 1 /*is_create*/);
@@ -130,8 +125,8 @@
TEST(UidMapTest, TestUpdateMap) {
const sp<UidMap> uidMap = new UidMap();
- const shared_ptr<StatsService> service =
- SharedRefBase::make<StatsService>(uidMap, /* queue */ nullptr);
+ const shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(
+ uidMap, /* queue */ nullptr, /* LogEventFilter */ nullptr);
sendPackagesToStatsd(service, kUids, kVersions, kVersionStrings, kApps, kInstallers,
kCertificateHashes);
@@ -149,22 +144,21 @@
name_set = uidMap->getAppNamesFromUid(12345, true /* returnNormalized */);
EXPECT_THAT(name_set, IsEmpty());
- // Certificate hashes are not sent to statsd by default (when flag is not present).
vector<PackageInfo> expectedPackageInfos =
buildPackageInfos(kApps, kUids, kVersions, kVersionStrings, kInstallers,
- /* certHashes */ {}, kDeleted, /* installerIndices */ {},
+ kCertificateHashes, kDeleted, /* installerIndices */ {},
/* hashStrings */ false);
PackageInfoSnapshot packageInfoSnapshot = getPackageInfoSnapshot(uidMap);
EXPECT_THAT(packageInfoSnapshot.package_info(),
- UnorderedPointwise(ProtoEq(), expectedPackageInfos));
+ UnorderedPointwise(EqPackageInfo(), expectedPackageInfos));
}
TEST(UidMapTest, TestUpdateMapMultiple) {
const sp<UidMap> uidMap = new UidMap();
- const shared_ptr<StatsService> service =
- SharedRefBase::make<StatsService>(uidMap, /* queue */ nullptr);
+ const shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(
+ uidMap, /* queue */ nullptr, /* LogEventFilter */ nullptr);
sendPackagesToStatsd(service, kUids, kVersions, kVersionStrings, kApps, kInstallers,
kCertificateHashes);
@@ -194,22 +188,21 @@
name_set = uidMap->getAppNamesFromUid(1500, true /* returnNormalized */);
EXPECT_THAT(name_set, IsEmpty());
- // Certificate hashes are not sent to statsd by default (when flag is not present).
vector<PackageInfo> expectedPackageInfos =
buildPackageInfos(apps, uids, kVersions, kVersionStrings, installers,
- /* certHashes */ {}, kDeleted, /* installerIndices */ {},
+ kCertificateHashes, kDeleted, /* installerIndices */ {},
/* hashStrings */ false);
PackageInfoSnapshot packageInfoSnapshot = getPackageInfoSnapshot(uidMap);
EXPECT_THAT(packageInfoSnapshot.package_info(),
- UnorderedPointwise(ProtoEq(), expectedPackageInfos));
+ UnorderedPointwise(EqPackageInfo(), expectedPackageInfos));
}
TEST(UidMapTest, TestRemoveApp) {
const sp<UidMap> uidMap = new UidMap();
- const shared_ptr<StatsService> service =
- SharedRefBase::make<StatsService>(uidMap, /* queue */ nullptr);
+ const shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(
+ uidMap, /* queue */ nullptr, /* LogEventFilter */ nullptr);
sendPackagesToStatsd(service, kUids, kVersions, kVersionStrings, kApps, kInstallers,
kCertificateHashes);
@@ -224,11 +217,11 @@
deleted[0] = true;
vector<PackageInfo> expectedPackageInfos =
buildPackageInfos(kApps, kUids, kVersions, kVersionStrings, kInstallers,
- /* certHashes */ {}, deleted, /* installerIndices */ {},
+ kCertificateHashes, deleted, /* installerIndices */ {},
/* hashStrings */ false);
PackageInfoSnapshot packageInfoSnapshot = getPackageInfoSnapshot(uidMap);
EXPECT_THAT(packageInfoSnapshot.package_info(),
- UnorderedPointwise(ProtoEq(), expectedPackageInfos));
+ UnorderedPointwise(EqPackageInfo(), expectedPackageInfos));
service->informOnePackageRemoved(kApp2, 1000);
EXPECT_FALSE(uidMap->hasApp(1000, kApp1));
@@ -239,13 +232,12 @@
EXPECT_THAT(name_set, IsEmpty());
deleted[1] = true;
- expectedPackageInfos =
- buildPackageInfos(kApps, kUids, kVersions, kVersionStrings, kInstallers,
- /* certHashes */ {}, deleted, /* installerIndices */ {},
- /* hashStrings */ false);
+ expectedPackageInfos = buildPackageInfos(kApps, kUids, kVersions, kVersionStrings, kInstallers,
+ kCertificateHashes, deleted, /* installerIndices */ {},
+ /* hashStrings */ false);
packageInfoSnapshot = getPackageInfoSnapshot(uidMap);
EXPECT_THAT(packageInfoSnapshot.package_info(),
- UnorderedPointwise(ProtoEq(), expectedPackageInfos));
+ UnorderedPointwise(EqPackageInfo(), expectedPackageInfos));
service->informOnePackageRemoved(kApp3, 1500);
EXPECT_FALSE(uidMap->hasApp(1000, kApp1));
@@ -256,19 +248,18 @@
EXPECT_THAT(name_set, IsEmpty());
deleted[2] = true;
- expectedPackageInfos =
- buildPackageInfos(kApps, kUids, kVersions, kVersionStrings, kInstallers,
- /* certHashes */ {}, deleted, /* installerIndices */ {},
- /* hashStrings */ false);
+ expectedPackageInfos = buildPackageInfos(kApps, kUids, kVersions, kVersionStrings, kInstallers,
+ kCertificateHashes, deleted, /* installerIndices */ {},
+ /* hashStrings */ false);
packageInfoSnapshot = getPackageInfoSnapshot(uidMap);
EXPECT_THAT(packageInfoSnapshot.package_info(),
- UnorderedPointwise(ProtoEq(), expectedPackageInfos));
+ UnorderedPointwise(EqPackageInfo(), expectedPackageInfos));
}
TEST(UidMapTest, TestUpdateApp) {
const sp<UidMap> uidMap = new UidMap();
- const shared_ptr<StatsService> service =
- SharedRefBase::make<StatsService>(uidMap, /* queue */ nullptr);
+ const shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(
+ uidMap, /* queue */ nullptr, /* LogEventFilter */ nullptr);
sendPackagesToStatsd(service, kUids, kVersions, kVersionStrings, kApps, kInstallers,
kCertificateHashes);
@@ -308,15 +299,15 @@
vector<string> apps = concatenate(kApps, {"NeW_aPP1_NAmE", "NeW_aPP1_NAmE"});
vector<string> installers = concatenate(kInstallers, {"com.android.vending", "new_installer"});
vector<bool> deleted = concatenate(kDeleted, {false, false});
- // Certificate hashes are not sent to statsd by default (when flag is not present).
+ vector<vector<uint8_t>> certHashes = concatenate(kCertificateHashes, {{'a'}, {'b'}});
vector<PackageInfo> expectedPackageInfos =
- buildPackageInfos(apps, uids, versions, versionStrings, installers, /* certHashes */ {},
- deleted, /* installerIndices */ {},
+ buildPackageInfos(apps, uids, versions, versionStrings, installers, certHashes, deleted,
+ /* installerIndices */ {},
/* hashStrings */ false);
PackageInfoSnapshot packageInfoSnapshot = getPackageInfoSnapshot(uidMap);
EXPECT_THAT(packageInfoSnapshot.package_info(),
- UnorderedPointwise(ProtoEq(), expectedPackageInfos));
+ UnorderedPointwise(EqPackageInfo(), expectedPackageInfos));
}
// Test that uid map returns at least one snapshot even if we already obtained
@@ -568,81 +559,6 @@
ASSERT_EQ(1U, m.mChanges.size());
}
-// Set up parameterized test for testing with different boolean flag values that gate the inclusion
-// of package certificate hashes in UidMap.
-class UidMapTestWithAppCertificateFlag : public TestWithParam<bool> {
-protected:
- const sp<UidMap> uidMap;
- const shared_ptr<StatsService> service;
- vector<PackageInfo> expectedPackageInfos;
-
- UidMapTestWithAppCertificateFlag()
- : uidMap(new UidMap()),
- service(SharedRefBase::make<StatsService>(uidMap, /* queue */ nullptr)) {
- }
-
- void SetUp() override {
- sendIncludeCertificateHashFlagToStatsd(service, GetParam());
- sendPackagesToStatsd(service, kUids, kVersions, kVersionStrings, kApps, kInstallers,
- kCertificateHashes);
-
- vector<vector<uint8_t>> emptyCertHashes;
- vector<vector<uint8_t>> certHashes = GetParam() ? kCertificateHashes : emptyCertHashes;
- expectedPackageInfos = buildPackageInfos(
- kApps, kUids, kVersions, kVersionStrings, kInstallers, certHashes, kDeleted,
- /* installerIndices */ {}, /* hashStrings */ false);
- }
-};
-
-INSTANTIATE_TEST_SUITE_P(Boolean, UidMapTestWithAppCertificateFlag, Bool(),
- PrintToStringParamName());
-
-TEST_P(UidMapTestWithAppCertificateFlag, TestUpdateMap) {
- PackageInfoSnapshot packageInfoSnapshot = getPackageInfoSnapshot(uidMap);
-
- EXPECT_THAT(packageInfoSnapshot.package_info(),
- UnorderedPointwise(ProtoEq(), expectedPackageInfos));
-}
-
-TEST_P(UidMapTestWithAppCertificateFlag, TestUpdateApp) {
- // Add a new name for uid 1000.
- service->informOnePackage("NeW_aPP1_NAmE", 1000, /* version */ 40,
- /* versionString */ "v40", /* installer */ "com.android.vending",
- /* certificateHash */ {'a'});
-
- vector<uint8_t> certHash;
- if (GetParam()) {
- certHash = {'a'};
- }
- PackageInfo expectedPackageInfo =
- buildPackageInfo("NeW_aPP1_NAmE", 1000, /* version */ 40, /* versionString */ "v40",
- /* installer */ "com.android.vending", certHash, /* deleted */ false,
- /* hashStrings */ false, /* installerIndex */ nullopt);
- expectedPackageInfos.push_back(expectedPackageInfo);
-
- PackageInfoSnapshot packageInfoSnapshot = getPackageInfoSnapshot(uidMap);
- EXPECT_THAT(packageInfoSnapshot.package_info(),
- UnorderedPointwise(ProtoEq(), expectedPackageInfos));
-
- // Update installer and certificate for the same package.
- service->informOnePackage("NeW_aPP1_NAmE", 1000, /* version */ 40,
- /* versionString */ "v40", /* installer */ "new_installer",
- /* certificateHash */ {'n', 'e', 'w'});
-
- if (GetParam()) {
- certHash = {'n', 'e', 'w'};
- }
- expectedPackageInfo =
- buildPackageInfo("NeW_aPP1_NAmE", 1000, /* version */ 40, /* versionString */ "v40",
- /* installer */ "new_installer", certHash, /* deleted */ false,
- /* hashStrings */ false, /* installerIndex */ nullopt);
- expectedPackageInfos.back() = expectedPackageInfo;
-
- packageInfoSnapshot = getPackageInfoSnapshot(uidMap);
- EXPECT_THAT(packageInfoSnapshot.package_info(),
- UnorderedPointwise(ProtoEq(), expectedPackageInfos));
-}
-
class UidMapTestAppendUidMap : public Test {
protected:
const ConfigKey config1;
@@ -656,11 +572,11 @@
UidMapTestAppendUidMap()
: config1(1, StringToId("config1")),
uidMap(new UidMap()),
- service(SharedRefBase::make<StatsService>(uidMap, /* queue */ nullptr)) {
+ service(SharedRefBase::make<StatsService>(uidMap, /* queue */ nullptr,
+ /* LogEventFilter */ nullptr)) {
}
void SetUp() override {
- sendIncludeCertificateHashFlagToStatsd(service, true);
sendPackagesToStatsd(service, kUids, kVersions, kVersionStrings, kApps, kInstallers,
kCertificateHashes);
@@ -707,7 +623,7 @@
EXPECT_THAT(strSet, IsSupersetOf(kApps));
EXPECT_THAT(results.snapshots(0).package_info(),
- UnorderedPointwise(ProtoEq(), expectedPackageInfos));
+ UnorderedPointwise(EqPackageInfo(), expectedPackageInfos));
}
TEST_F(UidMapTestAppendUidMap, TestInstallersInReportIncludeInstallerAndDontHashStrings) {
@@ -735,7 +651,7 @@
/* hashStrings */ false);
EXPECT_THAT(results.snapshots(0).package_info(),
- UnorderedPointwise(ProtoEq(), expectedPackageInfos));
+ UnorderedPointwise(EqPackageInfo(), expectedPackageInfos));
}
// Set up parameterized test with set<string>* parameter to control whether strings are hashed
@@ -813,7 +729,7 @@
/* hashStrings */ false);
EXPECT_THAT(results.snapshots(0).package_info(),
- UnorderedPointwise(ProtoEq(), expectedPackageInfos));
+ UnorderedPointwise(EqPackageInfo(), expectedPackageInfos));
}
#else
diff --git a/statsd/tests/e2e/ConfigUpdate_e2e_ab_test.cpp b/statsd/tests/e2e/ConfigUpdate_e2e_ab_test.cpp
index 92955bb..e078f47 100644
--- a/statsd/tests/e2e/ConfigUpdate_e2e_ab_test.cpp
+++ b/statsd/tests/e2e/ConfigUpdate_e2e_ab_test.cpp
@@ -98,7 +98,8 @@
ASSERT_EQ(uidMapping.snapshots_size(), 1);
ASSERT_EQ(uidMapping.snapshots(0).package_info_size(), 1);
EXPECT_FALSE(uidMapping.snapshots(0).package_info(0).has_version_string());
- EXPECT_EQ(uidMapping.snapshots(0).package_info(0).installer(), "installer1");
+ EXPECT_EQ(uidMapping.snapshots(0).package_info(0).installer_index(), 0);
+ EXPECT_THAT(uidMapping.installer_name(), ElementsAre("installer1"));
}
TEST_P(ConfigUpdateE2eAbTest, TestHashStrings) {
diff --git a/statsd/tests/e2e/ConfigUpdate_e2e_test.cpp b/statsd/tests/e2e/ConfigUpdate_e2e_test.cpp
index 2250da3..ead1c46 100644
--- a/statsd/tests/e2e/ConfigUpdate_e2e_test.cpp
+++ b/statsd/tests/e2e/ConfigUpdate_e2e_test.cpp
@@ -47,12 +47,44 @@
EXPECT_EQ(value.value_tuple().dimensions_value(0).value_str(), name);
}
+sp<StatsLogProcessor> CreateStatsLogProcessor(
+ const int64_t timeBaseNs, const int64_t currentTimeNs, const StatsdConfig& config,
+ const ConfigKey& key, const shared_ptr<MockLogEventFilter>& logEventFilter) {
+ if (logEventFilter) {
+ // call from StatsLogProcessor constructor
+ Expectation initCall = EXPECT_CALL(*logEventFilter,
+ setAtomIds(StatsLogProcessor::getDefaultAtomIdSet(), _))
+ .Times(1);
+ EXPECT_CALL(*logEventFilter, setAtomIds(CreateAtomIdSetFromConfig(config), _))
+ .Times(1)
+ .After(initCall);
+ }
+
+ return CreateStatsLogProcessor(timeBaseNs, currentTimeNs, config, key, nullptr, 0, new UidMap(),
+ logEventFilter);
+}
+
} // Anonymous namespace.
// Setup for test fixture.
-class ConfigUpdateE2eTest : public ::testing::Test {};
+class ConfigUpdateE2eTest : public testing::TestWithParam<bool> {
+protected:
+ std::shared_ptr<MockLogEventFilter> mLogEventFilter;
-TEST_F(ConfigUpdateE2eTest, TestEventMetric) {
+ void SetUp() override {
+ mLogEventFilter = GetParam() ? std::make_shared<MockLogEventFilter>() : nullptr;
+ }
+
+public:
+ static std::string ToString(testing::TestParamInfo<bool> info) {
+ return info.param ? "WithLogEventFilter" : "NoLogEventFilter";
+ }
+};
+
+INSTANTIATE_TEST_SUITE_P(ConfigUpdateE2eTest, ConfigUpdateE2eTest, testing::Bool(),
+ ConfigUpdateE2eTest::ToString);
+
+TEST_P(ConfigUpdateE2eTest, TestEventMetric) {
StatsdConfig config;
config.add_allowed_log_source("AID_ROOT");
@@ -99,8 +131,8 @@
ConfigKey key(123, 987);
uint64_t bucketStartTimeNs = 10000000000; // 0:10
- sp<StatsLogProcessor> processor =
- CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key);
+ sp<StatsLogProcessor> processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs,
+ config, key, mLogEventFilter);
int app1Uid = 123;
vector<int> attributionUids1 = {app1Uid};
@@ -161,6 +193,11 @@
*newConfig.add_event_metric() = eventPersist;
int64_t updateTimeNs = bucketStartTimeNs + 60 * NS_PER_SEC;
+ if (GetParam()) {
+ EXPECT_CALL(*mLogEventFilter,
+ setAtomIds(CreateAtomIdSetFromConfig(newConfig), processor.get()))
+ .Times(1);
+ }
processor->OnConfigUpdated(updateTimeNs, key, newConfig);
// Send events after the update.
@@ -274,7 +311,7 @@
EXPECT_EQ(data.atom().sync_state_changed().sync_name(), "sync3");
}
-TEST_F(ConfigUpdateE2eTest, TestCountMetric) {
+TEST_P(ConfigUpdateE2eTest, TestCountMetric) {
StatsdConfig config;
config.add_allowed_log_source("AID_ROOT");
@@ -336,8 +373,8 @@
ConfigKey key(123, 987);
uint64_t bucketStartTimeNs = 10000000000; // 0:10
uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(TEN_MINUTES) * 1000000LL;
- sp<StatsLogProcessor> processor =
- CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key);
+ sp<StatsLogProcessor> processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs,
+ config, key, mLogEventFilter);
int app1Uid = 123, app2Uid = 456;
vector<int> attributionUids1 = {app1Uid};
@@ -399,6 +436,11 @@
*newConfig.add_count_metric() = countPersist;
int64_t updateTimeNs = bucketStartTimeNs + 60 * NS_PER_SEC;
+ if (GetParam()) {
+ EXPECT_CALL(*mLogEventFilter,
+ setAtomIds(CreateAtomIdSetFromConfig(newConfig), processor.get()))
+ .Times(1);
+ }
processor->OnConfigUpdated(updateTimeNs, key, newConfig);
// Send events after the update. Counts reset to 0 since this is a new bucket.
@@ -527,7 +569,7 @@
ValidateCountBucket(data.bucket_info(0), updateTimeNs, bucketStartTimeNs + bucketSizeNs, 2);
}
-TEST_F(ConfigUpdateE2eTest, TestDurationMetric) {
+TEST_P(ConfigUpdateE2eTest, TestDurationMetric) {
StatsdConfig config;
config.add_allowed_log_source("AID_ROOT");
@@ -607,8 +649,8 @@
ConfigKey key(123, 987);
uint64_t bucketStartTimeNs = 10000000000; // 0:10
uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(TEN_MINUTES) * 1000000LL;
- sp<StatsLogProcessor> processor =
- CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key);
+ sp<StatsLogProcessor> processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs,
+ config, key, mLogEventFilter);
int app1Uid = 123, app2Uid = 456, app3Uid = 789;
vector<int> attributionUids1 = {app1Uid};
@@ -683,6 +725,11 @@
// At update, only uid 1 is syncing & holding a wakelock, duration=33. Max is paused for uid3.
// Before the update, only uid2 will report a duration for max, since others are started/paused.
int64_t updateTimeNs = bucketStartTimeNs + 60 * NS_PER_SEC;
+ if (GetParam()) {
+ EXPECT_CALL(*mLogEventFilter,
+ setAtomIds(CreateAtomIdSetFromConfig(newConfig), processor.get()))
+ .Times(1);
+ }
processor->OnConfigUpdated(updateTimeNs, key, newConfig);
// Send events after the update.
@@ -880,7 +927,7 @@
ValidateDurationBucket(data.bucket_info(0), updateTimeNs, bucketEndTimeNs, 20 * NS_PER_SEC);
}
-TEST_F(ConfigUpdateE2eTest, TestGaugeMetric) {
+TEST_P(ConfigUpdateE2eTest, TestGaugeMetric) {
StatsdConfig config;
config.add_allowed_log_source("AID_ROOT");
config.add_default_pull_packages("AID_ROOT"); // Fake puller is registered with root.
@@ -941,9 +988,19 @@
ConfigKey key(123, 987);
uint64_t bucketStartTimeNs = getElapsedRealtimeNs(); // 0:10
uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(TEN_MINUTES) * 1000000LL;
- sp<StatsLogProcessor> processor = CreateStatsLogProcessor(
- bucketStartTimeNs, bucketStartTimeNs, config, key,
- SharedRefBase::make<FakeSubsystemSleepCallback>(), util::SUBSYSTEM_SLEEP_STATE);
+ if (GetParam()) {
+ // call from StatsLogProcessor constructor
+ Expectation initCall = EXPECT_CALL(*mLogEventFilter,
+ setAtomIds(StatsLogProcessor::getDefaultAtomIdSet(), _))
+ .Times(1);
+ EXPECT_CALL(*mLogEventFilter, setAtomIds(CreateAtomIdSetFromConfig(config), _))
+ .Times(1)
+ .After(initCall);
+ }
+ sp<StatsLogProcessor> processor =
+ CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key,
+ SharedRefBase::make<FakeSubsystemSleepCallback>(),
+ util::SUBSYSTEM_SLEEP_STATE, new UidMap(), mLogEventFilter);
int app1Uid = 123, app2Uid = 456;
@@ -1009,6 +1066,9 @@
int64_t updateTimeNs = bucketStartTimeNs + 60 * NS_PER_SEC;
// Update pulls gaugePullPersist and gaugeNew.
+ if (GetParam()) {
+ EXPECT_CALL(*mLogEventFilter, setAtomIds(CreateAtomIdSetFromConfig(newConfig), _)).Times(1);
+ }
processor->OnConfigUpdated(updateTimeNs, key, newConfig);
// Verify puller manager is properly set.
@@ -1275,7 +1335,7 @@
EXPECT_EQ(data.bucket_info(0).atom(0).subsystem_sleep_state().time_millis(), 902);
}
-TEST_F(ConfigUpdateE2eTest, TestValueMetric) {
+TEST_P(ConfigUpdateE2eTest, TestValueMetric) {
StatsdConfig config;
config.add_allowed_log_source("AID_ROOT");
config.add_default_pull_packages("AID_ROOT"); // Fake puller is registered with root.
@@ -1329,10 +1389,20 @@
ConfigKey key(123, 987);
uint64_t bucketStartTimeNs = getElapsedRealtimeNs();
uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(TEN_MINUTES) * 1000000LL;
+ if (GetParam()) {
+ // call from StatsLogProcessor constructor
+ Expectation initCall = EXPECT_CALL(*mLogEventFilter,
+ setAtomIds(StatsLogProcessor::getDefaultAtomIdSet(), _))
+ .Times(1);
+ EXPECT_CALL(*mLogEventFilter, setAtomIds(CreateAtomIdSetFromConfig(config), _))
+ .Times(1)
+ .After(initCall);
+ }
// Config creation triggers pull #1.
- sp<StatsLogProcessor> processor = CreateStatsLogProcessor(
- bucketStartTimeNs, bucketStartTimeNs, config, key,
- SharedRefBase::make<FakeSubsystemSleepCallback>(), util::SUBSYSTEM_SLEEP_STATE);
+ sp<StatsLogProcessor> processor =
+ CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key,
+ SharedRefBase::make<FakeSubsystemSleepCallback>(),
+ util::SUBSYSTEM_SLEEP_STATE, new UidMap(), mLogEventFilter);
// Initialize log events before update.
// ValuePushPersist and ValuePullPersist will skip the bucket due to condition unknown.
@@ -1380,6 +1450,9 @@
int64_t updateTimeNs = bucketStartTimeNs + 30 * NS_PER_SEC;
// Update pulls valuePullPersist and valueNew. Pull #3.
+ if (GetParam()) {
+ EXPECT_CALL(*mLogEventFilter, setAtomIds(CreateAtomIdSetFromConfig(newConfig), _)).Times(1);
+ }
processor->OnConfigUpdated(updateTimeNs, key, newConfig);
// Verify puller manager is properly set.
@@ -1602,7 +1675,7 @@
EXPECT_EQ(skipBucket.drop_event(0).drop_reason(), BucketDropReason::DUMP_REPORT_REQUESTED);
}
-TEST_F(ConfigUpdateE2eTest, TestKllMetric) {
+TEST_P(ConfigUpdateE2eTest, TestKllMetric) {
StatsdConfig config;
config.add_allowed_log_source("AID_ROOT");
@@ -1637,8 +1710,8 @@
ConfigKey key(123, 987);
const uint64_t bucketStartTimeNs = 10000000000; // 0:10
uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(TEN_MINUTES) * 1000000LL;
- sp<StatsLogProcessor> processor =
- CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key);
+ sp<StatsLogProcessor> processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs,
+ config, key, mLogEventFilter);
// Initialize log events before update.
// kllPersist and kllRemove will skip the bucket due to condition unknown.
@@ -1684,6 +1757,11 @@
*newConfig.add_kll_metric() = kllPersist;
int64_t updateTimeNs = bucketStartTimeNs + 30 * NS_PER_SEC;
+ if (GetParam()) {
+ EXPECT_CALL(*mLogEventFilter,
+ setAtomIds(CreateAtomIdSetFromConfig(newConfig), processor.get()))
+ .Times(1);
+ }
processor->OnConfigUpdated(updateTimeNs, key, newConfig);
// Send events after the update. This is a new bucket.
@@ -1815,7 +1893,7 @@
EXPECT_EQ(kllPersistAfter.kll_metrics().skipped_size(), 0);
}
-TEST_F(ConfigUpdateE2eTest, TestMetricActivation) {
+TEST_P(ConfigUpdateE2eTest, TestMetricActivation) {
ALOGE("Start ConfigUpdateE2eTest#TestMetricActivation");
StatsdConfig config;
config.add_allowed_log_source("AID_ROOT");
@@ -1893,8 +1971,8 @@
ConfigKey key(123, 987);
uint64_t bucketStartTimeNs = 10000000000; // 0:10
uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(TEN_MINUTES) * 1000000LL;
- sp<StatsLogProcessor> processor =
- CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key);
+ sp<StatsLogProcessor> processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs,
+ config, key, mLogEventFilter);
int uid1 = 55555;
// Initialize log events before update.
@@ -1943,6 +2021,11 @@
*newConfig.add_metric_activation() = immediateMetricActivation;
int64_t updateTimeNs = bucketStartTimeNs + 40 * NS_PER_SEC;
+ if (GetParam()) {
+ EXPECT_CALL(*mLogEventFilter,
+ setAtomIds(CreateAtomIdSetFromConfig(newConfig), processor.get()))
+ .Times(1);
+ }
processor->OnConfigUpdated(updateTimeNs, key, newConfig);
// The reboot will write to disk again, so sleep for 1 second to avoid this.
@@ -1961,7 +2044,7 @@
// On boot, use StartUp. However, skip config manager for simplicity.
int64_t bootTimeNs = bucketStartTimeNs + 55 * NS_PER_SEC;
- processor = CreateStatsLogProcessor(bootTimeNs, bootTimeNs, newConfig, key);
+ processor = CreateStatsLogProcessor(bootTimeNs, bootTimeNs, newConfig, key, mLogEventFilter);
processor->LoadActiveConfigsFromDisk();
processor->LoadMetadataFromDisk(getWallClockNs(), bootTimeNs);
@@ -2066,7 +2149,7 @@
ALOGE("End ConfigUpdateE2eTest#TestMetricActivation");
}
-TEST_F(ConfigUpdateE2eTest, TestAnomalyCountMetric) {
+TEST_P(ConfigUpdateE2eTest, TestAnomalyCountMetric) {
StatsdConfig config;
config.add_allowed_log_source("AID_ROOT");
@@ -2163,8 +2246,8 @@
uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(TEN_MINUTES) * 1000000LL;
uint64_t bucketStartTimeNs = 10000000000; // 0:10
uint64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs;
- sp<StatsLogProcessor> processor =
- CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key);
+ sp<StatsLogProcessor> processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs,
+ config, key, mLogEventFilter);
StatsDimensionsValueParcel wlUid1 =
CreateAttributionUidDimensionsValueParcel(util::WAKELOCK_STATE_CHANGED, app1Uid);
@@ -2242,6 +2325,11 @@
SubscriberReporter::getInstance().setBroadcastSubscriber(key, newSubId, newBroadcast);
int64_t updateTimeNs = bucket2StartTimeNs + 15 * NS_PER_SEC;
+ if (GetParam()) {
+ EXPECT_CALL(*mLogEventFilter,
+ setAtomIds(CreateAtomIdSetFromConfig(newConfig), processor.get()))
+ .Times(1);
+ }
processor->OnConfigUpdated(updateTimeNs, key, newConfig);
// Within refractory of AlertPreserve, but AlertNew should fire since the full bucket has 2.
@@ -2277,7 +2365,7 @@
SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, newSubId);
}
-TEST_F(ConfigUpdateE2eTest, TestAnomalyDurationMetric) {
+TEST_P(ConfigUpdateE2eTest, TestAnomalyDurationMetric) {
StatsdConfig config;
config.add_allowed_log_source("AID_ROOT");
@@ -2381,8 +2469,17 @@
SubscriberReporter::getInstance().setBroadcastSubscriber(key, removeSubId, removeBroadcast);
const sp<UidMap> uidMap = new UidMap();
- const shared_ptr<StatsService> service =
- SharedRefBase::make<StatsService>(uidMap, /* queue */ nullptr);
+ if (GetParam()) {
+ // call from StatsLogProcessor constructor
+ Expectation initCall = EXPECT_CALL(*mLogEventFilter,
+ setAtomIds(StatsLogProcessor::getDefaultAtomIdSet(), _))
+ .Times(1);
+ EXPECT_CALL(*mLogEventFilter, setAtomIds(CreateAtomIdSetFromConfig(config), _))
+ .Times(1)
+ .After(initCall);
+ }
+ const shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(
+ uidMap, /* queue */ nullptr, /* LogEventFilter */ mLogEventFilter);
sp<StatsLogProcessor> processor = service->mProcessor;
uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(TEN_MINUTES) * 1000000LL;
int64_t bucketStartTimeNs = processor->mTimeBaseNs;
@@ -2569,6 +2666,9 @@
SubscriberReporter::getInstance().setBroadcastSubscriber(key, newSubId, newBroadcast);
int64_t updateTimeNs = bucket2StartTimeNs + 50 * NS_PER_SEC;
+ if (GetParam()) {
+ EXPECT_CALL(*mLogEventFilter, setAtomIds(CreateAtomIdSetFromConfig(newConfig), _)).Times(1);
+ }
processor->OnConfigUpdated(updateTimeNs, key, newConfig);
// Alert preserve will set alarm after the refractory period, but alert new will set it for
@@ -2628,7 +2728,7 @@
SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, newSubId);
}
-TEST_F(ConfigUpdateE2eTest, TestAlarms) {
+TEST_P(ConfigUpdateE2eTest, TestAlarms) {
StatsdConfig config;
config.add_allowed_log_source("AID_ROOT");
Alarm alarmPreserve = createAlarm("AlarmPreserve", /*offset*/ 5 * MS_PER_SEC,
@@ -2700,7 +2800,7 @@
int64_t startTimeSec = 10;
sp<StatsLogProcessor> processor = CreateStatsLogProcessor(
- startTimeSec * NS_PER_SEC, startTimeSec * NS_PER_SEC, config, key);
+ startTimeSec * NS_PER_SEC, startTimeSec * NS_PER_SEC, config, key, mLogEventFilter);
sp<AlarmMonitor> alarmMonitor = processor->getPeriodicAlarmMonitor();
// First alarm is for alarm preserve's offset of 5 seconds.
@@ -2756,7 +2856,9 @@
return Status::ok();
});
SubscriberReporter::getInstance().setBroadcastSubscriber(key, newSubId, newBroadcast);
-
+ if (GetParam()) {
+ EXPECT_CALL(*mLogEventFilter, setAtomIds(CreateAtomIdSetFromConfig(newConfig), _)).Times(1);
+ }
processor->OnConfigUpdated((startTimeSec + 90) * NS_PER_SEC, key, newConfig);
// After the update, the alarm time should remain unchanged since alarm replace now fires every
// minute with no offset.
@@ -2796,7 +2898,7 @@
SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, newSubId);
}
-TEST_F(ConfigUpdateE2eTest, TestNewDurationExistingWhat) {
+TEST_P(ConfigUpdateE2eTest, TestNewDurationExistingWhat) {
StatsdConfig config;
config.add_allowed_log_source("AID_ROOT");
*config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher();
@@ -2808,8 +2910,8 @@
ConfigKey key(123, 987);
uint64_t bucketStartTimeNs = 10000000000; // 0:10
uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(FIVE_MINUTES) * 1000000LL;
- sp<StatsLogProcessor> processor =
- CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key);
+ sp<StatsLogProcessor> processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs,
+ config, key, mLogEventFilter);
int app1Uid = 123;
vector<int> attributionUids1 = {app1Uid};
@@ -2828,6 +2930,11 @@
durationMetric->set_bucket(FIVE_MINUTES);
uint64_t updateTimeNs = bucketStartTimeNs + 60 * NS_PER_SEC; // 1:00
+ if (GetParam()) {
+ EXPECT_CALL(*mLogEventFilter,
+ setAtomIds(CreateAtomIdSetFromConfig(config), processor.get()))
+ .Times(1);
+ }
processor->OnConfigUpdated(updateTimeNs, key, config);
event = CreateReleaseWakelockEvent(bucketStartTimeNs + 80 * NS_PER_SEC, attributionUids1,
@@ -2858,7 +2965,7 @@
EXPECT_EQ(bucketInfo.duration_nanos(), 20 * NS_PER_SEC);
}
-TEST_F(ConfigUpdateE2eTest, TestNewDurationExistingWhatSlicedCondition) {
+TEST_P(ConfigUpdateE2eTest, TestNewDurationExistingWhatSlicedCondition) {
StatsdConfig config;
config.add_allowed_log_source("AID_ROOT");
*config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher();
@@ -2880,8 +2987,8 @@
ConfigKey key(123, 987);
uint64_t bucketStartTimeNs = 10000000000; // 0:10
uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(FIVE_MINUTES) * 1000000LL;
- sp<StatsLogProcessor> processor =
- CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key);
+ sp<StatsLogProcessor> processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs,
+ config, key, mLogEventFilter);
int app1Uid = 123, app2Uid = 456;
vector<int> attributionUids1 = {app1Uid};
@@ -2918,6 +3025,11 @@
CreateDimensions(util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /*uid*/});
uint64_t updateTimeNs = bucketStartTimeNs + 60 * NS_PER_SEC; // 1:00
+ if (GetParam()) {
+ EXPECT_CALL(*mLogEventFilter,
+ setAtomIds(CreateAtomIdSetFromConfig(config), processor.get()))
+ .Times(1);
+ }
processor->OnConfigUpdated(updateTimeNs, key, config);
event = CreateMoveToBackgroundEvent(bucketStartTimeNs + 73 * NS_PER_SEC, app2Uid); // 1:13
@@ -2957,7 +3069,7 @@
EXPECT_EQ(bucketInfo.duration_nanos(), 17 * NS_PER_SEC);
}
-TEST_F(ConfigUpdateE2eTest, TestNewDurationExistingWhatSlicedState) {
+TEST_P(ConfigUpdateE2eTest, TestNewDurationExistingWhatSlicedState) {
StatsdConfig config;
config.add_allowed_log_source("AID_ROOT");
*config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher();
@@ -2992,8 +3104,8 @@
ConfigKey key(123, 987);
uint64_t bucketStartTimeNs = 10000000000; // 0:10
uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(FIVE_MINUTES) * 1000000LL;
- sp<StatsLogProcessor> processor =
- CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key);
+ sp<StatsLogProcessor> processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs,
+ config, key, mLogEventFilter);
int app1Uid = 123, app2Uid = 456;
vector<int> attributionUids1 = {app1Uid};
@@ -3032,6 +3144,11 @@
CreateDimensions(util::UID_PROCESS_STATE_CHANGED, {1 /*uid*/});
uint64_t updateTimeNs = bucketStartTimeNs + 60 * NS_PER_SEC; // 1:00
+ if (GetParam()) {
+ EXPECT_CALL(*mLogEventFilter,
+ setAtomIds(CreateAtomIdSetFromConfig(config), processor.get()))
+ .Times(1);
+ }
processor->OnConfigUpdated(updateTimeNs, key, config);
event = CreateAcquireWakelockEvent(bucketStartTimeNs + 72 * NS_PER_SEC, attributionUids2,
diff --git a/statsd/tests/e2e/DurationMetric_e2e_test.cpp b/statsd/tests/e2e/DurationMetric_e2e_test.cpp
index 09ce418..4dcef51 100644
--- a/statsd/tests/e2e/DurationMetric_e2e_test.cpp
+++ b/statsd/tests/e2e/DurationMetric_e2e_test.cpp
@@ -237,6 +237,10 @@
sp<AlarmMonitor> subscriberAlarmMonitor;
vector<int64_t> activeConfigsBroadcast;
+ std::shared_ptr<MockLogEventFilter> mockLogEventFilter = std::make_shared<MockLogEventFilter>();
+ EXPECT_CALL(*mockLogEventFilter, setAtomIds(StatsLogProcessor::getDefaultAtomIdSet(), _))
+ .Times(1);
+
int broadcastCount = 0;
StatsLogProcessor processor(
m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, bucketStartTimeNs,
@@ -249,7 +253,11 @@
activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), activeConfigs.begin(),
activeConfigs.end());
return true;
- });
+ },
+ [](const ConfigKey&, const string&, const vector<int64_t>&) {}, mockLogEventFilter);
+
+ EXPECT_CALL(*mockLogEventFilter, setAtomIds(CreateAtomIdSetFromConfig(config), &processor))
+ .Times(1);
processor.OnConfigUpdated(bucketStartTimeNs, cfgKey, config); // 0:00
diff --git a/statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp b/statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp
index 011c28f..474cb7a 100644
--- a/statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp
+++ b/statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp
@@ -68,23 +68,7 @@
} // namespaces
-// Setup for test fixture.
-class GaugeMetricE2ePulledTest : public ::testing::TestWithParam<string> {
- void SetUp() override {
- FlagProvider::getInstance().overrideFuncs(&isAtLeastSFuncTrue);
- FlagProvider::getInstance().overrideFlag(LIMIT_PULL_FLAG, GetParam(),
- /*isBootFlag=*/true);
- }
-
- void TearDown() override {
- FlagProvider::getInstance().resetOverrides();
- }
-};
-
-INSTANTIATE_TEST_SUITE_P(LimitPull, GaugeMetricE2ePulledTest,
- testing::Values(FLAG_FALSE, FLAG_TRUE));
-
-TEST_P(GaugeMetricE2ePulledTest, TestRandomSamplePulledEvents) {
+TEST(GaugeMetricE2ePulledTest, TestRandomSamplePulledEvents) {
auto config = CreateStatsdConfig(GaugeMetric::RANDOM_ONE_SAMPLE);
int64_t baseTimeNs = getElapsedRealtimeNs();
int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs;
@@ -224,7 +208,7 @@
EXPECT_GT(data.bucket_info(5).atom(0).subsystem_sleep_state().time_millis(), 0);
}
-TEST_P(GaugeMetricE2ePulledTest, TestFirstNSamplesPulledNoTrigger) {
+TEST(GaugeMetricE2ePulledTest, TestFirstNSamplesPulledNoTrigger) {
StatsdConfig config = CreateStatsdConfig(GaugeMetric::FIRST_N_SAMPLES);
auto gaugeMetric = config.mutable_gauge_metric(0);
gaugeMetric->set_max_num_gauge_atoms_per_bucket(3);
@@ -346,7 +330,7 @@
/*eventTimesNs=*/{(int64_t)(configAddedTimeNs + (2 * bucketSizeNs) + 2)});
}
-TEST_P(GaugeMetricE2ePulledTest, TestConditionChangeToTrueSamplePulledEvents) {
+TEST(GaugeMetricE2ePulledTest, TestConditionChangeToTrueSamplePulledEvents) {
auto config = CreateStatsdConfig(GaugeMetric::CONDITION_CHANGE_TO_TRUE);
int64_t baseTimeNs = getElapsedRealtimeNs();
int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs;
@@ -444,7 +428,7 @@
EXPECT_GT(data.bucket_info(2).atom(1).subsystem_sleep_state().time_millis(), 0);
}
-TEST_P(GaugeMetricE2ePulledTest, TestRandomSamplePulledEvent_LateAlarm) {
+TEST(GaugeMetricE2ePulledTest, TestRandomSamplePulledEvent_LateAlarm) {
auto config = CreateStatsdConfig(GaugeMetric::RANDOM_ONE_SAMPLE);
int64_t baseTimeNs = getElapsedRealtimeNs();
int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs;
@@ -542,7 +526,7 @@
EXPECT_GT(data.bucket_info(2).atom(0).subsystem_sleep_state().time_millis(), 0);
}
-TEST_P(GaugeMetricE2ePulledTest, TestRandomSamplePulledEventsWithActivation) {
+TEST(GaugeMetricE2ePulledTest, TestRandomSamplePulledEventsWithActivation) {
auto config = CreateStatsdConfig(GaugeMetric::RANDOM_ONE_SAMPLE, /*useCondition=*/false);
int64_t baseTimeNs = getElapsedRealtimeNs();
@@ -721,7 +705,7 @@
EXPECT_EQ(gaugeMetrics.skipped_size(), 0);
}
-TEST_P(GaugeMetricE2ePulledTest, TestFirstNSamplesPulledNoTriggerWithActivation) {
+TEST(GaugeMetricE2ePulledTest, TestFirstNSamplesPulledNoTriggerWithActivation) {
StatsdConfig config = CreateStatsdConfig(GaugeMetric::FIRST_N_SAMPLES);
auto gaugeMetric = config.mutable_gauge_metric(0);
gaugeMetric->set_max_num_gauge_atoms_per_bucket(2);
@@ -882,7 +866,7 @@
{(int64_t)(configAddedTimeNs + (3 * bucketSizeNs) + 50)});
}
-TEST_P(GaugeMetricE2ePulledTest, TestRandomSamplePulledEventsNoCondition) {
+TEST(GaugeMetricE2ePulledTest, TestRandomSamplePulledEventsNoCondition) {
auto config = CreateStatsdConfig(GaugeMetric::RANDOM_ONE_SAMPLE, /*useCondition=*/false);
int64_t baseTimeNs = getElapsedRealtimeNs();
diff --git a/statsd/tests/e2e/MetricActivation_e2e_test.cpp b/statsd/tests/e2e/MetricActivation_e2e_test.cpp
index e320419..3c625ac 100644
--- a/statsd/tests/e2e/MetricActivation_e2e_test.cpp
+++ b/statsd/tests/e2e/MetricActivation_e2e_test.cpp
@@ -252,6 +252,9 @@
long timeBase1 = 1;
int broadcastCount = 0;
+ std::shared_ptr<MockLogEventFilter> mockLogEventFilter = std::make_shared<MockLogEventFilter>();
+ EXPECT_CALL(*mockLogEventFilter, setAtomIds(StatsLogProcessor::getDefaultAtomIdSet(), _))
+ .Times(1);
StatsLogProcessor processor(
m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, bucketStartTimeNs,
[](const ConfigKey& key) { return true; },
@@ -263,7 +266,11 @@
activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), activeConfigs.begin(),
activeConfigs.end());
return true;
- });
+ },
+ [](const ConfigKey&, const string&, const vector<int64_t>&) {}, mockLogEventFilter);
+
+ const LogEventFilter::AtomIdSet atomIdsList = CreateAtomIdSetFromConfig(config);
+ EXPECT_CALL(*mockLogEventFilter, setAtomIds(atomIdsList, &processor)).Times(1);
processor.OnConfigUpdated(bucketStartTimeNs, cfgKey, config);
@@ -463,6 +470,9 @@
long timeBase1 = 1;
int broadcastCount = 0;
+ std::shared_ptr<MockLogEventFilter> mockLogEventFilter = std::make_shared<MockLogEventFilter>();
+ EXPECT_CALL(*mockLogEventFilter, setAtomIds(StatsLogProcessor::getDefaultAtomIdSet(), _))
+ .Times(1);
StatsLogProcessor processor(
m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, bucketStartTimeNs,
[](const ConfigKey& key) { return true; },
@@ -474,7 +484,11 @@
activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), activeConfigs.begin(),
activeConfigs.end());
return true;
- });
+ },
+ [](const ConfigKey&, const string&, const vector<int64_t>&) {}, mockLogEventFilter);
+
+ const LogEventFilter::AtomIdSet atomIdsList = CreateAtomIdSetFromConfig(config);
+ EXPECT_CALL(*mockLogEventFilter, setAtomIds(atomIdsList, &processor)).Times(1);
processor.OnConfigUpdated(bucketStartTimeNs, cfgKey, config);
@@ -784,6 +798,9 @@
long timeBase1 = 1;
int broadcastCount = 0;
+ std::shared_ptr<MockLogEventFilter> mockLogEventFilter = std::make_shared<MockLogEventFilter>();
+ EXPECT_CALL(*mockLogEventFilter, setAtomIds(StatsLogProcessor::getDefaultAtomIdSet(), _))
+ .Times(1);
StatsLogProcessor processor(
m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, bucketStartTimeNs,
[](const ConfigKey& key) { return true; },
@@ -795,7 +812,11 @@
activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), activeConfigs.begin(),
activeConfigs.end());
return true;
- });
+ },
+ [](const ConfigKey&, const string&, const vector<int64_t>&) {}, mockLogEventFilter);
+
+ const LogEventFilter::AtomIdSet atomIdsList = CreateAtomIdSetFromConfig(config);
+ EXPECT_CALL(*mockLogEventFilter, setAtomIds(atomIdsList, &processor)).Times(1);
processor.OnConfigUpdated(bucketStartTimeNs, cfgKey, config);
@@ -1117,6 +1138,9 @@
long timeBase1 = 1;
int broadcastCount = 0;
+ std::shared_ptr<MockLogEventFilter> mockLogEventFilter = std::make_shared<MockLogEventFilter>();
+ EXPECT_CALL(*mockLogEventFilter, setAtomIds(StatsLogProcessor::getDefaultAtomIdSet(), _))
+ .Times(1);
StatsLogProcessor processor(
m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, bucketStartTimeNs,
[](const ConfigKey& key) { return true; },
@@ -1128,7 +1152,11 @@
activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), activeConfigs.begin(),
activeConfigs.end());
return true;
- });
+ },
+ [](const ConfigKey&, const string&, const vector<int64_t>&) {}, mockLogEventFilter);
+
+ const LogEventFilter::AtomIdSet atomIdsList = CreateAtomIdSetFromConfig(config);
+ EXPECT_CALL(*mockLogEventFilter, setAtomIds(atomIdsList, &processor)).Times(1);
processor.OnConfigUpdated(bucketStartTimeNs, cfgKey, config);
@@ -1314,6 +1342,9 @@
long timeBase1 = 1;
int broadcastCount = 0;
+ std::shared_ptr<MockLogEventFilter> mockLogEventFilter = std::make_shared<MockLogEventFilter>();
+ EXPECT_CALL(*mockLogEventFilter, setAtomIds(StatsLogProcessor::getDefaultAtomIdSet(), _))
+ .Times(1);
StatsLogProcessor processor(
m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, bucketStartTimeNs,
[](const ConfigKey& key) { return true; },
@@ -1325,7 +1356,11 @@
activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), activeConfigs.begin(),
activeConfigs.end());
return true;
- });
+ },
+ [](const ConfigKey&, const string&, const vector<int64_t>&) {}, mockLogEventFilter);
+
+ const LogEventFilter::AtomIdSet atomIdsList = CreateAtomIdSetFromConfig(config);
+ EXPECT_CALL(*mockLogEventFilter, setAtomIds(atomIdsList, &processor)).Times(1);
processor.OnConfigUpdated(bucketStartTimeNs, cfgKey, config);
diff --git a/statsd/tests/e2e/RestrictedConfig_e2e_test.cpp b/statsd/tests/e2e/RestrictedConfig_e2e_test.cpp
new file mode 100644
index 0000000..d02a8e5
--- /dev/null
+++ b/statsd/tests/e2e/RestrictedConfig_e2e_test.cpp
@@ -0,0 +1,479 @@
+// Copyright (C) 2023 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.
+
+#include <android-modules-utils/sdk_level.h>
+#include <gtest/gtest.h>
+
+#include "flags/FlagProvider.h"
+#include "storage/StorageManager.h"
+#include "tests/statsd_test_util.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using android::modules::sdklevel::IsAtLeastU;
+
+#ifdef __ANDROID__
+
+namespace {
+const int32_t atomTag = 666;
+const string delegatePackageName = "com.test.restricted.metrics.package";
+const int32_t delegateUid = 10200;
+const string configPackageName = "com.test.config.package";
+int64_t metricId;
+int64_t anotherMetricId;
+
+StatsdConfig CreateConfigWithOneMetric() {
+ StatsdConfig config;
+ config.add_allowed_log_source("AID_ROOT");
+ AtomMatcher atomMatcher = CreateSimpleAtomMatcher("testmatcher", atomTag);
+ *config.add_atom_matcher() = atomMatcher;
+
+ EventMetric eventMetric = createEventMetric("EventMetric", atomMatcher.id(), nullopt);
+ metricId = eventMetric.id();
+ *config.add_event_metric() = eventMetric;
+ return config;
+}
+StatsdConfig CreateConfigWithTwoMetrics() {
+ StatsdConfig config;
+ config.add_allowed_log_source("AID_ROOT");
+ AtomMatcher atomMatcher = CreateSimpleAtomMatcher("testmatcher", atomTag);
+ *config.add_atom_matcher() = atomMatcher;
+
+ EventMetric eventMetric = createEventMetric("EventMetric", atomMatcher.id(), nullopt);
+ metricId = eventMetric.id();
+ *config.add_event_metric() = eventMetric;
+ EventMetric anotherEventMetric =
+ createEventMetric("AnotherEventMetric", atomMatcher.id(), nullopt);
+ anotherMetricId = anotherEventMetric.id();
+ *config.add_event_metric() = anotherEventMetric;
+ return config;
+}
+
+std::vector<std::unique_ptr<LogEvent>> CreateLogEvents(int64_t configAddedTimeNs) {
+ std::vector<std::unique_ptr<LogEvent>> events;
+ events.push_back(CreateNonRestrictedLogEvent(atomTag, configAddedTimeNs + 10 * NS_PER_SEC));
+ events.push_back(CreateNonRestrictedLogEvent(atomTag, configAddedTimeNs + 20 * NS_PER_SEC));
+ events.push_back(CreateNonRestrictedLogEvent(atomTag, configAddedTimeNs + 30 * NS_PER_SEC));
+ return events;
+}
+
+} // Anonymous namespace
+
+class RestrictedConfigE2ETest : public StatsServiceConfigTest {
+protected:
+ shared_ptr<MockStatsQueryCallback> mockStatsQueryCallback;
+ const ConfigKey configKey = ConfigKey(kCallingUid, kConfigKey);
+ vector<string> queryDataResult;
+ vector<string> columnNamesResult;
+ vector<int32_t> columnTypesResult;
+ int32_t rowCountResult = 0;
+ string error;
+
+ void SetUp() override {
+ if (!IsAtLeastU()) {
+ GTEST_SKIP();
+ }
+ StatsServiceConfigTest::SetUp();
+
+ mockStatsQueryCallback = SharedRefBase::make<StrictMock<MockStatsQueryCallback>>();
+ EXPECT_CALL(*mockStatsQueryCallback, sendResults(_, _, _, _))
+ .Times(AnyNumber())
+ .WillRepeatedly(Invoke(
+ [this](const vector<string>& queryData, const vector<string>& columnNames,
+ const vector<int32_t>& columnTypes, int32_t rowCount) {
+ queryDataResult = queryData;
+ columnNamesResult = columnNames;
+ columnTypesResult = columnTypes;
+ rowCountResult = rowCount;
+ error = "";
+ return Status::ok();
+ }));
+ EXPECT_CALL(*mockStatsQueryCallback, sendFailure(_))
+ .Times(AnyNumber())
+ .WillRepeatedly(Invoke([this](const string& err) {
+ error = err;
+ queryDataResult.clear();
+ columnNamesResult.clear();
+ columnTypesResult.clear();
+ rowCountResult = 0;
+ return Status::ok();
+ }));
+
+ int64_t startTimeNs = getElapsedRealtimeNs();
+ service->mUidMap->updateMap(
+ startTimeNs, {delegateUid, kCallingUid},
+ /*versionCode=*/{1, 1}, /*versionString=*/{String16("v2"), String16("v2")},
+ {String16(delegatePackageName.c_str()), String16(configPackageName.c_str())},
+ /*installer=*/{String16(), String16()}, /*certificateHash=*/{{}, {}});
+ }
+ void TearDown() override {
+ if (!IsAtLeastU()) {
+ GTEST_SKIP();
+ }
+ Mock::VerifyAndClear(mockStatsQueryCallback.get());
+ queryDataResult.clear();
+ columnNamesResult.clear();
+ columnTypesResult.clear();
+ rowCountResult = 0;
+ error = "";
+ StatsServiceConfigTest::TearDown();
+ FlagProvider::getInstance().resetOverrides();
+ dbutils::deleteDb(configKey);
+ }
+
+ void verifyRestrictedData(int32_t expectedNumOfMetrics, int64_t metricIdToVerify = metricId,
+ bool shouldExist = true) {
+ std::stringstream query;
+ query << "SELECT * FROM metric_" << dbutils::reformatMetricId(metricIdToVerify);
+ string err;
+ std::vector<int32_t> columnTypes;
+ std::vector<string> columnNames;
+ std::vector<std::vector<std::string>> rows;
+ if (shouldExist) {
+ EXPECT_TRUE(
+ dbutils::query(configKey, query.str(), rows, columnTypes, columnNames, err));
+ EXPECT_EQ(rows.size(), expectedNumOfMetrics);
+ } else {
+ // Expect that table is deleted.
+ EXPECT_FALSE(
+ dbutils::query(configKey, query.str(), rows, columnTypes, columnNames, err));
+ }
+ }
+};
+
+TEST_F(RestrictedConfigE2ETest, RestrictedConfigNoReport) {
+ StatsdConfig config = CreateConfigWithOneMetric();
+ config.set_restricted_metrics_delegate_package_name("delegate");
+ sendConfig(config);
+ int64_t configAddedTimeNs = getElapsedRealtimeNs();
+
+ for (auto& event : CreateLogEvents(configAddedTimeNs)) {
+ service->OnLogEvent(event.get());
+ }
+
+ vector<uint8_t> output;
+ ConfigKey configKey(kCallingUid, kConfigKey);
+ service->getData(kConfigKey, kCallingUid, &output);
+
+ EXPECT_TRUE(output.empty());
+}
+
+TEST_F(RestrictedConfigE2ETest, NonRestrictedConfigGetReport) {
+ StatsdConfig config = CreateConfigWithOneMetric();
+ sendConfig(config);
+ int64_t configAddedTimeNs = getElapsedRealtimeNs();
+
+ for (auto& event : CreateLogEvents(configAddedTimeNs)) {
+ service->OnLogEvent(event.get());
+ }
+
+ ConfigMetricsReport report = getReports(service->mProcessor, /*timestamp=*/10);
+ EXPECT_EQ(report.metrics_size(), 1);
+}
+
+TEST_F(RestrictedConfigE2ETest, RestrictedShutdownFlushToRestrictedDB) {
+ StatsdConfig config = CreateConfigWithOneMetric();
+ config.set_restricted_metrics_delegate_package_name("delegate");
+ sendConfig(config);
+ int64_t configAddedTimeNs = getElapsedRealtimeNs();
+ std::vector<std::unique_ptr<LogEvent>> logEvents = CreateLogEvents(configAddedTimeNs);
+ for (const auto& e : logEvents) {
+ service->OnLogEvent(e.get());
+ }
+
+ service->informDeviceShutdown();
+
+ // Should not be written to non-restricted storage.
+ EXPECT_FALSE(StorageManager::hasConfigMetricsReport(ConfigKey(kCallingUid, kConfigKey)));
+ verifyRestrictedData(logEvents.size());
+}
+
+TEST_F(RestrictedConfigE2ETest, NonRestrictedOnShutdownWriteDataToDisk) {
+ StatsdConfig config = CreateConfigWithOneMetric();
+ sendConfig(config);
+ int64_t configAddedTimeNs = getElapsedRealtimeNs();
+ for (auto& event : CreateLogEvents(configAddedTimeNs)) {
+ service->OnLogEvent(event.get());
+ }
+
+ service->informDeviceShutdown();
+
+ EXPECT_TRUE(StorageManager::hasConfigMetricsReport(ConfigKey(kCallingUid, kConfigKey)));
+}
+
+TEST_F(RestrictedConfigE2ETest, RestrictedConfigOnTerminateFlushToRestrictedDB) {
+ StatsdConfig config = CreateConfigWithOneMetric();
+ config.set_restricted_metrics_delegate_package_name("delegate");
+ sendConfig(config);
+ int64_t configAddedTimeNs = getElapsedRealtimeNs();
+ std::vector<std::unique_ptr<LogEvent>> logEvents = CreateLogEvents(configAddedTimeNs);
+ for (auto& event : logEvents) {
+ service->OnLogEvent(event.get());
+ }
+
+ service->Terminate();
+
+ EXPECT_FALSE(StorageManager::hasConfigMetricsReport(ConfigKey(kCallingUid, kConfigKey)));
+ verifyRestrictedData(logEvents.size());
+}
+
+TEST_F(RestrictedConfigE2ETest, NonRestrictedConfigOnTerminateWriteDataToDisk) {
+ StatsdConfig config = CreateConfigWithOneMetric();
+ sendConfig(config);
+ int64_t configAddedTimeNs = getElapsedRealtimeNs();
+ for (auto& event : CreateLogEvents(configAddedTimeNs)) {
+ service->OnLogEvent(event.get());
+ }
+
+ service->Terminate();
+
+ EXPECT_TRUE(StorageManager::hasConfigMetricsReport(ConfigKey(kCallingUid, kConfigKey)));
+}
+
+TEST_F(RestrictedConfigE2ETest, RestrictedConfigOnUpdateWithMetricRemoval) {
+ StatsdConfig complexConfig = CreateConfigWithTwoMetrics();
+ complexConfig.set_restricted_metrics_delegate_package_name(delegatePackageName);
+ sendConfig(complexConfig);
+ int64_t configAddedTimeNs = getElapsedRealtimeNs();
+ std::vector<std::unique_ptr<LogEvent>> logEvents = CreateLogEvents(configAddedTimeNs);
+ for (auto& event : logEvents) {
+ service->OnLogEvent(event.get());
+ }
+
+ // Use query API to make sure data is flushed.
+ std::stringstream query;
+ query << "SELECT * FROM metric_" << dbutils::reformatMetricId(metricId);
+ service->querySql(query.str(), /*minSqlClientVersion=*/0,
+ /*policyConfig=*/{}, mockStatsQueryCallback,
+ /*configKey=*/kConfigKey, /*configPackage=*/configPackageName,
+ /*callingUid=*/delegateUid);
+ EXPECT_EQ(error, "");
+ EXPECT_EQ(rowCountResult, logEvents.size());
+ verifyRestrictedData(logEvents.size(), anotherMetricId, true);
+
+ // Update config to have only one metric
+ StatsdConfig config = CreateConfigWithOneMetric();
+ config.set_restricted_metrics_delegate_package_name(delegatePackageName);
+ sendConfig(config);
+
+ // Make sure metric data is deleted.
+ verifyRestrictedData(logEvents.size(), metricId, true);
+ verifyRestrictedData(logEvents.size(), anotherMetricId, false);
+}
+
+TEST_F(RestrictedConfigE2ETest, TestSendRestrictedMetricsChangedBroadcast) {
+ vector<int64_t> receivedMetricIds;
+ int receiveCount = 0;
+ shared_ptr<MockPendingIntentRef> pir = SharedRefBase::make<StrictMock<MockPendingIntentRef>>();
+ EXPECT_CALL(*pir, sendRestrictedMetricsChangedBroadcast(_))
+ .Times(7)
+ .WillRepeatedly(Invoke([&receivedMetricIds, &receiveCount](const vector<int64_t>& ids) {
+ receiveCount++;
+ receivedMetricIds = ids;
+ return Status::ok();
+ }));
+
+ // Set the operation. No configs present so empty list is returned.
+ vector<int64_t> returnedMetricIds;
+ service->setRestrictedMetricsChangedOperation(kConfigKey, configPackageName, pir, delegateUid,
+ &returnedMetricIds);
+ EXPECT_EQ(receiveCount, 0);
+ EXPECT_THAT(returnedMetricIds, IsEmpty());
+
+ // Add restricted config. Should receive one metric
+ StatsdConfig config = CreateConfigWithOneMetric();
+ config.set_restricted_metrics_delegate_package_name(delegatePackageName);
+ sendConfig(config);
+ EXPECT_EQ(receiveCount, 1);
+ EXPECT_THAT(receivedMetricIds, UnorderedElementsAre(metricId));
+
+ // Config update, should receive two metrics.
+ config = CreateConfigWithTwoMetrics();
+ config.set_restricted_metrics_delegate_package_name(delegatePackageName);
+ sendConfig(config);
+ EXPECT_EQ(receiveCount, 2);
+ EXPECT_THAT(receivedMetricIds, UnorderedElementsAre(metricId, anotherMetricId));
+
+ // Make config unrestricted. Should receive empty list.
+ config.clear_restricted_metrics_delegate_package_name();
+ sendConfig(config);
+ EXPECT_EQ(receiveCount, 3);
+ EXPECT_THAT(receivedMetricIds, IsEmpty());
+
+ // Update the unrestricted config. Nothing should be sent.
+ config = CreateConfigWithOneMetric();
+ sendConfig(config);
+
+ // Update config and make it restricted. Should receive one metric.
+ config.set_restricted_metrics_delegate_package_name(delegatePackageName);
+ sendConfig(config);
+ EXPECT_EQ(receiveCount, 4);
+ EXPECT_THAT(receivedMetricIds, UnorderedElementsAre(metricId));
+
+ // Send an invalid config. Should receive empty list.
+ config.clear_allowed_log_source();
+ sendConfig(config);
+ EXPECT_EQ(receiveCount, 5);
+ EXPECT_THAT(receivedMetricIds, IsEmpty());
+
+ service->removeRestrictedMetricsChangedOperation(kConfigKey, configPackageName, delegateUid);
+
+ // Nothing should be sent since the operation is removed.
+ config = CreateConfigWithTwoMetrics();
+ config.set_restricted_metrics_delegate_package_name(delegatePackageName);
+ sendConfig(config);
+
+ // Set the operation. Two metrics should be returned.
+ returnedMetricIds.clear();
+ service->setRestrictedMetricsChangedOperation(kConfigKey, configPackageName, pir, delegateUid,
+ &returnedMetricIds);
+ EXPECT_THAT(returnedMetricIds, UnorderedElementsAre(metricId, anotherMetricId));
+ EXPECT_EQ(receiveCount, 5);
+
+ // Config update, should receive two metrics.
+ config = CreateConfigWithOneMetric();
+ config.set_restricted_metrics_delegate_package_name(delegatePackageName);
+ sendConfig(config);
+ EXPECT_EQ(receiveCount, 6);
+ EXPECT_THAT(receivedMetricIds, UnorderedElementsAre(metricId));
+
+ // Remove the config and verify an empty list is received
+ service->removeConfiguration(kConfigKey, kCallingUid);
+ EXPECT_EQ(receiveCount, 7);
+ EXPECT_THAT(receivedMetricIds, IsEmpty());
+
+ // Cleanup.
+ service->removeRestrictedMetricsChangedOperation(kConfigKey, configPackageName, delegateUid);
+}
+
+TEST_F(RestrictedConfigE2ETest, TestSendRestrictedMetricsChangedBroadcastMultipleListeners) {
+ const string configPackageName2 = "com.test.config.package2";
+ const int32_t delegateUid2 = delegateUid + 1, delegateUid3 = delegateUid + 2;
+ service->informOnePackage(configPackageName2, kCallingUid, 0, "", "", {});
+ service->informOnePackage(delegatePackageName, delegateUid2, 0, "", "", {});
+ service->informOnePackage("not.a.good.package", delegateUid3, 0, "", "", {});
+
+ vector<int64_t> receivedMetricIds;
+ int receiveCount = 0;
+ shared_ptr<MockPendingIntentRef> pir = SharedRefBase::make<StrictMock<MockPendingIntentRef>>();
+ EXPECT_CALL(*pir, sendRestrictedMetricsChangedBroadcast(_))
+ .Times(2)
+ .WillRepeatedly(Invoke([&receivedMetricIds, &receiveCount](const vector<int64_t>& ids) {
+ receiveCount++;
+ receivedMetricIds = ids;
+ return Status::ok();
+ }));
+
+ int receiveCount2 = 0;
+ vector<int64_t> receivedMetricIds2;
+ shared_ptr<MockPendingIntentRef> pir2 = SharedRefBase::make<StrictMock<MockPendingIntentRef>>();
+ EXPECT_CALL(*pir2, sendRestrictedMetricsChangedBroadcast(_))
+ .Times(2)
+ .WillRepeatedly(
+ Invoke([&receivedMetricIds2, &receiveCount2](const vector<int64_t>& ids) {
+ receiveCount2++;
+ receivedMetricIds2 = ids;
+ return Status::ok();
+ }));
+
+ // This one should never be called.
+ shared_ptr<MockPendingIntentRef> pir3 = SharedRefBase::make<StrictMock<MockPendingIntentRef>>();
+ EXPECT_CALL(*pir3, sendRestrictedMetricsChangedBroadcast(_)).Times(0);
+
+ // Set the operations. No configs present so empty list is returned.
+ vector<int64_t> returnedMetricIds;
+ service->setRestrictedMetricsChangedOperation(kConfigKey, configPackageName, pir, delegateUid,
+ &returnedMetricIds);
+ EXPECT_EQ(receiveCount, 0);
+ EXPECT_THAT(returnedMetricIds, IsEmpty());
+
+ vector<int64_t> returnedMetricIds2;
+ service->setRestrictedMetricsChangedOperation(kConfigKey, configPackageName2, pir2,
+ delegateUid2, &returnedMetricIds2);
+ EXPECT_EQ(receiveCount2, 0);
+ EXPECT_THAT(returnedMetricIds2, IsEmpty());
+
+ // Represents a package listening for changes but doesn't match the restricted package in the
+ // config.
+ vector<int64_t> returnedMetricIds3;
+ service->setRestrictedMetricsChangedOperation(kConfigKey, configPackageName, pir3, delegateUid3,
+ &returnedMetricIds3);
+ EXPECT_THAT(returnedMetricIds3, IsEmpty());
+
+ // Add restricted config. Should receive one metric on pir1 and 2.
+ StatsdConfig config = CreateConfigWithOneMetric();
+ config.set_restricted_metrics_delegate_package_name(delegatePackageName);
+ sendConfig(config);
+ EXPECT_EQ(receiveCount, 1);
+ EXPECT_THAT(receivedMetricIds, UnorderedElementsAre(metricId));
+ EXPECT_EQ(receiveCount2, 1);
+ EXPECT_THAT(receivedMetricIds2, UnorderedElementsAre(metricId));
+
+ // Config update, should receive two metrics on pir1 and 2.
+ config = CreateConfigWithTwoMetrics();
+ config.set_restricted_metrics_delegate_package_name(delegatePackageName);
+ sendConfig(config);
+ EXPECT_EQ(receiveCount, 2);
+ EXPECT_THAT(receivedMetricIds, UnorderedElementsAre(metricId, anotherMetricId));
+ EXPECT_EQ(receiveCount2, 2);
+ EXPECT_THAT(receivedMetricIds2, UnorderedElementsAre(metricId, anotherMetricId));
+
+ // Cleanup.
+ service->removeRestrictedMetricsChangedOperation(kConfigKey, configPackageName, delegateUid);
+ service->removeRestrictedMetricsChangedOperation(kConfigKey, configPackageName2, delegateUid2);
+ service->removeRestrictedMetricsChangedOperation(kConfigKey, configPackageName, delegateUid3);
+}
+
+TEST_F(RestrictedConfigE2ETest, TestSendRestrictedMetricsChangedBroadcastMultipleMatchedConfigs) {
+ const int32_t callingUid2 = kCallingUid + 1;
+ service->informOnePackage(configPackageName, callingUid2, 0, "", "", {});
+
+ // Add restricted config.
+ StatsdConfig config = CreateConfigWithOneMetric();
+ config.set_restricted_metrics_delegate_package_name(delegatePackageName);
+ sendConfig(config);
+
+ // Add a second config.
+ const int64_t metricId2 = 42;
+ config.mutable_event_metric(0)->set_id(42);
+ string str;
+ config.SerializeToString(&str);
+ std::vector<uint8_t> configAsVec(str.begin(), str.end());
+ service->addConfiguration(kConfigKey, configAsVec, callingUid2);
+
+ // Set the operation. Matches multiple configs so a union of metrics are returned.
+ shared_ptr<MockPendingIntentRef> pir = SharedRefBase::make<StrictMock<MockPendingIntentRef>>();
+ vector<int64_t> returnedMetricIds;
+ service->setRestrictedMetricsChangedOperation(kConfigKey, configPackageName, pir, delegateUid,
+ &returnedMetricIds);
+ EXPECT_THAT(returnedMetricIds, UnorderedElementsAre(metricId, metricId2));
+
+ // Cleanup.
+ service->removeRestrictedMetricsChangedOperation(kConfigKey, configPackageName, delegateUid);
+
+ ConfigKey cfgKey(callingUid2, kConfigKey);
+ service->removeConfiguration(kConfigKey, callingUid2);
+ service->mProcessor->onDumpReport(cfgKey, getElapsedRealtimeNs(),
+ false /* include_current_bucket*/, true /* erase_data */,
+ ADB_DUMP, NO_TIME_CONSTRAINTS, nullptr);
+}
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+
+} // namespace statsd
+} // namespace os
+} // namespace android
\ No newline at end of file
diff --git a/statsd/tests/e2e/RestrictedEventMetric_e2e_test.cpp b/statsd/tests/e2e/RestrictedEventMetric_e2e_test.cpp
new file mode 100644
index 0000000..a45a88e
--- /dev/null
+++ b/statsd/tests/e2e/RestrictedEventMetric_e2e_test.cpp
@@ -0,0 +1,1193 @@
+// Copyright (C) 2023 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.
+
+#include <android-modules-utils/sdk_level.h>
+#include <gtest/gtest.h>
+
+#include <vector>
+
+#include "android-base/stringprintf.h"
+#include "flags/FlagProvider.h"
+#include "src/StatsLogProcessor.h"
+#include "src/state/StateTracker.h"
+#include "src/stats_log_util.h"
+#include "src/storage/StorageManager.h"
+#include "src/utils/RestrictedPolicyManager.h"
+#include "stats_annotations.h"
+#include "tests/statsd_test_util.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using android::modules::sdklevel::IsAtLeastU;
+using base::StringPrintf;
+
+#ifdef __ANDROID__
+
+namespace {
+const int64_t oneMonthLater = getWallClockNs() + 31 * 24 * 3600 * NS_PER_SEC;
+const int64_t configId = 12345;
+const string delegate_package_name = "com.test.restricted.metrics.package";
+const int32_t delegate_uid = 1005;
+const string config_package_name = "com.test.config.package";
+const int32_t config_app_uid = 123;
+const ConfigKey configKey(config_app_uid, configId);
+const int64_t eightDaysAgo = getWallClockNs() - 8 * 24 * 3600 * NS_PER_SEC;
+const int64_t oneDayAgo = getWallClockNs() - 1 * 24 * 3600 * NS_PER_SEC;
+} // anonymous namespace
+
+// Setup for test fixture.
+class RestrictedEventMetricE2eTest : public ::testing::Test {
+protected:
+ shared_ptr<MockStatsQueryCallback> mockStatsQueryCallback;
+ vector<string> queryDataResult;
+ vector<string> columnNamesResult;
+ vector<int32_t> columnTypesResult;
+ int32_t rowCountResult = 0;
+ string error;
+ sp<UidMap> uidMap;
+ sp<StatsLogProcessor> processor;
+ int32_t atomTag;
+ int64_t restrictedMetricId;
+ int64_t configAddedTimeNs;
+ StatsdConfig config;
+
+private:
+ void SetUp() override {
+ if (!IsAtLeastU()) {
+ GTEST_SKIP();
+ }
+
+ mockStatsQueryCallback = SharedRefBase::make<StrictMock<MockStatsQueryCallback>>();
+ EXPECT_CALL(*mockStatsQueryCallback, sendResults(_, _, _, _))
+ .Times(AnyNumber())
+ .WillRepeatedly(Invoke(
+ [this](const vector<string>& queryData, const vector<string>& columnNames,
+ const vector<int32_t>& columnTypes, int32_t rowCount) {
+ queryDataResult = queryData;
+ columnNamesResult = columnNames;
+ columnTypesResult = columnTypes;
+ rowCountResult = rowCount;
+ error = "";
+ return Status::ok();
+ }));
+ EXPECT_CALL(*mockStatsQueryCallback, sendFailure(_))
+ .Times(AnyNumber())
+ .WillRepeatedly(Invoke([this](const string& err) {
+ error = err;
+ queryDataResult.clear();
+ columnNamesResult.clear();
+ columnTypesResult.clear();
+ rowCountResult = 0;
+ return Status::ok();
+ }));
+
+ config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
+
+ atomTag = 999;
+ AtomMatcher restrictedAtomMatcher = CreateSimpleAtomMatcher("restricted_matcher", atomTag);
+ *config.add_atom_matcher() = restrictedAtomMatcher;
+
+ EventMetric restrictedEventMetric =
+ createEventMetric("RestrictedMetricLogged", restrictedAtomMatcher.id(), nullopt);
+ *config.add_event_metric() = restrictedEventMetric;
+ restrictedMetricId = restrictedEventMetric.id();
+
+ config.set_restricted_metrics_delegate_package_name(delegate_package_name.c_str());
+
+ const int64_t baseTimeNs = 0; // 0:00
+ configAddedTimeNs = baseTimeNs + 1 * NS_PER_SEC; // 0:01
+
+ uidMap = new UidMap();
+ uidMap->updateApp(configAddedTimeNs, String16(delegate_package_name.c_str()),
+ /*uid=*/delegate_uid, /*versionCode=*/1,
+ /*versionString=*/String16("v2"),
+ /*installer=*/String16(""), /*certificateHash=*/{});
+ uidMap->updateApp(configAddedTimeNs + 1, String16(config_package_name.c_str()),
+ /*uid=*/config_app_uid, /*versionCode=*/1,
+ /*versionString=*/String16("v2"),
+ /*installer=*/String16(""), /*certificateHash=*/{});
+
+ processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, configKey,
+ /*puller=*/nullptr, /*atomTag=*/0, uidMap);
+ }
+
+ void TearDown() override {
+ Mock::VerifyAndClear(mockStatsQueryCallback.get());
+ queryDataResult.clear();
+ columnNamesResult.clear();
+ columnTypesResult.clear();
+ rowCountResult = 0;
+ error = "";
+ dbutils::deleteDb(configKey);
+ dbutils::deleteDb(ConfigKey(config_app_uid + 1, configId));
+ FlagProvider::getInstance().resetOverrides();
+ }
+};
+
+TEST_F(RestrictedEventMetricE2eTest, TestQueryThreeEvents) {
+ std::vector<std::unique_ptr<LogEvent>> events;
+
+ events.push_back(CreateRestrictedLogEvent(atomTag, configAddedTimeNs + 100));
+ events.push_back(CreateRestrictedLogEvent(atomTag, configAddedTimeNs + 200));
+ events.push_back(CreateRestrictedLogEvent(atomTag, configAddedTimeNs + 300));
+
+ // Send log events to StatsLogProcessor.
+ for (auto& event : events) {
+ processor->OnLogEvent(event.get());
+ }
+
+ std::stringstream query;
+ query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+ processor->querySql(query.str(), /*minSqlClientVersion=*/0,
+ /*policyConfig=*/{}, mockStatsQueryCallback,
+ /*configKey=*/configId, /*configPackage=*/config_package_name,
+ /*callingUid=*/delegate_uid);
+
+ EXPECT_EQ(rowCountResult, 3);
+ EXPECT_THAT(queryDataResult, ElementsAre(to_string(atomTag), to_string(configAddedTimeNs + 100),
+ _, // wallClockNs
+ _, // field_1
+ to_string(atomTag), to_string(configAddedTimeNs + 200),
+ _, // wallClockNs
+ _, // field_1
+ to_string(atomTag), to_string(configAddedTimeNs + 300),
+ _, // wallClockNs
+ _ // field_1
+ ));
+
+ EXPECT_THAT(columnNamesResult,
+ ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+
+ EXPECT_THAT(columnTypesResult,
+ ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER));
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestInvalidSchemaIncreasingFieldCount) {
+ std::vector<std::unique_ptr<LogEvent>> events;
+
+ AStatsEvent* statsEvent = AStatsEvent_obtain();
+ AStatsEvent_setAtomId(statsEvent, atomTag);
+ AStatsEvent_addInt32Annotation(statsEvent, ASTATSLOG_ANNOTATION_ID_RESTRICTION_CATEGORY,
+ ASTATSLOG_RESTRICTION_CATEGORY_DIAGNOSTIC);
+ AStatsEvent_overwriteTimestamp(statsEvent, configAddedTimeNs + 200);
+ // This event has two extra fields
+ AStatsEvent_writeString(statsEvent, "111");
+ AStatsEvent_writeInt32(statsEvent, 11);
+ AStatsEvent_writeFloat(statsEvent, 11.0);
+ std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
+ parseStatsEventToLogEvent(statsEvent, logEvent.get());
+
+ events.push_back(CreateRestrictedLogEvent(atomTag, configAddedTimeNs + 100));
+ events.push_back(std::move(logEvent));
+
+ // Send log events to StatsLogProcessor.
+ for (auto& event : events) {
+ processor->OnLogEvent(event.get());
+ processor->WriteDataToDisk(DEVICE_SHUTDOWN, FAST,
+ event->GetElapsedTimestampNs() + 20 * NS_PER_SEC,
+ getWallClockNs());
+ }
+
+ std::stringstream query;
+ query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+ processor->querySql(query.str(), /*minSqlClientVersion=*/0,
+ /*policyConfig=*/{}, mockStatsQueryCallback,
+ /*configKey=*/configId, /*configPackage=*/config_package_name,
+ /*callingUid=*/delegate_uid);
+
+ EXPECT_EQ(rowCountResult, 1);
+ // Event 2 rejected.
+ EXPECT_THAT(queryDataResult, ElementsAre(to_string(atomTag), to_string(configAddedTimeNs + 100),
+ _, // wallClockNs
+ _ // field_1
+ ));
+
+ EXPECT_THAT(columnNamesResult,
+ ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+
+ EXPECT_THAT(columnTypesResult,
+ ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER));
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestInvalidSchemaDecreasingFieldCount) {
+ std::vector<std::unique_ptr<LogEvent>> events;
+
+ AStatsEvent* statsEvent = AStatsEvent_obtain();
+ AStatsEvent_setAtomId(statsEvent, atomTag);
+ AStatsEvent_addInt32Annotation(statsEvent, ASTATSLOG_ANNOTATION_ID_RESTRICTION_CATEGORY,
+ ASTATSLOG_RESTRICTION_CATEGORY_DIAGNOSTIC);
+ AStatsEvent_overwriteTimestamp(statsEvent, configAddedTimeNs + 100);
+ // This event has one extra field.
+ AStatsEvent_writeString(statsEvent, "111");
+ AStatsEvent_writeInt32(statsEvent, 11);
+ std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
+ parseStatsEventToLogEvent(statsEvent, logEvent.get());
+
+ events.push_back(std::move(logEvent));
+ events.push_back(CreateRestrictedLogEvent(atomTag, configAddedTimeNs + 200));
+
+ // Send log events to StatsLogProcessor.
+ for (auto& event : events) {
+ processor->OnLogEvent(event.get());
+ processor->WriteDataToDisk(DEVICE_SHUTDOWN, FAST,
+ event->GetElapsedTimestampNs() + 20 * NS_PER_SEC,
+ getWallClockNs());
+ }
+
+ std::stringstream query;
+ query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+ processor->querySql(query.str(), /*minSqlClientVersion=*/0,
+ /*policyConfig=*/{}, mockStatsQueryCallback,
+ /*configKey=*/configId, /*configPackage=*/config_package_name,
+ /*callingUid=*/delegate_uid);
+
+ EXPECT_EQ(rowCountResult, 1);
+ // Event 2 Rejected
+ EXPECT_THAT(queryDataResult, ElementsAre(to_string(atomTag), to_string(configAddedTimeNs + 100),
+ _, // wallClockNs
+ "111", // field_1
+ to_string(11) // field_2
+ ));
+
+ EXPECT_THAT(columnNamesResult, ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs",
+ "field_1", "field_2"));
+
+ EXPECT_THAT(columnTypesResult, ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER,
+ SQLITE_TEXT, SQLITE_INTEGER));
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestInvalidSchemaDifferentFieldType) {
+ std::vector<std::unique_ptr<LogEvent>> events;
+
+ AStatsEvent* statsEvent = AStatsEvent_obtain();
+ AStatsEvent_setAtomId(statsEvent, atomTag);
+ AStatsEvent_addInt32Annotation(statsEvent, ASTATSLOG_ANNOTATION_ID_RESTRICTION_CATEGORY,
+ ASTATSLOG_RESTRICTION_CATEGORY_DIAGNOSTIC);
+ AStatsEvent_overwriteTimestamp(statsEvent, configAddedTimeNs + 200);
+ // This event has a string instead of an int field
+ AStatsEvent_writeString(statsEvent, "test_string");
+ std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
+ parseStatsEventToLogEvent(statsEvent, logEvent.get());
+
+ events.push_back(CreateRestrictedLogEvent(atomTag, configAddedTimeNs + 100));
+ events.push_back(std::move(logEvent));
+
+ // Send log events to StatsLogProcessor.
+ for (auto& event : events) {
+ processor->OnLogEvent(event.get());
+ processor->WriteDataToDisk(DEVICE_SHUTDOWN, FAST,
+ event->GetElapsedTimestampNs() + 20 * NS_PER_SEC,
+ getWallClockNs());
+ }
+
+ std::stringstream query;
+ query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+ processor->querySql(query.str(), /*minSqlClientVersion=*/0,
+ /*policyConfig=*/{}, mockStatsQueryCallback,
+ /*configKey=*/configId, /*configPackage=*/config_package_name,
+ /*callingUid=*/delegate_uid);
+
+ EXPECT_EQ(rowCountResult, 1);
+ // Event 2 rejected.
+ EXPECT_THAT(queryDataResult, ElementsAre(to_string(atomTag), to_string(configAddedTimeNs + 100),
+ _, // wallClockNs
+ _ // field_1
+ ));
+ EXPECT_THAT(columnNamesResult,
+ ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+ EXPECT_THAT(columnTypesResult,
+ ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER));
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestNewMetricSchemaAcrossReboot) {
+ int64_t currentWallTimeNs = getWallClockNs();
+ int64_t originalEventElapsedTime = configAddedTimeNs + 100;
+ std::unique_ptr<LogEvent> event1 = CreateRestrictedLogEvent(atomTag, originalEventElapsedTime);
+ processor->OnLogEvent(event1.get());
+
+ std::stringstream query;
+ query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+ processor->querySql(query.str(), /*minSqlClientVersion=*/0,
+ /*policyConfig=*/{}, mockStatsQueryCallback,
+ /*configKey=*/configId, /*configPackage=*/config_package_name,
+ /*callingUid=*/delegate_uid);
+ EXPECT_EQ(rowCountResult, 1);
+ EXPECT_THAT(queryDataResult,
+ ElementsAre(to_string(atomTag), to_string(originalEventElapsedTime),
+ _, // wallTimestampNs
+ _ // field_1
+ ));
+ EXPECT_THAT(columnNamesResult,
+ ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+ EXPECT_THAT(columnTypesResult,
+ ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER));
+
+ // Create a new processor to simulate a reboot
+ auto processor2 =
+ CreateStatsLogProcessor(/*baseTimeNs=*/0, configAddedTimeNs, config, configKey,
+ /*puller=*/nullptr, /*atomTag=*/0, uidMap);
+
+ // Create a restricted event with one extra field.
+ AStatsEvent* statsEvent = AStatsEvent_obtain();
+ AStatsEvent_setAtomId(statsEvent, atomTag);
+ AStatsEvent_addInt32Annotation(statsEvent, ASTATSLOG_ANNOTATION_ID_RESTRICTION_CATEGORY,
+ ASTATSLOG_RESTRICTION_CATEGORY_DIAGNOSTIC);
+ AStatsEvent_overwriteTimestamp(statsEvent, originalEventElapsedTime + 100);
+ // This event has one extra field.
+ AStatsEvent_writeString(statsEvent, "111");
+ AStatsEvent_writeInt32(statsEvent, 11);
+ std::unique_ptr<LogEvent> event2 = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
+ parseStatsEventToLogEvent(statsEvent, event2.get());
+ processor2->OnLogEvent(event2.get());
+
+ processor2->querySql(query.str(), /*minSqlClientVersion=*/0,
+ /*policyConfig=*/{}, mockStatsQueryCallback,
+ /*configKey=*/configId, /*configPackage=*/config_package_name,
+ /*callingUid=*/delegate_uid);
+ EXPECT_EQ(rowCountResult, 1);
+ EXPECT_THAT(queryDataResult,
+ ElementsAre(to_string(atomTag), to_string(originalEventElapsedTime + 100),
+ _, // wallTimestampNs
+ to_string(111), // field_1
+ to_string(11) // field_2
+ ));
+ EXPECT_THAT(columnNamesResult, ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs",
+ "field_1", "field_2"));
+ EXPECT_THAT(columnTypesResult, ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER,
+ SQLITE_TEXT, SQLITE_INTEGER));
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestOneEventMultipleUids) {
+ uidMap->updateApp(configAddedTimeNs, String16(delegate_package_name.c_str()),
+ /*uid=*/delegate_uid + 1, /*versionCode=*/1,
+ /*versionString=*/String16("v2"),
+ /*installer=*/String16(""), /*certificateHash=*/{});
+ uidMap->updateApp(configAddedTimeNs + 1, String16(config_package_name.c_str()),
+ /*uid=*/config_app_uid + 1, /*versionCode=*/1,
+ /*versionString=*/String16("v2"),
+ /*installer=*/String16(""), /*certificateHash=*/{});
+
+ std::vector<std::unique_ptr<LogEvent>> events;
+
+ events.push_back(CreateRestrictedLogEvent(atomTag, configAddedTimeNs + 100));
+
+ // Send log events to StatsLogProcessor.
+ for (auto& event : events) {
+ processor->OnLogEvent(event.get());
+ }
+
+ std::stringstream query;
+ query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+ processor->querySql(query.str(), /*minSqlClientVersion=*/0,
+ /*policyConfig=*/{}, mockStatsQueryCallback,
+ /*configKey=*/configId, /*configPackage=*/config_package_name,
+ /*callingUid=*/delegate_uid);
+
+ EXPECT_EQ(rowCountResult, 1);
+ EXPECT_THAT(queryDataResult, ElementsAre(to_string(atomTag), to_string(configAddedTimeNs + 100),
+ _, // wallClockNs
+ _ // field_1
+ ));
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestOneEventStaticUid) {
+ ConfigKey key2(2000, configId); // shell uid
+ processor->OnConfigUpdated(configAddedTimeNs + 1 * NS_PER_SEC, key2, config);
+
+ std::vector<std::unique_ptr<LogEvent>> events;
+
+ events.push_back(CreateRestrictedLogEvent(atomTag, configAddedTimeNs + 100));
+
+ // Send log events to StatsLogProcessor.
+ for (auto& event : events) {
+ processor->OnLogEvent(event.get());
+ }
+
+ std::stringstream query;
+ query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+ processor->querySql(query.str(), /*minSqlClientVersion=*/0,
+ /*policyConfig=*/{}, mockStatsQueryCallback,
+ /*configKey=*/configId, /*configPackage=*/"AID_SHELL",
+ /*callingUid=*/delegate_uid);
+
+ EXPECT_EQ(rowCountResult, 1);
+ EXPECT_THAT(queryDataResult, ElementsAre(to_string(atomTag), to_string(configAddedTimeNs + 100),
+ _, // wallClockNs
+ _ // field_1
+ ));
+ dbutils::deleteDb(key2);
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestTooManyConfigsAmbiguousQuery) {
+ ConfigKey key2(config_app_uid + 1, configId);
+ processor->OnConfigUpdated(configAddedTimeNs + 1 * NS_PER_SEC, key2, config);
+
+ uidMap->updateApp(configAddedTimeNs, String16(delegate_package_name.c_str()),
+ /*uid=*/delegate_uid + 1, /*versionCode=*/1,
+ /*versionString=*/String16("v2"),
+ /*installer=*/String16(""), /*certificateHash=*/{});
+ uidMap->updateApp(configAddedTimeNs + 1, String16(config_package_name.c_str()),
+ /*uid=*/config_app_uid + 1, /*versionCode=*/1,
+ /*versionString=*/String16("v2"),
+ /*installer=*/String16(""), /*certificateHash=*/{});
+
+ std::vector<std::unique_ptr<LogEvent>> events;
+
+ events.push_back(CreateRestrictedLogEvent(atomTag, configAddedTimeNs + 100));
+
+ // Send log events to StatsLogProcessor.
+ for (auto& event : events) {
+ processor->OnLogEvent(event.get());
+ }
+
+ std::stringstream query;
+ query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+ processor->querySql(query.str(), /*minSqlClientVersion=*/0,
+ /*policyConfig=*/{}, mockStatsQueryCallback,
+ /*configKey=*/configId, /*configPackage=*/config_package_name,
+ /*callingUid=*/delegate_uid);
+
+ EXPECT_EQ(error, "Ambiguous ConfigKey");
+ dbutils::deleteDb(key2);
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestUnknownConfigPackage) {
+ std::vector<std::unique_ptr<LogEvent>> events;
+
+ events.push_back(CreateRestrictedLogEvent(atomTag, configAddedTimeNs + 100));
+
+ // Send log events to StatsLogProcessor.
+ for (auto& event : events) {
+ processor->OnLogEvent(event.get());
+ }
+
+ std::stringstream query;
+ query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+ processor->querySql(query.str(), /*minSqlClientVersion=*/0,
+ /*policyConfig=*/{}, mockStatsQueryCallback,
+ /*configKey=*/configId, /*configPackage=*/"unknown.config.package",
+ /*callingUid=*/delegate_uid);
+
+ EXPECT_EQ(error, "No configs found matching the config key");
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestUnknownDelegatePackage) {
+ std::vector<std::unique_ptr<LogEvent>> events;
+
+ events.push_back(CreateRestrictedLogEvent(atomTag, configAddedTimeNs + 100));
+
+ // Send log events to StatsLogProcessor.
+ for (auto& event : events) {
+ processor->OnLogEvent(event.get());
+ }
+
+ std::stringstream query;
+ query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+ processor->querySql(query.str(), /*minSqlClientVersion=*/0,
+ /*policyConfig=*/{}, mockStatsQueryCallback,
+ /*configKey=*/configId, /*configPackage=*/config_package_name,
+ /*callingUid=*/delegate_uid + 1);
+
+ EXPECT_EQ(error, "No matching configs for restricted metrics delegate");
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestUnsupportedDatabaseVersion) {
+ std::stringstream query;
+ query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+ processor->querySql(query.str(), /*minSqlClientVersion=*/INT_MAX,
+ /*policyConfig=*/{}, mockStatsQueryCallback,
+ /*configKey=*/configId, /*configPackage=*/config_package_name,
+ /*callingUid=*/delegate_uid);
+
+ EXPECT_THAT(error, StartsWith("Unsupported sqlite version"));
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestInvalidQuery) {
+ std::vector<std::unique_ptr<LogEvent>> events;
+
+ events.push_back(CreateRestrictedLogEvent(atomTag, configAddedTimeNs + 100));
+
+ // Send log events to StatsLogProcessor.
+ for (auto& event : events) {
+ processor->OnLogEvent(event.get());
+ }
+
+ std::stringstream query;
+ query << "SELECT * FROM invalid_metric_" << dbutils::reformatMetricId(restrictedMetricId);
+ processor->querySql(query.str(), /*minSqlClientVersion=*/0,
+ /*policyConfig=*/{}, mockStatsQueryCallback,
+ /*configKey=*/configId, /*configPackage=*/config_package_name,
+ /*callingUid=*/delegate_uid);
+
+ EXPECT_THAT(error, StartsWith("failed to query db"));
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestEnforceTtlRemovesOldEvents) {
+ int64_t currentWallTimeNs = getWallClockNs();
+ // 8 days are used here because the TTL threshold is 7 days.
+ int64_t eightDaysAgo = currentWallTimeNs - 8 * 24 * 3600 * NS_PER_SEC;
+ int64_t originalEventElapsedTime = configAddedTimeNs + 100;
+ std::unique_ptr<LogEvent> event1 = CreateRestrictedLogEvent(atomTag, originalEventElapsedTime);
+ event1->setLogdWallClockTimestampNs(eightDaysAgo);
+
+ // Send log events to StatsLogProcessor.
+ processor->OnLogEvent(event1.get(), originalEventElapsedTime);
+ processor->WriteDataToDisk(DEVICE_SHUTDOWN, FAST, originalEventElapsedTime + 20 * NS_PER_SEC,
+ getWallClockNs());
+ processor->EnforceDataTtls(currentWallTimeNs, originalEventElapsedTime + 100);
+
+ std::stringstream query;
+ query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+ string err;
+ std::vector<int32_t> columnTypes;
+ std::vector<string> columnNames;
+ std::vector<std::vector<std::string>> rows;
+ EXPECT_TRUE(dbutils::query(configKey, query.str(), rows, columnTypes, columnNames, err));
+ ASSERT_EQ(rows.size(), 0);
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestConfigRemovalDeletesData) {
+ std::vector<std::unique_ptr<LogEvent>> events;
+
+ events.push_back(CreateRestrictedLogEvent(atomTag, configAddedTimeNs + 100));
+
+ // Send log events to StatsLogProcessor.
+ for (auto& event : events) {
+ processor->OnLogEvent(event.get());
+ }
+ // Query to make sure data is flushed
+ std::stringstream query;
+ query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+ processor->querySql(query.str(), /*minSqlClientVersion=*/0,
+ /*policyConfig=*/{}, mockStatsQueryCallback,
+ /*configKey=*/configId, /*configPackage=*/config_package_name,
+ /*callingUid=*/delegate_uid);
+
+ processor->OnConfigRemoved(configKey);
+
+ string err;
+ std::vector<int32_t> columnTypes;
+ std::vector<string> columnNames;
+ std::vector<std::vector<std::string>> rows;
+ EXPECT_FALSE(dbutils::query(configKey, query.str(), rows, columnTypes, columnNames, err));
+
+ EXPECT_THAT(err, StartsWith("unable to open database file"));
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestConfigRemovalDeletesDataWithoutFlush) {
+ std::vector<std::unique_ptr<LogEvent>> events;
+
+ events.push_back(CreateRestrictedLogEvent(atomTag, configAddedTimeNs + 100));
+
+ // Send log events to StatsLogProcessor.
+ for (auto& event : events) {
+ processor->OnLogEvent(event.get());
+ }
+ processor->OnConfigRemoved(configKey);
+
+ std::stringstream query;
+ query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+ string err;
+ std::vector<int32_t> columnTypes;
+ std::vector<string> columnNames;
+ std::vector<std::vector<std::string>> rows;
+ EXPECT_FALSE(dbutils::query(configKey, query.str(), rows, columnTypes, columnNames, err));
+
+ EXPECT_THAT(err, StartsWith("unable to open database file"));
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestConfigUpdateRestrictedDelegateCleared) {
+ std::vector<std::unique_ptr<LogEvent>> events;
+ events.push_back(CreateRestrictedLogEvent(atomTag, configAddedTimeNs + 100));
+
+ // Send log events to StatsLogProcessor.
+ for (auto& event : events) {
+ processor->OnLogEvent(event.get());
+ }
+
+ // Update the existing config with no delegate
+ config.clear_restricted_metrics_delegate_package_name();
+ processor->OnConfigUpdated(configAddedTimeNs + 1 * NS_PER_SEC, configKey, config);
+
+ std::stringstream query;
+ query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+ string err;
+ std::vector<int32_t> columnTypes;
+ std::vector<string> columnNames;
+ std::vector<std::vector<std::string>> rows;
+ EXPECT_FALSE(dbutils::query(configKey, query.str(), rows, columnTypes, columnNames, err));
+ EXPECT_EQ(rows.size(), 0);
+ EXPECT_THAT(err, StartsWith("unable to open database file"));
+ dbutils::deleteDb(configKey);
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestNonModularConfigUpdateRestrictedDelegate) {
+ std::vector<std::unique_ptr<LogEvent>> events;
+ events.push_back(CreateRestrictedLogEvent(atomTag, configAddedTimeNs + 100));
+
+ // Send log events to StatsLogProcessor.
+ for (auto& event : events) {
+ processor->OnLogEvent(event.get());
+ }
+
+ // Update the existing config without modular update
+ processor->OnConfigUpdated(configAddedTimeNs + 1 * NS_PER_SEC, configKey, config, false);
+
+ std::stringstream query;
+ query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+ string err;
+ std::vector<int32_t> columnTypes;
+ std::vector<string> columnNames;
+ std::vector<std::vector<std::string>> rows;
+ EXPECT_FALSE(dbutils::query(configKey, query.str(), rows, columnTypes, columnNames, err));
+ EXPECT_EQ(rows.size(), 0);
+ EXPECT_THAT(err, StartsWith("no such table"));
+ dbutils::deleteDb(configKey);
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestModularConfigUpdateNewRestrictedDelegate) {
+ config.clear_restricted_metrics_delegate_package_name();
+ // Update the existing config without a restricted delegate
+ processor->OnConfigUpdated(configAddedTimeNs + 10, configKey, config);
+
+ // Update the existing config with a new restricted delegate
+ config.set_restricted_metrics_delegate_package_name("new.delegate.package");
+ processor->OnConfigUpdated(configAddedTimeNs + 1 * NS_PER_SEC, configKey, config);
+
+ std::vector<std::unique_ptr<LogEvent>> events;
+ events.push_back(CreateRestrictedLogEvent(atomTag, configAddedTimeNs + 2 * NS_PER_SEC));
+
+ // Send log events to StatsLogProcessor.
+ for (auto& event : events) {
+ processor->OnLogEvent(event.get());
+ }
+
+ uint64_t dumpTimeNs = configAddedTimeNs + 100 * NS_PER_SEC;
+ ConfigMetricsReportList reports;
+ vector<uint8_t> buffer;
+ processor->onDumpReport(configKey, dumpTimeNs, true, true, ADB_DUMP, FAST, &buffer);
+ EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+ ASSERT_EQ(reports.reports_size(), 0);
+
+ // Assert the config update was not modular and a RestrictedEventMetricProducer was created.
+ std::stringstream query;
+ query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+ string err;
+ std::vector<int32_t> columnTypes;
+ std::vector<string> columnNames;
+ std::vector<std::vector<std::string>> rows;
+ EXPECT_TRUE(dbutils::query(configKey, query.str(), rows, columnTypes, columnNames, err));
+ EXPECT_EQ(rows.size(), 1);
+ EXPECT_THAT(rows[0],
+ ElementsAre(to_string(atomTag), to_string(configAddedTimeNs + 2 * NS_PER_SEC),
+ _, // wallClockNs
+ _ // field_1
+ ));
+ EXPECT_THAT(columnNames,
+ ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+ EXPECT_THAT(columnTypes,
+ ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER));
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestModularConfigUpdateChangeRestrictedDelegate) {
+ std::vector<std::unique_ptr<LogEvent>> events;
+ events.push_back(CreateRestrictedLogEvent(atomTag, configAddedTimeNs + 100));
+
+ // Send log events to StatsLogProcessor.
+ for (auto& event : events) {
+ processor->OnLogEvent(event.get());
+ }
+
+ // Update the existing config with a new restricted delegate
+ int32_t newDelegateUid = delegate_uid + 1;
+ config.set_restricted_metrics_delegate_package_name("new.delegate.package");
+ uidMap->updateApp(configAddedTimeNs, String16("new.delegate.package"),
+ /*uid=*/newDelegateUid, /*versionCode=*/1,
+ /*versionString=*/String16("v2"),
+ /*installer=*/String16(""), /*certificateHash=*/{});
+ processor->OnConfigUpdated(configAddedTimeNs + 1 * NS_PER_SEC, configKey, config);
+
+ std::stringstream query;
+ query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+ processor->querySql(query.str(), /*minSqlClientVersion=*/0,
+ /*policyConfig=*/{}, mockStatsQueryCallback,
+ /*configKey=*/configId, /*configPackage=*/config_package_name,
+ /*callingUid=*/newDelegateUid);
+
+ EXPECT_EQ(rowCountResult, 1);
+ EXPECT_THAT(queryDataResult, ElementsAre(to_string(atomTag), to_string(configAddedTimeNs + 100),
+ _, // wallClockNs
+ _ // field_1
+ ));
+ EXPECT_THAT(columnNamesResult,
+ ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+ EXPECT_THAT(columnTypesResult,
+ ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER));
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestInvalidConfigUpdateRestrictedDelegate) {
+ std::vector<std::unique_ptr<LogEvent>> events;
+ events.push_back(CreateRestrictedLogEvent(atomTag, configAddedTimeNs + 100));
+
+ // Send log events to StatsLogProcessor.
+ for (auto& event : events) {
+ processor->OnLogEvent(event.get());
+ }
+
+ EventMetric metricWithoutMatcher = createEventMetric("metricWithoutMatcher", 999999, nullopt);
+ *config.add_event_metric() = metricWithoutMatcher;
+ // Update the existing config with an invalid config update
+ processor->OnConfigUpdated(configAddedTimeNs + 1 * NS_PER_SEC, configKey, config);
+
+ std::stringstream query;
+ query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+ string err;
+ std::vector<int32_t> columnTypes;
+ std::vector<string> columnNames;
+ std::vector<std::vector<std::string>> rows;
+ EXPECT_FALSE(dbutils::query(configKey, query.str(), rows, columnTypes, columnNames, err));
+ EXPECT_EQ(rows.size(), 0);
+ EXPECT_THAT(err, StartsWith("unable to open database file"));
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestRestrictedConfigUpdateDoesNotUpdateUidMap) {
+ auto& configKeyMap = processor->getUidMap()->mLastUpdatePerConfigKey;
+ EXPECT_EQ(configKeyMap.find(configKey), configKeyMap.end());
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestRestrictedConfigUpdateAddsDelegateRemovesUidMapEntry) {
+ auto& configKeyMap = processor->getUidMap()->mLastUpdatePerConfigKey;
+ config.clear_restricted_metrics_delegate_package_name();
+ // Update the existing config without a restricted delegate
+ processor->OnConfigUpdated(configAddedTimeNs + 1 * NS_PER_SEC, configKey, config);
+ EXPECT_NE(configKeyMap.find(configKey), configKeyMap.end());
+ // Update the existing config with a new restricted delegate
+ config.set_restricted_metrics_delegate_package_name(delegate_package_name.c_str());
+ processor->OnConfigUpdated(configAddedTimeNs + 1 * NS_PER_SEC, configKey, config);
+ EXPECT_EQ(configKeyMap.find(configKey), configKeyMap.end());
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestLogEventsEnforceTtls) {
+ int64_t currentWallTimeNs = getWallClockNs();
+ int64_t originalEventElapsedTime = configAddedTimeNs + 100;
+ // 2 hours used here because the TTL check period is 1 hour.
+ int64_t newEventElapsedTime = configAddedTimeNs + 2 * 3600 * NS_PER_SEC + 1; // 2 hrs later
+ std::unique_ptr<LogEvent> event1 = CreateRestrictedLogEvent(atomTag, originalEventElapsedTime);
+ event1->setLogdWallClockTimestampNs(eightDaysAgo);
+ std::unique_ptr<LogEvent> event2 =
+ CreateRestrictedLogEvent(atomTag, originalEventElapsedTime + 100);
+ event2->setLogdWallClockTimestampNs(oneDayAgo);
+ std::unique_ptr<LogEvent> event3 = CreateRestrictedLogEvent(atomTag, newEventElapsedTime);
+ event3->setLogdWallClockTimestampNs(currentWallTimeNs);
+
+ processor->mLastTtlTime = originalEventElapsedTime;
+ // Send log events to StatsLogProcessor.
+ processor->OnLogEvent(event1.get(), originalEventElapsedTime);
+ processor->OnLogEvent(event2.get(), newEventElapsedTime);
+ processor->OnLogEvent(event3.get(), newEventElapsedTime + 100);
+ processor->flushRestrictedDataLocked(newEventElapsedTime);
+
+ std::stringstream query;
+ query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+ string err;
+ std::vector<int32_t> columnTypes;
+ std::vector<string> columnNames;
+ std::vector<std::vector<std::string>> rows;
+ EXPECT_TRUE(dbutils::query(configKey, query.str(), rows, columnTypes, columnNames, err));
+ ASSERT_EQ(rows.size(), 2);
+ EXPECT_THAT(columnNames,
+ ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+ EXPECT_THAT(columnTypes,
+ ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER));
+ EXPECT_THAT(rows[0], ElementsAre(to_string(atomTag), to_string(originalEventElapsedTime + 100),
+ to_string(oneDayAgo), _));
+ EXPECT_THAT(rows[1], ElementsAre(to_string(atomTag), to_string(newEventElapsedTime),
+ to_string(currentWallTimeNs), _));
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestLogEventsDoesNotEnforceTtls) {
+ int64_t currentWallTimeNs = getWallClockNs();
+ int64_t originalEventElapsedTime = configAddedTimeNs + 100;
+ // 30 min used here because the TTL check period is 1 hour.
+ int64_t newEventElapsedTime = configAddedTimeNs + (3600 * NS_PER_SEC) / 2; // 30 min later
+ std::unique_ptr<LogEvent> event1 = CreateRestrictedLogEvent(atomTag, originalEventElapsedTime);
+ event1->setLogdWallClockTimestampNs(eightDaysAgo);
+ std::unique_ptr<LogEvent> event2 = CreateRestrictedLogEvent(atomTag, newEventElapsedTime);
+ event2->setLogdWallClockTimestampNs(currentWallTimeNs);
+
+ processor->mLastTtlTime = originalEventElapsedTime;
+ // Send log events to StatsLogProcessor.
+ processor->OnLogEvent(event1.get(), originalEventElapsedTime);
+ processor->OnLogEvent(event2.get(), newEventElapsedTime);
+ processor->flushRestrictedDataLocked(newEventElapsedTime);
+
+ std::stringstream query;
+ query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+ string err;
+ std::vector<int32_t> columnTypes;
+ std::vector<string> columnNames;
+ std::vector<std::vector<std::string>> rows;
+ EXPECT_TRUE(dbutils::query(configKey, query.str(), rows, columnTypes, columnNames, err));
+ ASSERT_EQ(rows.size(), 2);
+ EXPECT_THAT(columnNames,
+ ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+ EXPECT_THAT(columnTypes,
+ ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER));
+ EXPECT_THAT(rows[0], ElementsAre(to_string(atomTag), to_string(originalEventElapsedTime),
+ to_string(eightDaysAgo), _));
+ EXPECT_THAT(rows[1], ElementsAre(to_string(atomTag), to_string(newEventElapsedTime),
+ to_string(currentWallTimeNs), _));
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestQueryEnforceTtls) {
+ int64_t currentWallTimeNs = getWallClockNs();
+ int64_t originalEventElapsedTime = configAddedTimeNs + 100;
+ // 30 min used here because the TTL check period is 1 hour.
+ int64_t newEventElapsedTime = configAddedTimeNs + (3600 * NS_PER_SEC) / 2; // 30 min later
+ std::unique_ptr<LogEvent> event1 = CreateRestrictedLogEvent(atomTag, originalEventElapsedTime);
+ event1->setLogdWallClockTimestampNs(eightDaysAgo);
+ std::unique_ptr<LogEvent> event2 = CreateRestrictedLogEvent(atomTag, newEventElapsedTime);
+ event2->setLogdWallClockTimestampNs(currentWallTimeNs);
+
+ processor->mLastTtlTime = originalEventElapsedTime;
+ // Send log events to StatsLogProcessor.
+ processor->OnLogEvent(event1.get(), originalEventElapsedTime);
+ processor->OnLogEvent(event2.get(), newEventElapsedTime);
+
+ std::stringstream query;
+ query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+ processor->querySql(query.str(), /*minSqlClientVersion=*/0,
+ /*policyConfig=*/{}, mockStatsQueryCallback,
+ /*configKey=*/configId, /*configPackage=*/config_package_name,
+ /*callingUid=*/delegate_uid);
+
+ EXPECT_EQ(rowCountResult, 1);
+ EXPECT_THAT(queryDataResult, ElementsAre(to_string(atomTag), to_string(newEventElapsedTime),
+ to_string(currentWallTimeNs),
+ _ // field_1
+ ));
+ EXPECT_THAT(columnNamesResult,
+ ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+ EXPECT_THAT(columnTypesResult,
+ ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER));
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestNotFlushed) {
+ std::vector<std::unique_ptr<LogEvent>> events;
+ events.push_back(CreateRestrictedLogEvent(atomTag, configAddedTimeNs + 100));
+ // Send log events to StatsLogProcessor.
+ for (auto& event : events) {
+ processor->OnLogEvent(event.get(), event->GetElapsedTimestampNs());
+ }
+ std::stringstream query;
+ query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+ string err;
+ std::vector<int32_t> columnTypes;
+ std::vector<string> columnNames;
+ std::vector<std::vector<std::string>> rows;
+ EXPECT_FALSE(dbutils::query(configKey, query.str(), rows, columnTypes, columnNames, err));
+ EXPECT_EQ(rows.size(), 0);
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestEnforceDbGuardrails) {
+ int64_t currentWallTimeNs = getWallClockNs();
+ int64_t originalEventElapsedTime =
+ configAddedTimeNs + (3600 * NS_PER_SEC) * 2; // 2 hours after boot
+ // 2 hours used here because the TTL check period is 1 hour.
+ int64_t dbEnforcementTimeNs =
+ configAddedTimeNs + (3600 * NS_PER_SEC) * 4; // 4 hours after boot
+ std::unique_ptr<LogEvent> event1 = CreateRestrictedLogEvent(atomTag, originalEventElapsedTime);
+ event1->setLogdWallClockTimestampNs(currentWallTimeNs);
+ // Send log events to StatsLogProcessor.
+ processor->OnLogEvent(event1.get(), originalEventElapsedTime);
+
+ EXPECT_TRUE(StorageManager::hasFile(
+ base::StringPrintf("%s/%s", STATS_RESTRICTED_DATA_DIR, "123_12345.db").c_str()));
+ std::stringstream query;
+ query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+ processor->querySql(query.str(), /*minSqlClientVersion=*/0,
+ /*policyConfig=*/{}, mockStatsQueryCallback,
+ /*configKey=*/configId, /*configPackage=*/config_package_name,
+ /*callingUid=*/delegate_uid);
+ EXPECT_EQ(rowCountResult, 1);
+ EXPECT_THAT(queryDataResult,
+ ElementsAre(to_string(atomTag), to_string(originalEventElapsedTime),
+ to_string(currentWallTimeNs),
+ _ // field_1
+ ));
+ EXPECT_THAT(columnNamesResult,
+ ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+ EXPECT_THAT(columnTypesResult,
+ ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER));
+
+ processor->enforceDbGuardrailsIfNecessaryLocked(oneMonthLater, dbEnforcementTimeNs);
+
+ EXPECT_FALSE(StorageManager::hasFile(
+ base::StringPrintf("%s/%s", STATS_RESTRICTED_DATA_DIR, "123_12345.db").c_str()));
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestEnforceDbGuardrailsDoesNotDeleteBeforeGuardrail) {
+ int64_t currentWallTimeNs = getWallClockNs();
+ int64_t originalEventElapsedTime =
+ configAddedTimeNs + (3600 * NS_PER_SEC) * 2; // 2 hours after boot
+ // 2 hours used here because the TTL check period is 1 hour.
+ std::unique_ptr<LogEvent> event1 = CreateRestrictedLogEvent(atomTag, originalEventElapsedTime);
+ event1->setLogdWallClockTimestampNs(currentWallTimeNs);
+ // Send log events to StatsLogProcessor.
+ processor->OnLogEvent(event1.get(), originalEventElapsedTime);
+
+ EXPECT_TRUE(StorageManager::hasFile(
+ base::StringPrintf("%s/%s", STATS_RESTRICTED_DATA_DIR, "123_12345.db").c_str()));
+ std::stringstream query;
+ query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+ processor->querySql(query.str(), /*minSqlClientVersion=*/0,
+ /*policyConfig=*/{}, mockStatsQueryCallback,
+ /*configKey=*/configId, /*configPackage=*/config_package_name,
+ /*callingUid=*/delegate_uid);
+ EXPECT_EQ(rowCountResult, 1);
+ EXPECT_THAT(queryDataResult,
+ ElementsAre(to_string(atomTag), to_string(originalEventElapsedTime),
+ to_string(currentWallTimeNs),
+ _ // field_1
+ ));
+ EXPECT_THAT(columnNamesResult,
+ ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+ EXPECT_THAT(columnTypesResult,
+ ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER));
+
+ processor->enforceDbGuardrailsIfNecessaryLocked(oneMonthLater, originalEventElapsedTime);
+
+ EXPECT_TRUE(StorageManager::hasFile(
+ base::StringPrintf("%s/%s", STATS_RESTRICTED_DATA_DIR, "123_12345.db").c_str()));
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestFlushInWriteDataToDisk) {
+ std::vector<std::unique_ptr<LogEvent>> events;
+ events.push_back(CreateRestrictedLogEvent(atomTag, configAddedTimeNs + 100));
+ // Send log events to StatsLogProcessor.
+ for (auto& event : events) {
+ processor->OnLogEvent(event.get(), event->GetElapsedTimestampNs());
+ }
+
+ // Call WriteDataToDisk after 20 second because cooldown period is 15 second.
+ processor->WriteDataToDisk(DEVICE_SHUTDOWN, FAST, 20 * NS_PER_SEC, getWallClockNs());
+
+ std::stringstream query;
+ query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+ string err;
+ std::vector<int32_t> columnTypes;
+ std::vector<string> columnNames;
+ std::vector<std::vector<std::string>> rows;
+ EXPECT_TRUE(dbutils::query(configKey, query.str(), rows, columnTypes, columnNames, err));
+ EXPECT_EQ(rows.size(), 1);
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestFlushPeriodically) {
+ std::vector<std::unique_ptr<LogEvent>> events;
+
+ events.push_back(CreateRestrictedLogEvent(atomTag, configAddedTimeNs + 100));
+ events.push_back(CreateRestrictedLogEvent(
+ atomTag, configAddedTimeNs + StatsdStats::kMinFlushRestrictedPeriodNs + 1));
+
+ // Send log events to StatsLogProcessor.
+ for (auto& event : events) {
+ processor->OnLogEvent(event.get(), event->GetElapsedTimestampNs());
+ }
+
+ std::stringstream query;
+ query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+ string err;
+ std::vector<int32_t> columnTypes;
+ std::vector<string> columnNames;
+ std::vector<std::vector<std::string>> rows;
+ EXPECT_TRUE(dbutils::query(configKey, query.str(), rows, columnTypes, columnNames, err));
+ // Only first event is flushed when second event is logged.
+ EXPECT_EQ(rows.size(), 1);
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestOnLogEventMalformedDbNameDeleted) {
+ vector<string> emptyData;
+ string fileName = StringPrintf("%s/malformedname.db", STATS_RESTRICTED_DATA_DIR);
+ StorageManager::writeFile(fileName.c_str(), emptyData.data(), emptyData.size());
+ EXPECT_TRUE(StorageManager::hasFile(fileName.c_str()));
+ int64_t originalEventElapsedTime = configAddedTimeNs + 100;
+ // 2 hours used here because the TTL check period is 1 hour.
+ int64_t newEventElapsedTime = configAddedTimeNs + 2 * 3600 * NS_PER_SEC + 1; // 2 hrs later
+ std::unique_ptr<LogEvent> event2 = CreateRestrictedLogEvent(atomTag, newEventElapsedTime);
+ event2->setLogdWallClockTimestampNs(getWallClockNs());
+
+ processor->mLastTtlTime = originalEventElapsedTime;
+ // Send log events to StatsLogProcessor.
+ processor->OnLogEvent(event2.get(), newEventElapsedTime);
+
+ EXPECT_FALSE(StorageManager::hasFile(fileName.c_str()));
+ StorageManager::deleteFile(fileName.c_str());
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestRestrictedMetricSavesTtlToDisk) {
+ metadata::StatsMetadataList result;
+ processor->WriteMetadataToProto(getWallClockNs(), configAddedTimeNs, &result);
+
+ ASSERT_EQ(result.stats_metadata_size(), 1);
+ metadata::StatsMetadata statsMetadata = result.stats_metadata(0);
+ EXPECT_EQ(statsMetadata.config_key().config_id(), configId);
+ EXPECT_EQ(statsMetadata.config_key().uid(), config_app_uid);
+
+ ASSERT_EQ(statsMetadata.metric_metadata_size(), 1);
+ metadata::MetricMetadata metricMetadata = statsMetadata.metric_metadata(0);
+ EXPECT_EQ(metricMetadata.metric_id(), restrictedMetricId);
+ EXPECT_EQ(metricMetadata.restricted_category(), CATEGORY_UNKNOWN);
+ result.Clear();
+
+ std::unique_ptr<LogEvent> event = CreateRestrictedLogEvent(atomTag, configAddedTimeNs + 100);
+ processor->OnLogEvent(event.get());
+ processor->WriteMetadataToProto(getWallClockNs(), configAddedTimeNs, &result);
+
+ ASSERT_EQ(result.stats_metadata_size(), 1);
+ statsMetadata = result.stats_metadata(0);
+ EXPECT_EQ(statsMetadata.config_key().config_id(), configId);
+ EXPECT_EQ(statsMetadata.config_key().uid(), config_app_uid);
+
+ ASSERT_EQ(statsMetadata.metric_metadata_size(), 1);
+ metricMetadata = statsMetadata.metric_metadata(0);
+ EXPECT_EQ(metricMetadata.metric_id(), restrictedMetricId);
+ EXPECT_EQ(metricMetadata.restricted_category(), CATEGORY_DIAGNOSTIC);
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestRestrictedMetricLoadsTtlFromDisk) {
+ int64_t currentWallTimeNs = getWallClockNs();
+ int64_t originalEventElapsedTime = configAddedTimeNs + 100;
+ std::unique_ptr<LogEvent> event1 = CreateRestrictedLogEvent(atomTag, originalEventElapsedTime);
+ event1->setLogdWallClockTimestampNs(eightDaysAgo);
+ processor->OnLogEvent(event1.get(), originalEventElapsedTime);
+ processor->flushRestrictedDataLocked(originalEventElapsedTime);
+ int64_t wallClockNs = 1584991200 * NS_PER_SEC; // random time
+ int64_t metadataWriteTime = originalEventElapsedTime + 5000 * NS_PER_SEC;
+ processor->SaveMetadataToDisk(wallClockNs, metadataWriteTime);
+
+ std::stringstream query;
+ query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+ string err;
+ std::vector<int32_t> columnTypes;
+ std::vector<string> columnNames;
+ std::vector<std::vector<std::string>> rows;
+ EXPECT_TRUE(dbutils::query(configKey, query.str(), rows, columnTypes, columnNames, err));
+ ASSERT_EQ(rows.size(), 1);
+ EXPECT_THAT(columnNames,
+ ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+ EXPECT_THAT(columnTypes,
+ ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER));
+ EXPECT_THAT(rows[0], ElementsAre(to_string(atomTag), to_string(originalEventElapsedTime),
+ to_string(eightDaysAgo), _));
+
+ auto processor2 =
+ CreateStatsLogProcessor(/*baseTimeNs=*/0, configAddedTimeNs, config, configKey,
+ /*puller=*/nullptr, /*atomTag=*/0, uidMap);
+ // 2 hours used here because the TTL check period is 1 hour.
+ int64_t newEventElapsedTime = configAddedTimeNs + 2 * 3600 * NS_PER_SEC + 1; // 2 hrs later
+ processor2->LoadMetadataFromDisk(wallClockNs, newEventElapsedTime);
+
+ // Log another event and check that the original TTL is maintained across reboot
+ std::unique_ptr<LogEvent> event2 = CreateRestrictedLogEvent(atomTag, newEventElapsedTime);
+ event2->setLogdWallClockTimestampNs(currentWallTimeNs);
+ processor2->OnLogEvent(event2.get(), newEventElapsedTime);
+ processor2->flushRestrictedDataLocked(newEventElapsedTime);
+
+ columnTypes.clear();
+ columnNames.clear();
+ rows.clear();
+ EXPECT_TRUE(dbutils::query(configKey, query.str(), rows, columnTypes, columnNames, err));
+ ASSERT_EQ(rows.size(), 1);
+ EXPECT_THAT(columnNames,
+ ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+ EXPECT_THAT(columnTypes,
+ ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER));
+ EXPECT_THAT(rows[0], ElementsAre(to_string(atomTag), to_string(newEventElapsedTime),
+ to_string(currentWallTimeNs), _));
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestNewRestrictionCategoryEventDeletesTable) {
+ int64_t currentWallTimeNs = getWallClockNs();
+ int64_t originalEventElapsedTime = configAddedTimeNs + 100;
+ std::unique_ptr<LogEvent> event1 =
+ CreateNonRestrictedLogEvent(atomTag, originalEventElapsedTime);
+ processor->OnLogEvent(event1.get());
+
+ std::stringstream query;
+ query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+ processor->querySql(query.str(), /*minSqlClientVersion=*/0,
+ /*policyConfig=*/{}, mockStatsQueryCallback,
+ /*configKey=*/configId, /*configPackage=*/config_package_name,
+ /*callingUid=*/delegate_uid);
+ EXPECT_EQ(rowCountResult, 1);
+ EXPECT_THAT(queryDataResult,
+ ElementsAre(to_string(atomTag), to_string(originalEventElapsedTime),
+ _, // wallTimestampNs
+ _ // field_1
+ ));
+ EXPECT_THAT(columnNamesResult,
+ ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+ EXPECT_THAT(columnTypesResult,
+ ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER));
+
+ // Log a second event that will go into the cache
+ std::unique_ptr<LogEvent> event2 =
+ CreateNonRestrictedLogEvent(atomTag, originalEventElapsedTime + 100);
+ processor->OnLogEvent(event2.get());
+
+ // Log a third event with a different category
+ std::unique_ptr<LogEvent> event3 =
+ CreateRestrictedLogEvent(atomTag, originalEventElapsedTime + 200);
+ processor->OnLogEvent(event3.get());
+
+ processor->querySql(query.str(), /*minSqlClientVersion=*/0,
+ /*policyConfig=*/{}, mockStatsQueryCallback,
+ /*configKey=*/configId, /*configPackage=*/config_package_name,
+ /*callingUid=*/delegate_uid);
+ EXPECT_EQ(rowCountResult, 1);
+ EXPECT_THAT(queryDataResult,
+ ElementsAre(to_string(atomTag), to_string(originalEventElapsedTime + 200),
+ _, // wallTimestampNs
+ _ // field_1
+ ));
+ EXPECT_THAT(columnNamesResult,
+ ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+ EXPECT_THAT(columnTypesResult,
+ ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER));
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestDeviceInfoTableCreated) {
+ std::string query = "SELECT * FROM device_info";
+ processor->querySql(query.c_str(), /*minSqlClientVersion=*/0,
+ /*policyConfig=*/{}, mockStatsQueryCallback,
+ /*configKey=*/configId, /*configPackage=*/config_package_name,
+ /*callingUid=*/delegate_uid);
+ EXPECT_EQ(rowCountResult, 1);
+ EXPECT_THAT(queryDataResult, ElementsAre(_, _, _, _, _, _, _, _, _, _));
+ EXPECT_THAT(columnNamesResult,
+ ElementsAre("sdkVersion", "model", "product", "hardware", "device", "osBuild",
+ "fingerprint", "brand", "manufacturer", "board"));
+ EXPECT_THAT(columnTypesResult,
+ ElementsAre(SQLITE_INTEGER, SQLITE_TEXT, SQLITE_TEXT, SQLITE_TEXT, SQLITE_TEXT,
+ SQLITE_TEXT, SQLITE_TEXT, SQLITE_TEXT, SQLITE_TEXT, SQLITE_TEXT));
+}
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp b/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp
index 810b550..1d8307b 100644
--- a/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp
+++ b/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp
@@ -17,7 +17,6 @@
#include <vector>
-#include "flags/FlagProvider.h"
#include "src/StatsLogProcessor.h"
#include "src/stats_log_util.h"
#include "tests/statsd_test_util.h"
@@ -144,20 +143,6 @@
} // namespace
-// Setup for test fixture.
-class ValueMetricE2eTest : public testing::TestWithParam<string> {
- void SetUp() override {
- FlagProvider::getInstance().overrideFlag(LIMIT_PULL_FLAG, GetParam(),
- /*isBootFlag=*/true);
- }
-
- void TearDown() override {
- FlagProvider::getInstance().resetOverrides();
- }
-};
-
-INSTANTIATE_TEST_SUITE_P(LimitPull, ValueMetricE2eTest, testing::Values(FLAG_FALSE, FLAG_TRUE));
-
/**
* Tests the initial condition and condition after the first log events for
* value metrics with either a combination condition or simple condition.
@@ -217,7 +202,7 @@
EXPECT_EQ(ConditionState::kTrue, metricProducer2->mCondition);
}
-TEST_P(ValueMetricE2eTest, TestPulledEvents) {
+TEST(ValueMetricE2eTest, TestPulledEvents) {
auto config = CreateStatsdConfig();
int64_t baseTimeNs = getElapsedRealtimeNs();
int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs;
@@ -337,7 +322,7 @@
skipped.end_bucket_elapsed_nanos());
}
-TEST_P(ValueMetricE2eTest, TestPulledEvents_LateAlarm) {
+TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm) {
auto config = CreateStatsdConfig();
int64_t baseTimeNs = getElapsedRealtimeNs();
// 10 mins == 2 bucket durations.
@@ -464,7 +449,7 @@
skipped.end_bucket_elapsed_nanos());
}
-TEST_P(ValueMetricE2eTest, TestPulledEvents_WithActivation) {
+TEST(ValueMetricE2eTest, TestPulledEvents_WithActivation) {
auto config = CreateStatsdConfig(false);
int64_t baseTimeNs = getElapsedRealtimeNs();
int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs;
diff --git a/statsd/tests/external/puller_util_test.cpp b/statsd/tests/external/puller_util_test.cpp
index 25c0c7e..71f4de4 100644
--- a/statsd/tests/external/puller_util_test.cpp
+++ b/statsd/tests/external/puller_util_test.cpp
@@ -22,7 +22,6 @@
#include "../metrics/metrics_test_helper.h"
#include "FieldValue.h"
-#include "annotations.h"
#include "stats_event.h"
#include "tests/statsd_test_util.h"
diff --git a/statsd/tests/guardrail/StatsdStats_test.cpp b/statsd/tests/guardrail/StatsdStats_test.cpp
index 22c5440..a2f3280 100644
--- a/statsd/tests/guardrail/StatsdStats_test.cpp
+++ b/statsd/tests/guardrail/StatsdStats_test.cpp
@@ -327,11 +327,11 @@
StatsdStats stats;
time_t now = time(nullptr);
// old event, we get it from the stats buffer. should be ignored.
- stats.noteAtomLogged(util::SENSOR_STATE_CHANGED, 1000);
+ stats.noteAtomLogged(util::SENSOR_STATE_CHANGED, 1000, false);
- stats.noteAtomLogged(util::SENSOR_STATE_CHANGED, now + 1);
- stats.noteAtomLogged(util::SENSOR_STATE_CHANGED, now + 2);
- stats.noteAtomLogged(util::APP_CRASH_OCCURRED, now + 3);
+ stats.noteAtomLogged(util::SENSOR_STATE_CHANGED, now + 1, false);
+ stats.noteAtomLogged(util::SENSOR_STATE_CHANGED, now + 2, false);
+ stats.noteAtomLogged(util::APP_CRASH_OCCURRED, now + 3, false);
vector<uint8_t> output;
stats.dumpStats(&output, false);
@@ -351,6 +351,7 @@
dropboxAtomGood = true;
}
EXPECT_FALSE(atomStats.has_dropped_count());
+ EXPECT_FALSE(atomStats.has_skip_count());
}
EXPECT_TRUE(dropboxAtomGood);
@@ -363,9 +364,9 @@
int newAtom1 = StatsdStats::kMaxPushedAtomId + 1;
int newAtom2 = StatsdStats::kMaxPushedAtomId + 2;
- stats.noteAtomLogged(newAtom1, now + 1);
- stats.noteAtomLogged(newAtom1, now + 2);
- stats.noteAtomLogged(newAtom2, now + 3);
+ stats.noteAtomLogged(newAtom1, now + 1, false);
+ stats.noteAtomLogged(newAtom1, now + 2, false);
+ stats.noteAtomLogged(newAtom2, now + 3, false);
vector<uint8_t> output;
stats.dumpStats(&output, false);
@@ -385,6 +386,7 @@
newAtom2Good = true;
}
EXPECT_FALSE(atomStats.has_dropped_count());
+ EXPECT_FALSE(atomStats.has_skip_count());
}
EXPECT_TRUE(newAtom1Good);
@@ -479,6 +481,96 @@
EXPECT_EQ(1L, atomStats2.max_bucket_boundary_delay_ns());
}
+TEST(StatsdStatsTest, TestRestrictedMetricsStats) {
+ StatsdStats stats;
+ const int64_t metricId = -1234556L;
+ ConfigKey key(0, 12345);
+ stats.noteConfigReceived(key, 2, 3, 4, 5, {}, nullopt);
+ stats.noteRestrictedMetricInsertError(key, metricId);
+ stats.noteRestrictedMetricTableCreationError(key, metricId);
+ stats.noteRestrictedMetricTableDeletionError(key, metricId);
+ stats.noteDeviceInfoTableCreationFailed(key);
+ stats.noteRestrictedMetricFlushLatency(key, metricId, 3000);
+ stats.noteRestrictedMetricFlushLatency(key, metricId, 3001);
+ stats.noteRestrictedMetricCategoryChanged(key, metricId);
+ stats.noteRestrictedConfigFlushLatency(key, 4000);
+ ConfigKey configKeyWithoutError(0, 666);
+ stats.noteConfigReceived(configKeyWithoutError, 2, 3, 4, 5, {}, nullopt);
+ stats.noteDbCorrupted(key);
+ stats.noteDbCorrupted(key);
+ stats.noteRestrictedConfigDbSize(key, 999, 111);
+
+ vector<uint8_t> output;
+ stats.dumpStats(&output, false);
+ StatsdStatsReport report;
+ bool good = report.ParseFromArray(&output[0], output.size());
+ EXPECT_TRUE(good);
+
+ ASSERT_EQ(2, report.config_stats().size());
+ ASSERT_EQ(0, report.config_stats(0).restricted_metric_stats().size());
+ ASSERT_EQ(1, report.config_stats(1).restricted_metric_stats().size());
+ EXPECT_EQ(1, report.config_stats(1).restricted_metric_stats(0).insert_error());
+ EXPECT_EQ(1, report.config_stats(1).restricted_metric_stats(0).table_creation_error());
+ EXPECT_EQ(1, report.config_stats(1).restricted_metric_stats(0).table_deletion_error());
+ EXPECT_EQ(1, report.config_stats(1).restricted_metric_stats(0).category_changed_count());
+ ASSERT_EQ(2, report.config_stats(1).restricted_metric_stats(0).flush_latency_ns().size());
+ EXPECT_EQ(3000, report.config_stats(1).restricted_metric_stats(0).flush_latency_ns(0));
+ EXPECT_EQ(3001, report.config_stats(1).restricted_metric_stats(0).flush_latency_ns(1));
+ ASSERT_EQ(1, report.config_stats(1).restricted_db_size_time_sec().size());
+ EXPECT_EQ(999, report.config_stats(1).restricted_db_size_time_sec(0));
+ ASSERT_EQ(1, report.config_stats(1).restricted_db_size_bytes().size());
+ EXPECT_EQ(111, report.config_stats(1).restricted_db_size_bytes(0));
+ ASSERT_EQ(1, report.config_stats(1).restricted_flush_latency().size());
+ EXPECT_EQ(4000, report.config_stats(1).restricted_flush_latency(0));
+ EXPECT_TRUE(report.config_stats(1).device_info_table_creation_failed());
+ EXPECT_EQ(metricId, report.config_stats(1).restricted_metric_stats(0).restricted_metric_id());
+ EXPECT_EQ(2, report.config_stats(1).restricted_db_corrupted_count());
+}
+
+TEST(StatsdStatsTest, TestRestrictedMetricsQueryStats) {
+ StatsdStats stats;
+ const int32_t callingUid = 100;
+ ConfigKey configKey(0, 12345);
+ const string configPackage = "com.google.android.gm";
+ int64_t beforeNoteMetricSucceed = getWallClockNs();
+ stats.noteQueryRestrictedMetricSucceed(configKey.GetId(), configPackage, configKey.GetUid(),
+ callingUid, /*queryLatencyNs=*/5 * NS_PER_SEC);
+ int64_t afterNoteMetricSucceed = getWallClockNs();
+
+ const int64_t configIdWithError = 111;
+ stats.noteQueryRestrictedMetricFailed(configIdWithError, configPackage, std::nullopt,
+ callingUid, InvalidQueryReason(AMBIGUOUS_CONFIG_KEY));
+ stats.noteQueryRestrictedMetricFailed(configIdWithError, configPackage, std::nullopt,
+ callingUid, InvalidQueryReason(AMBIGUOUS_CONFIG_KEY),
+ "error_message");
+
+ vector<uint8_t> output;
+ stats.dumpStats(&output, false);
+ StatsdStatsReport report;
+ bool good = report.ParseFromArray(&output[0], output.size());
+ EXPECT_TRUE(good);
+
+ ASSERT_EQ(3, report.restricted_metric_query_stats().size());
+ EXPECT_EQ(configKey.GetId(), report.restricted_metric_query_stats(0).config_id());
+ EXPECT_EQ(configKey.GetUid(), report.restricted_metric_query_stats(0).config_uid());
+ EXPECT_EQ(callingUid, report.restricted_metric_query_stats(0).calling_uid());
+ EXPECT_EQ(configPackage, report.restricted_metric_query_stats(0).config_package());
+ EXPECT_FALSE(report.restricted_metric_query_stats(0).has_query_error());
+ EXPECT_LT(beforeNoteMetricSucceed,
+ report.restricted_metric_query_stats(0).query_wall_time_ns());
+ EXPECT_GT(afterNoteMetricSucceed, report.restricted_metric_query_stats(0).query_wall_time_ns());
+ EXPECT_EQ(5 * NS_PER_SEC, report.restricted_metric_query_stats(0).query_latency_ns());
+ EXPECT_EQ(configIdWithError, report.restricted_metric_query_stats(1).config_id());
+ EXPECT_EQ(AMBIGUOUS_CONFIG_KEY, report.restricted_metric_query_stats(1).invalid_query_reason());
+ EXPECT_EQ(false, report.restricted_metric_query_stats(1).has_config_uid());
+ EXPECT_FALSE(report.restricted_metric_query_stats(1).has_query_error());
+ EXPECT_FALSE(report.restricted_metric_query_stats(1).has_query_latency_ns());
+ EXPECT_EQ("error_message", report.restricted_metric_query_stats(2).query_error());
+ EXPECT_FALSE(report.restricted_metric_query_stats(2).has_query_latency_ns());
+ EXPECT_NE(report.restricted_metric_query_stats(1).query_wall_time_ns(),
+ report.restricted_metric_query_stats(0).query_wall_time_ns());
+}
+
TEST(StatsdStatsTest, TestAnomalyMonitor) {
StatsdStats stats;
stats.noteRegisteredAnomalyAlarmChanged();
@@ -619,7 +711,7 @@
// We must call noteAtomLogged as well because only those pushed atoms
// that have been logged will have stats printed about them in the
// proto.
- stats.noteAtomLogged(pushAtomTag, /*timeSec=*/0);
+ stats.noteAtomLogged(pushAtomTag, /*timeSec=*/0, false);
stats.noteAtomError(pushAtomTag, /*pull=*/false);
stats.noteAtomError(pullAtomTag, /*pull=*/true);
@@ -636,6 +728,7 @@
EXPECT_EQ(pushAtomTag, pushedAtomStats.tag());
EXPECT_EQ(numErrors, pushedAtomStats.error_count());
EXPECT_FALSE(pushedAtomStats.has_dropped_count());
+ EXPECT_FALSE(pushedAtomStats.has_skip_count());
// Check error count = numErrors for pull atom
ASSERT_EQ(1, report.pulled_atom_stats_size());
@@ -651,10 +744,9 @@
const int nonPlatformPushAtomTag = StatsdStats::kMaxPushedAtomId + 100;
const int numDropped = 10;
-
for (int i = 0; i < numDropped; i++) {
- stats.noteEventQueueOverflow(/*oldestEventTimestampNs*/ 0, pushAtomTag);
- stats.noteEventQueueOverflow(/*oldestEventTimestampNs*/ 0, nonPlatformPushAtomTag);
+ stats.noteEventQueueOverflow(/*oldestEventTimestampNs*/ 0, pushAtomTag, false);
+ stats.noteEventQueueOverflow(/*oldestEventTimestampNs*/ 0, nonPlatformPushAtomTag, false);
}
vector<uint8_t> output;
@@ -672,15 +764,17 @@
EXPECT_EQ(numDropped, pushedAtomStats.count());
EXPECT_EQ(numDropped, pushedAtomStats.dropped_count());
EXPECT_FALSE(pushedAtomStats.has_error_count());
+ EXPECT_FALSE(pushedAtomStats.has_skip_count());
const auto& nonPlatformPushedAtomStats = report.atom_stats(1);
EXPECT_EQ(nonPlatformPushAtomTag, nonPlatformPushedAtomStats.tag());
EXPECT_EQ(numDropped, nonPlatformPushedAtomStats.count());
EXPECT_EQ(numDropped, nonPlatformPushedAtomStats.dropped_count());
EXPECT_FALSE(nonPlatformPushedAtomStats.has_error_count());
+ EXPECT_FALSE(nonPlatformPushedAtomStats.has_skip_count());
}
-TEST(StatsdStatsTest, TestAtomDroppedAndLoggedStats) {
+TEST(StatsdStatsTest, TestAtomLoggedAndDroppedStats) {
StatsdStats stats;
const int pushAtomTag = 100;
@@ -688,14 +782,14 @@
const int numLogged = 10;
for (int i = 0; i < numLogged; i++) {
- stats.noteAtomLogged(pushAtomTag, /*timeSec*/ 0);
- stats.noteAtomLogged(nonPlatformPushAtomTag, /*timeSec*/ 0);
+ stats.noteAtomLogged(pushAtomTag, /*timeSec*/ 0, false);
+ stats.noteAtomLogged(nonPlatformPushAtomTag, /*timeSec*/ 0, false);
}
const int numDropped = 10;
for (int i = 0; i < numDropped; i++) {
- stats.noteEventQueueOverflow(/*oldestEventTimestampNs*/ 0, pushAtomTag);
- stats.noteEventQueueOverflow(/*oldestEventTimestampNs*/ 0, nonPlatformPushAtomTag);
+ stats.noteEventQueueOverflow(/*oldestEventTimestampNs*/ 0, pushAtomTag, false);
+ stats.noteEventQueueOverflow(/*oldestEventTimestampNs*/ 0, nonPlatformPushAtomTag, false);
}
vector<uint8_t> output;
@@ -711,12 +805,99 @@
EXPECT_EQ(numLogged + numDropped, pushedAtomStats.count());
EXPECT_EQ(numDropped, pushedAtomStats.dropped_count());
EXPECT_FALSE(pushedAtomStats.has_error_count());
+ EXPECT_FALSE(pushedAtomStats.has_skip_count());
const auto& nonPlatformPushedAtomStats = report.atom_stats(1);
EXPECT_EQ(nonPlatformPushAtomTag, nonPlatformPushedAtomStats.tag());
EXPECT_EQ(numLogged + numDropped, nonPlatformPushedAtomStats.count());
EXPECT_EQ(numDropped, nonPlatformPushedAtomStats.dropped_count());
EXPECT_FALSE(nonPlatformPushedAtomStats.has_error_count());
+ EXPECT_FALSE(nonPlatformPushedAtomStats.has_skip_count());
+}
+
+TEST(StatsdStatsTest, TestAtomSkippedStats) {
+ StatsdStats stats;
+
+ const int pushAtomTag = 100;
+ const int nonPlatformPushAtomTag = StatsdStats::kMaxPushedAtomId + 100;
+ const int numSkipped = 10;
+
+ for (int i = 0; i < numSkipped; i++) {
+ stats.noteAtomLogged(pushAtomTag, /*timeSec=*/0, /*isSkipped*/ true);
+ stats.noteAtomLogged(nonPlatformPushAtomTag, /*timeSec=*/0, /*isSkipped*/ true);
+ }
+
+ vector<uint8_t> output;
+ stats.dumpStats(&output, false);
+ StatsdStatsReport report;
+ EXPECT_TRUE(report.ParseFromArray(&output[0], output.size()));
+
+ // Check skip_count = numSkipped for push atoms
+ ASSERT_EQ(2, report.atom_stats_size());
+
+ const auto& pushedAtomStats = report.atom_stats(0);
+ EXPECT_EQ(pushAtomTag, pushedAtomStats.tag());
+ EXPECT_EQ(numSkipped, pushedAtomStats.count());
+ EXPECT_EQ(numSkipped, pushedAtomStats.skip_count());
+ EXPECT_FALSE(pushedAtomStats.has_error_count());
+
+ const auto& nonPlatformPushedAtomStats = report.atom_stats(1);
+ EXPECT_EQ(nonPlatformPushAtomTag, nonPlatformPushedAtomStats.tag());
+ EXPECT_EQ(numSkipped, nonPlatformPushedAtomStats.count());
+ EXPECT_EQ(numSkipped, nonPlatformPushedAtomStats.skip_count());
+ EXPECT_FALSE(nonPlatformPushedAtomStats.has_error_count());
+}
+
+TEST(StatsdStatsTest, TestAtomLoggedAndDroppedAndSkippedStats) {
+ StatsdStats stats;
+
+ const int pushAtomTag = 100;
+ const int nonPlatformPushAtomTag = StatsdStats::kMaxPushedAtomId + 100;
+
+ const int numLogged = 10;
+ for (int i = 0; i < numLogged; i++) {
+ stats.noteAtomLogged(pushAtomTag, /*timeSec*/ 0, false);
+ stats.noteAtomLogged(nonPlatformPushAtomTag, /*timeSec*/ 0, false);
+ }
+
+ const int numDropped = 10;
+ for (int i = 0; i < numDropped; i++) {
+ stats.noteEventQueueOverflow(/*oldestEventTimestampNs*/ 0, pushAtomTag, true);
+ stats.noteEventQueueOverflow(/*oldestEventTimestampNs*/ 0, nonPlatformPushAtomTag, true);
+ }
+
+ vector<uint8_t> output;
+ stats.dumpStats(&output, false);
+ StatsdStatsReport report;
+ EXPECT_TRUE(report.ParseFromArray(&output[0], output.size()));
+
+ // Check dropped_count = numDropped for push atoms
+ ASSERT_EQ(2, report.atom_stats_size());
+
+ const auto& pushedAtomStats = report.atom_stats(0);
+ EXPECT_EQ(pushAtomTag, pushedAtomStats.tag());
+ EXPECT_EQ(numLogged + numDropped, pushedAtomStats.count());
+ EXPECT_EQ(numDropped, pushedAtomStats.dropped_count());
+ EXPECT_EQ(numDropped, pushedAtomStats.skip_count());
+ EXPECT_FALSE(pushedAtomStats.has_error_count());
+
+ const auto& nonPlatformPushedAtomStats = report.atom_stats(1);
+ EXPECT_EQ(nonPlatformPushAtomTag, nonPlatformPushedAtomStats.tag());
+ EXPECT_EQ(numLogged + numDropped, nonPlatformPushedAtomStats.count());
+ EXPECT_EQ(numDropped, nonPlatformPushedAtomStats.dropped_count());
+ EXPECT_EQ(numDropped, nonPlatformPushedAtomStats.skip_count());
+ EXPECT_FALSE(nonPlatformPushedAtomStats.has_error_count());
+}
+
+TEST(StatsdStatsTest, TestShardOffsetProvider) {
+ StatsdStats stats;
+ ShardOffsetProvider::getInstance().setShardOffset(15);
+ vector<uint8_t> output;
+ stats.dumpStats(&output, false);
+
+ StatsdStatsReport report;
+ EXPECT_TRUE(report.ParseFromArray(&output[0], output.size()));
+ EXPECT_EQ(report.shard_offset(), 15);
}
} // namespace statsd
diff --git a/statsd/tests/metrics/RestrictedEventMetricProducer_test.cpp b/statsd/tests/metrics/RestrictedEventMetricProducer_test.cpp
new file mode 100644
index 0000000..b6c4545
--- /dev/null
+++ b/statsd/tests/metrics/RestrictedEventMetricProducer_test.cpp
@@ -0,0 +1,266 @@
+#include "src/metrics/RestrictedEventMetricProducer.h"
+
+#include <android-modules-utils/sdk_level.h>
+#include <gtest/gtest.h>
+
+#include "flags/FlagProvider.h"
+#include "metrics_test_helper.h"
+#include "stats_annotations.h"
+#include "tests/statsd_test_util.h"
+#include "utils/DbUtils.h"
+
+using namespace testing;
+using std::string;
+using std::stringstream;
+using std::vector;
+
+#ifdef __ANDROID__
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using android::modules::sdklevel::IsAtLeastU;
+
+namespace {
+const ConfigKey configKey(/*uid=*/0, /*id=*/12345);
+const int64_t metricId1 = 123;
+const int64_t metricId2 = 456;
+
+bool metricTableExist(int64_t metricId) {
+ stringstream query;
+ query << "SELECT * FROM metric_" << metricId;
+ vector<int32_t> columnTypes;
+ vector<vector<string>> rows;
+ vector<string> columnNames;
+ string err;
+ return dbutils::query(configKey, query.str(), rows, columnTypes, columnNames, err);
+}
+} // anonymous namespace
+
+class RestrictedEventMetricProducerTest : public Test {
+protected:
+ void SetUp() override {
+ if (!IsAtLeastU()) {
+ GTEST_SKIP();
+ }
+ }
+ void TearDown() override {
+ if (!IsAtLeastU()) {
+ GTEST_SKIP();
+ }
+ dbutils::deleteDb(configKey);
+ FlagProvider::getInstance().resetOverrides();
+ }
+};
+
+TEST_F(RestrictedEventMetricProducerTest, TestOnMatchedLogEventMultipleEvents) {
+ EventMetric metric;
+ metric.set_id(metricId1);
+ RestrictedEventMetricProducer producer(configKey, metric,
+ /*conditionIndex=*/-1,
+ /*initialConditionCache=*/{}, new ConditionWizard(),
+ /*protoHash=*/0x1234567890,
+ /*startTimeNs=*/0);
+ std::unique_ptr<LogEvent> event1 = CreateRestrictedLogEvent(/*atomTag=*/123, /*timestampNs=*/1);
+ std::unique_ptr<LogEvent> event2 = CreateRestrictedLogEvent(/*atomTag=*/123, /*timestampNs=*/3);
+
+ producer.onMatchedLogEvent(/*matcherIndex=*/1, *event1);
+ producer.onMatchedLogEvent(/*matcherIndex=*/1, *event2);
+ producer.flushRestrictedData();
+
+ stringstream query;
+ query << "SELECT * FROM metric_" << metricId1;
+ string err;
+ vector<int32_t> columnTypes;
+ std::vector<string> columnNames;
+ vector<vector<string>> rows;
+ dbutils::query(configKey, query.str(), rows, columnTypes, columnNames, err);
+ ASSERT_EQ(rows.size(), 2);
+ EXPECT_EQ(columnTypes.size(),
+ 3 + event1->getValues().size()); // col 0:2 are reserved for metadata.
+ EXPECT_EQ(/*tagId=*/rows[0][0], to_string(event1->GetTagId()));
+ EXPECT_EQ(/*elapsedTimestampNs=*/rows[0][1], to_string(event1->GetElapsedTimestampNs()));
+ EXPECT_EQ(/*elapsedTimestampNs=*/rows[1][1], to_string(event2->GetElapsedTimestampNs()));
+
+ EXPECT_THAT(columnNames,
+ ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+}
+
+TEST_F(RestrictedEventMetricProducerTest, TestOnMatchedLogEventMultipleFields) {
+ EventMetric metric;
+ metric.set_id(metricId2);
+ RestrictedEventMetricProducer producer(configKey, metric,
+ /*conditionIndex=*/-1,
+ /*initialConditionCache=*/{}, new ConditionWizard(),
+ /*protoHash=*/0x1234567890,
+ /*startTimeNs=*/0);
+ AStatsEvent* statsEvent = AStatsEvent_obtain();
+ AStatsEvent_setAtomId(statsEvent, 1);
+ AStatsEvent_addInt32Annotation(statsEvent, ASTATSLOG_ANNOTATION_ID_RESTRICTION_CATEGORY,
+ ASTATSLOG_RESTRICTION_CATEGORY_DIAGNOSTIC);
+ AStatsEvent_overwriteTimestamp(statsEvent, 1);
+
+ AStatsEvent_writeString(statsEvent, "111");
+ AStatsEvent_writeInt32(statsEvent, 11);
+ AStatsEvent_writeFloat(statsEvent, 11.0);
+ LogEvent logEvent(/*uid=*/0, /*pid=*/0);
+ parseStatsEventToLogEvent(statsEvent, &logEvent);
+
+ producer.onMatchedLogEvent(/*matcherIndex=1*/ 1, logEvent);
+ producer.flushRestrictedData();
+
+ stringstream query;
+ query << "SELECT * FROM metric_" << metricId2;
+ string err;
+ vector<int32_t> columnTypes;
+ std::vector<string> columnNames;
+ vector<vector<string>> rows;
+ EXPECT_TRUE(dbutils::query(configKey, query.str(), rows, columnTypes, columnNames, err));
+ ASSERT_EQ(rows.size(), 1);
+ EXPECT_EQ(columnTypes.size(),
+ 3 + logEvent.getValues().size()); // col 0:2 are reserved for metadata.
+ EXPECT_EQ(/*field1=*/rows[0][3], "111");
+ EXPECT_EQ(/*field2=*/rows[0][4], "11");
+ EXPECT_FLOAT_EQ(/*field3=*/std::stof(rows[0][5]), 11.0);
+
+ EXPECT_THAT(columnNames, ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs",
+ "field_1", "field_2", "field_3"));
+}
+
+TEST_F(RestrictedEventMetricProducerTest, TestOnMatchedLogEventWithCondition) {
+ EventMetric metric;
+ metric.set_id(metricId1);
+ metric.set_condition(StringToId("SCREEN_ON"));
+ RestrictedEventMetricProducer producer(configKey, metric,
+ /*conditionIndex=*/0,
+ /*initialConditionCache=*/{ConditionState::kUnknown},
+ new ConditionWizard(),
+ /*protoHash=*/0x1234567890,
+ /*startTimeNs=*/0);
+ std::unique_ptr<LogEvent> event1 = CreateRestrictedLogEvent(/*atomTag=*/123, /*timestampNs=*/1);
+ std::unique_ptr<LogEvent> event2 = CreateRestrictedLogEvent(/*atomTag=*/123, /*timestampNs=*/3);
+
+ producer.onConditionChanged(true, 0);
+ producer.onMatchedLogEvent(/*matcherIndex=*/1, *event1);
+ producer.onConditionChanged(false, 1);
+ producer.onMatchedLogEvent(/*matcherIndex=*/1, *event2);
+ producer.flushRestrictedData();
+
+ std::stringstream query;
+ query << "SELECT * FROM metric_" << metricId1;
+ string err;
+ std::vector<int32_t> columnTypes;
+ std::vector<string> columnNames;
+ std::vector<std::vector<std::string>> rows;
+ dbutils::query(configKey, query.str(), rows, columnTypes, columnNames, err);
+ ASSERT_EQ(rows.size(), 1);
+ EXPECT_EQ(columnTypes.size(), 3 + event1->getValues().size());
+ EXPECT_EQ(/*elapsedTimestampNs=*/rows[0][1], to_string(event1->GetElapsedTimestampNs()));
+
+ EXPECT_THAT(columnNames,
+ ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+}
+
+TEST_F(RestrictedEventMetricProducerTest, TestOnDumpReportNoOp) {
+ EventMetric metric;
+ metric.set_id(metricId1);
+ RestrictedEventMetricProducer producer(configKey, metric,
+ /*conditionIndex=*/-1,
+ /*initialConditionCache=*/{}, new ConditionWizard(),
+ /*protoHash=*/0x1234567890,
+ /*startTimeNs=*/0);
+ std::unique_ptr<LogEvent> event1 = CreateRestrictedLogEvent(/*timestampNs=*/1);
+ producer.onMatchedLogEvent(/*matcherIndex=*/1, *event1);
+ ProtoOutputStream output;
+ std::set<string> strSet;
+ producer.onDumpReport(/*dumpTimeNs=*/10,
+ /*include_current_partial_bucket=*/true,
+ /*erase_data=*/true, FAST, &strSet, &output);
+
+ ASSERT_EQ(output.size(), 0);
+ ASSERT_EQ(strSet.size(), 0);
+}
+
+TEST_F(RestrictedEventMetricProducerTest, TestOnMetricRemove) {
+ EventMetric metric;
+ metric.set_id(metricId1);
+ RestrictedEventMetricProducer producer(configKey, metric,
+ /*conditionIndex=*/-1,
+ /*initialConditionCache=*/{}, new ConditionWizard(),
+ /*protoHash=*/0x1234567890,
+ /*startTimeNs=*/0);
+ EXPECT_FALSE(metricTableExist(metricId1));
+
+ std::unique_ptr<LogEvent> event1 = CreateRestrictedLogEvent(/*timestampNs=*/1);
+ producer.onMatchedLogEvent(/*matcherIndex=*/1, *event1);
+ producer.flushRestrictedData();
+ EXPECT_TRUE(metricTableExist(metricId1));
+
+ producer.onMetricRemove();
+ EXPECT_FALSE(metricTableExist(metricId1));
+}
+
+TEST_F(RestrictedEventMetricProducerTest, TestRestrictedEventMetricTtlDeletesFirstEvent) {
+ EventMetric metric;
+ metric.set_id(metricId1);
+ RestrictedEventMetricProducer producer(configKey, metric,
+ /*conditionIndex=*/-1,
+ /*initialConditionCache=*/{}, new ConditionWizard(),
+ /*protoHash=*/0x1234567890,
+ /*startTimeNs=*/0);
+
+ int64_t currentTimeNs = getWallClockNs();
+ int64_t eightDaysAgo = currentTimeNs - 8 * 24 * 3600 * NS_PER_SEC;
+ std::unique_ptr<LogEvent> event1 = CreateRestrictedLogEvent(/*atomTag=*/123, /*timestampNs=*/1);
+ event1->setLogdWallClockTimestampNs(eightDaysAgo);
+ std::unique_ptr<LogEvent> event2 = CreateRestrictedLogEvent(/*atomTag=*/123, /*timestampNs=*/3);
+ event2->setLogdWallClockTimestampNs(currentTimeNs);
+
+ producer.onMatchedLogEvent(/*matcherIndex=*/1, *event1);
+ producer.onMatchedLogEvent(/*matcherIndex=*/1, *event2);
+ producer.flushRestrictedData();
+ sqlite3* dbHandle = dbutils::getDb(configKey);
+ producer.enforceRestrictedDataTtl(dbHandle, currentTimeNs + 100);
+ dbutils::closeDb(dbHandle);
+
+ std::stringstream query;
+ query << "SELECT * FROM metric_" << metricId1;
+ string err;
+ std::vector<int32_t> columnTypes;
+ std::vector<string> columnNames;
+ std::vector<std::vector<std::string>> rows;
+ dbutils::query(configKey, query.str(), rows, columnTypes, columnNames, err);
+ ASSERT_EQ(rows.size(), 1);
+ EXPECT_EQ(columnTypes.size(), 3 + event1->getValues().size());
+ EXPECT_THAT(columnNames,
+ ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+ EXPECT_THAT(rows[0], ElementsAre(to_string(event2->GetTagId()),
+ to_string(event2->GetElapsedTimestampNs()),
+ to_string(currentTimeNs), _));
+}
+
+TEST_F(RestrictedEventMetricProducerTest, TestLoadMetricMetadataSetsCategory) {
+ metadata::MetricMetadata metricMetadata;
+ metricMetadata.set_metric_id(metricId1);
+ metricMetadata.set_restricted_category(1); // CATEGORY_DIAGNOSTIC
+ EventMetric metric;
+ metric.set_id(metricId1);
+ RestrictedEventMetricProducer producer(configKey, metric,
+ /*conditionIndex=*/-1,
+ /*initialConditionCache=*/{}, new ConditionWizard(),
+ /*protoHash=*/0x1234567890,
+ /*startTimeNs=*/0);
+
+ producer.loadMetricMetadataFromProto(metricMetadata);
+
+ EXPECT_EQ(producer.getRestrictionCategory(), CATEGORY_DIAGNOSTIC);
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
+
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
\ No newline at end of file
diff --git a/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp b/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp
index e083cab..d7374a5 100644
--- a/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp
+++ b/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp
@@ -4104,6 +4104,44 @@
StringToId("ScreenIsOn")));
}
+TEST_F(ConfigUpdateTest, TestUpdateConfigNonEventMetricHasRestrictedDelegate) {
+ StatsdConfig config;
+ CountMetric* metric = config.add_count_metric();
+ config.set_restricted_metrics_delegate_package_name("com.android.app.test");
+
+ unordered_map<int64_t, unordered_map<int, int64_t>> allStateGroupMaps;
+ unordered_map<int64_t, int> newAtomMatchingTrackerMap;
+ unordered_map<int64_t, int> newConditionTrackerMap;
+ unordered_map<int64_t, int> newMetricProducerMap;
+ unordered_map<int64_t, int> stateAtomIdMap;
+ unordered_map<int, vector<int>> conditionToMetricMap;
+ unordered_map<int, vector<int>> trackerToMetricMap;
+ unordered_map<int, vector<int>> activationAtomTrackerToMetricMap;
+ unordered_map<int, vector<int>> deactivationAtomTrackerToMetricMap;
+ set<int64_t> noReportMetricIds;
+ vector<int> metricsWithActivation;
+ vector<sp<AtomMatchingTracker>> newAtomMatchingTrackers;
+ vector<sp<ConditionTracker>> newConditionTrackers;
+ vector<sp<MetricProducer>> newMetricProducers;
+ vector<ConditionState> conditionCache;
+ set<int64_t> replacedMetrics;
+ set<int64_t> replacedMatchers;
+ set<int64_t> replacedConditions;
+ set<int64_t> replacedStates;
+
+ EXPECT_EQ(updateMetrics(key, config, /*timeBaseNs=*/123, /*currentTimeNs=*/12345,
+ new StatsPullerManager(), oldAtomMatchingTrackerMap,
+ newAtomMatchingTrackerMap, replacedMatchers, newAtomMatchingTrackers,
+ newConditionTrackerMap, replacedConditions, newConditionTrackers,
+ conditionCache, stateAtomIdMap, allStateGroupMaps, replacedStates,
+ oldMetricProducerMap, oldMetricProducers, newMetricProducerMap,
+ newMetricProducers, conditionToMetricMap, trackerToMetricMap,
+ noReportMetricIds, activationAtomTrackerToMetricMap,
+ deactivationAtomTrackerToMetricMap, metricsWithActivation,
+ replacedMetrics),
+ InvalidConfigReason(INVALID_CONFIG_REASON_RESTRICTED_METRIC_NOT_SUPPORTED));
+}
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/statsd/tests/metrics/parsing_utils/metrics_manager_util_test.cpp b/statsd/tests/metrics/parsing_utils/metrics_manager_util_test.cpp
index 2d010a3..7dd695c 100644
--- a/statsd/tests/metrics/parsing_utils/metrics_manager_util_test.cpp
+++ b/statsd/tests/metrics/parsing_utils/metrics_manager_util_test.cpp
@@ -1764,6 +1764,51 @@
metric.id()));
}
+TEST_F(MetricsManagerUtilTest, TestCountMetricHasRestrictedDelegate) {
+ StatsdConfig config;
+ CountMetric* metric = config.add_count_metric();
+ config.set_restricted_metrics_delegate_package_name("com.android.app.test");
+
+ EXPECT_EQ(initConfig(config),
+ InvalidConfigReason(INVALID_CONFIG_REASON_RESTRICTED_METRIC_NOT_SUPPORTED));
+}
+
+TEST_F(MetricsManagerUtilTest, TestDurationMetricHasRestrictedDelegate) {
+ StatsdConfig config;
+ DurationMetric* metric = config.add_duration_metric();
+ config.set_restricted_metrics_delegate_package_name("com.android.app.test");
+
+ EXPECT_EQ(initConfig(config),
+ InvalidConfigReason(INVALID_CONFIG_REASON_RESTRICTED_METRIC_NOT_SUPPORTED));
+}
+
+TEST_F(MetricsManagerUtilTest, TestGaugeMetricHasRestrictedDelegate) {
+ StatsdConfig config;
+ GaugeMetric* metric = config.add_gauge_metric();
+ config.set_restricted_metrics_delegate_package_name("com.android.app.test");
+
+ EXPECT_EQ(initConfig(config),
+ InvalidConfigReason(INVALID_CONFIG_REASON_RESTRICTED_METRIC_NOT_SUPPORTED));
+}
+
+TEST_F(MetricsManagerUtilTest, TestNumericValueMetricHasRestrictedDelegate) {
+ StatsdConfig config;
+ ValueMetric* metric = config.add_value_metric();
+ config.set_restricted_metrics_delegate_package_name("com.android.app.test");
+
+ EXPECT_EQ(initConfig(config),
+ InvalidConfigReason(INVALID_CONFIG_REASON_RESTRICTED_METRIC_NOT_SUPPORTED));
+}
+
+TEST_F(MetricsManagerUtilTest, TestKllMetricHasRestrictedDelegate) {
+ StatsdConfig config;
+ KllMetric* metric = config.add_kll_metric();
+ config.set_restricted_metrics_delegate_package_name("com.android.app.test");
+
+ EXPECT_EQ(initConfig(config),
+ InvalidConfigReason(INVALID_CONFIG_REASON_RESTRICTED_METRIC_NOT_SUPPORTED));
+}
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/statsd/tests/shell/ShellSubscriber_test.cpp b/statsd/tests/shell/ShellSubscriber_test.cpp
index 2960cae..a994966 100644
--- a/statsd/tests/shell/ShellSubscriber_test.cpp
+++ b/statsd/tests/shell/ShellSubscriber_test.cpp
@@ -14,24 +14,43 @@
#include "src/shell/ShellSubscriber.h"
+#include <aidl/android/os/StatsSubscriptionCallbackReason.h>
#include <gtest/gtest.h>
#include <stdio.h>
#include <unistd.h>
+#include <optional>
#include <vector>
#include "frameworks/proto_logging/stats/atoms.pb.h"
+#include "gtest_matchers.h"
#include "src/shell/shell_config.pb.h"
#include "src/shell/shell_data.pb.h"
#include "stats_event.h"
+#include "statslog_statsdtest.h"
#include "tests/metrics/metrics_test_helper.h"
#include "tests/statsd_test_util.h"
+using ::aidl::android::os::StatsSubscriptionCallbackReason;
using android::sp;
+using android::os::statsd::TestAtomReported;
+using android::os::statsd::TrainExperimentIds;
+using android::os::statsd::util::BytesField;
+using android::os::statsd::util::CPU_ACTIVE_TIME;
+using android::os::statsd::util::PHONE_SIGNAL_STRENGTH_CHANGED;
+using android::os::statsd::util::PLUGGED_STATE_CHANGED;
+using android::os::statsd::util::SCREEN_STATE_CHANGED;
+using android::os::statsd::util::TEST_ATOM_REPORTED;
using std::vector;
using testing::_;
+using testing::A;
+using testing::ByMove;
+using testing::DoAll;
using testing::Invoke;
using testing::NaggyMock;
+using testing::Return;
+using testing::SaveArg;
+using testing::SetArgPointee;
using testing::StrictMock;
namespace android {
@@ -48,6 +67,8 @@
int kCpuTime1 = 100;
int kCpuTime2 = 200;
+int64_t kCpuActiveTimeEventTimestampNs = 1111L;
+
// Number of clients running simultaneously
// Just a single client
@@ -61,10 +82,12 @@
auto* atom1 = shellData.add_atom()->mutable_cpu_active_time();
atom1->set_uid(kUid1);
atom1->set_time_millis(kCpuTime1);
+ shellData.add_elapsed_timestamp_nanos(kCpuActiveTimeEventTimestampNs);
auto* atom2 = shellData.add_atom()->mutable_cpu_active_time();
atom2->set_uid(kUid2);
atom2->set_time_millis(kCpuTime2);
+ shellData.add_elapsed_timestamp_nanos(kCpuActiveTimeEventTimestampNs);
return shellData;
}
@@ -73,7 +96,7 @@
ShellSubscription getPulledConfig() {
ShellSubscription config;
auto* pull_config = config.add_pulled();
- pull_config->mutable_matcher()->set_atom_id(10016);
+ pull_config->mutable_matcher()->set_atom_id(CPU_ACTIVE_TIME);
pull_config->set_freq_millis(2000);
return config;
}
@@ -81,8 +104,8 @@
// Utility to adjust CPU time for pulled events
shared_ptr<LogEvent> makeCpuActiveTimeAtom(int32_t uid, int64_t timeMillis) {
AStatsEvent* statsEvent = AStatsEvent_obtain();
- AStatsEvent_setAtomId(statsEvent, 10016);
- AStatsEvent_overwriteTimestamp(statsEvent, 1111L);
+ AStatsEvent_setAtomId(statsEvent, CPU_ACTIVE_TIME);
+ AStatsEvent_overwriteTimestamp(statsEvent, kCpuActiveTimeEventTimestampNs);
AStatsEvent_writeInt32(statsEvent, uid);
AStatsEvent_writeInt64(statsEvent, timeMillis);
@@ -110,8 +133,8 @@
return pushedList;
}
-// Utility to read & return a string representing ShellData, skipping heartbeats.
-string readData(int fd) {
+// Utility to read & return ShellData proto, skipping heartbeats.
+static ShellData readData(int fd) {
ssize_t dataSize = 0;
while (dataSize == 0) {
read(fd, &dataSize, sizeof(dataSize));
@@ -123,14 +146,15 @@
// Make sure the received bytes can be parsed to an atom.
ShellData receivedAtom;
EXPECT_TRUE(receivedAtom.ParseFromArray(dataBuffer.data(), dataSize) != 0);
- return string(dataBuffer.begin(), dataBuffer.end());
+ return receivedAtom;
}
void runShellTest(ShellSubscription config, sp<MockUidMap> uidMap,
sp<MockStatsPullerManager> pullerManager,
const vector<std::shared_ptr<LogEvent>>& pushedEvents,
const vector<ShellData>& expectedData, int numClients) {
- sp<ShellSubscriber> shellManager = new ShellSubscriber(uidMap, pullerManager);
+ sp<ShellSubscriber> shellManager =
+ new ShellSubscriber(uidMap, pullerManager, /*LogEventFilter=*/nullptr);
size_t bufferSize = config.ByteSize();
vector<uint8_t> buffer(bufferSize);
@@ -159,32 +183,441 @@
shellManager->onLogEvent(*event);
}
- // Because we might receive heartbeats from statsd, consisting of data sizes
- // of 0, encapsulate reads within a while loop.
- vector<string> expectedDataSerialized;
- for (size_t i = 0; i < expectedData.size(); i++) {
- expectedDataSerialized.push_back(expectedData[i].SerializeAsString());
- }
-
for (int i = 0; i < numClients; i++) {
- vector<string> expectedDataCopy(expectedDataSerialized);
-
- while (expectedDataCopy.size() > 1) {
- // Use readData utility to read data in string format.
- string dataString = readData(fds_datas[i][0]);
-
- // Search for string within the expected data vector
- auto it = find(expectedDataCopy.begin(), expectedDataCopy.end(), dataString);
- EXPECT_NE(it, expectedDataCopy.end());
- expectedDataCopy.erase(it);
+ vector<ShellData> actualData;
+ for (int j = 1; j <= expectedData.size(); j++) {
+ actualData.push_back(readData(fds_datas[i][0]));
}
+
+ EXPECT_THAT(expectedData, UnorderedPointwise(EqShellData(), actualData));
}
// Not closing fds_datas[i][0] because this causes writes within ShellSubscriberClient to hang
}
+unique_ptr<LogEvent> createTestAtomReportedEvent(const uint64_t timestampNs,
+ const int32_t intFieldValue,
+ const vector<int64_t>& expIds) {
+ TrainExperimentIds trainExpIds;
+ *trainExpIds.mutable_experiment_id() = {expIds.begin(), expIds.end()};
+ const vector<uint8_t> trainExpIdsBytes = protoToBytes(trainExpIds);
+ return CreateTestAtomReportedEvent(
+ timestampNs, /* attributionUids */ {1001},
+ /* attributionTags */ {"app1"}, intFieldValue, /*longField */ 0LL,
+ /* floatField */ 0.0f,
+ /* stringField */ "abc", /* boolField */ false, TestAtomReported::OFF, trainExpIdsBytes,
+ /* repeatedIntField */ {}, /* repeatedLongField */ {}, /* repeatedFloatField */ {},
+ /* repeatedStringField */ {}, /* repeatedBoolField */ {},
+ /* repeatedBoolFieldLength */ 0, /* repeatedEnumField */ {});
+}
+
+TestAtomReported createTestAtomReportedProto(const int32_t intFieldValue,
+ const vector<int64_t>& expIds) {
+ TestAtomReported t;
+ auto* attributionNode = t.add_attribution_node();
+ attributionNode->set_uid(1001);
+ attributionNode->set_tag("app1");
+ t.set_int_field(intFieldValue);
+ t.set_long_field(0);
+ t.set_float_field(0.0f);
+ t.set_string_field("abc");
+ t.set_boolean_field(false);
+ t.set_state(TestAtomReported_State_OFF);
+ *t.mutable_bytes_field()->mutable_experiment_id() = {expIds.begin(), expIds.end()};
+ return t;
+}
+
+class ShellSubscriberCallbackTest : public ::testing::Test {
+protected:
+ ShellSubscriberCallbackTest()
+ : uidMap(new NaggyMock<MockUidMap>()),
+ pullerManager(new StrictMock<MockStatsPullerManager>()),
+ mockLogEventFilter(std::make_shared<MockLogEventFilter>()),
+ shellSubscriber(uidMap, pullerManager, mockLogEventFilter),
+ callback(SharedRefBase::make<StrictMock<MockStatsSubscriptionCallback>>()),
+ reason(nullopt) {
+ }
+
+ void SetUp() override {
+ // Save callback arguments when it is invoked.
+ ON_CALL(*callback, onSubscriptionData(_, _))
+ .WillByDefault(DoAll(SaveArg<0>(&reason), SaveArg<1>(&payload),
+ Return(ByMove(Status::ok()))));
+
+ ShellSubscription config;
+ config.add_pushed()->set_atom_id(TEST_ATOM_REPORTED);
+ config.add_pushed()->set_atom_id(SCREEN_STATE_CHANGED);
+ config.add_pushed()->set_atom_id(PHONE_SIGNAL_STRENGTH_CHANGED);
+ configBytes = protoToBytes(config);
+ }
+
+ void TearDown() override {
+ // Expect empty call from the shellSubscriber destructor
+ LogEventFilter::AtomIdSet tagIds;
+ EXPECT_CALL(*mockLogEventFilter, setAtomIds(tagIds, &shellSubscriber)).Times(1);
+ }
+
+ sp<MockUidMap> uidMap;
+ sp<MockStatsPullerManager> pullerManager;
+ std::shared_ptr<MockLogEventFilter> mockLogEventFilter;
+ ShellSubscriber shellSubscriber;
+ std::shared_ptr<MockStatsSubscriptionCallback> callback;
+ vector<uint8_t> configBytes;
+
+ // Capture callback arguments.
+ std::optional<StatsSubscriptionCallbackReason> reason;
+ vector<uint8_t> payload;
+};
+
+class ShellSubscriberCallbackPulledTest : public ShellSubscriberCallbackTest {
+protected:
+ void SetUp() override {
+ ShellSubscriberCallbackTest::SetUp();
+
+ const vector<int32_t> uids{AID_SYSTEM};
+ const vector<std::shared_ptr<LogEvent>> pulledData{
+ makeCpuActiveTimeAtom(/*uid=*/kUid1, /*timeMillis=*/kCpuTime1),
+ makeCpuActiveTimeAtom(/*uid=*/kUid2, /*timeMillis=*/kCpuTime2)};
+ ON_CALL(*pullerManager, Pull(CPU_ACTIVE_TIME, uids, _, _))
+ .WillByDefault(DoAll(SetArgPointee<3>(pulledData), Return(true)));
+
+ configBytes = protoToBytes(getPulledConfig());
+
+ // Used to call pullAndSendHeartbeatsIfNeeded directly without depending on sleep.
+ shellSubscriberClient = std::move(ShellSubscriberClient::create(
+ configBytes, callback, /* startTimeSec= */ 0, uidMap, pullerManager));
+ }
+
+ unique_ptr<ShellSubscriberClient> shellSubscriberClient;
+};
+
+LogEventFilter::AtomIdSet CreateAtomIdSetFromShellSubscriptionBytes(const vector<uint8_t>& bytes) {
+ LogEventFilter::AtomIdSet result;
+
+ ShellSubscription config;
+ config.ParseFromArray(bytes.data(), bytes.size());
+
+ for (int i = 0; i < config.pushed_size(); i++) {
+ const auto& pushed = config.pushed(i);
+ EXPECT_TRUE(pushed.has_atom_id());
+ result.insert(pushed.atom_id());
+ }
+
+ return result;
+}
+
} // namespace
+TEST_F(ShellSubscriberCallbackTest, testAddSubscription) {
+ EXPECT_CALL(
+ *mockLogEventFilter,
+ setAtomIds(CreateAtomIdSetFromShellSubscriptionBytes(configBytes), &shellSubscriber))
+ .Times(1);
+ EXPECT_TRUE(shellSubscriber.startNewSubscription(configBytes, callback));
+}
+
+TEST_F(ShellSubscriberCallbackTest, testAddSubscriptionExceedMax) {
+ const size_t maxSubs = ShellSubscriber::getMaxSubscriptions();
+ EXPECT_CALL(
+ *mockLogEventFilter,
+ setAtomIds(CreateAtomIdSetFromShellSubscriptionBytes(configBytes), &shellSubscriber))
+ .Times(maxSubs);
+ vector<bool> results(maxSubs, false);
+ for (int i = 0; i < maxSubs; i++) {
+ results[i] = shellSubscriber.startNewSubscription(configBytes, callback);
+ }
+
+ // First maxSubs subscriptions should succeed.
+ EXPECT_THAT(results, Each(IsTrue()));
+
+ // Subsequent startNewSubscription should fail.
+ EXPECT_FALSE(shellSubscriber.startNewSubscription(configBytes, callback));
+}
+
+TEST_F(ShellSubscriberCallbackTest, testPushedEventsAreCached) {
+ // Expect callback to not be invoked
+ EXPECT_CALL(*callback, onSubscriptionData(_, _)).Times(Exactly(0));
+ EXPECT_CALL(
+ *mockLogEventFilter,
+ setAtomIds(CreateAtomIdSetFromShellSubscriptionBytes(configBytes), &shellSubscriber))
+ .Times(1);
+ shellSubscriber.startNewSubscription(configBytes, callback);
+
+ // Log an event that does NOT invoke the callack.
+ shellSubscriber.onLogEvent(*CreateScreenStateChangedEvent(
+ 1000 /*timestamp*/, ::android::view::DisplayStateEnum::DISPLAY_STATE_ON));
+}
+
+TEST_F(ShellSubscriberCallbackTest, testOverflowCacheIsFlushed) {
+ // Expect callback to be invoked once.
+ EXPECT_CALL(*callback, onSubscriptionData(_, _)).Times(Exactly(1));
+ EXPECT_CALL(
+ *mockLogEventFilter,
+ setAtomIds(CreateAtomIdSetFromShellSubscriptionBytes(configBytes), &shellSubscriber))
+ .Times(1);
+ shellSubscriber.startNewSubscription(configBytes, callback);
+
+ shellSubscriber.onLogEvent(*CreateScreenStateChangedEvent(
+ 1000 /*timestamp*/, ::android::view::DisplayStateEnum::DISPLAY_STATE_ON));
+
+ // Inflate size of TestAtomReported through the MODE_BYTES field.
+ const vector<int64_t> expIds = vector<int64_t>(200, INT64_MAX);
+
+ // This event should trigger cache overflow flush.
+ shellSubscriber.onLogEvent(*createTestAtomReportedEvent(/*timestampNs=*/1100,
+ /*intFieldValue=*/1, expIds));
+
+ EXPECT_THAT(reason, Eq(StatsSubscriptionCallbackReason::STATSD_INITIATED));
+
+ // Get ShellData proto from the bytes payload of the callback.
+ ShellData actualShellData;
+ ASSERT_TRUE(actualShellData.ParseFromArray(payload.data(), payload.size()));
+
+ ShellData expectedShellData;
+ expectedShellData.add_atom()->mutable_screen_state_changed()->set_state(
+ ::android::view::DisplayStateEnum::DISPLAY_STATE_ON);
+ *expectedShellData.add_atom()->mutable_test_atom_reported() =
+ createTestAtomReportedProto(/* intFieldValue=*/1, expIds);
+ expectedShellData.add_elapsed_timestamp_nanos(1000);
+ expectedShellData.add_elapsed_timestamp_nanos(1100);
+
+ EXPECT_THAT(actualShellData, EqShellData(expectedShellData));
+}
+
+TEST_F(ShellSubscriberCallbackTest, testFlushTrigger) {
+ // Expect callback to be invoked once.
+ EXPECT_CALL(*callback, onSubscriptionData(_, _)).Times(Exactly(1));
+ EXPECT_CALL(
+ *mockLogEventFilter,
+ setAtomIds(CreateAtomIdSetFromShellSubscriptionBytes(configBytes), &shellSubscriber))
+ .Times(1);
+ shellSubscriber.startNewSubscription(configBytes, callback);
+
+ shellSubscriber.onLogEvent(*CreateScreenStateChangedEvent(
+ 1000 /*timestamp*/, ::android::view::DisplayStateEnum::DISPLAY_STATE_ON));
+
+ shellSubscriber.flushSubscription(callback);
+
+ EXPECT_THAT(reason, Eq(StatsSubscriptionCallbackReason::FLUSH_REQUESTED));
+
+ // Get ShellData proto from the bytes payload of the callback.
+ ShellData actualShellData;
+ ASSERT_TRUE(actualShellData.ParseFromArray(payload.data(), payload.size()));
+
+ ShellData expectedShellData;
+ expectedShellData.add_atom()->mutable_screen_state_changed()->set_state(
+ ::android::view::DisplayStateEnum::DISPLAY_STATE_ON);
+ expectedShellData.add_elapsed_timestamp_nanos(1000);
+
+ EXPECT_THAT(actualShellData, EqShellData(expectedShellData));
+}
+
+TEST_F(ShellSubscriberCallbackTest, testFlushTriggerEmptyCache) {
+ // Expect callback to be invoked once.
+ EXPECT_CALL(*callback, onSubscriptionData(_, _)).Times(Exactly(1));
+ EXPECT_CALL(
+ *mockLogEventFilter,
+ setAtomIds(CreateAtomIdSetFromShellSubscriptionBytes(configBytes), &shellSubscriber))
+ .Times(1);
+ shellSubscriber.startNewSubscription(configBytes, callback);
+
+ shellSubscriber.flushSubscription(callback);
+
+ EXPECT_THAT(reason, Eq(StatsSubscriptionCallbackReason::FLUSH_REQUESTED));
+
+ // Get ShellData proto from the bytes payload of the callback.
+ ShellData actualShellData;
+ ASSERT_TRUE(actualShellData.ParseFromArray(payload.data(), payload.size()));
+
+ ShellData expectedShellData;
+
+ EXPECT_THAT(actualShellData, EqShellData(expectedShellData));
+}
+
+TEST_F(ShellSubscriberCallbackTest, testUnsubscribe) {
+ // Expect callback to be invoked once.
+ EXPECT_CALL(*callback, onSubscriptionData(_, _)).Times(Exactly(1));
+ Expectation newSubcriptionEvent =
+ EXPECT_CALL(*mockLogEventFilter,
+ setAtomIds(CreateAtomIdSetFromShellSubscriptionBytes(configBytes),
+ &shellSubscriber))
+ .Times(1);
+ LogEventFilter::AtomIdSet idSetEmpty;
+ EXPECT_CALL(*mockLogEventFilter, setAtomIds(idSetEmpty, &shellSubscriber))
+ .Times(1)
+ .After(newSubcriptionEvent);
+
+ shellSubscriber.startNewSubscription(configBytes, callback);
+
+ shellSubscriber.onLogEvent(*CreateScreenStateChangedEvent(
+ 1000 /*timestamp*/, ::android::view::DisplayStateEnum::DISPLAY_STATE_ON));
+
+ shellSubscriber.unsubscribe(callback);
+
+ EXPECT_THAT(reason, Eq(StatsSubscriptionCallbackReason::SUBSCRIPTION_ENDED));
+
+ // Get ShellData proto from the bytes payload of the callback.
+ ShellData actualShellData;
+ ASSERT_TRUE(actualShellData.ParseFromArray(payload.data(), payload.size()));
+
+ ShellData expectedShellData;
+ expectedShellData.add_atom()->mutable_screen_state_changed()->set_state(
+ ::android::view::DisplayStateEnum::DISPLAY_STATE_ON);
+ expectedShellData.add_elapsed_timestamp_nanos(1000);
+
+ EXPECT_THAT(actualShellData, EqShellData(expectedShellData));
+
+ // This event is ignored as the subscription has ended.
+ shellSubscriber.onLogEvent(*CreateScreenStateChangedEvent(
+ 1000 /*timestamp*/, ::android::view::DisplayStateEnum::DISPLAY_STATE_ON));
+
+ // This should be a no-op as we've already unsubscribed.
+ shellSubscriber.unsubscribe(callback);
+}
+
+TEST_F(ShellSubscriberCallbackTest, testUnsubscribeEmptyCache) {
+ // Expect callback to be invoked once.
+ EXPECT_CALL(*callback, onSubscriptionData(_, _)).Times(Exactly(1));
+ Expectation newSubcriptionEvent =
+ EXPECT_CALL(*mockLogEventFilter,
+ setAtomIds(CreateAtomIdSetFromShellSubscriptionBytes(configBytes),
+ &shellSubscriber))
+ .Times(1);
+ LogEventFilter::AtomIdSet idSetEmpty;
+ EXPECT_CALL(*mockLogEventFilter, setAtomIds(idSetEmpty, &shellSubscriber))
+ .Times(1)
+ .After(newSubcriptionEvent);
+
+ shellSubscriber.startNewSubscription(configBytes, callback);
+
+ shellSubscriber.unsubscribe(callback);
+
+ EXPECT_THAT(reason, Eq(StatsSubscriptionCallbackReason::SUBSCRIPTION_ENDED));
+
+ // Get ShellData proto from the bytes payload of the callback.
+ ShellData actualShellData;
+ ASSERT_TRUE(actualShellData.ParseFromArray(payload.data(), payload.size()));
+
+ ShellData expectedShellData;
+
+ EXPECT_THAT(actualShellData, EqShellData(expectedShellData));
+}
+
+TEST_F(ShellSubscriberCallbackTest, testTruncateTimestampAtom) {
+ // Expect callback to be invoked once.
+ EXPECT_CALL(*callback, onSubscriptionData(_, _)).Times(Exactly(1));
+ EXPECT_CALL(
+ *mockLogEventFilter,
+ setAtomIds(CreateAtomIdSetFromShellSubscriptionBytes(configBytes), &shellSubscriber))
+ .Times(1);
+ shellSubscriber.startNewSubscription(configBytes, callback);
+
+ shellSubscriber.onLogEvent(*CreatePhoneSignalStrengthChangedEvent(
+ NS_PER_SEC * 5 * 60 + 1000 /*timestamp*/,
+ ::android::telephony::SignalStrengthEnum::SIGNAL_STRENGTH_GOOD));
+
+ shellSubscriber.flushSubscription(callback);
+
+ // Get ShellData proto from the bytes payload of the callback.
+ ShellData actualShellData;
+ ASSERT_TRUE(actualShellData.ParseFromArray(payload.data(), payload.size()));
+
+ ShellData expectedShellData;
+ expectedShellData.add_atom()->mutable_phone_signal_strength_changed()->set_signal_strength(
+ ::android::telephony::SignalStrengthEnum::SIGNAL_STRENGTH_GOOD);
+ expectedShellData.add_elapsed_timestamp_nanos(NS_PER_SEC * 5 * 60);
+
+ EXPECT_THAT(actualShellData, EqShellData(expectedShellData));
+}
+
+TEST_F(ShellSubscriberCallbackPulledTest, testPullIfNeededBeforeInterval) {
+ // Pull should not happen
+ EXPECT_CALL(*pullerManager, Pull(_, A<const vector<int32_t>&>(), _, _)).Times(Exactly(0));
+
+ // Expect callback to not be invoked.
+ EXPECT_CALL(*callback, onSubscriptionData(_, _)).Times(Exactly(0));
+
+ shellSubscriberClient->pullAndSendHeartbeatsIfNeeded(/* nowSecs= */ 0, /* nowMillis= */ 0,
+ /* nowNanos= */ 0);
+}
+
+TEST_F(ShellSubscriberCallbackPulledTest, testPullAtInterval) {
+ // Pull should happen once. The data is cached.
+ EXPECT_CALL(*pullerManager, Pull(_, A<const vector<int32_t>&>(), _, _)).Times(Exactly(1));
+
+ // Expect callback to not be invoked.
+ EXPECT_CALL(*callback, onSubscriptionData(_, _)).Times(Exactly(0));
+
+ // This pull should NOT trigger a cache flush.
+ shellSubscriberClient->pullAndSendHeartbeatsIfNeeded(/* nowSecs= */ 61, /* nowMillis= */ 61'000,
+ /* nowNanos= */ 61'000'000'000);
+}
+
+TEST_F(ShellSubscriberCallbackPulledTest, testCachedPullIsFlushed) {
+ // Pull should happen once. The data is cached.
+ EXPECT_CALL(*pullerManager, Pull(_, A<const vector<int32_t>&>(), _, _)).Times(Exactly(1));
+
+ // This pull should NOT trigger a cache flush.
+ shellSubscriberClient->pullAndSendHeartbeatsIfNeeded(/* nowSecs= */ 61, /* nowMillis= */ 61'000,
+ /* nowNanos= */ 61'000'000'000);
+
+ // Expect callback to be invoked once flush is requested.
+ EXPECT_CALL(*callback, onSubscriptionData(_, _)).Times(Exactly(1));
+
+ // This should flush out data cached from the pull.
+ shellSubscriberClient->flush();
+
+ EXPECT_THAT(reason, Eq(StatsSubscriptionCallbackReason::FLUSH_REQUESTED));
+
+ // Get ShellData proto from the bytes payload of the callback.
+ ShellData actualShellData;
+ ASSERT_TRUE(actualShellData.ParseFromArray(payload.data(), payload.size()));
+
+ EXPECT_THAT(actualShellData, EqShellData(getExpectedPulledData()));
+}
+
+TEST_F(ShellSubscriberCallbackPulledTest, testPullAtCacheTimeout) {
+ // Pull should happen once. The data is flushed.
+ EXPECT_CALL(*pullerManager, Pull(_, A<const vector<int32_t>&>(), _, _)).Times(Exactly(1));
+
+ // Expect callback to be invoked.
+ EXPECT_CALL(*callback, onSubscriptionData(_, _)).Times(Exactly(1));
+
+ // This pull should trigger a cache flush.
+ shellSubscriberClient->pullAndSendHeartbeatsIfNeeded(/* nowSecs= */ 70, /* nowMillis= */ 70'000,
+ /* nowNanos= */ 70'000'000'000);
+
+ EXPECT_THAT(reason, Eq(StatsSubscriptionCallbackReason::STATSD_INITIATED));
+
+ // Get ShellData proto from the bytes payload of the callback.
+ ShellData actualShellData;
+ ASSERT_TRUE(actualShellData.ParseFromArray(payload.data(), payload.size()));
+
+ EXPECT_THAT(actualShellData, EqShellData(getExpectedPulledData()));
+}
+
+TEST_F(ShellSubscriberCallbackPulledTest, testPullFrequencyTooShort) {
+ // Pull should NOT happen.
+ EXPECT_CALL(*pullerManager, Pull(_, A<const vector<int32_t>&>(), _, _)).Times(Exactly(0));
+
+ // This should not trigger a pull even though the timestamp passed in matches the pull interval
+ // specified in the config.
+ const int64_t sleepTimeMs =
+ shellSubscriberClient->pullAndSendHeartbeatsIfNeeded(2, 2000, 2'000'000'000);
+}
+
+TEST_F(ShellSubscriberCallbackPulledTest, testMinSleep) {
+ // Pull should NOT happen.
+ EXPECT_CALL(*pullerManager, Pull(_, A<const vector<int32_t>&>(), _, _)).Times(Exactly(0));
+
+ const int64_t sleepTimeMs =
+ shellSubscriberClient->pullAndSendHeartbeatsIfNeeded(59, 59'000, 59'000'000'000);
+
+ // Even though there is only 1000 ms left until the next pull, the sleep time returned is
+ // kMinCallbackSleepIntervalMs.
+ EXPECT_THAT(sleepTimeMs, Eq(ShellSubscriberClient::kMinCallbackSleepIntervalMs));
+}
+
TEST(ShellSubscriberTest, testPushedSubscription) {
sp<MockUidMap> uidMap = new NaggyMock<MockUidMap>();
sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
@@ -193,24 +626,27 @@
// create a simple config to get screen events
ShellSubscription config;
- config.add_pushed()->set_atom_id(29);
+ config.add_pushed()->set_atom_id(SCREEN_STATE_CHANGED);
// this is the expected screen event atom.
vector<ShellData> expectedData;
ShellData shellData1;
shellData1.add_atom()->mutable_screen_state_changed()->set_state(
::android::view::DisplayStateEnum::DISPLAY_STATE_ON);
+ shellData1.add_elapsed_timestamp_nanos(pushedList[0]->GetElapsedTimestampNs());
ShellData shellData2;
shellData2.add_atom()->mutable_screen_state_changed()->set_state(
::android::view::DisplayStateEnum::DISPLAY_STATE_OFF);
+ shellData2.add_elapsed_timestamp_nanos(pushedList[1]->GetElapsedTimestampNs());
expectedData.push_back(shellData1);
expectedData.push_back(shellData2);
// Test with single client
- runShellTest(config, uidMap, pullerManager, pushedList, expectedData, kSingleClient);
+ TRACE_CALL(runShellTest, config, uidMap, pullerManager, pushedList, expectedData,
+ kSingleClient);
// Test with multiple client
- runShellTest(config, uidMap, pullerManager, pushedList, expectedData, kNumClients);
+ TRACE_CALL(runShellTest, config, uidMap, pullerManager, pushedList, expectedData, kNumClients);
}
TEST(ShellSubscriberTest, testPulledSubscription) {
@@ -218,7 +654,7 @@
sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
const vector<int32_t> uids = {AID_SYSTEM};
- EXPECT_CALL(*pullerManager, Pull(10016, uids, _, _))
+ EXPECT_CALL(*pullerManager, Pull(CPU_ACTIVE_TIME, uids, _, _))
.WillRepeatedly(Invoke([](int tagId, const vector<int32_t>&, const int64_t,
vector<std::shared_ptr<LogEvent>>* data) {
data->clear();
@@ -228,12 +664,12 @@
}));
// Test with single client
- runShellTest(getPulledConfig(), uidMap, pullerManager, {}, {getExpectedPulledData()},
- kSingleClient);
+ TRACE_CALL(runShellTest, getPulledConfig(), uidMap, pullerManager, /*pushedEvents=*/{},
+ {getExpectedPulledData()}, kSingleClient);
// Test with multiple clients.
- runShellTest(getPulledConfig(), uidMap, pullerManager, {}, {getExpectedPulledData()},
- kNumClients);
+ TRACE_CALL(runShellTest, getPulledConfig(), uidMap, pullerManager, {},
+ {getExpectedPulledData()}, kNumClients);
}
TEST(ShellSubscriberTest, testBothSubscriptions) {
@@ -241,7 +677,7 @@
sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
const vector<int32_t> uids = {AID_SYSTEM};
- EXPECT_CALL(*pullerManager, Pull(10016, uids, _, _))
+ EXPECT_CALL(*pullerManager, Pull(CPU_ACTIVE_TIME, uids, _, _))
.WillRepeatedly(Invoke([](int tagId, const vector<int32_t>&, const int64_t,
vector<std::shared_ptr<LogEvent>>* data) {
data->clear();
@@ -253,30 +689,34 @@
vector<std::shared_ptr<LogEvent>> pushedList = getPushedEvents();
ShellSubscription config = getPulledConfig();
- config.add_pushed()->set_atom_id(29);
+ config.add_pushed()->set_atom_id(SCREEN_STATE_CHANGED);
vector<ShellData> expectedData;
ShellData shellData1;
shellData1.add_atom()->mutable_screen_state_changed()->set_state(
::android::view::DisplayStateEnum::DISPLAY_STATE_ON);
+ shellData1.add_elapsed_timestamp_nanos(pushedList[0]->GetElapsedTimestampNs());
ShellData shellData2;
shellData2.add_atom()->mutable_screen_state_changed()->set_state(
::android::view::DisplayStateEnum::DISPLAY_STATE_OFF);
+ shellData2.add_elapsed_timestamp_nanos(pushedList[1]->GetElapsedTimestampNs());
expectedData.push_back(getExpectedPulledData());
expectedData.push_back(shellData1);
expectedData.push_back(shellData2);
// Test with single client
- runShellTest(config, uidMap, pullerManager, pushedList, expectedData, kSingleClient);
+ TRACE_CALL(runShellTest, config, uidMap, pullerManager, pushedList, expectedData,
+ kSingleClient);
// Test with multiple client
- runShellTest(config, uidMap, pullerManager, pushedList, expectedData, kNumClients);
+ TRACE_CALL(runShellTest, config, uidMap, pullerManager, pushedList, expectedData, kNumClients);
}
TEST(ShellSubscriberTest, testMaxSizeGuard) {
sp<MockUidMap> uidMap = new NaggyMock<MockUidMap>();
sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
- sp<ShellSubscriber> shellManager = new ShellSubscriber(uidMap, pullerManager);
+ sp<ShellSubscriber> shellManager =
+ new ShellSubscriber(uidMap, pullerManager, /*LogEventFilter=*/nullptr);
// set up 2 pipes for read/write config and data
int fds_config[2];
@@ -299,11 +739,12 @@
TEST(ShellSubscriberTest, testMaxSubscriptionsGuard) {
sp<MockUidMap> uidMap = new NaggyMock<MockUidMap>();
sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
- sp<ShellSubscriber> shellManager = new ShellSubscriber(uidMap, pullerManager);
+ sp<ShellSubscriber> shellManager =
+ new ShellSubscriber(uidMap, pullerManager, /*LogEventFilter=*/nullptr);
// create a simple config to get screen events
ShellSubscription config;
- config.add_pushed()->set_atom_id(29);
+ config.add_pushed()->set_atom_id(SCREEN_STATE_CHANGED);
size_t bufferSize = config.ByteSize();
vector<uint8_t> buffer(bufferSize);
@@ -348,15 +789,16 @@
TEST(ShellSubscriberTest, testDifferentConfigs) {
sp<MockUidMap> uidMap = new NaggyMock<MockUidMap>();
sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
- sp<ShellSubscriber> shellManager = new ShellSubscriber(uidMap, pullerManager);
+ sp<ShellSubscriber> shellManager =
+ new ShellSubscriber(uidMap, pullerManager, /*LogEventFilter=*/nullptr);
// number of different configs
int numConfigs = 2;
// create a simple config to get screen events
ShellSubscription configs[numConfigs];
- configs[0].add_pushed()->set_atom_id(29);
- configs[1].add_pushed()->set_atom_id(32);
+ configs[0].add_pushed()->set_atom_id(SCREEN_STATE_CHANGED);
+ configs[1].add_pushed()->set_atom_id(PLUGGED_STATE_CHANGED);
vector<vector<uint8_t>> configBuffers;
for (int i = 0; i < numConfigs; i++) {
@@ -387,43 +829,70 @@
}
// send a log event that matches the config.
- for (const auto& event : getPushedEvents()) {
+ vector<std::shared_ptr<LogEvent>> pushedList = getPushedEvents();
+ for (const auto& event : pushedList) {
shellManager->onLogEvent(*event);
}
// Validate Config 1
- string receivedData = readData(fds_datas[0][0]);
+ ShellData actual1 = readData(fds_datas[0][0]);
ShellData expected1;
expected1.add_atom()->mutable_screen_state_changed()->set_state(
::android::view::DisplayStateEnum::DISPLAY_STATE_ON);
- EXPECT_EQ(receivedData, expected1.SerializeAsString());
+ expected1.add_elapsed_timestamp_nanos(pushedList[0]->GetElapsedTimestampNs());
+ EXPECT_THAT(expected1, EqShellData(actual1));
- receivedData = readData(fds_datas[0][0]);
+ ShellData actual2 = readData(fds_datas[0][0]);
ShellData expected2;
expected2.add_atom()->mutable_screen_state_changed()->set_state(
::android::view::DisplayStateEnum::DISPLAY_STATE_OFF);
- EXPECT_EQ(receivedData, expected2.SerializeAsString());
+ expected2.add_elapsed_timestamp_nanos(pushedList[1]->GetElapsedTimestampNs());
+ EXPECT_THAT(expected2, EqShellData(actual2));
// Validate Config 2, repeating the process
- receivedData = readData(fds_datas[1][0]);
+ ShellData actual3 = readData(fds_datas[1][0]);
ShellData expected3;
expected3.add_atom()->mutable_plugged_state_changed()->set_state(
BatteryPluggedStateEnum::BATTERY_PLUGGED_USB);
- EXPECT_EQ(receivedData, expected3.SerializeAsString());
+ expected3.add_elapsed_timestamp_nanos(pushedList[2]->GetElapsedTimestampNs());
+ EXPECT_THAT(expected3, EqShellData(actual3));
- receivedData = readData(fds_datas[1][0]);
+ ShellData actual4 = readData(fds_datas[1][0]);
ShellData expected4;
expected4.add_atom()->mutable_plugged_state_changed()->set_state(
BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE);
- EXPECT_EQ(receivedData, expected4.SerializeAsString());
+ expected4.add_elapsed_timestamp_nanos(pushedList[3]->GetElapsedTimestampNs());
+ EXPECT_THAT(expected4, EqShellData(actual4));
// Not closing fds_datas[i][0] because this causes writes within ShellSubscriberClient to hang
}
+TEST(ShellSubscriberTest, testPushedSubscriptionRestrictedEvent) {
+ sp<MockUidMap> uidMap = new NaggyMock<MockUidMap>();
+ sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+
+ std::vector<shared_ptr<LogEvent>> pushedList;
+ pushedList.push_back(CreateRestrictedLogEvent(/*atomTag=*/10, /*timestamp=*/1000));
+
+ // create a simple config to get screen events
+ ShellSubscription config;
+ config.add_pushed()->set_atom_id(10);
+
+ // expect empty data
+ vector<ShellData> expectedData;
+
+ // Test with single client
+ TRACE_CALL(runShellTest, config, uidMap, pullerManager, pushedList, expectedData,
+ kSingleClient);
+
+ // Test with multiple client
+ TRACE_CALL(runShellTest, config, uidMap, pullerManager, pushedList, expectedData, kNumClients);
+}
+
#else
GTEST_LOG_(INFO) << "This test does nothing.\n";
#endif
} // namespace statsd
} // namespace os
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/statsd/tests/state/StateTracker_test.cpp b/statsd/tests/state/StateTracker_test.cpp
index 6516c15..d2fbb3f 100644
--- a/statsd/tests/state/StateTracker_test.cpp
+++ b/statsd/tests/state/StateTracker_test.cpp
@@ -113,6 +113,7 @@
mgr.registerListener(util::SCREEN_STATE_CHANGED, listener1);
EXPECT_EQ(1, mgr.getStateTrackersCount());
EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
+ mgr.unregisterListener(util::SCREEN_STATE_CHANGED, listener1);
}
TEST(StateManagerTest, TestOnLogEvent) {
diff --git a/statsd/tests/statsd_test_util.cpp b/statsd/tests/statsd_test_util.cpp
index b4a65e9..db21817 100644
--- a/statsd/tests/statsd_test_util.cpp
+++ b/statsd/tests/statsd_test_util.cpp
@@ -20,7 +20,6 @@
#include <android-base/stringprintf.h>
#include "matchers/SimpleAtomMatchingTracker.h"
-#include "stats_annotations.h"
#include "stats_event.h"
#include "stats_util.h"
@@ -34,11 +33,11 @@
namespace os {
namespace statsd {
-void StatsServiceConfigTest::sendConfig(const StatsdConfig& config) {
+bool StatsServiceConfigTest::sendConfig(const StatsdConfig& config) {
string str;
config.SerializeToString(&str);
std::vector<uint8_t> configAsVec(str.begin(), str.end());
- service->addConfiguration(kConfigKey, configAsVec, kCallingUid);
+ return service->addConfiguration(kConfigKey, configAsVec, kCallingUid).isOk();
}
ConfigMetricsReport StatsServiceConfigTest::getReports(sp<StatsLogProcessor> processor,
@@ -50,7 +49,7 @@
ConfigMetricsReportList reports;
reports.ParseFromArray(output.data(), output.size());
EXPECT_EQ(1, reports.reports_size());
- return reports.reports(kCallingUid);
+ return reports.reports(0);
}
StatsLogReport outputStreamToProto(ProtoOutputStream* proto) {
@@ -812,7 +811,7 @@
AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs);
AStatsEvent_writeInt32(statsEvent, uid);
- AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true);
+ AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
AStatsEvent_writeInt32(statsEvent, data1);
AStatsEvent_writeInt32(statsEvent, data2);
@@ -825,7 +824,7 @@
AStatsEvent_setAtomId(statsEvent, atomId);
AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs);
AStatsEvent_writeInt32(statsEvent, uid);
- AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true);
+ AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
AStatsEvent_writeInt32(statsEvent, data1);
AStatsEvent_writeInt32Array(statsEvent, data2.data(), data2.size());
@@ -855,7 +854,7 @@
AStatsEvent* statsEvent = makeUidStatsEvent(atomId, eventTimeNs, uid1, data1, data2);
for (const int extraUid : extraUids) {
AStatsEvent_writeInt32(statsEvent, extraUid);
- AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true);
+ AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
}
shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0);
@@ -869,7 +868,7 @@
AStatsEvent_setAtomId(statsEvent, atomId);
AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs);
AStatsEvent_writeInt32Array(statsEvent, uids.data(), uids.size());
- AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true);
+ AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0);
parseStatsEventToLogEvent(statsEvent, logEvent.get());
@@ -883,7 +882,7 @@
AStatsEvent_setAtomId(statsEvent, atomId);
AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs);
AStatsEvent_writeInt32Array(statsEvent, uids.data(), uids.size());
- AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true);
+ AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
AStatsEvent_writeInt32(statsEvent, data1);
AStatsEvent_writeInt32(statsEvent, data2);
@@ -900,7 +899,7 @@
AStatsEvent_setAtomId(statsEvent, atomId);
AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs);
AStatsEvent_writeInt32Array(statsEvent, uids.data(), uids.size());
- AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true);
+ AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
AStatsEvent_writeInt32(statsEvent, data1);
AStatsEvent_writeInt32Array(statsEvent, data2.data(), data2.size());
@@ -953,8 +952,8 @@
AStatsEvent_setAtomId(statsEvent, util::SCREEN_STATE_CHANGED);
AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
AStatsEvent_writeInt32(statsEvent, state);
- AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_EXCLUSIVE_STATE, true);
- AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_STATE_NESTED, false);
+ AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_EXCLUSIVE_STATE, true);
+ AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_STATE_NESTED, false);
std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(loggerUid, /*pid=*/0);
parseStatsEventToLogEvent(statsEvent, logEvent.get());
@@ -966,8 +965,8 @@
AStatsEvent_setAtomId(statsEvent, util::BATTERY_SAVER_MODE_STATE_CHANGED);
AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
AStatsEvent_writeInt32(statsEvent, BatterySaverModeStateChanged::ON);
- AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_EXCLUSIVE_STATE, true);
- AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_STATE_NESTED, false);
+ AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_EXCLUSIVE_STATE, true);
+ AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_STATE_NESTED, false);
std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
parseStatsEventToLogEvent(statsEvent, logEvent.get());
@@ -979,8 +978,8 @@
AStatsEvent_setAtomId(statsEvent, util::BATTERY_SAVER_MODE_STATE_CHANGED);
AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
AStatsEvent_writeInt32(statsEvent, BatterySaverModeStateChanged::OFF);
- AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_EXCLUSIVE_STATE, true);
- AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_STATE_NESTED, false);
+ AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_EXCLUSIVE_STATE, true);
+ AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_STATE_NESTED, false);
std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
parseStatsEventToLogEvent(statsEvent, logEvent.get());
@@ -1112,14 +1111,15 @@
AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
writeAttribution(statsEvent, attributionUids, attributionTags);
- AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID, true);
+ AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID,
+ true);
AStatsEvent_writeInt32(statsEvent, android::os::WakeLockLevelEnum::PARTIAL_WAKE_LOCK);
- AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_PRIMARY_FIELD, true);
+ AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_PRIMARY_FIELD, true);
AStatsEvent_writeString(statsEvent, wakelockName.c_str());
- AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_PRIMARY_FIELD, true);
+ AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_PRIMARY_FIELD, true);
AStatsEvent_writeInt32(statsEvent, state);
- AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_EXCLUSIVE_STATE, true);
- AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_STATE_NESTED, true);
+ AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_EXCLUSIVE_STATE, true);
+ AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_STATE_NESTED, true);
std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
parseStatsEventToLogEvent(statsEvent, logEvent.get());
@@ -1258,11 +1258,11 @@
AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
AStatsEvent_writeInt32(statsEvent, uid);
- AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true);
- AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_PRIMARY_FIELD, true);
+ AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
+ AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_PRIMARY_FIELD, true);
AStatsEvent_writeInt32(statsEvent, state);
- AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_EXCLUSIVE_STATE, true);
- AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_STATE_NESTED, false);
+ AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_EXCLUSIVE_STATE, true);
+ AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_STATE_NESTED, false);
std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
parseStatsEventToLogEvent(statsEvent, logEvent.get());
@@ -1280,20 +1280,21 @@
AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
writeAttribution(statsEvent, attributionUids, attributionTags);
- AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID, true);
+ AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID,
+ true);
AStatsEvent_writeInt32(statsEvent, state);
- AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_EXCLUSIVE_STATE, true);
- AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_STATE_NESTED, true);
+ AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_EXCLUSIVE_STATE, true);
+ AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_STATE_NESTED, true);
if (state == util::BLE_SCAN_STATE_CHANGED__STATE__RESET) {
- AStatsEvent_addInt32Annotation(statsEvent, ANNOTATION_ID_TRIGGER_STATE_RESET,
+ AStatsEvent_addInt32Annotation(statsEvent, ASTATSLOG_ANNOTATION_ID_TRIGGER_STATE_RESET,
util::BLE_SCAN_STATE_CHANGED__STATE__OFF);
}
AStatsEvent_writeBool(statsEvent, filtered); // filtered
- AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_PRIMARY_FIELD, true);
+ AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_PRIMARY_FIELD, true);
AStatsEvent_writeBool(statsEvent, firstMatch); // first match
- AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_PRIMARY_FIELD, true);
+ AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_PRIMARY_FIELD, true);
AStatsEvent_writeBool(statsEvent, opportunistic); // opportunistic
- AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_PRIMARY_FIELD, true);
+ AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_PRIMARY_FIELD, true);
std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
parseStatsEventToLogEvent(statsEvent, logEvent.get());
@@ -1309,14 +1310,14 @@
AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
AStatsEvent_writeInt32(statsEvent, uid);
- AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true);
- AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_PRIMARY_FIELD, true);
+ AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
+ AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_PRIMARY_FIELD, true);
AStatsEvent_writeString(statsEvent, packageName.c_str());
- AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_PRIMARY_FIELD, true);
+ AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_PRIMARY_FIELD, true);
AStatsEvent_writeBool(statsEvent, usingAlertWindow);
AStatsEvent_writeInt32(statsEvent, state);
- AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_EXCLUSIVE_STATE, true);
- AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_STATE_NESTED, false);
+ AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_EXCLUSIVE_STATE, true);
+ AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_STATE_NESTED, false);
std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
parseStatsEventToLogEvent(statsEvent, logEvent.get());
@@ -1360,10 +1361,46 @@
return logEvent;
}
+std::unique_ptr<LogEvent> CreateRestrictedLogEvent(int atomTag, int64_t timestampNs) {
+ AStatsEvent* statsEvent = AStatsEvent_obtain();
+ AStatsEvent_setAtomId(statsEvent, atomTag);
+ AStatsEvent_addInt32Annotation(statsEvent, ASTATSLOG_ANNOTATION_ID_RESTRICTION_CATEGORY,
+ ASTATSLOG_RESTRICTION_CATEGORY_DIAGNOSTIC);
+ AStatsEvent_writeInt32(statsEvent, 10);
+ AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
+ std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
+ parseStatsEventToLogEvent(statsEvent, logEvent.get());
+ return logEvent;
+}
+
+std::unique_ptr<LogEvent> CreateNonRestrictedLogEvent(int atomTag, int64_t timestampNs) {
+ AStatsEvent* statsEvent = AStatsEvent_obtain();
+ AStatsEvent_setAtomId(statsEvent, atomTag);
+ AStatsEvent_writeInt32(statsEvent, 10);
+ AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
+ std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
+ parseStatsEventToLogEvent(statsEvent, logEvent.get());
+ return logEvent;
+}
+
+std::unique_ptr<LogEvent> CreatePhoneSignalStrengthChangedEvent(
+ int64_t timestampNs, ::telephony::SignalStrengthEnum state) {
+ AStatsEvent* statsEvent = AStatsEvent_obtain();
+ AStatsEvent_setAtomId(statsEvent, util::PHONE_SIGNAL_STRENGTH_CHANGED);
+ AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_TRUNCATE_TIMESTAMP, true);
+ AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
+ AStatsEvent_writeInt32(statsEvent, state);
+
+ std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
+ parseStatsEventToLogEvent(statsEvent, logEvent.get());
+ return logEvent;
+}
+
sp<StatsLogProcessor> CreateStatsLogProcessor(const int64_t timeBaseNs, const int64_t currentTimeNs,
const StatsdConfig& config, const ConfigKey& key,
const shared_ptr<IPullAtomCallback>& puller,
- const int32_t atomTag, const sp<UidMap> uidMap) {
+ const int32_t atomTag, const sp<UidMap> uidMap,
+ const shared_ptr<LogEventFilter>& logEventFilter) {
sp<StatsPullerManager> pullerManager = new StatsPullerManager();
if (puller != nullptr) {
pullerManager->RegisterPullAtomCallback(/*uid=*/0, atomTag, NS_PER_SEC, NS_PER_SEC * 10, {},
@@ -1377,14 +1414,45 @@
new AlarmMonitor(1,
[](const shared_ptr<IStatsCompanionService>&, int64_t){},
[](const shared_ptr<IStatsCompanionService>&){});
- sp<StatsLogProcessor> processor =
- new StatsLogProcessor(uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
- timeBaseNs, [](const ConfigKey&) { return true; },
- [](const int&, const vector<int64_t>&) {return true;});
+ sp<StatsLogProcessor> processor = new StatsLogProcessor(
+ uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, timeBaseNs,
+ [](const ConfigKey&) { return true; },
+ [](const int&, const vector<int64_t>&) { return true; },
+ [](const ConfigKey&, const string&, const vector<int64_t>&) {}, logEventFilter);
+
processor->OnConfigUpdated(currentTimeNs, key, config);
return processor;
}
+LogEventFilter::AtomIdSet CreateAtomIdSetDefault() {
+ LogEventFilter::AtomIdSet resultList(std::move(StatsLogProcessor::getDefaultAtomIdSet()));
+ StateManager::getInstance().addAllAtomIds(resultList);
+ return resultList;
+}
+
+LogEventFilter::AtomIdSet CreateAtomIdSetFromConfig(const StatsdConfig& config) {
+ LogEventFilter::AtomIdSet resultList(std::move(StatsLogProcessor::getDefaultAtomIdSet()));
+
+ // Parse the config for atom ids. A combination atom matcher is a combination of (in the end)
+ // simple atom matchers. So by adding all the atoms from the simple atom matchers
+ // function adds all of the atoms.
+ for (int i = 0; i < config.atom_matcher_size(); i++) {
+ const AtomMatcher& matcher = config.atom_matcher(i);
+ if (matcher.has_simple_atom_matcher()) {
+ EXPECT_TRUE(matcher.simple_atom_matcher().has_atom_id());
+ resultList.insert(matcher.simple_atom_matcher().atom_id());
+ }
+ }
+
+ for (int i = 0; i < config.state_size(); i++) {
+ const State& state = config.state(i);
+ EXPECT_TRUE(state.has_atom_id());
+ resultList.insert(state.atom_id());
+ }
+
+ return resultList;
+}
+
void sortLogEventsByTimestamp(std::vector<std::unique_ptr<LogEvent>> *events) {
std::sort(events->begin(), events->end(),
[](const std::unique_ptr<LogEvent>& a, const std::unique_ptr<LogEvent>& b) {
@@ -2108,6 +2176,46 @@
return pulledAtomStats;
}
+void createStatsEvent(AStatsEvent* statsEvent, uint8_t typeId, uint32_t atomId) {
+ AStatsEvent_setAtomId(statsEvent, atomId);
+ fillStatsEventWithSampleValue(statsEvent, typeId);
+}
+
+void fillStatsEventWithSampleValue(AStatsEvent* statsEvent, uint8_t typeId) {
+ int int32Array[2] = {3, 6};
+ uint32_t uids[] = {1001, 1002};
+ const char* tags[] = {"tag1", "tag2"};
+
+ switch (typeId) {
+ case INT32_TYPE:
+ AStatsEvent_writeInt32(statsEvent, 10);
+ break;
+ case INT64_TYPE:
+ AStatsEvent_writeInt64(statsEvent, 1000L);
+ break;
+ case STRING_TYPE:
+ AStatsEvent_writeString(statsEvent, "test");
+ break;
+ case LIST_TYPE:
+ AStatsEvent_writeInt32Array(statsEvent, int32Array, 2);
+ break;
+ case FLOAT_TYPE:
+ AStatsEvent_writeFloat(statsEvent, 1.3f);
+ break;
+ case BOOL_TYPE:
+ AStatsEvent_writeBool(statsEvent, 1);
+ break;
+ case BYTE_ARRAY_TYPE:
+ AStatsEvent_writeByteArray(statsEvent, (uint8_t*)"test", strlen("test"));
+ break;
+ case ATTRIBUTION_CHAIN_TYPE:
+ AStatsEvent_writeAttributionChain(statsEvent, uids, tags, 2);
+ break;
+ default:
+ break;
+ }
+}
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/statsd/tests/statsd_test_util.h b/statsd/tests/statsd_test_util.h
index b72dfe4..cba3ebf 100644
--- a/statsd/tests/statsd_test_util.h
+++ b/statsd/tests/statsd_test_util.h
@@ -16,10 +16,12 @@
#include <aidl/android/os/BnPendingIntentRef.h>
#include <aidl/android/os/BnPullAtomCallback.h>
+#include <aidl/android/os/BnStatsQueryCallback.h>
+#include <aidl/android/os/BnStatsSubscriptionCallback.h>
#include <aidl/android/os/IPullAtomCallback.h>
#include <aidl/android/os/IPullAtomResultReceiver.h>
+#include <aidl/android/os/StatsSubscriptionCallbackReason.h>
#include <gmock/gmock.h>
-#include <google/protobuf/util/message_differencer.h>
#include <gtest/gtest.h>
#include "src/StatsLogProcessor.h"
@@ -32,6 +34,7 @@
#include "src/stats_log.pb.h"
#include "src/stats_log_util.h"
#include "src/statsd_config.pb.h"
+#include "stats_annotations.h"
#include "stats_event.h"
#include "statslog_statsdtest.h"
@@ -41,11 +44,13 @@
using namespace testing;
using ::aidl::android::os::BnPullAtomCallback;
+using ::aidl::android::os::BnStatsQueryCallback;
+using ::aidl::android::os::BnStatsSubscriptionCallback;
using ::aidl::android::os::IPullAtomCallback;
using ::aidl::android::os::IPullAtomResultReceiver;
+using ::aidl::android::os::StatsSubscriptionCallbackReason;
using android::util::ProtoReader;
using google::protobuf::RepeatedPtrField;
-using google::protobuf::util::MessageDifferencer;
using Status = ::ndk::ScopedAStatus;
using PackageInfoSnapshot = UidMapping_PackageInfoSnapshot;
using PackageInfo = UidMapping_PackageInfoSnapshot_PackageInfo;
@@ -81,23 +86,49 @@
MOCK_METHOD(std::set<int32_t>, getAppUid, (const string& package), (const));
};
+class BasicMockLogEventFilter : public LogEventFilter {
+public:
+ MOCK_METHOD(void, setFilteringEnabled, (bool isEnabled), (override));
+ MOCK_METHOD(void, setAtomIds, (AtomIdSet tagIds, ConsumerId consumer), (override));
+};
+
class MockPendingIntentRef : public aidl::android::os::BnPendingIntentRef {
public:
MOCK_METHOD1(sendDataBroadcast, Status(int64_t lastReportTimeNs));
MOCK_METHOD1(sendActiveConfigsChangedBroadcast, Status(const vector<int64_t>& configIds));
+ MOCK_METHOD1(sendRestrictedMetricsChangedBroadcast, Status(const vector<int64_t>& metricIds));
MOCK_METHOD6(sendSubscriberBroadcast,
Status(int64_t configUid, int64_t configId, int64_t subscriptionId,
int64_t subscriptionRuleId, const vector<string>& cookies,
const StatsDimensionsValueParcel& dimensionsValueParcel));
};
+typedef StrictMock<BasicMockLogEventFilter> MockLogEventFilter;
+
+class MockStatsQueryCallback : public BnStatsQueryCallback {
+public:
+ MOCK_METHOD4(sendResults,
+ Status(const vector<string>& queryData, const vector<string>& columnNames,
+ const vector<int32_t>& columnTypes, int32_t rowCount));
+ MOCK_METHOD1(sendFailure, Status(const string& in_error));
+};
+
+class MockStatsSubscriptionCallback : public BnStatsSubscriptionCallback {
+public:
+ MOCK_METHOD(Status, onSubscriptionData,
+ (StatsSubscriptionCallbackReason in_reason,
+ const std::vector<uint8_t>& in_subscriptionPayload),
+ (override));
+};
+
class StatsServiceConfigTest : public ::testing::Test {
protected:
shared_ptr<StatsService> service;
const int kConfigKey = 789130123; // Randomly chosen
- const int kCallingUid = 0; // Randomly chosen
+ const int kCallingUid = 10100; // Randomly chosen
+
void SetUp() override {
- service = SharedRefBase::make<StatsService>(new UidMap(), /* queue */ nullptr);
+ service = createStatsService();
// Removing config file from data/misc/stats-service and data/misc/stats-data if present
ConfigKey configKey(kCallingUid, kConfigKey);
service->removeConfiguration(kConfigKey, kCallingUid);
@@ -115,7 +146,12 @@
ADB_DUMP, NO_TIME_CONSTRAINTS, nullptr);
}
- void sendConfig(const StatsdConfig& config);
+ virtual shared_ptr<StatsService> createStatsService() {
+ return SharedRefBase::make<StatsService>(new UidMap(), /* queue */ nullptr,
+ /* LogEventFilter */ nullptr);
+ }
+
+ bool sendConfig(const StatsdConfig& config);
ConfigMetricsReport getReports(sp<StatsLogProcessor> processor, int64_t timestamp,
bool include_current = false);
@@ -502,6 +538,12 @@
const vector<string>& repeatedStringField, const bool* repeatedBoolField,
const size_t repeatedBoolFieldLength, const vector<int>& repeatedEnumField);
+std::unique_ptr<LogEvent> CreateRestrictedLogEvent(int atomTag, int64_t timestampNs = 0);
+std::unique_ptr<LogEvent> CreateNonRestrictedLogEvent(int atomTag, int64_t timestampNs = 0);
+
+std::unique_ptr<LogEvent> CreatePhoneSignalStrengthChangedEvent(
+ int64_t timestampNs, ::telephony::SignalStrengthEnum state);
+
std::unique_ptr<LogEvent> CreateTestAtomReportedEvent(
uint64_t timestampNs, const vector<int>& attributionUids,
const vector<string>& attributionTags, const int intField, const long longField,
@@ -512,12 +554,19 @@
const bool* repeatedBoolField, const size_t repeatedBoolFieldLength,
const vector<int>& repeatedEnumField);
+void createStatsEvent(AStatsEvent* statsEvent, uint8_t typeId, uint32_t atomId);
+
+void fillStatsEventWithSampleValue(AStatsEvent* statsEvent, uint8_t typeId);
+
// Create a statsd log event processor upon the start time in seconds, config and key.
-sp<StatsLogProcessor> CreateStatsLogProcessor(const int64_t timeBaseNs, const int64_t currentTimeNs,
- const StatsdConfig& config, const ConfigKey& key,
- const shared_ptr<IPullAtomCallback>& puller = nullptr,
- const int32_t atomTag = 0 /*for puller only*/,
- const sp<UidMap> = new UidMap());
+sp<StatsLogProcessor> CreateStatsLogProcessor(
+ const int64_t timeBaseNs, const int64_t currentTimeNs, const StatsdConfig& config,
+ const ConfigKey& key, const shared_ptr<IPullAtomCallback>& puller = nullptr,
+ const int32_t atomTag = 0 /*for puller only*/, const sp<UidMap> = new UidMap(),
+ const shared_ptr<LogEventFilter>& logEventFilter = nullptr);
+
+LogEventFilter::AtomIdSet CreateAtomIdSetDefault();
+LogEventFilter::AtomIdSet CreateAtomIdSetFromConfig(const StatsdConfig& config);
// Util function to sort the log events by timestamp.
void sortLogEventsByTimestamp(std::vector<std::unique_ptr<LogEvent>> *events);
@@ -749,17 +798,6 @@
const std::vector<std::vector<uint8_t>>& certHashes, const std::vector<bool>& deleted,
const std::vector<uint32_t>& installerIndices, const bool hashStrings);
-// Checks equality on explicitly set values.
-MATCHER(ProtoEq, "") {
- return MessageDifferencer::Equals(std::get<0>(arg), std::get<1>(arg));
-}
-
-// Checks equality on explicitly and implicitly set values.
-// Implicitly set values comes from fields with a default value specifier.
-MATCHER(ProtoEquiv, "") {
- return MessageDifferencer::Equivalent(std::get<0>(arg), std::get<1>(arg));
-}
-
template <typename T>
std::vector<T> concatenate(const vector<T>& a, const vector<T>& b) {
vector<T> result(a);
@@ -768,6 +806,14 @@
}
StatsdStatsReport_PulledAtomStats getPulledAtomStats(int atom_id);
+
+template <typename P>
+std::vector<uint8_t> protoToBytes(const P& proto) {
+ const size_t byteSize = proto.ByteSizeLong();
+ vector<uint8_t> bytes(byteSize);
+ proto.SerializeToArray(bytes.data(), byteSize);
+ return bytes;
+}
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/statsd/tests/statsd_test_util_test.cpp b/statsd/tests/statsd_test_util_test.cpp
index 9b28446..3db5ec3 100644
--- a/statsd/tests/statsd_test_util_test.cpp
+++ b/statsd/tests/statsd_test_util_test.cpp
@@ -14,6 +14,7 @@
#include "statsd_test_util.h"
+#include "gtest_matchers.h"
#include "src/stats_log.pb.h"
namespace android {
@@ -160,7 +161,7 @@
appName, uid, version, versionString, /* installer */ nullopt, /* certHash */ {},
/* deleted */ false, hashStrings, /* installerIndex */ nullopt);
- EXPECT_THAT(packageInfos, Pointwise(ProtoEq(), {packageInfo}));
+ EXPECT_THAT(packageInfos, Pointwise(EqPackageInfo(), {packageInfo}));
}
TEST_P(StatsdTestUtil_PackageInfo_HashStrings, TestBuildPackageInfosNonEmptyOptionalParams) {
@@ -173,7 +174,7 @@
buildPackageInfo(appName, uid, version, versionString, installer, /* certHash */ {'a'},
/* deleted */ false, hashStrings, /* installerIndex */ 3);
- EXPECT_THAT(packageInfos, Pointwise(ProtoEq(), {packageInfo}));
+ EXPECT_THAT(packageInfos, Pointwise(EqPackageInfo(), {packageInfo}));
}
#else
diff --git a/statsd/tests/storage/StorageManager_test.cpp b/statsd/tests/storage/StorageManager_test.cpp
index 96409e6..db80e77 100644
--- a/statsd/tests/storage/StorageManager_test.cpp
+++ b/statsd/tests/storage/StorageManager_test.cpp
@@ -15,12 +15,15 @@
#include "src/storage/StorageManager.h"
#include <android-base/unique_fd.h>
+#include <android-modules-utils/sdk_level.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <stdio.h>
#include "android-base/stringprintf.h"
#include "stats_log_util.h"
+#include "tests/statsd_test_util.h"
+#include "utils/DbUtils.h"
#ifdef __ANDROID__
@@ -29,6 +32,7 @@
namespace statsd {
using namespace testing;
+using android::modules::sdklevel::IsAtLeastU;
using std::make_shared;
using std::shared_ptr;
using std::vector;
@@ -278,6 +282,42 @@
EXPECT_EQ(trainInfo.experimentIds, trainInfoResult.experimentIds);
}
+TEST(StorageManagerTest, DeleteUnmodifiedOldDbFiles) {
+ if (!IsAtLeastU()) {
+ GTEST_SKIP();
+ }
+ ConfigKey key(123, 12345);
+ unique_ptr<LogEvent> event = CreateRestrictedLogEvent(/*atomTag=*/10, /*timestampNs=*/1000);
+ dbutils::createTableIfNeeded(key, /*metricId=*/1, *event);
+ EXPECT_TRUE(StorageManager::hasFile(
+ base::StringPrintf("%s/%s", STATS_RESTRICTED_DATA_DIR, "123_12345.db").c_str()));
+
+ int64_t wallClockSec = getWallClockSec() + (StatsdStats::kMaxAgeSecond + 1);
+ StorageManager::enforceDbGuardrails(STATS_RESTRICTED_DATA_DIR, wallClockSec,
+ /*maxBytes=*/INT_MAX);
+
+ EXPECT_FALSE(StorageManager::hasFile(
+ base::StringPrintf("%s/%s", STATS_RESTRICTED_DATA_DIR, "123_12345.db").c_str()));
+}
+
+TEST(StorageManagerTest, DeleteLargeDbFiles) {
+ if (!IsAtLeastU()) {
+ GTEST_SKIP();
+ }
+ ConfigKey key(123, 12345);
+ unique_ptr<LogEvent> event = CreateRestrictedLogEvent(/*atomTag=*/10, /*timestampNs=*/1000);
+ dbutils::createTableIfNeeded(key, /*metricId=*/1, *event);
+ EXPECT_TRUE(StorageManager::hasFile(
+ base::StringPrintf("%s/%s", STATS_RESTRICTED_DATA_DIR, "123_12345.db").c_str()));
+
+ StorageManager::enforceDbGuardrails(STATS_RESTRICTED_DATA_DIR,
+ /*wallClockSec=*/getWallClockSec(),
+ /*maxBytes=*/0);
+
+ EXPECT_FALSE(StorageManager::hasFile(
+ base::StringPrintf("%s/%s", STATS_RESTRICTED_DATA_DIR, "123_12345.db").c_str()));
+}
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/statsd/tests/utils/DbUtils_test.cpp b/statsd/tests/utils/DbUtils_test.cpp
new file mode 100644
index 0000000..8a9b337
--- /dev/null
+++ b/statsd/tests/utils/DbUtils_test.cpp
@@ -0,0 +1,435 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#include "utils/DbUtils.h"
+
+#include <android-modules-utils/sdk_level.h>
+#include <gtest/gtest.h>
+
+#include "android-base/stringprintf.h"
+#include "storage/StorageManager.h"
+#include "tests/statsd_test_util.h"
+
+#ifdef __ANDROID__
+
+using namespace std;
+
+namespace android {
+namespace os {
+namespace statsd {
+namespace dbutils {
+
+using android::modules::sdklevel::IsAtLeastU;
+using base::StringPrintf;
+
+namespace {
+const ConfigKey key = ConfigKey(111, 222);
+const int64_t metricId = 111;
+const int32_t tagId = 1;
+
+AStatsEvent* makeAStatsEvent(int32_t atomId, int64_t timestampNs) {
+ AStatsEvent* statsEvent = AStatsEvent_obtain();
+ AStatsEvent_setAtomId(statsEvent, atomId);
+ AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
+ return statsEvent;
+}
+
+LogEvent makeLogEvent(AStatsEvent* statsEvent) {
+ LogEvent event(/*uid=*/0, /*pid=*/0);
+ parseStatsEventToLogEvent(statsEvent, &event);
+ return event;
+}
+
+class DbUtilsTest : public ::testing::Test {
+public:
+ void SetUp() override {
+ if (!IsAtLeastU()) {
+ GTEST_SKIP();
+ }
+ }
+ void TearDown() override {
+ if (!IsAtLeastU()) {
+ GTEST_SKIP();
+ }
+ deleteDb(key);
+ }
+};
+} // Anonymous namespace.
+
+TEST_F(DbUtilsTest, TestInsertString) {
+ int64_t eventElapsedTimeNs = 10000000000;
+
+ AStatsEvent* statsEvent = makeAStatsEvent(tagId, eventElapsedTimeNs + 10);
+ AStatsEvent_writeString(statsEvent, "test_string");
+ LogEvent logEvent = makeLogEvent(statsEvent);
+ vector<LogEvent> events{logEvent};
+
+ EXPECT_TRUE(createTableIfNeeded(key, metricId, logEvent));
+ string err;
+ EXPECT_TRUE(insert(key, metricId, events, err));
+
+ std::vector<int32_t> columnTypes;
+ std::vector<string> columnNames;
+ std::vector<std::vector<std::string>> rows;
+ string zSql = "SELECT * FROM metric_111 ORDER BY elapsedTimestampNs";
+ EXPECT_TRUE(query(key, zSql, rows, columnTypes, columnNames, err));
+
+ ASSERT_EQ(rows.size(), 1);
+ EXPECT_THAT(rows[0], ElementsAre("1", to_string(eventElapsedTimeNs + 10), _, "test_string"));
+ EXPECT_THAT(columnTypes,
+ ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_TEXT));
+ EXPECT_THAT(columnNames,
+ ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+}
+
+TEST_F(DbUtilsTest, TestMaliciousString) {
+ int64_t eventElapsedTimeNs = 10000000000;
+
+ AStatsEvent* statsEvent = makeAStatsEvent(tagId, eventElapsedTimeNs + 10);
+ AStatsEvent_writeString(statsEvent, "111); DROP TABLE metric_111;--");
+ LogEvent logEvent = makeLogEvent(statsEvent);
+ vector<LogEvent> events{logEvent};
+
+ EXPECT_TRUE(createTableIfNeeded(key, metricId, logEvent));
+ string err;
+ EXPECT_TRUE(insert(key, metricId, events, err));
+
+ std::vector<int32_t> columnTypes;
+ std::vector<string> columnNames;
+ std::vector<std::vector<std::string>> rows;
+ string zSql = "SELECT * FROM metric_111 ORDER BY elapsedTimestampNs";
+ EXPECT_TRUE(query(key, zSql, rows, columnTypes, columnNames, err));
+
+ ASSERT_EQ(rows.size(), 1);
+ EXPECT_THAT(rows[0], ElementsAre("1", to_string(eventElapsedTimeNs + 10), _,
+ "111); DROP TABLE metric_111;--"));
+ EXPECT_THAT(columnTypes,
+ ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_TEXT));
+ EXPECT_THAT(columnNames,
+ ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+}
+
+TEST_F(DbUtilsTest, TestInsertStringNegativeMetricId) {
+ int64_t eventElapsedTimeNs = 10000000000;
+ int64_t metricId2 = -111;
+
+ AStatsEvent* statsEvent = makeAStatsEvent(tagId, eventElapsedTimeNs + 10);
+ AStatsEvent_writeString(statsEvent, "111");
+ LogEvent logEvent = makeLogEvent(statsEvent);
+ vector<LogEvent> events{logEvent};
+
+ EXPECT_TRUE(createTableIfNeeded(key, metricId2, logEvent));
+ string err;
+ EXPECT_TRUE(insert(key, metricId2, events, err));
+
+ std::vector<int32_t> columnTypes;
+ std::vector<string> columnNames;
+ std::vector<std::vector<std::string>> rows;
+ string zSql = "SELECT * FROM metric_n111 ORDER BY elapsedTimestampNs";
+ EXPECT_TRUE(query(key, zSql, rows, columnTypes, columnNames, err));
+
+ ASSERT_EQ(rows.size(), 1);
+ EXPECT_THAT(rows[0], ElementsAre("1", to_string(eventElapsedTimeNs + 10), _, "111"));
+ EXPECT_THAT(columnTypes,
+ ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_TEXT));
+ EXPECT_THAT(columnNames,
+ ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+}
+
+TEST_F(DbUtilsTest, TestInsertInteger) {
+ int64_t eventElapsedTimeNs = 10000000000;
+
+ AStatsEvent* statsEvent = makeAStatsEvent(tagId, eventElapsedTimeNs + 10);
+ AStatsEvent_writeInt32(statsEvent, 11);
+ AStatsEvent_writeInt64(statsEvent, 111);
+ LogEvent logEvent = makeLogEvent(statsEvent);
+ vector<LogEvent> events{logEvent};
+
+ EXPECT_TRUE(createTableIfNeeded(key, metricId, logEvent));
+ string err;
+ EXPECT_TRUE(insert(key, metricId, events, err));
+
+ std::vector<int32_t> columnTypes;
+ std::vector<string> columnNames;
+ std::vector<std::vector<std::string>> rows;
+ string zSql = "SELECT * FROM metric_111 ORDER BY elapsedTimestampNs";
+ EXPECT_TRUE(query(key, zSql, rows, columnTypes, columnNames, err));
+
+ ASSERT_EQ(rows.size(), 1);
+ EXPECT_THAT(rows[0], ElementsAre("1", to_string(eventElapsedTimeNs + 10), _, "11", "111"));
+ EXPECT_THAT(columnTypes, ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER,
+ SQLITE_INTEGER, SQLITE_INTEGER));
+ EXPECT_THAT(columnNames, ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs",
+ "field_1", "field_2"));
+}
+
+TEST_F(DbUtilsTest, TestInsertFloat) {
+ int64_t eventElapsedTimeNs = 10000000000;
+
+ AStatsEvent* statsEvent = makeAStatsEvent(tagId, eventElapsedTimeNs + 10);
+ AStatsEvent_writeFloat(statsEvent, 11.0);
+ LogEvent logEvent = makeLogEvent(statsEvent);
+ vector<LogEvent> events{logEvent};
+
+ EXPECT_TRUE(createTableIfNeeded(key, metricId, logEvent));
+ string err;
+ EXPECT_TRUE(insert(key, metricId, events, err));
+
+ std::vector<int32_t> columnTypes;
+ std::vector<string> columnNames;
+ std::vector<std::vector<std::string>> rows;
+ string zSql = "SELECT * FROM metric_111 ORDER BY elapsedTimestampNs";
+ EXPECT_TRUE(query(key, zSql, rows, columnTypes, columnNames, err));
+
+ ASSERT_EQ(rows.size(), 1);
+ EXPECT_THAT(rows[0], ElementsAre("1", to_string(eventElapsedTimeNs + 10), _, _));
+ EXPECT_FLOAT_EQ(/*field1=*/std::stof(rows[0][3]), 11.0);
+ EXPECT_THAT(columnTypes,
+ ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_FLOAT));
+ EXPECT_THAT(columnNames,
+ ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+}
+
+TEST_F(DbUtilsTest, TestInsertTwoEvents) {
+ int64_t eventElapsedTimeNs = 10000000000;
+
+ AStatsEvent* statsEvent1 = makeAStatsEvent(tagId, eventElapsedTimeNs + 10);
+ AStatsEvent_writeString(statsEvent1, "111");
+ LogEvent logEvent1 = makeLogEvent(statsEvent1);
+
+ AStatsEvent* statsEvent2 = makeAStatsEvent(tagId, eventElapsedTimeNs + 20);
+ AStatsEvent_writeString(statsEvent2, "222");
+ LogEvent logEvent2 = makeLogEvent(statsEvent2);
+
+ vector<LogEvent> events{logEvent1, logEvent2};
+
+ EXPECT_TRUE(createTableIfNeeded(key, metricId, logEvent1));
+ string err;
+ EXPECT_TRUE(insert(key, metricId, events, err));
+
+ std::vector<int32_t> columnTypes;
+ std::vector<string> columnNames;
+ std::vector<std::vector<std::string>> rows;
+ string zSql = "SELECT * FROM metric_111 ORDER BY elapsedTimestampNs";
+ EXPECT_TRUE(query(key, zSql, rows, columnTypes, columnNames, err));
+
+ ASSERT_EQ(rows.size(), 2);
+ EXPECT_THAT(rows[0], ElementsAre("1", to_string(eventElapsedTimeNs + 10), _, "111"));
+ EXPECT_THAT(rows[1], ElementsAre("1", to_string(eventElapsedTimeNs + 20), _, "222"));
+ EXPECT_THAT(columnTypes,
+ ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_TEXT));
+ EXPECT_THAT(columnNames,
+ ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+}
+
+TEST_F(DbUtilsTest, TestInsertTwoEventsEnforceTtl) {
+ int64_t eventElapsedTimeNs = 10000000000;
+ int64_t eventWallClockNs = 50000000000;
+
+ AStatsEvent* statsEvent1 = makeAStatsEvent(tagId, eventElapsedTimeNs + 10);
+ AStatsEvent_writeString(statsEvent1, "111");
+ LogEvent logEvent1 = makeLogEvent(statsEvent1);
+ logEvent1.setLogdWallClockTimestampNs(eventWallClockNs);
+
+ AStatsEvent* statsEvent2 = makeAStatsEvent(tagId, eventElapsedTimeNs + 20);
+ AStatsEvent_writeString(statsEvent2, "222");
+ LogEvent logEvent2 = makeLogEvent(statsEvent2);
+ logEvent2.setLogdWallClockTimestampNs(eventWallClockNs + eventElapsedTimeNs);
+
+ vector<LogEvent> events{logEvent1, logEvent2};
+
+ EXPECT_TRUE(createTableIfNeeded(key, metricId, logEvent1));
+ sqlite3* db = getDb(key);
+ string err;
+ EXPECT_TRUE(insert(db, metricId, events, err));
+ EXPECT_TRUE(flushTtl(db, metricId, eventWallClockNs));
+ closeDb(db);
+
+ std::vector<int32_t> columnTypes;
+ std::vector<string> columnNames;
+ std::vector<std::vector<std::string>> rows;
+ string zSql = "SELECT * FROM metric_111 ORDER BY elapsedTimestampNs";
+ EXPECT_TRUE(query(key, zSql, rows, columnTypes, columnNames, err));
+
+ ASSERT_EQ(rows.size(), 1);
+ EXPECT_THAT(rows[0], ElementsAre("1", to_string(eventElapsedTimeNs + 20), _, "222"));
+ EXPECT_THAT(columnTypes,
+ ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_TEXT));
+ EXPECT_THAT(columnNames,
+ ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+}
+
+TEST_F(DbUtilsTest, TestMaliciousQuery) {
+ int64_t eventElapsedTimeNs = 10000000000;
+
+ AStatsEvent* statsEvent = makeAStatsEvent(tagId, eventElapsedTimeNs + 10);
+ AStatsEvent_writeString(statsEvent, "111");
+ LogEvent logEvent = makeLogEvent(statsEvent);
+ vector<LogEvent> events{logEvent};
+
+ EXPECT_TRUE(createTableIfNeeded(key, metricId, logEvent));
+ string err;
+ EXPECT_TRUE(insert(key, metricId, events, err));
+
+ std::vector<int32_t> columnTypes;
+ std::vector<string> columnNames;
+ std::vector<std::vector<std::string>> rows;
+ string zSql = "DROP TABLE metric_111";
+ EXPECT_FALSE(query(key, zSql, rows, columnTypes, columnNames, err));
+ EXPECT_THAT(err, StartsWith("attempt to write a readonly database"));
+}
+
+TEST_F(DbUtilsTest, TestInsertStringIntegrityCheckPasses) {
+ int64_t eventElapsedTimeNs = 10000000000;
+
+ AStatsEvent* statsEvent = makeAStatsEvent(tagId, eventElapsedTimeNs + 10);
+ AStatsEvent_writeString(statsEvent, "111");
+ LogEvent logEvent = makeLogEvent(statsEvent);
+ vector<LogEvent> events{logEvent};
+
+ EXPECT_TRUE(createTableIfNeeded(key, metricId, logEvent));
+ string err;
+ EXPECT_TRUE(insert(key, metricId, events, err));
+ verifyIntegrityAndDeleteIfNecessary(key);
+
+ std::vector<int32_t> columnTypes;
+ std::vector<string> columnNames;
+ std::vector<std::vector<std::string>> rows;
+ string zSql = "SELECT * FROM metric_111 ORDER BY elapsedTimestampNs";
+ EXPECT_TRUE(query(key, zSql, rows, columnTypes, columnNames, err));
+ ASSERT_EQ(rows.size(), 1);
+ EXPECT_THAT(rows[0], ElementsAre("1", to_string(eventElapsedTimeNs + 10), _, "111"));
+ EXPECT_THAT(columnTypes,
+ ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_TEXT));
+ EXPECT_THAT(columnNames,
+ ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+}
+
+TEST_F(DbUtilsTest, TestInsertStringIntegrityCheckFails) {
+ int64_t eventElapsedTimeNs = 10000000000;
+
+ AStatsEvent* statsEvent = makeAStatsEvent(tagId, eventElapsedTimeNs + 10);
+ AStatsEvent_writeString(statsEvent, "111");
+ LogEvent logEvent = makeLogEvent(statsEvent);
+ vector<LogEvent> events{logEvent};
+ EXPECT_TRUE(createTableIfNeeded(key, metricId, logEvent));
+ string err;
+ EXPECT_TRUE(insert(key, metricId, events, err));
+
+ vector<string> randomData{"1232hasha14125ashfas21512sh31321"};
+ string fileName = StringPrintf("%s/%d_%lld.db", STATS_RESTRICTED_DATA_DIR, key.GetUid(),
+ (long long)key.GetId());
+ StorageManager::writeFile(fileName.c_str(), randomData.data(), randomData.size());
+ EXPECT_TRUE(StorageManager::hasFile(fileName.c_str()));
+ verifyIntegrityAndDeleteIfNecessary(key);
+ std::vector<int32_t> columnTypes;
+ std::vector<string> columnNames;
+ std::vector<std::vector<std::string>> rows;
+ string zSql = "SELECT * FROM metric_111 ORDER BY elapsedTimestampNs";
+ EXPECT_FALSE(query(key, zSql, rows, columnTypes, columnNames, err));
+ EXPECT_THAT(err, StartsWith("unable to open database file"));
+ EXPECT_FALSE(StorageManager::hasFile(fileName.c_str()));
+}
+
+TEST_F(DbUtilsTest, TestEventCompatibilityEventMatchesTable) {
+ AStatsEvent* statsEvent = makeAStatsEvent(tagId, /*eventElapsedTime=*/10000000000);
+ AStatsEvent_writeString(statsEvent, "111");
+ AStatsEvent_writeFloat(statsEvent, 111.0);
+ AStatsEvent_writeInt32(statsEvent, 23);
+ LogEvent logEvent = makeLogEvent(statsEvent);
+
+ EXPECT_TRUE(createTableIfNeeded(key, metricId, logEvent));
+
+ EXPECT_TRUE(isEventCompatible(key, metricId, logEvent));
+}
+
+TEST_F(DbUtilsTest, TestEventCompatibilityEventDoesNotMatchesTable) {
+ AStatsEvent* statsEvent = makeAStatsEvent(tagId, /*eventElapsedTime=*/10000000000);
+ AStatsEvent_writeString(statsEvent, "111");
+ AStatsEvent_writeFloat(statsEvent, 111.0);
+ AStatsEvent_writeInt32(statsEvent, 23);
+ LogEvent logEvent = makeLogEvent(statsEvent);
+
+ AStatsEvent* statsEvent2 = makeAStatsEvent(tagId, /*eventElapsedTime=*/10000000000);
+ AStatsEvent_writeString(statsEvent2, "111");
+ AStatsEvent_writeFloat(statsEvent2, 111.0);
+ AStatsEvent_writeInt32(statsEvent2, 23);
+ AStatsEvent_writeInt32(statsEvent2, 25);
+ LogEvent logEvent2 = makeLogEvent(statsEvent2);
+
+ EXPECT_TRUE(createTableIfNeeded(key, metricId, logEvent));
+
+ EXPECT_FALSE(isEventCompatible(key, metricId, logEvent2));
+}
+
+TEST_F(DbUtilsTest, TestEventCompatibilityTableNotCreated) {
+ AStatsEvent* statsEvent = makeAStatsEvent(tagId, /*eventElapsedTime=*/10000000000);
+ AStatsEvent_writeString(statsEvent, "111");
+ AStatsEvent_writeFloat(statsEvent, 111.0);
+ AStatsEvent_writeInt32(statsEvent, 23);
+ LogEvent logEvent = makeLogEvent(statsEvent);
+
+ EXPECT_TRUE(isEventCompatible(key, metricId, logEvent));
+}
+
+TEST_F(DbUtilsTest, TestUpdateDeviceInfoTable) {
+ string err;
+ updateDeviceInfoTable(key, err);
+
+ std::vector<int32_t> columnTypes;
+ std::vector<string> columnNames;
+ std::vector<std::vector<std::string>> rows;
+ string zSql = "SELECT * FROM device_info";
+ EXPECT_TRUE(query(key, zSql, rows, columnTypes, columnNames, err));
+
+ ASSERT_EQ(rows.size(), 1);
+ EXPECT_THAT(rows[0], ElementsAre(_, _, _, _, _, _, _, _, _, _));
+ EXPECT_THAT(columnTypes,
+ ElementsAre(SQLITE_INTEGER, SQLITE_TEXT, SQLITE_TEXT, SQLITE_TEXT, SQLITE_TEXT,
+ SQLITE_TEXT, SQLITE_TEXT, SQLITE_TEXT, SQLITE_TEXT, SQLITE_TEXT));
+ EXPECT_THAT(columnNames,
+ ElementsAre("sdkVersion", "model", "product", "hardware", "device", "osBuild",
+ "fingerprint", "brand", "manufacturer", "board"));
+}
+
+TEST_F(DbUtilsTest, TestUpdateDeviceInfoTableInvokeTwice) {
+ string err;
+ updateDeviceInfoTable(key, err);
+ updateDeviceInfoTable(key, err);
+
+ std::vector<int32_t> columnTypes;
+ std::vector<string> columnNames;
+ std::vector<std::vector<std::string>> rows;
+ string zSql = "SELECT * FROM device_info";
+ EXPECT_TRUE(query(key, zSql, rows, columnTypes, columnNames, err));
+
+ ASSERT_EQ(rows.size(), 1);
+ EXPECT_THAT(rows[0], ElementsAre(_, _, _, _, _, _, _, _, _, _));
+ EXPECT_THAT(columnTypes,
+ ElementsAre(SQLITE_INTEGER, SQLITE_TEXT, SQLITE_TEXT, SQLITE_TEXT, SQLITE_TEXT,
+ SQLITE_TEXT, SQLITE_TEXT, SQLITE_TEXT, SQLITE_TEXT, SQLITE_TEXT));
+ EXPECT_THAT(columnNames,
+ ElementsAre("sdkVersion", "model", "product", "hardware", "device", "osBuild",
+ "fingerprint", "brand", "manufacturer", "board"));
+}
+
+} // namespace dbutils
+} // namespace statsd
+} // namespace os
+} // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/statsd/tools/localtools/Android.bp b/statsd/tools/localtools/Android.bp
index c07cb4e..d452402 100644
--- a/statsd/tools/localtools/Android.bp
+++ b/statsd/tools/localtools/Android.bp
@@ -7,6 +7,7 @@
manifest: "localdrive_manifest.txt",
srcs: [
"src/com/android/statsd/shelltools/localdrive/*.java",
+ "src/com/android/statsd/shelltools/CustomExtensionRegistry.java",
"src/com/android/statsd/shelltools/Utils.java",
],
static_libs: [
@@ -19,6 +20,7 @@
name: "statsd_testdrive_lib",
srcs: [
"src/com/android/statsd/shelltools/testdrive/*.java",
+ "src/com/android/statsd/shelltools/CustomExtensionRegistry.java",
"src/com/android/statsd/shelltools/Utils.java",
],
static_libs: [
diff --git a/statsd/tools/localtools/src/com/android/statsd/shelltools/CustomExtensionRegistry.java b/statsd/tools/localtools/src/com/android/statsd/shelltools/CustomExtensionRegistry.java
new file mode 100644
index 0000000..def616c
--- /dev/null
+++ b/statsd/tools/localtools/src/com/android/statsd/shelltools/CustomExtensionRegistry.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2023 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.statsd.shelltools;
+
+import com.android.internal.os.ExperimentIdsProto;
+import com.android.internal.os.UidDataProto;
+import com.android.os.ActiveConfigProto;
+import com.android.os.ShellConfig;
+import com.android.os.adservices.AdservicesExtensionAtoms;
+import com.android.os.automotive.caruilib.AutomotiveCaruilibAtoms;
+import com.android.os.devicelogs.DeviceLogsAtoms;
+import com.android.os.dnd.DndAtoms;
+import com.android.os.dnd.DndExtensionAtoms;
+import com.android.os.expresslog.ExpresslogExtensionAtoms;
+import com.android.os.framework.FrameworkExtensionAtoms;
+import com.android.os.gps.GpsAtoms;
+import com.android.os.grammaticalinflection.GrammaticalInflection;
+import com.android.os.hardware.biometrics.BiometricsAtoms;
+import com.android.os.healthfitness.api.ApiExtensionAtoms;
+import com.android.os.healthfitness.ui.UiExtensionAtoms;
+import com.android.os.hotword.HotwordAtoms;
+import com.android.os.kernel.KernelAtoms;
+import com.android.os.locale.LocaleAtoms;
+import com.android.os.location.LocationAtoms;
+import com.android.os.location.LocationExtensionAtoms;
+import com.android.os.media.MediaDrmAtoms;
+import com.android.os.memorysafety.MemorysafetyExtensionAtoms;
+import com.android.os.permissioncontroller.PermissioncontrollerExtensionAtoms;
+import com.android.os.providers.mediaprovider.MediaProviderAtoms;
+import com.android.os.settings.SettingsExtensionAtoms;
+import com.android.os.statsd.ShellDataProto;
+import com.android.os.sysui.SysuiAtoms;
+import com.android.os.telecom.TelecomExtensionAtom;
+import com.android.os.telephony.SatelliteExtensionAtoms;
+import com.android.os.telephony.TelephonyExtensionAtoms;
+import com.android.os.telephony.qns.QnsExtensionAtoms;
+import com.android.os.usb.UsbAtoms;
+import com.android.os.uwb.UwbExtensionAtoms;
+import com.android.os.view.inputmethod.InputmethodAtoms;
+import com.android.os.wear.media.WearMediaAtoms;
+import com.android.os.wear.media.WearMediaExtensionAtoms;
+import com.android.os.wearpas.WearpasExtensionAtoms;
+import com.android.os.wearservices.WearservicesAtoms;
+import com.android.os.wearservices.WearservicesExtensionAtoms;
+import com.android.os.wearsysui.WearsysuiAtoms;
+import com.android.os.wifi.WifiExtensionAtoms;
+import android.os.statsd.media.MediaCodecExtensionAtoms;
+import com.android.os.credentials.CredentialsExtensionAtoms;
+
+import com.google.protobuf.ExtensionRegistry;
+
+/**
+ * CustomExtensionRegistry for local use of statsd.
+ */
+public class CustomExtensionRegistry {
+
+ public static ExtensionRegistry REGISTRY;
+
+ static {
+ /** In Java, when parsing a message containing extensions, you must provide an
+ * ExtensionRegistry which contains definitions of all of the extensions which you
+ * want the parser to recognize. This is necessary because Java's bytecode loading
+ * semantics do not provide any way for the protocol buffers library to automatically
+ * discover all extensions defined in your binary.
+ *
+ * See http://sites/protocol-buffers/user-docs/miscellaneous-howtos/extensions
+ * #Java_ExtensionRegistry_
+ */
+ REGISTRY = ExtensionRegistry.newInstance();
+ registerAllExtensions(REGISTRY);
+ REGISTRY = REGISTRY.getUnmodifiable();
+ }
+
+ /**
+ * Registers all proto2 extensions.
+ */
+ private static void registerAllExtensions(ExtensionRegistry extensionRegistry) {
+ ExperimentIdsProto.registerAllExtensions(extensionRegistry);
+ UidDataProto.registerAllExtensions(extensionRegistry);
+ ActiveConfigProto.registerAllExtensions(extensionRegistry);
+ ShellConfig.registerAllExtensions(extensionRegistry);
+ AdservicesExtensionAtoms.registerAllExtensions(extensionRegistry);
+ AutomotiveCaruilibAtoms.registerAllExtensions(extensionRegistry);
+ DeviceLogsAtoms.registerAllExtensions(extensionRegistry);
+ DndAtoms.registerAllExtensions(extensionRegistry);
+ DndExtensionAtoms.registerAllExtensions(extensionRegistry);
+ ExpresslogExtensionAtoms.registerAllExtensions(extensionRegistry);
+ FrameworkExtensionAtoms.registerAllExtensions(extensionRegistry);
+ GpsAtoms.registerAllExtensions(extensionRegistry);
+ GrammaticalInflection.registerAllExtensions(extensionRegistry);
+ BiometricsAtoms.registerAllExtensions(extensionRegistry);
+ ApiExtensionAtoms.registerAllExtensions(extensionRegistry);
+ UiExtensionAtoms.registerAllExtensions(extensionRegistry);
+ HotwordAtoms.registerAllExtensions(extensionRegistry);
+ KernelAtoms.registerAllExtensions(extensionRegistry);
+ LocaleAtoms.registerAllExtensions(extensionRegistry);
+ LocationAtoms.registerAllExtensions(extensionRegistry);
+ LocationExtensionAtoms.registerAllExtensions(extensionRegistry);
+ MediaDrmAtoms.registerAllExtensions(extensionRegistry);
+ MemorysafetyExtensionAtoms.registerAllExtensions(extensionRegistry);
+ PermissioncontrollerExtensionAtoms.registerAllExtensions(extensionRegistry);
+ MediaProviderAtoms.registerAllExtensions(extensionRegistry);
+ SettingsExtensionAtoms.registerAllExtensions(extensionRegistry);
+ ShellDataProto.registerAllExtensions(extensionRegistry);
+ SysuiAtoms.registerAllExtensions(extensionRegistry);
+ TelecomExtensionAtom.registerAllExtensions(extensionRegistry);
+ SatelliteExtensionAtoms.registerAllExtensions(extensionRegistry);
+ TelephonyExtensionAtoms.registerAllExtensions(extensionRegistry);
+ QnsExtensionAtoms.registerAllExtensions(extensionRegistry);
+ UsbAtoms.registerAllExtensions(extensionRegistry);
+ UwbExtensionAtoms.registerAllExtensions(extensionRegistry);
+ InputmethodAtoms.registerAllExtensions(extensionRegistry);
+ WearMediaAtoms.registerAllExtensions(extensionRegistry);
+ WearMediaExtensionAtoms.registerAllExtensions(extensionRegistry);
+ WearpasExtensionAtoms.registerAllExtensions(extensionRegistry);
+ WearservicesAtoms.registerAllExtensions(extensionRegistry);
+ WearservicesExtensionAtoms.registerAllExtensions(extensionRegistry);
+ WearsysuiAtoms.registerAllExtensions(extensionRegistry);
+ WifiExtensionAtoms.registerAllExtensions(extensionRegistry);
+ MediaCodecExtensionAtoms.registerAllExtensions(extensionRegistry);
+ CredentialsExtensionAtoms.registerAllExtensions(extensionRegistry);
+ }
+}
diff --git a/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java b/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java
index 65fdb07..3da7222 100644
--- a/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java
+++ b/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java
@@ -21,6 +21,7 @@
import com.android.os.StatsLog.StatsLogReport;
import com.google.common.io.Files;
+import com.google.protobuf.ExtensionRegistry;
import java.io.BufferedReader;
import java.io.File;
@@ -40,6 +41,8 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import com.android.statsd.shelltools.CustomExtensionRegistry;
+
/**
* Utilities for local use of statsd.
*/
@@ -112,7 +115,8 @@
"--include_current_bucket",
"--proto");
ConfigMetricsReportList reportList =
- ConfigMetricsReportList.parseFrom(new FileInputStream(outputFile));
+ ConfigMetricsReportList.parseFrom(new FileInputStream(outputFile),
+ CustomExtensionRegistry.REGISTRY);
return reportList;
} catch (com.google.protobuf.InvalidProtocolBufferException e) {
logger.severe("Failed to fetch and parse the statsd output report. "
diff --git a/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java b/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
index 2e2be66..5be447c 100644
--- a/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
+++ b/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
@@ -30,6 +30,7 @@
import com.android.os.StatsLog.ConfigMetricsReport;
import com.android.os.StatsLog.ConfigMetricsReportList;
import com.android.os.StatsLog.StatsLogReport;
+import com.android.os.telephony.qns.QnsExtensionAtoms;
import com.android.statsd.shelltools.Utils;
import com.google.common.annotations.VisibleForTesting;
@@ -95,6 +96,8 @@
"AID_NFC",
"AID_SECURE_ELEMENT",
"com.google.android.wearable.media.routing",
+ "com.google.android.healthconnect.controller",
+ "com.android.telephony.qns",
};
private static final String[] DEFAULT_PULL_SOURCES = {
"AID_KEYSTORE", "AID_RADIO", "AID_SYSTEM",
@@ -594,6 +597,21 @@
PullAtomPackages.newBuilder()
.setAtomId(Atom.LAUNCHER_LAYOUT_SNAPSHOT_FIELD_NUMBER)
.addPackages("com.google.android.apps.nexuslauncher"))
+ .addPullAtomPackages(
+ PullAtomPackages.newBuilder()
+ .setAtomId(QnsExtensionAtoms
+ .QNS_RAT_PREFERENCE_MISMATCH_INFO_FIELD_NUMBER)
+ .addPackages("com.android.telephony.qns"))
+ .addPullAtomPackages(
+ PullAtomPackages.newBuilder()
+ .setAtomId(QnsExtensionAtoms
+ .QNS_HANDOVER_TIME_MILLIS_FIELD_NUMBER)
+ .addPackages("com.android.telephony.qns"))
+ .addPullAtomPackages(
+ PullAtomPackages.newBuilder()
+ .setAtomId(QnsExtensionAtoms
+ .QNS_HANDOVER_PINGPONG_FIELD_NUMBER)
+ .addPackages("com.android.telephony.qns"))
.setHashStringsInMetricReport(false);
}
}
diff --git a/tests/AndroidTest.xml b/tests/AndroidTest.xml
index be8a12a..e33c2a0 100644
--- a/tests/AndroidTest.xml
+++ b/tests/AndroidTest.xml
@@ -28,4 +28,12 @@
<object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
<option name="mainline-module-package-name" value="com.google.android.os.statsd" />
</object>
+
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- avoid restarting device after modifying settings -->
+ <option name="force-skip-system-props" value="true" />
+
+ <option name="set-global-setting" key="verifier_verify_adb_installs" value="0" />
+ <option name="restore-settings" value="true" />
+ </target_preparer>
</configuration>
diff --git a/tests/apps/statsdapp/src/com/android/server/cts/device/statsd/RestrictedPermissionTests.java b/tests/apps/statsdapp/src/com/android/server/cts/device/statsd/RestrictedPermissionTests.java
new file mode 100644
index 0000000..7896e5b
--- /dev/null
+++ b/tests/apps/statsdapp/src/com/android/server/cts/device/statsd/RestrictedPermissionTests.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 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.server.cts.device.statsd;
+
+import static org.junit.Assert.fail;
+
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.UserHandle;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.CddTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+/**
+ * Build, install and run tests with following command:
+ * atest CtsStatsdHostTestCases
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RestrictedPermissionTests {
+
+ /**
+ * Verify that the {@link android.Manifest.permission#READ_RESTRICTED_STATS}
+ * permission is only held by at most one package.
+ */
+ @Test
+ @CddTest(requirements={"9.8.17/C-0-1"})
+ public void testReadRestrictedStatsPermission() throws Exception {
+ final PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
+ final List<PackageInfo> holding = pm.getPackagesHoldingPermissions(new String[]{
+ android.Manifest.permission.READ_RESTRICTED_STATS
+ }, PackageManager.MATCH_ALL);
+
+ int count = 0;
+ String pkgNames = "";
+ for (PackageInfo pkg : holding) {
+ int uid = pm.getApplicationInfo(pkg.packageName, 0).uid;
+ if (UserHandle.isApp(uid)) {
+ pkgNames += pkg.packageName + "\n";
+ count++;
+ }
+ }
+ if (count > 1) {
+ fail("Only one app may hold the READ_RESTRICTED_STATS permission; found packages: \n"
+ + pkgNames);
+ }
+ }
+}
diff --git a/tests/src/android/cts/statsd/atom/AtomTestCase.java b/tests/src/android/cts/statsd/atom/AtomTestCase.java
index fc37543..0e6c9c6 100644
--- a/tests/src/android/cts/statsd/atom/AtomTestCase.java
+++ b/tests/src/android/cts/statsd/atom/AtomTestCase.java
@@ -304,14 +304,16 @@
protected void uploadConfig(StatsdConfig config) throws Exception {
LogUtil.CLog.d("Uploading the following config:\n" + config.toString());
File configFile = File.createTempFile("statsdconfig", ".config");
- configFile.deleteOnExit();
- Files.write(config.toByteArray(), configFile);
- String remotePath = "/data/local/tmp/" + configFile.getName();
- getDevice().pushFile(configFile, remotePath);
- getDevice().executeShellCommand(
- String.join(" ", "cat", remotePath, "|", UPDATE_CONFIG_CMD,
- String.valueOf(SHELL_UID), String.valueOf(CONFIG_ID)));
- getDevice().executeShellCommand("rm " + remotePath);
+ try {
+ Files.write(config.toByteArray(), configFile);
+ String remotePath = "/data/local/tmp/" + configFile.getName();
+ getDevice().pushFile(configFile, remotePath);
+ getDevice().executeShellCommand(String.join(" ", "cat", remotePath, "|",
+ UPDATE_CONFIG_CMD, String.valueOf(SHELL_UID), String.valueOf(CONFIG_ID)));
+ getDevice().executeShellCommand("rm " + remotePath);
+ } finally {
+ configFile.delete();
+ }
}
protected void removeConfig(long configId) throws Exception {
diff --git a/tests/src/android/cts/statsd/restricted/ReadRestrictedStatsPermissionTest.java b/tests/src/android/cts/statsd/restricted/ReadRestrictedStatsPermissionTest.java
new file mode 100644
index 0000000..5191f39
--- /dev/null
+++ b/tests/src/android/cts/statsd/restricted/ReadRestrictedStatsPermissionTest.java
@@ -0,0 +1,14 @@
+package android.cts.statsd.restricted;
+
+import android.cts.statsd.atom.DeviceAtomTestCase;
+
+/**
+ * Tests Suite for restricted stats permissions.
+ */
+public class ReadRestrictedStatsPermissionTest extends DeviceAtomTestCase {
+
+ public void testReadRestrictedStatsPermission() throws Exception {
+ runDeviceTests(DEVICE_SIDE_TEST_PACKAGE,
+ ".RestrictedPermissionTests", "testReadRestrictedStatsPermission");
+ }
+}