summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--statsd/src/guardrail/StatsdStats.cpp105
-rw-r--r--statsd/src/guardrail/StatsdStats.h34
-rw-r--r--statsd/src/stats_log.proto26
-rw-r--r--statsd/tests/guardrail/StatsdStats_test.cpp80
-rw-r--r--tests/Android.bp2
-rw-r--r--tests/AndroidTest.xml6
-rw-r--r--tests/apps/atomstormapp/Android.bp64
-rw-r--r--tests/apps/atomstormapp/AndroidManifest.xml29
-rw-r--r--tests/apps/atomstormapp/AndroidManifest2.xml29
-rw-r--r--tests/apps/atomstormapp/src/com/android/statsd/app/atomstorm/StatsdAtomStorm.java48
-rw-r--r--tests/src/android/cts/statsd/metadata/MetadataTests.java49
11 files changed, 445 insertions, 27 deletions
diff --git a/statsd/src/guardrail/StatsdStats.cpp b/statsd/src/guardrail/StatsdStats.cpp
index d659de7d..939d39fa 100644
--- a/statsd/src/guardrail/StatsdStats.cpp
+++ b/statsd/src/guardrail/StatsdStats.cpp
@@ -61,6 +61,7 @@ const int FIELD_ID_RESTRICTED_METRIC_QUERY_STATS = 20;
const int FIELD_ID_SHARD_OFFSET = 21;
const int FIELD_ID_STATSD_STATS_ID = 22;
const int FIELD_ID_SUBSCRIPTION_STATS = 23;
+const int FIELD_ID_SOCKET_LOSS_STATS = 24;
const int FIELD_ID_RESTRICTED_METRIC_QUERY_STATS_CALLING_UID = 1;
const int FIELD_ID_RESTRICTED_METRIC_QUERY_STATS_CONFIG_ID = 2;
@@ -158,6 +159,25 @@ const int FIELD_ID_UID_MAP_DELETED_APPS = 4;
const int FIELD_ID_ACTIVATION_BROADCAST_GUARDRAIL_UID = 1;
const int FIELD_ID_ACTIVATION_BROADCAST_GUARDRAIL_TIME = 2;
+// SocketLossStats
+const int FIELD_ID_SOCKET_LOSS_STATS_PER_UID = 1;
+const int FIELD_ID_SOCKET_LOSS_STATS_OVERFLOW_COUNTERS = 2;
+
+// for LossStatsOverflowCounters proto
+const int FIELD_ID_SOCKET_LOSS_STATS_OVERFLOW_COUNTERS_UID = 1;
+const int FIELD_ID_SOCKET_LOSS_STATS_OVERFLOW_COUNTERS_COUNT = 2;
+
+// for LossStatsPerUid proto
+const int FIELD_ID_SOCKET_LOSS_STATS_UID = 1;
+const int FIELD_ID_SOCKET_LOSS_STATS_FIRST_TIMESTAMP_NANOS = 2;
+const int FIELD_ID_SOCKET_LOSS_STATS_LAST_TIMESTAMP_NANOS = 3;
+const int FIELD_ID_SOCKET_LOSS_ATOM_ID_LOSS_STATS = 4;
+
+// for AtomIdLossStats proto
+const int FIELD_ID_ATOM_ID_LOSS_STATS_ATOM_ID = 1;
+const int FIELD_ID_ATOM_ID_LOSS_STATS_ERROR = 2;
+const int FIELD_ID_ATOM_ID_LOSS_STATS_COUNT = 3;
+
// for RestrictedMetricStats proto
const int FIELD_ID_RESTRICTED_STATS_METRIC_ID = 1;
const int FIELD_ID_RESTRICTED_STATS_INSERT_ERROR = 2;
@@ -353,25 +373,26 @@ void StatsdStats::noteAtomSocketLoss(const SocketLossInfo& lossInfo) {
ALOGW("SocketLossEvent detected: %lld (firstLossTsNanos), %lld (lastLossTsNanos)",
(long long)lossInfo.firstLossTsNanos, (long long)lossInfo.lastLossTsNanos);
lock_guard<std::mutex> lock(mLock);
+
+ if (mSocketLossStats.size() == kMaxSocketLossStatsSize) {
+ // erase the oldest record
+ mSocketLossStats.pop_front();
+ }
+ mSocketLossStats.emplace_back(lossInfo.uid, lossInfo.firstLossTsNanos,
+ lossInfo.lastLossTsNanos);
for (size_t i = 0; i < lossInfo.atomIds.size(); i++) {
ALOGW("For uid %d atom %d was lost %d times with error %d", lossInfo.uid,
lossInfo.atomIds[i], lossInfo.counts[i], lossInfo.errors[i]);
- const auto lossInfoKey =
- std::make_tuple(lossInfo.uid, lossInfo.atomIds[i], lossInfo.errors[i]);
- auto counterIt = mSocketLossStats.find(lossInfoKey);
- if (counterIt != mSocketLossStats.end()) {
- counterIt->second += lossInfo.counts[i];
- } else if (mSocketLossStats.size() < kMaxSocketLossStatsSize) {
- mSocketLossStats[lossInfoKey] = lossInfo.counts[i];
- }
+ mSocketLossStats.back().mLossCountPerErrorAtomId.emplace_back(
+ lossInfo.atomIds[i], lossInfo.errors[i], lossInfo.counts[i]);
}
if (lossInfo.overflowCounter > 0) {
- auto overflowPerUid = mSocketLossStatsOverflowCounter.find(lossInfo.uid);
- if (overflowPerUid != mSocketLossStatsOverflowCounter.end()) {
+ auto overflowPerUid = mSocketLossStatsOverflowCounters.find(lossInfo.uid);
+ if (overflowPerUid != mSocketLossStatsOverflowCounters.end()) {
overflowPerUid->second += lossInfo.overflowCounter;
- } else if (mSocketLossStatsOverflowCounter.size() < kMaxSocketLossStatsSize) {
- mSocketLossStatsOverflowCounter[lossInfo.uid] = lossInfo.overflowCounter;
+ } else if (mSocketLossStatsOverflowCounters.size() < kMaxSocketLossStatsSize) {
+ mSocketLossStatsOverflowCounters[lossInfo.uid] = lossInfo.overflowCounter;
}
}
}
@@ -1060,7 +1081,7 @@ void StatsdStats::resetInternalLocked() {
mActivationBroadcastGuardrailStats.clear();
mPushedAtomErrorStats.clear();
mSocketLossStats.clear();
- mSocketLossStatsOverflowCounter.clear();
+ mSocketLossStatsOverflowCounters.clear();
mPushedAtomDropsStats.clear();
mRestrictedMetricQueryStats.clear();
mSubscriptionPullThreadWakeupCount = 0;
@@ -1387,17 +1408,18 @@ void StatsdStats::dumpStats(int out) const {
if (mSocketLossStats.size()) {
dprintf(out, "********SocketLossStats stats***********\n");
for (const auto& loss : mSocketLossStats) {
- const int32_t uid = std::get<0>(loss.first);
- const int32_t tag = std::get<1>(loss.first);
- const int32_t error = std::get<2>(loss.first);
- dprintf(out, "Socket loss: %d (uid), %d (tag) %d (error), %d (count)\n", uid, tag,
- error, loss.second);
+ dprintf(out, "Socket loss: %d (uid), first loss at %lld, last loss at %lld\n",
+ loss.mUid, (long long)loss.mFirstLossTsNanos, (long long)loss.mLastLossTsNanos);
+ for (const auto& counterInfo : loss.mLossCountPerErrorAtomId) {
+ dprintf(out, "\t\t %d (atomId) %d (error), %d (count)\n", counterInfo.mAtomId,
+ counterInfo.mError, counterInfo.mCount);
+ }
}
}
- if (mSocketLossStatsOverflowCounter.size()) {
- dprintf(out, "********mSocketLossStatsOverflowCounter stats***********\n");
- for (const auto& overflow : mSocketLossStatsOverflowCounter) {
+ if (mSocketLossStatsOverflowCounters.size()) {
+ dprintf(out, "********mSocketLossStatsOverflowCounters stats***********\n");
+ for (const auto& overflow : mSocketLossStatsOverflowCounters) {
dprintf(out, "Socket loss overflow for %d uid is %d times\n", overflow.first,
overflow.second);
}
@@ -1843,6 +1865,47 @@ void StatsdStats::dumpStats(std::vector<uint8_t>* output, bool reset) {
mSubscriptionPullThreadWakeupCount, &proto);
proto.end(token);
+ // libstatssocket specific stats
+
+ const uint64_t socketLossStatsToken =
+ proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_SOCKET_LOSS_STATS);
+
+ // socket loss stats info per uid/error/atom id counter
+ for (const auto& perUidLossInfo : mSocketLossStats) {
+ uint64_t token = proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_SOCKET_LOSS_STATS_PER_UID |
+ FIELD_COUNT_REPEATED);
+ proto.write(FIELD_TYPE_INT32 | FIELD_ID_SOCKET_LOSS_STATS_UID, perUidLossInfo.mUid);
+ proto.write(FIELD_TYPE_INT32 | FIELD_ID_SOCKET_LOSS_STATS_FIRST_TIMESTAMP_NANOS,
+ perUidLossInfo.mFirstLossTsNanos);
+ proto.write(FIELD_TYPE_INT32 | FIELD_ID_SOCKET_LOSS_STATS_LAST_TIMESTAMP_NANOS,
+ perUidLossInfo.mLastLossTsNanos);
+ for (const auto& counterInfo : perUidLossInfo.mLossCountPerErrorAtomId) {
+ uint64_t token =
+ proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_SOCKET_LOSS_ATOM_ID_LOSS_STATS |
+ FIELD_COUNT_REPEATED);
+ proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_ID_LOSS_STATS_ATOM_ID,
+ counterInfo.mAtomId);
+ proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_ID_LOSS_STATS_ERROR, counterInfo.mError);
+ proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_ID_LOSS_STATS_COUNT, counterInfo.mCount);
+ proto.end(token);
+ }
+ proto.end(token);
+ }
+
+ // socket loss stats overflow counters
+ for (const auto& overflowInfo : mSocketLossStatsOverflowCounters) {
+ uint64_t token =
+ proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_SOCKET_LOSS_STATS_OVERFLOW_COUNTERS |
+ FIELD_COUNT_REPEATED);
+ proto.write(FIELD_TYPE_INT32 | FIELD_ID_SOCKET_LOSS_STATS_OVERFLOW_COUNTERS_UID,
+ overflowInfo.first);
+ proto.write(FIELD_TYPE_INT32 | FIELD_ID_SOCKET_LOSS_STATS_OVERFLOW_COUNTERS_COUNT,
+ overflowInfo.second);
+ proto.end(token);
+ }
+
+ proto.end(socketLossStatsToken);
+
output->clear();
proto.serializeToVector(output);
diff --git a/statsd/src/guardrail/StatsdStats.h b/statsd/src/guardrail/StatsdStats.h
index adf9c49b..c7ca8d0c 100644
--- a/statsd/src/guardrail/StatsdStats.h
+++ b/statsd/src/guardrail/StatsdStats.h
@@ -273,7 +273,7 @@ public:
static const int kMaxPushedAtomErrorStatsSize = 100;
// Maximum number of socket loss stats to track.
- static const int kMaxSocketLossStatsSize = 100;
+ static const int kMaxSocketLossStatsSize = 50;
// 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.
@@ -855,15 +855,35 @@ private:
std::map<int, int> mPushedAtomErrorStats;
// Stores the number of times a pushed atom was lost due to socket error.
- // Represents counter per uid per tag per error
- // The max size of this map is kMaxSocketLossStatsSize.
- std::map<std::tuple<int32_t, int32_t, int32_t>, int32_t> mSocketLossStats;
+ // Represents counter per uid per tag per error with indication when the loss event was observed
+ // first & last time.
+ struct SocketLossStats {
+ SocketLossStats(int32_t uid, int64_t firstLossTsNanos, int64_t lastLossTsNanos)
+ : mUid(uid), mFirstLossTsNanos(firstLossTsNanos), mLastLossTsNanos(lastLossTsNanos) {
+ }
+
+ int32_t mUid;
+ int64_t mFirstLossTsNanos;
+ int64_t mLastLossTsNanos;
+ // atom loss count per error, atom id
+ struct AtomLossInfo {
+ AtomLossInfo(int32_t atomId, int32_t error, int32_t count)
+ : mAtomId(atomId), mError(error), mCount(count) {
+ }
+ int mAtomId;
+ int mError;
+ int mCount;
+ };
+ std::vector<AtomLossInfo> mLossCountPerErrorAtomId;
+ };
+ // The max size of this list is kMaxSocketLossStatsSize.
+ std::list<SocketLossStats> mSocketLossStats;
- // Stores the number of times a pushed atom loss info was dropped to be added into stats
+ // Stores the number of times a pushed atom loss info was dropped from the stats
// on libstatssocket side due to guardrail hit.
// Represents counter per uid.
// The max size of this map is kMaxSocketLossStatsSize.
- std::map<int32_t, int32_t> mSocketLossStatsOverflowCounter;
+ std::map<int32_t, int32_t> mSocketLossStatsOverflowCounters;
// Maps metric ID to its stats. The size is capped by the number of metrics.
std::map<int64_t, AtomMetricStats> mAtomMetricStats;
@@ -1019,6 +1039,8 @@ private:
FRIEND_TEST(StatsdStatsTest, TestSubscriptionPullThreadWakeup);
FRIEND_TEST(StatsdStatsTest, TestSubscriptionStartedMaxActiveSubscriptions);
FRIEND_TEST(StatsdStatsTest, TestSubscriptionStartedRemoveFinishedSubscription);
+ FRIEND_TEST(StatsdStatsTest, TestSocketLossStats);
+ FRIEND_TEST(StatsdStatsTest, TestSocketLossStatsOverflowCounter);
FRIEND_TEST(StatsLogProcessorTest, InvalidConfigRemoved);
};
diff --git a/statsd/src/stats_log.proto b/statsd/src/stats_log.proto
index 972f760e..b0955467 100644
--- a/statsd/src/stats_log.proto
+++ b/statsd/src/stats_log.proto
@@ -656,6 +656,32 @@ message StatsdStatsReport {
}
optional SubscriptionStats subscription_stats = 23;
+
+ message SocketLossStats {
+ message LossStatsPerUid {
+ message AtomIdLossStats {
+ optional int32 atom_id = 1;
+ optional int32 error = 2;
+ optional int32 count = 3;
+ }
+ optional int32 uid = 1;
+ optional int64 first_timestamp_nanos = 2;
+ optional int64 last_timestamp_nanos = 3;
+ repeated AtomIdLossStats atom_id_loss_stats = 4;
+ }
+
+ repeated LossStatsPerUid loss_stats_per_uid = 1;
+
+ // tracks overflow of stats container on the libstatssocket side per logging application
+ message LossStatsOverflowCounters {
+ optional int32 uid = 1;
+ optional int32 count = 2;
+ }
+
+ repeated LossStatsOverflowCounters loss_stats_overflow_counters = 2;
+ }
+
+ optional SocketLossStats socket_loss_stats = 24;
}
message AlertTriggerDetails {
diff --git a/statsd/tests/guardrail/StatsdStats_test.cpp b/statsd/tests/guardrail/StatsdStats_test.cpp
index 7835391e..6e61b620 100644
--- a/statsd/tests/guardrail/StatsdStats_test.cpp
+++ b/statsd/tests/guardrail/StatsdStats_test.cpp
@@ -1010,6 +1010,86 @@ TEST(StatsdStatsTest, TestEnforceDimensionKeySizeLimit) {
StatsdStats::kDimensionKeySizeHardLimitMax);
}
+TEST(StatsdStatsTest, TestSocketLossStats) {
+ StatsdStats stats;
+
+ const int maxLossEvents = StatsdStats::kMaxSocketLossStatsSize;
+
+ // Note maxLossEvents + 1
+ for (int eventId = 0; eventId <= maxLossEvents; eventId++) {
+ SocketLossInfo info;
+
+ info.uid = eventId;
+ info.firstLossTsNanos = 10 * eventId;
+ info.lastLossTsNanos = 10 * eventId + 1;
+
+ info.atomIds.push_back(eventId * 10);
+ info.errors.push_back(eventId * 20);
+ info.counts.push_back(eventId * 30);
+
+ stats.noteAtomSocketLoss(info);
+ }
+
+ StatsdStatsReport report = getStatsdStatsReport(stats, /* reset stats */ false);
+
+ auto socketLossStats = report.socket_loss_stats();
+ ASSERT_EQ(socketLossStats.loss_stats_per_uid().size(), maxLossEvents);
+
+ for (int i = 0; i < socketLossStats.loss_stats_per_uid().size(); i++) {
+ const auto& info = report.socket_loss_stats().loss_stats_per_uid(i);
+
+ // due to the very first one with id 0 is popped out from the list ids (index) start from 1
+ const int index = i + 1;
+
+ ASSERT_EQ(info.uid(), index);
+ ASSERT_EQ(info.first_timestamp_nanos(), 10 * index);
+ ASSERT_EQ(info.last_timestamp_nanos(), 10 * index + 1);
+
+ ASSERT_EQ(info.atom_id_loss_stats().size(), 1);
+
+ ASSERT_EQ(info.atom_id_loss_stats(0).atom_id(), index * 10);
+ ASSERT_EQ(info.atom_id_loss_stats(0).error(), index * 20);
+ ASSERT_EQ(info.atom_id_loss_stats(0).count(), index * 30);
+ }
+}
+
+TEST(StatsdStatsTest, TestSocketLossStatsOverflowCounter) {
+ StatsdStats stats;
+
+ const int uidsCount = 5;
+ const int lossEventCount = 5;
+
+ for (int uid = 0; uid < uidsCount; uid++) {
+ for (int eventId = 0; eventId < lossEventCount; eventId++) {
+ SocketLossInfo info;
+
+ info.uid = uid;
+ info.firstLossTsNanos = 10 * eventId;
+ info.lastLossTsNanos = 10 * eventId + 1;
+ // the counter value will be accumulated
+ info.overflowCounter = 1;
+
+ info.atomIds.push_back(eventId * 10);
+ info.errors.push_back(eventId * 20);
+ info.counts.push_back(eventId * 30);
+
+ stats.noteAtomSocketLoss(info);
+ }
+ }
+ StatsdStatsReport report = getStatsdStatsReport(stats, /* reset stats */ false);
+
+ auto socketLossStatsOverflowCounters =
+ report.socket_loss_stats().loss_stats_overflow_counters();
+ ASSERT_EQ(socketLossStatsOverflowCounters.size(), uidsCount);
+
+ for (int i = 0; i < socketLossStatsOverflowCounters.size(); i++) {
+ const auto& counters = report.socket_loss_stats().loss_stats_overflow_counters(i);
+
+ ASSERT_EQ(counters.uid(), i);
+ ASSERT_EQ(counters.count(), lossEventCount);
+ }
+}
+
TEST_P(StatsdStatsTest_GetAtomDimensionKeySizeLimit_InMap, TestGetAtomDimensionKeySizeLimits) {
const auto& [atomId, defaultHardLimit] = GetParam();
EXPECT_EQ(StatsdStats::getAtomDimensionKeySizeLimits(atomId, defaultHardLimit),
diff --git a/tests/Android.bp b/tests/Android.bp
index 0ec80caf..e67a76c2 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -46,5 +46,7 @@ java_test_host {
data: [
"**/*.pbtxt",
":CtsStatsdApp",
+ ":StatsdAtomStormApp",
+ ":StatsdAtomStormApp2",
],
}
diff --git a/tests/AndroidTest.xml b/tests/AndroidTest.xml
index e33c2a0e..781813a5 100644
--- a/tests/AndroidTest.xml
+++ b/tests/AndroidTest.xml
@@ -36,4 +36,10 @@
<option name="set-global-setting" key="verifier_verify_adb_installs" value="0" />
<option name="restore-settings" value="true" />
</target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="StatsdAtomStormApp.apk" />
+ <option name="test-file-name" value="StatsdAtomStormApp2.apk" />
+ </target_preparer>
</configuration>
diff --git a/tests/apps/atomstormapp/Android.bp b/tests/apps/atomstormapp/Android.bp
new file mode 100644
index 00000000..86e5953f
--- /dev/null
+++ b/tests/apps/atomstormapp/Android.bp
@@ -0,0 +1,64 @@
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "StatsdAtomStormApp",
+ defaults: ["cts_defaults"],
+ platform_apis: true,
+ min_sdk_version: "24",
+ srcs: [
+ "src/**/*.java",
+ ],
+ libs: [
+ "android.test.runner",
+ "junit",
+ "org.apache.http.legacy",
+ ],
+ privileged: true,
+ static_libs: [
+ "ctstestrunner-axt",
+ "compatibility-device-util-axt",
+ "androidx.legacy_legacy-support-v4",
+ "androidx.test.rules",
+ ],
+ compile_multilib: "both",
+}
+
+android_test_helper_app {
+ name: "StatsdAtomStormApp2",
+ manifest: "AndroidManifest2.xml",
+ defaults: ["cts_defaults"],
+ platform_apis: true,
+ min_sdk_version: "24",
+ srcs: [
+ "src/**/*.java",
+ ],
+ libs: [
+ "android.test.runner",
+ "junit",
+ "org.apache.http.legacy",
+ ],
+ privileged: true,
+ static_libs: [
+ "ctstestrunner-axt",
+ "compatibility-device-util-axt",
+ "androidx.legacy_legacy-support-v4",
+ "androidx.test.rules",
+ ],
+ compile_multilib: "both",
+}
diff --git a/tests/apps/atomstormapp/AndroidManifest.xml b/tests/apps/atomstormapp/AndroidManifest.xml
new file mode 100644
index 00000000..2993928f
--- /dev/null
+++ b/tests/apps/atomstormapp/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.statsd.app.atomstorm">
+ <!-- Using gms shared uid is necessary for the receivers to work. -->
+
+ <!-- GTS started at 14; do not change minSdkVersion. -->
+ <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="28"/>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.statsd.app.atomstorm"
+ android:label="CTS tests of android.os.statsd stats collection">
+ </instrumentation>
+</manifest>
diff --git a/tests/apps/atomstormapp/AndroidManifest2.xml b/tests/apps/atomstormapp/AndroidManifest2.xml
new file mode 100644
index 00000000..310397fd
--- /dev/null
+++ b/tests/apps/atomstormapp/AndroidManifest2.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.statsd.app.atomstorm.copy">
+ <!-- Using gms shared uid is necessary for the receivers to work. -->
+
+ <!-- GTS started at 14; do not change minSdkVersion. -->
+ <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="28"/>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.statsd.app.atomstorm.copy"
+ android:label="CTS tests of android.os.statsd stats collection">
+ </instrumentation>
+</manifest>
diff --git a/tests/apps/atomstormapp/src/com/android/statsd/app/atomstorm/StatsdAtomStorm.java b/tests/apps/atomstormapp/src/com/android/statsd/app/atomstorm/StatsdAtomStorm.java
new file mode 100644
index 00000000..ce4ced99
--- /dev/null
+++ b/tests/apps/atomstormapp/src/com/android/statsd/app/atomstorm/StatsdAtomStorm.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 Google LLC.
+ *
+ * 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.app.atomstorm;
+
+import android.util.StatsLog;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class StatsdAtomStorm {
+ private static final int EventStormAtomsCount = 100000;
+
+ /** Tests socket overflow. */
+ @Test
+ public void testLogManyAtomsBackToBack() throws Exception {
+ // logging back to back many atoms to force socket overflow
+ performAtomStorm(EventStormAtomsCount);
+ // make pause to resolve socket overflow
+ Thread.sleep(100);
+ // give chance for libstatssocket send loss stats to statsd triggering successful logging
+ performAtomStorm(1);
+ }
+
+ private void performAtomStorm(int iterations) {
+ // single atom logging takes ~2us excluding JNI interactions
+ for (int i = 0; i < iterations; i++) {
+ StatsLog.logStart(i);
+ StatsLog.logStop(i);
+ }
+ }
+}
diff --git a/tests/src/android/cts/statsd/metadata/MetadataTests.java b/tests/src/android/cts/statsd/metadata/MetadataTests.java
index 6d834043..f912e81e 100644
--- a/tests/src/android/cts/statsd/metadata/MetadataTests.java
+++ b/tests/src/android/cts/statsd/metadata/MetadataTests.java
@@ -24,7 +24,11 @@ import com.android.os.AtomsProto.Atom;
import com.android.os.StatsLog.StatsdStatsReport;
import com.android.os.StatsLog.StatsdStatsReport.ConfigStats;
import com.android.os.StatsLog.StatsdStatsReport.LogLossStats;
+import com.android.os.StatsLog.StatsdStatsReport.SocketLossStats;
+import com.android.os.StatsLog.StatsdStatsReport.SocketLossStats.LossStatsPerUid;
+import com.android.os.StatsLog.StatsdStatsReport.SocketLossStats.LossStatsPerUid.AtomIdLossStats;
import com.android.tradefed.log.LogUtil;
+import java.util.HashSet;
/**
* Statsd Metadata tests.
@@ -155,6 +159,51 @@ public class MetadataTests extends MetadataTestCase {
assertThat(detectedLossEventForAppBreadcrumbAtom).isTrue();
assertThat(detectedLossEventForSystemServer).isFalse();
+
+ boolean detectedLossEventForAppBreadcrumbAtomViaSocketLossStats = false;
+ for (LossStatsPerUid lossStats : report.getSocketLossStats().getLossStatsPerUidList()) {
+ for (AtomIdLossStats atomLossStats : lossStats.getAtomIdLossStatsList()) {
+ if (atomLossStats.getAtomId() == Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER) {
+ detectedLossEventForAppBreadcrumbAtomViaSocketLossStats = true;
+ }
+ }
+ }
+
+ assertThat(detectedLossEventForAppBreadcrumbAtomViaSocketLossStats).isTrue();
+ }
+ }
+
+ /** Test libstatssocket logging queue atom id distribution collection */
+ public void testAtomIdLossDistributionCollection() throws Exception {
+ if (!ApiLevelUtil.codenameEquals(getDevice(), "VanillaIceCream")) {
+ return;
+ }
+
+ final String appTestApk = "StatsdAtomStormApp.apk";
+ final String app2TestApk = "StatsdAtomStormApp2.apk";
+
+ final String appTestPkg = "com.android.statsd.app.atomstorm";
+ final String app2TestPkg = "com.android.statsd.app.atomstorm.copy";
+
+ getDevice().uninstallPackage(appTestPkg);
+ getDevice().uninstallPackage(app2TestPkg);
+ installPackage(appTestApk, true);
+ installPackage(app2TestApk, true);
+
+ // run reference test app with UID 1
+ runDeviceTests(appTestPkg, null, null);
+ // run reference test app with UID 2
+ runDeviceTests(app2TestPkg, null, null);
+
+ StatsdStatsReport report = getStatsdStatsReport();
+ assertThat(report).isNotNull();
+ HashSet<Integer> reportedUids = new HashSet<Integer>();
+ for (LossStatsPerUid lossStats : report.getSocketLossStats().getLossStatsPerUidList()) {
+ reportedUids.add(lossStats.getUid());
}
+ assertThat(reportedUids.size()).isGreaterThan(1);
+
+ getDevice().uninstallPackage(appTestPkg);
+ getDevice().uninstallPackage(app2TestPkg);
}
}