Statsd cpu optimizaton
- cache the changed dimensions in condition tracker.
- avoid query condition wizard when unnecessary.
- avoid copy dimension keys in condition key generation.
Test: statsd tests.
BUG: b/73959649
Change-Id: I17d68e2a82643de3f421309841e75f84c6fd8f43
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
index 605198f..7f0a26c 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -199,7 +199,9 @@
tests/e2e/MetricConditionLink_e2e_test.cpp \
tests/e2e/Attribution_e2e_test.cpp \
tests/e2e/GaugeMetric_e2e_test.cpp \
- tests/e2e/DimensionInCondition_e2e_test.cpp
+ tests/e2e/DimensionInCondition_e2e_combination_AND_cond_test.cpp \
+ tests/e2e/DimensionInCondition_e2e_combination_OR_cond_test.cpp \
+ tests/e2e/DimensionInCondition_e2e_simple_cond_test.cpp
LOCAL_STATIC_LIBRARIES := \
$(statsd_common_static_libraries) \
@@ -247,11 +249,32 @@
LOCAL_MODULE := statsd_benchmark
LOCAL_SRC_FILES := $(statsd_common_src) \
+ src/atom_field_options.proto \
+ src/atoms.proto \
+ src/stats_log.proto \
benchmark/main.cpp \
benchmark/hello_world_benchmark.cpp \
benchmark/log_event_benchmark.cpp \
benchmark/stats_write_benchmark.cpp \
- benchmark/filter_value_benchmark.cpp
+ benchmark/filter_value_benchmark.cpp \
+ benchmark/get_dimensions_for_condition_benchmark.cpp \
+ benchmark/metric_util.cpp \
+ benchmark/duration_metric_benchmark.cpp
+
+LOCAL_STATIC_LIBRARIES := \
+ $(statsd_common_static_libraries)
+
+LOCAL_PROTOC_OPTIMIZE_TYPE := full
+
+LOCAL_PROTOC_FLAGS := \
+ -Iexternal/protobuf/src
+
+LOCAL_SHARED_LIBRARIES := $(statsd_common_shared_libraries) \
+ libprotobuf-cpp-full
+
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ platformprotoslite
LOCAL_C_INCLUDES := $(statsd_common_c_includes)
@@ -285,4 +308,4 @@
statsd_common_shared_libraries:=
-include $(call all-makefiles-under,$(LOCAL_PATH))
+include $(call all-makefiles-under,$(LOCAL_PATH))
\ No newline at end of file
diff --git a/cmds/statsd/benchmark/duration_metric_benchmark.cpp b/cmds/statsd/benchmark/duration_metric_benchmark.cpp
new file mode 100644
index 0000000..2631009
--- /dev/null
+++ b/cmds/statsd/benchmark/duration_metric_benchmark.cpp
@@ -0,0 +1,320 @@
+/*
+ * Copyright (C) 2018 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 <vector>
+#include "benchmark/benchmark.h"
+#include "FieldValue.h"
+#include "HashableDimensionKey.h"
+#include "logd/LogEvent.h"
+#include "stats_log_util.h"
+#include "metric_util.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using std::vector;
+
+static StatsdConfig CreateDurationMetricConfig_NoLink_AND_CombinationCondition(
+ DurationMetric::AggregationType aggregationType, bool addExtraDimensionInCondition) {
+ StatsdConfig config;
+ *config.add_atom_matcher() = CreateStartScheduledJobAtomMatcher();
+ *config.add_atom_matcher() = CreateFinishScheduledJobAtomMatcher();
+ *config.add_atom_matcher() = CreateSyncStartAtomMatcher();
+ *config.add_atom_matcher() = CreateSyncEndAtomMatcher();
+ *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
+ *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher();
+
+ auto scheduledJobPredicate = CreateScheduledJobPredicate();
+ auto dimensions = scheduledJobPredicate.mutable_simple_predicate()->mutable_dimensions();
+ dimensions->set_field(android::util::SCHEDULED_JOB_STATE_CHANGED);
+ dimensions->add_child()->set_field(2); // job name field.
+
+ auto screenIsOffPredicate = CreateScreenIsOffPredicate();
+
+ auto isSyncingPredicate = CreateIsSyncingPredicate();
+ auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions();
+ *syncDimension = CreateAttributionUidAndTagDimensions(android::util::SYNC_STATE_CHANGED,
+ {Position::FIRST});
+ if (addExtraDimensionInCondition) {
+ syncDimension->add_child()->set_field(2 /* name field*/);
+ }
+
+ *config.add_predicate() = scheduledJobPredicate;
+ *config.add_predicate() = screenIsOffPredicate;
+ *config.add_predicate() = isSyncingPredicate;
+ auto combinationPredicate = config.add_predicate();
+ combinationPredicate->set_id(StringToId("CombinationPredicate"));
+ combinationPredicate->mutable_combination()->set_operation(LogicalOperation::AND);
+ addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate);
+ addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate);
+
+ auto metric = config.add_duration_metric();
+ metric->set_bucket(FIVE_MINUTES);
+ metric->set_id(StringToId("scheduledJob"));
+ metric->set_what(scheduledJobPredicate.id());
+ metric->set_condition(combinationPredicate->id());
+ metric->set_aggregation_type(aggregationType);
+ auto dimensionWhat = metric->mutable_dimensions_in_what();
+ dimensionWhat->set_field(android::util::SCHEDULED_JOB_STATE_CHANGED);
+ dimensionWhat->add_child()->set_field(2); // job name field.
+ *metric->mutable_dimensions_in_condition() = CreateAttributionUidAndTagDimensions(
+ android::util::SYNC_STATE_CHANGED, {Position::FIRST});
+ return config;
+}
+
+static StatsdConfig CreateDurationMetricConfig_Link_AND_CombinationCondition(
+ DurationMetric::AggregationType aggregationType, bool addExtraDimensionInCondition) {
+ StatsdConfig config;
+ *config.add_atom_matcher() = CreateStartScheduledJobAtomMatcher();
+ *config.add_atom_matcher() = CreateFinishScheduledJobAtomMatcher();
+ *config.add_atom_matcher() = CreateSyncStartAtomMatcher();
+ *config.add_atom_matcher() = CreateSyncEndAtomMatcher();
+ *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
+ *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher();
+
+ auto scheduledJobPredicate = CreateScheduledJobPredicate();
+ auto dimensions = scheduledJobPredicate.mutable_simple_predicate()->mutable_dimensions();
+ *dimensions = CreateAttributionUidDimensions(
+ android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST});
+ dimensions->add_child()->set_field(2); // job name field.
+
+ auto isSyncingPredicate = CreateIsSyncingPredicate();
+ auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions();
+ *syncDimension = CreateAttributionUidDimensions(
+ android::util::SYNC_STATE_CHANGED, {Position::FIRST});
+ if (addExtraDimensionInCondition) {
+ syncDimension->add_child()->set_field(2 /* name field*/);
+ }
+
+ auto screenIsOffPredicate = CreateScreenIsOffPredicate();
+
+ *config.add_predicate() = scheduledJobPredicate;
+ *config.add_predicate() = screenIsOffPredicate;
+ *config.add_predicate() = isSyncingPredicate;
+ auto combinationPredicate = config.add_predicate();
+ combinationPredicate->set_id(StringToId("CombinationPredicate"));
+ combinationPredicate->mutable_combination()->set_operation(LogicalOperation::AND);
+ addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate);
+ addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate);
+
+ auto metric = config.add_duration_metric();
+ metric->set_bucket(FIVE_MINUTES);
+ metric->set_id(StringToId("scheduledJob"));
+ metric->set_what(scheduledJobPredicate.id());
+ metric->set_condition(combinationPredicate->id());
+ metric->set_aggregation_type(aggregationType);
+ *metric->mutable_dimensions_in_what() = CreateAttributionUidDimensions(
+ android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST});
+
+ auto links = metric->add_links();
+ links->set_condition(isSyncingPredicate.id());
+ *links->mutable_fields_in_what() =
+ CreateAttributionUidDimensions(
+ android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST});
+ *links->mutable_fields_in_condition() =
+ CreateAttributionUidDimensions(android::util::SYNC_STATE_CHANGED, {Position::FIRST});
+ return config;
+}
+
+static void BM_DurationMetricNoLink(benchmark::State& state) {
+ ConfigKey cfgKey;
+ auto config = CreateDurationMetricConfig_NoLink_AND_CombinationCondition(
+ DurationMetric::SUM, false);
+ int64_t bucketStartTimeNs = 10000000000;
+ int64_t bucketSizeNs =
+ TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
+
+
+ std::vector<AttributionNodeInternal> attributions1 = {
+ CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"),
+ CreateAttribution(222, "GMSCoreModule2")};
+
+ std::vector<AttributionNodeInternal> attributions2 = {
+ CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"),
+ CreateAttribution(555, "GMSCoreModule2")};
+
+ std::vector<std::unique_ptr<LogEvent>> events;
+
+ events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
+ bucketStartTimeNs + 11));
+ events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
+ bucketStartTimeNs + 40));
+
+ events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
+ bucketStartTimeNs + 102));
+ events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
+ bucketStartTimeNs + 450));
+
+ events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
+ bucketStartTimeNs + 650));
+ events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
+ bucketStartTimeNs + bucketSizeNs + 100));
+
+ events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
+ bucketStartTimeNs + bucketSizeNs + 640));
+ events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
+ bucketStartTimeNs + bucketSizeNs + 650));
+
+ events.push_back(CreateStartScheduledJobEvent(
+ {CreateAttribution(9999, "")}, "job0", bucketStartTimeNs + 2));
+ events.push_back(CreateFinishScheduledJobEvent(
+ {CreateAttribution(9999, "")}, "job0",bucketStartTimeNs + 101));
+
+ events.push_back(CreateStartScheduledJobEvent(
+ {CreateAttribution(9999, "")}, "job2", bucketStartTimeNs + 201));
+ events.push_back(CreateFinishScheduledJobEvent(
+ {CreateAttribution(9999, "")}, "job2",bucketStartTimeNs + 500));
+
+ events.push_back(CreateStartScheduledJobEvent(
+ {CreateAttribution(8888, "")}, "job2", bucketStartTimeNs + 600));
+ events.push_back(CreateFinishScheduledJobEvent(
+ {CreateAttribution(8888, "")}, "job2",bucketStartTimeNs + bucketSizeNs + 850));
+
+ events.push_back(CreateStartScheduledJobEvent(
+ {CreateAttribution(8888, "")}, "job1", bucketStartTimeNs + bucketSizeNs + 600));
+ events.push_back(CreateFinishScheduledJobEvent(
+ {CreateAttribution(8888, "")}, "job1", bucketStartTimeNs + bucketSizeNs + 900));
+
+ events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail",
+ bucketStartTimeNs + 10));
+ events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail",
+ bucketStartTimeNs + 50));
+
+ events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail",
+ bucketStartTimeNs + 200));
+ events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail",
+ bucketStartTimeNs + bucketSizeNs + 300));
+
+ events.push_back(CreateSyncStartEvent(attributions1, "ReadDoc",
+ bucketStartTimeNs + 400));
+ events.push_back(CreateSyncEndEvent(attributions1, "ReadDoc",
+ bucketStartTimeNs + bucketSizeNs - 1));
+
+ events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail",
+ bucketStartTimeNs + 401));
+ events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail",
+ bucketStartTimeNs + bucketSizeNs + 700));
+
+ sortLogEventsByTimestamp(&events);
+
+ while (state.KeepRunning()) {
+ auto processor = CreateStatsLogProcessor(
+ bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+ for (const auto& event : events) {
+ processor->OnLogEvent(event.get());
+ }
+ }
+}
+
+BENCHMARK(BM_DurationMetricNoLink);
+
+
+static void BM_DurationMetricLink(benchmark::State& state) {
+ ConfigKey cfgKey;
+ auto config = CreateDurationMetricConfig_Link_AND_CombinationCondition(
+ DurationMetric::SUM, false);
+ int64_t bucketStartTimeNs = 10000000000;
+ int64_t bucketSizeNs =
+ TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
+
+ std::vector<AttributionNodeInternal> attributions1 = {
+ CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"),
+ CreateAttribution(222, "GMSCoreModule2")};
+
+ std::vector<AttributionNodeInternal> attributions2 = {
+ CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"),
+ CreateAttribution(555, "GMSCoreModule2")};
+
+ std::vector<AttributionNodeInternal> attributions3 = {
+ CreateAttribution(444, "App3"), CreateAttribution(222, "GMSCoreModule1"),
+ CreateAttribution(555, "GMSCoreModule2")};
+
+ std::vector<std::unique_ptr<LogEvent>> events;
+
+ events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
+ bucketStartTimeNs + 55));
+ events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
+ bucketStartTimeNs + 120));
+ events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
+ bucketStartTimeNs + 121));
+ events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
+ bucketStartTimeNs + 450));
+
+ events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
+ bucketStartTimeNs + 501));
+ events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
+ bucketStartTimeNs + bucketSizeNs + 100));
+
+ events.push_back(CreateStartScheduledJobEvent(
+ {CreateAttribution(111, "App1")}, "job1", bucketStartTimeNs + 1));
+ events.push_back(CreateFinishScheduledJobEvent(
+ {CreateAttribution(111, "App1")}, "job1",bucketStartTimeNs + 101));
+
+ events.push_back(CreateStartScheduledJobEvent(
+ {CreateAttribution(333, "App2")}, "job2", bucketStartTimeNs + 201));
+ events.push_back(CreateFinishScheduledJobEvent(
+ {CreateAttribution(333, "App2")}, "job2",bucketStartTimeNs + 500));
+ events.push_back(CreateStartScheduledJobEvent(
+ {CreateAttribution(333, "App2")}, "job2", bucketStartTimeNs + 600));
+ events.push_back(CreateFinishScheduledJobEvent(
+ {CreateAttribution(333, "App2")}, "job2",
+ bucketStartTimeNs + bucketSizeNs + 850));
+
+ events.push_back(
+ CreateStartScheduledJobEvent({CreateAttribution(444, "App3")}, "job3",
+ bucketStartTimeNs + bucketSizeNs - 2));
+ events.push_back(
+ CreateFinishScheduledJobEvent({CreateAttribution(444, "App3")}, "job3",
+ bucketStartTimeNs + bucketSizeNs + 900));
+
+ events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail",
+ bucketStartTimeNs + 50));
+ events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail",
+ bucketStartTimeNs + 110));
+
+ events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail",
+ bucketStartTimeNs + 300));
+ events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail",
+ bucketStartTimeNs + bucketSizeNs + 700));
+ events.push_back(CreateSyncStartEvent(attributions2, "ReadDoc",
+ bucketStartTimeNs + 400));
+ events.push_back(CreateSyncEndEvent(attributions2, "ReadDoc",
+ bucketStartTimeNs + bucketSizeNs - 1));
+
+ events.push_back(CreateSyncStartEvent(attributions3, "ReadDoc",
+ bucketStartTimeNs + 550));
+ events.push_back(CreateSyncEndEvent(attributions3, "ReadDoc",
+ bucketStartTimeNs + 800));
+ events.push_back(CreateSyncStartEvent(attributions3, "ReadDoc",
+ bucketStartTimeNs + bucketSizeNs - 1));
+ events.push_back(CreateSyncEndEvent(attributions3, "ReadDoc",
+ bucketStartTimeNs + bucketSizeNs + 700));
+ sortLogEventsByTimestamp(&events);
+
+ while (state.KeepRunning()) {
+ auto processor = CreateStatsLogProcessor(
+ bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+ for (const auto& event : events) {
+ processor->OnLogEvent(event.get());
+ }
+ }
+}
+
+BENCHMARK(BM_DurationMetricLink);
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/benchmark/filter_value_benchmark.cpp b/cmds/statsd/benchmark/filter_value_benchmark.cpp
index b9ddf36..66c4def 100644
--- a/cmds/statsd/benchmark/filter_value_benchmark.cpp
+++ b/cmds/statsd/benchmark/filter_value_benchmark.cpp
@@ -18,6 +18,7 @@
#include "FieldValue.h"
#include "HashableDimensionKey.h"
#include "logd/LogEvent.h"
+#include "stats_log_util.h"
namespace android {
namespace os {
@@ -25,17 +26,29 @@
using std::vector;
+static void createLogEventAndMatcher(LogEvent* event, FieldMatcher *field_matcher) {
+ AttributionNodeInternal node;
+ node.set_uid(100);
+ node.set_tag("LOCATION");
+
+ std::vector<AttributionNodeInternal> nodes = {node, node};
+ event->write(nodes);
+ event->write(3.2f);
+ event->write("LOCATION");
+ event->write((int64_t)990);
+ event->init();
+
+ field_matcher->set_field(1);
+ auto child = field_matcher->add_child();
+ child->set_field(1);
+ child->set_position(FIRST);
+ child->add_child()->set_field(1);
+}
+
static void BM_FilterValue(benchmark::State& state) {
LogEvent event(1, 100000);
- event.write(3.2f);
- event.write("LOCATION");
- event.write((int64_t)990);
- event.init();
-
FieldMatcher field_matcher;
- field_matcher.set_field(1);
- field_matcher.add_child()->set_field(2);
- field_matcher.add_child()->set_field(3);
+ createLogEventAndMatcher(&event, &field_matcher);
std::vector<Matcher> matchers;
translateFieldMatcher(field_matcher, &matchers);
@@ -48,6 +61,22 @@
BENCHMARK(BM_FilterValue);
+static void BM_FilterValue2(benchmark::State& state) {
+ LogEvent event(1, 100000);
+ FieldMatcher field_matcher;
+ createLogEventAndMatcher(&event, &field_matcher);
+
+ std::vector<Matcher> matchers;
+ translateFieldMatcher(field_matcher, &matchers);
+
+ while (state.KeepRunning()) {
+ HashableDimensionKey output;
+ filterValues(matchers, event.getValues(), &output);
+ }
+}
+
+BENCHMARK(BM_FilterValue2);
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/benchmark/get_dimensions_for_condition_benchmark.cpp b/cmds/statsd/benchmark/get_dimensions_for_condition_benchmark.cpp
new file mode 100644
index 0000000..2a4403e
--- /dev/null
+++ b/cmds/statsd/benchmark/get_dimensions_for_condition_benchmark.cpp
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2018 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 <vector>
+#include "benchmark/benchmark.h"
+#include "FieldValue.h"
+#include "HashableDimensionKey.h"
+#include "logd/LogEvent.h"
+#include "stats_log_util.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using std::vector;
+
+static void createLogEventAndLink(LogEvent* event, Metric2Condition *link) {
+ AttributionNodeInternal node;
+ node.set_uid(100);
+ node.set_tag("LOCATION");
+
+ std::vector<AttributionNodeInternal> nodes = {node, node};
+ event->write(nodes);
+ event->write(3.2f);
+ event->write("LOCATION");
+ event->write((int64_t)990);
+ event->init();
+
+ link->conditionId = 1;
+
+ FieldMatcher field_matcher;
+ field_matcher.set_field(event->GetTagId());
+ auto child = field_matcher.add_child();
+ child->set_field(1);
+ child->set_position(FIRST);
+ child->add_child()->set_field(1);
+
+ translateFieldMatcher(field_matcher, &link->metricFields);
+ field_matcher.set_field(event->GetTagId() + 1);
+ translateFieldMatcher(field_matcher, &link->conditionFields);
+}
+
+static void BM_GetDimensionInCondition(benchmark::State& state) {
+ Metric2Condition link;
+ LogEvent event(1, 100000);
+ createLogEventAndLink(&event, &link);
+
+ while (state.KeepRunning()) {
+ HashableDimensionKey output;
+ getDimensionForCondition(event.getValues(), link, &output);
+ }
+}
+
+BENCHMARK(BM_GetDimensionInCondition);
+
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/benchmark/metric_util.cpp b/cmds/statsd/benchmark/metric_util.cpp
new file mode 100644
index 0000000..b67764b
--- /dev/null
+++ b/cmds/statsd/benchmark/metric_util.cpp
@@ -0,0 +1,394 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "metric_util.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+AtomMatcher CreateSimpleAtomMatcher(const string& name, int atomId) {
+ AtomMatcher atom_matcher;
+ atom_matcher.set_id(StringToId(name));
+ auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
+ simple_atom_matcher->set_atom_id(atomId);
+ return atom_matcher;
+}
+
+AtomMatcher CreateScheduledJobStateChangedAtomMatcher(const string& name,
+ ScheduledJobStateChanged::State state) {
+ AtomMatcher atom_matcher;
+ atom_matcher.set_id(StringToId(name));
+ auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
+ simple_atom_matcher->set_atom_id(android::util::SCHEDULED_JOB_STATE_CHANGED);
+ auto field_value_matcher = simple_atom_matcher->add_field_value_matcher();
+ field_value_matcher->set_field(3); // State field.
+ field_value_matcher->set_eq_int(state);
+ return atom_matcher;
+}
+
+AtomMatcher CreateStartScheduledJobAtomMatcher() {
+ return CreateScheduledJobStateChangedAtomMatcher("ScheduledJobStart",
+ ScheduledJobStateChanged::STARTED);
+}
+
+AtomMatcher CreateFinishScheduledJobAtomMatcher() {
+ return CreateScheduledJobStateChangedAtomMatcher("ScheduledJobFinish",
+ ScheduledJobStateChanged::FINISHED);
+}
+
+AtomMatcher CreateScreenBrightnessChangedAtomMatcher() {
+ AtomMatcher atom_matcher;
+ atom_matcher.set_id(StringToId("ScreenBrightnessChanged"));
+ auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
+ simple_atom_matcher->set_atom_id(android::util::SCREEN_BRIGHTNESS_CHANGED);
+ return atom_matcher;
+}
+
+AtomMatcher CreateUidProcessStateChangedAtomMatcher() {
+ AtomMatcher atom_matcher;
+ atom_matcher.set_id(StringToId("UidProcessStateChanged"));
+ auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
+ simple_atom_matcher->set_atom_id(android::util::UID_PROCESS_STATE_CHANGED);
+ return atom_matcher;
+}
+
+AtomMatcher CreateWakelockStateChangedAtomMatcher(const string& name,
+ WakelockStateChanged::State state) {
+ AtomMatcher atom_matcher;
+ atom_matcher.set_id(StringToId(name));
+ auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
+ simple_atom_matcher->set_atom_id(android::util::WAKELOCK_STATE_CHANGED);
+ auto field_value_matcher = simple_atom_matcher->add_field_value_matcher();
+ field_value_matcher->set_field(4); // State field.
+ field_value_matcher->set_eq_int(state);
+ return atom_matcher;
+}
+
+AtomMatcher CreateAcquireWakelockAtomMatcher() {
+ return CreateWakelockStateChangedAtomMatcher("AcquireWakelock", WakelockStateChanged::ACQUIRE);
+}
+
+AtomMatcher CreateReleaseWakelockAtomMatcher() {
+ return CreateWakelockStateChangedAtomMatcher("ReleaseWakelock", WakelockStateChanged::RELEASE);
+}
+
+AtomMatcher CreateScreenStateChangedAtomMatcher(
+ const string& name, android::view::DisplayStateEnum state) {
+ AtomMatcher atom_matcher;
+ atom_matcher.set_id(StringToId(name));
+ auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
+ simple_atom_matcher->set_atom_id(android::util::SCREEN_STATE_CHANGED);
+ auto field_value_matcher = simple_atom_matcher->add_field_value_matcher();
+ field_value_matcher->set_field(1); // State field.
+ field_value_matcher->set_eq_int(state);
+ return atom_matcher;
+}
+
+AtomMatcher CreateScreenTurnedOnAtomMatcher() {
+ return CreateScreenStateChangedAtomMatcher("ScreenTurnedOn",
+ android::view::DisplayStateEnum::DISPLAY_STATE_ON);
+}
+
+AtomMatcher CreateScreenTurnedOffAtomMatcher() {
+ return CreateScreenStateChangedAtomMatcher("ScreenTurnedOff",
+ ::android::view::DisplayStateEnum::DISPLAY_STATE_OFF);
+}
+
+AtomMatcher CreateSyncStateChangedAtomMatcher(
+ const string& name, SyncStateChanged::State state) {
+ AtomMatcher atom_matcher;
+ atom_matcher.set_id(StringToId(name));
+ auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
+ simple_atom_matcher->set_atom_id(android::util::SYNC_STATE_CHANGED);
+ auto field_value_matcher = simple_atom_matcher->add_field_value_matcher();
+ field_value_matcher->set_field(3); // State field.
+ field_value_matcher->set_eq_int(state);
+ return atom_matcher;
+}
+
+AtomMatcher CreateSyncStartAtomMatcher() {
+ return CreateSyncStateChangedAtomMatcher("SyncStart", SyncStateChanged::ON);
+}
+
+AtomMatcher CreateSyncEndAtomMatcher() {
+ return CreateSyncStateChangedAtomMatcher("SyncEnd", SyncStateChanged::OFF);
+}
+
+AtomMatcher CreateActivityForegroundStateChangedAtomMatcher(
+ const string& name, ActivityForegroundStateChanged::Activity activity) {
+ AtomMatcher atom_matcher;
+ atom_matcher.set_id(StringToId(name));
+ auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
+ simple_atom_matcher->set_atom_id(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED);
+ auto field_value_matcher = simple_atom_matcher->add_field_value_matcher();
+ field_value_matcher->set_field(4); // Activity field.
+ field_value_matcher->set_eq_int(activity);
+ return atom_matcher;
+}
+
+AtomMatcher CreateMoveToBackgroundAtomMatcher() {
+ return CreateActivityForegroundStateChangedAtomMatcher(
+ "MoveToBackground", ActivityForegroundStateChanged::MOVE_TO_BACKGROUND);
+}
+
+AtomMatcher CreateMoveToForegroundAtomMatcher() {
+ return CreateActivityForegroundStateChangedAtomMatcher(
+ "MoveToForeground", ActivityForegroundStateChanged::MOVE_TO_FOREGROUND);
+}
+
+Predicate CreateScheduledJobPredicate() {
+ Predicate predicate;
+ predicate.set_id(StringToId("ScheduledJobRunningPredicate"));
+ predicate.mutable_simple_predicate()->set_start(StringToId("ScheduledJobStart"));
+ predicate.mutable_simple_predicate()->set_stop(StringToId("ScheduledJobFinish"));
+ return predicate;
+}
+
+Predicate CreateBatterySaverModePredicate() {
+ Predicate predicate;
+ predicate.set_id(StringToId("BatterySaverIsOn"));
+ predicate.mutable_simple_predicate()->set_start(StringToId("BatterySaverModeStart"));
+ predicate.mutable_simple_predicate()->set_stop(StringToId("BatterySaverModeStop"));
+ return predicate;
+}
+
+Predicate CreateScreenIsOnPredicate() {
+ Predicate predicate;
+ predicate.set_id(StringToId("ScreenIsOn"));
+ predicate.mutable_simple_predicate()->set_start(StringToId("ScreenTurnedOn"));
+ predicate.mutable_simple_predicate()->set_stop(StringToId("ScreenTurnedOff"));
+ return predicate;
+}
+
+Predicate CreateScreenIsOffPredicate() {
+ Predicate predicate;
+ predicate.set_id(1111123);
+ predicate.mutable_simple_predicate()->set_start(StringToId("ScreenTurnedOff"));
+ predicate.mutable_simple_predicate()->set_stop(StringToId("ScreenTurnedOn"));
+ return predicate;
+}
+
+Predicate CreateHoldingWakelockPredicate() {
+ Predicate predicate;
+ predicate.set_id(StringToId("HoldingWakelock"));
+ predicate.mutable_simple_predicate()->set_start(StringToId("AcquireWakelock"));
+ predicate.mutable_simple_predicate()->set_stop(StringToId("ReleaseWakelock"));
+ return predicate;
+}
+
+Predicate CreateIsSyncingPredicate() {
+ Predicate predicate;
+ predicate.set_id(33333333333333);
+ predicate.mutable_simple_predicate()->set_start(StringToId("SyncStart"));
+ predicate.mutable_simple_predicate()->set_stop(StringToId("SyncEnd"));
+ return predicate;
+}
+
+Predicate CreateIsInBackgroundPredicate() {
+ Predicate predicate;
+ predicate.set_id(StringToId("IsInBackground"));
+ predicate.mutable_simple_predicate()->set_start(StringToId("MoveToBackground"));
+ predicate.mutable_simple_predicate()->set_stop(StringToId("MoveToForeground"));
+ return predicate;
+}
+
+void addPredicateToPredicateCombination(const Predicate& predicate,
+ Predicate* combinationPredicate) {
+ combinationPredicate->mutable_combination()->add_predicate(predicate.id());
+}
+
+FieldMatcher CreateAttributionUidDimensions(const int atomId,
+ const std::vector<Position>& positions) {
+ FieldMatcher dimensions;
+ dimensions.set_field(atomId);
+ for (const auto position : positions) {
+ auto child = dimensions.add_child();
+ child->set_field(1);
+ child->set_position(position);
+ child->add_child()->set_field(1);
+ }
+ return dimensions;
+}
+
+FieldMatcher CreateAttributionUidAndTagDimensions(const int atomId,
+ const std::vector<Position>& positions) {
+ FieldMatcher dimensions;
+ dimensions.set_field(atomId);
+ for (const auto position : positions) {
+ auto child = dimensions.add_child();
+ child->set_field(1);
+ child->set_position(position);
+ child->add_child()->set_field(1);
+ child->add_child()->set_field(2);
+ }
+ return dimensions;
+}
+
+FieldMatcher CreateDimensions(const int atomId, const std::vector<int>& fields) {
+ FieldMatcher dimensions;
+ dimensions.set_field(atomId);
+ for (const int field : fields) {
+ dimensions.add_child()->set_field(field);
+ }
+ return dimensions;
+}
+
+std::unique_ptr<LogEvent> CreateScreenStateChangedEvent(
+ const android::view::DisplayStateEnum state, uint64_t timestampNs) {
+ auto event = std::make_unique<LogEvent>(android::util::SCREEN_STATE_CHANGED, timestampNs);
+ event->write(state);
+ event->init();
+ return event;
+}
+
+std::unique_ptr<LogEvent> CreateScreenBrightnessChangedEvent(
+ int level, uint64_t timestampNs) {
+ auto event = std::make_unique<LogEvent>(android::util::SCREEN_BRIGHTNESS_CHANGED, timestampNs);
+ (event->write(level));
+ event->init();
+ return event;
+
+}
+
+std::unique_ptr<LogEvent> CreateScheduledJobStateChangedEvent(
+ const std::vector<AttributionNodeInternal>& attributions, const string& jobName,
+ const ScheduledJobStateChanged::State state, uint64_t timestampNs) {
+ auto event = std::make_unique<LogEvent>(android::util::SCHEDULED_JOB_STATE_CHANGED, timestampNs);
+ event->write(attributions);
+ event->write(jobName);
+ event->write(state);
+ event->init();
+ return event;
+}
+
+std::unique_ptr<LogEvent> CreateStartScheduledJobEvent(
+ const std::vector<AttributionNodeInternal>& attributions,
+ const string& name, uint64_t timestampNs) {
+ return CreateScheduledJobStateChangedEvent(
+ attributions, name, ScheduledJobStateChanged::STARTED, timestampNs);
+}
+
+// Create log event when scheduled job finishes.
+std::unique_ptr<LogEvent> CreateFinishScheduledJobEvent(
+ const std::vector<AttributionNodeInternal>& attributions,
+ const string& name, uint64_t timestampNs) {
+ return CreateScheduledJobStateChangedEvent(
+ attributions, name, ScheduledJobStateChanged::FINISHED, timestampNs);
+}
+
+std::unique_ptr<LogEvent> CreateWakelockStateChangedEvent(
+ const std::vector<AttributionNodeInternal>& attributions, const string& wakelockName,
+ const WakelockStateChanged::State state, uint64_t timestampNs) {
+ auto event = std::make_unique<LogEvent>(android::util::WAKELOCK_STATE_CHANGED, timestampNs);
+ event->write(attributions);
+ event->write(android::os::WakeLockLevelEnum::PARTIAL_WAKE_LOCK);
+ event->write(wakelockName);
+ event->write(state);
+ event->init();
+ return event;
+}
+
+std::unique_ptr<LogEvent> CreateAcquireWakelockEvent(
+ const std::vector<AttributionNodeInternal>& attributions, const string& wakelockName,
+ uint64_t timestampNs) {
+ return CreateWakelockStateChangedEvent(
+ attributions, wakelockName, WakelockStateChanged::ACQUIRE, timestampNs);
+}
+
+std::unique_ptr<LogEvent> CreateReleaseWakelockEvent(
+ const std::vector<AttributionNodeInternal>& attributions, const string& wakelockName,
+ uint64_t timestampNs) {
+ return CreateWakelockStateChangedEvent(
+ attributions, wakelockName, WakelockStateChanged::RELEASE, timestampNs);
+}
+
+std::unique_ptr<LogEvent> CreateActivityForegroundStateChangedEvent(
+ const int uid, const ActivityForegroundStateChanged::Activity activity, uint64_t timestampNs) {
+ auto event = std::make_unique<LogEvent>(
+ android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, timestampNs);
+ event->write(uid);
+ event->write("pkg_name");
+ event->write("class_name");
+ event->write(activity);
+ event->init();
+ return event;
+}
+
+std::unique_ptr<LogEvent> CreateMoveToBackgroundEvent(const int uid, uint64_t timestampNs) {
+ return CreateActivityForegroundStateChangedEvent(
+ uid, ActivityForegroundStateChanged::MOVE_TO_BACKGROUND, timestampNs);
+}
+
+std::unique_ptr<LogEvent> CreateMoveToForegroundEvent(const int uid, uint64_t timestampNs) {
+ return CreateActivityForegroundStateChangedEvent(
+ uid, ActivityForegroundStateChanged::MOVE_TO_FOREGROUND, timestampNs);
+}
+
+std::unique_ptr<LogEvent> CreateSyncStateChangedEvent(
+ const std::vector<AttributionNodeInternal>& attributions, const string& name,
+ const SyncStateChanged::State state, uint64_t timestampNs) {
+ auto event = std::make_unique<LogEvent>(android::util::SYNC_STATE_CHANGED, timestampNs);
+ event->write(attributions);
+ event->write(name);
+ event->write(state);
+ event->init();
+ return event;
+}
+
+std::unique_ptr<LogEvent> CreateSyncStartEvent(
+ const std::vector<AttributionNodeInternal>& attributions, const string& name,
+ uint64_t timestampNs) {
+ return CreateSyncStateChangedEvent(attributions, name, SyncStateChanged::ON, timestampNs);
+}
+
+std::unique_ptr<LogEvent> CreateSyncEndEvent(
+ const std::vector<AttributionNodeInternal>& attributions, const string& name,
+ uint64_t timestampNs) {
+ return CreateSyncStateChangedEvent(attributions, name, SyncStateChanged::OFF, timestampNs);
+}
+
+sp<StatsLogProcessor> CreateStatsLogProcessor(const long timeBaseSec, const StatsdConfig& config,
+ const ConfigKey& key) {
+ sp<UidMap> uidMap = new UidMap();
+ sp<AlarmMonitor> anomalyAlarmMonitor;
+ sp<AlarmMonitor> periodicAlarmMonitor;
+ sp<StatsLogProcessor> processor = new StatsLogProcessor(
+ uidMap, anomalyAlarmMonitor, periodicAlarmMonitor, timeBaseSec, [](const ConfigKey&){});
+ processor->OnConfigUpdated(key, config);
+ return processor;
+}
+
+AttributionNodeInternal CreateAttribution(const int& uid, const string& tag) {
+ AttributionNodeInternal attribution;
+ attribution.set_uid(uid);
+ attribution.set_tag(tag);
+ return attribution;
+}
+
+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) {
+ return a->GetElapsedTimestampNs() < b->GetElapsedTimestampNs();
+ });
+}
+
+int64_t StringToId(const string& str) {
+ return static_cast<int64_t>(std::hash<std::string>()(str));
+}
+
+
+} // namespace statsd
+} // namespace os
+} // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/benchmark/metric_util.h b/cmds/statsd/benchmark/metric_util.h
new file mode 100644
index 0000000..9b28d60
--- /dev/null
+++ b/cmds/statsd/benchmark/metric_util.h
@@ -0,0 +1,161 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "src/StatsLogProcessor.h"
+#include "src/logd/LogEvent.h"
+#include "statslog.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+// Create AtomMatcher proto to simply match a specific atom type.
+AtomMatcher CreateSimpleAtomMatcher(const string& name, int atomId);
+
+// Create AtomMatcher proto for scheduled job state changed.
+AtomMatcher CreateScheduledJobStateChangedAtomMatcher();
+
+// Create AtomMatcher proto for starting a scheduled job.
+AtomMatcher CreateStartScheduledJobAtomMatcher();
+
+// Create AtomMatcher proto for a scheduled job is done.
+AtomMatcher CreateFinishScheduledJobAtomMatcher();
+
+// Create AtomMatcher proto for screen brightness state changed.
+AtomMatcher CreateScreenBrightnessChangedAtomMatcher();
+
+// Create AtomMatcher proto for acquiring wakelock.
+AtomMatcher CreateAcquireWakelockAtomMatcher();
+
+// Create AtomMatcher proto for releasing wakelock.
+AtomMatcher CreateReleaseWakelockAtomMatcher() ;
+
+// Create AtomMatcher proto for screen turned on.
+AtomMatcher CreateScreenTurnedOnAtomMatcher();
+
+// Create AtomMatcher proto for screen turned off.
+AtomMatcher CreateScreenTurnedOffAtomMatcher();
+
+// Create AtomMatcher proto for app sync turned on.
+AtomMatcher CreateSyncStartAtomMatcher();
+
+// Create AtomMatcher proto for app sync turned off.
+AtomMatcher CreateSyncEndAtomMatcher();
+
+// Create AtomMatcher proto for app sync moves to background.
+AtomMatcher CreateMoveToBackgroundAtomMatcher();
+
+// Create AtomMatcher proto for app sync moves to foreground.
+AtomMatcher CreateMoveToForegroundAtomMatcher();
+
+// Create Predicate proto for screen is off.
+Predicate CreateScreenIsOffPredicate();
+
+// Create Predicate proto for a running scheduled job.
+Predicate CreateScheduledJobPredicate();
+
+// Create Predicate proto for holding wakelock.
+Predicate CreateHoldingWakelockPredicate();
+
+// Create a Predicate proto for app syncing.
+Predicate CreateIsSyncingPredicate();
+
+// Create a Predicate proto for app is in background.
+Predicate CreateIsInBackgroundPredicate();
+
+// Add a predicate to the predicate combination.
+void addPredicateToPredicateCombination(const Predicate& predicate, Predicate* combination);
+
+// Create dimensions from primitive fields.
+FieldMatcher CreateDimensions(const int atomId, const std::vector<int>& fields);
+
+// Create dimensions by attribution uid and tag.
+FieldMatcher CreateAttributionUidAndTagDimensions(const int atomId,
+ const std::vector<Position>& positions);
+
+// Create dimensions by attribution uid only.
+FieldMatcher CreateAttributionUidDimensions(const int atomId,
+ const std::vector<Position>& positions);
+
+// Create log event for screen state changed.
+std::unique_ptr<LogEvent> CreateScreenStateChangedEvent(
+ const android::view::DisplayStateEnum state, uint64_t timestampNs);
+
+// Create log event for screen brightness state changed.
+std::unique_ptr<LogEvent> CreateScreenBrightnessChangedEvent(
+ int level, uint64_t timestampNs);
+
+// Create log event when scheduled job starts.
+std::unique_ptr<LogEvent> CreateStartScheduledJobEvent(
+ const std::vector<AttributionNodeInternal>& attributions,
+ const string& name, uint64_t timestampNs);
+
+// Create log event when scheduled job finishes.
+std::unique_ptr<LogEvent> CreateFinishScheduledJobEvent(
+ const std::vector<AttributionNodeInternal>& attributions,
+ const string& name, uint64_t timestampNs);
+
+// Create log event for app moving to background.
+std::unique_ptr<LogEvent> CreateMoveToBackgroundEvent(const int uid, uint64_t timestampNs);
+
+// Create log event for app moving to foreground.
+std::unique_ptr<LogEvent> CreateMoveToForegroundEvent(const int uid, uint64_t timestampNs);
+
+// Create log event when the app sync starts.
+std::unique_ptr<LogEvent> CreateSyncStartEvent(
+ const std::vector<AttributionNodeInternal>& attributions, const string& name,
+ uint64_t timestampNs);
+
+// Create log event when the app sync ends.
+std::unique_ptr<LogEvent> CreateSyncEndEvent(
+ const std::vector<AttributionNodeInternal>& attributions, const string& name,
+ uint64_t timestampNs);
+
+// Create log event when the app sync ends.
+std::unique_ptr<LogEvent> CreateAppCrashEvent(
+ const int uid, uint64_t timestampNs);
+
+// Create log event for acquiring wakelock.
+std::unique_ptr<LogEvent> CreateAcquireWakelockEvent(
+ const std::vector<AttributionNodeInternal>& attributions, const string& wakelockName,
+ uint64_t timestampNs);
+
+// Create log event for releasing wakelock.
+std::unique_ptr<LogEvent> CreateReleaseWakelockEvent(
+ const std::vector<AttributionNodeInternal>& attributions, const string& wakelockName,
+ uint64_t timestampNs);
+
+// Create log event for releasing wakelock.
+std::unique_ptr<LogEvent> CreateIsolatedUidChangedEvent(
+ int isolatedUid, int hostUid, bool is_create, uint64_t timestampNs);
+
+// Helper function to create an AttributionNodeInternal proto.
+AttributionNodeInternal CreateAttribution(const int& uid, const string& tag);
+
+// Create a statsd log event processor upon the start time in seconds, config and key.
+sp<StatsLogProcessor> CreateStatsLogProcessor(const long timeBaseSec, const StatsdConfig& config,
+ const ConfigKey& key);
+
+// Util function to sort the log events by timestamp.
+void sortLogEventsByTimestamp(std::vector<std::unique_ptr<LogEvent>> *events);
+
+int64_t StringToId(const string& str);
+
+} // namespace statsd
+} // namespace os
+} // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/FieldValue.cpp b/cmds/statsd/src/FieldValue.cpp
index b541612..0c9b701 100644
--- a/cmds/statsd/src/FieldValue.cpp
+++ b/cmds/statsd/src/FieldValue.cpp
@@ -205,6 +205,29 @@
}
}
+bool equalDimensions(const std::vector<Matcher>& dimension_a,
+ const std::vector<Matcher>& dimension_b) {
+ bool eq = dimension_a.size() == dimension_b.size();
+ for (size_t i = 0; eq && i < dimension_a.size(); ++i) {
+ if (dimension_b[i] != dimension_a[i]) {
+ eq = false;
+ }
+ }
+ return eq;
+}
+
+bool HasPositionANY(const FieldMatcher& matcher) {
+ if (matcher.has_position() && matcher.position() == Position::ANY) {
+ return true;
+ }
+ for (const auto& child : matcher.child()) {
+ if (HasPositionANY(child)) {
+ return true;
+ }
+ }
+ return false;
+}
+
} // namespace statsd
} // namespace os
} // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/FieldValue.h b/cmds/statsd/src/FieldValue.h
index 621d0be9..0e3ae06 100644
--- a/cmds/statsd/src/FieldValue.h
+++ b/cmds/statsd/src/FieldValue.h
@@ -336,11 +336,16 @@
Value mValue;
};
+bool HasPositionANY(const FieldMatcher& matcher);
+
bool isAttributionUidField(const FieldValue& value);
void translateFieldMatcher(const FieldMatcher& matcher, std::vector<Matcher>* output);
bool isAttributionUidField(const Field& field, const Value& value);
+
+bool equalDimensions(const std::vector<Matcher>& dimension_a,
+ const std::vector<Matcher>& dimension_b);
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/src/HashableDimensionKey.cpp b/cmds/statsd/src/HashableDimensionKey.cpp
index cc706313..d0c8311 100644
--- a/cmds/statsd/src/HashableDimensionKey.cpp
+++ b/cmds/statsd/src/HashableDimensionKey.cpp
@@ -59,6 +59,33 @@
return JenkinsHashWhiten(hash);
}
+bool filterValues(const vector<Matcher>& matcherFields, const vector<FieldValue>& values,
+ HashableDimensionKey* output) {
+ for (size_t i = 0; i < matcherFields.size(); ++i) {
+ const auto& matcher = matcherFields[i];
+ bool found = false;
+ for (const auto& value : values) {
+ // TODO: potential optimization here to break early because all fields are naturally
+ // sorted.
+ if (value.mField.matches(matcher)) {
+ output->addValue(value);
+ output->mutableValue(i)->mField.setTag(value.mField.getTag());
+ output->mutableValue(i)->mField.setField(value.mField.getField() & matcher.mMask);
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ VLOG("We can't find a dimension value for matcher (%d)%#x.", matcher.mMatcher.getTag(),
+ matcher.mMatcher.getField());
+ return false;
+ }
+ }
+
+ return true;
+}
+
// Filter fields using the matchers and output the results as a HashableDimensionKey.
// Note: HashableDimensionKey is just a wrapper for vector<FieldValue>
bool filterValues(const vector<Matcher>& matcherFields, const vector<FieldValue>& values,
@@ -168,22 +195,21 @@
void getDimensionForCondition(const std::vector<FieldValue>& eventValues,
const Metric2Condition& links,
- vector<HashableDimensionKey>* conditionDimension) {
+ HashableDimensionKey* conditionDimension) {
// Get the dimension first by using dimension from what.
filterValues(links.metricFields, eventValues, conditionDimension);
- // Then replace the field with the dimension from condition.
- for (auto& dim : *conditionDimension) {
- size_t count = dim.getValues().size();
- if (count != links.conditionFields.size()) {
- // ALOGE("WTF condition link is bad");
- return;
- }
+ size_t count = conditionDimension->getValues().size();
+ if (count != links.conditionFields.size()) {
+ // ALOGE("WTF condition link is bad");
+ return;
+ }
- for (size_t i = 0; i < count; i++) {
- dim.mutableValue(i)->mField.setField(links.conditionFields[i].mMatcher.getField());
- dim.mutableValue(i)->mField.setTag(links.conditionFields[i].mMatcher.getTag());
- }
+ for (size_t i = 0; i < count; i++) {
+ conditionDimension->mutableValue(i)->mField.setField(
+ links.conditionFields[i].mMatcher.getField());
+ conditionDimension->mutableValue(i)->mField.setTag(
+ links.conditionFields[i].mMatcher.getTag());
}
}
diff --git a/cmds/statsd/src/HashableDimensionKey.h b/cmds/statsd/src/HashableDimensionKey.h
index 57bdf68..4cfed88 100644
--- a/cmds/statsd/src/HashableDimensionKey.h
+++ b/cmds/statsd/src/HashableDimensionKey.h
@@ -65,10 +65,6 @@
std::string toString() const;
- inline const char* c_str() const {
- return toString().c_str();
- }
-
bool operator==(const HashableDimensionKey& that) const;
bool operator<(const HashableDimensionKey& that) const;
@@ -104,6 +100,10 @@
return mDimensionKeyInCondition;
}
+ inline void setDimensionKeyInCondition(const HashableDimensionKey& key) {
+ mDimensionKeyInCondition = key;
+ }
+
bool hasDimensionKeyInCondition() const {
return mDimensionKeyInCondition.getValues().size() > 0;
}
@@ -112,9 +112,6 @@
bool operator<(const MetricDimensionKey& that) const;
- inline const char* c_str() const {
- return toString().c_str();
- }
private:
HashableDimensionKey mDimensionKeyInWhat;
HashableDimensionKey mDimensionKeyInCondition;
@@ -134,6 +131,9 @@
*/
bool filterValues(const std::vector<Matcher>& matcherFields, const std::vector<FieldValue>& values,
std::vector<HashableDimensionKey>* output);
+// This function is used when there is at most one output dimension key. (no ANY matcher)
+bool filterValues(const std::vector<Matcher>& matcherFields, const std::vector<FieldValue>& values,
+ HashableDimensionKey* output);
/**
* Filter the values from FieldValues using the matchers.
@@ -146,7 +146,7 @@
void getDimensionForCondition(const std::vector<FieldValue>& eventValues,
const Metric2Condition& links,
- std::vector<HashableDimensionKey>* conditionDimension);
+ HashableDimensionKey* conditionDimension);
} // namespace statsd
} // namespace os
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index a672ab4..8db8200 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -123,10 +123,21 @@
FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks2);
FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSlice);
FRIEND_TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent);
- FRIEND_TEST(DimensionInConditionE2eTest, TestCountMetricNoLink);
- FRIEND_TEST(DimensionInConditionE2eTest, TestCountMetricWithLink);
- FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetricNoLink);
- FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetricWithLink);
+ FRIEND_TEST(DimensionInConditionE2eTest, TestCreateCountMetric_NoLink_OR_CombinationCondition);
+ FRIEND_TEST(DimensionInConditionE2eTest, TestCreateCountMetric_Link_OR_CombinationCondition);
+ FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_NoLink_OR_CombinationCondition);
+ FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_Link_OR_CombinationCondition);
+
+ FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_NoLink_SimpleCondition);
+ FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_Link_SimpleCondition);
+ FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_PartialLink_SimpleCondition);
+
+
+
+ FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_PartialLink_AND_CombinationCondition);
+ FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_NoLink_AND_CombinationCondition);
+ FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_Link_AND_CombinationCondition);
+
};
} // namespace statsd
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index e0a1299..30896e6 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -177,11 +177,6 @@
/**
* Print the event log.
*/
- status_t cmd_print_stats_log(FILE* out, const Vector<String8>& args);
-
- /**
- * Print the event log.
- */
status_t cmd_dump_report(FILE* out, FILE* err, const Vector<String8>& args);
/**
diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.cpp b/cmds/statsd/src/condition/CombinationConditionTracker.cpp
index 13a2b7b..3661d2b 100644
--- a/cmds/statsd/src/condition/CombinationConditionTracker.cpp
+++ b/cmds/statsd/src/condition/CombinationConditionTracker.cpp
@@ -91,6 +91,9 @@
if (allConditionTrackers[childIndex]->isSliced()) {
setSliced(true);
+ mSlicedChildren.push_back(childIndex);
+ } else {
+ mUnSlicedChildren.push_back(childIndex);
}
mChildren.push_back(childIndex);
mTrackerIndex.insert(childTracker->getLogTrackerIndex().begin(),
@@ -107,13 +110,19 @@
void CombinationConditionTracker::isConditionMet(
const ConditionKey& conditionParameters, const vector<sp<ConditionTracker>>& allConditions,
- const std::vector<Matcher>& dimensionFields, vector<ConditionState>& conditionCache,
+ const std::vector<Matcher>& dimensionFields,
+ const bool isSubOutputDimensionFields,
+ const bool isPartialLink,
+ vector<ConditionState>& conditionCache,
std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const {
// So far, this is fine as there is at most one child having sliced output.
for (const int childIndex : mChildren) {
if (conditionCache[childIndex] == ConditionState::kNotEvaluated) {
allConditions[childIndex]->isConditionMet(conditionParameters, allConditions,
- dimensionFields, conditionCache,
+ dimensionFields,
+ isSubOutputDimensionFields,
+ isPartialLink,
+ conditionCache,
dimensionsKeySet);
}
}
@@ -150,7 +159,11 @@
nonSlicedConditionCache[mIndex] = mNonSlicedConditionState;
conditionChangedCache[mIndex] = nonSlicedChanged;
+ mUnSlicedPart = newCondition;
} else {
+ mUnSlicedPart = evaluateCombinationCondition(
+ mUnSlicedChildren, mLogicalOperation, nonSlicedConditionCache);
+
for (const int childIndex : mChildren) {
// If any of the sliced condition in children condition changes, the combination
// condition may be changed too.
@@ -168,13 +181,14 @@
ConditionState CombinationConditionTracker::getMetConditionDimension(
const std::vector<sp<ConditionTracker>>& allConditions,
const std::vector<Matcher>& dimensionFields,
+ const bool isSubOutputDimensionFields,
std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const {
vector<ConditionState> conditionCache(allConditions.size(), ConditionState::kNotEvaluated);
// So far, this is fine as there is at most one child having sliced output.
for (const int childIndex : mChildren) {
conditionCache[childIndex] = conditionCache[childIndex] |
allConditions[childIndex]->getMetConditionDimension(
- allConditions, dimensionFields, dimensionsKeySet);
+ allConditions, dimensionFields, isSubOutputDimensionFields, dimensionsKeySet);
}
evaluateCombinationCondition(mChildren, mLogicalOperation, conditionCache);
if (conditionCache[mIndex] == ConditionState::kTrue && dimensionsKeySet.empty()) {
@@ -183,6 +197,18 @@
return conditionCache[mIndex];
}
+bool CombinationConditionTracker::equalOutputDimensions(
+ const std::vector<sp<ConditionTracker>>& allConditions,
+ const vector<Matcher>& dimensions) const {
+ if (mSlicedChildren.size() != 1 ||
+ mSlicedChildren.front() >= (int)allConditions.size() ||
+ mLogicalOperation != LogicalOperation::AND) {
+ return false;
+ }
+ const sp<ConditionTracker>& slicedChild = allConditions.at(mSlicedChildren.front());
+ return slicedChild->equalOutputDimensions(allConditions, dimensions);
+}
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.h b/cmds/statsd/src/condition/CombinationConditionTracker.h
index 7b8dc6b..481cb20 100644
--- a/cmds/statsd/src/condition/CombinationConditionTracker.h
+++ b/cmds/statsd/src/condition/CombinationConditionTracker.h
@@ -44,12 +44,15 @@
void isConditionMet(const ConditionKey& conditionParameters,
const std::vector<sp<ConditionTracker>>& allConditions,
const vector<Matcher>& dimensionFields,
+ const bool isSubOutputDimensionFields,
+ const bool isPartialLink,
std::vector<ConditionState>& conditionCache,
std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const override;
ConditionState getMetConditionDimension(
const std::vector<sp<ConditionTracker>>& allConditions,
const vector<Matcher>& dimensionFields,
+ const bool isSubOutputDimensionFields,
std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const override;
// Only one child predicate can have dimension.
@@ -63,6 +66,7 @@
}
return nullptr;
}
+
// Only one child predicate can have dimension.
const std::set<HashableDimensionKey>* getChangedToFalseDimensions(
const std::vector<sp<ConditionTracker>>& allConditions) const override {
@@ -75,6 +79,26 @@
return nullptr;
}
+ bool IsSimpleCondition() const override { return false; }
+
+ bool IsChangedDimensionTrackable() const override {
+ return mLogicalOperation == LogicalOperation::AND && mSlicedChildren.size() == 1;
+ }
+
+ bool equalOutputDimensions(
+ const std::vector<sp<ConditionTracker>>& allConditions,
+ const vector<Matcher>& dimensions) const override;
+
+ void getTrueSlicedDimensions(
+ const std::vector<sp<ConditionTracker>>& allConditions,
+ std::set<HashableDimensionKey>* dimensions) const override {
+ if (mSlicedChildren.size() == 1) {
+ return allConditions[mSlicedChildren.front()]->getTrueSlicedDimensions(
+ allConditions, dimensions);
+ }
+ }
+
+
private:
LogicalOperation mLogicalOperation;
@@ -83,6 +107,10 @@
// map the name to object. We don't want to store smart pointers to children, because it
// increases the risk of circular dependency and memory leak.
std::vector<int> mChildren;
+
+ std::vector<int> mSlicedChildren;
+ std::vector<int> mUnSlicedChildren;
+
};
} // namespace statsd
diff --git a/cmds/statsd/src/condition/ConditionTracker.h b/cmds/statsd/src/condition/ConditionTracker.h
index 856a3a0..1f4266b 100644
--- a/cmds/statsd/src/condition/ConditionTracker.h
+++ b/cmds/statsd/src/condition/ConditionTracker.h
@@ -84,18 +84,28 @@
// condition.
// [allConditions]: all condition trackers. This is needed because the condition evaluation is
// done recursively
+ // [dimensionFields]: the needed dimension fields which should be all or subset of the condition
+ // tracker output dimension.
+ // [isSubOutputDimensionFields]: true if the needed dimension fields which is strictly subset of
+ // the condition tracker output dimension.
+ // [isPartialLink]: true if the link specified by 'conditionParameters' contains all the fields
+ // in the condition tracker output dimension.
// [conditionCache]: the cache holding the condition evaluation values.
// [dimensionsKeySet]: the dimensions where the sliced condition is true. For combination
// condition, it assumes that only one child predicate is sliced.
virtual void isConditionMet(
const ConditionKey& conditionParameters,
const std::vector<sp<ConditionTracker>>& allConditions,
- const vector<Matcher>& dimensionFields, std::vector<ConditionState>& conditionCache,
+ const vector<Matcher>& dimensionFields,
+ const bool isSubOutputDimensionFields,
+ const bool isPartialLink,
+ std::vector<ConditionState>& conditionCache,
std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const = 0;
virtual ConditionState getMetConditionDimension(
const std::vector<sp<ConditionTracker>>& allConditions,
const vector<Matcher>& dimensionFields,
+ const bool isSubOutputDimensionFields,
std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const = 0;
// return the list of LogMatchingTracker index that this ConditionTracker uses.
@@ -107,7 +117,7 @@
mSliced = mSliced | sliced;
}
- bool isSliced() const {
+ inline bool isSliced() const {
return mSliced;
}
@@ -116,6 +126,26 @@
virtual const std::set<HashableDimensionKey>* getChangedToFalseDimensions(
const std::vector<sp<ConditionTracker>>& allConditions) const = 0;
+ inline int64_t getConditionId() const {
+ return mConditionId;
+ }
+
+ virtual void getTrueSlicedDimensions(
+ const std::vector<sp<ConditionTracker>>& allConditions,
+ std::set<HashableDimensionKey>* dimensions) const = 0;
+
+ virtual bool IsChangedDimensionTrackable() const = 0;
+
+ virtual bool IsSimpleCondition() const = 0;
+
+ virtual bool equalOutputDimensions(
+ const std::vector<sp<ConditionTracker>>& allConditions,
+ const vector<Matcher>& dimensions) const = 0;
+
+ inline ConditionState getUnSlicedPartConditionState() const {
+ return mUnSlicedPart;
+ }
+
protected:
const int64_t mConditionId;
@@ -131,6 +161,7 @@
ConditionState mNonSlicedConditionState;
bool mSliced;
+ ConditionState mUnSlicedPart;
};
} // namespace statsd
diff --git a/cmds/statsd/src/condition/ConditionWizard.cpp b/cmds/statsd/src/condition/ConditionWizard.cpp
index 952b0cc..23a9d37 100644
--- a/cmds/statsd/src/condition/ConditionWizard.cpp
+++ b/cmds/statsd/src/condition/ConditionWizard.cpp
@@ -26,19 +26,24 @@
ConditionState ConditionWizard::query(const int index, const ConditionKey& parameters,
const vector<Matcher>& dimensionFields,
+ const bool isSubOutputDimensionFields,
+ const bool isPartialLink,
std::unordered_set<HashableDimensionKey>* dimensionKeySet) {
vector<ConditionState> cache(mAllConditions.size(), ConditionState::kNotEvaluated);
mAllConditions[index]->isConditionMet(
- parameters, mAllConditions, dimensionFields, cache, *dimensionKeySet);
+ parameters, mAllConditions, dimensionFields, isSubOutputDimensionFields, isPartialLink,
+ cache, *dimensionKeySet);
return cache[index];
}
ConditionState ConditionWizard::getMetConditionDimension(
const int index, const vector<Matcher>& dimensionFields,
+ const bool isSubOutputDimensionFields,
std::unordered_set<HashableDimensionKey>* dimensionsKeySet) const {
return mAllConditions[index]->getMetConditionDimension(mAllConditions, dimensionFields,
- *dimensionsKeySet);
+ isSubOutputDimensionFields,
+ *dimensionsKeySet);
}
const set<HashableDimensionKey>* ConditionWizard::getChangedToTrueDimensions(
@@ -51,6 +56,30 @@
return mAllConditions[index]->getChangedToFalseDimensions(mAllConditions);
}
+bool ConditionWizard::IsChangedDimensionTrackable(const int index) {
+ if (index >= 0 && index < (int)mAllConditions.size()) {
+ return mAllConditions[index]->IsChangedDimensionTrackable();
+ } else {
+ return false;
+ }
+}
+
+bool ConditionWizard::IsSimpleCondition(const int index) {
+ if (index >= 0 && index < (int)mAllConditions.size()) {
+ return mAllConditions[index]->IsSimpleCondition();
+ } else {
+ return false;
+ }
+}
+
+bool ConditionWizard::equalOutputDimensions(const int index, const vector<Matcher>& dimensions) {
+ if (index >= 0 && index < (int)mAllConditions.size()) {
+ return mAllConditions[index]->equalOutputDimensions(mAllConditions, dimensions);
+ } else {
+ return false;
+ }
+}
+
} // namespace statsd
} // namespace os
} // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/condition/ConditionWizard.h b/cmds/statsd/src/condition/ConditionWizard.h
index fcfdc2a..a6f88af 100644
--- a/cmds/statsd/src/condition/ConditionWizard.h
+++ b/cmds/statsd/src/condition/ConditionWizard.h
@@ -41,15 +41,30 @@
// the conditionParameters contains the parameters for it's children SimpleConditionTrackers.
virtual ConditionState query(const int conditionIndex, const ConditionKey& conditionParameters,
const vector<Matcher>& dimensionFields,
+ const bool isSubOutputDimensionFields,
+ const bool isPartialLink,
std::unordered_set<HashableDimensionKey>* dimensionKeySet);
virtual ConditionState getMetConditionDimension(
const int index, const vector<Matcher>& dimensionFields,
+ const bool isSubOutputDimensionFields,
std::unordered_set<HashableDimensionKey>* dimensionsKeySet) const;
virtual const std::set<HashableDimensionKey>* getChangedToTrueDimensions(const int index) const;
virtual const std::set<HashableDimensionKey>* getChangedToFalseDimensions(
const int index) const;
+ bool equalOutputDimensions(const int index, const vector<Matcher>& dimensions);
+
+ bool IsChangedDimensionTrackable(const int index);
+ bool IsSimpleCondition(const int index);
+
+ ConditionState getUnSlicedPartConditionState(const int index) {
+ return mAllConditions[index]->getUnSlicedPartConditionState();
+ }
+ void getTrueSlicedDimensions(const int index,
+ std::set<HashableDimensionKey>* trueDimensions) const {
+ return mAllConditions[index]->getTrueSlicedDimensions(mAllConditions, trueDimensions);
+ }
private:
std::vector<sp<ConditionTracker>> mAllConditions;
diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.cpp b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
index 9e27a8b..4913aef 100644
--- a/cmds/statsd/src/condition/SimpleConditionTracker.cpp
+++ b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
@@ -34,7 +34,7 @@
const ConfigKey& key, const int64_t& id, const int index,
const SimplePredicate& simplePredicate,
const unordered_map<int64_t, int>& trackerNameIndexMap)
- : ConditionTracker(id, index), mConfigKey(key) {
+ : ConditionTracker(id, index), mConfigKey(key), mContainANYPositionInInternalDimensions(false) {
VLOG("creating SimpleConditionTracker %lld", (long long)mConditionId);
mCountNesting = simplePredicate.count_nesting();
@@ -80,6 +80,7 @@
mSliced = true;
mDimensionTag = mOutputDimensions[0].mMatcher.getTag();
}
+ mContainANYPositionInInternalDimensions = HasPositionANY(simplePredicate.dimensions());
}
if (simplePredicate.initial_value() == SimplePredicate_InitialValue_FALSE) {
@@ -90,6 +91,10 @@
mNonSlicedConditionState = mInitialValue;
+ if (!mSliced) {
+ mUnSlicedPart = mInitialValue;
+ }
+
mInitialized = true;
}
@@ -109,7 +114,7 @@
void SimpleConditionTracker::dumpState() {
VLOG("%lld DUMP:", (long long)mConditionId);
for (const auto& pair : mSlicedConditionState) {
- VLOG("\t%s : %d", pair.first.c_str(), pair.second);
+ VLOG("\t%s : %d", pair.first.toString().c_str(), pair.second);
}
VLOG("Changed to true keys: \n");
@@ -140,6 +145,9 @@
mInitialValue = ConditionState::kFalse;
mSlicedConditionState.clear();
conditionCache[mIndex] = ConditionState::kFalse;
+ if (!mSliced) {
+ mUnSlicedPart = ConditionState::kFalse;
+ }
}
bool SimpleConditionTracker::hitGuardRail(const HashableDimensionKey& newKey) {
@@ -154,7 +162,7 @@
// 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
ALOGE("Predicate %lld dropping data for dimension key %s",
- (long long)mConditionId, newKey.c_str());
+ (long long)mConditionId, newKey.toString().c_str());
return true;
}
}
@@ -177,13 +185,13 @@
// We get a new output key.
newCondition = matchStart ? ConditionState::kTrue : ConditionState::kFalse;
if (matchStart && mInitialValue != ConditionState::kTrue) {
- mSlicedConditionState.insert(std::make_pair(outputKey, 1));
+ mSlicedConditionState[outputKey] = 1;
changed = true;
mLastChangedToTrueDimensions.insert(outputKey);
} else if (mInitialValue != ConditionState::kFalse) {
// it's a stop and we don't have history about it.
// If the default condition is not false, it means this stop is valuable to us.
- mSlicedConditionState.insert(std::make_pair(outputKey, 0));
+ mSlicedConditionState[outputKey] = 0;
mLastChangedToFalseDimensions.insert(outputKey);
changed = true;
}
@@ -226,7 +234,7 @@
// if default condition is false, it means we don't need to keep the false values.
if (mInitialValue == ConditionState::kFalse && startedCount == 0) {
mSlicedConditionState.erase(outputIt);
- VLOG("erase key %s", outputKey.c_str());
+ VLOG("erase key %s", outputKey.toString().c_str());
}
}
}
@@ -238,6 +246,7 @@
(*conditionChangedCache) = changed;
(*conditionCache) = newCondition;
+
VLOG("SimplePredicate %lld nonSlicedChange? %d", (long long)mConditionId,
conditionChangedCache[mIndex] == true);
}
@@ -294,6 +303,7 @@
conditionCache[mIndex] =
itr->second > 0 ? ConditionState::kTrue : ConditionState::kFalse;
}
+ mUnSlicedPart = conditionCache[mIndex];
}
return;
@@ -305,6 +315,17 @@
if (mOutputDimensions.size() == 0) {
handleConditionEvent(DEFAULT_DIMENSION_KEY, matchedState == 1, &overallState,
&overallChanged);
+ } else if (!mContainANYPositionInInternalDimensions) {
+ HashableDimensionKey outputValue;
+ filterValues(mOutputDimensions, event.getValues(), &outputValue);
+
+ // If this event has multiple nodes in the attribution chain, this log event probably will
+ // generate multiple dimensions. If so, we will find if the condition changes for any
+ // dimension and ask the corresponding metric producer to verify whether the actual sliced
+ // condition has changed or not.
+ // A high level assumption is that a predicate is either sliced or unsliced. We will never
+ // have both sliced and unsliced version of a predicate.
+ handleConditionEvent(outputValue, matchedState == 1, &overallState, &overallChanged);
} else {
std::vector<HashableDimensionKey> outputValues;
filterValues(mOutputDimensions, event.getValues(), &outputValues);
@@ -328,11 +349,17 @@
}
conditionCache[mIndex] = overallState;
conditionChangedCache[mIndex] = overallChanged;
+ if (!mSliced) {
+ mUnSlicedPart = overallState;
+ }
}
void SimpleConditionTracker::isConditionMet(
const ConditionKey& conditionParameters, const vector<sp<ConditionTracker>>& allConditions,
- const vector<Matcher>& dimensionFields, vector<ConditionState>& conditionCache,
+ const vector<Matcher>& dimensionFields,
+ const bool isSubOutputDimensionFields,
+ const bool isPartialLink,
+ vector<ConditionState>& conditionCache,
std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const {
if (conditionCache[mIndex] != ConditionState::kNotEvaluated) {
@@ -347,7 +374,7 @@
ConditionState conditionState = ConditionState::kNotEvaluated;
if (dimensionFields.size() > 0 && dimensionFields[0].mMatcher.getTag() == mDimensionTag) {
conditionState = conditionState | getMetConditionDimension(
- allConditions, dimensionFields, dimensionsKeySet);
+ allConditions, dimensionFields, isSubOutputDimensionFields, dimensionsKeySet);
} else {
conditionState = conditionState | mInitialValue;
if (!mSliced) {
@@ -362,42 +389,48 @@
conditionCache[mIndex] = conditionState;
return;
}
- std::vector<HashableDimensionKey> defaultKeys = { DEFAULT_DIMENSION_KEY };
- const std::vector<HashableDimensionKey> &keys =
- (pair == conditionParameters.end()) ? defaultKeys : pair->second;
ConditionState conditionState = ConditionState::kNotEvaluated;
- for (size_t i = 0; i < keys.size(); ++i) {
- const HashableDimensionKey& key = keys[i];
+ const HashableDimensionKey& key = pair->second;
+ if (isPartialLink) {
+ // For unseen key, check whether the require dimensions are subset of sliced condition
+ // output.
+ conditionState = conditionState | mInitialValue;
+ for (const auto& slice : mSlicedConditionState) {
+ ConditionState sliceState =
+ slice.second > 0 ? ConditionState::kTrue : ConditionState::kFalse;
+ if (slice.first.contains(key)) {
+ conditionState = conditionState | sliceState;
+ if (sliceState == ConditionState::kTrue && dimensionFields.size() > 0) {
+ if (isSubOutputDimensionFields) {
+ HashableDimensionKey dimensionKey;
+ filterValues(dimensionFields, slice.first.getValues(), &dimensionKey);
+ dimensionsKeySet.insert(dimensionKey);
+ } else {
+ dimensionsKeySet.insert(slice.first);
+ }
+ }
+ }
+ }
+ } else {
auto startedCountIt = mSlicedConditionState.find(key);
+ conditionState = conditionState | mInitialValue;
if (startedCountIt != mSlicedConditionState.end()) {
ConditionState sliceState =
startedCountIt->second > 0 ? ConditionState::kTrue : ConditionState::kFalse;
conditionState = conditionState | sliceState;
if (sliceState == ConditionState::kTrue && dimensionFields.size() > 0) {
- vector<HashableDimensionKey> dimensionKeys;
- filterValues(dimensionFields, startedCountIt->first.getValues(), &dimensionKeys);
- dimensionsKeySet.insert(dimensionKeys.begin(), dimensionKeys.end());
- }
- } else {
- // For unseen key, check whether the require dimensions are subset of sliced condition
- // output.
- conditionState = conditionState | mInitialValue;
- for (const auto& slice : mSlicedConditionState) {
- ConditionState sliceState =
- slice.second > 0 ? ConditionState::kTrue : ConditionState::kFalse;
- if (slice.first.contains(key)) {
- conditionState = conditionState | sliceState;
- if (sliceState == ConditionState::kTrue && dimensionFields.size() > 0) {
- vector<HashableDimensionKey> dimensionKeys;
- filterValues(dimensionFields, slice.first.getValues(), &dimensionKeys);
-
- dimensionsKeySet.insert(dimensionKeys.begin(), dimensionKeys.end());
- }
- }
+ if (isSubOutputDimensionFields) {
+ HashableDimensionKey dimensionKey;
+ filterValues(dimensionFields, startedCountIt->first.getValues(), &dimensionKey);
+ dimensionsKeySet.insert(dimensionKey);
+ } else {
+ dimensionsKeySet.insert(startedCountIt->first);
}
}
}
+
+ }
conditionCache[mIndex] = conditionState;
VLOG("Predicate %lld return %d", (long long)mConditionId, conditionCache[mIndex]);
}
@@ -405,6 +438,7 @@
ConditionState SimpleConditionTracker::getMetConditionDimension(
const std::vector<sp<ConditionTracker>>& allConditions,
const vector<Matcher>& dimensionFields,
+ const bool isSubOutputDimensionFields,
std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const {
ConditionState conditionState = mInitialValue;
if (dimensionFields.size() == 0 || mOutputDimensions.size() == 0 ||
@@ -424,10 +458,13 @@
conditionState = conditionState | sliceState;
if (sliceState == ConditionState::kTrue && dimensionFields.size() > 0) {
- vector<HashableDimensionKey> dimensionKeys;
- filterValues(dimensionFields, slice.first.getValues(), &dimensionKeys);
-
- dimensionsKeySet.insert(dimensionKeys.begin(), dimensionKeys.end());
+ if (isSubOutputDimensionFields) {
+ HashableDimensionKey dimensionKey;
+ filterValues(dimensionFields, slice.first.getValues(), &dimensionKey);
+ dimensionsKeySet.insert(dimensionKey);
+ } else {
+ dimensionsKeySet.insert(slice.first);
+ }
}
}
return conditionState;
diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.h b/cmds/statsd/src/condition/SimpleConditionTracker.h
index e4b72b8..47d1ece 100644
--- a/cmds/statsd/src/condition/SimpleConditionTracker.h
+++ b/cmds/statsd/src/condition/SimpleConditionTracker.h
@@ -49,12 +49,15 @@
void isConditionMet(const ConditionKey& conditionParameters,
const std::vector<sp<ConditionTracker>>& allConditions,
const vector<Matcher>& dimensionFields,
+ const bool isSubOutputDimensionFields,
+ const bool isPartialLink,
std::vector<ConditionState>& conditionCache,
std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const override;
ConditionState getMetConditionDimension(
const std::vector<sp<ConditionTracker>>& allConditions,
const vector<Matcher>& dimensionFields,
+ const bool isSubOutputDimensionFields,
std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const override;
virtual const std::set<HashableDimensionKey>* getChangedToTrueDimensions(
@@ -65,6 +68,7 @@
return nullptr;
}
}
+
virtual const std::set<HashableDimensionKey>* getChangedToFalseDimensions(
const std::vector<sp<ConditionTracker>>& allConditions) const {
if (mSliced) {
@@ -74,6 +78,26 @@
}
}
+ void getTrueSlicedDimensions(
+ const std::vector<sp<ConditionTracker>>& allConditions,
+ std::set<HashableDimensionKey>* dimensions) const override {
+ for (const auto& itr : mSlicedConditionState) {
+ if (itr.second > 0) {
+ dimensions->insert(itr.first);
+ }
+ }
+ }
+
+ bool IsChangedDimensionTrackable() const override { return true; }
+
+ bool IsSimpleCondition() const override { return true; }
+
+ bool equalOutputDimensions(
+ const std::vector<sp<ConditionTracker>>& allConditions,
+ const vector<Matcher>& dimensions) const override {
+ return equalDimensions(mOutputDimensions, dimensions);
+ }
+
private:
const ConfigKey mConfigKey;
// The index of the LogEventMatcher which defines the start.
@@ -92,6 +116,8 @@
std::vector<Matcher> mOutputDimensions;
+ bool mContainANYPositionInInternalDimensions;
+
std::set<HashableDimensionKey> mLastChangedToTrueDimensions;
std::set<HashableDimensionKey> mLastChangedToFalseDimensions;
diff --git a/cmds/statsd/src/condition/StateTracker.cpp b/cmds/statsd/src/condition/StateTracker.cpp
index e479f93..c68875c 100644
--- a/cmds/statsd/src/condition/StateTracker.cpp
+++ b/cmds/statsd/src/condition/StateTracker.cpp
@@ -107,7 +107,7 @@
// 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
ALOGE("Predicate %lld dropping data for dimension key %s",
- (long long)mConditionId, newKey.c_str());
+ (long long)mConditionId, newKey.toString().c_str());
return true;
}
}
@@ -181,7 +181,10 @@
void StateTracker::isConditionMet(
const ConditionKey& conditionParameters, const vector<sp<ConditionTracker>>& allConditions,
- const vector<Matcher>& dimensionFields, vector<ConditionState>& conditionCache,
+ const vector<Matcher>& dimensionFields,
+ const bool isSubOutputDimensionFields,
+ const bool isPartialLink,
+ vector<ConditionState>& conditionCache,
std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const {
if (conditionCache[mIndex] != ConditionState::kNotEvaluated) {
// it has been evaluated.
@@ -203,20 +206,19 @@
return;
}
- const auto& primaryKeys = pair->second;
+ const auto& primaryKey = pair->second;
conditionCache[mIndex] = mInitialValue;
- for (const auto& primaryKey : primaryKeys) {
- auto it = mSlicedState.find(primaryKey);
- if (it != mSlicedState.end()) {
- conditionCache[mIndex] = ConditionState::kTrue;
- dimensionsKeySet.insert(it->second);
- }
+ auto it = mSlicedState.find(primaryKey);
+ if (it != mSlicedState.end()) {
+ conditionCache[mIndex] = ConditionState::kTrue;
+ dimensionsKeySet.insert(it->second);
}
}
ConditionState StateTracker::getMetConditionDimension(
const std::vector<sp<ConditionTracker>>& allConditions,
const vector<Matcher>& dimensionFields,
+ const bool isSubOutputDimensionFields,
std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const {
if (mSlicedState.size() > 0) {
for (const auto& state : mSlicedState) {
diff --git a/cmds/statsd/src/condition/StateTracker.h b/cmds/statsd/src/condition/StateTracker.h
index 3fe6e60..2bdf98c 100644
--- a/cmds/statsd/src/condition/StateTracker.h
+++ b/cmds/statsd/src/condition/StateTracker.h
@@ -56,6 +56,8 @@
void isConditionMet(const ConditionKey& conditionParameters,
const std::vector<sp<ConditionTracker>>& allConditions,
const vector<Matcher>& dimensionFields,
+ const bool isSubOutputDimensionFields,
+ const bool isPartialLink,
std::vector<ConditionState>& conditionCache,
std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const override;
@@ -67,6 +69,7 @@
ConditionState getMetConditionDimension(
const std::vector<sp<ConditionTracker>>& allConditions,
const vector<Matcher>& dimensionFields,
+ const bool isSubOutputDimensionFields,
std::unordered_set<HashableDimensionKey>& dimensionsKeySet) const override;
virtual const std::set<HashableDimensionKey>* getChangedToTrueDimensions(
@@ -79,6 +82,24 @@
return &mLastChangedToFalseDimensions;
}
+ bool IsChangedDimensionTrackable() const override { return true; }
+
+ bool IsSimpleCondition() const override { return true; }
+
+ bool equalOutputDimensions(
+ const std::vector<sp<ConditionTracker>>& allConditions,
+ const vector<Matcher>& dimensions) const override {
+ return equalDimensions(mOutputDimensions, dimensions);
+ }
+
+ void getTrueSlicedDimensions(
+ const std::vector<sp<ConditionTracker>>& allConditions,
+ std::set<HashableDimensionKey>* dimensions) const override {
+ for (const auto& itr : mSlicedState) {
+ dimensions->insert(itr.second);
+ }
+ }
+
private:
const ConfigKey mConfigKey;
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp
index da5ef44..22b2a30 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp
@@ -71,6 +71,7 @@
if (metric.has_dimensions_in_what()) {
translateFieldMatcher(metric.dimensions_in_what(), &mDimensionsInWhat);
+ mContainANYPositionInDimensionsInWhat = HasPositionANY(metric.dimensions_in_what());
}
if (metric.has_dimensions_in_condition()) {
@@ -113,7 +114,7 @@
for (const auto& counter : mPastBuckets) {
const MetricDimensionKey& dimensionKey = counter.first;
- VLOG(" dimension key %s", dimensionKey.c_str());
+ VLOG(" dimension key %s", dimensionKey.toString().c_str());
uint64_t wrapperToken =
protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
@@ -176,7 +177,7 @@
// 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
ALOGE("CountMetric %lld dropping data for dimension key %s",
- (long long)mMetricId, newKey.c_str());
+ (long long)mMetricId, newKey.toString().c_str());
return true;
}
}
@@ -218,7 +219,7 @@
countWholeBucket);
}
- VLOG("metric %lld %s->%lld", (long long)mMetricId, eventKey.c_str(),
+ VLOG("metric %lld %s->%lld", (long long)mMetricId, eventKey.toString().c_str(),
(long long)(*mCurrentSlicedCounter)[eventKey]);
}
@@ -253,7 +254,8 @@
info.mCount = counter.second;
auto& bucketList = mPastBuckets[counter.first];
bucketList.push_back(info);
- VLOG("metric %lld, dump key value: %s -> %lld", (long long)mMetricId, counter.first.c_str(),
+ VLOG("metric %lld, dump key value: %s -> %lld", (long long)mMetricId,
+ counter.first.toString().c_str(),
(long long)counter.second);
}
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index 29a892d..0dd3f70 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -67,7 +67,8 @@
mStartIndex(startIndex),
mStopIndex(stopIndex),
mStopAllIndex(stopAllIndex),
- mNested(nesting) {
+ mNested(nesting),
+ mContainANYPositionInInternalDimensions(false) {
// TODO: The following boiler plate code appears in all MetricProducers, but we can't abstract
// them in the base class, because the proto generated CountMetric, and DurationMetric are
// not related. Maybe we should add a template in the future??
@@ -80,10 +81,12 @@
if (metric.has_dimensions_in_what()) {
translateFieldMatcher(metric.dimensions_in_what(), &mDimensionsInWhat);
+ mContainANYPositionInDimensionsInWhat = HasPositionANY(metric.dimensions_in_what());
}
if (internalDimensions.has_field()) {
translateFieldMatcher(internalDimensions, &mInternalDimensions);
+ mContainANYPositionInInternalDimensions = HasPositionANY(internalDimensions);
}
if (metric.has_dimensions_in_condition()) {
@@ -100,19 +103,18 @@
}
}
mConditionSliced = (metric.links().size() > 0) || (mDimensionsInCondition.size() > 0);
+ mUnSlicedPartCondition = ConditionState::kUnknown;
- if (mDimensionsInWhat.size() == mInternalDimensions.size()) {
- bool mUseWhatDimensionAsInternalDimension = true;
- for (size_t i = 0; mUseWhatDimensionAsInternalDimension &&
- i < mDimensionsInWhat.size(); ++i) {
- if (mDimensionsInWhat[i] != mInternalDimensions[i]) {
- mUseWhatDimensionAsInternalDimension = false;
- }
+ mUseWhatDimensionAsInternalDimension = equalDimensions(mDimensionsInWhat, mInternalDimensions);
+ if (mWizard != nullptr && mConditionTrackerIndex >= 0) {
+ mSameConditionDimensionsInTracker =
+ mWizard->equalOutputDimensions(mConditionTrackerIndex, mDimensionsInCondition);
+ if (mMetric2ConditionLinks.size() == 1) {
+ mHasLinksToAllConditionDimensionsInTracker =
+ mWizard->equalOutputDimensions(mConditionTrackerIndex,
+ mMetric2ConditionLinks.begin()->conditionFields);
}
- } else {
- mUseWhatDimensionAsInternalDimension = false;
}
-
VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(),
(long long)mBucketSizeNs, (long long)mStartTimeNs);
}
@@ -139,12 +141,171 @@
return make_unique<OringDurationTracker>(
mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex,
mDimensionsInCondition, mNested, mCurrentBucketStartTimeNs, mCurrentBucketNum,
- mStartTimeNs, mBucketSizeNs, mConditionSliced, mAnomalyTrackers);
+ mStartTimeNs, mBucketSizeNs, mConditionSliced,
+ mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers);
case DurationMetric_AggregationType_MAX_SPARSE:
return make_unique<MaxDurationTracker>(
mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex,
mDimensionsInCondition, mNested, mCurrentBucketStartTimeNs, mCurrentBucketNum,
- mStartTimeNs, mBucketSizeNs, mConditionSliced, mAnomalyTrackers);
+ mStartTimeNs, mBucketSizeNs, mConditionSliced,
+ mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers);
+ }
+}
+
+// SlicedConditionChange optimization case 1:
+// 1. If combination condition, logical operation is AND, only one sliced child predicate.
+// 2. No condition in dimension
+// 3. The links covers all dimension fields in the sliced child condition predicate.
+void DurationMetricProducer::onSlicedConditionMayChangeLocked_opt1(const uint64_t eventTime) {
+ if (mMetric2ConditionLinks.size() != 1 ||
+ !mHasLinksToAllConditionDimensionsInTracker ||
+ !mDimensionsInCondition.empty()) {
+ return;
+ }
+
+ bool currentUnSlicedPartCondition = true;
+ if (!mWizard->IsSimpleCondition(mConditionTrackerIndex)) {
+ ConditionState unslicedPartState =
+ mWizard->getUnSlicedPartConditionState(mConditionTrackerIndex);
+ // When the unsliced part is still false, return directly.
+ if (mUnSlicedPartCondition == ConditionState::kFalse &&
+ unslicedPartState == ConditionState::kFalse) {
+ return;
+ }
+ mUnSlicedPartCondition = unslicedPartState;
+ currentUnSlicedPartCondition = mUnSlicedPartCondition > 0;
+ }
+
+ auto dimensionsChangedToTrue = mWizard->getChangedToTrueDimensions(mConditionTrackerIndex);
+ auto dimensionsChangedToFalse = mWizard->getChangedToFalseDimensions(mConditionTrackerIndex);
+
+ // The condition change is from the unsliced predicates.
+ // We need to find out the true dimensions from the sliced predicate and flip their condition
+ // state based on the new unsliced condition state.
+ if (dimensionsChangedToTrue == nullptr || dimensionsChangedToFalse == nullptr ||
+ (dimensionsChangedToTrue->empty() && dimensionsChangedToFalse->empty())) {
+ std::set<HashableDimensionKey> trueConditionDimensions;
+ mWizard->getTrueSlicedDimensions(mConditionTrackerIndex, &trueConditionDimensions);
+ for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
+ HashableDimensionKey linkedConditionDimensionKey;
+ getDimensionForCondition(whatIt.first.getValues(),
+ mMetric2ConditionLinks[0],
+ &linkedConditionDimensionKey);
+ if (trueConditionDimensions.find(linkedConditionDimensionKey) !=
+ trueConditionDimensions.end()) {
+ for (auto& condIt : whatIt.second) {
+ condIt.second->onConditionChanged(
+ currentUnSlicedPartCondition, eventTime);
+ }
+ }
+ }
+ } else {
+ // Handle the condition change from the sliced predicate.
+ if (currentUnSlicedPartCondition) {
+ for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
+ HashableDimensionKey linkedConditionDimensionKey;
+ getDimensionForCondition(whatIt.first.getValues(),
+ mMetric2ConditionLinks[0],
+ &linkedConditionDimensionKey);
+ if (dimensionsChangedToTrue->find(linkedConditionDimensionKey) !=
+ dimensionsChangedToTrue->end()) {
+ for (auto& condIt : whatIt.second) {
+ condIt.second->onConditionChanged(true, eventTime);
+ }
+ }
+ if (dimensionsChangedToFalse->find(linkedConditionDimensionKey) !=
+ dimensionsChangedToFalse->end()) {
+ for (auto& condIt : whatIt.second) {
+ condIt.second->onConditionChanged(false, eventTime);
+ }
+ }
+ }
+ }
+ }
+}
+
+
+// SlicedConditionChange optimization case 2:
+// 1. If combination condition, logical operation is AND, only one sliced child predicate.
+// 2. Has dimensions_in_condition and it equals to the output dimensions of the sliced predicate.
+void DurationMetricProducer::onSlicedConditionMayChangeLocked_opt2(const uint64_t eventTime) {
+ if (mMetric2ConditionLinks.size() > 1 || !mSameConditionDimensionsInTracker) {
+ return;
+ }
+
+ auto dimensionsChangedToTrue = mWizard->getChangedToTrueDimensions(mConditionTrackerIndex);
+ auto dimensionsChangedToFalse = mWizard->getChangedToFalseDimensions(mConditionTrackerIndex);
+
+ bool currentUnSlicedPartCondition = true;
+ if (!mWizard->IsSimpleCondition(mConditionTrackerIndex)) {
+ ConditionState unslicedPartState =
+ mWizard->getUnSlicedPartConditionState(mConditionTrackerIndex);
+ // When the unsliced part is still false, return directly.
+ if (mUnSlicedPartCondition == ConditionState::kFalse &&
+ unslicedPartState == ConditionState::kFalse) {
+ return;
+ }
+ mUnSlicedPartCondition = unslicedPartState;
+ currentUnSlicedPartCondition = mUnSlicedPartCondition > 0;
+ }
+
+ const std::set<HashableDimensionKey>* trueDimensionsToProcess = nullptr;
+ const std::set<HashableDimensionKey>* falseDimensionsToProcess = nullptr;
+
+ std::set<HashableDimensionKey> currentTrueConditionDimensions;
+ if (dimensionsChangedToTrue == nullptr || dimensionsChangedToFalse == nullptr ||
+ (dimensionsChangedToTrue->empty() && dimensionsChangedToFalse->empty())) {
+ mWizard->getTrueSlicedDimensions(mConditionTrackerIndex, ¤tTrueConditionDimensions);
+ trueDimensionsToProcess = ¤tTrueConditionDimensions;
+ } else if (currentUnSlicedPartCondition) {
+ // Handles the condition change from the sliced predicate. If the unsliced condition state
+ // is not true, not need to do anything.
+ trueDimensionsToProcess = dimensionsChangedToTrue;
+ falseDimensionsToProcess = dimensionsChangedToFalse;
+ }
+
+ if (trueDimensionsToProcess == nullptr && falseDimensionsToProcess == nullptr) {
+ return;
+ }
+
+ for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
+ if (falseDimensionsToProcess != nullptr) {
+ for (const auto& changedDim : *falseDimensionsToProcess) {
+ auto condIt = whatIt.second.find(changedDim);
+ if (condIt != whatIt.second.end()) {
+ condIt->second->onConditionChanged(false, eventTime);
+ }
+ }
+ }
+ if (trueDimensionsToProcess != nullptr) {
+ HashableDimensionKey linkedConditionDimensionKey;
+ if (!trueDimensionsToProcess->empty() && mMetric2ConditionLinks.size() == 1) {
+ getDimensionForCondition(whatIt.first.getValues(),
+ mMetric2ConditionLinks[0],
+ &linkedConditionDimensionKey);
+ }
+ for (auto& trueDim : *trueDimensionsToProcess) {
+ auto condIt = whatIt.second.find(trueDim);
+ if (condIt != whatIt.second.end()) {
+ condIt->second->onConditionChanged(
+ currentUnSlicedPartCondition, eventTime);
+ } else {
+ if (mMetric2ConditionLinks.size() == 0 ||
+ trueDim.contains(linkedConditionDimensionKey)) {
+ if (!whatIt.second.empty()) {
+ unique_ptr<DurationTracker> newTracker =
+ whatIt.second.begin()->second->clone(eventTime);
+ if (newTracker != nullptr) {
+ newTracker->setEventKey(
+ MetricDimensionKey(whatIt.first, trueDim));
+ newTracker->onConditionChanged(true, eventTime);
+ whatIt.second[trueDim] = std::move(newTracker);
+ }
+ }
+ }
+ }
+ }
+ }
}
}
@@ -152,6 +313,23 @@
VLOG("Metric %lld onSlicedConditionMayChange", (long long)mMetricId);
flushIfNeededLocked(eventTime);
+ if (!mConditionSliced) {
+ return;
+ }
+
+ bool changeDimTrackable = mWizard->IsChangedDimensionTrackable(mConditionTrackerIndex);
+ if (changeDimTrackable && mHasLinksToAllConditionDimensionsInTracker &&
+ mDimensionsInCondition.empty()) {
+ onSlicedConditionMayChangeLocked_opt1(eventTime);
+ return;
+ }
+
+ if (changeDimTrackable && mSameConditionDimensionsInTracker &&
+ mMetric2ConditionLinks.size() <= 1) {
+ onSlicedConditionMayChangeLocked_opt2(eventTime);
+ return;
+ }
+
// Now for each of the on-going event, check if the condition has changed for them.
for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
for (auto& pair : whatIt.second) {
@@ -166,6 +344,7 @@
if (mMetric2ConditionLinks.empty()) {
std::unordered_set<HashableDimensionKey> conditionDimensionsKeySet;
mWizard->getMetConditionDimension(mConditionTrackerIndex, mDimensionsInCondition,
+ !mSameConditionDimensionsInTracker,
&conditionDimensionsKeySet);
for (const auto& whatIt : mCurrentSlicedDurationTrackerMap) {
for (const auto& pair : whatIt.second) {
@@ -177,9 +356,12 @@
if (!whatIt.second.empty()) {
unique_ptr<DurationTracker> newTracker =
whatIt.second.begin()->second->clone(eventTime);
- newTracker->setEventKey(MetricDimensionKey(whatIt.first, conditionDimension));
- newTracker->onSlicedConditionMayChange(eventTime);
- whatIt.second[conditionDimension] = std::move(newTracker);
+ if (newTracker != nullptr) {
+ newTracker->setEventKey(MetricDimensionKey(
+ whatIt.first, conditionDimension));
+ newTracker->onSlicedConditionMayChange(eventTime);
+ whatIt.second[conditionDimension] = std::move(newTracker);
+ }
}
}
}
@@ -192,15 +374,20 @@
}
std::unordered_set<HashableDimensionKey> conditionDimensionsKeys;
mWizard->query(mConditionTrackerIndex, conditionKey, mDimensionsInCondition,
+ !mSameConditionDimensionsInTracker,
+ !mHasLinksToAllConditionDimensionsInTracker,
&conditionDimensionsKeys);
for (const auto& conditionDimension : conditionDimensionsKeys) {
if (!whatIt.second.empty() &&
whatIt.second.find(conditionDimension) == whatIt.second.end()) {
auto newTracker = whatIt.second.begin()->second->clone(eventTime);
- newTracker->setEventKey(MetricDimensionKey(whatIt.first, conditionDimension));
- newTracker->onSlicedConditionMayChange(eventTime);
- whatIt.second[conditionDimension] = std::move(newTracker);
+ if (newTracker != nullptr) {
+ newTracker->setEventKey(
+ MetricDimensionKey(whatIt.first, conditionDimension));
+ newTracker->onSlicedConditionMayChange(eventTime);
+ whatIt.second[conditionDimension] = std::move(newTracker);
+ }
}
}
}
@@ -241,7 +428,7 @@
for (const auto& pair : mPastBuckets) {
const MetricDimensionKey& dimensionKey = pair.first;
- VLOG(" dimension key %s", dimensionKey.c_str());
+ VLOG(" dimension key %s", dimensionKey.toString().c_str());
uint64_t wrapperToken =
protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
@@ -291,7 +478,8 @@
whatIt != mCurrentSlicedDurationTrackerMap.end();) {
for (auto it = whatIt->second.begin(); it != whatIt->second.end();) {
if (it->second->flushIfNeeded(eventTimeNs, &mPastBuckets)) {
- VLOG("erase bucket for key %s %s", whatIt->first.c_str(), it->first.c_str());
+ VLOG("erase bucket for key %s %s",
+ whatIt->first.toString().c_str(), it->first.toString().c_str());
it = whatIt->second.erase(it);
} else {
++it;
@@ -314,7 +502,8 @@
whatIt != mCurrentSlicedDurationTrackerMap.end();) {
for (auto it = whatIt->second.begin(); it != whatIt->second.end();) {
if (it->second->flushCurrentBucket(eventTimeNs, &mPastBuckets)) {
- VLOG("erase bucket for key %s %s", whatIt->first.c_str(), it->first.c_str());
+ VLOG("erase bucket for key %s %s", whatIt->first.toString().c_str(),
+ it->first.toString().c_str());
it = whatIt->second.erase(it);
} else {
++it;
@@ -338,7 +527,8 @@
if (verbose) {
for (const auto& whatIt : mCurrentSlicedDurationTrackerMap) {
for (const auto& slice : whatIt.second) {
- fprintf(out, "\t%s\t%s\n", whatIt.first.c_str(), slice.first.c_str());
+ fprintf(out, "\t(what)%s\t(condition)%s\n", whatIt.first.toString().c_str(),
+ slice.first.toString().c_str());
slice.second->dumpStates(out, verbose);
}
}
@@ -353,7 +543,7 @@
// 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
ALOGE("DurationMetric %lld dropping data for dimension key %s",
- (long long)mMetricId, newKey.c_str());
+ (long long)mMetricId, newKey.toString().c_str());
return true;
}
}
@@ -388,14 +578,21 @@
return;
}
- std::vector<HashableDimensionKey> values;
- filterValues(mInternalDimensions, event.getValues(), &values);
- if (values.empty()) {
+ if (mInternalDimensions.empty()) {
it->second->noteStart(DEFAULT_DIMENSION_KEY, condition,
event.GetElapsedTimestampNs(), conditionKeys);
} else {
- for (const auto& value : values) {
- it->second->noteStart(value, condition, event.GetElapsedTimestampNs(), conditionKeys);
+ if (mContainANYPositionInInternalDimensions) {
+ std::vector<HashableDimensionKey> dimensionKeys;
+ filterValues(mInternalDimensions, event.getValues(), &dimensionKeys);
+ for (const auto& key : dimensionKeys) {
+ it->second->noteStart(key, condition, event.GetElapsedTimestampNs(), conditionKeys);
+ }
+ } else {
+ HashableDimensionKey dimensionKey = DEFAULT_DIMENSION_KEY;
+ filterValues(mInternalDimensions, event.getValues(), &dimensionKey);
+ it->second->noteStart(
+ dimensionKey, condition, event.GetElapsedTimestampNs(), conditionKeys);
}
}
@@ -408,8 +605,113 @@
ALOGW("Not used in duration tracker.");
}
+void DurationMetricProducer::onMatchedLogEventLocked_simple(const size_t matcherIndex,
+ const LogEvent& event) {
+ uint64_t eventTimeNs = event.GetElapsedTimestampNs();
+ if (eventTimeNs < mStartTimeNs) {
+ return;
+ }
+
+ flushIfNeededLocked(event.GetElapsedTimestampNs());
+
+ // Handles Stopall events.
+ if (matcherIndex == mStopAllIndex) {
+ for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
+ for (auto& pair : whatIt.second) {
+ pair.second->noteStopAll(event.GetElapsedTimestampNs());
+ }
+ }
+ return;
+ }
+
+ HashableDimensionKey dimensionInWhat;
+ if (!mDimensionsInWhat.empty()) {
+ filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhat);
+ } else {
+ dimensionInWhat = DEFAULT_DIMENSION_KEY;
+ }
+
+ // Handles Stop events.
+ if (matcherIndex == mStopIndex) {
+ if (mUseWhatDimensionAsInternalDimension) {
+ auto whatIt = mCurrentSlicedDurationTrackerMap.find(dimensionInWhat);
+ if (whatIt != mCurrentSlicedDurationTrackerMap.end()) {
+ for (const auto& condIt : whatIt->second) {
+ condIt.second->noteStop(dimensionInWhat, event.GetElapsedTimestampNs(), false);
+ }
+ }
+ return;
+ }
+
+ HashableDimensionKey internalDimensionKey = DEFAULT_DIMENSION_KEY;
+ if (!mInternalDimensions.empty()) {
+ filterValues(mInternalDimensions, event.getValues(), &internalDimensionKey);
+ }
+
+ auto whatIt = mCurrentSlicedDurationTrackerMap.find(dimensionInWhat);
+ if (whatIt != mCurrentSlicedDurationTrackerMap.end()) {
+ for (const auto& condIt : whatIt->second) {
+ condIt.second->noteStop(
+ internalDimensionKey, event.GetElapsedTimestampNs(), false);
+ }
+ }
+ return;
+ }
+
+ bool condition;
+ ConditionKey conditionKey;
+ std::unordered_set<HashableDimensionKey> dimensionKeysInCondition;
+ if (mConditionSliced) {
+ for (const auto& link : mMetric2ConditionLinks) {
+ getDimensionForCondition(event.getValues(), link, &conditionKey[link.conditionId]);
+ }
+
+ auto conditionState =
+ mWizard->query(mConditionTrackerIndex, conditionKey, mDimensionsInCondition,
+ !mSameConditionDimensionsInTracker,
+ !mHasLinksToAllConditionDimensionsInTracker,
+ &dimensionKeysInCondition);
+ condition = (conditionState == ConditionState::kTrue);
+ if (mDimensionsInCondition.empty() && condition) {
+ dimensionKeysInCondition.insert(DEFAULT_DIMENSION_KEY);
+ }
+ } else {
+ condition = mCondition;
+ if (condition) {
+ dimensionKeysInCondition.insert(DEFAULT_DIMENSION_KEY);
+ }
+ }
+
+ if (dimensionKeysInCondition.empty()) {
+ handleStartEvent(MetricDimensionKey(dimensionInWhat, DEFAULT_DIMENSION_KEY),
+ conditionKey, condition, event);
+ } else {
+ auto whatIt = mCurrentSlicedDurationTrackerMap.find(dimensionInWhat);
+ // If the what dimension is already there, we should update all the trackers even
+ // the condition is false.
+ if (whatIt != mCurrentSlicedDurationTrackerMap.end()) {
+ for (const auto& condIt : whatIt->second) {
+ const bool cond = dimensionKeysInCondition.find(condIt.first) !=
+ dimensionKeysInCondition.end();
+ handleStartEvent(MetricDimensionKey(dimensionInWhat, condIt.first),
+ conditionKey, cond, event);
+ dimensionKeysInCondition.erase(condIt.first);
+ }
+ }
+ for (const auto& conditionDimension : dimensionKeysInCondition) {
+ handleStartEvent(MetricDimensionKey(dimensionInWhat, conditionDimension), conditionKey,
+ condition, event);
+ }
+ }
+}
+
void DurationMetricProducer::onMatchedLogEventLocked(const size_t matcherIndex,
const LogEvent& event) {
+ if (!mContainANYPositionInDimensionsInWhat) {
+ onMatchedLogEventLocked_simple(matcherIndex, event);
+ return;
+ }
+
uint64_t eventTimeNs = event.GetElapsedTimestampNs();
if (eventTimeNs < mStartTimeNs) {
return;
@@ -448,19 +750,17 @@
return;
}
- std::vector<HashableDimensionKey> internalDimensionKeys;
- filterValues(mInternalDimensions, event.getValues(), &internalDimensionKeys);
- if (internalDimensionKeys.empty()) {
- internalDimensionKeys.push_back(DEFAULT_DIMENSION_KEY);
+ HashableDimensionKey internalDimensionKey = DEFAULT_DIMENSION_KEY;
+ if (!mInternalDimensions.empty()) {
+ filterValues(mInternalDimensions, event.getValues(), &internalDimensionKey);
}
+
for (const HashableDimensionKey& whatDimension : dimensionInWhatValues) {
auto whatIt = mCurrentSlicedDurationTrackerMap.find(whatDimension);
if (whatIt != mCurrentSlicedDurationTrackerMap.end()) {
for (const auto& condIt : whatIt->second) {
- for (const auto& internalDimensionKey : internalDimensionKeys) {
- condIt.second->noteStop(
- internalDimensionKey, event.GetElapsedTimestampNs(), false);
- }
+ condIt.second->noteStop(
+ internalDimensionKey, event.GetElapsedTimestampNs(), false);
}
}
}
@@ -477,6 +777,8 @@
auto conditionState =
mWizard->query(mConditionTrackerIndex, conditionKey, mDimensionsInCondition,
+ !mSameConditionDimensionsInTracker,
+ !mHasLinksToAllConditionDimensionsInTracker,
&dimensionKeysInCondition);
condition = (conditionState == ConditionState::kTrue);
if (mDimensionsInCondition.empty() && condition) {
@@ -490,32 +792,30 @@
}
for (const auto& whatDimension : dimensionInWhatValues) {
- auto whatIt = mCurrentSlicedDurationTrackerMap.find(whatDimension);
- // If the what dimension is already there, we should update all the trackers even
- // the condition is false.
- if (whatIt != mCurrentSlicedDurationTrackerMap.end()) {
- for (const auto& condIt : whatIt->second) {
- const bool cond = dimensionKeysInCondition.find(condIt.first) !=
- dimensionKeysInCondition.end();
- handleStartEvent(MetricDimensionKey(whatDimension, condIt.first),
- conditionKey, cond, event);
- }
+ if (dimensionKeysInCondition.empty()) {
+ handleStartEvent(MetricDimensionKey(whatDimension, DEFAULT_DIMENSION_KEY),
+ conditionKey, condition, event);
} else {
- // If it is a new what dimension key, we need to handle the start events for all current
- // condition dimensions.
+ auto whatIt = mCurrentSlicedDurationTrackerMap.find(whatDimension);
+ // If the what dimension is already there, we should update all the trackers even
+ // the condition is false.
+ if (whatIt != mCurrentSlicedDurationTrackerMap.end()) {
+ for (const auto& condIt : whatIt->second) {
+ const bool cond = dimensionKeysInCondition.find(condIt.first) !=
+ dimensionKeysInCondition.end();
+ handleStartEvent(MetricDimensionKey(whatDimension, condIt.first),
+ conditionKey, cond, event);
+ dimensionKeysInCondition.erase(condIt.first);
+ }
+ }
for (const auto& conditionDimension : dimensionKeysInCondition) {
handleStartEvent(MetricDimensionKey(whatDimension, conditionDimension),
conditionKey, condition, event);
}
}
- if (dimensionKeysInCondition.empty()) {
- handleStartEvent(MetricDimensionKey(whatDimension, DEFAULT_DIMENSION_KEY),
- conditionKey, condition, event);
- }
}
}
-
size_t DurationMetricProducer::byteSizeLocked() const {
size_t totalSize = 0;
for (const auto& pair : mPastBuckets) {
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
index 23408a7..6746e11 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -51,6 +51,9 @@
protected:
void onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event) override;
+
+ void onMatchedLogEventLocked_simple(const size_t matcherIndex, const LogEvent& event);
+
void onMatchedLogEventInternalLocked(
const size_t matcherIndex, const MetricDimensionKey& eventKey,
const ConditionKey& conditionKeys, bool condition,
@@ -69,6 +72,9 @@
// Internal interface to handle sliced condition change.
void onSlicedConditionMayChangeLocked(const uint64_t eventTime) override;
+ void onSlicedConditionMayChangeLocked_opt1(const uint64_t eventTime);
+ void onSlicedConditionMayChangeLocked_opt2(const uint64_t eventTime);
+
// Internal function to calculate the current used bytes.
size_t byteSizeLocked() const override;
@@ -98,9 +104,14 @@
// The dimension from the atom predicate. e.g., uid, wakelock name.
vector<Matcher> mInternalDimensions;
+ bool mContainANYPositionInInternalDimensions;
+
// This boolean is true iff When mInternalDimensions == mDimensionsInWhat
bool mUseWhatDimensionAsInternalDimension;
+ // Caches the current unsliced part condition.
+ ConditionState mUnSlicedPartCondition;
+
// Save the past buckets and we can clear when the StatsLogReport is dumped.
// TODO: Add a lock to mPastBuckets.
std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>> mPastBuckets;
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
index 288f563c..e479e5c 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -83,6 +83,7 @@
// TODO: use UidMap if uid->pkg_name is required
if (metric.has_dimensions_in_what()) {
translateFieldMatcher(metric.dimensions_in_what(), &mDimensionsInWhat);
+ mContainANYPositionInDimensionsInWhat = HasPositionANY(metric.dimensions_in_what());
}
if (metric.has_dimensions_in_condition()) {
@@ -140,7 +141,7 @@
for (const auto& pair : mPastBuckets) {
const MetricDimensionKey& dimensionKey = pair.first;
- VLOG(" dimension key %s", dimensionKey.c_str());
+ VLOG(" dimension key %s", dimensionKey.toString().c_str());
uint64_t wrapperToken =
protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
@@ -283,7 +284,7 @@
// 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
ALOGE("GaugeMetric %lld dropping data for dimension key %s",
- (long long)mMetricId, newKey.c_str());
+ (long long)mMetricId, newKey.toString().c_str());
return true;
}
}
@@ -398,7 +399,8 @@
info.mGaugeAtoms = slice.second;
auto& bucketList = mPastBuckets[slice.first];
bucketList.push_back(info);
- VLOG("gauge metric %lld, dump key value: %s", (long long)mMetricId, slice.first.c_str());
+ VLOG("gauge metric %lld, dump key value: %s", (long long)mMetricId,
+ slice.first.toString().c_str());
}
// If we have anomaly trackers, we need to update the partial bucket values.
diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp
index 18694a1..6c90b03 100644
--- a/cmds/statsd/src/metrics/MetricProducer.cpp
+++ b/cmds/statsd/src/metrics/MetricProducer.cpp
@@ -39,9 +39,10 @@
for (const auto& link : mMetric2ConditionLinks) {
getDimensionForCondition(event.getValues(), link, &conditionKey[link.conditionId]);
}
-
auto conditionState =
mWizard->query(mConditionTrackerIndex, conditionKey, mDimensionsInCondition,
+ !mSameConditionDimensionsInTracker,
+ !mHasLinksToAllConditionDimensionsInTracker,
&dimensionKeysInCondition);
condition = (conditionState == ConditionState::kTrue);
} else {
@@ -52,25 +53,41 @@
dimensionKeysInCondition.insert(DEFAULT_DIMENSION_KEY);
}
- vector<HashableDimensionKey> dimensionInWhatValues;
- if (!mDimensionsInWhat.empty()) {
- filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhatValues);
- } else {
- dimensionInWhatValues.push_back(DEFAULT_DIMENSION_KEY);
- }
+ if (mContainANYPositionInDimensionsInWhat) {
+ vector<HashableDimensionKey> dimensionInWhatValues;
+ if (!mDimensionsInWhat.empty()) {
+ filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhatValues);
+ } else {
+ dimensionInWhatValues.push_back(DEFAULT_DIMENSION_KEY);
+ }
- for (const auto& whatDimension : dimensionInWhatValues) {
+ for (const auto& whatDimension : dimensionInWhatValues) {
+ for (const auto& conditionDimensionKey : dimensionKeysInCondition) {
+ onMatchedLogEventInternalLocked(
+ matcherIndex, MetricDimensionKey(whatDimension, conditionDimensionKey),
+ conditionKey, condition, event);
+ }
+ if (dimensionKeysInCondition.empty()) {
+ onMatchedLogEventInternalLocked(
+ matcherIndex, MetricDimensionKey(whatDimension, DEFAULT_DIMENSION_KEY),
+ conditionKey, condition, event);
+ }
+ }
+ } else {
+ HashableDimensionKey dimensionInWhat;
+ filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhat);
+ MetricDimensionKey metricKey(dimensionInWhat, DEFAULT_DIMENSION_KEY);
for (const auto& conditionDimensionKey : dimensionKeysInCondition) {
+ metricKey.setDimensionKeyInCondition(conditionDimensionKey);
onMatchedLogEventInternalLocked(
- matcherIndex, MetricDimensionKey(whatDimension, conditionDimensionKey),
- conditionKey, condition, event);
+ matcherIndex, metricKey, conditionKey, condition, event);
}
if (dimensionKeysInCondition.empty()) {
onMatchedLogEventInternalLocked(
- matcherIndex, MetricDimensionKey(whatDimension, DEFAULT_DIMENSION_KEY),
- conditionKey, condition, event);
+ matcherIndex, metricKey, conditionKey, condition, event);
}
}
+
}
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index 05b7f87..ea45f43 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -50,7 +50,11 @@
mCondition(conditionIndex >= 0 ? false : true),
mConditionSliced(false),
mWizard(wizard),
- mConditionTrackerIndex(conditionIndex){};
+ mConditionTrackerIndex(conditionIndex),
+ mContainANYPositionInDimensionsInWhat(false),
+ mSameConditionDimensionsInTracker(false),
+ mHasLinksToAllConditionDimensionsInTracker(false) {
+ }
virtual ~MetricProducer(){};
@@ -219,6 +223,16 @@
vector<Matcher> mDimensionsInWhat; // The dimensions_in_what defined in statsd_config
vector<Matcher> mDimensionsInCondition; // The dimensions_in_condition defined in statsd_config
+ bool mContainANYPositionInDimensionsInWhat;
+
+ // True iff the condition dimensions equal to the sliced dimensions in the simple condition
+ // tracker. This field is always false for combinational condition trackers.
+ bool mSameConditionDimensionsInTracker;
+
+ // True iff the metric to condition links cover all dimension fields in the condition tracker.
+ // This field is always false for combinational condition trackers.
+ bool mHasLinksToAllConditionDimensionsInTracker;
+
std::vector<Metric2Condition> mMetric2ConditionLinks;
std::vector<sp<AnomalyTracker>> mAnomalyTrackers;
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index a32e037..dbab814 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -160,10 +160,18 @@
FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks);
FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSlice);
FRIEND_TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent);
- FRIEND_TEST(DimensionInConditionE2eTest, TestCountMetricNoLink);
- FRIEND_TEST(DimensionInConditionE2eTest, TestCountMetricWithLink);
- FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetricNoLink);
- FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetricWithLink);
+ FRIEND_TEST(DimensionInConditionE2eTest, TestCreateCountMetric_NoLink_OR_CombinationCondition);
+ FRIEND_TEST(DimensionInConditionE2eTest, TestCreateCountMetric_Link_OR_CombinationCondition);
+ FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_NoLink_OR_CombinationCondition);
+ FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_Link_OR_CombinationCondition);
+
+ FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_NoLink_SimpleCondition);
+ FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_Link_SimpleCondition);
+ FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_PartialLink_SimpleCondition);
+ FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_NoLink_AND_CombinationCondition);
+ FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_Link_AND_CombinationCondition);
+ FRIEND_TEST(DimensionInConditionE2eTest, TestDurationMetric_PartialLink_AND_CombinationCondition);
+
};
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
index e88daf7..09913dc 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -80,6 +80,7 @@
mBucketSizeNs = bucketSizeMills * 1000000;
if (metric.has_dimensions_in_what()) {
translateFieldMatcher(metric.dimensions_in_what(), &mDimensionsInWhat);
+ mContainANYPositionInDimensionsInWhat = HasPositionANY(metric.dimensions_in_what());
}
if (metric.has_dimensions_in_condition()) {
@@ -146,7 +147,7 @@
for (const auto& pair : mPastBuckets) {
const MetricDimensionKey& dimensionKey = pair.first;
- VLOG(" dimension key %s", dimensionKey.c_str());
+ VLOG(" dimension key %s", dimensionKey.toString().c_str());
uint64_t wrapperToken =
protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
@@ -254,7 +255,7 @@
// 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
ALOGE("ValueMetric %lld dropping data for dimension key %s",
- (long long)mMetricId, newKey.c_str());
+ (long long)mMetricId, newKey.toString().c_str());
return true;
}
}
diff --git a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
index 8f236fa..7b3393f 100644
--- a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
@@ -64,7 +64,7 @@
sp<ConditionWizard> wizard, int conditionIndex,
const std::vector<Matcher>& dimensionInCondition, bool nesting,
uint64_t currentBucketStartNs, uint64_t currentBucketNum, uint64_t startTimeNs,
- uint64_t bucketSizeNs, bool conditionSliced,
+ uint64_t bucketSizeNs, bool conditionSliced, bool fullLink,
const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
: mConfigKey(key),
mTrackerId(id),
@@ -80,6 +80,7 @@
mCurrentBucketNum(currentBucketNum),
mStartTimeNs(startTimeNs),
mConditionSliced(conditionSliced),
+ mHasLinksToAllConditionDimensionsInTracker(fullLink),
mAnomalyTrackers(anomalyTrackers){};
virtual ~DurationTracker(){};
@@ -198,6 +199,9 @@
const bool mConditionSliced;
+ bool mSameConditionDimensionsInTracker;
+ bool mHasLinksToAllConditionDimensionsInTracker;
+
std::vector<sp<DurationAnomalyTracker>> mAnomalyTrackers;
FRIEND_TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp);
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
index b225560..8e0bf26 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
@@ -30,20 +30,33 @@
const vector<Matcher>& dimensionInCondition, bool nesting,
uint64_t currentBucketStartNs, uint64_t currentBucketNum,
uint64_t startTimeNs, uint64_t bucketSizeNs,
- bool conditionSliced,
+ bool conditionSliced, bool fullLink,
const vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
: DurationTracker(key, id, eventKey, wizard, conditionIndex, dimensionInCondition, nesting,
currentBucketStartNs, currentBucketNum, startTimeNs, bucketSizeNs,
- conditionSliced, anomalyTrackers) {
+ conditionSliced, fullLink, anomalyTrackers) {
+ if (mWizard != nullptr) {
+ mSameConditionDimensionsInTracker =
+ mWizard->equalOutputDimensions(conditionIndex, mDimensionInCondition);
+ }
}
unique_ptr<DurationTracker> MaxDurationTracker::clone(const uint64_t eventTime) {
auto clonedTracker = make_unique<MaxDurationTracker>(*this);
- for (auto it = clonedTracker->mInfos.begin(); it != clonedTracker->mInfos.end(); ++it) {
- it->second.lastStartTime = eventTime;
- it->second.lastDuration = 0;
+ for (auto it = clonedTracker->mInfos.begin(); it != clonedTracker->mInfos.end();) {
+ if (it->second.state != kStopped) {
+ it->second.lastStartTime = eventTime;
+ it->second.lastDuration = 0;
+ it++;
+ } else {
+ it = clonedTracker->mInfos.erase(it);
+ }
}
- return clonedTracker;
+ if (clonedTracker->mInfos.empty()) {
+ return nullptr;
+ } else {
+ return clonedTracker;
+ }
}
bool MaxDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) {
@@ -59,7 +72,7 @@
// 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
ALOGE("MaxDurTracker %lld dropping data for dimension key %s",
- (long long)mTrackerId, newKey.c_str());
+ (long long)mTrackerId, newKey.toString().c_str());
return true;
}
}
@@ -77,7 +90,7 @@
if (mConditionSliced) {
duration.conditionKeys = conditionKey;
}
- VLOG("MaxDuration: key %s start condition %d", key.c_str(), condition);
+ VLOG("MaxDuration: key %s start condition %d", key.toString().c_str(), condition);
switch (duration.state) {
case kStarted:
@@ -103,7 +116,7 @@
void MaxDurationTracker::noteStop(const HashableDimensionKey& key, const uint64_t eventTime,
bool forceStop) {
- VLOG("MaxDuration: key %s stop", key.c_str());
+ VLOG("MaxDuration: key %s stop", key.toString().c_str());
if (mInfos.find(key) == mInfos.end()) {
// we didn't see a start event before. do nothing.
return;
@@ -120,7 +133,7 @@
stopAnomalyAlarm();
duration.state = DurationState::kStopped;
int64_t durationTime = eventTime - duration.lastStartTime;
- VLOG("Max, key %s, Stop %lld %lld %lld", key.c_str(),
+ VLOG("Max, key %s, Stop %lld %lld %lld", key.toString().c_str(),
(long long)duration.lastStartTime, (long long)eventTime,
(long long)durationTime);
duration.lastDuration += durationTime;
@@ -241,13 +254,15 @@
std::unordered_set<HashableDimensionKey> conditionDimensionKeySet;
ConditionState conditionState = mWizard->query(
mConditionTrackerIndex, pair.second.conditionKeys, mDimensionInCondition,
+ !mSameConditionDimensionsInTracker,
+ !mHasLinksToAllConditionDimensionsInTracker,
&conditionDimensionKeySet);
bool conditionMet =
(conditionState == ConditionState::kTrue) &&
(mDimensionInCondition.size() == 0 ||
conditionDimensionKeySet.find(mEventKey.getDimensionKeyInCondition()) !=
conditionDimensionKeySet.end());
- VLOG("key: %s, condition: %d", pair.first.c_str(), conditionMet);
+ VLOG("key: %s, condition: %d", pair.first.toString().c_str(), conditionMet);
noteConditionChanged(pair.first, conditionMet, timestamp);
}
}
@@ -277,7 +292,7 @@
// In case any other dimensions are still started, we need to set the alarm.
startAnomalyAlarm(timestamp);
}
- VLOG("MaxDurationTracker Key: %s Started->Paused ", key.c_str());
+ VLOG("MaxDurationTracker Key: %s Started->Paused ", key.toString().c_str());
}
break;
case kStopped:
@@ -290,7 +305,7 @@
it->second.state = DurationState::kStarted;
it->second.lastStartTime = timestamp;
startAnomalyAlarm(timestamp);
- VLOG("MaxDurationTracker Key: %s Paused->Started", key.c_str());
+ VLOG("MaxDurationTracker Key: %s Paused->Started", key.toString().c_str());
}
break;
}
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
index c731b75..0452d37 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
@@ -33,6 +33,7 @@
const std::vector<Matcher>& dimensionInCondition, bool nesting,
uint64_t currentBucketStartNs, uint64_t currentBucketNum,
uint64_t startTimeNs, uint64_t bucketSizeNs, bool conditionSliced,
+ bool fullLink,
const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers);
MaxDurationTracker(const MaxDurationTracker& tracker) = default;
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
index f583f91..2358415 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
@@ -28,14 +28,18 @@
const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey,
sp<ConditionWizard> wizard, int conditionIndex, const vector<Matcher>& dimensionInCondition,
bool nesting, uint64_t currentBucketStartNs, uint64_t currentBucketNum,
- uint64_t startTimeNs, uint64_t bucketSizeNs, bool conditionSliced,
+ uint64_t startTimeNs, uint64_t bucketSizeNs, bool conditionSliced, bool fullLink,
const vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
: DurationTracker(key, id, eventKey, wizard, conditionIndex, dimensionInCondition, nesting,
currentBucketStartNs, currentBucketNum, startTimeNs, bucketSizeNs,
- conditionSliced, anomalyTrackers),
+ conditionSliced, fullLink, anomalyTrackers),
mStarted(),
mPaused() {
mLastStartTime = 0;
+ if (mWizard != nullptr) {
+ mSameConditionDimensionsInTracker =
+ mWizard->equalOutputDimensions(conditionIndex, mDimensionInCondition);
+ }
}
unique_ptr<DurationTracker> OringDurationTracker::clone(const uint64_t eventTime) {
@@ -57,7 +61,7 @@
// 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
ALOGE("OringDurTracker %lld dropping data for dimension key %s",
- (long long)mTrackerId, newKey.c_str());
+ (long long)mTrackerId, newKey.toString().c_str());
return true;
}
}
@@ -83,13 +87,13 @@
if (mConditionSliced && mConditionKeyMap.find(key) == mConditionKeyMap.end()) {
mConditionKeyMap[key] = conditionKey;
}
- VLOG("Oring: %s start, condition %d", key.c_str(), condition);
+ VLOG("Oring: %s start, condition %d", key.toString().c_str(), condition);
}
void OringDurationTracker::noteStop(const HashableDimensionKey& key, const uint64_t timestamp,
const bool stopAll) {
declareAnomalyIfAlarmExpired(timestamp);
- VLOG("Oring: %s stop", key.c_str());
+ VLOG("Oring: %s stop", key.toString().c_str());
auto it = mStarted.find(key);
if (it != mStarted.end()) {
(it->second)--;
@@ -217,22 +221,26 @@
if (!mStarted.empty()) {
for (auto it = mStarted.begin(); it != mStarted.end();) {
const auto& key = it->first;
- if (mConditionKeyMap.find(key) == mConditionKeyMap.end()) {
- VLOG("Key %s dont have condition key", key.c_str());
+ const auto& condIt = mConditionKeyMap.find(key);
+ if (condIt == mConditionKeyMap.end()) {
+ VLOG("Key %s dont have condition key", key.toString().c_str());
++it;
continue;
}
std::unordered_set<HashableDimensionKey> conditionDimensionKeySet;
ConditionState conditionState =
- mWizard->query(mConditionTrackerIndex, mConditionKeyMap[key],
- mDimensionInCondition, &conditionDimensionKeySet);
+ mWizard->query(mConditionTrackerIndex, condIt->second,
+ mDimensionInCondition,
+ !mSameConditionDimensionsInTracker,
+ !mHasLinksToAllConditionDimensionsInTracker,
+ &conditionDimensionKeySet);
if (conditionState != ConditionState::kTrue ||
(mDimensionInCondition.size() != 0 &&
conditionDimensionKeySet.find(mEventKey.getDimensionKeyInCondition()) ==
conditionDimensionKeySet.end())) {
startedToPaused.push_back(*it);
it = mStarted.erase(it);
- VLOG("Key %s started -> paused", key.c_str());
+ VLOG("Key %s started -> paused", key.toString().c_str());
} else {
++it;
}
@@ -250,21 +258,24 @@
for (auto it = mPaused.begin(); it != mPaused.end();) {
const auto& key = it->first;
if (mConditionKeyMap.find(key) == mConditionKeyMap.end()) {
- VLOG("Key %s dont have condition key", key.c_str());
+ VLOG("Key %s dont have condition key", key.toString().c_str());
++it;
continue;
}
std::unordered_set<HashableDimensionKey> conditionDimensionKeySet;
ConditionState conditionState =
mWizard->query(mConditionTrackerIndex, mConditionKeyMap[key],
- mDimensionInCondition, &conditionDimensionKeySet);
+ mDimensionInCondition,
+ !mSameConditionDimensionsInTracker,
+ !mHasLinksToAllConditionDimensionsInTracker,
+ &conditionDimensionKeySet);
if (conditionState == ConditionState::kTrue &&
(mDimensionInCondition.size() == 0 ||
conditionDimensionKeySet.find(mEventKey.getDimensionKeyInCondition()) !=
conditionDimensionKeySet.end())) {
pausedToStarted.push_back(*it);
it = mPaused.erase(it);
- VLOG("Key %s paused -> started", key.c_str());
+ VLOG("Key %s paused -> started", key.toString().c_str());
} else {
++it;
}
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
index 07c1329..610e3ea 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
@@ -32,6 +32,7 @@
int conditionIndex, const std::vector<Matcher>& dimensionInCondition,
bool nesting, uint64_t currentBucketStartNs, uint64_t currentBucketNum,
uint64_t startTimeNs, uint64_t bucketSizeNs, bool conditionSliced,
+ bool fullLink,
const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers);
OringDurationTracker(const OringDurationTracker& tracker) = default;
diff --git a/cmds/statsd/src/stats_util.h b/cmds/statsd/src/stats_util.h
index bbaf50a..80e46d6 100644
--- a/cmds/statsd/src/stats_util.h
+++ b/cmds/statsd/src/stats_util.h
@@ -32,7 +32,7 @@
// Minimum bucket size in seconds
const long kMinBucketSizeSec = 5 * 60;
-typedef std::map<int64_t, std::vector<HashableDimensionKey>> ConditionKey;
+typedef std::map<int64_t, HashableDimensionKey> ConditionKey;
typedef std::unordered_map<MetricDimensionKey, int64_t> DimToValMap;
diff --git a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
index 3dc3fd1..e826a52 100644
--- a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
+++ b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
@@ -75,10 +75,10 @@
event->init();
}
-std::map<int64_t, std::vector<HashableDimensionKey>> getWakeLockQueryKey(
+std::map<int64_t, HashableDimensionKey> getWakeLockQueryKey(
const Position position,
const std::vector<int> &uids, const string& conditionName) {
- std::map<int64_t, std::vector<HashableDimensionKey>> outputKeyMap;
+ std::map<int64_t, HashableDimensionKey> outputKeyMap;
std::vector<int> uid_indexes;
int pos[] = {1, 1, 1};
int depth = 2;
@@ -104,7 +104,7 @@
Value value((int32_t)uids[idx]);
HashableDimensionKey dim;
dim.addValue(FieldValue(field, value));
- outputKeyMap[StringToId(conditionName)].push_back(dim);
+ outputKeyMap[StringToId(conditionName)] = dim;
}
return outputKeyMap;
}
@@ -122,6 +122,7 @@
SimpleConditionTracker conditionTracker(kConfigKey, StringToId("SCREEN_IS_ON"), 0 /*tracker index*/,
simplePredicate, trackerNameIndexMap);
+ EXPECT_FALSE(conditionTracker.isSliced());
LogEvent event(1 /*tagId*/, 0 /*timestamp*/);
@@ -193,6 +194,7 @@
}
TEST(SimpleConditionTrackerTest, TestNonSlicedConditionNestCounting) {
+ std::vector<sp<ConditionTracker>> allConditions;
SimplePredicate simplePredicate;
simplePredicate.set_start(StringToId("SCREEN_TURNED_ON"));
simplePredicate.set_stop(StringToId("SCREEN_TURNED_OFF"));
@@ -205,6 +207,7 @@
SimpleConditionTracker conditionTracker(kConfigKey, StringToId("SCREEN_IS_ON"),
0 /*condition tracker index*/, simplePredicate,
trackerNameIndexMap);
+ EXPECT_FALSE(conditionTracker.isSliced());
LogEvent event(1 /*tagId*/, 0 /*timestamp*/);
@@ -257,14 +260,14 @@
conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache,
changedCache);
- // result should still be true
EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
EXPECT_TRUE(changedCache[0]);
}
TEST(SimpleConditionTrackerTest, TestSlicedCondition) {
+ std::vector<sp<ConditionTracker>> allConditions;
for (Position position :
- { Position::ANY, Position::FIRST, Position::LAST}) {
+ { Position::FIRST, Position::LAST}) {
vector<Matcher> dimensionInCondition;
std::unordered_set<HashableDimensionKey> dimensionKeys;
@@ -281,6 +284,7 @@
SimpleConditionTracker conditionTracker(kConfigKey, StringToId(conditionName),
0 /*condition tracker index*/, simplePredicate,
trackerNameIndexMap);
+
std::vector<int> uids = {111, 222, 333};
LogEvent event(1 /*tagId*/, 0 /*timestamp*/);
@@ -305,12 +309,20 @@
EXPECT_EQ(uids.size(), conditionTracker.mSlicedConditionState.size());
}
EXPECT_TRUE(changedCache[0]);
+ if (position == Position::FIRST ||
+ position == Position::LAST) {
+ EXPECT_EQ(conditionTracker.getChangedToTrueDimensions(allConditions)->size(), 1u);
+ EXPECT_TRUE(conditionTracker.getChangedToFalseDimensions(allConditions)->empty());
+ } else {
+ EXPECT_EQ(conditionTracker.getChangedToTrueDimensions(allConditions)->size(), uids.size());
+ }
// Now test query
const auto queryKey = getWakeLockQueryKey(position, uids, conditionName);
conditionCache[0] = ConditionState::kNotEvaluated;
conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition,
+ false, false,
conditionCache, dimensionKeys);
EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
@@ -331,6 +343,9 @@
} else {
EXPECT_EQ(uids.size(), conditionTracker.mSlicedConditionState.size());
}
+ EXPECT_TRUE(conditionTracker.getChangedToTrueDimensions(allConditions)->empty());
+ EXPECT_TRUE(conditionTracker.getChangedToFalseDimensions(allConditions)->empty());
+
// wake lock 1 release
LogEvent event3(1 /*tagId*/, 0 /*timestamp*/);
@@ -350,6 +365,8 @@
} else {
EXPECT_EQ(uids.size(), conditionTracker.mSlicedConditionState.size());
}
+ EXPECT_TRUE(conditionTracker.getChangedToTrueDimensions(allConditions)->empty());
+ EXPECT_TRUE(conditionTracker.getChangedToFalseDimensions(allConditions)->empty());
LogEvent event4(1 /*tagId*/, 0 /*timestamp*/);
makeWakeLockEvent(&event4, uids, "wl2", 0); // now release it.
@@ -362,18 +379,26 @@
changedCache);
EXPECT_EQ(0UL, conditionTracker.mSlicedConditionState.size());
EXPECT_TRUE(changedCache[0]);
+ if (position == Position::FIRST ||
+ position == Position::LAST) {
+ EXPECT_EQ(conditionTracker.getChangedToFalseDimensions(allConditions)->size(), 1u);
+ EXPECT_TRUE(conditionTracker.getChangedToTrueDimensions(allConditions)->empty());
+ } else {
+ EXPECT_EQ(conditionTracker.getChangedToFalseDimensions(allConditions)->size(), uids.size());
+ }
// query again
conditionCache[0] = ConditionState::kNotEvaluated;
conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition,
+ false, false,
conditionCache, dimensionKeys);
EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
-
}
}
TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim) {
+ std::vector<sp<ConditionTracker>> allConditions;
vector<Matcher> dimensionInCondition;
std::unordered_set<HashableDimensionKey> dimensionKeys;
@@ -391,6 +416,8 @@
0 /*condition tracker index*/, simplePredicate,
trackerNameIndexMap);
+ EXPECT_FALSE(conditionTracker.isSliced());
+
std::vector<int> uid_list1 = {111, 1111, 11111};
string uid1_wl1 = "wl1_1";
std::vector<int> uid_list2 = {222, 2222, 22222};
@@ -419,6 +446,7 @@
conditionCache[0] = ConditionState::kNotEvaluated;
conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition,
+ true, true,
conditionCache, dimensionKeys);
EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
@@ -463,13 +491,15 @@
conditionCache[0] = ConditionState::kNotEvaluated;
dimensionKeys.clear();
conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition,
+ true, true,
conditionCache, dimensionKeys);
EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
}
TEST(SimpleConditionTrackerTest, TestStopAll) {
+ std::vector<sp<ConditionTracker>> allConditions;
for (Position position :
- {Position::ANY, Position::FIRST, Position::LAST}) {
+ { Position::FIRST, Position::LAST }) {
vector<Matcher> dimensionInCondition;
std::unordered_set<HashableDimensionKey> dimensionKeys;
SimplePredicate simplePredicate = getWakeLockHeldCondition(
@@ -510,12 +540,23 @@
EXPECT_EQ(uid_list1.size(), conditionTracker.mSlicedConditionState.size());
}
EXPECT_TRUE(changedCache[0]);
+ {
+ if (position == Position::FIRST ||
+ position == Position::LAST) {
+ EXPECT_EQ(1UL, conditionTracker.getChangedToTrueDimensions(allConditions)->size());
+ EXPECT_TRUE(conditionTracker.getChangedToFalseDimensions(allConditions)->empty());
+ } else {
+ EXPECT_EQ(uid_list1.size(), conditionTracker.getChangedToTrueDimensions(allConditions)->size());
+ EXPECT_TRUE(conditionTracker.getChangedToFalseDimensions(allConditions)->empty());
+ }
+ }
// Now test query
const auto queryKey = getWakeLockQueryKey(position, uid_list1, conditionName);
conditionCache[0] = ConditionState::kNotEvaluated;
conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition,
+ false, false,
conditionCache, dimensionKeys);
EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
@@ -538,11 +579,23 @@
conditionTracker.mSlicedConditionState.size());
}
EXPECT_TRUE(changedCache[0]);
+ {
+ if (position == Position::FIRST ||
+ position == Position::LAST) {
+ EXPECT_EQ(1UL, conditionTracker.getChangedToTrueDimensions(allConditions)->size());
+ EXPECT_TRUE(conditionTracker.getChangedToFalseDimensions(allConditions)->empty());
+ } else {
+ EXPECT_EQ(uid_list2.size(), conditionTracker.getChangedToTrueDimensions(allConditions)->size());
+ EXPECT_TRUE(conditionTracker.getChangedToFalseDimensions(allConditions)->empty());
+ }
+ }
+
// TEST QUERY
const auto queryKey2 = getWakeLockQueryKey(position, uid_list2, conditionName);
conditionCache[0] = ConditionState::kNotEvaluated;
conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition,
+ false, false,
conditionCache, dimensionKeys);
EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
@@ -561,11 +614,22 @@
changedCache);
EXPECT_TRUE(changedCache[0]);
EXPECT_EQ(0UL, conditionTracker.mSlicedConditionState.size());
+ {
+ if (position == Position::FIRST || position == Position::LAST) {
+ EXPECT_EQ(2UL, conditionTracker.getChangedToFalseDimensions(allConditions)->size());
+ EXPECT_TRUE(conditionTracker.getChangedToTrueDimensions(allConditions)->empty());
+ } else {
+ EXPECT_EQ(uid_list1.size() + uid_list2.size(),
+ conditionTracker.getChangedToFalseDimensions(allConditions)->size());
+ EXPECT_TRUE(conditionTracker.getChangedToTrueDimensions(allConditions)->empty());
+ }
+ }
// TEST QUERY
const auto queryKey3 = getWakeLockQueryKey(position, uid_list1, conditionName);
conditionCache[0] = ConditionState::kNotEvaluated;
conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition,
+ false, false,
conditionCache, dimensionKeys);
EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
@@ -573,6 +637,7 @@
const auto queryKey4 = getWakeLockQueryKey(position, uid_list2, conditionName);
conditionCache[0] = ConditionState::kNotEvaluated;
conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition,
+ false, false,
conditionCache, dimensionKeys);
EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
}
diff --git a/cmds/statsd/tests/e2e/DimensionInCondition_e2e_combination_AND_cond_test.cpp b/cmds/statsd/tests/e2e/DimensionInCondition_e2e_combination_AND_cond_test.cpp
new file mode 100644
index 0000000..a08f606
--- /dev/null
+++ b/cmds/statsd/tests/e2e/DimensionInCondition_e2e_combination_AND_cond_test.cpp
@@ -0,0 +1,879 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <gtest/gtest.h>
+
+#include "src/StatsLogProcessor.h"
+#include "src/stats_log_util.h"
+#include "tests/statsd_test_util.h"
+
+#include <vector>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+#ifdef __ANDROID__
+
+namespace {
+
+StatsdConfig CreateDurationMetricConfig_NoLink_AND_CombinationCondition(
+ DurationMetric::AggregationType aggregationType, bool addExtraDimensionInCondition) {
+ StatsdConfig config;
+ *config.add_atom_matcher() = CreateStartScheduledJobAtomMatcher();
+ *config.add_atom_matcher() = CreateFinishScheduledJobAtomMatcher();
+ *config.add_atom_matcher() = CreateSyncStartAtomMatcher();
+ *config.add_atom_matcher() = CreateSyncEndAtomMatcher();
+ *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
+ *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher();
+
+ auto scheduledJobPredicate = CreateScheduledJobPredicate();
+ auto dimensions = scheduledJobPredicate.mutable_simple_predicate()->mutable_dimensions();
+ dimensions->set_field(android::util::SCHEDULED_JOB_STATE_CHANGED);
+ dimensions->add_child()->set_field(2); // job name field.
+
+ auto screenIsOffPredicate = CreateScreenIsOffPredicate();
+
+ auto isSyncingPredicate = CreateIsSyncingPredicate();
+ auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions();
+ *syncDimension = CreateAttributionUidAndTagDimensions(android::util::SYNC_STATE_CHANGED,
+ {Position::FIRST});
+ if (addExtraDimensionInCondition) {
+ syncDimension->add_child()->set_field(2 /* name field*/);
+ }
+
+ *config.add_predicate() = scheduledJobPredicate;
+ *config.add_predicate() = screenIsOffPredicate;
+ *config.add_predicate() = isSyncingPredicate;
+ auto combinationPredicate = config.add_predicate();
+ combinationPredicate->set_id(StringToId("CombinationPredicate"));
+ combinationPredicate->mutable_combination()->set_operation(LogicalOperation::AND);
+ addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate);
+ addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate);
+
+ auto metric = config.add_duration_metric();
+ metric->set_bucket(FIVE_MINUTES);
+ metric->set_id(StringToId("scheduledJob"));
+ metric->set_what(scheduledJobPredicate.id());
+ metric->set_condition(combinationPredicate->id());
+ metric->set_aggregation_type(aggregationType);
+ auto dimensionWhat = metric->mutable_dimensions_in_what();
+ dimensionWhat->set_field(android::util::SCHEDULED_JOB_STATE_CHANGED);
+ dimensionWhat->add_child()->set_field(2); // job name field.
+ *metric->mutable_dimensions_in_condition() = CreateAttributionUidAndTagDimensions(
+ android::util::SYNC_STATE_CHANGED, {Position::FIRST});
+ return config;
+}
+
+} // namespace
+
+TEST(DimensionInConditionE2eTest, TestDurationMetric_NoLink_AND_CombinationCondition) {
+ for (bool isDimensionInConditionSubSetOfConditionTrackerDimension : { true, false }) {
+ for (auto aggregationType : {DurationMetric::MAX_SPARSE, DurationMetric::SUM}) {
+ ConfigKey cfgKey;
+ auto config = CreateDurationMetricConfig_NoLink_AND_CombinationCondition(
+ aggregationType, isDimensionInConditionSubSetOfConditionTrackerDimension);
+ int64_t bucketStartTimeNs = 10000000000;
+ int64_t bucketSizeNs =
+ TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
+
+ auto processor = CreateStatsLogProcessor(
+ bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+ EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+ EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
+
+ std::vector<AttributionNodeInternal> attributions1 = {
+ CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"),
+ CreateAttribution(222, "GMSCoreModule2")};
+
+ std::vector<AttributionNodeInternal> attributions2 = {
+ CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"),
+ CreateAttribution(555, "GMSCoreModule2")};
+
+ std::vector<std::unique_ptr<LogEvent>> events;
+
+ events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
+ bucketStartTimeNs + 11));
+ events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
+ bucketStartTimeNs + 40));
+
+ events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
+ bucketStartTimeNs + 102));
+ events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
+ bucketStartTimeNs + 450));
+
+ events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
+ bucketStartTimeNs + 650));
+ events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
+ bucketStartTimeNs + bucketSizeNs + 100));
+
+ events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
+ bucketStartTimeNs + bucketSizeNs + 640));
+ events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
+ bucketStartTimeNs + bucketSizeNs + 650));
+
+ events.push_back(CreateStartScheduledJobEvent(
+ {CreateAttribution(9999, "")}, "job0", bucketStartTimeNs + 2));
+ events.push_back(CreateFinishScheduledJobEvent(
+ {CreateAttribution(9999, "")}, "job0",bucketStartTimeNs + 101));
+
+ events.push_back(CreateStartScheduledJobEvent(
+ {CreateAttribution(9999, "")}, "job2", bucketStartTimeNs + 201));
+ events.push_back(CreateFinishScheduledJobEvent(
+ {CreateAttribution(9999, "")}, "job2",bucketStartTimeNs + 500));
+
+ events.push_back(CreateStartScheduledJobEvent(
+ {CreateAttribution(8888, "")}, "job2", bucketStartTimeNs + 600));
+ events.push_back(CreateFinishScheduledJobEvent(
+ {CreateAttribution(8888, "")}, "job2",bucketStartTimeNs + bucketSizeNs + 850));
+
+ events.push_back(CreateStartScheduledJobEvent(
+ {CreateAttribution(8888, "")}, "job1", bucketStartTimeNs + bucketSizeNs + 600));
+ events.push_back(CreateFinishScheduledJobEvent(
+ {CreateAttribution(8888, "")}, "job1", bucketStartTimeNs + bucketSizeNs + 900));
+
+ events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail",
+ bucketStartTimeNs + 10));
+ events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail",
+ bucketStartTimeNs + 50));
+
+ events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail",
+ bucketStartTimeNs + 200));
+ events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail",
+ bucketStartTimeNs + bucketSizeNs + 300));
+
+ events.push_back(CreateSyncStartEvent(attributions1, "ReadDoc",
+ bucketStartTimeNs + 400));
+ events.push_back(CreateSyncEndEvent(attributions1, "ReadDoc",
+ bucketStartTimeNs + bucketSizeNs - 1));
+
+ events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail",
+ bucketStartTimeNs + 401));
+ events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail",
+ bucketStartTimeNs + bucketSizeNs + 700));
+
+ sortLogEventsByTimestamp(&events);
+
+ for (const auto& event : events) {
+ processor->OnLogEvent(event.get());
+ }
+
+ ConfigMetricsReportList reports;
+ vector<uint8_t> buffer;
+ processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, &buffer);
+ EXPECT_TRUE(buffer.size() > 0);
+ EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+
+ EXPECT_EQ(reports.reports_size(), 1);
+ EXPECT_EQ(reports.reports(0).metrics_size(), 1);
+ StatsLogReport::DurationMetricDataWrapper metrics;
+ sortMetricDataByDimensionsValue(
+ reports.reports(0).metrics(0).duration_metrics(), &metrics);
+ if (aggregationType == DurationMetric::SUM) {
+ EXPECT_EQ(metrics.data_size(), 4);
+ auto data = metrics.data(0);
+ EXPECT_EQ(data.dimensions_in_what().field(),
+ android::util::SCHEDULED_JOB_STATE_CHANGED);
+ EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(),
+ 2); // job name field
+ EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(),
+ "job0"); // job name
+ ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(),
+ android::util::SYNC_STATE_CHANGED, 111, "App1");
+ EXPECT_EQ(data.bucket_info_size(), 1);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 40 - 11);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+
+
+ data = metrics.data(1);
+ EXPECT_EQ(data.dimensions_in_what().field(),
+ android::util::SCHEDULED_JOB_STATE_CHANGED);
+ EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(),
+ 2); // job name field
+ EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(),
+ "job1"); // job name
+ ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(),
+ android::util::SYNC_STATE_CHANGED, 333, "App2");
+ EXPECT_EQ(data.bucket_info_size(), 1);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 10);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + 2 * bucketSizeNs);
+
+ data = metrics.data(2);
+ EXPECT_EQ(data.dimensions_in_what().field(),
+ android::util::SCHEDULED_JOB_STATE_CHANGED);
+ EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(),
+ 2); // job name field
+ EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(),
+ "job2"); // job name
+ ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(),
+ android::util::SYNC_STATE_CHANGED, 111, "App1");
+ EXPECT_EQ(data.bucket_info_size(), 2);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 450 - 201 + bucketSizeNs - 600);
+ EXPECT_EQ(data.bucket_info(1).duration_nanos(), 100);
+ EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + 2 * bucketSizeNs);
+
+ data = metrics.data(3);
+ EXPECT_EQ(data.dimensions_in_what().field(),
+ android::util::SCHEDULED_JOB_STATE_CHANGED);
+ EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(),
+ 2); // job name field
+ EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(),
+ "job2"); // job name
+ ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(),
+ android::util::SYNC_STATE_CHANGED, 333, "App2");
+ EXPECT_EQ(data.bucket_info_size(), 2);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 450 - 401 + bucketSizeNs - 600);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(1).duration_nanos(), 100 + 650 - 640);
+ EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + 2 * bucketSizeNs);
+ } else {
+ EXPECT_EQ(metrics.data_size(), 4);
+ auto data = metrics.data(0);
+ EXPECT_EQ(data.dimensions_in_what().field(),
+ android::util::SCHEDULED_JOB_STATE_CHANGED);
+ EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(),
+ 2); // job name field
+ EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(),
+ "job0"); // job name
+ ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(),
+ android::util::SYNC_STATE_CHANGED, 111, "App1");
+ EXPECT_EQ(data.bucket_info_size(), 1);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 40 - 11);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+
+ data = metrics.data(1);
+ EXPECT_EQ(data.dimensions_in_what().field(),
+ android::util::SCHEDULED_JOB_STATE_CHANGED);
+ EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(),
+ 2); // job name field
+ EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(),
+ "job1"); // job name
+ ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(),
+ android::util::SYNC_STATE_CHANGED, 333, "App2");
+ EXPECT_EQ(data.bucket_info_size(), 1);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 10);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + 2 * bucketSizeNs);
+
+ data = metrics.data(2);
+ EXPECT_EQ(data.dimensions_in_what().field(),
+ android::util::SCHEDULED_JOB_STATE_CHANGED);
+ EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(),
+ 2); // job name field
+ EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(),
+ "job2"); // job name
+ ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(),
+ android::util::SYNC_STATE_CHANGED, 111, "App1");
+ EXPECT_EQ(data.bucket_info_size(), 2);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 450 - 201);
+ EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 600 + 100);
+ EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + 2 * bucketSizeNs);
+
+ data = metrics.data(3);
+ EXPECT_EQ(data.dimensions_in_what().field(),
+ android::util::SCHEDULED_JOB_STATE_CHANGED);
+ EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(),
+ 2); // job name field
+ EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(),
+ "job2"); // job name
+ ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(),
+ android::util::SYNC_STATE_CHANGED, 333, "App2");
+ EXPECT_EQ(data.bucket_info_size(), 2);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 450 - 401);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 600 + 110);
+ EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + 2 * bucketSizeNs);
+ }
+ }
+ }
+}
+
+namespace {
+
+StatsdConfig CreateDurationMetricConfig_Link_AND_CombinationCondition(
+ DurationMetric::AggregationType aggregationType, bool addExtraDimensionInCondition) {
+ StatsdConfig config;
+ *config.add_atom_matcher() = CreateStartScheduledJobAtomMatcher();
+ *config.add_atom_matcher() = CreateFinishScheduledJobAtomMatcher();
+ *config.add_atom_matcher() = CreateSyncStartAtomMatcher();
+ *config.add_atom_matcher() = CreateSyncEndAtomMatcher();
+ *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
+ *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher();
+
+ auto scheduledJobPredicate = CreateScheduledJobPredicate();
+ auto dimensions = scheduledJobPredicate.mutable_simple_predicate()->mutable_dimensions();
+ *dimensions = CreateAttributionUidDimensions(
+ android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST});
+ dimensions->add_child()->set_field(2); // job name field.
+
+ auto isSyncingPredicate = CreateIsSyncingPredicate();
+ auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions();
+ *syncDimension = CreateAttributionUidDimensions(
+ android::util::SYNC_STATE_CHANGED, {Position::FIRST});
+ if (addExtraDimensionInCondition) {
+ syncDimension->add_child()->set_field(2 /* name field*/);
+ }
+
+ auto screenIsOffPredicate = CreateScreenIsOffPredicate();
+
+ *config.add_predicate() = scheduledJobPredicate;
+ *config.add_predicate() = screenIsOffPredicate;
+ *config.add_predicate() = isSyncingPredicate;
+ auto combinationPredicate = config.add_predicate();
+ combinationPredicate->set_id(StringToId("CombinationPredicate"));
+ combinationPredicate->mutable_combination()->set_operation(LogicalOperation::AND);
+ addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate);
+ addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate);
+
+ auto metric = config.add_duration_metric();
+ metric->set_bucket(FIVE_MINUTES);
+ metric->set_id(StringToId("scheduledJob"));
+ metric->set_what(scheduledJobPredicate.id());
+ metric->set_condition(combinationPredicate->id());
+ metric->set_aggregation_type(aggregationType);
+ *metric->mutable_dimensions_in_what() = CreateAttributionUidDimensions(
+ android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST});
+
+ auto links = metric->add_links();
+ links->set_condition(isSyncingPredicate.id());
+ *links->mutable_fields_in_what() =
+ CreateAttributionUidDimensions(
+ android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST});
+ *links->mutable_fields_in_condition() =
+ CreateAttributionUidDimensions(android::util::SYNC_STATE_CHANGED, {Position::FIRST});
+ return config;
+}
+
+} // namespace
+
+TEST(DimensionInConditionE2eTest, TestDurationMetric_Link_AND_CombinationCondition) {
+ for (bool isFullLink : {true, false}) {
+ for (auto aggregationType : {DurationMetric::SUM, DurationMetric::MAX_SPARSE}) {
+ ConfigKey cfgKey;
+ auto config = CreateDurationMetricConfig_Link_AND_CombinationCondition(
+ aggregationType, !isFullLink);
+ int64_t bucketStartTimeNs = 10000000000;
+ int64_t bucketSizeNs =
+ TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
+
+ auto processor = CreateStatsLogProcessor(
+ bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+ EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+ EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
+
+ std::vector<AttributionNodeInternal> attributions1 = {
+ CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"),
+ CreateAttribution(222, "GMSCoreModule2")};
+
+ std::vector<AttributionNodeInternal> attributions2 = {
+ CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"),
+ CreateAttribution(555, "GMSCoreModule2")};
+
+ std::vector<AttributionNodeInternal> attributions3 = {
+ CreateAttribution(444, "App3"), CreateAttribution(222, "GMSCoreModule1"),
+ CreateAttribution(555, "GMSCoreModule2")};
+
+ std::vector<std::unique_ptr<LogEvent>> events;
+
+ events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
+ bucketStartTimeNs + 55));
+ events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
+ bucketStartTimeNs + 120));
+ events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
+ bucketStartTimeNs + 121));
+ events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
+ bucketStartTimeNs + 450));
+
+ events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
+ bucketStartTimeNs + 501));
+ events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
+ bucketStartTimeNs + bucketSizeNs + 100));
+
+ events.push_back(CreateStartScheduledJobEvent(
+ {CreateAttribution(111, "App1")}, "job1", bucketStartTimeNs + 1));
+ events.push_back(CreateFinishScheduledJobEvent(
+ {CreateAttribution(111, "App1")}, "job1",bucketStartTimeNs + 101));
+
+ events.push_back(CreateStartScheduledJobEvent(
+ {CreateAttribution(333, "App2")}, "job2", bucketStartTimeNs + 201));
+ events.push_back(CreateFinishScheduledJobEvent(
+ {CreateAttribution(333, "App2")}, "job2",bucketStartTimeNs + 500));
+ events.push_back(CreateStartScheduledJobEvent(
+ {CreateAttribution(333, "App2")}, "job2", bucketStartTimeNs + 600));
+ events.push_back(CreateFinishScheduledJobEvent(
+ {CreateAttribution(333, "App2")}, "job2",
+ bucketStartTimeNs + bucketSizeNs + 850));
+
+ events.push_back(
+ CreateStartScheduledJobEvent({CreateAttribution(444, "App3")}, "job3",
+ bucketStartTimeNs + bucketSizeNs - 2));
+ events.push_back(
+ CreateFinishScheduledJobEvent({CreateAttribution(444, "App3")}, "job3",
+ bucketStartTimeNs + bucketSizeNs + 900));
+
+ events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail",
+ bucketStartTimeNs + 50));
+ events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail",
+ bucketStartTimeNs + 110));
+
+ events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail",
+ bucketStartTimeNs + 300));
+ events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail",
+ bucketStartTimeNs + bucketSizeNs + 700));
+ events.push_back(CreateSyncStartEvent(attributions2, "ReadDoc",
+ bucketStartTimeNs + 400));
+ events.push_back(CreateSyncEndEvent(attributions2, "ReadDoc",
+ bucketStartTimeNs + bucketSizeNs - 1));
+
+ events.push_back(CreateSyncStartEvent(attributions3, "ReadDoc",
+ bucketStartTimeNs + 550));
+ events.push_back(CreateSyncEndEvent(attributions3, "ReadDoc",
+ bucketStartTimeNs + 800));
+ events.push_back(CreateSyncStartEvent(attributions3, "ReadDoc",
+ bucketStartTimeNs + bucketSizeNs - 1));
+ events.push_back(CreateSyncEndEvent(attributions3, "ReadDoc",
+ bucketStartTimeNs + bucketSizeNs + 700));
+
+ sortLogEventsByTimestamp(&events);
+
+ for (const auto& event : events) {
+ processor->OnLogEvent(event.get());
+ }
+
+ ConfigMetricsReportList reports;
+ vector<uint8_t> buffer;
+ processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, &buffer);
+ EXPECT_TRUE(buffer.size() > 0);
+ EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+
+ EXPECT_EQ(reports.reports_size(), 1);
+ EXPECT_EQ(reports.reports(0).metrics_size(), 1);
+ StatsLogReport::DurationMetricDataWrapper metrics;
+ sortMetricDataByDimensionsValue(
+ reports.reports(0).metrics(0).duration_metrics(), &metrics);
+
+ if (aggregationType == DurationMetric::SUM) {
+ EXPECT_EQ(metrics.data_size(), 3);
+ auto data = metrics.data(0);
+ ValidateAttributionUidDimension(
+ data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 111);
+ EXPECT_EQ(data.bucket_info_size(), 1);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 101 - 55);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+
+ data = metrics.data(1);
+ ValidateAttributionUidDimension(
+ data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333);
+ EXPECT_EQ(data.bucket_info_size(), 2);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 450 - 300 + bucketSizeNs - 600);
+ EXPECT_EQ(data.bucket_info(1).duration_nanos(), 100);
+ EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + 2 * bucketSizeNs);
+
+ data = metrics.data(2);
+ ValidateAttributionUidDimension(
+ data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 444);
+ EXPECT_EQ(data.bucket_info_size(), 2);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 1);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(1).duration_nanos(), 100);
+ EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + 2 * bucketSizeNs);
+ } else {
+ EXPECT_EQ(metrics.data_size(), 3);
+ auto data = metrics.data(0);
+ ValidateAttributionUidDimension(
+ data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 111);
+ EXPECT_EQ(data.bucket_info_size(), 1);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 101 - 55);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+
+ data = metrics.data(1);
+ ValidateAttributionUidDimension(
+ data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333);
+ EXPECT_EQ(data.bucket_info_size(), 2);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 450 - 300);
+ EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 600 + 100);
+ EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + 2 * bucketSizeNs);
+
+ data = metrics.data(2);
+ ValidateAttributionUidDimension(
+ data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 444);
+ EXPECT_EQ(data.bucket_info_size(), 1);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 101);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + 2 * bucketSizeNs);
+ }
+ }
+ }
+}
+
+namespace {
+
+StatsdConfig CreateDurationMetricConfig_PartialLink_AND_CombinationCondition(
+ DurationMetric::AggregationType aggregationType) {
+ StatsdConfig config;
+ *config.add_atom_matcher() = CreateStartScheduledJobAtomMatcher();
+ *config.add_atom_matcher() = CreateFinishScheduledJobAtomMatcher();
+ *config.add_atom_matcher() = CreateSyncStartAtomMatcher();
+ *config.add_atom_matcher() = CreateSyncEndAtomMatcher();
+ *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
+ *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher();
+
+ auto scheduledJobPredicate = CreateScheduledJobPredicate();
+ auto dimensions = scheduledJobPredicate.mutable_simple_predicate()->mutable_dimensions();
+ *dimensions = CreateAttributionUidDimensions(
+ android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST});
+ dimensions->add_child()->set_field(2); // job name field.
+
+ auto isSyncingPredicate = CreateIsSyncingPredicate();
+ auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions();
+ *syncDimension = CreateAttributionUidDimensions(
+ android::util::SYNC_STATE_CHANGED, {Position::FIRST});
+ syncDimension->add_child()->set_field(2 /* name field*/);
+
+ auto screenIsOffPredicate = CreateScreenIsOffPredicate();
+
+ *config.add_predicate() = scheduledJobPredicate;
+ *config.add_predicate() = screenIsOffPredicate;
+ *config.add_predicate() = isSyncingPredicate;
+ auto combinationPredicate = config.add_predicate();
+ combinationPredicate->set_id(StringToId("CombinationPredicate"));
+ combinationPredicate->mutable_combination()->set_operation(LogicalOperation::AND);
+ addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate);
+ addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate);
+
+ auto metric = config.add_duration_metric();
+ metric->set_bucket(FIVE_MINUTES);
+ metric->set_id(StringToId("scheduledJob"));
+ metric->set_what(scheduledJobPredicate.id());
+ metric->set_condition(combinationPredicate->id());
+ metric->set_aggregation_type(aggregationType);
+ *metric->mutable_dimensions_in_what() = CreateAttributionUidDimensions(
+ android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST});
+ *metric->mutable_dimensions_in_condition() = *syncDimension;
+
+
+ auto links = metric->add_links();
+ links->set_condition(isSyncingPredicate.id());
+ *links->mutable_fields_in_what() =
+ CreateAttributionUidDimensions(
+ android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST});
+ *links->mutable_fields_in_condition() =
+ CreateAttributionUidDimensions(android::util::SYNC_STATE_CHANGED, {Position::FIRST});
+ return config;
+}
+
+} // namespace
+
+TEST(DimensionInConditionE2eTest, TestDurationMetric_PartialLink_AND_CombinationCondition) {
+ for (auto aggregationType : {DurationMetric::SUM, DurationMetric::MAX_SPARSE}) {
+ ConfigKey cfgKey;
+ auto config =
+ CreateDurationMetricConfig_PartialLink_AND_CombinationCondition(aggregationType);
+ int64_t bucketStartTimeNs = 10000000000;
+ int64_t bucketSizeNs =
+ TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
+
+ auto processor = CreateStatsLogProcessor(
+ bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+ EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+ EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
+
+ std::vector<AttributionNodeInternal> attributions1 = {
+ CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"),
+ CreateAttribution(222, "GMSCoreModule2")};
+
+ std::vector<AttributionNodeInternal> attributions2 = {
+ CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"),
+ CreateAttribution(555, "GMSCoreModule2")};
+
+ std::vector<AttributionNodeInternal> attributions3 = {
+ CreateAttribution(444, "App3"), CreateAttribution(222, "GMSCoreModule1"),
+ CreateAttribution(555, "GMSCoreModule2")};
+
+ std::vector<std::unique_ptr<LogEvent>> events;
+
+ events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
+ bucketStartTimeNs + 55));
+ events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
+ bucketStartTimeNs + 120));
+ events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
+ bucketStartTimeNs + 121));
+ events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
+ bucketStartTimeNs + 450));
+
+ events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_OFF,
+ bucketStartTimeNs + 501));
+ events.push_back(CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
+ bucketStartTimeNs + bucketSizeNs + 100));
+
+ events.push_back(CreateStartScheduledJobEvent(
+ {CreateAttribution(111, "App1")}, "job1", bucketStartTimeNs + 1));
+ events.push_back(CreateFinishScheduledJobEvent(
+ {CreateAttribution(111, "App1")}, "job1",bucketStartTimeNs + 101));
+
+ events.push_back(CreateStartScheduledJobEvent(
+ {CreateAttribution(333, "App2")}, "job2", bucketStartTimeNs + 201));
+ events.push_back(CreateFinishScheduledJobEvent(
+ {CreateAttribution(333, "App2")}, "job2",bucketStartTimeNs + 500));
+ events.push_back(CreateStartScheduledJobEvent(
+ {CreateAttribution(333, "App2")}, "job2", bucketStartTimeNs + 600));
+ events.push_back(CreateFinishScheduledJobEvent(
+ {CreateAttribution(333, "App2")}, "job2",
+ bucketStartTimeNs + bucketSizeNs + 850));
+
+ events.push_back(
+ CreateStartScheduledJobEvent({CreateAttribution(444, "App3")}, "job3",
+ bucketStartTimeNs + bucketSizeNs - 2));
+ events.push_back(
+ CreateFinishScheduledJobEvent({CreateAttribution(444, "App3")}, "job3",
+ bucketStartTimeNs + bucketSizeNs + 900));
+
+ events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail",
+ bucketStartTimeNs + 50));
+ events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail",
+ bucketStartTimeNs + 110));
+
+ events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail",
+ bucketStartTimeNs + 300));
+ events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail",
+ bucketStartTimeNs + bucketSizeNs + 700));
+ events.push_back(CreateSyncStartEvent(attributions2, "ReadDoc",
+ bucketStartTimeNs + 400));
+ events.push_back(CreateSyncEndEvent(attributions2, "ReadDoc",
+ bucketStartTimeNs + bucketSizeNs - 1));
+
+ events.push_back(CreateSyncStartEvent(attributions3, "ReadDoc",
+ bucketStartTimeNs + 550));
+ events.push_back(CreateSyncEndEvent(attributions3, "ReadDoc",
+ bucketStartTimeNs + 800));
+ events.push_back(CreateSyncStartEvent(attributions3, "ReadDoc",
+ bucketStartTimeNs + bucketSizeNs - 1));
+ events.push_back(CreateSyncEndEvent(attributions3, "ReadDoc",
+ bucketStartTimeNs + bucketSizeNs + 700));
+
+ sortLogEventsByTimestamp(&events);
+
+ for (const auto& event : events) {
+ processor->OnLogEvent(event.get());
+ }
+
+ ConfigMetricsReportList reports;
+ vector<uint8_t> buffer;
+ processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, &buffer);
+ EXPECT_TRUE(buffer.size() > 0);
+ EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+
+ EXPECT_EQ(reports.reports_size(), 1);
+ EXPECT_EQ(reports.reports(0).metrics_size(), 1);
+ StatsLogReport::DurationMetricDataWrapper metrics;
+ sortMetricDataByDimensionsValue(
+ reports.reports(0).metrics(0).duration_metrics(), &metrics);
+ if (aggregationType == DurationMetric::SUM) {
+ EXPECT_EQ(metrics.data_size(), 4);
+ auto data = metrics.data(0);
+ ValidateAttributionUidDimension(
+ data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 111);
+ ValidateAttributionUidDimension(
+ data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 111);
+ EXPECT_EQ("ReadEmail",
+ data.dimensions_in_condition().value_tuple().dimensions_value(1).value_str());
+ EXPECT_EQ(data.bucket_info_size(), 1);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 101 - 55);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+
+ data = metrics.data(1);
+ ValidateAttributionUidDimension(
+ data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333);
+ ValidateAttributionUidDimension(
+ data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 333);
+ EXPECT_EQ("ReadDoc",
+ data.dimensions_in_condition().value_tuple().dimensions_value(1).value_str());
+ EXPECT_EQ(data.bucket_info_size(), 1);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), bucketSizeNs - 1 - 600 + 50);
+
+ data = metrics.data(2);
+ ValidateAttributionUidDimension(
+ data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333);
+ ValidateAttributionUidDimension(
+ data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 333);
+ EXPECT_EQ("ReadEmail",
+ data.dimensions_in_condition().value_tuple().dimensions_value(1).value_str());
+ EXPECT_EQ(data.bucket_info_size(), 2);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 450 - 300 + bucketSizeNs - 600);
+ EXPECT_EQ(data.bucket_info(1).duration_nanos(), 100);
+ EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + 2 * bucketSizeNs);
+
+ data = metrics.data(3);
+ ValidateAttributionUidDimension(
+ data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 444);
+ ValidateAttributionUidDimension(
+ data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 444);
+ EXPECT_EQ("ReadDoc",
+ data.dimensions_in_condition().value_tuple().dimensions_value(1).value_str());
+ EXPECT_EQ(data.bucket_info_size(), 2);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 1);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(1).duration_nanos(), 100);
+ EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + 2 * bucketSizeNs);
+ } else {
+ EXPECT_EQ(metrics.data_size(), 4);
+ auto data = metrics.data(0);
+ ValidateAttributionUidDimension(
+ data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 111);
+ ValidateAttributionUidDimension(
+ data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 111);
+ EXPECT_EQ("ReadEmail",
+ data.dimensions_in_condition().value_tuple().dimensions_value(1).value_str());
+ EXPECT_EQ(data.bucket_info_size(), 1);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 101 - 55);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+
+ data = metrics.data(1);
+ ValidateAttributionUidDimension(
+ data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333);
+ ValidateAttributionUidDimension(
+ data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 333);
+ EXPECT_EQ("ReadDoc",
+ data.dimensions_in_condition().value_tuple().dimensions_value(1).value_str());
+ EXPECT_EQ(data.bucket_info_size(), 2);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 50);
+ EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 1 - 600);
+ EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + 2 * bucketSizeNs);
+
+ data = metrics.data(2);
+ ValidateAttributionUidDimension(
+ data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333);
+ ValidateAttributionUidDimension(
+ data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 333);
+ EXPECT_EQ("ReadEmail",
+ data.dimensions_in_condition().value_tuple().dimensions_value(1).value_str());
+ EXPECT_EQ(data.bucket_info_size(), 2);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 450 - 300);
+ EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 600 + 100);
+ EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + 2 * bucketSizeNs);
+
+ data = metrics.data(3);
+ ValidateAttributionUidDimension(
+ data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 444);
+ ValidateAttributionUidDimension(
+ data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 444);
+ EXPECT_EQ("ReadDoc",
+ data.dimensions_in_condition().value_tuple().dimensions_value(1).value_str());
+ EXPECT_EQ(data.bucket_info_size(), 1);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 101);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + 2 * bucketSizeNs);
+ }
+ }
+}
+
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/tests/e2e/DimensionInCondition_e2e_test.cpp b/cmds/statsd/tests/e2e/DimensionInCondition_e2e_combination_OR_cond_test.cpp
similarity index 85%
rename from cmds/statsd/tests/e2e/DimensionInCondition_e2e_test.cpp
rename to cmds/statsd/tests/e2e/DimensionInCondition_e2e_combination_OR_cond_test.cpp
index 01348bd..435e199 100644
--- a/cmds/statsd/tests/e2e/DimensionInCondition_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/DimensionInCondition_e2e_combination_OR_cond_test.cpp
@@ -28,7 +28,7 @@
namespace {
-StatsdConfig CreateCountMetricWithNoLinkConfig() {
+StatsdConfig CreateCountMetric_NoLink_CombinationCondition_Config() {
StatsdConfig config;
auto screenBrightnessChangeAtomMatcher = CreateScreenBrightnessChangedAtomMatcher();
*config.add_atom_matcher() = screenBrightnessChangeAtomMatcher;
@@ -67,9 +67,9 @@
} // namespace
-TEST(DimensionInConditionE2eTest, TestCountMetricNoLink) {
+TEST(DimensionInConditionE2eTest, TestCreateCountMetric_NoLink_OR_CombinationCondition) {
ConfigKey cfgKey;
- auto config = CreateCountMetricWithNoLinkConfig();
+ auto config = CreateCountMetric_NoLink_CombinationCondition_Config();
int64_t bucketStartTimeNs = 10000000000;
int64_t bucketSizeNs =
TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL;
@@ -227,7 +227,7 @@
namespace {
-StatsdConfig CreateCountMetricWithLinkConfig() {
+StatsdConfig CreateCountMetric_Link_CombinationCondition() {
StatsdConfig config;
auto appCrashMatcher = CreateProcessCrashAtomMatcher();
*config.add_atom_matcher() = appCrashMatcher;
@@ -274,9 +274,9 @@
} // namespace
-TEST(DimensionInConditionE2eTest, TestCountMetricWithLink) {
+TEST(DimensionInConditionE2eTest, TestCreateCountMetric_Link_OR_CombinationCondition) {
ConfigKey cfgKey;
- auto config = CreateCountMetricWithLinkConfig();
+ auto config = CreateCountMetric_Link_CombinationCondition();
int64_t bucketStartTimeNs = 10000000000;
int64_t bucketSizeNs =
TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL;
@@ -413,7 +413,8 @@
namespace {
-StatsdConfig CreateDurationMetricConfigNoLink(DurationMetric::AggregationType aggregationType) {
+StatsdConfig CreateDurationMetricConfig_NoLink_CombinationCondition(
+ DurationMetric::AggregationType aggregationType) {
StatsdConfig config;
*config.add_atom_matcher() = CreateBatterySaverModeStartAtomMatcher();
*config.add_atom_matcher() = CreateBatterySaverModeStopAtomMatcher();
@@ -445,6 +446,7 @@
metric->set_id(StringToId("BatterySaverModeDurationMetric"));
metric->set_what(inBatterySaverModePredicate.id());
metric->set_condition(combinationPredicate->id());
+ metric->set_aggregation_type(aggregationType);
*metric->mutable_dimensions_in_condition() = CreateAttributionUidAndTagDimensions(
android::util::SYNC_STATE_CHANGED, {Position::FIRST});
return config;
@@ -452,10 +454,10 @@
} // namespace
-TEST(DimensionInConditionE2eTest, TestDurationMetricNoLink) {
- for (auto aggregationType : {DurationMetric::SUM, DurationMetric::MAX_SPARSE}) {
+TEST(DimensionInConditionE2eTest, TestDurationMetric_NoLink_OR_CombinationCondition) {
+ for (auto aggregationType : { DurationMetric::MAX_SPARSE}) { // DurationMetric::SUM,
ConfigKey cfgKey;
- auto config = CreateDurationMetricConfigNoLink(aggregationType);
+ auto config = CreateDurationMetricConfig_NoLink_CombinationCondition(aggregationType);
int64_t bucketStartTimeNs = 10000000000;
int64_t bucketSizeNs =
TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
@@ -529,43 +531,77 @@
auto data = metrics.data(0);
EXPECT_FALSE(data.dimensions_in_what().has_field());
EXPECT_FALSE(data.dimensions_in_condition().has_field());
- EXPECT_EQ(data.bucket_info_size(), 2);
- EXPECT_EQ(data.bucket_info(0).duration_nanos(), 9);
- EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
- EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs);
- EXPECT_EQ(data.bucket_info(1).duration_nanos(), 30);
- EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs);
- EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), bucketStartTimeNs + 2 * bucketSizeNs);
+ if (aggregationType == DurationMetric::SUM) {
+ EXPECT_EQ(data.bucket_info_size(), 2);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 9);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(1).duration_nanos(), 30);
+ EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + 2 * bucketSizeNs);
+ } else {
+ EXPECT_EQ(data.bucket_info_size(), 2);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 9);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(1).duration_nanos(), 30);
+ EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + 2 * bucketSizeNs);
+ }
data = metrics.data(1);
EXPECT_FALSE(data.dimensions_in_what().has_field());
ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(),
android::util::SYNC_STATE_CHANGED, 111, "App1");
EXPECT_EQ(data.bucket_info_size(), 2);
- EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 201 + bucketSizeNs - 600);
+
+ if (aggregationType == DurationMetric::SUM) {
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 201 + bucketSizeNs - 600);
+ EXPECT_EQ(data.bucket_info(1).duration_nanos(), 300);
+ } else {
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 201);
+ EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 300);
+ }
EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
- EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs);
- EXPECT_EQ(data.bucket_info(1).duration_nanos(), 300);
- EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs);
- EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), bucketStartTimeNs + 2 * bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + 2 * bucketSizeNs);
data = metrics.data(2);
EXPECT_FALSE(data.dimensions_in_what().has_field());
ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(),
android::util::SYNC_STATE_CHANGED, 333, "App2");
EXPECT_EQ(data.bucket_info_size(), 2);
- EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 401 + bucketSizeNs - 600);
+ if (aggregationType == DurationMetric::SUM) {
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 401 + bucketSizeNs - 600);
+ EXPECT_EQ(data.bucket_info(1).duration_nanos(), 700);
+ } else {
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 401);
+ EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs + 700 - 600);
+ }
EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
- EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs);
- EXPECT_EQ(data.bucket_info(1).duration_nanos(), 700);
- EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs);
- EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), bucketStartTimeNs + 2 * bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + 2 * bucketSizeNs);
}
}
namespace {
-StatsdConfig CreateDurationMetricConfigWithLink(DurationMetric::AggregationType aggregationType) {
+StatsdConfig CreateDurationMetricConfig_Link_CombinationCondition(
+ DurationMetric::AggregationType aggregationType) {
StatsdConfig config;
*config.add_atom_matcher() = CreateMoveToBackgroundAtomMatcher();
*config.add_atom_matcher() = CreateMoveToForegroundAtomMatcher();
@@ -599,6 +635,7 @@
metric->set_id(StringToId("AppInBackgroundMetric"));
metric->set_what(isInBackgroundPredicate.id());
metric->set_condition(combinationPredicate->id());
+ metric->set_aggregation_type(aggregationType);
*metric->mutable_dimensions_in_what() =
CreateDimensions(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /* uid field */});
*metric->mutable_dimensions_in_condition() = CreateAttributionUidAndTagDimensions(
@@ -617,10 +654,10 @@
} // namespace
-TEST(DimensionInConditionE2eTest, TestDurationMetricWithLink) {
+TEST(DimensionInConditionE2eTest, TestDurationMetric_Link_OR_CombinationCondition) {
for (auto aggregationType : {DurationMetric::SUM, DurationMetric::MAX_SPARSE}) {
ConfigKey cfgKey;
- auto config = CreateDurationMetricConfigWithLink(aggregationType);
+ auto config = CreateDurationMetricConfig_Link_CombinationCondition(aggregationType);
int64_t bucketStartTimeNs = 10000000000;
int64_t bucketSizeNs =
TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
@@ -701,26 +738,50 @@
EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 111);
ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(),
android::util::SYNC_STATE_CHANGED, 111, "App1");
- EXPECT_EQ(data.bucket_info_size(), 2);
- EXPECT_EQ(data.bucket_info(0).duration_nanos(), bucketSizeNs - 201);
- EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
- EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs);
- EXPECT_EQ(data.bucket_info(1).duration_nanos(), 100);
- EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs);
- EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), bucketStartTimeNs + 2 * bucketSizeNs);
+ if (aggregationType == DurationMetric::SUM) {
+ EXPECT_EQ(data.bucket_info_size(), 2);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), bucketSizeNs - 201);
+ EXPECT_EQ(data.bucket_info(1).duration_nanos(), 100);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + 2 * bucketSizeNs);
+ } else {
+ EXPECT_EQ(data.bucket_info_size(), 1);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), bucketSizeNs + 100 - 201);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + 2 * bucketSizeNs);
+ }
data = metrics.data(2);
EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1);
EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 333);
ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(),
android::util::SYNC_STATE_CHANGED, 333, "App2");
- EXPECT_EQ(data.bucket_info_size(), 2);
- EXPECT_EQ(data.bucket_info(0).duration_nanos(), bucketSizeNs - 401);
- EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
- EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs);
- EXPECT_EQ(data.bucket_info(1).duration_nanos(), 700);
- EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(), bucketStartTimeNs + bucketSizeNs);
- EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(), bucketStartTimeNs + 2 * bucketSizeNs);
+ if (aggregationType == DurationMetric::SUM) {
+ EXPECT_EQ(data.bucket_info_size(), 2);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), bucketSizeNs - 401);
+ EXPECT_EQ(data.bucket_info(1).duration_nanos(), 700);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + 2 * bucketSizeNs);
+ } else {
+ EXPECT_EQ(data.bucket_info_size(), 1);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), bucketSizeNs + 299);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + 2 * bucketSizeNs);
+ }
}
}
diff --git a/cmds/statsd/tests/e2e/DimensionInCondition_e2e_simple_cond_test.cpp b/cmds/statsd/tests/e2e/DimensionInCondition_e2e_simple_cond_test.cpp
new file mode 100644
index 0000000..75ceafb
--- /dev/null
+++ b/cmds/statsd/tests/e2e/DimensionInCondition_e2e_simple_cond_test.cpp
@@ -0,0 +1,799 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <gtest/gtest.h>
+
+#include "src/StatsLogProcessor.h"
+#include "src/stats_log_util.h"
+#include "tests/statsd_test_util.h"
+
+#include <vector>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+#ifdef __ANDROID__
+
+namespace {
+
+StatsdConfig CreateDurationMetricConfig_NoLink_SimpleCondition(
+ DurationMetric::AggregationType aggregationType, bool addExtraDimensionInCondition) {
+ StatsdConfig config;
+ *config.add_atom_matcher() = CreateStartScheduledJobAtomMatcher();
+ *config.add_atom_matcher() = CreateFinishScheduledJobAtomMatcher();
+ *config.add_atom_matcher() = CreateSyncStartAtomMatcher();
+ *config.add_atom_matcher() = CreateSyncEndAtomMatcher();
+
+ auto scheduledJobPredicate = CreateScheduledJobPredicate();
+ auto dimensions = scheduledJobPredicate.mutable_simple_predicate()->mutable_dimensions();
+ dimensions->set_field(android::util::SCHEDULED_JOB_STATE_CHANGED);
+ dimensions->add_child()->set_field(2); // job name field.
+
+ auto isSyncingPredicate = CreateIsSyncingPredicate();
+ auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions();
+ *syncDimension = CreateAttributionUidAndTagDimensions(android::util::SYNC_STATE_CHANGED,
+ {Position::FIRST});
+ if (addExtraDimensionInCondition) {
+ syncDimension->add_child()->set_field(2 /* name field*/);
+ }
+
+ *config.add_predicate() = scheduledJobPredicate;
+ *config.add_predicate() = isSyncingPredicate;
+
+ auto metric = config.add_duration_metric();
+ metric->set_bucket(FIVE_MINUTES);
+ metric->set_id(StringToId("scheduledJob"));
+ metric->set_what(scheduledJobPredicate.id());
+ metric->set_condition(isSyncingPredicate.id());
+ metric->set_aggregation_type(aggregationType);
+ auto dimensionWhat = metric->mutable_dimensions_in_what();
+ dimensionWhat->set_field(android::util::SCHEDULED_JOB_STATE_CHANGED);
+ dimensionWhat->add_child()->set_field(2); // job name field.
+ *metric->mutable_dimensions_in_condition() = CreateAttributionUidAndTagDimensions(
+ android::util::SYNC_STATE_CHANGED, {Position::FIRST});
+ return config;
+}
+
+} // namespace
+
+TEST(DimensionInConditionE2eTest, TestDurationMetric_NoLink_SimpleCondition) {
+ for (bool isDimensionInConditionSubSetOfConditionTrackerDimension : {true, false}) {
+ for (auto aggregationType : {DurationMetric::SUM, DurationMetric::MAX_SPARSE}) {
+ ConfigKey cfgKey;
+ auto config = CreateDurationMetricConfig_NoLink_SimpleCondition(
+ aggregationType, isDimensionInConditionSubSetOfConditionTrackerDimension);
+ int64_t bucketStartTimeNs = 10000000000;
+ int64_t bucketSizeNs =
+ TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
+
+ auto processor = CreateStatsLogProcessor(
+ bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+ EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+ EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
+
+ std::vector<AttributionNodeInternal> attributions1 = {
+ CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"),
+ CreateAttribution(222, "GMSCoreModule2")};
+
+ std::vector<AttributionNodeInternal> attributions2 = {
+ CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"),
+ CreateAttribution(555, "GMSCoreModule2")};
+
+ std::vector<std::unique_ptr<LogEvent>> events;
+
+ events.push_back(CreateStartScheduledJobEvent(
+ {CreateAttribution(9999, "")}, "job0", bucketStartTimeNs + 1));
+ events.push_back(CreateFinishScheduledJobEvent(
+ {CreateAttribution(9999, "")}, "job0",bucketStartTimeNs + 101));
+
+ events.push_back(CreateStartScheduledJobEvent(
+ {CreateAttribution(9999, "")}, "job2", bucketStartTimeNs + 201));
+ events.push_back(CreateFinishScheduledJobEvent(
+ {CreateAttribution(9999, "")}, "job2",bucketStartTimeNs + 500));
+
+ events.push_back(CreateStartScheduledJobEvent(
+ {CreateAttribution(8888, "")}, "job2", bucketStartTimeNs + 600));
+ events.push_back(CreateFinishScheduledJobEvent(
+ {CreateAttribution(8888, "")}, "job2",bucketStartTimeNs + bucketSizeNs + 850));
+
+ events.push_back(CreateStartScheduledJobEvent(
+ {CreateAttribution(8888, "")}, "job1", bucketStartTimeNs + bucketSizeNs + 600));
+ events.push_back(CreateFinishScheduledJobEvent(
+ {CreateAttribution(8888, "")}, "job1", bucketStartTimeNs + bucketSizeNs + 900));
+
+ events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail",
+ bucketStartTimeNs + 10));
+ events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail",
+ bucketStartTimeNs + 50));
+
+ events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail",
+ bucketStartTimeNs + 200));
+ events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail",
+ bucketStartTimeNs + bucketSizeNs + 300));
+
+ events.push_back(CreateSyncStartEvent(attributions1, "ReadDoc",
+ bucketStartTimeNs + 400));
+ events.push_back(CreateSyncEndEvent(attributions1, "ReadDoc",
+ bucketStartTimeNs + bucketSizeNs - 1));
+
+ events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail",
+ bucketStartTimeNs + 401));
+ events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail",
+ bucketStartTimeNs + bucketSizeNs + 700));
+
+ sortLogEventsByTimestamp(&events);
+
+ for (const auto& event : events) {
+ processor->OnLogEvent(event.get());
+ }
+
+ ConfigMetricsReportList reports;
+ vector<uint8_t> buffer;
+ processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, &buffer);
+ EXPECT_TRUE(buffer.size() > 0);
+ EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+
+ EXPECT_EQ(reports.reports_size(), 1);
+ EXPECT_EQ(reports.reports(0).metrics_size(), 1);
+ StatsLogReport::DurationMetricDataWrapper metrics;
+ sortMetricDataByDimensionsValue(
+ reports.reports(0).metrics(0).duration_metrics(), &metrics);
+ if (aggregationType == DurationMetric::SUM) {
+ EXPECT_EQ(metrics.data_size(), 4);
+ auto data = metrics.data(0);
+ EXPECT_EQ(data.dimensions_in_what().field(),
+ android::util::SCHEDULED_JOB_STATE_CHANGED);
+ EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(),
+ 2); // job name field
+ EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(),
+ "job0"); // job name
+ ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(),
+ android::util::SYNC_STATE_CHANGED, 111, "App1");
+ EXPECT_EQ(data.bucket_info_size(), 1);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 40);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+
+ data = metrics.data(1);
+ EXPECT_EQ(data.dimensions_in_what().field(),
+ android::util::SCHEDULED_JOB_STATE_CHANGED);
+ EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(),
+ 2); // job name field
+ EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(),
+ "job1"); // job name
+ ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(),
+ android::util::SYNC_STATE_CHANGED, 333, "App2");
+ EXPECT_EQ(data.bucket_info_size(), 1);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 100);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + 2 * bucketSizeNs);
+
+ data = metrics.data(2);
+ EXPECT_EQ(data.dimensions_in_what().field(),
+ android::util::SCHEDULED_JOB_STATE_CHANGED);
+ EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(),
+ 2); // job name field
+ EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(),
+ "job2"); // job name
+ ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(),
+ android::util::SYNC_STATE_CHANGED, 111, "App1");
+ EXPECT_EQ(data.bucket_info_size(), 2);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 201 + bucketSizeNs - 600);
+ EXPECT_EQ(data.bucket_info(1).duration_nanos(), 300);
+ EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + 2 * bucketSizeNs);
+
+ data = metrics.data(3);
+ EXPECT_EQ(data.dimensions_in_what().field(),
+ android::util::SCHEDULED_JOB_STATE_CHANGED);
+ EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(),
+ 2); // job name field
+ EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(),
+ "job2"); // job name
+ ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(),
+ android::util::SYNC_STATE_CHANGED, 333, "App2");
+ EXPECT_EQ(data.bucket_info_size(), 2);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 401 + bucketSizeNs - 600);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(1).duration_nanos(), 700);
+ EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + 2 * bucketSizeNs);
+ } else {
+ EXPECT_EQ(metrics.data_size(), 4);
+ auto data = metrics.data(0);
+ EXPECT_EQ(data.dimensions_in_what().field(),
+ android::util::SCHEDULED_JOB_STATE_CHANGED);
+ EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(),
+ 2); // job name field
+ EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(),
+ "job0"); // job name
+ ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(),
+ android::util::SYNC_STATE_CHANGED, 111, "App1");
+ EXPECT_EQ(data.bucket_info_size(), 1);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 40);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+
+ data = metrics.data(1);
+ EXPECT_EQ(data.dimensions_in_what().field(),
+ android::util::SCHEDULED_JOB_STATE_CHANGED);
+ EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(),
+ 2); // job name field
+ EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(),
+ "job1"); // job name
+ ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(),
+ android::util::SYNC_STATE_CHANGED, 333, "App2");
+ EXPECT_EQ(data.bucket_info_size(), 1);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 100);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + 2 * bucketSizeNs);
+
+ data = metrics.data(2);
+ EXPECT_EQ(data.dimensions_in_what().field(),
+ android::util::SCHEDULED_JOB_STATE_CHANGED);
+ EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(),
+ 2); // job name field
+ EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(),
+ "job2"); // job name
+ ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(),
+ android::util::SYNC_STATE_CHANGED, 111, "App1");
+ EXPECT_EQ(data.bucket_info_size(), 2);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 201);
+ EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 600 + 300);
+ EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + 2 * bucketSizeNs);
+
+ data = metrics.data(3);
+ EXPECT_EQ(data.dimensions_in_what().field(),
+ android::util::SCHEDULED_JOB_STATE_CHANGED);
+ EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(),
+ 2); // job name field
+ EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(),
+ "job2"); // job name
+ ValidateAttributionUidAndTagDimension(data.dimensions_in_condition(),
+ android::util::SYNC_STATE_CHANGED, 333, "App2");
+ EXPECT_EQ(data.bucket_info_size(), 2);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 401 );
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 600 + 700);
+ EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + 2 * bucketSizeNs);
+ }
+ }
+ }
+}
+
+namespace {
+
+StatsdConfig createDurationMetric_Link_SimpleConditionConfig(
+ DurationMetric::AggregationType aggregationType, bool addExtraDimensionInCondition) {
+ StatsdConfig config;
+ *config.add_atom_matcher() = CreateStartScheduledJobAtomMatcher();
+ *config.add_atom_matcher() = CreateFinishScheduledJobAtomMatcher();
+ *config.add_atom_matcher() = CreateSyncStartAtomMatcher();
+ *config.add_atom_matcher() = CreateSyncEndAtomMatcher();
+
+ auto scheduledJobPredicate = CreateScheduledJobPredicate();
+ auto dimensions = scheduledJobPredicate.mutable_simple_predicate()->mutable_dimensions();
+ *dimensions = CreateAttributionUidDimensions(
+ android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST});
+ dimensions->add_child()->set_field(2); // job name field.
+
+ auto isSyncingPredicate = CreateIsSyncingPredicate();
+ auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions();
+ *syncDimension = CreateAttributionUidDimensions(
+ android::util::SYNC_STATE_CHANGED, {Position::FIRST});
+ if (addExtraDimensionInCondition) {
+ syncDimension->add_child()->set_field(2 /* name field*/);
+ }
+
+ *config.add_predicate() = scheduledJobPredicate;
+ *config.add_predicate() = isSyncingPredicate;
+
+ auto metric = config.add_duration_metric();
+ metric->set_bucket(FIVE_MINUTES);
+ metric->set_id(StringToId("scheduledJob"));
+ metric->set_what(scheduledJobPredicate.id());
+ metric->set_condition(isSyncingPredicate.id());
+ metric->set_aggregation_type(aggregationType);
+ *metric->mutable_dimensions_in_what() = CreateAttributionUidDimensions(
+ android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST});
+
+ auto links = metric->add_links();
+ links->set_condition(isSyncingPredicate.id());
+ *links->mutable_fields_in_what() =
+ CreateAttributionUidDimensions(
+ android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST});
+ *links->mutable_fields_in_condition() =
+ CreateAttributionUidDimensions(android::util::SYNC_STATE_CHANGED, {Position::FIRST});
+ return config;
+}
+
+} // namespace
+
+TEST(DimensionInConditionE2eTest, TestDurationMetric_Link_SimpleCondition) {
+ for (bool isFullLink : {true, false}) {
+ for (auto aggregationType : {DurationMetric::SUM, DurationMetric::MAX_SPARSE}) {
+ ConfigKey cfgKey;
+ auto config = createDurationMetric_Link_SimpleConditionConfig(
+ aggregationType, !isFullLink);
+ int64_t bucketStartTimeNs = 10000000000;
+ int64_t bucketSizeNs =
+ TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
+
+ auto processor = CreateStatsLogProcessor(
+ bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+ EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+ EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
+
+ std::vector<AttributionNodeInternal> attributions1 = {
+ CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"),
+ CreateAttribution(222, "GMSCoreModule2")};
+
+ std::vector<AttributionNodeInternal> attributions2 = {
+ CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"),
+ CreateAttribution(555, "GMSCoreModule2")};
+
+ std::vector<AttributionNodeInternal> attributions3 = {
+ CreateAttribution(444, "App3"), CreateAttribution(222, "GMSCoreModule1"),
+ CreateAttribution(555, "GMSCoreModule2")};
+
+ std::vector<std::unique_ptr<LogEvent>> events;
+
+ events.push_back(CreateStartScheduledJobEvent(
+ {CreateAttribution(111, "App1")}, "job1", bucketStartTimeNs + 1));
+ events.push_back(CreateFinishScheduledJobEvent(
+ {CreateAttribution(111, "App1")}, "job1",bucketStartTimeNs + 101));
+
+ events.push_back(CreateStartScheduledJobEvent(
+ {CreateAttribution(333, "App2")}, "job2", bucketStartTimeNs + 201));
+ events.push_back(CreateFinishScheduledJobEvent(
+ {CreateAttribution(333, "App2")}, "job2",bucketStartTimeNs + 500));
+ events.push_back(CreateStartScheduledJobEvent(
+ {CreateAttribution(333, "App2")}, "job2", bucketStartTimeNs + 600));
+ events.push_back(
+ CreateFinishScheduledJobEvent({CreateAttribution(333, "App2")}, "job2",
+ bucketStartTimeNs + bucketSizeNs + 850));
+
+ events.push_back(
+ CreateStartScheduledJobEvent({CreateAttribution(444, "App3")}, "job3",
+ bucketStartTimeNs + bucketSizeNs - 2));
+ events.push_back(
+ CreateFinishScheduledJobEvent({CreateAttribution(444, "App3")}, "job3",
+ bucketStartTimeNs + bucketSizeNs + 900));
+
+ events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail",
+ bucketStartTimeNs + 50));
+ events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail",
+ bucketStartTimeNs + 110));
+
+ events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail",
+ bucketStartTimeNs + 300));
+ events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail",
+ bucketStartTimeNs + bucketSizeNs + 700));
+ events.push_back(CreateSyncStartEvent(attributions2, "ReadDoc",
+ bucketStartTimeNs + 400));
+ events.push_back(CreateSyncEndEvent(attributions2, "ReadDoc",
+ bucketStartTimeNs + bucketSizeNs - 1));
+
+ events.push_back(CreateSyncStartEvent(attributions3, "ReadDoc",
+ bucketStartTimeNs + 550));
+ events.push_back(CreateSyncEndEvent(attributions3, "ReadDoc",
+ bucketStartTimeNs + 800));
+ events.push_back(CreateSyncStartEvent(attributions3, "ReadDoc",
+ bucketStartTimeNs + bucketSizeNs - 1));
+ events.push_back(CreateSyncEndEvent(attributions3, "ReadDoc",
+ bucketStartTimeNs + bucketSizeNs + 700));
+
+ sortLogEventsByTimestamp(&events);
+
+ for (const auto& event : events) {
+ processor->OnLogEvent(event.get());
+ }
+
+ ConfigMetricsReportList reports;
+ vector<uint8_t> buffer;
+ processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, &buffer);
+ EXPECT_TRUE(buffer.size() > 0);
+ EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+
+ EXPECT_EQ(reports.reports_size(), 1);
+ EXPECT_EQ(reports.reports(0).metrics_size(), 1);
+ StatsLogReport::DurationMetricDataWrapper metrics;
+ sortMetricDataByDimensionsValue(
+ reports.reports(0).metrics(0).duration_metrics(), &metrics);
+
+ if (aggregationType == DurationMetric::SUM) {
+ EXPECT_EQ(metrics.data_size(), 3);
+ auto data = metrics.data(0);
+ ValidateAttributionUidDimension(
+ data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 111);
+ EXPECT_EQ(data.bucket_info_size(), 1);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 101 - 50);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+
+ data = metrics.data(1);
+ ValidateAttributionUidDimension(
+ data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333);
+ EXPECT_EQ(data.bucket_info_size(), 2);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 300 + bucketSizeNs - 600);
+ EXPECT_EQ(data.bucket_info(1).duration_nanos(), 700);
+ EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + 2 * bucketSizeNs);
+
+ data = metrics.data(2);
+ ValidateAttributionUidDimension(
+ data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 444);
+ EXPECT_EQ(data.bucket_info_size(), 2);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 1);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(1).duration_nanos(), 700);
+ EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + 2 * bucketSizeNs);
+ } else {
+ EXPECT_EQ(metrics.data_size(), 3);
+ auto data = metrics.data(0);
+ ValidateAttributionUidDimension(
+ data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 111);
+ EXPECT_EQ(data.bucket_info_size(), 1);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 101 - 50);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+
+ data = metrics.data(1);
+ ValidateAttributionUidDimension(
+ data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333);
+ EXPECT_EQ(data.bucket_info_size(), 2);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 300);
+ EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 600 + 700);
+ EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + 2 * bucketSizeNs);
+
+ data = metrics.data(2);
+ ValidateAttributionUidDimension(
+ data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 444);
+ EXPECT_EQ(data.bucket_info_size(), 1);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 701);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + 2 * bucketSizeNs);
+ }
+ }
+ }
+}
+
+namespace {
+
+StatsdConfig createDurationMetric_PartialLink_SimpleConditionConfig(
+ DurationMetric::AggregationType aggregationType) {
+ StatsdConfig config;
+ *config.add_atom_matcher() = CreateStartScheduledJobAtomMatcher();
+ *config.add_atom_matcher() = CreateFinishScheduledJobAtomMatcher();
+ *config.add_atom_matcher() = CreateSyncStartAtomMatcher();
+ *config.add_atom_matcher() = CreateSyncEndAtomMatcher();
+
+ auto scheduledJobPredicate = CreateScheduledJobPredicate();
+ auto dimensions = scheduledJobPredicate.mutable_simple_predicate()->mutable_dimensions();
+ *dimensions = CreateAttributionUidDimensions(
+ android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST});
+ dimensions->add_child()->set_field(2); // job name field.
+
+ auto isSyncingPredicate = CreateIsSyncingPredicate();
+ auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions();
+ *syncDimension = CreateAttributionUidDimensions(
+ android::util::SYNC_STATE_CHANGED, {Position::FIRST});
+ syncDimension->add_child()->set_field(2 /* name field*/);
+
+ *config.add_predicate() = scheduledJobPredicate;
+ *config.add_predicate() = isSyncingPredicate;
+
+ auto metric = config.add_duration_metric();
+ metric->set_bucket(FIVE_MINUTES);
+ metric->set_id(StringToId("scheduledJob"));
+ metric->set_what(scheduledJobPredicate.id());
+ metric->set_condition(isSyncingPredicate.id());
+ metric->set_aggregation_type(aggregationType);
+ *metric->mutable_dimensions_in_what() = CreateAttributionUidDimensions(
+ android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST});
+ *metric->mutable_dimensions_in_condition() = *syncDimension;
+
+ auto links = metric->add_links();
+ links->set_condition(isSyncingPredicate.id());
+ *links->mutable_fields_in_what() =
+ CreateAttributionUidDimensions(
+ android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST});
+ *links->mutable_fields_in_condition() =
+ CreateAttributionUidDimensions(android::util::SYNC_STATE_CHANGED, {Position::FIRST});
+ return config;
+}
+
+} // namespace
+
+TEST(DimensionInConditionE2eTest, TestDurationMetric_PartialLink_SimpleCondition) {
+ for (auto aggregationType : {DurationMetric::SUM, DurationMetric::MAX_SPARSE}) {
+ ConfigKey cfgKey;
+ auto config = createDurationMetric_PartialLink_SimpleConditionConfig(
+ aggregationType);
+ int64_t bucketStartTimeNs = 10000000000;
+ int64_t bucketSizeNs =
+ TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
+
+ auto processor = CreateStatsLogProcessor(
+ bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+ EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+ EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
+
+ std::vector<AttributionNodeInternal> attributions1 = {
+ CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"),
+ CreateAttribution(222, "GMSCoreModule2")};
+
+ std::vector<AttributionNodeInternal> attributions2 = {
+ CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"),
+ CreateAttribution(555, "GMSCoreModule2")};
+
+ std::vector<AttributionNodeInternal> attributions3 = {
+ CreateAttribution(444, "App3"), CreateAttribution(222, "GMSCoreModule1"),
+ CreateAttribution(555, "GMSCoreModule2")};
+
+ std::vector<std::unique_ptr<LogEvent>> events;
+
+ events.push_back(CreateStartScheduledJobEvent(
+ {CreateAttribution(111, "App1")}, "job1", bucketStartTimeNs + 1));
+ events.push_back(CreateFinishScheduledJobEvent(
+ {CreateAttribution(111, "App1")}, "job1",bucketStartTimeNs + 101));
+
+ events.push_back(CreateStartScheduledJobEvent(
+ {CreateAttribution(333, "App2")}, "job2", bucketStartTimeNs + 201));
+ events.push_back(CreateFinishScheduledJobEvent(
+ {CreateAttribution(333, "App2")}, "job2",bucketStartTimeNs + 500));
+ events.push_back(CreateStartScheduledJobEvent(
+ {CreateAttribution(333, "App2")}, "job2", bucketStartTimeNs + 600));
+ events.push_back(CreateFinishScheduledJobEvent(
+ {CreateAttribution(333, "App2")}, "job2", bucketStartTimeNs + bucketSizeNs + 850));
+
+ events.push_back(
+ CreateStartScheduledJobEvent({CreateAttribution(444, "App3")}, "job3",
+ bucketStartTimeNs + bucketSizeNs - 2));
+ events.push_back(
+ CreateFinishScheduledJobEvent({CreateAttribution(444, "App3")}, "job3",
+ bucketStartTimeNs + bucketSizeNs + 900));
+
+ events.push_back(CreateSyncStartEvent(attributions1, "ReadEmail",
+ bucketStartTimeNs + 50));
+ events.push_back(CreateSyncEndEvent(attributions1, "ReadEmail",
+ bucketStartTimeNs + 110));
+
+ events.push_back(CreateSyncStartEvent(attributions2, "ReadEmail",
+ bucketStartTimeNs + 300));
+ events.push_back(CreateSyncEndEvent(attributions2, "ReadEmail",
+ bucketStartTimeNs + bucketSizeNs + 700));
+ events.push_back(CreateSyncStartEvent(attributions2, "ReadDoc",
+ bucketStartTimeNs + 400));
+ events.push_back(CreateSyncEndEvent(attributions2, "ReadDoc",
+ bucketStartTimeNs + bucketSizeNs - 1));
+
+ events.push_back(CreateSyncStartEvent(attributions3, "ReadDoc",
+ bucketStartTimeNs + 550));
+ events.push_back(CreateSyncEndEvent(attributions3, "ReadDoc",
+ bucketStartTimeNs + 800));
+ events.push_back(CreateSyncStartEvent(attributions3, "ReadDoc",
+ bucketStartTimeNs + bucketSizeNs - 1));
+ events.push_back(CreateSyncEndEvent(attributions3, "ReadDoc",
+ bucketStartTimeNs + bucketSizeNs + 700));
+
+ sortLogEventsByTimestamp(&events);
+
+ for (const auto& event : events) {
+ processor->OnLogEvent(event.get());
+ }
+
+ ConfigMetricsReportList reports;
+ vector<uint8_t> buffer;
+ processor->onDumpReport(cfgKey, bucketStartTimeNs + 2 * bucketSizeNs + 1, &buffer);
+ EXPECT_TRUE(buffer.size() > 0);
+ EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+
+ EXPECT_EQ(reports.reports_size(), 1);
+ EXPECT_EQ(reports.reports(0).metrics_size(), 1);
+ StatsLogReport::DurationMetricDataWrapper metrics;
+ sortMetricDataByDimensionsValue(
+ reports.reports(0).metrics(0).duration_metrics(), &metrics);
+
+ if (aggregationType == DurationMetric::SUM) {
+ EXPECT_EQ(4, metrics.data_size());
+ auto data = metrics.data(0);
+ ValidateAttributionUidDimension(
+ data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 111);
+ ValidateAttributionUidDimension(
+ data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 111);
+ EXPECT_EQ("ReadEmail",
+ data.dimensions_in_condition().value_tuple().dimensions_value(1).value_str());
+ EXPECT_EQ(data.bucket_info_size(), 1);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 101 - 50);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+
+ data = metrics.data(1);
+ ValidateAttributionUidDimension(
+ data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333);
+ ValidateAttributionUidDimension(
+ data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 333);
+ EXPECT_EQ("ReadDoc",
+ data.dimensions_in_condition().value_tuple().dimensions_value(1).value_str());
+ EXPECT_EQ(data.bucket_info_size(), 1);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), bucketSizeNs - 1 - 400 - 100);
+
+ data = metrics.data(2);
+ ValidateAttributionUidDimension(
+ data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333);
+ ValidateAttributionUidDimension(
+ data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 333);
+ EXPECT_EQ("ReadEmail",
+ data.dimensions_in_condition().value_tuple().dimensions_value(1).value_str());
+ EXPECT_EQ(data.bucket_info_size(), 2);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 300 + bucketSizeNs - 600);
+ EXPECT_EQ(data.bucket_info(1).duration_nanos(), 700);
+ EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + 2 * bucketSizeNs);
+
+ data = metrics.data(3);
+ ValidateAttributionUidDimension(
+ data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 444);
+ ValidateAttributionUidDimension(
+ data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 444);
+ EXPECT_EQ("ReadDoc",
+ data.dimensions_in_condition().value_tuple().dimensions_value(1).value_str());
+ EXPECT_EQ(data.bucket_info_size(), 2);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 1);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(1).duration_nanos(), 700);
+ EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + 2 * bucketSizeNs);
+ } else {
+ EXPECT_EQ(metrics.data_size(), 4);
+ auto data = metrics.data(0);
+ ValidateAttributionUidDimension(
+ data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 111);
+ ValidateAttributionUidDimension(
+ data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 111);
+ EXPECT_EQ("ReadEmail",
+ data.dimensions_in_condition().value_tuple().dimensions_value(1).value_str());
+ EXPECT_EQ(data.bucket_info_size(), 1);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 101 - 50);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+
+ data = metrics.data(1);
+ ValidateAttributionUidDimension(
+ data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333);
+ ValidateAttributionUidDimension(
+ data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 333);
+ EXPECT_EQ("ReadDoc",
+ data.dimensions_in_condition().value_tuple().dimensions_value(1).value_str());
+ EXPECT_EQ(data.bucket_info_size(), 2);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 100);
+ EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + 2 * bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 1 - 600);
+
+ data = metrics.data(2);
+ ValidateAttributionUidDimension(
+ data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 333);
+ ValidateAttributionUidDimension(
+ data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 333);
+ EXPECT_EQ("ReadEmail",
+ data.dimensions_in_condition().value_tuple().dimensions_value(1).value_str());
+ EXPECT_EQ(data.bucket_info_size(), 2);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(), bucketStartTimeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 500 - 300);
+ EXPECT_EQ(data.bucket_info(1).duration_nanos(), bucketSizeNs - 600 + 700);
+ EXPECT_EQ(data.bucket_info(1).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(1).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + 2 * bucketSizeNs);
+
+ data = metrics.data(3);
+ ValidateAttributionUidDimension(
+ data.dimensions_in_what(), android::util::SCHEDULED_JOB_STATE_CHANGED, 444);
+ ValidateAttributionUidDimension(
+ data.dimensions_in_condition(), android::util::SYNC_STATE_CHANGED, 444);
+ EXPECT_EQ("ReadDoc",
+ data.dimensions_in_condition().value_tuple().dimensions_value(1).value_str());
+ EXPECT_EQ(data.bucket_info_size(), 1);
+ EXPECT_EQ(data.bucket_info(0).duration_nanos(), 701);
+ EXPECT_EQ(data.bucket_info(0).start_bucket_elapsed_nanos(),
+ bucketStartTimeNs + bucketSizeNs);
+ EXPECT_EQ(data.bucket_info(0).end_bucket_elapsed_nanos(),
+ bucketStartTimeNs + 2 * bucketSizeNs);
+ }
+ }
+}
+
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
index 9a0de0d..a07683e 100644
--- a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
@@ -175,9 +175,9 @@
{getMockedDimensionKey(conditionTagId, 2, "222")};
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
- EXPECT_CALL(*wizard, query(_, key1, _, _)).WillOnce(Return(ConditionState::kFalse));
+ EXPECT_CALL(*wizard, query(_, key1, _, _, _, _)).WillOnce(Return(ConditionState::kFalse));
- EXPECT_CALL(*wizard, query(_, key2, _, _)).WillOnce(Return(ConditionState::kTrue));
+ EXPECT_CALL(*wizard, query(_, key2, _, _, _, _)).WillOnce(Return(ConditionState::kTrue));
CountMetricProducer countProducer(kConfigKey, metric, 1 /*condition tracker index*/, wizard,
bucketStartTimeNs);
diff --git a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp
index 8246268..23d3171 100644
--- a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp
@@ -113,9 +113,9 @@
key2[StringToId("APP_IN_BACKGROUND_PER_UID")] = {getMockedDimensionKey(conditionTagId, 2, "222")};
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
- EXPECT_CALL(*wizard, query(_, key1, _, _)).WillOnce(Return(ConditionState::kFalse));
+ EXPECT_CALL(*wizard, query(_, key1, _, _, _, _)).WillOnce(Return(ConditionState::kFalse));
- EXPECT_CALL(*wizard, query(_, key2, _, _)).WillOnce(Return(ConditionState::kTrue));
+ EXPECT_CALL(*wizard, query(_, key2, _, _, _, _)).WillOnce(Return(ConditionState::kTrue));
EventMetricProducer eventProducer(kConfigKey, metric, 1, wizard, bucketStartTimeNs);
diff --git a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
index 83b1cbf..57a8925 100644
--- a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
@@ -42,14 +42,13 @@
const int TagId = 1;
const HashableDimensionKey eventKey = getMockedDimensionKey(TagId, 0, "1");
-const std::vector<HashableDimensionKey> conditionKey = {getMockedDimensionKey(TagId, 4, "1")};
+const HashableDimensionKey conditionKey = getMockedDimensionKey(TagId, 4, "1");
const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1");
const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2");
const uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
TEST(MaxDurationTrackerTest, TestSimpleMaxDuration) {
const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1");
- const std::vector<HashableDimensionKey> conditionKey = {getMockedDimensionKey(TagId, 4, "1")};
const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1");
const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2");
@@ -66,7 +65,7 @@
int64_t metricId = 1;
MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition,
false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
- false, {});
+ false, false, {});
tracker.noteStart(key1, true, bucketStartTimeNs, ConditionKey());
// Event starts again. This would not change anything as it already starts.
@@ -86,7 +85,6 @@
TEST(MaxDurationTrackerTest, TestStopAll) {
const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1");
- const std::vector<HashableDimensionKey> conditionKey = {getMockedDimensionKey(TagId, 4, "1")};
const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1");
const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2");
@@ -103,7 +101,7 @@
int64_t metricId = 1;
MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition,
false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
- false, {});
+ false, false, {});
tracker.noteStart(key1, true, bucketStartTimeNs + 1, ConditionKey());
@@ -124,7 +122,6 @@
TEST(MaxDurationTrackerTest, TestCrossBucketBoundary) {
const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1");
- const std::vector<HashableDimensionKey> conditionKey = {getMockedDimensionKey(TagId, 4, "1")};
const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1");
const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2");
vector<Matcher> dimensionInCondition;
@@ -140,7 +137,7 @@
int64_t metricId = 1;
MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition,
false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
- false, {});
+ false, false, {});
// The event starts.
tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, ConditionKey());
@@ -166,7 +163,6 @@
TEST(MaxDurationTrackerTest, TestCrossBucketBoundary_nested) {
const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1");
- const std::vector<HashableDimensionKey> conditionKey = {getMockedDimensionKey(TagId, 4, "1")};
const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1");
const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2");
vector<Matcher> dimensionInCondition;
@@ -182,7 +178,7 @@
int64_t metricId = 1;
MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, dimensionInCondition,
true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
- false, {});
+ false, false, {});
// 2 starts
tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, ConditionKey());
@@ -204,14 +200,14 @@
}
TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) {
- const std::vector<HashableDimensionKey> conditionKey = {key1};
+ const HashableDimensionKey conditionDimKey = key1;
vector<Matcher> dimensionInCondition;
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
ConditionKey conditionKey1;
MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 1, "1");
- conditionKey1[StringToId("APP_BACKGROUND")] = conditionKey;
+ conditionKey1[StringToId("APP_BACKGROUND")] = conditionDimKey;
/**
Start in first bucket, stop in second bucket. Condition turns on and off in the first bucket
@@ -229,7 +225,7 @@
int64_t metricId = 1;
MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
false, bucketStartTimeNs, 0, bucketStartTimeNs, bucketSizeNs, true,
- {});
+ false, {});
EXPECT_TRUE(tracker.mAnomalyTrackers.empty());
tracker.noteStart(key1, false, eventStartTimeNs, conditionKey1);
@@ -250,8 +246,6 @@
}
TEST(MaxDurationTrackerTest, TestAnomalyDetection) {
- const std::vector<HashableDimensionKey> conditionKey = {getMockedDimensionKey(TagId, 4, "1")};
-
vector<Matcher> dimensionInCondition;
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
@@ -281,7 +275,7 @@
new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
- true, {anomalyTracker});
+ true, false, {anomalyTracker});
tracker.noteStart(key1, true, eventStartTimeNs, conditionKey1);
sp<const InternalAlarm> alarm = anomalyTracker->mAlarms.begin()->second;
@@ -301,8 +295,6 @@
// This tests that we correctly compute the predicted time of an anomaly assuming that the current
// state continues forward as-is.
TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp) {
- const std::vector<HashableDimensionKey> conditionKey = {getMockedDimensionKey(TagId, 4, "1")};
-
vector<Matcher> dimensionInCondition;
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
@@ -310,7 +302,7 @@
MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 2, "maps");
conditionKey1[StringToId("APP_BACKGROUND")] = conditionKey;
ConditionKey conditionKey2;
- conditionKey2[StringToId("APP_BACKGROUND")] = {getMockedDimensionKey(TagId, 4, "2")};
+ conditionKey2[StringToId("APP_BACKGROUND")] = getMockedDimensionKey(TagId, 4, "2");
unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
@@ -343,7 +335,7 @@
new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
- true, {anomalyTracker});
+ true, false, {anomalyTracker});
tracker.noteStart(key1, false, eventStartTimeNs, conditionKey1);
tracker.noteConditionChanged(key1, true, conditionStarts1);
@@ -360,8 +352,6 @@
// Suppose A starts, then B starts, and then A stops. We still need to set an anomaly based on the
// elapsed duration of B.
TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp_UpdatedOnStop) {
- const std::vector<HashableDimensionKey> conditionKey = {getMockedDimensionKey(TagId, 4, "1")};
-
vector<Matcher> dimensionInCondition;
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
@@ -369,7 +359,7 @@
MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 2, "maps");
conditionKey1[StringToId("APP_BACKGROUND")] = conditionKey;
ConditionKey conditionKey2;
- conditionKey2[StringToId("APP_BACKGROUND")] = {getMockedDimensionKey(TagId, 4, "2")};
+ conditionKey2[StringToId("APP_BACKGROUND")] = getMockedDimensionKey(TagId, 4, "2");
unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
@@ -399,7 +389,7 @@
new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
- true, {anomalyTracker});
+ true, false, {anomalyTracker});
tracker.noteStart(key1, true, eventStartTimeNs1, conditionKey1);
tracker.noteStart(key2, true, eventStartTimeNs2, conditionKey2);
@@ -415,4 +405,4 @@
} // namespace android
#else
GTEST_LOG_(INFO) << "This test does nothing.\n";
-#endif
+#endif
\ No newline at end of file
diff --git a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
index aa41038..54abcb2 100644
--- a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
@@ -40,7 +40,7 @@
const int64_t metricId = 123;
const HashableDimensionKey eventKey = getMockedDimensionKey(TagId, 0, "event");
-const std::vector<HashableDimensionKey> kConditionKey1 = {getMockedDimensionKey(TagId, 1, "maps")};
+const HashableDimensionKey kConditionKey1 = getMockedDimensionKey(TagId, 1, "maps");
const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
const uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
@@ -48,8 +48,6 @@
TEST(OringDurationTrackerTest, TestDurationOverlap) {
const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
- const std::vector<HashableDimensionKey> kConditionKey1 =
- {getMockedDimensionKey(TagId, 1, "maps")};
const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
vector<Matcher> dimensionInCondition;
@@ -65,7 +63,7 @@
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
false, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
- bucketSizeNs, false, {});
+ bucketSizeNs, false, false, {});
tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
@@ -83,8 +81,6 @@
TEST(OringDurationTrackerTest, TestDurationNested) {
const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
- const std::vector<HashableDimensionKey> kConditionKey1 =
- {getMockedDimensionKey(TagId, 1, "maps")};
const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
vector<Matcher> dimensionInCondition;
@@ -99,7 +95,7 @@
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
- bucketSizeNs, false, {});
+ bucketSizeNs, false, false, {});
tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
tracker.noteStart(kEventKey1, true, eventStartTimeNs + 10, ConditionKey()); // overlapping wl
@@ -132,7 +128,7 @@
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
- bucketSizeNs, false, {});
+ bucketSizeNs, false, false, {});
tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
tracker.noteStart(kEventKey2, true, eventStartTimeNs + 10, ConditionKey()); // overlapping wl
@@ -148,8 +144,6 @@
TEST(OringDurationTrackerTest, TestCrossBucketBoundary) {
const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
- const std::vector<HashableDimensionKey> kConditionKey1 =
- {getMockedDimensionKey(TagId, 1, "maps")};
const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
vector<Matcher> dimensionInCondition;
@@ -165,7 +159,7 @@
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
- bucketSizeNs, false, {});
+ bucketSizeNs, false, false, {});
tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
@@ -189,8 +183,6 @@
TEST(OringDurationTrackerTest, TestDurationConditionChange) {
const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
- const std::vector<HashableDimensionKey> kConditionKey1 =
- {getMockedDimensionKey(TagId, 1, "maps")};
const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
vector<Matcher> dimensionInCondition;
@@ -199,7 +191,7 @@
ConditionKey key1;
key1[StringToId("APP_BACKGROUND")] = kConditionKey1;
- EXPECT_CALL(*wizard, query(_, key1, _, _)) // #4
+ EXPECT_CALL(*wizard, query(_, key1, _, _, _, _)) // #4
.WillOnce(Return(ConditionState::kFalse));
unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
@@ -212,7 +204,7 @@
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
false, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
- bucketSizeNs, true, {});
+ bucketSizeNs, true, false, {});
tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
@@ -229,8 +221,6 @@
TEST(OringDurationTrackerTest, TestDurationConditionChange2) {
const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
- const std::vector<HashableDimensionKey> kConditionKey1 =
- {getMockedDimensionKey(TagId, 1, "maps")};
const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
vector<Matcher> dimensionInCondition;
@@ -239,7 +229,7 @@
ConditionKey key1;
key1[StringToId("APP_BACKGROUND")] = kConditionKey1;
- EXPECT_CALL(*wizard, query(_, key1, _, _))
+ EXPECT_CALL(*wizard, query(_, key1, _, _, _, _))
.Times(2)
.WillOnce(Return(ConditionState::kFalse))
.WillOnce(Return(ConditionState::kTrue));
@@ -254,7 +244,7 @@
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
false, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
- bucketSizeNs, true, {});
+ bucketSizeNs, true, false, {});
tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
// condition to false; record duration 5n
@@ -273,8 +263,6 @@
TEST(OringDurationTrackerTest, TestDurationConditionChangeNested) {
const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
- const std::vector<HashableDimensionKey> kConditionKey1 =
- {getMockedDimensionKey(TagId, 1, "maps")};
const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
vector<Matcher> dimensionInCondition;
@@ -283,7 +271,7 @@
ConditionKey key1;
key1[StringToId("APP_BACKGROUND")] = kConditionKey1;
- EXPECT_CALL(*wizard, query(_, key1, _, _)) // #4
+ EXPECT_CALL(*wizard, query(_, key1, _, _, _, _)) // #4
.WillOnce(Return(ConditionState::kFalse));
unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
@@ -295,7 +283,7 @@
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
- bucketSizeNs, true, {});
+ bucketSizeNs, true, false, {});
tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
tracker.noteStart(kEventKey1, true, eventStartTimeNs + 2, key1);
@@ -315,8 +303,6 @@
TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) {
const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
- const std::vector<HashableDimensionKey> kConditionKey1 =
- {getMockedDimensionKey(TagId, 1, "maps")};
const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
vector<Matcher> dimensionInCondition;
@@ -339,7 +325,7 @@
new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
- bucketSizeNs, true, {anomalyTracker});
+ bucketSizeNs, true, false, {anomalyTracker});
// Nothing in the past bucket.
tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, ConditionKey());
@@ -386,7 +372,6 @@
TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm) {
const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
- const std::vector<HashableDimensionKey> kConditionKey1 = {getMockedDimensionKey(TagId, 1, "maps")};
const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
vector<Matcher> dimensionInCondition;
@@ -410,7 +395,7 @@
new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
true /*nesting*/, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
- bucketSizeNs, false, {anomalyTracker});
+ bucketSizeNs, false, false, {anomalyTracker});
tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
tracker.noteStop(kEventKey1, eventStartTimeNs + 10, false);
@@ -437,8 +422,6 @@
TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm) {
const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event");
- const std::vector<HashableDimensionKey> kConditionKey1 =
- {getMockedDimensionKey(TagId, 1, "maps")};
const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps");
const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps");
vector<Matcher> dimensionInCondition;
@@ -462,7 +445,7 @@
new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, dimensionInCondition,
true /*nesting*/, bucketStartTimeNs, 0, bucketStartTimeNs,
- bucketSizeNs, false, {anomalyTracker});
+ bucketSizeNs, false, false, {anomalyTracker});
tracker.noteStart(kEventKey1, true, 15 * NS_PER_SEC, conkey); // start key1
EXPECT_EQ(1u, anomalyTracker->mAlarms.size());
@@ -508,4 +491,4 @@
} // namespace android
#else
GTEST_LOG_(INFO) << "This test does nothing.\n";
-#endif
+#endif
\ No newline at end of file
diff --git a/cmds/statsd/tests/metrics/metrics_test_helper.h b/cmds/statsd/tests/metrics/metrics_test_helper.h
index a01de63..f040bf9 100644
--- a/cmds/statsd/tests/metrics/metrics_test_helper.h
+++ b/cmds/statsd/tests/metrics/metrics_test_helper.h
@@ -26,9 +26,10 @@
class MockConditionWizard : public ConditionWizard {
public:
- MOCK_METHOD4(query,
+ MOCK_METHOD6(query,
ConditionState(const int conditionIndex, const ConditionKey& conditionParameters,
const vector<Matcher>& dimensionFields,
+ const bool isSubsetDim, const bool isPartialLink,
std::unordered_set<HashableDimensionKey>* dimensionKeySet));
};
diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp
index 242b6eb..2678c8a 100644
--- a/cmds/statsd/tests/statsd_test_util.cpp
+++ b/cmds/statsd/tests/statsd_test_util.cpp
@@ -26,6 +26,28 @@
return atom_matcher;
}
+AtomMatcher CreateScheduledJobStateChangedAtomMatcher(const string& name,
+ ScheduledJobStateChanged::State state) {
+ AtomMatcher atom_matcher;
+ atom_matcher.set_id(StringToId(name));
+ auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
+ simple_atom_matcher->set_atom_id(android::util::SCHEDULED_JOB_STATE_CHANGED);
+ auto field_value_matcher = simple_atom_matcher->add_field_value_matcher();
+ field_value_matcher->set_field(3); // State field.
+ field_value_matcher->set_eq_int(state);
+ return atom_matcher;
+}
+
+AtomMatcher CreateStartScheduledJobAtomMatcher() {
+ return CreateScheduledJobStateChangedAtomMatcher("ScheduledJobStart",
+ ScheduledJobStateChanged::STARTED);
+}
+
+AtomMatcher CreateFinishScheduledJobAtomMatcher() {
+ return CreateScheduledJobStateChangedAtomMatcher("ScheduledJobFinish",
+ ScheduledJobStateChanged::FINISHED);
+}
+
AtomMatcher CreateScreenBrightnessChangedAtomMatcher() {
AtomMatcher atom_matcher;
atom_matcher.set_id(StringToId("ScreenBrightnessChanged"));
@@ -168,6 +190,14 @@
"ProcessCrashed", ProcessLifeCycleStateChanged::PROCESS_CRASHED);
}
+Predicate CreateScheduledJobPredicate() {
+ Predicate predicate;
+ predicate.set_id(StringToId("ScheduledJobRunningPredicate"));
+ predicate.mutable_simple_predicate()->set_start(StringToId("ScheduledJobStart"));
+ predicate.mutable_simple_predicate()->set_stop(StringToId("ScheduledJobFinish"));
+ return predicate;
+}
+
Predicate CreateBatterySaverModePredicate() {
Predicate predicate;
predicate.set_id(StringToId("BatterySaverIsOn"));
@@ -290,6 +320,32 @@
}
+std::unique_ptr<LogEvent> CreateScheduledJobStateChangedEvent(
+ const std::vector<AttributionNodeInternal>& attributions, const string& jobName,
+ const ScheduledJobStateChanged::State state, uint64_t timestampNs) {
+ auto event = std::make_unique<LogEvent>(android::util::SCHEDULED_JOB_STATE_CHANGED, timestampNs);
+ event->write(attributions);
+ event->write(jobName);
+ event->write(state);
+ event->init();
+ return event;
+}
+
+std::unique_ptr<LogEvent> CreateStartScheduledJobEvent(
+ const std::vector<AttributionNodeInternal>& attributions,
+ const string& name, uint64_t timestampNs) {
+ return CreateScheduledJobStateChangedEvent(
+ attributions, name, ScheduledJobStateChanged::STARTED, timestampNs);
+}
+
+// Create log event when scheduled job finishes.
+std::unique_ptr<LogEvent> CreateFinishScheduledJobEvent(
+ const std::vector<AttributionNodeInternal>& attributions,
+ const string& name, uint64_t timestampNs) {
+ return CreateScheduledJobStateChangedEvent(
+ attributions, name, ScheduledJobStateChanged::FINISHED, timestampNs);
+}
+
std::unique_ptr<LogEvent> CreateWakelockStateChangedEvent(
const std::vector<AttributionNodeInternal>& attributions, const string& wakelockName,
const WakelockStateChanged::State state, uint64_t timestampNs) {
@@ -419,7 +475,6 @@
void ValidateAttributionUidDimension(const DimensionsValue& value, int atomId, int uid) {
EXPECT_EQ(value.field(), atomId);
- EXPECT_EQ(value.value_tuple().dimensions_value_size(), 1);
// Attribution field.
EXPECT_EQ(value.value_tuple().dimensions_value(0).field(), 1);
// Uid only.
diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h
index 1708cc3..14eba1f 100644
--- a/cmds/statsd/tests/statsd_test_util.h
+++ b/cmds/statsd/tests/statsd_test_util.h
@@ -28,6 +28,15 @@
// Create AtomMatcher proto to simply match a specific atom type.
AtomMatcher CreateSimpleAtomMatcher(const string& name, int atomId);
+// Create AtomMatcher proto for scheduled job state changed.
+AtomMatcher CreateScheduledJobStateChangedAtomMatcher();
+
+// Create AtomMatcher proto for starting a scheduled job.
+AtomMatcher CreateStartScheduledJobAtomMatcher();
+
+// Create AtomMatcher proto for a scheduled job is done.
+AtomMatcher CreateFinishScheduledJobAtomMatcher();
+
// Create AtomMatcher proto for screen brightness state changed.
AtomMatcher CreateScreenBrightnessChangedAtomMatcher();
@@ -73,6 +82,9 @@
// Create Predicate proto for screen is off.
Predicate CreateScreenIsOffPredicate();
+// Create Predicate proto for a running scheduled job.
+Predicate CreateScheduledJobPredicate();
+
// Create Predicate proto for battery saver mode.
Predicate CreateBatterySaverModePredicate();
@@ -107,6 +119,16 @@
std::unique_ptr<LogEvent> CreateScreenBrightnessChangedEvent(
int level, uint64_t timestampNs);
+// Create log event when scheduled job starts.
+std::unique_ptr<LogEvent> CreateStartScheduledJobEvent(
+ const std::vector<AttributionNodeInternal>& attributions,
+ const string& name, uint64_t timestampNs);
+
+// Create log event when scheduled job finishes.
+std::unique_ptr<LogEvent> CreateFinishScheduledJobEvent(
+ const std::vector<AttributionNodeInternal>& attributions,
+ const string& name, uint64_t timestampNs);
+
// Create log event when battery saver starts.
std::unique_ptr<LogEvent> CreateBatterySaverOnEvent(uint64_t timestampNs);
// Create log event when battery saver stops.