diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk index 605198f4b72a2..7f0a26c1714ea 100644 --- a/cmds/statsd/Android.mk +++ b/cmds/statsd/Android.mk @@ -199,7 +199,9 @@ LOCAL_SRC_FILES := \ 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 @@ include $(CLEAR_VARS) 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_static_libraries:= 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 0000000000000..2631009c71f26 --- /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 +#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 attributions1 = { + CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"), + CreateAttribution(222, "GMSCoreModule2")}; + + std::vector attributions2 = { + CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"), + CreateAttribution(555, "GMSCoreModule2")}; + + std::vector> 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 attributions1 = { + CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"), + CreateAttribution(222, "GMSCoreModule2")}; + + std::vector attributions2 = { + CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"), + CreateAttribution(555, "GMSCoreModule2")}; + + std::vector attributions3 = { + CreateAttribution(444, "App3"), CreateAttribution(222, "GMSCoreModule1"), + CreateAttribution(555, "GMSCoreModule2")}; + + std::vector> 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 b9ddf36d82955..66c4defe7adb8 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 @@ namespace statsd { using std::vector; +static void createLogEventAndMatcher(LogEvent* event, FieldMatcher *field_matcher) { + AttributionNodeInternal node; + node.set_uid(100); + node.set_tag("LOCATION"); + + std::vector 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 matchers; translateFieldMatcher(field_matcher, &matchers); @@ -48,6 +61,22 @@ static void BM_FilterValue(benchmark::State& state) { BENCHMARK(BM_FilterValue); +static void BM_FilterValue2(benchmark::State& state) { + LogEvent event(1, 100000); + FieldMatcher field_matcher; + createLogEventAndMatcher(&event, &field_matcher); + + std::vector 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 0000000000000..2a4403ed8282e --- /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 +#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 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 0000000000000..b67764bd5130a --- /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& 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& 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& fields) { + FieldMatcher dimensions; + dimensions.set_field(atomId); + for (const int field : fields) { + dimensions.add_child()->set_field(field); + } + return dimensions; +} + +std::unique_ptr CreateScreenStateChangedEvent( + const android::view::DisplayStateEnum state, uint64_t timestampNs) { + auto event = std::make_unique(android::util::SCREEN_STATE_CHANGED, timestampNs); + event->write(state); + event->init(); + return event; +} + +std::unique_ptr CreateScreenBrightnessChangedEvent( + int level, uint64_t timestampNs) { + auto event = std::make_unique(android::util::SCREEN_BRIGHTNESS_CHANGED, timestampNs); + (event->write(level)); + event->init(); + return event; + +} + +std::unique_ptr CreateScheduledJobStateChangedEvent( + const std::vector& attributions, const string& jobName, + const ScheduledJobStateChanged::State state, uint64_t timestampNs) { + auto event = std::make_unique(android::util::SCHEDULED_JOB_STATE_CHANGED, timestampNs); + event->write(attributions); + event->write(jobName); + event->write(state); + event->init(); + return event; +} + +std::unique_ptr CreateStartScheduledJobEvent( + const std::vector& attributions, + const string& name, uint64_t timestampNs) { + return CreateScheduledJobStateChangedEvent( + attributions, name, ScheduledJobStateChanged::STARTED, timestampNs); +} + +// Create log event when scheduled job finishes. +std::unique_ptr CreateFinishScheduledJobEvent( + const std::vector& attributions, + const string& name, uint64_t timestampNs) { + return CreateScheduledJobStateChangedEvent( + attributions, name, ScheduledJobStateChanged::FINISHED, timestampNs); +} + +std::unique_ptr CreateWakelockStateChangedEvent( + const std::vector& attributions, const string& wakelockName, + const WakelockStateChanged::State state, uint64_t timestampNs) { + auto event = std::make_unique(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 CreateAcquireWakelockEvent( + const std::vector& attributions, const string& wakelockName, + uint64_t timestampNs) { + return CreateWakelockStateChangedEvent( + attributions, wakelockName, WakelockStateChanged::ACQUIRE, timestampNs); +} + +std::unique_ptr CreateReleaseWakelockEvent( + const std::vector& attributions, const string& wakelockName, + uint64_t timestampNs) { + return CreateWakelockStateChangedEvent( + attributions, wakelockName, WakelockStateChanged::RELEASE, timestampNs); +} + +std::unique_ptr CreateActivityForegroundStateChangedEvent( + const int uid, const ActivityForegroundStateChanged::Activity activity, uint64_t timestampNs) { + auto event = std::make_unique( + 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 CreateMoveToBackgroundEvent(const int uid, uint64_t timestampNs) { + return CreateActivityForegroundStateChangedEvent( + uid, ActivityForegroundStateChanged::MOVE_TO_BACKGROUND, timestampNs); +} + +std::unique_ptr CreateMoveToForegroundEvent(const int uid, uint64_t timestampNs) { + return CreateActivityForegroundStateChangedEvent( + uid, ActivityForegroundStateChanged::MOVE_TO_FOREGROUND, timestampNs); +} + +std::unique_ptr CreateSyncStateChangedEvent( + const std::vector& attributions, const string& name, + const SyncStateChanged::State state, uint64_t timestampNs) { + auto event = std::make_unique(android::util::SYNC_STATE_CHANGED, timestampNs); + event->write(attributions); + event->write(name); + event->write(state); + event->init(); + return event; +} + +std::unique_ptr CreateSyncStartEvent( + const std::vector& attributions, const string& name, + uint64_t timestampNs) { + return CreateSyncStateChangedEvent(attributions, name, SyncStateChanged::ON, timestampNs); +} + +std::unique_ptr CreateSyncEndEvent( + const std::vector& attributions, const string& name, + uint64_t timestampNs) { + return CreateSyncStateChangedEvent(attributions, name, SyncStateChanged::OFF, timestampNs); +} + +sp CreateStatsLogProcessor(const long timeBaseSec, const StatsdConfig& config, + const ConfigKey& key) { + sp uidMap = new UidMap(); + sp anomalyAlarmMonitor; + sp periodicAlarmMonitor; + sp 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> *events) { + std::sort(events->begin(), events->end(), + [](const std::unique_ptr& a, const std::unique_ptr& b) { + return a->GetElapsedTimestampNs() < b->GetElapsedTimestampNs(); + }); +} + +int64_t StringToId(const string& str) { + return static_cast(std::hash()(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 0000000000000..9b28d60b38c07 --- /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& fields); + +// Create dimensions by attribution uid and tag. +FieldMatcher CreateAttributionUidAndTagDimensions(const int atomId, + const std::vector& positions); + +// Create dimensions by attribution uid only. +FieldMatcher CreateAttributionUidDimensions(const int atomId, + const std::vector& positions); + +// Create log event for screen state changed. +std::unique_ptr CreateScreenStateChangedEvent( + const android::view::DisplayStateEnum state, uint64_t timestampNs); + +// Create log event for screen brightness state changed. +std::unique_ptr CreateScreenBrightnessChangedEvent( + int level, uint64_t timestampNs); + +// Create log event when scheduled job starts. +std::unique_ptr CreateStartScheduledJobEvent( + const std::vector& attributions, + const string& name, uint64_t timestampNs); + +// Create log event when scheduled job finishes. +std::unique_ptr CreateFinishScheduledJobEvent( + const std::vector& attributions, + const string& name, uint64_t timestampNs); + +// Create log event for app moving to background. +std::unique_ptr CreateMoveToBackgroundEvent(const int uid, uint64_t timestampNs); + +// Create log event for app moving to foreground. +std::unique_ptr CreateMoveToForegroundEvent(const int uid, uint64_t timestampNs); + +// Create log event when the app sync starts. +std::unique_ptr CreateSyncStartEvent( + const std::vector& attributions, const string& name, + uint64_t timestampNs); + +// Create log event when the app sync ends. +std::unique_ptr CreateSyncEndEvent( + const std::vector& attributions, const string& name, + uint64_t timestampNs); + +// Create log event when the app sync ends. +std::unique_ptr CreateAppCrashEvent( + const int uid, uint64_t timestampNs); + +// Create log event for acquiring wakelock. +std::unique_ptr CreateAcquireWakelockEvent( + const std::vector& attributions, const string& wakelockName, + uint64_t timestampNs); + +// Create log event for releasing wakelock. +std::unique_ptr CreateReleaseWakelockEvent( + const std::vector& attributions, const string& wakelockName, + uint64_t timestampNs); + +// Create log event for releasing wakelock. +std::unique_ptr 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 CreateStatsLogProcessor(const long timeBaseSec, const StatsdConfig& config, + const ConfigKey& key); + +// Util function to sort the log events by timestamp. +void sortLogEventsByTimestamp(std::vector> *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 b541612f73df3..0c9b7016eaff1 100644 --- a/cmds/statsd/src/FieldValue.cpp +++ b/cmds/statsd/src/FieldValue.cpp @@ -205,6 +205,29 @@ bool Value::operator<(const Value& that) const { } } +bool equalDimensions(const std::vector& dimension_a, + const std::vector& 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 621d0be9e853d..0e3ae06033e75 100644 --- a/cmds/statsd/src/FieldValue.h +++ b/cmds/statsd/src/FieldValue.h @@ -336,11 +336,16 @@ struct FieldValue { Value mValue; }; +bool HasPositionANY(const FieldMatcher& matcher); + bool isAttributionUidField(const FieldValue& value); void translateFieldMatcher(const FieldMatcher& matcher, std::vector* output); bool isAttributionUidField(const Field& field, const Value& value); + +bool equalDimensions(const std::vector& dimension_a, + const std::vector& dimension_b); } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/HashableDimensionKey.cpp b/cmds/statsd/src/HashableDimensionKey.cpp index cc7063131f18f..d0c83119dc43e 100644 --- a/cmds/statsd/src/HashableDimensionKey.cpp +++ b/cmds/statsd/src/HashableDimensionKey.cpp @@ -59,6 +59,33 @@ android::hash_t hashDimension(const HashableDimensionKey& value) { return JenkinsHashWhiten(hash); } +bool filterValues(const vector& matcherFields, const vector& 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 bool filterValues(const vector& matcherFields, const vector& values, @@ -168,22 +195,21 @@ void filterGaugeValues(const std::vector& matcherFields, void getDimensionForCondition(const std::vector& eventValues, const Metric2Condition& links, - vector* 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 57bdf68091d0f..4cfed883ec075 100644 --- a/cmds/statsd/src/HashableDimensionKey.h +++ b/cmds/statsd/src/HashableDimensionKey.h @@ -65,10 +65,6 @@ public: 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 @@ class MetricDimensionKey { return mDimensionKeyInCondition; } + inline void setDimensionKeyInCondition(const HashableDimensionKey& key) { + mDimensionKeyInCondition = key; + } + bool hasDimensionKeyInCondition() const { return mDimensionKeyInCondition.getValues().size() > 0; } @@ -112,9 +112,6 @@ class MetricDimensionKey { 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 @@ android::hash_t hashDimension(const HashableDimensionKey& key); */ bool filterValues(const std::vector& matcherFields, const std::vector& values, std::vector* output); +// This function is used when there is at most one output dimension key. (no ANY matcher) +bool filterValues(const std::vector& matcherFields, const std::vector& values, + HashableDimensionKey* output); /** * Filter the values from FieldValues using the matchers. @@ -146,7 +146,7 @@ void filterGaugeValues(const std::vector& matchers, const std::vector& eventValues, const Metric2Condition& links, - std::vector* conditionDimension); + HashableDimensionKey* conditionDimension); } // namespace statsd } // namespace os diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h index a672ab4fb314c..8db82006d082f 100644 --- a/cmds/statsd/src/StatsLogProcessor.h +++ b/cmds/statsd/src/StatsLogProcessor.h @@ -123,10 +123,21 @@ private: 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 e0a1299a9c384..30896e69804bd 100644 --- a/cmds/statsd/src/StatsService.h +++ b/cmds/statsd/src/StatsService.h @@ -174,11 +174,6 @@ private: */ status_t cmd_print_stats(FILE* out, const Vector& args); - /** - * Print the event log. - */ - status_t cmd_print_stats_log(FILE* out, const Vector& args); - /** * Print the event log. */ diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.cpp b/cmds/statsd/src/condition/CombinationConditionTracker.cpp index 13a2b7be9152b..3661d2b9a7687 100644 --- a/cmds/statsd/src/condition/CombinationConditionTracker.cpp +++ b/cmds/statsd/src/condition/CombinationConditionTracker.cpp @@ -91,6 +91,9 @@ bool CombinationConditionTracker::init(const vector& allConditionConf 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 @@ bool CombinationConditionTracker::init(const vector& allConditionConf void CombinationConditionTracker::isConditionMet( const ConditionKey& conditionParameters, const vector>& allConditions, - const std::vector& dimensionFields, vector& conditionCache, + const std::vector& dimensionFields, + const bool isSubOutputDimensionFields, + const bool isPartialLink, + vector& conditionCache, std::unordered_set& 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 @@ void CombinationConditionTracker::evaluateCondition( 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 @@ void CombinationConditionTracker::evaluateCondition( ConditionState CombinationConditionTracker::getMetConditionDimension( const std::vector>& allConditions, const std::vector& dimensionFields, + const bool isSubOutputDimensionFields, std::unordered_set& dimensionsKeySet) const { vector 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 @@ ConditionState CombinationConditionTracker::getMetConditionDimension( return conditionCache[mIndex]; } +bool CombinationConditionTracker::equalOutputDimensions( + const std::vector>& allConditions, + const vector& dimensions) const { + if (mSlicedChildren.size() != 1 || + mSlicedChildren.front() >= (int)allConditions.size() || + mLogicalOperation != LogicalOperation::AND) { + return false; + } + const sp& 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 7b8dc6bfd938b..481cb200d8e6d 100644 --- a/cmds/statsd/src/condition/CombinationConditionTracker.h +++ b/cmds/statsd/src/condition/CombinationConditionTracker.h @@ -44,12 +44,15 @@ public: void isConditionMet(const ConditionKey& conditionParameters, const std::vector>& allConditions, const vector& dimensionFields, + const bool isSubOutputDimensionFields, + const bool isPartialLink, std::vector& conditionCache, std::unordered_set& dimensionsKeySet) const override; ConditionState getMetConditionDimension( const std::vector>& allConditions, const vector& dimensionFields, + const bool isSubOutputDimensionFields, std::unordered_set& dimensionsKeySet) const override; // Only one child predicate can have dimension. @@ -63,6 +66,7 @@ public: } return nullptr; } + // Only one child predicate can have dimension. const std::set* getChangedToFalseDimensions( const std::vector>& allConditions) const override { @@ -75,6 +79,26 @@ public: return nullptr; } + bool IsSimpleCondition() const override { return false; } + + bool IsChangedDimensionTrackable() const override { + return mLogicalOperation == LogicalOperation::AND && mSlicedChildren.size() == 1; + } + + bool equalOutputDimensions( + const std::vector>& allConditions, + const vector& dimensions) const override; + + void getTrueSlicedDimensions( + const std::vector>& allConditions, + std::set* dimensions) const override { + if (mSlicedChildren.size() == 1) { + return allConditions[mSlicedChildren.front()]->getTrueSlicedDimensions( + allConditions, dimensions); + } + } + + private: LogicalOperation mLogicalOperation; @@ -83,6 +107,10 @@ private: // 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 mChildren; + + std::vector mSlicedChildren; + std::vector mUnSlicedChildren; + }; } // namespace statsd diff --git a/cmds/statsd/src/condition/ConditionTracker.h b/cmds/statsd/src/condition/ConditionTracker.h index 856a3a0467eaa..1f4266b61cdf6 100644 --- a/cmds/statsd/src/condition/ConditionTracker.h +++ b/cmds/statsd/src/condition/ConditionTracker.h @@ -84,18 +84,28 @@ public: // 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>& allConditions, - const vector& dimensionFields, std::vector& conditionCache, + const vector& dimensionFields, + const bool isSubOutputDimensionFields, + const bool isPartialLink, + std::vector& conditionCache, std::unordered_set& dimensionsKeySet) const = 0; virtual ConditionState getMetConditionDimension( const std::vector>& allConditions, const vector& dimensionFields, + const bool isSubOutputDimensionFields, std::unordered_set& dimensionsKeySet) const = 0; // return the list of LogMatchingTracker index that this ConditionTracker uses. @@ -107,7 +117,7 @@ public: mSliced = mSliced | sliced; } - bool isSliced() const { + inline bool isSliced() const { return mSliced; } @@ -116,6 +126,26 @@ public: virtual const std::set* getChangedToFalseDimensions( const std::vector>& allConditions) const = 0; + inline int64_t getConditionId() const { + return mConditionId; + } + + virtual void getTrueSlicedDimensions( + const std::vector>& allConditions, + std::set* dimensions) const = 0; + + virtual bool IsChangedDimensionTrackable() const = 0; + + virtual bool IsSimpleCondition() const = 0; + + virtual bool equalOutputDimensions( + const std::vector>& allConditions, + const vector& dimensions) const = 0; + + inline ConditionState getUnSlicedPartConditionState() const { + return mUnSlicedPart; + } + protected: const int64_t mConditionId; @@ -131,6 +161,7 @@ protected: 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 952b0cccf217c..23a9d371145e7 100644 --- a/cmds/statsd/src/condition/ConditionWizard.cpp +++ b/cmds/statsd/src/condition/ConditionWizard.cpp @@ -26,19 +26,24 @@ using std::vector; ConditionState ConditionWizard::query(const int index, const ConditionKey& parameters, const vector& dimensionFields, + const bool isSubOutputDimensionFields, + const bool isPartialLink, std::unordered_set* dimensionKeySet) { vector 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& dimensionFields, + const bool isSubOutputDimensionFields, std::unordered_set* dimensionsKeySet) const { return mAllConditions[index]->getMetConditionDimension(mAllConditions, dimensionFields, - *dimensionsKeySet); + isSubOutputDimensionFields, + *dimensionsKeySet); } const set* ConditionWizard::getChangedToTrueDimensions( @@ -51,6 +56,30 @@ const set* ConditionWizard::getChangedToFalseDimensions( 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& 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 fcfdc2a5815be..a6f88af4b4d68 100644 --- a/cmds/statsd/src/condition/ConditionWizard.h +++ b/cmds/statsd/src/condition/ConditionWizard.h @@ -41,15 +41,30 @@ public: // the conditionParameters contains the parameters for it's children SimpleConditionTrackers. virtual ConditionState query(const int conditionIndex, const ConditionKey& conditionParameters, const vector& dimensionFields, + const bool isSubOutputDimensionFields, + const bool isPartialLink, std::unordered_set* dimensionKeySet); virtual ConditionState getMetConditionDimension( const int index, const vector& dimensionFields, + const bool isSubOutputDimensionFields, std::unordered_set* dimensionsKeySet) const; virtual const std::set* getChangedToTrueDimensions(const int index) const; virtual const std::set* getChangedToFalseDimensions( const int index) const; + bool equalOutputDimensions(const int index, const vector& 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* trueDimensions) const { + return mAllConditions[index]->getTrueSlicedDimensions(mAllConditions, trueDimensions); + } private: std::vector> mAllConditions; diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.cpp b/cmds/statsd/src/condition/SimpleConditionTracker.cpp index 9e27a8b51a996..4913aef3347ff 100644 --- a/cmds/statsd/src/condition/SimpleConditionTracker.cpp +++ b/cmds/statsd/src/condition/SimpleConditionTracker.cpp @@ -34,7 +34,7 @@ SimpleConditionTracker::SimpleConditionTracker( const ConfigKey& key, const int64_t& id, const int index, const SimplePredicate& simplePredicate, const unordered_map& 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 @@ SimpleConditionTracker::SimpleConditionTracker( mSliced = true; mDimensionTag = mOutputDimensions[0].mMatcher.getTag(); } + mContainANYPositionInInternalDimensions = HasPositionANY(simplePredicate.dimensions()); } if (simplePredicate.initial_value() == SimplePredicate_InitialValue_FALSE) { @@ -90,6 +91,10 @@ SimpleConditionTracker::SimpleConditionTracker( mNonSlicedConditionState = mInitialValue; + if (!mSliced) { + mUnSlicedPart = mInitialValue; + } + mInitialized = true; } @@ -109,7 +114,7 @@ bool SimpleConditionTracker::init(const vector& allConditionConfig, 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 @@ void SimpleConditionTracker::handleStopAll(std::vector& conditio mInitialValue = ConditionState::kFalse; mSlicedConditionState.clear(); conditionCache[mIndex] = ConditionState::kFalse; + if (!mSliced) { + mUnSlicedPart = ConditionState::kFalse; + } } bool SimpleConditionTracker::hitGuardRail(const HashableDimensionKey& newKey) { @@ -154,7 +162,7 @@ bool SimpleConditionTracker::hitGuardRail(const HashableDimensionKey& newKey) { // 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 @@ void SimpleConditionTracker::handleConditionEvent(const HashableDimensionKey& ou // 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 @@ void SimpleConditionTracker::handleConditionEvent(const HashableDimensionKey& ou // 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 @@ void SimpleConditionTracker::handleConditionEvent(const HashableDimensionKey& ou (*conditionChangedCache) = changed; (*conditionCache) = newCondition; + VLOG("SimplePredicate %lld nonSlicedChange? %d", (long long)mConditionId, conditionChangedCache[mIndex] == true); } @@ -294,6 +303,7 @@ void SimpleConditionTracker::evaluateCondition( conditionCache[mIndex] = itr->second > 0 ? ConditionState::kTrue : ConditionState::kFalse; } + mUnSlicedPart = conditionCache[mIndex]; } return; @@ -305,6 +315,17 @@ void SimpleConditionTracker::evaluateCondition( 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 outputValues; filterValues(mOutputDimensions, event.getValues(), &outputValues); @@ -328,11 +349,17 @@ void SimpleConditionTracker::evaluateCondition( } conditionCache[mIndex] = overallState; conditionChangedCache[mIndex] = overallChanged; + if (!mSliced) { + mUnSlicedPart = overallState; + } } void SimpleConditionTracker::isConditionMet( const ConditionKey& conditionParameters, const vector>& allConditions, - const vector& dimensionFields, vector& conditionCache, + const vector& dimensionFields, + const bool isSubOutputDimensionFields, + const bool isPartialLink, + vector& conditionCache, std::unordered_set& dimensionsKeySet) const { if (conditionCache[mIndex] != ConditionState::kNotEvaluated) { @@ -347,7 +374,7 @@ void SimpleConditionTracker::isConditionMet( 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 @@ void SimpleConditionTracker::isConditionMet( conditionCache[mIndex] = conditionState; return; } - std::vector defaultKeys = { DEFAULT_DIMENSION_KEY }; - const std::vector &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 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 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 @@ void SimpleConditionTracker::isConditionMet( ConditionState SimpleConditionTracker::getMetConditionDimension( const std::vector>& allConditions, const vector& dimensionFields, + const bool isSubOutputDimensionFields, std::unordered_set& dimensionsKeySet) const { ConditionState conditionState = mInitialValue; if (dimensionFields.size() == 0 || mOutputDimensions.size() == 0 || @@ -424,10 +458,13 @@ ConditionState SimpleConditionTracker::getMetConditionDimension( conditionState = conditionState | sliceState; if (sliceState == ConditionState::kTrue && dimensionFields.size() > 0) { - vector 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 e4b72b8322156..47d1eceb90225 100644 --- a/cmds/statsd/src/condition/SimpleConditionTracker.h +++ b/cmds/statsd/src/condition/SimpleConditionTracker.h @@ -49,12 +49,15 @@ public: void isConditionMet(const ConditionKey& conditionParameters, const std::vector>& allConditions, const vector& dimensionFields, + const bool isSubOutputDimensionFields, + const bool isPartialLink, std::vector& conditionCache, std::unordered_set& dimensionsKeySet) const override; ConditionState getMetConditionDimension( const std::vector>& allConditions, const vector& dimensionFields, + const bool isSubOutputDimensionFields, std::unordered_set& dimensionsKeySet) const override; virtual const std::set* getChangedToTrueDimensions( @@ -65,6 +68,7 @@ public: return nullptr; } } + virtual const std::set* getChangedToFalseDimensions( const std::vector>& allConditions) const { if (mSliced) { @@ -74,6 +78,26 @@ public: } } + void getTrueSlicedDimensions( + const std::vector>& allConditions, + std::set* 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>& allConditions, + const vector& dimensions) const override { + return equalDimensions(mOutputDimensions, dimensions); + } + private: const ConfigKey mConfigKey; // The index of the LogEventMatcher which defines the start. @@ -92,6 +116,8 @@ private: std::vector mOutputDimensions; + bool mContainANYPositionInInternalDimensions; + std::set mLastChangedToTrueDimensions; std::set mLastChangedToFalseDimensions; diff --git a/cmds/statsd/src/condition/StateTracker.cpp b/cmds/statsd/src/condition/StateTracker.cpp index e479f93d9a291..c68875c581622 100644 --- a/cmds/statsd/src/condition/StateTracker.cpp +++ b/cmds/statsd/src/condition/StateTracker.cpp @@ -107,7 +107,7 @@ bool StateTracker::hitGuardRail(const HashableDimensionKey& newKey) { // 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::evaluateCondition(const LogEvent& event, void StateTracker::isConditionMet( const ConditionKey& conditionParameters, const vector>& allConditions, - const vector& dimensionFields, vector& conditionCache, + const vector& dimensionFields, + const bool isSubOutputDimensionFields, + const bool isPartialLink, + vector& conditionCache, std::unordered_set& dimensionsKeySet) const { if (conditionCache[mIndex] != ConditionState::kNotEvaluated) { // it has been evaluated. @@ -203,20 +206,19 @@ void StateTracker::isConditionMet( 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>& allConditions, const vector& dimensionFields, + const bool isSubOutputDimensionFields, std::unordered_set& 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 3fe6e60225d24..2bdf98c34c329 100644 --- a/cmds/statsd/src/condition/StateTracker.h +++ b/cmds/statsd/src/condition/StateTracker.h @@ -56,6 +56,8 @@ public: void isConditionMet(const ConditionKey& conditionParameters, const std::vector>& allConditions, const vector& dimensionFields, + const bool isSubOutputDimensionFields, + const bool isPartialLink, std::vector& conditionCache, std::unordered_set& dimensionsKeySet) const override; @@ -67,6 +69,7 @@ public: ConditionState getMetConditionDimension( const std::vector>& allConditions, const vector& dimensionFields, + const bool isSubOutputDimensionFields, std::unordered_set& dimensionsKeySet) const override; virtual const std::set* getChangedToTrueDimensions( @@ -79,6 +82,24 @@ public: return &mLastChangedToFalseDimensions; } + bool IsChangedDimensionTrackable() const override { return true; } + + bool IsSimpleCondition() const override { return true; } + + bool equalOutputDimensions( + const std::vector>& allConditions, + const vector& dimensions) const override { + return equalDimensions(mOutputDimensions, dimensions); + } + + void getTrueSlicedDimensions( + const std::vector>& allConditions, + std::set* 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 da5ef44497ef8..22b2a30af328a 100644 --- a/cmds/statsd/src/metrics/CountMetricProducer.cpp +++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp @@ -71,6 +71,7 @@ CountMetricProducer::CountMetricProducer(const ConfigKey& key, const CountMetric 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 @@ void CountMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, 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 @@ bool CountMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) { // 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 @@ void CountMetricProducer::onMatchedLogEventInternalLocked( 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 @@ void CountMetricProducer::flushCurrentBucketLocked(const uint64_t& eventTimeNs) 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 29a892dff9353..0dd3f706d899a 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp +++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp @@ -67,7 +67,8 @@ DurationMetricProducer::DurationMetricProducer(const ConfigKey& key, const Durat 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 @@ DurationMetricProducer::DurationMetricProducer(const ConfigKey& key, const Durat 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 @@ DurationMetricProducer::DurationMetricProducer(const ConfigKey& key, const Durat } } 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 @@ unique_ptr DurationMetricProducer::createDurationTracker( return make_unique( 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( 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 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* trueDimensionsToProcess = nullptr; + const std::set* falseDimensionsToProcess = nullptr; + + std::set 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 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 @@ void DurationMetricProducer::onSlicedConditionMayChangeLocked(const uint64_t eve 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 @@ void DurationMetricProducer::onSlicedConditionMayChangeLocked(const uint64_t eve if (mMetric2ConditionLinks.empty()) { std::unordered_set conditionDimensionsKeySet; mWizard->getMetConditionDimension(mConditionTrackerIndex, mDimensionsInCondition, + !mSameConditionDimensionsInTracker, &conditionDimensionsKeySet); for (const auto& whatIt : mCurrentSlicedDurationTrackerMap) { for (const auto& pair : whatIt.second) { @@ -177,9 +356,12 @@ void DurationMetricProducer::onSlicedConditionMayChangeLocked(const uint64_t eve if (!whatIt.second.empty()) { unique_ptr 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 @@ void DurationMetricProducer::onSlicedConditionMayChangeLocked(const uint64_t eve } std::unordered_set 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 @@ void DurationMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, 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 @@ void DurationMetricProducer::flushIfNeededLocked(const uint64_t& eventTimeNs) { 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 @@ void DurationMetricProducer::flushCurrentBucketLocked(const uint64_t& eventTimeN 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 @@ void DurationMetricProducer::dumpStatesLocked(FILE* out, bool verbose) const { 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 @@ bool DurationMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey // 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 @@ void DurationMetricProducer::handleStartEvent(const MetricDimensionKey& eventKey return; } - std::vector 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 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 @@ void DurationMetricProducer::onMatchedLogEventInternalLocked( 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 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 @@ void DurationMetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, return; } - std::vector 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 @@ void DurationMetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, auto conditionState = mWizard->query(mConditionTrackerIndex, conditionKey, mDimensionsInCondition, + !mSameConditionDimensionsInTracker, + !mHasLinksToAllConditionDimensionsInTracker, &dimensionKeysInCondition); condition = (conditionState == ConditionState::kTrue); if (mDimensionsInCondition.empty() && condition) { @@ -490,32 +792,30 @@ void DurationMetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, } 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 23408a76998e7..6746e116333f0 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.h +++ b/cmds/statsd/src/metrics/DurationMetricProducer.h @@ -51,6 +51,9 @@ public: 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 @@ private: // 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 @@ private: // The dimension from the atom predicate. e.g., uid, wakelock name. vector 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> mPastBuckets; diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp index 288f563cc9481..e479e5caec2f4 100644 --- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp +++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp @@ -83,6 +83,7 @@ GaugeMetricProducer::GaugeMetricProducer(const ConfigKey& key, const GaugeMetric // 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 @@ void GaugeMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, 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 @@ bool GaugeMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) { // 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 @@ void GaugeMetricProducer::flushCurrentBucketLocked(const uint64_t& eventTimeNs) 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 18694a1cadcd7..6c90b031a9cce 100644 --- a/cmds/statsd/src/metrics/MetricProducer.cpp +++ b/cmds/statsd/src/metrics/MetricProducer.cpp @@ -39,9 +39,10 @@ void MetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, const Lo 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 @@ void MetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, const Lo dimensionKeysInCondition.insert(DEFAULT_DIMENSION_KEY); } - vector dimensionInWhatValues; - if (!mDimensionsInWhat.empty()) { - filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhatValues); - } else { - dimensionInWhatValues.push_back(DEFAULT_DIMENSION_KEY); - } + if (mContainANYPositionInDimensionsInWhat) { + vector 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 05b7f87669836..ea45f43a2d29c 100644 --- a/cmds/statsd/src/metrics/MetricProducer.h +++ b/cmds/statsd/src/metrics/MetricProducer.h @@ -50,7 +50,11 @@ public: 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 @@ protected: vector mDimensionsInWhat; // The dimensions_in_what defined in statsd_config vector 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 mMetric2ConditionLinks; std::vector> mAnomalyTrackers; diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h index a32e037b5acd0..dbab81431743b 100644 --- a/cmds/statsd/src/metrics/MetricsManager.h +++ b/cmds/statsd/src/metrics/MetricsManager.h @@ -160,10 +160,18 @@ private: 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 e88daf793dc31..09913dc513fd1 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp +++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp @@ -80,6 +80,7 @@ ValueMetricProducer::ValueMetricProducer(const ConfigKey& key, const ValueMetric 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 @@ void ValueMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, 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 @@ bool ValueMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) { // 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 8f236fa8bab62..7b3393fda7e6e 100644 --- a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h @@ -64,7 +64,7 @@ public: sp wizard, int conditionIndex, const std::vector& 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>& anomalyTrackers) : mConfigKey(key), mTrackerId(id), @@ -80,6 +80,7 @@ public: mCurrentBucketNum(currentBucketNum), mStartTimeNs(startTimeNs), mConditionSliced(conditionSliced), + mHasLinksToAllConditionDimensionsInTracker(fullLink), mAnomalyTrackers(anomalyTrackers){}; virtual ~DurationTracker(){}; @@ -198,6 +199,9 @@ protected: const bool mConditionSliced; + bool mSameConditionDimensionsInTracker; + bool mHasLinksToAllConditionDimensionsInTracker; + std::vector> 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 b225560e76a8c..8e0bf2678fea2 100644 --- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp +++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp @@ -30,20 +30,33 @@ MaxDurationTracker::MaxDurationTracker(const ConfigKey& key, const int64_t& id, const vector& dimensionInCondition, bool nesting, uint64_t currentBucketStartNs, uint64_t currentBucketNum, uint64_t startTimeNs, uint64_t bucketSizeNs, - bool conditionSliced, + bool conditionSliced, bool fullLink, const vector>& 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 MaxDurationTracker::clone(const uint64_t eventTime) { auto clonedTracker = make_unique(*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); + } + } + if (clonedTracker->mInfos.empty()) { + return nullptr; + } else { + return clonedTracker; } - return clonedTracker; } bool MaxDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) { @@ -59,7 +72,7 @@ bool MaxDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) { // 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 @@ void MaxDurationTracker::noteStart(const HashableDimensionKey& key, bool conditi 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::noteStart(const HashableDimensionKey& key, bool conditi 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 @@ void MaxDurationTracker::noteStop(const HashableDimensionKey& key, const uint64_ 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 @@ void MaxDurationTracker::onSlicedConditionMayChange(const uint64_t timestamp) { std::unordered_set 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 @@ void MaxDurationTracker::noteConditionChanged(const HashableDimensionKey& key, b // 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 @@ void MaxDurationTracker::noteConditionChanged(const HashableDimensionKey& key, b 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 c731b75018811..0452d373d413c 100644 --- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h @@ -33,6 +33,7 @@ public: const std::vector& dimensionInCondition, bool nesting, uint64_t currentBucketStartNs, uint64_t currentBucketNum, uint64_t startTimeNs, uint64_t bucketSizeNs, bool conditionSliced, + bool fullLink, const std::vector>& 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 f583f91608b8c..2358415dfaf71 100644 --- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp +++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp @@ -28,14 +28,18 @@ OringDurationTracker::OringDurationTracker( const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey, sp wizard, int conditionIndex, const vector& 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>& 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 OringDurationTracker::clone(const uint64_t eventTime) { @@ -57,7 +61,7 @@ bool OringDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) { // 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 @@ void OringDurationTracker::noteStart(const HashableDimensionKey& key, bool condi 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 @@ void OringDurationTracker::onSlicedConditionMayChange(const uint64_t timestamp) 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 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 @@ void OringDurationTracker::onSlicedConditionMayChange(const uint64_t timestamp) 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 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 07c13294fa449..610e3ea79aa12 100644 --- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h @@ -32,6 +32,7 @@ public: int conditionIndex, const std::vector& dimensionInCondition, bool nesting, uint64_t currentBucketStartNs, uint64_t currentBucketNum, uint64_t startTimeNs, uint64_t bucketSizeNs, bool conditionSliced, + bool fullLink, const std::vector>& anomalyTrackers); OringDurationTracker(const OringDurationTracker& tracker) = default; diff --git a/cmds/statsd/src/stats_util.h b/cmds/statsd/src/stats_util.h index bbaf50a83fa92..80e46d604b218 100644 --- a/cmds/statsd/src/stats_util.h +++ b/cmds/statsd/src/stats_util.h @@ -32,7 +32,7 @@ const MetricDimensionKey DEFAULT_METRIC_DIMENSION_KEY = MetricDimensionKey(); // Minimum bucket size in seconds const long kMinBucketSizeSec = 5 * 60; -typedef std::map> ConditionKey; +typedef std::map ConditionKey; typedef std::unordered_map DimToValMap; diff --git a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp index 3dc3fd1160f19..e826a52c2f336 100644 --- a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp +++ b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp @@ -75,10 +75,10 @@ void makeWakeLockEvent( event->init(); } -std::map> getWakeLockQueryKey( +std::map getWakeLockQueryKey( const Position position, const std::vector &uids, const string& conditionName) { - std::map> outputKeyMap; + std::map outputKeyMap; std::vector uid_indexes; int pos[] = {1, 1, 1}; int depth = 2; @@ -104,7 +104,7 @@ std::map> getWakeLockQueryKey( 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 @@ TEST(SimpleConditionTrackerTest, TestNonSlicedCondition) { 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, TestNonSlicedCondition) { } TEST(SimpleConditionTrackerTest, TestNonSlicedConditionNestCounting) { + std::vector> allConditions; SimplePredicate simplePredicate; simplePredicate.set_start(StringToId("SCREEN_TURNED_ON")); simplePredicate.set_stop(StringToId("SCREEN_TURNED_OFF")); @@ -205,6 +207,7 @@ TEST(SimpleConditionTrackerTest, TestNonSlicedConditionNestCounting) { 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 @@ TEST(SimpleConditionTrackerTest, TestNonSlicedConditionNestCounting) { 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> allConditions; for (Position position : - { Position::ANY, Position::FIRST, Position::LAST}) { + { Position::FIRST, Position::LAST}) { vector dimensionInCondition; std::unordered_set dimensionKeys; @@ -281,6 +284,7 @@ TEST(SimpleConditionTrackerTest, TestSlicedCondition) { SimpleConditionTracker conditionTracker(kConfigKey, StringToId(conditionName), 0 /*condition tracker index*/, simplePredicate, trackerNameIndexMap); + std::vector uids = {111, 222, 333}; LogEvent event(1 /*tagId*/, 0 /*timestamp*/); @@ -305,12 +309,20 @@ TEST(SimpleConditionTrackerTest, TestSlicedCondition) { 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 @@ TEST(SimpleConditionTrackerTest, TestSlicedCondition) { } 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 @@ TEST(SimpleConditionTrackerTest, TestSlicedCondition) { } 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 @@ TEST(SimpleConditionTrackerTest, TestSlicedCondition) { 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> allConditions; vector dimensionInCondition; std::unordered_set dimensionKeys; @@ -391,6 +416,8 @@ TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim) { 0 /*condition tracker index*/, simplePredicate, trackerNameIndexMap); + EXPECT_FALSE(conditionTracker.isSliced()); + std::vector uid_list1 = {111, 1111, 11111}; string uid1_wl1 = "wl1_1"; std::vector uid_list2 = {222, 2222, 22222}; @@ -419,6 +446,7 @@ TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim) { conditionCache[0] = ConditionState::kNotEvaluated; conditionTracker.isConditionMet(queryKey, allPredicates, dimensionInCondition, + true, true, conditionCache, dimensionKeys); EXPECT_EQ(ConditionState::kTrue, conditionCache[0]); @@ -463,13 +491,15 @@ TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim) { 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> allConditions; for (Position position : - {Position::ANY, Position::FIRST, Position::LAST}) { + { Position::FIRST, Position::LAST }) { vector dimensionInCondition; std::unordered_set dimensionKeys; SimplePredicate simplePredicate = getWakeLockHeldCondition( @@ -510,12 +540,23 @@ TEST(SimpleConditionTrackerTest, TestStopAll) { 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 @@ TEST(SimpleConditionTrackerTest, TestStopAll) { 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 @@ TEST(SimpleConditionTrackerTest, TestStopAll) { 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 @@ TEST(SimpleConditionTrackerTest, TestStopAll) { 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 0000000000000..a08f6067c96c2 --- /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 + +#include "src/StatsLogProcessor.h" +#include "src/stats_log_util.h" +#include "tests/statsd_test_util.h" + +#include + +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 attributions1 = { + CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"), + CreateAttribution(222, "GMSCoreModule2")}; + + std::vector attributions2 = { + CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"), + CreateAttribution(555, "GMSCoreModule2")}; + + std::vector> 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 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 attributions1 = { + CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"), + CreateAttribution(222, "GMSCoreModule2")}; + + std::vector attributions2 = { + CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"), + CreateAttribution(555, "GMSCoreModule2")}; + + std::vector attributions3 = { + CreateAttribution(444, "App3"), CreateAttribution(222, "GMSCoreModule1"), + CreateAttribution(555, "GMSCoreModule2")}; + + std::vector> 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 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 attributions1 = { + CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"), + CreateAttribution(222, "GMSCoreModule2")}; + + std::vector attributions2 = { + CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"), + CreateAttribution(555, "GMSCoreModule2")}; + + std::vector attributions3 = { + CreateAttribution(444, "App3"), CreateAttribution(222, "GMSCoreModule1"), + CreateAttribution(555, "GMSCoreModule2")}; + + std::vector> 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 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 86% rename from cmds/statsd/tests/e2e/DimensionInCondition_e2e_test.cpp rename to cmds/statsd/tests/e2e/DimensionInCondition_e2e_combination_OR_cond_test.cpp index 01348bd664aa8..435e1991b9e36 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 statsd { namespace { -StatsdConfig CreateCountMetricWithNoLinkConfig() { +StatsdConfig CreateCountMetric_NoLink_CombinationCondition_Config() { StatsdConfig config; auto screenBrightnessChangeAtomMatcher = CreateScreenBrightnessChangedAtomMatcher(); *config.add_atom_matcher() = screenBrightnessChangeAtomMatcher; @@ -67,9 +67,9 @@ StatsdConfig CreateCountMetricWithNoLinkConfig() { } // 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 @@ TEST(DimensionInConditionE2eTest, TestCountMetricNoLink) { namespace { -StatsdConfig CreateCountMetricWithLinkConfig() { +StatsdConfig CreateCountMetric_Link_CombinationCondition() { StatsdConfig config; auto appCrashMatcher = CreateProcessCrashAtomMatcher(); *config.add_atom_matcher() = appCrashMatcher; @@ -274,9 +274,9 @@ StatsdConfig CreateCountMetricWithLinkConfig() { } // 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 @@ TEST(DimensionInConditionE2eTest, TestCountMetricWithLink) { 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 @@ StatsdConfig CreateDurationMetricConfigNoLink(DurationMetric::AggregationType ag 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 @@ StatsdConfig CreateDurationMetricConfigNoLink(DurationMetric::AggregationType ag } // 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 @@ TEST(DimensionInConditionE2eTest, TestDurationMetricNoLink) { 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 @@ StatsdConfig CreateDurationMetricConfigWithLink(DurationMetric::AggregationType 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 @@ StatsdConfig CreateDurationMetricConfigWithLink(DurationMetric::AggregationType } // 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 @@ TEST(DimensionInConditionE2eTest, TestDurationMetricWithLink) { 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 0000000000000..75ceafb489b3e --- /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 + +#include "src/StatsLogProcessor.h" +#include "src/stats_log_util.h" +#include "tests/statsd_test_util.h" + +#include + +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 attributions1 = { + CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"), + CreateAttribution(222, "GMSCoreModule2")}; + + std::vector attributions2 = { + CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"), + CreateAttribution(555, "GMSCoreModule2")}; + + std::vector> 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 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 attributions1 = { + CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"), + CreateAttribution(222, "GMSCoreModule2")}; + + std::vector attributions2 = { + CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"), + CreateAttribution(555, "GMSCoreModule2")}; + + std::vector attributions3 = { + CreateAttribution(444, "App3"), CreateAttribution(222, "GMSCoreModule1"), + CreateAttribution(555, "GMSCoreModule2")}; + + std::vector> 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 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 attributions1 = { + CreateAttribution(111, "App1"), CreateAttribution(222, "GMSCoreModule1"), + CreateAttribution(222, "GMSCoreModule2")}; + + std::vector attributions2 = { + CreateAttribution(333, "App2"), CreateAttribution(222, "GMSCoreModule1"), + CreateAttribution(555, "GMSCoreModule2")}; + + std::vector attributions3 = { + CreateAttribution(444, "App3"), CreateAttribution(222, "GMSCoreModule1"), + CreateAttribution(555, "GMSCoreModule2")}; + + std::vector> 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 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 9a0de0d818025..a07683e5b0455 100644 --- a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp @@ -175,9 +175,9 @@ TEST(CountMetricProducerTest, TestEventsWithSlicedCondition) { {getMockedDimensionKey(conditionTagId, 2, "222")}; sp wizard = new NaggyMock(); - 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 8246268d88e78..23d31717d99dc 100644 --- a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp @@ -113,9 +113,9 @@ TEST(EventMetricProducerTest, TestEventsWithSlicedCondition) { key2[StringToId("APP_IN_BACKGROUND_PER_UID")] = {getMockedDimensionKey(conditionTagId, 2, "222")}; sp wizard = new NaggyMock(); - 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 83b1cbfb6fb3d..57a8925a122e3 100644 --- a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp +++ b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp @@ -42,14 +42,13 @@ const ConfigKey kConfigKey(0, 12345); const int TagId = 1; const HashableDimensionKey eventKey = getMockedDimensionKey(TagId, 0, "1"); -const std::vector 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 conditionKey = {getMockedDimensionKey(TagId, 4, "1")}; const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1"); const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2"); @@ -66,7 +65,7 @@ TEST(MaxDurationTrackerTest, TestSimpleMaxDuration) { 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, TestSimpleMaxDuration) { TEST(MaxDurationTrackerTest, TestStopAll) { const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1"); - const std::vector conditionKey = {getMockedDimensionKey(TagId, 4, "1")}; const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1"); const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2"); @@ -103,7 +101,7 @@ TEST(MaxDurationTrackerTest, TestStopAll) { 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, TestStopAll) { TEST(MaxDurationTrackerTest, TestCrossBucketBoundary) { const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1"); - const std::vector conditionKey = {getMockedDimensionKey(TagId, 4, "1")}; const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1"); const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2"); vector dimensionInCondition; @@ -140,7 +137,7 @@ TEST(MaxDurationTrackerTest, TestCrossBucketBoundary) { 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) { TEST(MaxDurationTrackerTest, TestCrossBucketBoundary_nested) { const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "1"); - const std::vector conditionKey = {getMockedDimensionKey(TagId, 4, "1")}; const HashableDimensionKey key1 = getMockedDimensionKey(TagId, 1, "1"); const HashableDimensionKey key2 = getMockedDimensionKey(TagId, 1, "2"); vector dimensionInCondition; @@ -182,7 +178,7 @@ TEST(MaxDurationTrackerTest, TestCrossBucketBoundary_nested) { 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, TestCrossBucketBoundary_nested) { } TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) { - const std::vector conditionKey = {key1}; + const HashableDimensionKey conditionDimKey = key1; vector dimensionInCondition; sp wizard = new NaggyMock(); 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 @@ TEST(MaxDurationTrackerTest, TestMaxDurationWithCondition) { 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, TestMaxDurationWithCondition) { } TEST(MaxDurationTrackerTest, TestAnomalyDetection) { - const std::vector conditionKey = {getMockedDimensionKey(TagId, 4, "1")}; - vector dimensionInCondition; sp wizard = new NaggyMock(); @@ -281,7 +275,7 @@ TEST(MaxDurationTrackerTest, TestAnomalyDetection) { 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 alarm = anomalyTracker->mAlarms.begin()->second; @@ -301,8 +295,6 @@ TEST(MaxDurationTrackerTest, TestAnomalyDetection) { // 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 conditionKey = {getMockedDimensionKey(TagId, 4, "1")}; - vector dimensionInCondition; sp wizard = new NaggyMock(); @@ -310,7 +302,7 @@ TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp) { 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> buckets; @@ -343,7 +335,7 @@ TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp) { 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 @@ TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp) { // 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 conditionKey = {getMockedDimensionKey(TagId, 4, "1")}; - vector dimensionInCondition; sp wizard = new NaggyMock(); @@ -369,7 +359,7 @@ TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp_UpdatedOnStop) { 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> buckets; @@ -399,7 +389,7 @@ TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp_UpdatedOnStop) { 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 @@ TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp_UpdatedOnStop) { } // 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 aa41038b9acb6..54abcb28a4492 100644 --- a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp +++ b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp @@ -40,7 +40,7 @@ const int TagId = 1; const int64_t metricId = 123; const HashableDimensionKey eventKey = getMockedDimensionKey(TagId, 0, "event"); -const std::vector 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 @@ const uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL; TEST(OringDurationTrackerTest, TestDurationOverlap) { const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); - const std::vector kConditionKey1 = - {getMockedDimensionKey(TagId, 1, "maps")}; const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); vector dimensionInCondition; @@ -65,7 +63,7 @@ TEST(OringDurationTrackerTest, TestDurationOverlap) { 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, TestDurationOverlap) { TEST(OringDurationTrackerTest, TestDurationNested) { const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); - const std::vector kConditionKey1 = - {getMockedDimensionKey(TagId, 1, "maps")}; const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); vector dimensionInCondition; @@ -99,7 +95,7 @@ TEST(OringDurationTrackerTest, TestDurationNested) { 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 @@ TEST(OringDurationTrackerTest, TestStopAll) { 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, TestStopAll) { TEST(OringDurationTrackerTest, TestCrossBucketBoundary) { const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); - const std::vector kConditionKey1 = - {getMockedDimensionKey(TagId, 1, "maps")}; const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); vector dimensionInCondition; @@ -165,7 +159,7 @@ TEST(OringDurationTrackerTest, TestCrossBucketBoundary) { 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, TestCrossBucketBoundary) { TEST(OringDurationTrackerTest, TestDurationConditionChange) { const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); - const std::vector kConditionKey1 = - {getMockedDimensionKey(TagId, 1, "maps")}; const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); vector dimensionInCondition; @@ -199,7 +191,7 @@ TEST(OringDurationTrackerTest, TestDurationConditionChange) { 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> buckets; @@ -212,7 +204,7 @@ TEST(OringDurationTrackerTest, TestDurationConditionChange) { 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, TestDurationConditionChange) { TEST(OringDurationTrackerTest, TestDurationConditionChange2) { const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); - const std::vector kConditionKey1 = - {getMockedDimensionKey(TagId, 1, "maps")}; const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); vector dimensionInCondition; @@ -239,7 +229,7 @@ TEST(OringDurationTrackerTest, TestDurationConditionChange2) { 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 @@ TEST(OringDurationTrackerTest, TestDurationConditionChange2) { 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, TestDurationConditionChange2) { TEST(OringDurationTrackerTest, TestDurationConditionChangeNested) { const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); - const std::vector kConditionKey1 = - {getMockedDimensionKey(TagId, 1, "maps")}; const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); vector dimensionInCondition; @@ -283,7 +271,7 @@ TEST(OringDurationTrackerTest, TestDurationConditionChangeNested) { 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> buckets; @@ -295,7 +283,7 @@ TEST(OringDurationTrackerTest, TestDurationConditionChangeNested) { 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, TestDurationConditionChangeNested) { TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) { const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); - const std::vector kConditionKey1 = - {getMockedDimensionKey(TagId, 1, "maps")}; const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); vector dimensionInCondition; @@ -339,7 +325,7 @@ TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp) { 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, TestPredictAnomalyTimestamp) { TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm) { const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); - const std::vector kConditionKey1 = {getMockedDimensionKey(TagId, 1, "maps")}; const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); vector dimensionInCondition; @@ -410,7 +395,7 @@ TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm) { 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, TestAnomalyDetectionExpiredAlarm) { TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm) { const MetricDimensionKey eventKey = getMockedMetricDimensionKey(TagId, 0, "event"); - const std::vector kConditionKey1 = - {getMockedDimensionKey(TagId, 1, "maps")}; const HashableDimensionKey kEventKey1 = getMockedDimensionKey(TagId, 2, "maps"); const HashableDimensionKey kEventKey2 = getMockedDimensionKey(TagId, 3, "maps"); vector dimensionInCondition; @@ -462,7 +445,7 @@ TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm) { 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 @@ TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm) { } // 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 a01de6334ab4f..f040bf9f37aea 100644 --- a/cmds/statsd/tests/metrics/metrics_test_helper.h +++ b/cmds/statsd/tests/metrics/metrics_test_helper.h @@ -26,9 +26,10 @@ namespace statsd { class MockConditionWizard : public ConditionWizard { public: - MOCK_METHOD4(query, + MOCK_METHOD6(query, ConditionState(const int conditionIndex, const ConditionKey& conditionParameters, const vector& dimensionFields, + const bool isSubsetDim, const bool isPartialLink, std::unordered_set* dimensionKeySet)); }; diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp index 242b6ebe5ec1b..2678c8a93463c 100644 --- a/cmds/statsd/tests/statsd_test_util.cpp +++ b/cmds/statsd/tests/statsd_test_util.cpp @@ -26,6 +26,28 @@ AtomMatcher CreateSimpleAtomMatcher(const string& name, int 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")); @@ -168,6 +190,14 @@ AtomMatcher CreateProcessCrashAtomMatcher() { "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 CreateScreenBrightnessChangedEvent( } +std::unique_ptr CreateScheduledJobStateChangedEvent( + const std::vector& attributions, const string& jobName, + const ScheduledJobStateChanged::State state, uint64_t timestampNs) { + auto event = std::make_unique(android::util::SCHEDULED_JOB_STATE_CHANGED, timestampNs); + event->write(attributions); + event->write(jobName); + event->write(state); + event->init(); + return event; +} + +std::unique_ptr CreateStartScheduledJobEvent( + const std::vector& attributions, + const string& name, uint64_t timestampNs) { + return CreateScheduledJobStateChangedEvent( + attributions, name, ScheduledJobStateChanged::STARTED, timestampNs); +} + +// Create log event when scheduled job finishes. +std::unique_ptr CreateFinishScheduledJobEvent( + const std::vector& attributions, + const string& name, uint64_t timestampNs) { + return CreateScheduledJobStateChangedEvent( + attributions, name, ScheduledJobStateChanged::FINISHED, timestampNs); +} + std::unique_ptr CreateWakelockStateChangedEvent( const std::vector& attributions, const string& wakelockName, const WakelockStateChanged::State state, uint64_t timestampNs) { @@ -419,7 +475,6 @@ int64_t StringToId(const string& str) { 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 1708cc31bf14f..14eba1f67d9d1 100644 --- a/cmds/statsd/tests/statsd_test_util.h +++ b/cmds/statsd/tests/statsd_test_util.h @@ -28,6 +28,15 @@ 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(); @@ -73,6 +82,9 @@ Predicate CreateScreenIsOnPredicate(); // 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 CreateScreenStateChangedEvent( std::unique_ptr CreateScreenBrightnessChangedEvent( int level, uint64_t timestampNs); +// Create log event when scheduled job starts. +std::unique_ptr CreateStartScheduledJobEvent( + const std::vector& attributions, + const string& name, uint64_t timestampNs); + +// Create log event when scheduled job finishes. +std::unique_ptr CreateFinishScheduledJobEvent( + const std::vector& attributions, + const string& name, uint64_t timestampNs); + // Create log event when battery saver starts. std::unique_ptr CreateBatterySaverOnEvent(uint64_t timestampNs); // Create log event when battery saver stops.