Migrate frameworks/base/cmds/statsd to packages/modules/StatsD/bin
Add statsd protos to platform_protos rule BUG: 167962588 TEST: TH TEST: Local build [ m com.android.os.statsd ] Change-Id: Ic6703ea7636a00152c8a6483e1d91729f758e24d Merged-In: I053f2a211ea28c2f181937af3d58ad16b235d096
This commit is contained in:
@@ -753,6 +753,7 @@ java_library_host {
|
||||
":ipconnectivity-proto-src",
|
||||
":libstats_atom_enum_protos",
|
||||
":libstats_internal_protos",
|
||||
":statsd_internal_protos",
|
||||
"cmds/am/proto/instrumentation_data.proto",
|
||||
"cmds/statsd/src/**/*.proto",
|
||||
"core/proto/**/*.proto",
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
BasedOnStyle: Google
|
||||
AllowShortIfStatementsOnASingleLine: true
|
||||
AllowShortFunctionsOnASingleLine: false
|
||||
AllowShortLoopsOnASingleLine: true
|
||||
BinPackArguments: true
|
||||
BinPackParameters: true
|
||||
ColumnLimit: 100
|
||||
CommentPragmas: NOLINT:.*
|
||||
ContinuationIndentWidth: 8
|
||||
DerivePointerAlignment: false
|
||||
IndentWidth: 4
|
||||
PointerAlignment: Left
|
||||
TabWidth: 4
|
||||
AccessModifierOffset: -4
|
||||
IncludeCategories:
|
||||
- Regex: '^"Log\.h"'
|
||||
Priority: -1
|
||||
@@ -1,408 +0,0 @@
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
cc_defaults {
|
||||
name: "statsd_defaults",
|
||||
|
||||
srcs: [
|
||||
"src/active_config_list.proto",
|
||||
"src/anomaly/AlarmMonitor.cpp",
|
||||
"src/anomaly/AlarmTracker.cpp",
|
||||
"src/anomaly/AnomalyTracker.cpp",
|
||||
"src/anomaly/DurationAnomalyTracker.cpp",
|
||||
"src/anomaly/subscriber_util.cpp",
|
||||
"src/condition/CombinationConditionTracker.cpp",
|
||||
"src/condition/condition_util.cpp",
|
||||
"src/condition/ConditionWizard.cpp",
|
||||
"src/condition/SimpleConditionTracker.cpp",
|
||||
"src/config/ConfigKey.cpp",
|
||||
"src/config/ConfigListener.cpp",
|
||||
"src/config/ConfigManager.cpp",
|
||||
"src/experiment_ids.proto",
|
||||
"src/external/Perfetto.cpp",
|
||||
"src/external/PullResultReceiver.cpp",
|
||||
"src/external/puller_util.cpp",
|
||||
"src/external/StatsCallbackPuller.cpp",
|
||||
"src/external/StatsPuller.cpp",
|
||||
"src/external/StatsPullerManager.cpp",
|
||||
"src/external/TrainInfoPuller.cpp",
|
||||
"src/FieldValue.cpp",
|
||||
"src/guardrail/StatsdStats.cpp",
|
||||
"src/hash.cpp",
|
||||
"src/HashableDimensionKey.cpp",
|
||||
"src/logd/LogEvent.cpp",
|
||||
"src/logd/LogEventQueue.cpp",
|
||||
"src/matchers/CombinationLogMatchingTracker.cpp",
|
||||
"src/matchers/EventMatcherWizard.cpp",
|
||||
"src/matchers/matcher_util.cpp",
|
||||
"src/matchers/SimpleLogMatchingTracker.cpp",
|
||||
"src/metadata_util.cpp",
|
||||
"src/metrics/CountMetricProducer.cpp",
|
||||
"src/metrics/duration_helper/MaxDurationTracker.cpp",
|
||||
"src/metrics/duration_helper/OringDurationTracker.cpp",
|
||||
"src/metrics/DurationMetricProducer.cpp",
|
||||
"src/metrics/EventMetricProducer.cpp",
|
||||
"src/metrics/GaugeMetricProducer.cpp",
|
||||
"src/metrics/MetricProducer.cpp",
|
||||
"src/metrics/metrics_manager_util.cpp",
|
||||
"src/metrics/MetricsManager.cpp",
|
||||
"src/metrics/ValueMetricProducer.cpp",
|
||||
"src/packages/UidMap.cpp",
|
||||
"src/shell/shell_config.proto",
|
||||
"src/shell/ShellSubscriber.cpp",
|
||||
"src/socket/StatsSocketListener.cpp",
|
||||
"src/state/StateManager.cpp",
|
||||
"src/state/StateTracker.cpp",
|
||||
"src/stats_log_util.cpp",
|
||||
"src/statscompanion_util.cpp",
|
||||
"src/statsd_config.proto",
|
||||
"src/statsd_metadata.proto",
|
||||
"src/StatsLogProcessor.cpp",
|
||||
"src/StatsService.cpp",
|
||||
"src/storage/StorageManager.cpp",
|
||||
"src/subscriber/IncidentdReporter.cpp",
|
||||
"src/subscriber/SubscriberReporter.cpp",
|
||||
"src/uid_data.proto",
|
||||
"src/utils/MultiConditionTrigger.cpp",
|
||||
],
|
||||
|
||||
local_include_dirs: [
|
||||
"src",
|
||||
],
|
||||
|
||||
static_libs: [
|
||||
"libbase",
|
||||
"libcutils",
|
||||
"libgtest_prod",
|
||||
"libprotoutil",
|
||||
"libstatslog_statsd",
|
||||
"libsysutils",
|
||||
"libutils",
|
||||
"statsd-aidl-ndk_platform",
|
||||
],
|
||||
shared_libs: [
|
||||
"libbinder_ndk",
|
||||
"libincident",
|
||||
"liblog",
|
||||
],
|
||||
}
|
||||
|
||||
genrule {
|
||||
name: "statslog_statsd.h",
|
||||
tools: ["stats-log-api-gen"],
|
||||
cmd: "$(location stats-log-api-gen) --header $(genDir)/statslog_statsd.h --module statsd --namespace android,os,statsd,util",
|
||||
out: [
|
||||
"statslog_statsd.h",
|
||||
],
|
||||
}
|
||||
|
||||
genrule {
|
||||
name: "statslog_statsd.cpp",
|
||||
tools: ["stats-log-api-gen"],
|
||||
cmd: "$(location stats-log-api-gen) --cpp $(genDir)/statslog_statsd.cpp --module statsd --namespace android,os,statsd,util --importHeader statslog_statsd.h",
|
||||
out: [
|
||||
"statslog_statsd.cpp",
|
||||
],
|
||||
}
|
||||
|
||||
genrule {
|
||||
name: "statslog_statsdtest.h",
|
||||
tools: ["stats-log-api-gen"],
|
||||
cmd: "$(location stats-log-api-gen) --header $(genDir)/statslog_statsdtest.h --module statsdtest --namespace android,os,statsd,util",
|
||||
out: [
|
||||
"statslog_statsdtest.h",
|
||||
],
|
||||
}
|
||||
|
||||
genrule {
|
||||
name: "statslog_statsdtest.cpp",
|
||||
tools: ["stats-log-api-gen"],
|
||||
cmd: "$(location stats-log-api-gen) --cpp $(genDir)/statslog_statsdtest.cpp --module statsdtest --namespace android,os,statsd,util --importHeader statslog_statsdtest.h",
|
||||
out: [
|
||||
"statslog_statsdtest.cpp",
|
||||
],
|
||||
}
|
||||
|
||||
cc_library_static {
|
||||
name: "libstatslog_statsdtest",
|
||||
generated_sources: ["statslog_statsdtest.cpp"],
|
||||
generated_headers: ["statslog_statsdtest.h"],
|
||||
export_generated_headers: ["statslog_statsdtest.h"],
|
||||
shared_libs: [
|
||||
"libstatssocket",
|
||||
]
|
||||
}
|
||||
|
||||
cc_library_static {
|
||||
name: "libstatslog_statsd",
|
||||
generated_sources: ["statslog_statsd.cpp"],
|
||||
generated_headers: ["statslog_statsd.h"],
|
||||
export_generated_headers: ["statslog_statsd.h"],
|
||||
apex_available: [
|
||||
"com.android.os.statsd",
|
||||
"test_com.android.os.statsd",
|
||||
],
|
||||
shared_libs: [
|
||||
"libstatssocket",
|
||||
]
|
||||
}
|
||||
|
||||
// =========
|
||||
// statsd
|
||||
// =========
|
||||
|
||||
cc_binary {
|
||||
name: "statsd",
|
||||
defaults: ["statsd_defaults"],
|
||||
|
||||
srcs: ["src/main.cpp"],
|
||||
|
||||
cflags: [
|
||||
"-Wall",
|
||||
"-Wextra",
|
||||
"-Werror",
|
||||
"-Wno-unused-parameter",
|
||||
// optimize for size (protobuf glop can get big)
|
||||
"-Os",
|
||||
// "-g",
|
||||
// "-O0",
|
||||
],
|
||||
|
||||
product_variables: {
|
||||
eng: {
|
||||
// Enable sanitizer ONLY on eng builds
|
||||
//sanitize: {
|
||||
// address: true,
|
||||
//},
|
||||
},
|
||||
},
|
||||
|
||||
proto: {
|
||||
type: "lite",
|
||||
static: true,
|
||||
},
|
||||
stl: "libc++_static",
|
||||
|
||||
shared_libs: [
|
||||
"libstatssocket",
|
||||
],
|
||||
|
||||
apex_available: [
|
||||
"com.android.os.statsd",
|
||||
"test_com.android.os.statsd",
|
||||
],
|
||||
}
|
||||
|
||||
// ==============
|
||||
// statsd_test
|
||||
// ==============
|
||||
|
||||
cc_test {
|
||||
name: "statsd_test",
|
||||
defaults: ["statsd_defaults"],
|
||||
test_suites: ["device-tests", "mts"],
|
||||
test_config: "statsd_test.xml",
|
||||
|
||||
//TODO(b/153588990): Remove when the build system properly separates
|
||||
//32bit and 64bit architectures.
|
||||
compile_multilib: "both",
|
||||
multilib: {
|
||||
lib64: {
|
||||
suffix: "64",
|
||||
},
|
||||
lib32: {
|
||||
suffix: "32",
|
||||
},
|
||||
},
|
||||
|
||||
cflags: [
|
||||
"-Wall",
|
||||
"-Werror",
|
||||
"-Wno-missing-field-initializers",
|
||||
"-Wno-unused-variable",
|
||||
"-Wno-unused-function",
|
||||
"-Wno-unused-parameter",
|
||||
],
|
||||
|
||||
require_root: true,
|
||||
|
||||
srcs: [
|
||||
// atom_field_options.proto needs field_options.proto, but that is
|
||||
// not included in libprotobuf-cpp-lite, so compile it here.
|
||||
":libprotobuf-internal-protos",
|
||||
":libstats_internal_protos",
|
||||
|
||||
"src/shell/shell_data.proto",
|
||||
"src/stats_log.proto",
|
||||
"tests/AlarmMonitor_test.cpp",
|
||||
"tests/anomaly/AlarmTracker_test.cpp",
|
||||
"tests/anomaly/AnomalyTracker_test.cpp",
|
||||
"tests/condition/CombinationConditionTracker_test.cpp",
|
||||
"tests/condition/ConditionTimer_test.cpp",
|
||||
"tests/condition/SimpleConditionTracker_test.cpp",
|
||||
"tests/ConfigManager_test.cpp",
|
||||
"tests/e2e/Alarm_e2e_test.cpp",
|
||||
"tests/e2e/Anomaly_count_e2e_test.cpp",
|
||||
"tests/e2e/Anomaly_duration_sum_e2e_test.cpp",
|
||||
"tests/e2e/Attribution_e2e_test.cpp",
|
||||
"tests/e2e/ConfigTtl_e2e_test.cpp",
|
||||
"tests/e2e/CountMetric_e2e_test.cpp",
|
||||
"tests/e2e/DurationMetric_e2e_test.cpp",
|
||||
"tests/e2e/GaugeMetric_e2e_pull_test.cpp",
|
||||
"tests/e2e/GaugeMetric_e2e_push_test.cpp",
|
||||
"tests/e2e/MetricActivation_e2e_test.cpp",
|
||||
"tests/e2e/MetricConditionLink_e2e_test.cpp",
|
||||
"tests/e2e/PartialBucket_e2e_test.cpp",
|
||||
"tests/e2e/ValueMetric_pull_e2e_test.cpp",
|
||||
"tests/e2e/WakelockDuration_e2e_test.cpp",
|
||||
"tests/external/puller_util_test.cpp",
|
||||
"tests/external/StatsCallbackPuller_test.cpp",
|
||||
"tests/external/StatsPuller_test.cpp",
|
||||
"tests/external/StatsPullerManager_test.cpp",
|
||||
"tests/FieldValue_test.cpp",
|
||||
"tests/guardrail/StatsdStats_test.cpp",
|
||||
"tests/HashableDimensionKey_test.cpp",
|
||||
"tests/indexed_priority_queue_test.cpp",
|
||||
"tests/log_event/LogEventQueue_test.cpp",
|
||||
"tests/LogEntryMatcher_test.cpp",
|
||||
"tests/LogEvent_test.cpp",
|
||||
"tests/metadata_util_test.cpp",
|
||||
"tests/metrics/CountMetricProducer_test.cpp",
|
||||
"tests/metrics/DurationMetricProducer_test.cpp",
|
||||
"tests/metrics/EventMetricProducer_test.cpp",
|
||||
"tests/metrics/GaugeMetricProducer_test.cpp",
|
||||
"tests/metrics/MaxDurationTracker_test.cpp",
|
||||
"tests/metrics/metrics_test_helper.cpp",
|
||||
"tests/metrics/OringDurationTracker_test.cpp",
|
||||
"tests/metrics/ValueMetricProducer_test.cpp",
|
||||
"tests/MetricsManager_test.cpp",
|
||||
"tests/shell/ShellSubscriber_test.cpp",
|
||||
"tests/state/StateTracker_test.cpp",
|
||||
"tests/statsd_test_util.cpp",
|
||||
"tests/StatsLogProcessor_test.cpp",
|
||||
"tests/StatsService_test.cpp",
|
||||
"tests/storage/StorageManager_test.cpp",
|
||||
"tests/UidMap_test.cpp",
|
||||
"tests/utils/MultiConditionTrigger_test.cpp",
|
||||
],
|
||||
|
||||
static_libs: [
|
||||
"libgmock",
|
||||
"libplatformprotos",
|
||||
"libstatslog_statsdtest",
|
||||
"libstatssocket_private",
|
||||
],
|
||||
|
||||
proto: {
|
||||
type: "lite",
|
||||
include_dirs: [
|
||||
"external/protobuf/src",
|
||||
"frameworks/proto_logging/stats",
|
||||
],
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
//#############################
|
||||
// statsd micro benchmark
|
||||
//#############################
|
||||
|
||||
cc_benchmark {
|
||||
name: "statsd_benchmark",
|
||||
defaults: ["statsd_defaults"],
|
||||
|
||||
srcs: [
|
||||
// atom_field_options.proto needs field_options.proto, but that is
|
||||
// not included in libprotobuf-cpp-lite, so compile it here.
|
||||
":libprotobuf-internal-protos",
|
||||
":libstats_internal_protos",
|
||||
|
||||
"benchmark/duration_metric_benchmark.cpp",
|
||||
"benchmark/filter_value_benchmark.cpp",
|
||||
"benchmark/get_dimensions_for_condition_benchmark.cpp",
|
||||
"benchmark/hello_world_benchmark.cpp",
|
||||
"benchmark/log_event_benchmark.cpp",
|
||||
"benchmark/main.cpp",
|
||||
"benchmark/metric_util.cpp",
|
||||
"benchmark/stats_write_benchmark.cpp",
|
||||
"src/stats_log.proto",
|
||||
],
|
||||
|
||||
proto: {
|
||||
type: "lite",
|
||||
include_dirs: [
|
||||
"external/protobuf/src",
|
||||
"frameworks/proto_logging/stats",
|
||||
],
|
||||
},
|
||||
|
||||
cflags: [
|
||||
"-Wall",
|
||||
"-Werror",
|
||||
"-Wno-unused-parameter",
|
||||
"-Wno-unused-variable",
|
||||
"-Wno-unused-function",
|
||||
|
||||
// Bug: http://b/29823425 Disable -Wvarargs for Clang update to r271374
|
||||
"-Wno-varargs",
|
||||
],
|
||||
|
||||
static_libs: [
|
||||
"libplatformprotos",
|
||||
"libstatssocket_private",
|
||||
],
|
||||
|
||||
shared_libs: [
|
||||
"libgtest_prod",
|
||||
"libprotobuf-cpp-lite",
|
||||
"libstatslog",
|
||||
],
|
||||
}
|
||||
|
||||
// ==== java proto device library (for test only) ==============================
|
||||
java_library {
|
||||
name: "statsdprotolite",
|
||||
sdk_version: "core_current",
|
||||
proto: {
|
||||
type: "lite",
|
||||
include_dirs: [
|
||||
"external/protobuf/src",
|
||||
"frameworks/proto_logging/stats",
|
||||
],
|
||||
},
|
||||
|
||||
srcs: [
|
||||
":libstats_atoms_proto",
|
||||
"src/shell/shell_config.proto",
|
||||
"src/shell/shell_data.proto",
|
||||
"src/stats_log.proto",
|
||||
"src/statsd_config.proto",
|
||||
],
|
||||
|
||||
static_libs: [
|
||||
"platformprotoslite",
|
||||
],
|
||||
// Protos have lots of MissingOverride and similar.
|
||||
errorprone: {
|
||||
javacflags: ["-XepDisableAllChecks"],
|
||||
},
|
||||
}
|
||||
|
||||
// Filegroup for statsd config proto definition.
|
||||
filegroup {
|
||||
name: "statsd-config-proto-def",
|
||||
srcs: ["src/statsd_config.proto"],
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
baligh@google.com
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"presubmit" : [
|
||||
{
|
||||
"name" : "statsd_test"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,314 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#include <vector>
|
||||
#include "benchmark/benchmark.h"
|
||||
#include "FieldValue.h"
|
||||
#include "HashableDimensionKey.h"
|
||||
#include "logd/LogEvent.h"
|
||||
#include "stats_log_util.h"
|
||||
#include "metric_util.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
using std::vector;
|
||||
|
||||
static StatsdConfig CreateDurationMetricConfig_NoLink_AND_CombinationCondition(
|
||||
DurationMetric::AggregationType aggregationType, bool addExtraDimensionInCondition) {
|
||||
StatsdConfig config;
|
||||
*config.add_atom_matcher() = CreateStartScheduledJobAtomMatcher();
|
||||
*config.add_atom_matcher() = CreateFinishScheduledJobAtomMatcher();
|
||||
*config.add_atom_matcher() = CreateSyncStartAtomMatcher();
|
||||
*config.add_atom_matcher() = CreateSyncEndAtomMatcher();
|
||||
*config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
|
||||
*config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher();
|
||||
|
||||
auto scheduledJobPredicate = CreateScheduledJobPredicate();
|
||||
auto dimensions = scheduledJobPredicate.mutable_simple_predicate()->mutable_dimensions();
|
||||
dimensions->set_field(android::util::SCHEDULED_JOB_STATE_CHANGED);
|
||||
dimensions->add_child()->set_field(2); // job name field.
|
||||
|
||||
auto screenIsOffPredicate = CreateScreenIsOffPredicate();
|
||||
|
||||
auto isSyncingPredicate = CreateIsSyncingPredicate();
|
||||
auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions();
|
||||
*syncDimension = CreateAttributionUidAndTagDimensions(android::util::SYNC_STATE_CHANGED,
|
||||
{Position::FIRST});
|
||||
if (addExtraDimensionInCondition) {
|
||||
syncDimension->add_child()->set_field(2 /* name field*/);
|
||||
}
|
||||
|
||||
*config.add_predicate() = scheduledJobPredicate;
|
||||
*config.add_predicate() = screenIsOffPredicate;
|
||||
*config.add_predicate() = isSyncingPredicate;
|
||||
auto combinationPredicate = config.add_predicate();
|
||||
combinationPredicate->set_id(StringToId("CombinationPredicate"));
|
||||
combinationPredicate->mutable_combination()->set_operation(LogicalOperation::AND);
|
||||
addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate);
|
||||
addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate);
|
||||
|
||||
auto metric = config.add_duration_metric();
|
||||
metric->set_bucket(FIVE_MINUTES);
|
||||
metric->set_id(StringToId("scheduledJob"));
|
||||
metric->set_what(scheduledJobPredicate.id());
|
||||
metric->set_condition(combinationPredicate->id());
|
||||
metric->set_aggregation_type(aggregationType);
|
||||
auto dimensionWhat = metric->mutable_dimensions_in_what();
|
||||
dimensionWhat->set_field(android::util::SCHEDULED_JOB_STATE_CHANGED);
|
||||
dimensionWhat->add_child()->set_field(2); // job name field.
|
||||
*metric->mutable_dimensions_in_condition() = CreateAttributionUidAndTagDimensions(
|
||||
android::util::SYNC_STATE_CHANGED, {Position::FIRST});
|
||||
return config;
|
||||
}
|
||||
|
||||
static StatsdConfig CreateDurationMetricConfig_Link_AND_CombinationCondition(
|
||||
DurationMetric::AggregationType aggregationType, bool addExtraDimensionInCondition) {
|
||||
StatsdConfig config;
|
||||
*config.add_atom_matcher() = CreateStartScheduledJobAtomMatcher();
|
||||
*config.add_atom_matcher() = CreateFinishScheduledJobAtomMatcher();
|
||||
*config.add_atom_matcher() = CreateSyncStartAtomMatcher();
|
||||
*config.add_atom_matcher() = CreateSyncEndAtomMatcher();
|
||||
*config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
|
||||
*config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher();
|
||||
|
||||
auto scheduledJobPredicate = CreateScheduledJobPredicate();
|
||||
auto dimensions = scheduledJobPredicate.mutable_simple_predicate()->mutable_dimensions();
|
||||
*dimensions = CreateAttributionUidDimensions(
|
||||
android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST});
|
||||
dimensions->add_child()->set_field(2); // job name field.
|
||||
|
||||
auto isSyncingPredicate = CreateIsSyncingPredicate();
|
||||
auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions();
|
||||
*syncDimension = CreateAttributionUidDimensions(
|
||||
android::util::SYNC_STATE_CHANGED, {Position::FIRST});
|
||||
if (addExtraDimensionInCondition) {
|
||||
syncDimension->add_child()->set_field(2 /* name field*/);
|
||||
}
|
||||
|
||||
auto screenIsOffPredicate = CreateScreenIsOffPredicate();
|
||||
|
||||
*config.add_predicate() = scheduledJobPredicate;
|
||||
*config.add_predicate() = screenIsOffPredicate;
|
||||
*config.add_predicate() = isSyncingPredicate;
|
||||
auto combinationPredicate = config.add_predicate();
|
||||
combinationPredicate->set_id(StringToId("CombinationPredicate"));
|
||||
combinationPredicate->mutable_combination()->set_operation(LogicalOperation::AND);
|
||||
addPredicateToPredicateCombination(screenIsOffPredicate, combinationPredicate);
|
||||
addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate);
|
||||
|
||||
auto metric = config.add_duration_metric();
|
||||
metric->set_bucket(FIVE_MINUTES);
|
||||
metric->set_id(StringToId("scheduledJob"));
|
||||
metric->set_what(scheduledJobPredicate.id());
|
||||
metric->set_condition(combinationPredicate->id());
|
||||
metric->set_aggregation_type(aggregationType);
|
||||
*metric->mutable_dimensions_in_what() = CreateAttributionUidDimensions(
|
||||
android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST});
|
||||
|
||||
auto links = metric->add_links();
|
||||
links->set_condition(isSyncingPredicate.id());
|
||||
*links->mutable_fields_in_what() =
|
||||
CreateAttributionUidDimensions(
|
||||
android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST});
|
||||
*links->mutable_fields_in_condition() =
|
||||
CreateAttributionUidDimensions(android::util::SYNC_STATE_CHANGED, {Position::FIRST});
|
||||
return config;
|
||||
}
|
||||
|
||||
static void BM_DurationMetricNoLink(benchmark::State& state) {
|
||||
ConfigKey cfgKey;
|
||||
auto config = CreateDurationMetricConfig_NoLink_AND_CombinationCondition(
|
||||
DurationMetric::SUM, false);
|
||||
int64_t bucketStartTimeNs = 10000000000;
|
||||
int64_t bucketSizeNs =
|
||||
TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
|
||||
|
||||
std::vector<std::unique_ptr<LogEvent>> events;
|
||||
|
||||
events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 11,
|
||||
android::view::DISPLAY_STATE_OFF));
|
||||
events.push_back(
|
||||
CreateScreenStateChangedEvent(bucketStartTimeNs + 40, android::view::DISPLAY_STATE_ON));
|
||||
|
||||
events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 102,
|
||||
android::view::DISPLAY_STATE_OFF));
|
||||
events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 450,
|
||||
android::view::DISPLAY_STATE_ON));
|
||||
|
||||
events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 650,
|
||||
android::view::DISPLAY_STATE_OFF));
|
||||
events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + bucketSizeNs + 100,
|
||||
android::view::DISPLAY_STATE_ON));
|
||||
|
||||
events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + bucketSizeNs + 640,
|
||||
android::view::DISPLAY_STATE_OFF));
|
||||
events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + bucketSizeNs + 650,
|
||||
android::view::DISPLAY_STATE_ON));
|
||||
|
||||
vector<int> attributionUids1 = {9999};
|
||||
vector<string> attributionTags1 = {""};
|
||||
events.push_back(CreateStartScheduledJobEvent(bucketStartTimeNs + 2, attributionUids1,
|
||||
attributionTags1, "job0"));
|
||||
events.push_back(CreateFinishScheduledJobEvent(bucketStartTimeNs + 101, attributionUids1,
|
||||
attributionTags1, "job0"));
|
||||
|
||||
events.push_back(CreateStartScheduledJobEvent(bucketStartTimeNs + 201, attributionUids1,
|
||||
attributionTags1, "job2"));
|
||||
events.push_back(CreateFinishScheduledJobEvent(bucketStartTimeNs + 500, attributionUids1,
|
||||
attributionTags1, "job2"));
|
||||
|
||||
vector<int> attributionUids2 = {8888};
|
||||
events.push_back(CreateStartScheduledJobEvent(bucketStartTimeNs + 600, attributionUids2,
|
||||
attributionTags1, "job2"));
|
||||
events.push_back(CreateFinishScheduledJobEvent(bucketStartTimeNs + bucketSizeNs + 850,
|
||||
attributionUids2, attributionTags1, "job2"));
|
||||
|
||||
events.push_back(CreateStartScheduledJobEvent(bucketStartTimeNs + bucketSizeNs + 600,
|
||||
attributionUids2, attributionTags1, "job1"));
|
||||
events.push_back(CreateFinishScheduledJobEvent(bucketStartTimeNs + bucketSizeNs + 900,
|
||||
attributionUids2, attributionTags1, "job1"));
|
||||
|
||||
vector<int> attributionUids3 = {111, 222, 222};
|
||||
vector<string> attributionTags3 = {"App1", "GMSCoreModule1", "GMSCoreModule2"};
|
||||
events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 10, attributionUids3,
|
||||
attributionTags3, "ReadEmail"));
|
||||
events.push_back(CreateSyncEndEvent(bucketStartTimeNs + 50, attributionUids3, attributionTags3,
|
||||
"ReadEmail"));
|
||||
|
||||
events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 200, attributionUids3,
|
||||
attributionTags3, "ReadEmail"));
|
||||
events.push_back(CreateSyncEndEvent(bucketStartTimeNs + bucketSizeNs + 300, attributionUids3,
|
||||
attributionTags3, "ReadEmail"));
|
||||
|
||||
events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 400, attributionUids3,
|
||||
attributionTags3, "ReadDoc"));
|
||||
events.push_back(CreateSyncEndEvent(bucketStartTimeNs + bucketSizeNs - 1, attributionUids3,
|
||||
attributionTags3, "ReadDoc"));
|
||||
|
||||
vector<int> attributionUids4 = {333, 222, 555};
|
||||
vector<string> attributionTags4 = {"App2", "GMSCoreModule1", "GMSCoreModule2"};
|
||||
events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 401, attributionUids4,
|
||||
attributionTags4, "ReadEmail"));
|
||||
events.push_back(CreateSyncEndEvent(bucketStartTimeNs + bucketSizeNs + 700, attributionUids4,
|
||||
attributionTags4, "ReadEmail"));
|
||||
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<std::unique_ptr<LogEvent>> events;
|
||||
|
||||
events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 55,
|
||||
android::view::DISPLAY_STATE_OFF));
|
||||
events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 120,
|
||||
android::view::DISPLAY_STATE_ON));
|
||||
events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 121,
|
||||
android::view::DISPLAY_STATE_OFF));
|
||||
events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 450,
|
||||
android::view::DISPLAY_STATE_ON));
|
||||
|
||||
events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + 501,
|
||||
android::view::DISPLAY_STATE_OFF));
|
||||
events.push_back(CreateScreenStateChangedEvent(bucketStartTimeNs + bucketSizeNs + 100,
|
||||
android::view::DISPLAY_STATE_ON));
|
||||
|
||||
vector<int> attributionUids1 = {111};
|
||||
vector<string> attributionTags1 = {"App1"};
|
||||
events.push_back(CreateStartScheduledJobEvent(bucketStartTimeNs + 1, attributionUids1,
|
||||
attributionTags1, "job1"));
|
||||
events.push_back(CreateFinishScheduledJobEvent(bucketStartTimeNs + 101, attributionUids1,
|
||||
attributionTags1, "job1"));
|
||||
|
||||
vector<int> attributionUids2 = {333};
|
||||
vector<string> attributionTags2 = {"App2"};
|
||||
events.push_back(CreateStartScheduledJobEvent(bucketStartTimeNs + 201, attributionUids2,
|
||||
attributionTags2, "job2"));
|
||||
events.push_back(CreateFinishScheduledJobEvent(bucketStartTimeNs + 500, attributionUids2,
|
||||
attributionTags2, "job2"));
|
||||
events.push_back(CreateStartScheduledJobEvent(bucketStartTimeNs + 600, attributionUids2,
|
||||
attributionTags2, "job2"));
|
||||
events.push_back(CreateFinishScheduledJobEvent(bucketStartTimeNs + bucketSizeNs + 850,
|
||||
attributionUids2, attributionTags2, "job2"));
|
||||
|
||||
vector<int> attributionUids3 = {444};
|
||||
vector<string> attributionTags3 = {"App3"};
|
||||
events.push_back(CreateStartScheduledJobEvent(bucketStartTimeNs + bucketSizeNs - 2,
|
||||
attributionUids3, attributionTags3, "job3"));
|
||||
events.push_back(CreateFinishScheduledJobEvent(bucketStartTimeNs + bucketSizeNs + 900,
|
||||
attributionUids3, attributionTags3, "job3"));
|
||||
|
||||
vector<int> attributionUids4 = {111, 222, 222};
|
||||
vector<string> attributionTags4 = {"App1", "GMSCoreModule1", "GMSCoreModule2"};
|
||||
events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 50, attributionUids4,
|
||||
attributionTags4, "ReadEmail"));
|
||||
events.push_back(CreateSyncEndEvent(bucketStartTimeNs + 110, attributionUids4, attributionTags4,
|
||||
"ReadEmail"));
|
||||
|
||||
vector<int> attributionUids5 = {333, 222, 555};
|
||||
vector<string> attributionTags5 = {"App2", "GMSCoreModule1", "GMSCoreModule2"};
|
||||
events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 300, attributionUids5,
|
||||
attributionTags5, "ReadEmail"));
|
||||
events.push_back(CreateSyncEndEvent(bucketStartTimeNs + bucketSizeNs + 700, attributionUids5,
|
||||
attributionTags5, "ReadEmail"));
|
||||
events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 400, attributionUids5,
|
||||
attributionTags5, "ReadDoc"));
|
||||
events.push_back(CreateSyncEndEvent(bucketStartTimeNs + bucketSizeNs - 1, attributionUids5,
|
||||
attributionTags5, "ReadDoc"));
|
||||
|
||||
vector<int> attributionUids6 = {444, 222, 555};
|
||||
vector<string> attributionTags6 = {"App3", "GMSCoreModule1", "GMSCoreModule2"};
|
||||
events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 550, attributionUids6,
|
||||
attributionTags6, "ReadDoc"));
|
||||
events.push_back(CreateSyncEndEvent(bucketStartTimeNs + 800, attributionUids6, attributionTags6,
|
||||
"ReadDoc"));
|
||||
events.push_back(CreateSyncStartEvent(bucketStartTimeNs + bucketSizeNs - 1, attributionUids6,
|
||||
attributionTags6, "ReadDoc"));
|
||||
events.push_back(CreateSyncEndEvent(bucketStartTimeNs + bucketSizeNs + 700, attributionUids6,
|
||||
attributionTags6, "ReadDoc"));
|
||||
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
|
||||
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#include <vector>
|
||||
|
||||
#include "FieldValue.h"
|
||||
#include "HashableDimensionKey.h"
|
||||
#include "benchmark/benchmark.h"
|
||||
#include "logd/LogEvent.h"
|
||||
#include "metric_util.h"
|
||||
#include "stats_event.h"
|
||||
#include "stats_log_util.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
using std::vector;
|
||||
|
||||
static void createLogEventAndMatcher(LogEvent* event, FieldMatcher* field_matcher) {
|
||||
AStatsEvent* statsEvent = AStatsEvent_obtain();
|
||||
AStatsEvent_setAtomId(statsEvent, 1);
|
||||
AStatsEvent_overwriteTimestamp(statsEvent, 100000);
|
||||
|
||||
std::vector<int> attributionUids = {100, 100};
|
||||
std::vector<string> attributionTags = {"LOCATION", "LOCATION"};
|
||||
writeAttribution(statsEvent, attributionUids, attributionTags);
|
||||
|
||||
AStatsEvent_writeFloat(statsEvent, 3.2f);
|
||||
AStatsEvent_writeString(statsEvent, "LOCATION");
|
||||
AStatsEvent_writeInt64(statsEvent, 990);
|
||||
|
||||
parseStatsEventToLogEvent(statsEvent, event);
|
||||
|
||||
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(/*uid=*/0, /*pid=*/0);
|
||||
FieldMatcher field_matcher;
|
||||
createLogEventAndMatcher(&event, &field_matcher);
|
||||
|
||||
std::vector<Matcher> matchers;
|
||||
translateFieldMatcher(field_matcher, &matchers);
|
||||
|
||||
while (state.KeepRunning()) {
|
||||
HashableDimensionKey output;
|
||||
filterValues(matchers, event.getValues(), &output);
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK(BM_FilterValue);
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#include <vector>
|
||||
|
||||
#include "FieldValue.h"
|
||||
#include "HashableDimensionKey.h"
|
||||
#include "benchmark/benchmark.h"
|
||||
#include "logd/LogEvent.h"
|
||||
#include "metric_util.h"
|
||||
#include "stats_event.h"
|
||||
#include "stats_log_util.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
using std::vector;
|
||||
|
||||
static void createLogEventAndLink(LogEvent* event, Metric2Condition *link) {
|
||||
AStatsEvent* statsEvent = AStatsEvent_obtain();
|
||||
AStatsEvent_setAtomId(statsEvent, 1);
|
||||
AStatsEvent_overwriteTimestamp(statsEvent, 100000);
|
||||
|
||||
std::vector<int> attributionUids = {100, 100};
|
||||
std::vector<string> attributionTags = {"LOCATION", "LOCATION"};
|
||||
writeAttribution(statsEvent, attributionUids, attributionTags);
|
||||
|
||||
AStatsEvent_writeFloat(statsEvent, 3.2f);
|
||||
AStatsEvent_writeString(statsEvent, "LOCATION");
|
||||
AStatsEvent_writeInt64(statsEvent, 990);
|
||||
|
||||
parseStatsEventToLogEvent(statsEvent, event);
|
||||
|
||||
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(/*uid=*/0, /*pid=*/0);
|
||||
createLogEventAndLink(&event, &link);
|
||||
|
||||
while (state.KeepRunning()) {
|
||||
HashableDimensionKey output;
|
||||
getDimensionForCondition(event.getValues(), link, &output);
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK(BM_GetDimensionInCondition);
|
||||
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
* 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 "benchmark/benchmark.h"
|
||||
|
||||
static void BM_StringCreation(benchmark::State& state) {
|
||||
while (state.KeepRunning()) std::string empty_string;
|
||||
}
|
||||
// Register the function as a benchmark
|
||||
BENCHMARK(BM_StringCreation);
|
||||
|
||||
// Define another benchmark
|
||||
static void BM_StringCopy(benchmark::State& state) {
|
||||
std::string x = "hello";
|
||||
while (state.KeepRunning()) std::string copy(x);
|
||||
}
|
||||
BENCHMARK(BM_StringCopy);
|
||||
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#include <vector>
|
||||
#include "benchmark/benchmark.h"
|
||||
#include "logd/LogEvent.h"
|
||||
#include "stats_event.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
static size_t createAndParseStatsEvent(uint8_t* msg) {
|
||||
AStatsEvent* event = AStatsEvent_obtain();
|
||||
AStatsEvent_setAtomId(event, 100);
|
||||
AStatsEvent_writeInt32(event, 2);
|
||||
AStatsEvent_writeFloat(event, 2.0);
|
||||
AStatsEvent_build(event);
|
||||
|
||||
size_t size;
|
||||
uint8_t* buf = AStatsEvent_getBuffer(event, &size);
|
||||
memcpy(msg, buf, size);
|
||||
return size;
|
||||
}
|
||||
|
||||
static void BM_LogEventCreation(benchmark::State& state) {
|
||||
uint8_t msg[LOGGER_ENTRY_MAX_PAYLOAD];
|
||||
size_t size = createAndParseStatsEvent(msg);
|
||||
while (state.KeepRunning()) {
|
||||
LogEvent event(/*uid=*/ 1000, /*pid=*/ 1001);
|
||||
benchmark::DoNotOptimize(event.parseBuffer(msg, size));
|
||||
}
|
||||
}
|
||||
BENCHMARK(BM_LogEventCreation);
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,19 +0,0 @@
|
||||
/*
|
||||
* 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 <benchmark/benchmark.h>
|
||||
|
||||
BENCHMARK_MAIN();
|
||||
@@ -1,379 +0,0 @@
|
||||
// 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"
|
||||
|
||||
#include "stats_event.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::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::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(state);
|
||||
return atom_matcher;
|
||||
}
|
||||
|
||||
AtomMatcher CreateMoveToBackgroundAtomMatcher() {
|
||||
return CreateActivityForegroundStateChangedAtomMatcher(
|
||||
"MoveToBackground", ActivityForegroundStateChanged::BACKGROUND);
|
||||
}
|
||||
|
||||
AtomMatcher CreateMoveToForegroundAtomMatcher() {
|
||||
return CreateActivityForegroundStateChangedAtomMatcher(
|
||||
"MoveToForeground", ActivityForegroundStateChanged::FOREGROUND);
|
||||
}
|
||||
|
||||
Predicate CreateScheduledJobPredicate() {
|
||||
Predicate predicate;
|
||||
predicate.set_id(StringToId("ScheduledJobRunningPredicate"));
|
||||
predicate.mutable_simple_predicate()->set_start(StringToId("ScheduledJobStart"));
|
||||
predicate.mutable_simple_predicate()->set_stop(StringToId("ScheduledJobFinish"));
|
||||
return predicate;
|
||||
}
|
||||
|
||||
Predicate CreateBatterySaverModePredicate() {
|
||||
Predicate predicate;
|
||||
predicate.set_id(StringToId("BatterySaverIsOn"));
|
||||
predicate.mutable_simple_predicate()->set_start(StringToId("BatterySaverModeStart"));
|
||||
predicate.mutable_simple_predicate()->set_stop(StringToId("BatterySaverModeStop"));
|
||||
return predicate;
|
||||
}
|
||||
|
||||
Predicate CreateScreenIsOnPredicate() {
|
||||
Predicate predicate;
|
||||
predicate.set_id(StringToId("ScreenIsOn"));
|
||||
predicate.mutable_simple_predicate()->set_start(StringToId("ScreenTurnedOn"));
|
||||
predicate.mutable_simple_predicate()->set_stop(StringToId("ScreenTurnedOff"));
|
||||
return predicate;
|
||||
}
|
||||
|
||||
Predicate CreateScreenIsOffPredicate() {
|
||||
Predicate predicate;
|
||||
predicate.set_id(1111123);
|
||||
predicate.mutable_simple_predicate()->set_start(StringToId("ScreenTurnedOff"));
|
||||
predicate.mutable_simple_predicate()->set_stop(StringToId("ScreenTurnedOn"));
|
||||
return predicate;
|
||||
}
|
||||
|
||||
Predicate CreateHoldingWakelockPredicate() {
|
||||
Predicate predicate;
|
||||
predicate.set_id(StringToId("HoldingWakelock"));
|
||||
predicate.mutable_simple_predicate()->set_start(StringToId("AcquireWakelock"));
|
||||
predicate.mutable_simple_predicate()->set_stop(StringToId("ReleaseWakelock"));
|
||||
return predicate;
|
||||
}
|
||||
|
||||
Predicate CreateIsSyncingPredicate() {
|
||||
Predicate predicate;
|
||||
predicate.set_id(33333333333333);
|
||||
predicate.mutable_simple_predicate()->set_start(StringToId("SyncStart"));
|
||||
predicate.mutable_simple_predicate()->set_stop(StringToId("SyncEnd"));
|
||||
return predicate;
|
||||
}
|
||||
|
||||
Predicate CreateIsInBackgroundPredicate() {
|
||||
Predicate predicate;
|
||||
predicate.set_id(StringToId("IsInBackground"));
|
||||
predicate.mutable_simple_predicate()->set_start(StringToId("MoveToBackground"));
|
||||
predicate.mutable_simple_predicate()->set_stop(StringToId("MoveToForeground"));
|
||||
return predicate;
|
||||
}
|
||||
|
||||
void addPredicateToPredicateCombination(const Predicate& predicate,
|
||||
Predicate* combinationPredicate) {
|
||||
combinationPredicate->mutable_combination()->add_predicate(predicate.id());
|
||||
}
|
||||
|
||||
FieldMatcher CreateAttributionUidDimensions(const int atomId,
|
||||
const std::vector<Position>& positions) {
|
||||
FieldMatcher dimensions;
|
||||
dimensions.set_field(atomId);
|
||||
for (const auto position : positions) {
|
||||
auto child = dimensions.add_child();
|
||||
child->set_field(1);
|
||||
child->set_position(position);
|
||||
child->add_child()->set_field(1);
|
||||
}
|
||||
return dimensions;
|
||||
}
|
||||
|
||||
FieldMatcher CreateAttributionUidAndTagDimensions(const int atomId,
|
||||
const std::vector<Position>& positions) {
|
||||
FieldMatcher dimensions;
|
||||
dimensions.set_field(atomId);
|
||||
for (const auto position : positions) {
|
||||
auto child = dimensions.add_child();
|
||||
child->set_field(1);
|
||||
child->set_position(position);
|
||||
child->add_child()->set_field(1);
|
||||
child->add_child()->set_field(2);
|
||||
}
|
||||
return dimensions;
|
||||
}
|
||||
|
||||
FieldMatcher CreateDimensions(const int atomId, const std::vector<int>& fields) {
|
||||
FieldMatcher dimensions;
|
||||
dimensions.set_field(atomId);
|
||||
for (const int field : fields) {
|
||||
dimensions.add_child()->set_field(field);
|
||||
}
|
||||
return dimensions;
|
||||
}
|
||||
|
||||
void writeAttribution(AStatsEvent* statsEvent, const vector<int>& attributionUids,
|
||||
const vector<string>& attributionTags) {
|
||||
vector<const char*> cTags(attributionTags.size());
|
||||
for (int i = 0; i < cTags.size(); i++) {
|
||||
cTags[i] = attributionTags[i].c_str();
|
||||
}
|
||||
|
||||
AStatsEvent_writeAttributionChain(statsEvent,
|
||||
reinterpret_cast<const uint32_t*>(attributionUids.data()),
|
||||
cTags.data(), attributionUids.size());
|
||||
}
|
||||
|
||||
void parseStatsEventToLogEvent(AStatsEvent* statsEvent, LogEvent* logEvent) {
|
||||
AStatsEvent_build(statsEvent);
|
||||
|
||||
size_t size;
|
||||
uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
|
||||
logEvent->parseBuffer(buf, size);
|
||||
|
||||
AStatsEvent_release(statsEvent);
|
||||
}
|
||||
|
||||
std::unique_ptr<LogEvent> CreateScreenStateChangedEvent(
|
||||
uint64_t timestampNs, const android::view::DisplayStateEnum state) {
|
||||
AStatsEvent* statsEvent = AStatsEvent_obtain();
|
||||
AStatsEvent_setAtomId(statsEvent, util::SCREEN_STATE_CHANGED);
|
||||
AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
|
||||
AStatsEvent_writeInt32(statsEvent, state);
|
||||
|
||||
std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
|
||||
parseStatsEventToLogEvent(statsEvent, logEvent.get());
|
||||
return logEvent;
|
||||
}
|
||||
|
||||
std::unique_ptr<LogEvent> CreateScheduledJobStateChangedEvent(
|
||||
const vector<int>& attributionUids, const vector<string>& attributionTags,
|
||||
const string& jobName, const ScheduledJobStateChanged::State state, uint64_t timestampNs) {
|
||||
AStatsEvent* statsEvent = AStatsEvent_obtain();
|
||||
AStatsEvent_setAtomId(statsEvent, util::SCHEDULED_JOB_STATE_CHANGED);
|
||||
AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
|
||||
|
||||
writeAttribution(statsEvent, attributionUids, attributionTags);
|
||||
AStatsEvent_writeString(statsEvent, jobName.c_str());
|
||||
AStatsEvent_writeInt32(statsEvent, state);
|
||||
|
||||
std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
|
||||
parseStatsEventToLogEvent(statsEvent, logEvent.get());
|
||||
return logEvent;
|
||||
}
|
||||
|
||||
std::unique_ptr<LogEvent> CreateStartScheduledJobEvent(uint64_t timestampNs,
|
||||
const vector<int>& attributionUids,
|
||||
const vector<string>& attributionTags,
|
||||
const string& jobName) {
|
||||
return CreateScheduledJobStateChangedEvent(attributionUids, attributionTags, jobName,
|
||||
ScheduledJobStateChanged::STARTED, timestampNs);
|
||||
}
|
||||
|
||||
// Create log event when scheduled job finishes.
|
||||
std::unique_ptr<LogEvent> CreateFinishScheduledJobEvent(uint64_t timestampNs,
|
||||
const vector<int>& attributionUids,
|
||||
const vector<string>& attributionTags,
|
||||
const string& jobName) {
|
||||
return CreateScheduledJobStateChangedEvent(attributionUids, attributionTags, jobName,
|
||||
ScheduledJobStateChanged::FINISHED, timestampNs);
|
||||
}
|
||||
|
||||
std::unique_ptr<LogEvent> CreateSyncStateChangedEvent(uint64_t timestampNs,
|
||||
const vector<int>& attributionUids,
|
||||
const vector<string>& attributionTags,
|
||||
const string& name,
|
||||
const SyncStateChanged::State state) {
|
||||
AStatsEvent* statsEvent = AStatsEvent_obtain();
|
||||
AStatsEvent_setAtomId(statsEvent, util::SYNC_STATE_CHANGED);
|
||||
AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
|
||||
|
||||
writeAttribution(statsEvent, attributionUids, attributionTags);
|
||||
AStatsEvent_writeString(statsEvent, name.c_str());
|
||||
AStatsEvent_writeInt32(statsEvent, state);
|
||||
|
||||
std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
|
||||
parseStatsEventToLogEvent(statsEvent, logEvent.get());
|
||||
return logEvent;
|
||||
}
|
||||
|
||||
std::unique_ptr<LogEvent> CreateSyncStartEvent(uint64_t timestampNs,
|
||||
const vector<int>& attributionUids,
|
||||
const vector<string>& attributionTags,
|
||||
const string& name) {
|
||||
return CreateSyncStateChangedEvent(timestampNs, attributionUids, attributionTags, name,
|
||||
SyncStateChanged::ON);
|
||||
}
|
||||
|
||||
std::unique_ptr<LogEvent> CreateSyncEndEvent(uint64_t timestampNs,
|
||||
const vector<int>& attributionUids,
|
||||
const vector<string>& attributionTags,
|
||||
const string& name) {
|
||||
return CreateSyncStateChangedEvent(timestampNs, attributionUids, attributionTags, name,
|
||||
SyncStateChanged::OFF);
|
||||
}
|
||||
|
||||
sp<StatsLogProcessor> CreateStatsLogProcessor(const long timeBaseSec, const StatsdConfig& config,
|
||||
const ConfigKey& key) {
|
||||
sp<UidMap> uidMap = new UidMap();
|
||||
sp<StatsPullerManager> pullerManager = new StatsPullerManager();
|
||||
sp<AlarmMonitor> anomalyAlarmMonitor;
|
||||
sp<AlarmMonitor> periodicAlarmMonitor;
|
||||
sp<StatsLogProcessor> processor =
|
||||
new StatsLogProcessor(uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
|
||||
timeBaseSec * NS_PER_SEC, [](const ConfigKey&) { return true; },
|
||||
[](const int&, const vector<int64_t>&) { return true; });
|
||||
processor->OnConfigUpdated(timeBaseSec * NS_PER_SEC, key, config);
|
||||
return processor;
|
||||
}
|
||||
|
||||
void sortLogEventsByTimestamp(std::vector<std::unique_ptr<LogEvent>> *events) {
|
||||
std::sort(events->begin(), events->end(),
|
||||
[](const std::unique_ptr<LogEvent>& a, const std::unique_ptr<LogEvent>& b) {
|
||||
return a->GetElapsedTimestampNs() < b->GetElapsedTimestampNs();
|
||||
});
|
||||
}
|
||||
|
||||
int64_t StringToId(const string& str) {
|
||||
return static_cast<int64_t>(std::hash<std::string>()(str));
|
||||
}
|
||||
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,140 +0,0 @@
|
||||
// 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 "stats_event.h"
|
||||
#include "statslog.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
// Create AtomMatcher proto to simply match a specific atom type.
|
||||
AtomMatcher CreateSimpleAtomMatcher(const string& name, int atomId);
|
||||
|
||||
// Create AtomMatcher proto for scheduled job state changed.
|
||||
AtomMatcher CreateScheduledJobStateChangedAtomMatcher();
|
||||
|
||||
// Create AtomMatcher proto for starting a scheduled job.
|
||||
AtomMatcher CreateStartScheduledJobAtomMatcher();
|
||||
|
||||
// Create AtomMatcher proto for a scheduled job is done.
|
||||
AtomMatcher CreateFinishScheduledJobAtomMatcher();
|
||||
|
||||
// Create AtomMatcher proto for screen brightness state changed.
|
||||
AtomMatcher CreateScreenBrightnessChangedAtomMatcher();
|
||||
|
||||
// Create AtomMatcher proto for acquiring wakelock.
|
||||
AtomMatcher CreateAcquireWakelockAtomMatcher();
|
||||
|
||||
// Create AtomMatcher proto for releasing wakelock.
|
||||
AtomMatcher CreateReleaseWakelockAtomMatcher() ;
|
||||
|
||||
// Create AtomMatcher proto for screen turned on.
|
||||
AtomMatcher CreateScreenTurnedOnAtomMatcher();
|
||||
|
||||
// Create AtomMatcher proto for screen turned off.
|
||||
AtomMatcher CreateScreenTurnedOffAtomMatcher();
|
||||
|
||||
// Create AtomMatcher proto for app sync turned on.
|
||||
AtomMatcher CreateSyncStartAtomMatcher();
|
||||
|
||||
// Create AtomMatcher proto for app sync turned off.
|
||||
AtomMatcher CreateSyncEndAtomMatcher();
|
||||
|
||||
// Create AtomMatcher proto for app sync moves to background.
|
||||
AtomMatcher CreateMoveToBackgroundAtomMatcher();
|
||||
|
||||
// Create AtomMatcher proto for app sync moves to foreground.
|
||||
AtomMatcher CreateMoveToForegroundAtomMatcher();
|
||||
|
||||
// Create Predicate proto for screen is off.
|
||||
Predicate CreateScreenIsOffPredicate();
|
||||
|
||||
// Create Predicate proto for a running scheduled job.
|
||||
Predicate CreateScheduledJobPredicate();
|
||||
|
||||
// Create Predicate proto for holding wakelock.
|
||||
Predicate CreateHoldingWakelockPredicate();
|
||||
|
||||
// Create a Predicate proto for app syncing.
|
||||
Predicate CreateIsSyncingPredicate();
|
||||
|
||||
// Create a Predicate proto for app is in background.
|
||||
Predicate CreateIsInBackgroundPredicate();
|
||||
|
||||
// Add a predicate to the predicate combination.
|
||||
void addPredicateToPredicateCombination(const Predicate& predicate, Predicate* combination);
|
||||
|
||||
// Create dimensions from primitive fields.
|
||||
FieldMatcher CreateDimensions(const int atomId, const std::vector<int>& fields);
|
||||
|
||||
// Create dimensions by attribution uid and tag.
|
||||
FieldMatcher CreateAttributionUidAndTagDimensions(const int atomId,
|
||||
const std::vector<Position>& positions);
|
||||
|
||||
// Create dimensions by attribution uid only.
|
||||
FieldMatcher CreateAttributionUidDimensions(const int atomId,
|
||||
const std::vector<Position>& positions);
|
||||
|
||||
void writeAttribution(AStatsEvent* statsEvent, const vector<int>& attributionUids,
|
||||
const vector<string>& attributionTags);
|
||||
|
||||
void parseStatsEventToLogEvent(AStatsEvent* statsEvent, LogEvent* logEvent);
|
||||
|
||||
// Create log event for screen state changed.
|
||||
std::unique_ptr<LogEvent> CreateScreenStateChangedEvent(
|
||||
uint64_t timestampNs, const android::view::DisplayStateEnum state);
|
||||
|
||||
// Create log event when scheduled job starts.
|
||||
std::unique_ptr<LogEvent> CreateStartScheduledJobEvent(uint64_t timestampNs,
|
||||
const vector<int>& attributionUids,
|
||||
const vector<string>& attributionTags,
|
||||
const string& jobName);
|
||||
|
||||
// Create log event when scheduled job finishes.
|
||||
std::unique_ptr<LogEvent> CreateFinishScheduledJobEvent(uint64_t timestampNs,
|
||||
const vector<int>& attributionUids,
|
||||
const vector<string>& attributionTags,
|
||||
const string& jobName);
|
||||
|
||||
// Create log event when the app sync starts.
|
||||
std::unique_ptr<LogEvent> CreateSyncStartEvent(uint64_t timestampNs,
|
||||
const vector<int>& attributionUids,
|
||||
const vector<string>& attributionTags,
|
||||
const string& name);
|
||||
|
||||
// Create log event when the app sync ends.
|
||||
std::unique_ptr<LogEvent> CreateSyncEndEvent(uint64_t timestampNs,
|
||||
const vector<int>& attributionUids,
|
||||
const vector<string>& attributionTags,
|
||||
const string& name);
|
||||
|
||||
// Create a statsd log event processor upon the start time in seconds, config and key.
|
||||
sp<StatsLogProcessor> CreateStatsLogProcessor(const long timeBaseSec, const StatsdConfig& config,
|
||||
const ConfigKey& key);
|
||||
|
||||
// Util function to sort the log events by timestamp.
|
||||
void sortLogEventsByTimestamp(std::vector<std::unique_ptr<LogEvent>> *events);
|
||||
|
||||
int64_t StringToId(const string& str);
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* 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 "benchmark/benchmark.h"
|
||||
#include <statslog.h>
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
static void BM_StatsWrite(benchmark::State& state) {
|
||||
const char* reason = "test";
|
||||
int64_t boot_end_time = 1234567;
|
||||
int64_t total_duration = 100;
|
||||
int64_t bootloader_duration = 10;
|
||||
int64_t time_since_last_boot = 99999999;
|
||||
while (state.KeepRunning()) {
|
||||
android::util::stats_write(
|
||||
android::util::BOOT_SEQUENCE_REPORTED, reason, reason,
|
||||
boot_end_time, total_duration, bootloader_duration, time_since_last_boot);
|
||||
total_duration++;
|
||||
}
|
||||
}
|
||||
BENCHMARK(BM_StatsWrite);
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,474 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#define DEBUG false
|
||||
#include "Log.h"
|
||||
#include "FieldValue.h"
|
||||
#include "HashableDimensionKey.h"
|
||||
#include "math.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
int32_t getEncodedField(int32_t pos[], int32_t depth, bool includeDepth) {
|
||||
int32_t field = 0;
|
||||
for (int32_t i = 0; i <= depth; i++) {
|
||||
int32_t shiftBits = 8 * (kMaxLogDepth - i);
|
||||
field |= (pos[i] << shiftBits);
|
||||
}
|
||||
|
||||
if (includeDepth) {
|
||||
field |= (depth << 24);
|
||||
}
|
||||
return field;
|
||||
}
|
||||
|
||||
int32_t encodeMatcherMask(int32_t mask[], int32_t depth) {
|
||||
return getEncodedField(mask, depth, false) | 0xff000000;
|
||||
}
|
||||
|
||||
bool Field::matches(const Matcher& matcher) const {
|
||||
if (mTag != matcher.mMatcher.getTag()) {
|
||||
return false;
|
||||
}
|
||||
if ((mField & matcher.mMask) == matcher.mMatcher.getField()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (matcher.hasAllPositionMatcher() &&
|
||||
(mField & (matcher.mMask & kClearAllPositionMatcherMask)) == matcher.mMatcher.getField()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void translateFieldMatcher(int tag, const FieldMatcher& matcher, int depth, int* pos, int* mask,
|
||||
std::vector<Matcher>* output) {
|
||||
if (depth > kMaxLogDepth) {
|
||||
ALOGE("depth > 2");
|
||||
return;
|
||||
}
|
||||
|
||||
pos[depth] = matcher.field();
|
||||
mask[depth] = 0x7f;
|
||||
|
||||
if (matcher.has_position()) {
|
||||
depth++;
|
||||
if (depth > 2) {
|
||||
return;
|
||||
}
|
||||
switch (matcher.position()) {
|
||||
case Position::ALL:
|
||||
pos[depth] = 0x00;
|
||||
mask[depth] = 0x7f;
|
||||
break;
|
||||
case Position::ANY:
|
||||
pos[depth] = 0;
|
||||
mask[depth] = 0;
|
||||
break;
|
||||
case Position::FIRST:
|
||||
pos[depth] = 1;
|
||||
mask[depth] = 0x7f;
|
||||
break;
|
||||
case Position::LAST:
|
||||
pos[depth] = 0x80;
|
||||
mask[depth] = 0x80;
|
||||
break;
|
||||
case Position::POSITION_UNKNOWN:
|
||||
pos[depth] = 0;
|
||||
mask[depth] = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (matcher.child_size() == 0) {
|
||||
output->push_back(Matcher(Field(tag, pos, depth), encodeMatcherMask(mask, depth)));
|
||||
} else {
|
||||
for (const auto& child : matcher.child()) {
|
||||
translateFieldMatcher(tag, child, depth + 1, pos, mask, output);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void translateFieldMatcher(const FieldMatcher& matcher, std::vector<Matcher>* output) {
|
||||
int pos[] = {1, 1, 1};
|
||||
int mask[] = {0x7f, 0x7f, 0x7f};
|
||||
int tag = matcher.field();
|
||||
for (const auto& child : matcher.child()) {
|
||||
translateFieldMatcher(tag, child, 0, pos, mask, output);
|
||||
}
|
||||
}
|
||||
|
||||
bool isAttributionUidField(const FieldValue& value) {
|
||||
return isAttributionUidField(value.mField, value.mValue);
|
||||
}
|
||||
|
||||
int32_t getUidIfExists(const FieldValue& value) {
|
||||
// the field is uid field if the field is the uid field in attribution node
|
||||
// or annotated as such in the atom
|
||||
bool isUid = isAttributionUidField(value) || isUidField(value);
|
||||
return isUid ? value.mValue.int_value : -1;
|
||||
}
|
||||
|
||||
bool isAttributionUidField(const Field& field, const Value& value) {
|
||||
int f = field.getField() & 0xff007f;
|
||||
if (f == 0x10001 && value.getType() == INT) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isUidField(const FieldValue& fieldValue) {
|
||||
return fieldValue.mAnnotations.isUidField();
|
||||
}
|
||||
|
||||
Value::Value(const Value& from) {
|
||||
type = from.getType();
|
||||
switch (type) {
|
||||
case INT:
|
||||
int_value = from.int_value;
|
||||
break;
|
||||
case LONG:
|
||||
long_value = from.long_value;
|
||||
break;
|
||||
case FLOAT:
|
||||
float_value = from.float_value;
|
||||
break;
|
||||
case DOUBLE:
|
||||
double_value = from.double_value;
|
||||
break;
|
||||
case STRING:
|
||||
str_value = from.str_value;
|
||||
break;
|
||||
case STORAGE:
|
||||
storage_value = from.storage_value;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::string Value::toString() const {
|
||||
switch (type) {
|
||||
case INT:
|
||||
return std::to_string(int_value) + "[I]";
|
||||
case LONG:
|
||||
return std::to_string(long_value) + "[L]";
|
||||
case FLOAT:
|
||||
return std::to_string(float_value) + "[F]";
|
||||
case DOUBLE:
|
||||
return std::to_string(double_value) + "[D]";
|
||||
case STRING:
|
||||
return str_value + "[S]";
|
||||
case STORAGE:
|
||||
return "bytes of size " + std::to_string(storage_value.size()) + "[ST]";
|
||||
default:
|
||||
return "[UNKNOWN]";
|
||||
}
|
||||
}
|
||||
|
||||
bool Value::isZero() const {
|
||||
switch (type) {
|
||||
case INT:
|
||||
return int_value == 0;
|
||||
case LONG:
|
||||
return long_value == 0;
|
||||
case FLOAT:
|
||||
return fabs(float_value) <= std::numeric_limits<float>::epsilon();
|
||||
case DOUBLE:
|
||||
return fabs(double_value) <= std::numeric_limits<double>::epsilon();
|
||||
case STRING:
|
||||
return str_value.size() == 0;
|
||||
case STORAGE:
|
||||
return storage_value.size() == 0;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Value::operator==(const Value& that) const {
|
||||
if (type != that.getType()) return false;
|
||||
|
||||
switch (type) {
|
||||
case INT:
|
||||
return int_value == that.int_value;
|
||||
case LONG:
|
||||
return long_value == that.long_value;
|
||||
case FLOAT:
|
||||
return float_value == that.float_value;
|
||||
case DOUBLE:
|
||||
return double_value == that.double_value;
|
||||
case STRING:
|
||||
return str_value == that.str_value;
|
||||
case STORAGE:
|
||||
return storage_value == that.storage_value;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Value::operator!=(const Value& that) const {
|
||||
if (type != that.getType()) return true;
|
||||
switch (type) {
|
||||
case INT:
|
||||
return int_value != that.int_value;
|
||||
case LONG:
|
||||
return long_value != that.long_value;
|
||||
case FLOAT:
|
||||
return float_value != that.float_value;
|
||||
case DOUBLE:
|
||||
return double_value != that.double_value;
|
||||
case STRING:
|
||||
return str_value != that.str_value;
|
||||
case STORAGE:
|
||||
return storage_value != that.storage_value;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Value::operator<(const Value& that) const {
|
||||
if (type != that.getType()) return type < that.getType();
|
||||
|
||||
switch (type) {
|
||||
case INT:
|
||||
return int_value < that.int_value;
|
||||
case LONG:
|
||||
return long_value < that.long_value;
|
||||
case FLOAT:
|
||||
return float_value < that.float_value;
|
||||
case DOUBLE:
|
||||
return double_value < that.double_value;
|
||||
case STRING:
|
||||
return str_value < that.str_value;
|
||||
case STORAGE:
|
||||
return storage_value < that.storage_value;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Value::operator>(const Value& that) const {
|
||||
if (type != that.getType()) return type > that.getType();
|
||||
|
||||
switch (type) {
|
||||
case INT:
|
||||
return int_value > that.int_value;
|
||||
case LONG:
|
||||
return long_value > that.long_value;
|
||||
case FLOAT:
|
||||
return float_value > that.float_value;
|
||||
case DOUBLE:
|
||||
return double_value > that.double_value;
|
||||
case STRING:
|
||||
return str_value > that.str_value;
|
||||
case STORAGE:
|
||||
return storage_value > that.storage_value;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Value::operator>=(const Value& that) const {
|
||||
if (type != that.getType()) return type >= that.getType();
|
||||
|
||||
switch (type) {
|
||||
case INT:
|
||||
return int_value >= that.int_value;
|
||||
case LONG:
|
||||
return long_value >= that.long_value;
|
||||
case FLOAT:
|
||||
return float_value >= that.float_value;
|
||||
case DOUBLE:
|
||||
return double_value >= that.double_value;
|
||||
case STRING:
|
||||
return str_value >= that.str_value;
|
||||
case STORAGE:
|
||||
return storage_value >= that.storage_value;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Value Value::operator-(const Value& that) const {
|
||||
Value v;
|
||||
if (type != that.type) {
|
||||
ALOGE("Can't operate on different value types, %d, %d", type, that.type);
|
||||
return v;
|
||||
}
|
||||
if (type == STRING) {
|
||||
ALOGE("Can't operate on string value type");
|
||||
return v;
|
||||
}
|
||||
|
||||
if (type == STORAGE) {
|
||||
ALOGE("Can't operate on storage value type");
|
||||
return v;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case INT:
|
||||
v.setInt(int_value - that.int_value);
|
||||
break;
|
||||
case LONG:
|
||||
v.setLong(long_value - that.long_value);
|
||||
break;
|
||||
case FLOAT:
|
||||
v.setFloat(float_value - that.float_value);
|
||||
break;
|
||||
case DOUBLE:
|
||||
v.setDouble(double_value - that.double_value);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
Value& Value::operator=(const Value& that) {
|
||||
type = that.type;
|
||||
switch (type) {
|
||||
case INT:
|
||||
int_value = that.int_value;
|
||||
break;
|
||||
case LONG:
|
||||
long_value = that.long_value;
|
||||
break;
|
||||
case FLOAT:
|
||||
float_value = that.float_value;
|
||||
break;
|
||||
case DOUBLE:
|
||||
double_value = that.double_value;
|
||||
break;
|
||||
case STRING:
|
||||
str_value = that.str_value;
|
||||
break;
|
||||
case STORAGE:
|
||||
storage_value = that.storage_value;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Value& Value::operator+=(const Value& that) {
|
||||
if (type != that.type) {
|
||||
ALOGE("Can't operate on different value types, %d, %d", type, that.type);
|
||||
return *this;
|
||||
}
|
||||
if (type == STRING) {
|
||||
ALOGE("Can't operate on string value type");
|
||||
return *this;
|
||||
}
|
||||
if (type == STORAGE) {
|
||||
ALOGE("Can't operate on storage value type");
|
||||
return *this;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case INT:
|
||||
int_value += that.int_value;
|
||||
break;
|
||||
case LONG:
|
||||
long_value += that.long_value;
|
||||
break;
|
||||
case FLOAT:
|
||||
float_value += that.float_value;
|
||||
break;
|
||||
case DOUBLE:
|
||||
double_value += that.double_value;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
double Value::getDouble() const {
|
||||
switch (type) {
|
||||
case INT:
|
||||
return int_value;
|
||||
case LONG:
|
||||
return long_value;
|
||||
case FLOAT:
|
||||
return float_value;
|
||||
case DOUBLE:
|
||||
return double_value;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool equalDimensions(const std::vector<Matcher>& dimension_a,
|
||||
const std::vector<Matcher>& dimension_b) {
|
||||
bool eq = dimension_a.size() == dimension_b.size();
|
||||
for (size_t i = 0; eq && i < dimension_a.size(); ++i) {
|
||||
if (dimension_b[i] != dimension_a[i]) {
|
||||
eq = false;
|
||||
}
|
||||
}
|
||||
return eq;
|
||||
}
|
||||
|
||||
bool subsetDimensions(const std::vector<Matcher>& dimension_a,
|
||||
const std::vector<Matcher>& dimension_b) {
|
||||
if (dimension_a.size() > dimension_b.size()) {
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 0; i < dimension_a.size(); ++i) {
|
||||
bool found = false;
|
||||
for (size_t j = 0; j < dimension_b.size(); ++j) {
|
||||
if (dimension_a[i] == dimension_b[j]) {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
bool HasPositionALL(const FieldMatcher& matcher) {
|
||||
if (matcher.has_position() && matcher.position() == Position::ALL) {
|
||||
return true;
|
||||
}
|
||||
for (const auto& child : matcher.child()) {
|
||||
if (HasPositionALL(child)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,462 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
|
||||
#include "annotations.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
class HashableDimensionKey;
|
||||
struct Matcher;
|
||||
struct Field;
|
||||
struct FieldValue;
|
||||
|
||||
const int32_t kMaxLogDepth = 2;
|
||||
const int32_t kLastBitMask = 0x80;
|
||||
const int32_t kClearLastBitDeco = 0x7f;
|
||||
const int32_t kClearAllPositionMatcherMask = 0xffff00ff;
|
||||
|
||||
enum Type { UNKNOWN, INT, LONG, FLOAT, DOUBLE, STRING, STORAGE };
|
||||
|
||||
int32_t getEncodedField(int32_t pos[], int32_t depth, bool includeDepth);
|
||||
|
||||
int32_t encodeMatcherMask(int32_t mask[], int32_t depth);
|
||||
|
||||
// Get the encoded field for a leaf with a [field] number at depth 0;
|
||||
inline int32_t getSimpleField(size_t field) {
|
||||
return ((int32_t)field << 8 * 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Field is a wrapper class for 2 integers that represents the field of a log element in its Atom
|
||||
* proto.
|
||||
* [mTag]: the atom id.
|
||||
* [mField]: encoded path from the root (atom) to leaf.
|
||||
*
|
||||
* For example:
|
||||
* WakeLockStateChanged {
|
||||
* repeated AttributionNode = 1;
|
||||
* int state = 2;
|
||||
* string tag = 3;
|
||||
* }
|
||||
* Read from logd, the items are structured as below:
|
||||
* [[[1000, "tag"], [2000, "tag2"],], 2,"hello"]
|
||||
*
|
||||
* When we read through the list, we will encode each field in a 32bit integer.
|
||||
* 8bit segments |--------|--------|--------|--------|
|
||||
* Depth field0 [L]field1 [L]field1
|
||||
*
|
||||
* The first 8 bits are the depth of the field. for example, the uid 1000 has depth 2.
|
||||
* The following 3 8-bit are for the item's position at each level.
|
||||
* The first bit of each 8bits field is reserved to mark if the item is the last item at that level
|
||||
* this is to make matching easier later.
|
||||
*
|
||||
* The above wakelock event is translated into FieldValue pairs.
|
||||
* 0x02010101->1000
|
||||
* 0x02010182->tag
|
||||
* 0x02018201->2000
|
||||
* 0x02018282->tag2
|
||||
* 0x00020000->2
|
||||
* 0x00030000->"hello"
|
||||
*
|
||||
* This encoding is the building block for the later operations.
|
||||
* Please see the definition for Matcher below to see how the matching is done.
|
||||
*/
|
||||
struct Field {
|
||||
private:
|
||||
int32_t mTag;
|
||||
int32_t mField;
|
||||
|
||||
public:
|
||||
Field() {}
|
||||
|
||||
Field(int32_t tag, int32_t pos[], int32_t depth) : mTag(tag) {
|
||||
mField = getEncodedField(pos, depth, true);
|
||||
}
|
||||
|
||||
Field(const Field& from) : mTag(from.getTag()), mField(from.getField()) {
|
||||
}
|
||||
|
||||
Field(int32_t tag, int32_t field) : mTag(tag), mField(field){};
|
||||
|
||||
inline void setField(int32_t field) {
|
||||
mField = field;
|
||||
}
|
||||
|
||||
inline void setTag(int32_t tag) {
|
||||
mTag = tag;
|
||||
}
|
||||
|
||||
inline void decorateLastPos(int32_t depth) {
|
||||
int32_t mask = kLastBitMask << 8 * (kMaxLogDepth - depth);
|
||||
mField |= mask;
|
||||
}
|
||||
|
||||
inline int32_t getTag() const {
|
||||
return mTag;
|
||||
}
|
||||
|
||||
inline int32_t getDepth() const {
|
||||
return (mField >> 24);
|
||||
}
|
||||
|
||||
inline int32_t getPath(int32_t depth) const {
|
||||
if (depth > 2 || depth < 0) return 0;
|
||||
|
||||
int32_t field = (mField & 0x00ffffff);
|
||||
int32_t mask = 0xffffffff;
|
||||
return (field & (mask << 8 * (kMaxLogDepth - depth)));
|
||||
}
|
||||
|
||||
inline int32_t getPrefix(int32_t depth) const {
|
||||
if (depth == 0) return 0;
|
||||
return getPath(depth - 1);
|
||||
}
|
||||
|
||||
inline int32_t getField() const {
|
||||
return mField;
|
||||
}
|
||||
|
||||
inline int32_t getRawPosAtDepth(int32_t depth) const {
|
||||
int32_t field = (mField & 0x00ffffff);
|
||||
int32_t shift = 8 * (kMaxLogDepth - depth);
|
||||
int32_t mask = 0xff << shift;
|
||||
|
||||
return (field & mask) >> shift;
|
||||
}
|
||||
|
||||
inline int32_t getPosAtDepth(int32_t depth) const {
|
||||
return getRawPosAtDepth(depth) & kClearLastBitDeco;
|
||||
}
|
||||
|
||||
// Check if the first bit of the 8-bit segment for depth is 1
|
||||
inline bool isLastPos(int32_t depth) const {
|
||||
int32_t field = (mField & 0x00ffffff);
|
||||
int32_t mask = kLastBitMask << 8 * (kMaxLogDepth - depth);
|
||||
return (field & mask) != 0;
|
||||
}
|
||||
|
||||
// if the 8-bit segment is all 0's
|
||||
inline bool isAnyPosMatcher(int32_t depth) const {
|
||||
return getDepth() >= depth && getRawPosAtDepth(depth) == 0;
|
||||
}
|
||||
// if the 8bit is 0x80 (1000 0000)
|
||||
inline bool isLastPosMatcher(int32_t depth) const {
|
||||
return getDepth() >= depth && getRawPosAtDepth(depth) == kLastBitMask;
|
||||
}
|
||||
|
||||
inline bool operator==(const Field& that) const {
|
||||
return mTag == that.getTag() && mField == that.getField();
|
||||
};
|
||||
|
||||
inline bool operator!=(const Field& that) const {
|
||||
return mTag != that.getTag() || mField != that.getField();
|
||||
};
|
||||
|
||||
bool operator<(const Field& that) const {
|
||||
if (mTag != that.getTag()) {
|
||||
return mTag < that.getTag();
|
||||
}
|
||||
|
||||
if (mField != that.getField()) {
|
||||
return mField < that.getField();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool matches(const Matcher& that) const;
|
||||
};
|
||||
|
||||
/**
|
||||
* Matcher represents a leaf matcher in the FieldMatcher in statsd_config.
|
||||
*
|
||||
* It contains all information needed to match one or more leaf node.
|
||||
* All information is encoded in a Field(2 ints) and a bit mask(1 int).
|
||||
*
|
||||
* For example, to match the first/any/last uid field in attribution chain in Atom 10,
|
||||
* we have the following FieldMatcher in statsd_config
|
||||
* FieldMatcher {
|
||||
* field:10
|
||||
* FieldMatcher {
|
||||
* field:1
|
||||
* position: any/last/first
|
||||
* FieldMatcher {
|
||||
* field:1
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* We translate the FieldMatcher into a Field, and mask
|
||||
* First: [Matcher Field] 0x02010101 [Mask]0xff7f7f7f
|
||||
* Last: [Matcher Field] 0x02018001 [Mask]0xff7f807f
|
||||
* Any: [Matcher Field] 0x02010001 [Mask]0xff7f007f
|
||||
* All: [Matcher Field] 0x02010001 [Mask]0xff7f7f7f
|
||||
*
|
||||
* [To match a log Field with a Matcher] we apply the bit mask to the log Field and check if
|
||||
* the result is equal to the Matcher Field. That's a bit wise AND operation + check if 2 ints are
|
||||
* equal. Nothing can beat the performance of this matching algorithm.
|
||||
*
|
||||
* TODO(b/110561213): ADD EXAMPLE HERE.
|
||||
*/
|
||||
struct Matcher {
|
||||
Matcher(const Field& matcher, int32_t mask) : mMatcher(matcher), mMask(mask){};
|
||||
|
||||
const Field mMatcher;
|
||||
const int32_t mMask;
|
||||
|
||||
inline const Field& getMatcher() const {
|
||||
return mMatcher;
|
||||
}
|
||||
|
||||
inline int32_t getMask() const {
|
||||
return mMask;
|
||||
}
|
||||
|
||||
inline int32_t getRawMaskAtDepth(int32_t depth) const {
|
||||
int32_t field = (mMask & 0x00ffffff);
|
||||
int32_t shift = 8 * (kMaxLogDepth - depth);
|
||||
int32_t mask = 0xff << shift;
|
||||
|
||||
return (field & mask) >> shift;
|
||||
}
|
||||
|
||||
bool hasAllPositionMatcher() const {
|
||||
return mMatcher.getDepth() == 2 && getRawMaskAtDepth(1) == 0x7f;
|
||||
}
|
||||
|
||||
bool hasAnyPositionMatcher(int* prefix) const {
|
||||
if (mMatcher.getDepth() == 2 && mMatcher.getRawPosAtDepth(1) == 0) {
|
||||
(*prefix) = mMatcher.getPrefix(1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool operator!=(const Matcher& that) const {
|
||||
return mMatcher != that.getMatcher() || mMask != that.getMask();
|
||||
}
|
||||
|
||||
inline bool operator==(const Matcher& that) const {
|
||||
return mMatcher == that.mMatcher && mMask == that.mMask;
|
||||
}
|
||||
};
|
||||
|
||||
inline Matcher getSimpleMatcher(int32_t tag, size_t field) {
|
||||
return Matcher(Field(tag, getSimpleField(field)), 0xff7f0000);
|
||||
}
|
||||
|
||||
inline Matcher getFirstUidMatcher(int32_t atomId) {
|
||||
int32_t pos[] = {1, 1, 1};
|
||||
return Matcher(Field(atomId, pos, 2), 0xff7f7f7f);
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper for a union type to contain multiple types of values.
|
||||
*
|
||||
*/
|
||||
struct Value {
|
||||
Value() : type(UNKNOWN) {}
|
||||
|
||||
Value(int32_t v) {
|
||||
int_value = v;
|
||||
type = INT;
|
||||
}
|
||||
|
||||
Value(int64_t v) {
|
||||
long_value = v;
|
||||
type = LONG;
|
||||
}
|
||||
|
||||
Value(float v) {
|
||||
float_value = v;
|
||||
type = FLOAT;
|
||||
}
|
||||
|
||||
Value(double v) {
|
||||
double_value = v;
|
||||
type = DOUBLE;
|
||||
}
|
||||
|
||||
Value(const std::string& v) {
|
||||
str_value = v;
|
||||
type = STRING;
|
||||
}
|
||||
|
||||
Value(const std::vector<uint8_t>& v) {
|
||||
storage_value = v;
|
||||
type = STORAGE;
|
||||
}
|
||||
|
||||
void setInt(int32_t v) {
|
||||
int_value = v;
|
||||
type = INT;
|
||||
}
|
||||
|
||||
void setLong(int64_t v) {
|
||||
long_value = v;
|
||||
type = LONG;
|
||||
}
|
||||
|
||||
void setFloat(float v) {
|
||||
float_value = v;
|
||||
type = FLOAT;
|
||||
}
|
||||
|
||||
void setDouble(double v) {
|
||||
double_value = v;
|
||||
type = DOUBLE;
|
||||
}
|
||||
|
||||
union {
|
||||
int32_t int_value;
|
||||
int64_t long_value;
|
||||
float float_value;
|
||||
double double_value;
|
||||
};
|
||||
std::string str_value;
|
||||
std::vector<uint8_t> storage_value;
|
||||
|
||||
Type type;
|
||||
|
||||
std::string toString() const;
|
||||
|
||||
bool isZero() const;
|
||||
|
||||
Type getType() const {
|
||||
return type;
|
||||
}
|
||||
|
||||
double getDouble() const;
|
||||
|
||||
Value(const Value& from);
|
||||
|
||||
bool operator==(const Value& that) const;
|
||||
bool operator!=(const Value& that) const;
|
||||
|
||||
bool operator<(const Value& that) const;
|
||||
bool operator>(const Value& that) const;
|
||||
bool operator>=(const Value& that) const;
|
||||
Value operator-(const Value& that) const;
|
||||
Value& operator+=(const Value& that);
|
||||
Value& operator=(const Value& that);
|
||||
};
|
||||
|
||||
class Annotations {
|
||||
public:
|
||||
Annotations() {
|
||||
setNested(true); // Nested = true by default
|
||||
}
|
||||
|
||||
// This enum stores where particular annotations can be found in the
|
||||
// bitmask. Note that these pos do not correspond to annotation ids.
|
||||
enum {
|
||||
NESTED_POS = 0x0,
|
||||
PRIMARY_POS = 0x1,
|
||||
EXCLUSIVE_POS = 0x2,
|
||||
UID_POS = 0x3
|
||||
};
|
||||
|
||||
inline void setNested(bool nested) { setBitmaskAtPos(NESTED_POS, nested); }
|
||||
|
||||
inline void setPrimaryField(bool primary) { setBitmaskAtPos(PRIMARY_POS, primary); }
|
||||
|
||||
inline void setExclusiveState(bool exclusive) { setBitmaskAtPos(EXCLUSIVE_POS, exclusive); }
|
||||
|
||||
inline void setUidField(bool isUid) { setBitmaskAtPos(UID_POS, isUid); }
|
||||
|
||||
// Default value = false
|
||||
inline bool isNested() const { return getValueFromBitmask(NESTED_POS); }
|
||||
|
||||
// Default value = false
|
||||
inline bool isPrimaryField() const { return getValueFromBitmask(PRIMARY_POS); }
|
||||
|
||||
// Default value = false
|
||||
inline bool isExclusiveState() const { return getValueFromBitmask(EXCLUSIVE_POS); }
|
||||
|
||||
// Default value = false
|
||||
inline bool isUidField() const { return getValueFromBitmask(UID_POS); }
|
||||
|
||||
private:
|
||||
inline void setBitmaskAtPos(int pos, bool value) {
|
||||
mBooleanBitmask &= ~(1 << pos); // clear
|
||||
mBooleanBitmask |= (value << pos); // set
|
||||
}
|
||||
|
||||
inline bool getValueFromBitmask(int pos) const {
|
||||
return (mBooleanBitmask >> pos) & 0x1;
|
||||
}
|
||||
|
||||
// This is a bitmask over all annotations stored in boolean form. Because
|
||||
// there are only 4 booleans, just one byte is required.
|
||||
uint8_t mBooleanBitmask = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a log item, or a dimension item (They are essentially the same).
|
||||
*/
|
||||
struct FieldValue {
|
||||
FieldValue() {}
|
||||
FieldValue(const Field& field, const Value& value) : mField(field), mValue(value) {
|
||||
}
|
||||
bool operator==(const FieldValue& that) const {
|
||||
return mField == that.mField && mValue == that.mValue;
|
||||
}
|
||||
bool operator!=(const FieldValue& that) const {
|
||||
return mField != that.mField || mValue != that.mValue;
|
||||
}
|
||||
bool operator<(const FieldValue& that) const {
|
||||
if (mField != that.mField) {
|
||||
return mField < that.mField;
|
||||
}
|
||||
|
||||
if (mValue != that.mValue) {
|
||||
return mValue < that.mValue;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Field mField;
|
||||
Value mValue;
|
||||
Annotations mAnnotations;
|
||||
};
|
||||
|
||||
bool HasPositionANY(const FieldMatcher& matcher);
|
||||
bool HasPositionALL(const FieldMatcher& matcher);
|
||||
|
||||
bool isAttributionUidField(const FieldValue& value);
|
||||
|
||||
/* returns uid if the field is uid field, or -1 if the field is not a uid field */
|
||||
int getUidIfExists(const FieldValue& value);
|
||||
|
||||
void translateFieldMatcher(const FieldMatcher& matcher, std::vector<Matcher>* output);
|
||||
|
||||
bool isAttributionUidField(const Field& field, const Value& value);
|
||||
bool isUidField(const FieldValue& fieldValue);
|
||||
|
||||
bool equalDimensions(const std::vector<Matcher>& dimension_a,
|
||||
const std::vector<Matcher>& dimension_b);
|
||||
|
||||
// Returns true if dimension_a is a subset of dimension_b.
|
||||
bool subsetDimensions(const std::vector<Matcher>& dimension_a,
|
||||
const std::vector<Matcher>& dimension_b);
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,381 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
#define DEBUG false // STOPSHIP if true
|
||||
#include "Log.h"
|
||||
|
||||
#include "HashableDimensionKey.h"
|
||||
#include "FieldValue.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
using android::base::StringPrintf;
|
||||
|
||||
// These constants must be kept in sync with those in StatsDimensionsValue.java
|
||||
const static int STATS_DIMENSIONS_VALUE_STRING_TYPE = 2;
|
||||
const static int STATS_DIMENSIONS_VALUE_INT_TYPE = 3;
|
||||
const static int STATS_DIMENSIONS_VALUE_LONG_TYPE = 4;
|
||||
// const static int STATS_DIMENSIONS_VALUE_BOOL_TYPE = 5; (commented out because
|
||||
// unused -- statsd does not correctly support bool types)
|
||||
const static int STATS_DIMENSIONS_VALUE_FLOAT_TYPE = 6;
|
||||
const static int STATS_DIMENSIONS_VALUE_TUPLE_TYPE = 7;
|
||||
|
||||
/**
|
||||
* Recursive helper function that populates a parent StatsDimensionsValueParcel
|
||||
* with children StatsDimensionsValueParcels.
|
||||
*
|
||||
* \param parent parcel that will be populated with children
|
||||
* \param childDepth depth of children FieldValues
|
||||
* \param childPrefix expected FieldValue prefix of children
|
||||
* \param dims vector of FieldValues stored by HashableDimensionKey
|
||||
* \param index position in dims to start reading children from
|
||||
*/
|
||||
static void populateStatsDimensionsValueParcelChildren(StatsDimensionsValueParcel& parent,
|
||||
int childDepth, int childPrefix,
|
||||
const vector<FieldValue>& dims,
|
||||
size_t& index) {
|
||||
if (childDepth > 2) {
|
||||
ALOGE("Depth > 2 not supported by StatsDimensionsValueParcel.");
|
||||
return;
|
||||
}
|
||||
|
||||
while (index < dims.size()) {
|
||||
const FieldValue& dim = dims[index];
|
||||
int fieldDepth = dim.mField.getDepth();
|
||||
int fieldPrefix = dim.mField.getPrefix(childDepth);
|
||||
|
||||
StatsDimensionsValueParcel child;
|
||||
child.field = dim.mField.getPosAtDepth(childDepth);
|
||||
|
||||
if (fieldDepth == childDepth && fieldPrefix == childPrefix) {
|
||||
switch (dim.mValue.getType()) {
|
||||
case INT:
|
||||
child.valueType = STATS_DIMENSIONS_VALUE_INT_TYPE;
|
||||
child.intValue = dim.mValue.int_value;
|
||||
break;
|
||||
case LONG:
|
||||
child.valueType = STATS_DIMENSIONS_VALUE_LONG_TYPE;
|
||||
child.longValue = dim.mValue.long_value;
|
||||
break;
|
||||
case FLOAT:
|
||||
child.valueType = STATS_DIMENSIONS_VALUE_FLOAT_TYPE;
|
||||
child.floatValue = dim.mValue.float_value;
|
||||
break;
|
||||
case STRING:
|
||||
child.valueType = STATS_DIMENSIONS_VALUE_STRING_TYPE;
|
||||
child.stringValue = dim.mValue.str_value;
|
||||
break;
|
||||
default:
|
||||
ALOGE("Encountered FieldValue with unsupported value type.");
|
||||
break;
|
||||
}
|
||||
index++;
|
||||
parent.tupleValue.push_back(child);
|
||||
} else if (fieldDepth > childDepth && fieldPrefix == childPrefix) {
|
||||
// This FieldValue is not a child of the current parent, but it is
|
||||
// an indirect descendant. Thus, create a direct child of TUPLE_TYPE
|
||||
// and recurse to parcel the indirect descendants.
|
||||
child.valueType = STATS_DIMENSIONS_VALUE_TUPLE_TYPE;
|
||||
populateStatsDimensionsValueParcelChildren(child, childDepth + 1,
|
||||
dim.mField.getPrefix(childDepth + 1), dims,
|
||||
index);
|
||||
parent.tupleValue.push_back(child);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StatsDimensionsValueParcel HashableDimensionKey::toStatsDimensionsValueParcel() const {
|
||||
StatsDimensionsValueParcel root;
|
||||
if (mValues.size() == 0) {
|
||||
return root;
|
||||
}
|
||||
|
||||
root.field = mValues[0].mField.getTag();
|
||||
root.valueType = STATS_DIMENSIONS_VALUE_TUPLE_TYPE;
|
||||
|
||||
// Children of the root correspond to top-level (depth = 0) FieldValues.
|
||||
int childDepth = 0;
|
||||
int childPrefix = 0;
|
||||
size_t index = 0;
|
||||
populateStatsDimensionsValueParcelChildren(root, childDepth, childPrefix, mValues, index);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
android::hash_t hashDimension(const HashableDimensionKey& value) {
|
||||
android::hash_t hash = 0;
|
||||
for (const auto& fieldValue : value.getValues()) {
|
||||
hash = android::JenkinsHashMix(hash, android::hash_type((int)fieldValue.mField.getField()));
|
||||
hash = android::JenkinsHashMix(hash, android::hash_type((int)fieldValue.mField.getTag()));
|
||||
hash = android::JenkinsHashMix(hash, android::hash_type((int)fieldValue.mValue.getType()));
|
||||
switch (fieldValue.mValue.getType()) {
|
||||
case INT:
|
||||
hash = android::JenkinsHashMix(hash,
|
||||
android::hash_type(fieldValue.mValue.int_value));
|
||||
break;
|
||||
case LONG:
|
||||
hash = android::JenkinsHashMix(hash,
|
||||
android::hash_type(fieldValue.mValue.long_value));
|
||||
break;
|
||||
case STRING:
|
||||
hash = android::JenkinsHashMix(hash, static_cast<uint32_t>(std::hash<std::string>()(
|
||||
fieldValue.mValue.str_value)));
|
||||
break;
|
||||
case FLOAT: {
|
||||
hash = android::JenkinsHashMix(hash,
|
||||
android::hash_type(fieldValue.mValue.float_value));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return JenkinsHashWhiten(hash);
|
||||
}
|
||||
|
||||
bool filterValues(const Matcher& matcherField, const vector<FieldValue>& values,
|
||||
FieldValue* output) {
|
||||
for (const auto& value : values) {
|
||||
if (value.mField.matches(matcherField)) {
|
||||
(*output) = value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool filterValues(const vector<Matcher>& matcherFields, const vector<FieldValue>& values,
|
||||
HashableDimensionKey* output) {
|
||||
size_t num_matches = 0;
|
||||
for (const auto& value : values) {
|
||||
for (size_t i = 0; i < matcherFields.size(); ++i) {
|
||||
const auto& matcher = matcherFields[i];
|
||||
if (value.mField.matches(matcher)) {
|
||||
output->addValue(value);
|
||||
output->mutableValue(num_matches)->mField.setTag(value.mField.getTag());
|
||||
output->mutableValue(num_matches)->mField.setField(
|
||||
value.mField.getField() & matcher.mMask);
|
||||
num_matches++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return num_matches > 0;
|
||||
}
|
||||
|
||||
bool filterPrimaryKey(const std::vector<FieldValue>& values, HashableDimensionKey* output) {
|
||||
size_t num_matches = 0;
|
||||
const int32_t simpleFieldMask = 0xff7f0000;
|
||||
const int32_t attributionUidFieldMask = 0xff7f7f7f;
|
||||
for (const auto& value : values) {
|
||||
if (value.mAnnotations.isPrimaryField()) {
|
||||
output->addValue(value);
|
||||
output->mutableValue(num_matches)->mField.setTag(value.mField.getTag());
|
||||
const int32_t mask =
|
||||
isAttributionUidField(value) ? attributionUidFieldMask : simpleFieldMask;
|
||||
output->mutableValue(num_matches)->mField.setField(value.mField.getField() & mask);
|
||||
num_matches++;
|
||||
}
|
||||
}
|
||||
return num_matches > 0;
|
||||
}
|
||||
|
||||
void filterGaugeValues(const std::vector<Matcher>& matcherFields,
|
||||
const std::vector<FieldValue>& values, std::vector<FieldValue>* output) {
|
||||
for (const auto& field : matcherFields) {
|
||||
for (const auto& value : values) {
|
||||
if (value.mField.matches(field)) {
|
||||
output->push_back(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void getDimensionForCondition(const std::vector<FieldValue>& eventValues,
|
||||
const Metric2Condition& links,
|
||||
HashableDimensionKey* conditionDimension) {
|
||||
// Get the dimension first by using dimension from what.
|
||||
filterValues(links.metricFields, eventValues, conditionDimension);
|
||||
|
||||
size_t count = conditionDimension->getValues().size();
|
||||
if (count != links.conditionFields.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
void getDimensionForState(const std::vector<FieldValue>& eventValues, const Metric2State& link,
|
||||
HashableDimensionKey* statePrimaryKey) {
|
||||
// First, get the dimension from the event using the "what" fields from the
|
||||
// MetricStateLinks.
|
||||
filterValues(link.metricFields, eventValues, statePrimaryKey);
|
||||
|
||||
// Then check that the statePrimaryKey size equals the number of state fields
|
||||
size_t count = statePrimaryKey->getValues().size();
|
||||
if (count != link.stateFields.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// For each dimension Value in the statePrimaryKey, set the field and tag
|
||||
// using the state atom fields from MetricStateLinks.
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
statePrimaryKey->mutableValue(i)->mField.setField(link.stateFields[i].mMatcher.getField());
|
||||
statePrimaryKey->mutableValue(i)->mField.setTag(link.stateFields[i].mMatcher.getTag());
|
||||
}
|
||||
}
|
||||
|
||||
bool containsLinkedStateValues(const HashableDimensionKey& whatKey,
|
||||
const HashableDimensionKey& primaryKey,
|
||||
const vector<Metric2State>& stateLinks, const int32_t stateAtomId) {
|
||||
if (whatKey.getValues().size() < primaryKey.getValues().size()) {
|
||||
ALOGE("Contains linked values false: whatKey is too small");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto& primaryValue : primaryKey.getValues()) {
|
||||
bool found = false;
|
||||
for (const auto& whatValue : whatKey.getValues()) {
|
||||
if (linked(stateLinks, stateAtomId, primaryValue.mField, whatValue.mField) &&
|
||||
primaryValue.mValue == whatValue.mValue) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool linked(const vector<Metric2State>& stateLinks, const int32_t stateAtomId,
|
||||
const Field& stateField, const Field& metricField) {
|
||||
for (auto stateLink : stateLinks) {
|
||||
if (stateLink.stateAtomId != stateAtomId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < stateLink.stateFields.size(); i++) {
|
||||
if (stateLink.stateFields[i].mMatcher == stateField &&
|
||||
stateLink.metricFields[i].mMatcher == metricField) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LessThan(const vector<FieldValue>& s1, const vector<FieldValue>& s2) {
|
||||
if (s1.size() != s2.size()) {
|
||||
return s1.size() < s2.size();
|
||||
}
|
||||
|
||||
size_t count = s1.size();
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
if (s1[i] != s2[i]) {
|
||||
return s1[i] < s2[i];
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HashableDimensionKey::operator!=(const HashableDimensionKey& that) const {
|
||||
return !((*this) == that);
|
||||
}
|
||||
|
||||
bool HashableDimensionKey::operator==(const HashableDimensionKey& that) const {
|
||||
if (mValues.size() != that.getValues().size()) {
|
||||
return false;
|
||||
}
|
||||
size_t count = mValues.size();
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
if (mValues[i] != (that.getValues())[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
bool HashableDimensionKey::operator<(const HashableDimensionKey& that) const {
|
||||
return LessThan(getValues(), that.getValues());
|
||||
};
|
||||
|
||||
bool HashableDimensionKey::contains(const HashableDimensionKey& that) const {
|
||||
if (mValues.size() < that.getValues().size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mValues.size() == that.getValues().size()) {
|
||||
return (*this) == that;
|
||||
}
|
||||
|
||||
for (const auto& value : that.getValues()) {
|
||||
bool found = false;
|
||||
for (const auto& myValue : mValues) {
|
||||
if (value.mField == myValue.mField && value.mValue == myValue.mValue) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
string HashableDimensionKey::toString() const {
|
||||
std::string output;
|
||||
for (const auto& value : mValues) {
|
||||
output += StringPrintf("(%d)%#x->%s ", value.mField.getTag(), value.mField.getField(),
|
||||
value.mValue.toString().c_str());
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
bool MetricDimensionKey::operator==(const MetricDimensionKey& that) const {
|
||||
return mDimensionKeyInWhat == that.getDimensionKeyInWhat() &&
|
||||
mStateValuesKey == that.getStateValuesKey();
|
||||
};
|
||||
|
||||
string MetricDimensionKey::toString() const {
|
||||
return mDimensionKeyInWhat.toString() + mStateValuesKey.toString();
|
||||
}
|
||||
|
||||
bool MetricDimensionKey::operator<(const MetricDimensionKey& that) const {
|
||||
if (mDimensionKeyInWhat < that.getDimensionKeyInWhat()) {
|
||||
return true;
|
||||
} else if (that.getDimensionKeyInWhat() < mDimensionKeyInWhat) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return mStateValuesKey < that.getStateValuesKey();
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,238 +0,0 @@
|
||||
/*
|
||||
* 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 <aidl/android/os/StatsDimensionsValueParcel.h>
|
||||
#include <utils/JenkinsHash.h>
|
||||
#include <vector>
|
||||
#include "android-base/stringprintf.h"
|
||||
#include "FieldValue.h"
|
||||
#include "logd/LogEvent.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
using ::aidl::android::os::StatsDimensionsValueParcel;
|
||||
|
||||
struct Metric2Condition {
|
||||
int64_t conditionId;
|
||||
std::vector<Matcher> metricFields;
|
||||
std::vector<Matcher> conditionFields;
|
||||
};
|
||||
|
||||
struct Metric2State {
|
||||
int32_t stateAtomId;
|
||||
std::vector<Matcher> metricFields;
|
||||
std::vector<Matcher> stateFields;
|
||||
};
|
||||
|
||||
class HashableDimensionKey {
|
||||
public:
|
||||
explicit HashableDimensionKey(const std::vector<FieldValue>& values) {
|
||||
mValues = values;
|
||||
}
|
||||
|
||||
HashableDimensionKey() {};
|
||||
|
||||
HashableDimensionKey(const HashableDimensionKey& that) : mValues(that.getValues()){};
|
||||
|
||||
inline void addValue(const FieldValue& value) {
|
||||
mValues.push_back(value);
|
||||
}
|
||||
|
||||
inline const std::vector<FieldValue>& getValues() const {
|
||||
return mValues;
|
||||
}
|
||||
|
||||
inline std::vector<FieldValue>* mutableValues() {
|
||||
return &mValues;
|
||||
}
|
||||
|
||||
inline FieldValue* mutableValue(size_t i) {
|
||||
if (i >= 0 && i < mValues.size()) {
|
||||
return &(mValues[i]);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
StatsDimensionsValueParcel toStatsDimensionsValueParcel() const;
|
||||
|
||||
std::string toString() const;
|
||||
|
||||
bool operator!=(const HashableDimensionKey& that) const;
|
||||
|
||||
bool operator==(const HashableDimensionKey& that) const;
|
||||
|
||||
bool operator<(const HashableDimensionKey& that) const;
|
||||
|
||||
bool contains(const HashableDimensionKey& that) const;
|
||||
|
||||
private:
|
||||
std::vector<FieldValue> mValues;
|
||||
};
|
||||
|
||||
class MetricDimensionKey {
|
||||
public:
|
||||
explicit MetricDimensionKey(const HashableDimensionKey& dimensionKeyInWhat,
|
||||
const HashableDimensionKey& stateValuesKey)
|
||||
: mDimensionKeyInWhat(dimensionKeyInWhat), mStateValuesKey(stateValuesKey){};
|
||||
|
||||
MetricDimensionKey(){};
|
||||
|
||||
MetricDimensionKey(const MetricDimensionKey& that)
|
||||
: mDimensionKeyInWhat(that.getDimensionKeyInWhat()),
|
||||
mStateValuesKey(that.getStateValuesKey()){};
|
||||
|
||||
MetricDimensionKey& operator=(const MetricDimensionKey& from) = default;
|
||||
|
||||
std::string toString() const;
|
||||
|
||||
inline const HashableDimensionKey& getDimensionKeyInWhat() const {
|
||||
return mDimensionKeyInWhat;
|
||||
}
|
||||
|
||||
inline const HashableDimensionKey& getStateValuesKey() const {
|
||||
return mStateValuesKey;
|
||||
}
|
||||
|
||||
inline HashableDimensionKey* getMutableStateValuesKey() {
|
||||
return &mStateValuesKey;
|
||||
}
|
||||
|
||||
inline void setStateValuesKey(const HashableDimensionKey& key) {
|
||||
mStateValuesKey = key;
|
||||
}
|
||||
|
||||
bool hasStateValuesKey() const {
|
||||
return mStateValuesKey.getValues().size() > 0;
|
||||
}
|
||||
|
||||
bool operator==(const MetricDimensionKey& that) const;
|
||||
|
||||
bool operator<(const MetricDimensionKey& that) const;
|
||||
|
||||
private:
|
||||
HashableDimensionKey mDimensionKeyInWhat;
|
||||
HashableDimensionKey mStateValuesKey;
|
||||
};
|
||||
|
||||
android::hash_t hashDimension(const HashableDimensionKey& key);
|
||||
|
||||
/**
|
||||
* Returns true if a FieldValue field matches the matcher field.
|
||||
* The value of the FieldValue is output.
|
||||
*/
|
||||
bool filterValues(const Matcher& matcherField, const std::vector<FieldValue>& values,
|
||||
FieldValue* output);
|
||||
|
||||
/**
|
||||
* Creating HashableDimensionKeys from FieldValues using matcher.
|
||||
*
|
||||
* This function may make modifications to the Field if the matcher has Position=FIRST,LAST or ALL
|
||||
* in it. This is because: for example, when we create dimension from last uid in attribution chain,
|
||||
* In one event, uid 1000 is at position 5 and it's the last
|
||||
* In another event, uid 1000 is at position 6, and it's the last
|
||||
* these 2 events should be mapped to the same dimension. So we will remove the original position
|
||||
* from the dimension key for the uid field (by applying 0x80 bit mask).
|
||||
*/
|
||||
bool filterValues(const std::vector<Matcher>& matcherFields, const std::vector<FieldValue>& values,
|
||||
HashableDimensionKey* output);
|
||||
|
||||
/**
|
||||
* Creating HashableDimensionKeys from State Primary Keys in FieldValues.
|
||||
*
|
||||
* This function may make modifications to the Field if the matcher has Position=FIRST,LAST or ALL
|
||||
* in it. This is because: for example, when we create dimension from last uid in attribution chain,
|
||||
* In one event, uid 1000 is at position 5 and it's the last
|
||||
* In another event, uid 1000 is at position 6, and it's the last
|
||||
* these 2 events should be mapped to the same dimension. So we will remove the original position
|
||||
* from the dimension key for the uid field (by applying 0x80 bit mask).
|
||||
*/
|
||||
bool filterPrimaryKey(const std::vector<FieldValue>& values, HashableDimensionKey* output);
|
||||
|
||||
/**
|
||||
* Filter the values from FieldValues using the matchers.
|
||||
*
|
||||
* In contrast to the above function, this function will not do any modification to the original
|
||||
* data. Considering it as taking a snapshot on the atom event.
|
||||
*/
|
||||
void filterGaugeValues(const std::vector<Matcher>& matchers, const std::vector<FieldValue>& values,
|
||||
std::vector<FieldValue>* output);
|
||||
|
||||
void getDimensionForCondition(const std::vector<FieldValue>& eventValues,
|
||||
const Metric2Condition& links,
|
||||
HashableDimensionKey* conditionDimension);
|
||||
|
||||
/**
|
||||
* Get dimension values using metric's "what" fields and fill statePrimaryKey's
|
||||
* mField information using "state" fields.
|
||||
*/
|
||||
void getDimensionForState(const std::vector<FieldValue>& eventValues, const Metric2State& link,
|
||||
HashableDimensionKey* statePrimaryKey);
|
||||
|
||||
/**
|
||||
* Returns true if the primaryKey values are a subset of the whatKey values.
|
||||
* The values from the primaryKey come from the state atom, so we need to
|
||||
* check that a link exists between the state atom field and what atom field.
|
||||
*
|
||||
* Example:
|
||||
* whatKey = [Atom: 10, {uid: 1005, wakelock_name: "compose"}]
|
||||
* statePrimaryKey = [Atom: 27, {uid: 1005}]
|
||||
* Returns true IF one of the Metric2State links Atom 10's uid to Atom 27's uid
|
||||
*
|
||||
* Example:
|
||||
* whatKey = [Atom: 10, {uid: 1005, wakelock_name: "compose"}]
|
||||
* statePrimaryKey = [Atom: 59, {uid: 1005, package_name: "system"}]
|
||||
* Returns false
|
||||
*/
|
||||
bool containsLinkedStateValues(const HashableDimensionKey& whatKey,
|
||||
const HashableDimensionKey& primaryKey,
|
||||
const std::vector<Metric2State>& stateLinks,
|
||||
const int32_t stateAtomId);
|
||||
|
||||
/**
|
||||
* Returns true if there is a Metric2State link that links the stateField and
|
||||
* the metricField (they are equal fields from different atoms).
|
||||
*/
|
||||
bool linked(const std::vector<Metric2State>& stateLinks, const int32_t stateAtomId,
|
||||
const Field& stateField, const Field& metricField);
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
|
||||
namespace std {
|
||||
|
||||
using android::os::statsd::HashableDimensionKey;
|
||||
using android::os::statsd::MetricDimensionKey;
|
||||
|
||||
template <>
|
||||
struct hash<HashableDimensionKey> {
|
||||
std::size_t operator()(const HashableDimensionKey& key) const {
|
||||
return hashDimension(key);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct hash<MetricDimensionKey> {
|
||||
std::size_t operator()(const MetricDimensionKey& key) const {
|
||||
android::hash_t hash = hashDimension(key.getDimensionKeyInWhat());
|
||||
hash = android::JenkinsHashMix(hash, hashDimension(key.getStateValuesKey()));
|
||||
return android::JenkinsHashWhiten(hash);
|
||||
}
|
||||
};
|
||||
} // namespace std
|
||||
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file must be included at the top of the file. Other header files
|
||||
* occasionally include log.h, and if LOG_TAG isn't set when that happens
|
||||
* we'll get a preprocesser error when we try to define it here.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#define LOG_TAG "statsd"
|
||||
|
||||
#include <log/log.h>
|
||||
|
||||
// Use the local value to turn on/off debug logs instead of using log.tag. properties.
|
||||
// The advantage is that in production compiler can remove the logging code if the local
|
||||
// DEBUG/VERBOSE is false.
|
||||
#define VLOG(...) \
|
||||
if (DEBUG) ALOGD(__VA_ARGS__);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,370 +0,0 @@
|
||||
/*
|
||||
* 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 <gtest/gtest_prod.h>
|
||||
#include "config/ConfigListener.h"
|
||||
#include "logd/LogEvent.h"
|
||||
#include "metrics/MetricsManager.h"
|
||||
#include "packages/UidMap.h"
|
||||
#include "external/StatsPullerManager.h"
|
||||
|
||||
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
|
||||
#include "frameworks/base/cmds/statsd/src/statsd_metadata.pb.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
|
||||
class StatsLogProcessor : public ConfigListener, public virtual PackageInfoListener {
|
||||
public:
|
||||
StatsLogProcessor(const sp<UidMap>& uidMap, const sp<StatsPullerManager>& pullerManager,
|
||||
const sp<AlarmMonitor>& anomalyAlarmMonitor,
|
||||
const sp<AlarmMonitor>& subscriberTriggerAlarmMonitor,
|
||||
const int64_t timeBaseNs,
|
||||
const std::function<bool(const ConfigKey&)>& sendBroadcast,
|
||||
const std::function<bool(const int&,
|
||||
const vector<int64_t>&)>& sendActivationBroadcast);
|
||||
virtual ~StatsLogProcessor();
|
||||
|
||||
void OnLogEvent(LogEvent* event);
|
||||
|
||||
void OnConfigUpdated(const int64_t timestampNs, const ConfigKey& key,
|
||||
const StatsdConfig& config);
|
||||
void OnConfigRemoved(const ConfigKey& key);
|
||||
|
||||
size_t GetMetricsSize(const ConfigKey& key) const;
|
||||
|
||||
void GetActiveConfigs(const int uid, vector<int64_t>& outActiveConfigs);
|
||||
|
||||
void onDumpReport(const ConfigKey& key, const int64_t dumpTimeNs,
|
||||
const bool include_current_partial_bucket, const bool erase_data,
|
||||
const DumpReportReason dumpReportReason,
|
||||
const DumpLatency dumpLatency,
|
||||
vector<uint8_t>* outData);
|
||||
void onDumpReport(const ConfigKey& key, const int64_t dumpTimeNs,
|
||||
const bool include_current_partial_bucket, const bool erase_data,
|
||||
const DumpReportReason dumpReportReason,
|
||||
const DumpLatency dumpLatency,
|
||||
ProtoOutputStream* proto);
|
||||
|
||||
/* Tells MetricsManager that the alarms in alarmSet have fired. Modifies periodic alarmSet. */
|
||||
void onPeriodicAlarmFired(
|
||||
const int64_t& timestampNs,
|
||||
unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>> alarmSet);
|
||||
|
||||
/* Flushes data to disk. Data on memory will be gone after written to disk. */
|
||||
void WriteDataToDisk(const DumpReportReason dumpReportReason,
|
||||
const DumpLatency dumpLatency);
|
||||
|
||||
/* Persist configs containing metrics with active activations to disk. */
|
||||
void SaveActiveConfigsToDisk(int64_t currentTimeNs);
|
||||
|
||||
/* Writes the current active status/ttl for all configs and metrics to ProtoOutputStream. */
|
||||
void WriteActiveConfigsToProtoOutputStream(
|
||||
int64_t currentTimeNs, const DumpReportReason reason, ProtoOutputStream* proto);
|
||||
|
||||
/* Load configs containing metrics with active activations from disk. */
|
||||
void LoadActiveConfigsFromDisk();
|
||||
|
||||
/* Persist metadata for configs and metrics to disk. */
|
||||
void SaveMetadataToDisk(int64_t currentWallClockTimeNs, int64_t systemElapsedTimeNs);
|
||||
|
||||
/* Writes the statsd metadata for all configs and metrics to StatsMetadataList. */
|
||||
void WriteMetadataToProto(int64_t currentWallClockTimeNs,
|
||||
int64_t systemElapsedTimeNs,
|
||||
metadata::StatsMetadataList* metadataList);
|
||||
|
||||
/* Load stats metadata for configs and metrics from disk. */
|
||||
void LoadMetadataFromDisk(int64_t currentWallClockTimeNs,
|
||||
int64_t systemElapsedTimeNs);
|
||||
|
||||
/* Sets the metadata for all configs and metrics */
|
||||
void SetMetadataState(const metadata::StatsMetadataList& statsMetadataList,
|
||||
int64_t currentWallClockTimeNs,
|
||||
int64_t systemElapsedTimeNs);
|
||||
|
||||
/* Sets the active status/ttl for all configs and metrics to the status in ActiveConfigList. */
|
||||
void SetConfigsActiveState(const ActiveConfigList& activeConfigList, int64_t currentTimeNs);
|
||||
|
||||
/* Notify all MetricsManagers of app upgrades */
|
||||
void notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk, const int uid,
|
||||
const int64_t version) override;
|
||||
|
||||
/* Notify all MetricsManagers of app removals */
|
||||
void notifyAppRemoved(const int64_t& eventTimeNs, const string& apk, const int uid) override;
|
||||
|
||||
/* Notify all MetricsManagers of uid map snapshots received */
|
||||
void onUidMapReceived(const int64_t& eventTimeNs) override;
|
||||
|
||||
/* Notify all metrics managers of boot completed
|
||||
* This will force a bucket split when the boot is finished.
|
||||
*/
|
||||
void onStatsdInitCompleted(const int64_t& elapsedTimeNs);
|
||||
|
||||
// Reset all configs.
|
||||
void resetConfigs();
|
||||
|
||||
inline sp<UidMap> getUidMap() {
|
||||
return mUidMap;
|
||||
}
|
||||
|
||||
void dumpStates(int outFd, bool verbose);
|
||||
|
||||
void informPullAlarmFired(const int64_t timestampNs);
|
||||
|
||||
int64_t getLastReportTimeNs(const ConfigKey& key);
|
||||
|
||||
inline void setPrintLogs(bool enabled) {
|
||||
std::lock_guard<std::mutex> lock(mMetricsMutex);
|
||||
mPrintAllLogs = enabled;
|
||||
}
|
||||
|
||||
// Add a specific config key to the possible configs to dump ASAP.
|
||||
void noteOnDiskData(const ConfigKey& key);
|
||||
|
||||
void setAnomalyAlarm(const int64_t timeMillis);
|
||||
|
||||
void cancelAnomalyAlarm();
|
||||
|
||||
private:
|
||||
// For testing only.
|
||||
inline sp<AlarmMonitor> getAnomalyAlarmMonitor() const {
|
||||
return mAnomalyAlarmMonitor;
|
||||
}
|
||||
|
||||
inline sp<AlarmMonitor> getPeriodicAlarmMonitor() const {
|
||||
return mPeriodicAlarmMonitor;
|
||||
}
|
||||
|
||||
mutable mutex mMetricsMutex;
|
||||
|
||||
// Guards mNextAnomalyAlarmTime. A separate mutex is needed because alarms are set/cancelled
|
||||
// in the onLogEvent code path, which is locked by mMetricsMutex.
|
||||
// DO NOT acquire mMetricsMutex while holding mAnomalyAlarmMutex. This can lead to a deadlock.
|
||||
mutable mutex mAnomalyAlarmMutex;
|
||||
|
||||
std::unordered_map<ConfigKey, sp<MetricsManager>> mMetricsManagers;
|
||||
|
||||
std::unordered_map<ConfigKey, int64_t> mLastBroadcastTimes;
|
||||
|
||||
// Last time we sent a broadcast to this uid that the active configs had changed.
|
||||
std::unordered_map<int, int64_t> mLastActivationBroadcastTimes;
|
||||
|
||||
// Tracks when we last checked the bytes consumed for each config key.
|
||||
std::unordered_map<ConfigKey, int64_t> mLastByteSizeTimes;
|
||||
|
||||
// Tracks which config keys has metric reports on disk
|
||||
std::set<ConfigKey> mOnDiskDataConfigs;
|
||||
|
||||
sp<UidMap> mUidMap; // Reference to the UidMap to lookup app name and version for each uid.
|
||||
|
||||
sp<StatsPullerManager> mPullerManager; // Reference to StatsPullerManager
|
||||
|
||||
sp<AlarmMonitor> mAnomalyAlarmMonitor;
|
||||
|
||||
sp<AlarmMonitor> mPeriodicAlarmMonitor;
|
||||
|
||||
void OnLogEvent(LogEvent* event, int64_t elapsedRealtimeNs);
|
||||
|
||||
void resetIfConfigTtlExpiredLocked(const int64_t timestampNs);
|
||||
|
||||
void OnConfigUpdatedLocked(
|
||||
const int64_t currentTimestampNs, const ConfigKey& key, const StatsdConfig& config);
|
||||
|
||||
void GetActiveConfigsLocked(const int uid, vector<int64_t>& outActiveConfigs);
|
||||
|
||||
void WriteActiveConfigsToProtoOutputStreamLocked(
|
||||
int64_t currentTimeNs, const DumpReportReason reason, ProtoOutputStream* proto);
|
||||
|
||||
void SetConfigsActiveStateLocked(const ActiveConfigList& activeConfigList,
|
||||
int64_t currentTimeNs);
|
||||
|
||||
void SetMetadataStateLocked(const metadata::StatsMetadataList& statsMetadataList,
|
||||
int64_t currentWallClockTimeNs,
|
||||
int64_t systemElapsedTimeNs);
|
||||
|
||||
void WriteMetadataToProtoLocked(int64_t currentWallClockTimeNs,
|
||||
int64_t systemElapsedTimeNs,
|
||||
metadata::StatsMetadataList* metadataList);
|
||||
|
||||
void WriteDataToDiskLocked(const DumpReportReason dumpReportReason,
|
||||
const DumpLatency dumpLatency);
|
||||
|
||||
void WriteDataToDiskLocked(const ConfigKey& key, const int64_t timestampNs,
|
||||
const DumpReportReason dumpReportReason,
|
||||
const DumpLatency dumpLatency);
|
||||
|
||||
void onConfigMetricsReportLocked(
|
||||
const ConfigKey& key, const int64_t dumpTimeStampNs,
|
||||
const bool include_current_partial_bucket, const bool erase_data,
|
||||
const DumpReportReason dumpReportReason, const DumpLatency dumpLatency,
|
||||
/*if dataSavedToDisk is true, it indicates the caller will write the data to disk
|
||||
(e.g., before reboot). So no need to further persist local history.*/
|
||||
const bool dataSavedToDisk, vector<uint8_t>* proto);
|
||||
|
||||
/* Check if we should send a broadcast if approaching memory limits and if we're over, we
|
||||
* actually delete the data. */
|
||||
void flushIfNecessaryLocked(const ConfigKey& key, MetricsManager& metricsManager);
|
||||
|
||||
// Maps the isolated uid in the log event to host uid if the log event contains uid fields.
|
||||
void mapIsolatedUidToHostUidIfNecessaryLocked(LogEvent* event) const;
|
||||
|
||||
// Handler over the isolated uid change event.
|
||||
void onIsolatedUidChangedEventLocked(const LogEvent& event);
|
||||
|
||||
// Handler over the binary push state changed event.
|
||||
void onBinaryPushStateChangedEventLocked(LogEvent* event);
|
||||
|
||||
// Handler over the watchdog rollback occurred event.
|
||||
void onWatchdogRollbackOccurredLocked(LogEvent* event);
|
||||
|
||||
// Updates train info on disk based on binary push state changed info and
|
||||
// write disk info into parameters.
|
||||
void getAndUpdateTrainInfoOnDisk(bool is_rollback, InstallTrainInfo* trainInfoIn);
|
||||
|
||||
// Gets experiment ids on disk for associated train and updates them
|
||||
// depending on rollback type. Then writes them back to disk and returns
|
||||
// them.
|
||||
std::vector<int64_t> processWatchdogRollbackOccurred(const int32_t rollbackTypeIn,
|
||||
const string& packageName);
|
||||
|
||||
// Reset all configs.
|
||||
void resetConfigsLocked(const int64_t timestampNs);
|
||||
// Reset the specified configs.
|
||||
void resetConfigsLocked(const int64_t timestampNs, const std::vector<ConfigKey>& configs);
|
||||
|
||||
// An anomaly alarm should have fired.
|
||||
// Check with anomaly alarm manager to find the alarms and process the result.
|
||||
void informAnomalyAlarmFiredLocked(const int64_t elapsedTimeMillis);
|
||||
|
||||
/* Tells MetricsManager that the alarms in alarmSet have fired. Modifies anomaly alarmSet. */
|
||||
void processFiredAnomalyAlarmsLocked(
|
||||
const int64_t& timestampNs,
|
||||
unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>> alarmSet);
|
||||
|
||||
// Function used to send a broadcast so that receiver for the config key can call getData
|
||||
// to retrieve the stored data.
|
||||
std::function<bool(const ConfigKey& key)> mSendBroadcast;
|
||||
|
||||
// Function used to send a broadcast so that receiver can be notified of which configs
|
||||
// are currently active.
|
||||
std::function<bool(const int& uid, const vector<int64_t>& configIds)> mSendActivationBroadcast;
|
||||
|
||||
const int64_t mTimeBaseNs;
|
||||
|
||||
// Largest timestamp of the events that we have processed.
|
||||
int64_t mLargestTimestampSeen = 0;
|
||||
|
||||
int64_t mLastTimestampSeen = 0;
|
||||
|
||||
int64_t mLastPullerCacheClearTimeSec = 0;
|
||||
|
||||
// Last time we wrote data to disk.
|
||||
int64_t mLastWriteTimeNs = 0;
|
||||
|
||||
// Last time we wrote active metrics to disk.
|
||||
int64_t mLastActiveMetricsWriteNs = 0;
|
||||
|
||||
//Last time we wrote metadata to disk.
|
||||
int64_t mLastMetadataWriteNs = 0;
|
||||
|
||||
// The time for the next anomaly alarm for alerts.
|
||||
int64_t mNextAnomalyAlarmTime = 0;
|
||||
|
||||
bool mPrintAllLogs = false;
|
||||
|
||||
FRIEND_TEST(StatsLogProcessorTest, TestOutOfOrderLogs);
|
||||
FRIEND_TEST(StatsLogProcessorTest, TestRateLimitByteSize);
|
||||
FRIEND_TEST(StatsLogProcessorTest, TestRateLimitBroadcast);
|
||||
FRIEND_TEST(StatsLogProcessorTest, TestDropWhenByteSizeTooLarge);
|
||||
FRIEND_TEST(StatsLogProcessorTest, InvalidConfigRemoved);
|
||||
FRIEND_TEST(StatsLogProcessorTest, TestActiveConfigMetricDiskWriteRead);
|
||||
FRIEND_TEST(StatsLogProcessorTest, TestActivationOnBoot);
|
||||
FRIEND_TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivations);
|
||||
FRIEND_TEST(StatsLogProcessorTest,
|
||||
TestActivationOnBootMultipleActivationsDifferentActivationTypes);
|
||||
FRIEND_TEST(StatsLogProcessorTest, TestActivationsPersistAcrossSystemServerRestart);
|
||||
|
||||
FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForSumDuration1);
|
||||
FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForSumDuration2);
|
||||
FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForSumDuration3);
|
||||
FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForMaxDuration1);
|
||||
FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForMaxDuration2);
|
||||
FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForMaxDuration3);
|
||||
FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks1);
|
||||
FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks2);
|
||||
FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSliceByFirstUid);
|
||||
FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSliceByChain);
|
||||
FRIEND_TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent);
|
||||
FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvents);
|
||||
FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvent_LateAlarm);
|
||||
FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsWithActivation);
|
||||
FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsNoCondition);
|
||||
FRIEND_TEST(GaugeMetricE2eTest, TestConditionChangeToTrueSamplePulledEvents);
|
||||
|
||||
FRIEND_TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_single_bucket);
|
||||
FRIEND_TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_multiple_buckets);
|
||||
FRIEND_TEST(AnomalyDetectionE2eTest, TestCountMetric_save_refractory_to_disk_no_data_written);
|
||||
FRIEND_TEST(AnomalyDetectionE2eTest, TestCountMetric_save_refractory_to_disk);
|
||||
FRIEND_TEST(AnomalyDetectionE2eTest, TestCountMetric_load_refractory_from_disk);
|
||||
FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_single_bucket);
|
||||
FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_multiple_buckets);
|
||||
FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_long_refractory_period);
|
||||
|
||||
FRIEND_TEST(AlarmE2eTest, TestMultipleAlarms);
|
||||
FRIEND_TEST(ConfigTtlE2eTest, TestCountMetric);
|
||||
FRIEND_TEST(MetricActivationE2eTest, TestCountMetric);
|
||||
FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation);
|
||||
FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations);
|
||||
FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithSameDeactivation);
|
||||
FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations);
|
||||
|
||||
FRIEND_TEST(CountMetricE2eTest, TestInitialConditionChanges);
|
||||
FRIEND_TEST(CountMetricE2eTest, TestSlicedState);
|
||||
FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithMap);
|
||||
FRIEND_TEST(CountMetricE2eTest, TestMultipleSlicedStates);
|
||||
FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithPrimaryFields);
|
||||
|
||||
FRIEND_TEST(DurationMetricE2eTest, TestOneBucket);
|
||||
FRIEND_TEST(DurationMetricE2eTest, TestTwoBuckets);
|
||||
FRIEND_TEST(DurationMetricE2eTest, TestWithActivation);
|
||||
FRIEND_TEST(DurationMetricE2eTest, TestWithCondition);
|
||||
FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedCondition);
|
||||
FRIEND_TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition);
|
||||
FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedState);
|
||||
FRIEND_TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState);
|
||||
FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStateMapped);
|
||||
FRIEND_TEST(DurationMetricE2eTest, TestSlicedStatePrimaryFieldsNotSubsetDimInWhat);
|
||||
FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset);
|
||||
|
||||
FRIEND_TEST(ValueMetricE2eTest, TestInitialConditionChanges);
|
||||
FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents);
|
||||
FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm);
|
||||
FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_WithActivation);
|
||||
FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState);
|
||||
FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions);
|
||||
FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithIncorrectDimensions);
|
||||
};
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,416 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef STATS_SERVICE_H
|
||||
#define STATS_SERVICE_H
|
||||
|
||||
#include <aidl/android/os/BnStatsd.h>
|
||||
#include <aidl/android/os/IPendingIntentRef.h>
|
||||
#include <aidl/android/os/IPullAtomCallback.h>
|
||||
#include <gtest/gtest_prod.h>
|
||||
#include <utils/Looper.h>
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include "StatsLogProcessor.h"
|
||||
#include "anomaly/AlarmMonitor.h"
|
||||
#include "config/ConfigManager.h"
|
||||
#include "external/StatsPullerManager.h"
|
||||
#include "logd/LogEventQueue.h"
|
||||
#include "packages/UidMap.h"
|
||||
#include "shell/ShellSubscriber.h"
|
||||
#include "statscompanion_util.h"
|
||||
#include "utils/MultiConditionTrigger.h"
|
||||
|
||||
using namespace android;
|
||||
using namespace android::os;
|
||||
using namespace std;
|
||||
|
||||
using Status = ::ndk::ScopedAStatus;
|
||||
using aidl::android::os::BnStatsd;
|
||||
using aidl::android::os::IPendingIntentRef;
|
||||
using aidl::android::os::IPullAtomCallback;
|
||||
using ::ndk::ScopedAIBinder_DeathRecipient;
|
||||
using ::ndk::ScopedFileDescriptor;
|
||||
using std::shared_ptr;
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
class StatsService : public BnStatsd {
|
||||
public:
|
||||
StatsService(const sp<Looper>& handlerLooper, std::shared_ptr<LogEventQueue> queue);
|
||||
virtual ~StatsService();
|
||||
|
||||
/** The anomaly alarm registered with AlarmManager won't be updated by less than this. */
|
||||
const uint32_t MIN_DIFF_TO_UPDATE_REGISTERED_ALARM_SECS = 5;
|
||||
|
||||
virtual status_t dump(int fd, const char** args, uint32_t numArgs) override;
|
||||
virtual status_t handleShellCommand(int in, int out, int err, const char** argv,
|
||||
uint32_t argc) override;
|
||||
|
||||
virtual Status systemRunning();
|
||||
virtual Status statsCompanionReady();
|
||||
virtual Status bootCompleted();
|
||||
virtual Status informPollAlarmFired();
|
||||
virtual Status informAlarmForSubscriberTriggeringFired();
|
||||
|
||||
virtual Status informAllUidData(const ScopedFileDescriptor& fd);
|
||||
virtual Status informOnePackage(const string& app, int32_t uid, int64_t version,
|
||||
const string& versionString, const string& installer);
|
||||
virtual Status informOnePackageRemoved(const string& app, int32_t uid);
|
||||
virtual Status informDeviceShutdown();
|
||||
|
||||
/**
|
||||
* Called right before we start processing events.
|
||||
*/
|
||||
void Startup();
|
||||
|
||||
/**
|
||||
* Called when terminiation signal received.
|
||||
*/
|
||||
void Terminate();
|
||||
|
||||
/**
|
||||
* Test ONLY interface. In real world, StatsService reads from LogEventQueue.
|
||||
*/
|
||||
virtual void OnLogEvent(LogEvent* event);
|
||||
|
||||
/**
|
||||
* Binder call for clients to request data for this configuration key.
|
||||
*/
|
||||
virtual Status getData(int64_t key,
|
||||
const int32_t callingUid,
|
||||
vector<int8_t>* output) override;
|
||||
|
||||
|
||||
/**
|
||||
* Binder call for clients to get metadata across all configs in statsd.
|
||||
*/
|
||||
virtual Status getMetadata(vector<int8_t>* output) override;
|
||||
|
||||
|
||||
/**
|
||||
* Binder call to let clients send a configuration and indicate they're interested when they
|
||||
* should requestData for this configuration.
|
||||
*/
|
||||
virtual Status addConfiguration(int64_t key,
|
||||
const vector<int8_t>& config,
|
||||
const int32_t callingUid) override;
|
||||
|
||||
/**
|
||||
* Binder call to let clients register the data fetch operation for a configuration.
|
||||
*/
|
||||
virtual Status setDataFetchOperation(int64_t key,
|
||||
const shared_ptr<IPendingIntentRef>& pir,
|
||||
const int32_t callingUid) override;
|
||||
|
||||
/**
|
||||
* Binder call to remove the data fetch operation for the specified config key.
|
||||
*/
|
||||
virtual Status removeDataFetchOperation(int64_t key,
|
||||
const int32_t callingUid) override;
|
||||
|
||||
/**
|
||||
* Binder call to let clients register the active configs changed operation.
|
||||
*/
|
||||
virtual Status setActiveConfigsChangedOperation(const shared_ptr<IPendingIntentRef>& pir,
|
||||
const int32_t callingUid,
|
||||
vector<int64_t>* output) override;
|
||||
|
||||
/**
|
||||
* Binder call to remove the active configs changed operation for the specified package..
|
||||
*/
|
||||
virtual Status removeActiveConfigsChangedOperation(const int32_t callingUid) override;
|
||||
/**
|
||||
* Binder call to allow clients to remove the specified configuration.
|
||||
*/
|
||||
virtual Status removeConfiguration(int64_t key,
|
||||
const int32_t callingUid) override;
|
||||
|
||||
/**
|
||||
* Binder call to associate the given config's subscriberId with the given pendingIntentRef.
|
||||
*/
|
||||
virtual Status setBroadcastSubscriber(int64_t configId,
|
||||
int64_t subscriberId,
|
||||
const shared_ptr<IPendingIntentRef>& pir,
|
||||
const int32_t callingUid) override;
|
||||
|
||||
/**
|
||||
* Binder call to unassociate the given config's subscriberId with any pendingIntentRef.
|
||||
*/
|
||||
virtual Status unsetBroadcastSubscriber(int64_t configId,
|
||||
int64_t subscriberId,
|
||||
const int32_t callingUid) override;
|
||||
|
||||
/** Inform statsCompanion that statsd is ready. */
|
||||
virtual void sayHiToStatsCompanion();
|
||||
|
||||
/**
|
||||
* Binder call to notify statsd that all pullers from boot have been registered.
|
||||
*/
|
||||
virtual Status allPullersFromBootRegistered();
|
||||
|
||||
/**
|
||||
* Binder call to register a callback function for a pulled atom.
|
||||
*/
|
||||
virtual Status registerPullAtomCallback(
|
||||
int32_t uid, int32_t atomTag, int64_t coolDownMillis, int64_t timeoutMillis,
|
||||
const std::vector<int32_t>& additiveFields,
|
||||
const shared_ptr<IPullAtomCallback>& pullerCallback) override;
|
||||
|
||||
/**
|
||||
* Binder call to register a callback function for a pulled atom.
|
||||
*/
|
||||
virtual Status registerNativePullAtomCallback(
|
||||
int32_t atomTag, int64_t coolDownMillis, int64_t timeoutMillis,
|
||||
const std::vector<int32_t>& additiveFields,
|
||||
const shared_ptr<IPullAtomCallback>& pullerCallback) override;
|
||||
|
||||
/**
|
||||
* Binder call to unregister any existing callback for the given uid and atom.
|
||||
*/
|
||||
virtual Status unregisterPullAtomCallback(int32_t uid, int32_t atomTag) override;
|
||||
|
||||
/**
|
||||
* Binder call to unregister any existing callback for the given atom and calling uid.
|
||||
*/
|
||||
virtual Status unregisterNativePullAtomCallback(int32_t atomTag) override;
|
||||
|
||||
/**
|
||||
* Binder call to get registered experiment IDs.
|
||||
*/
|
||||
virtual Status getRegisteredExperimentIds(std::vector<int64_t>* expIdsOut);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Load system properties at init.
|
||||
*/
|
||||
void init_system_properties();
|
||||
|
||||
/**
|
||||
* Helper for loading system properties.
|
||||
*/
|
||||
static void init_build_type_callback(void* cookie, const char* name, const char* value,
|
||||
uint32_t serial);
|
||||
|
||||
/**
|
||||
* Proto output of statsd report data dumpsys, wrapped in a StatsDataDumpProto.
|
||||
*/
|
||||
void dumpIncidentSection(int outFd);
|
||||
|
||||
/**
|
||||
* Text or proto output of statsdStats dumpsys.
|
||||
*/
|
||||
void dumpStatsdStats(int outFd, bool verbose, bool proto);
|
||||
|
||||
/**
|
||||
* Print usage information for the commands
|
||||
*/
|
||||
void print_cmd_help(int out);
|
||||
|
||||
/* Runs on its dedicated thread to process pushed stats event from socket. */
|
||||
void readLogs();
|
||||
|
||||
/**
|
||||
* Trigger a broadcast.
|
||||
*/
|
||||
status_t cmd_trigger_broadcast(int outFd, Vector<String8>& args);
|
||||
|
||||
|
||||
/**
|
||||
* Trigger an active configs changed broadcast.
|
||||
*/
|
||||
status_t cmd_trigger_active_config_broadcast(int outFd, Vector<String8>& args);
|
||||
|
||||
/**
|
||||
* Handle the config sub-command.
|
||||
*/
|
||||
status_t cmd_config(int inFd, int outFd, int err, Vector<String8>& args);
|
||||
|
||||
/**
|
||||
* Prints some basic stats to std out.
|
||||
*/
|
||||
status_t cmd_print_stats(int outFd, const Vector<String8>& args);
|
||||
|
||||
/**
|
||||
* Print the event log.
|
||||
*/
|
||||
status_t cmd_dump_report(int outFd, const Vector<String8>& args);
|
||||
|
||||
/**
|
||||
* Print the mapping of uids to package names.
|
||||
*/
|
||||
status_t cmd_print_uid_map(int outFd, const Vector<String8>& args);
|
||||
|
||||
/**
|
||||
* Flush the data to disk.
|
||||
*/
|
||||
status_t cmd_write_data_to_disk(int outFd);
|
||||
|
||||
/**
|
||||
* Write an AppBreadcrumbReported event to the StatsLog buffer, as if calling
|
||||
* StatsLog.write(APP_BREADCRUMB_REPORTED).
|
||||
*/
|
||||
status_t cmd_log_app_breadcrumb(int outFd, const Vector<String8>& args);
|
||||
|
||||
/**
|
||||
* Write an BinaryPushStateChanged event, as if calling StatsLog.logBinaryPushStateChanged().
|
||||
*/
|
||||
status_t cmd_log_binary_push(int outFd, const Vector<String8>& args);
|
||||
|
||||
/**
|
||||
* Print contents of a pulled metrics source.
|
||||
*/
|
||||
status_t cmd_print_pulled_metrics(int outFd, const Vector<String8>& args);
|
||||
|
||||
/**
|
||||
* Removes all configs stored on disk and on memory.
|
||||
*/
|
||||
status_t cmd_remove_all_configs(int outFd);
|
||||
|
||||
/*
|
||||
* Dump memory usage by statsd.
|
||||
*/
|
||||
status_t cmd_dump_memory_info(int outFd);
|
||||
|
||||
/*
|
||||
* Clear all puller cached data
|
||||
*/
|
||||
status_t cmd_clear_puller_cache(int outFd);
|
||||
|
||||
/**
|
||||
* Print all stats logs received to logcat.
|
||||
*/
|
||||
status_t cmd_print_logs(int outFd, const Vector<String8>& args);
|
||||
|
||||
/**
|
||||
* Writes the value of args[uidArgIndex] into uid.
|
||||
* Returns whether the uid is reasonable (type uid_t) and whether
|
||||
* 1. it is equal to the calling uid, or
|
||||
* 2. the device is mEngBuild, or
|
||||
* 3. the caller is AID_ROOT and the uid is AID_SHELL (i.e. ROOT can impersonate SHELL).
|
||||
*/
|
||||
bool getUidFromArgs(const Vector<String8>& args, size_t uidArgIndex, int32_t& uid);
|
||||
|
||||
/**
|
||||
* Writes the value of uidSting into uid.
|
||||
* Returns whether the uid is reasonable (type uid_t) and whether
|
||||
* 1. it is equal to the calling uid, or
|
||||
* 2. the device is mEngBuild, or
|
||||
* 3. the caller is AID_ROOT and the uid is AID_SHELL (i.e. ROOT can impersonate SHELL).
|
||||
*/
|
||||
bool getUidFromString(const char* uidString, int32_t& uid);
|
||||
|
||||
/**
|
||||
* Adds a configuration after checking permissions and obtaining UID from binder call.
|
||||
*/
|
||||
bool addConfigurationChecked(int uid, int64_t key, const vector<int8_t>& config);
|
||||
|
||||
/**
|
||||
* Update a configuration.
|
||||
*/
|
||||
void set_config(int uid, const string& name, const StatsdConfig& config);
|
||||
|
||||
/**
|
||||
* Death recipient callback that is called when StatsCompanionService dies.
|
||||
* The cookie is a pointer to a StatsService object.
|
||||
*/
|
||||
static void statsCompanionServiceDied(void* cookie);
|
||||
|
||||
/**
|
||||
* Implementation of statsCompanionServiceDied.
|
||||
*/
|
||||
void statsCompanionServiceDiedImpl();
|
||||
|
||||
/**
|
||||
* Tracks the uid <--> package name mapping.
|
||||
*/
|
||||
sp<UidMap> mUidMap;
|
||||
|
||||
/**
|
||||
* Fetches external metrics
|
||||
*/
|
||||
sp<StatsPullerManager> mPullerManager;
|
||||
|
||||
/**
|
||||
* Tracks the configurations that have been passed to statsd.
|
||||
*/
|
||||
sp<ConfigManager> mConfigManager;
|
||||
|
||||
/**
|
||||
* The metrics recorder.
|
||||
*/
|
||||
sp<StatsLogProcessor> mProcessor;
|
||||
|
||||
/**
|
||||
* The alarm monitor for anomaly detection.
|
||||
*/
|
||||
const sp<AlarmMonitor> mAnomalyAlarmMonitor;
|
||||
|
||||
/**
|
||||
* The alarm monitor for alarms to directly trigger subscriber.
|
||||
*/
|
||||
const sp<AlarmMonitor> mPeriodicAlarmMonitor;
|
||||
|
||||
/**
|
||||
* Whether this is an eng build.
|
||||
*/
|
||||
bool mEngBuild;
|
||||
|
||||
sp<ShellSubscriber> mShellSubscriber;
|
||||
|
||||
/**
|
||||
* Mutex for setting the shell subscriber
|
||||
*/
|
||||
mutable mutex mShellSubscriberMutex;
|
||||
std::shared_ptr<LogEventQueue> mEventQueue;
|
||||
|
||||
MultiConditionTrigger mBootCompleteTrigger;
|
||||
static const inline string kBootCompleteTag = "BOOT_COMPLETE";
|
||||
static const inline string kUidMapReceivedTag = "UID_MAP";
|
||||
static const inline string kAllPullersRegisteredTag = "PULLERS_REGISTERED";
|
||||
|
||||
ScopedAIBinder_DeathRecipient mStatsCompanionServiceDeathRecipient;
|
||||
|
||||
FRIEND_TEST(StatsLogProcessorTest, TestActivationsPersistAcrossSystemServerRestart);
|
||||
FRIEND_TEST(StatsServiceTest, TestAddConfig_simple);
|
||||
FRIEND_TEST(StatsServiceTest, TestAddConfig_empty);
|
||||
FRIEND_TEST(StatsServiceTest, TestAddConfig_invalid);
|
||||
FRIEND_TEST(StatsServiceTest, TestGetUidFromArgs);
|
||||
FRIEND_TEST(PartialBucketE2eTest, TestCountMetricNoSplitOnNewApp);
|
||||
FRIEND_TEST(PartialBucketE2eTest, TestCountMetricSplitOnBoot);
|
||||
FRIEND_TEST(PartialBucketE2eTest, TestCountMetricSplitOnUpgrade);
|
||||
FRIEND_TEST(PartialBucketE2eTest, TestCountMetricSplitOnRemoval);
|
||||
FRIEND_TEST(PartialBucketE2eTest, TestCountMetricWithoutSplit);
|
||||
FRIEND_TEST(PartialBucketE2eTest, TestValueMetricOnBootWithoutMinPartialBucket);
|
||||
FRIEND_TEST(PartialBucketE2eTest, TestValueMetricWithoutMinPartialBucket);
|
||||
FRIEND_TEST(PartialBucketE2eTest, TestValueMetricWithMinPartialBucket);
|
||||
FRIEND_TEST(PartialBucketE2eTest, TestGaugeMetricOnBootWithoutMinPartialBucket);
|
||||
FRIEND_TEST(PartialBucketE2eTest, TestGaugeMetricWithoutMinPartialBucket);
|
||||
FRIEND_TEST(PartialBucketE2eTest, TestGaugeMetricWithMinPartialBucket);
|
||||
|
||||
FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_single_bucket);
|
||||
FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_multiple_buckets);
|
||||
FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_long_refractory_period);
|
||||
};
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
|
||||
#endif // STATS_SERVICE_H
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
syntax = "proto2";
|
||||
|
||||
package android.os.statsd;
|
||||
option java_package = "com.android.os";
|
||||
option java_multiple_files = true;
|
||||
option java_outer_classname = "ActiveConfigProto";
|
||||
|
||||
message ActiveEventActivation {
|
||||
optional int32 atom_matcher_index = 1;
|
||||
|
||||
// Time left in activation. When this proto is loaded after device boot,
|
||||
// the activation should be set to active for this duration.
|
||||
// This field will only be set when the state is ACTIVE
|
||||
optional int64 remaining_ttl_nanos = 2;
|
||||
|
||||
enum State {
|
||||
UNNKNOWN = 0;
|
||||
// This metric should activate for remaining_ttl_nanos when we load the activations.
|
||||
ACTIVE = 1;
|
||||
// When we load the activations, this metric should activate on next boot for the tll
|
||||
// specified in the config.
|
||||
ACTIVATE_ON_BOOT = 2;
|
||||
}
|
||||
optional State state = 3;
|
||||
}
|
||||
|
||||
message ActiveMetric {
|
||||
optional int64 id = 1;
|
||||
repeated ActiveEventActivation activation = 2;
|
||||
}
|
||||
|
||||
message ActiveConfig {
|
||||
optional int64 id = 1;
|
||||
optional int32 uid = 2;
|
||||
repeated ActiveMetric metric = 3;
|
||||
}
|
||||
|
||||
// all configs and their metrics on device.
|
||||
message ActiveConfigList {
|
||||
repeated ActiveConfig config = 1;
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
const uint8_t ANNOTATION_ID_IS_UID = 1;
|
||||
const uint8_t ANNOTATION_ID_TRUNCATE_TIMESTAMP = 2;
|
||||
const uint8_t ANNOTATION_ID_PRIMARY_FIELD = 3;
|
||||
const uint8_t ANNOTATION_ID_EXCLUSIVE_STATE = 4;
|
||||
const uint8_t ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID = 5;
|
||||
const uint8_t ANNOTATION_ID_TRIGGER_STATE_RESET = 7;
|
||||
const uint8_t ANNOTATION_ID_STATE_NESTED = 8;
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,139 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#define DEBUG false
|
||||
#include "Log.h"
|
||||
|
||||
#include "anomaly/AlarmMonitor.h"
|
||||
#include "guardrail/StatsdStats.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
AlarmMonitor::AlarmMonitor(
|
||||
uint32_t minDiffToUpdateRegisteredAlarmTimeSec,
|
||||
const std::function<void(const shared_ptr<IStatsCompanionService>&, int64_t)>& updateAlarm,
|
||||
const std::function<void(const shared_ptr<IStatsCompanionService>&)>& cancelAlarm)
|
||||
: mRegisteredAlarmTimeSec(0),
|
||||
mMinUpdateTimeSec(minDiffToUpdateRegisteredAlarmTimeSec),
|
||||
mUpdateAlarm(updateAlarm),
|
||||
mCancelAlarm(cancelAlarm) {}
|
||||
|
||||
AlarmMonitor::~AlarmMonitor() {}
|
||||
|
||||
void AlarmMonitor::setStatsCompanionService(
|
||||
shared_ptr<IStatsCompanionService> statsCompanionService) {
|
||||
std::lock_guard<std::mutex> lock(mLock);
|
||||
shared_ptr<IStatsCompanionService> tmpForLock = mStatsCompanionService;
|
||||
mStatsCompanionService = statsCompanionService;
|
||||
if (statsCompanionService == nullptr) {
|
||||
VLOG("Erasing link to statsCompanionService");
|
||||
return;
|
||||
}
|
||||
VLOG("Creating link to statsCompanionService");
|
||||
const sp<const InternalAlarm> top = mPq.top();
|
||||
if (top != nullptr) {
|
||||
updateRegisteredAlarmTime_l(top->timestampSec);
|
||||
}
|
||||
}
|
||||
|
||||
void AlarmMonitor::add(sp<const InternalAlarm> alarm) {
|
||||
std::lock_guard<std::mutex> lock(mLock);
|
||||
if (alarm == nullptr) {
|
||||
ALOGW("Asked to add a null alarm.");
|
||||
return;
|
||||
}
|
||||
if (alarm->timestampSec < 1) {
|
||||
// forbidden since a timestamp 0 is used to indicate no alarm registered
|
||||
ALOGW("Asked to add a 0-time alarm.");
|
||||
return;
|
||||
}
|
||||
// TODO(b/110563466): Ensure that refractory period is respected.
|
||||
VLOG("Adding alarm with time %u", alarm->timestampSec);
|
||||
mPq.push(alarm);
|
||||
if (mRegisteredAlarmTimeSec < 1 ||
|
||||
alarm->timestampSec + mMinUpdateTimeSec < mRegisteredAlarmTimeSec) {
|
||||
updateRegisteredAlarmTime_l(alarm->timestampSec);
|
||||
}
|
||||
}
|
||||
|
||||
void AlarmMonitor::remove(sp<const InternalAlarm> alarm) {
|
||||
std::lock_guard<std::mutex> lock(mLock);
|
||||
if (alarm == nullptr) {
|
||||
ALOGW("Asked to remove a null alarm.");
|
||||
return;
|
||||
}
|
||||
VLOG("Removing alarm with time %u", alarm->timestampSec);
|
||||
bool wasPresent = mPq.remove(alarm);
|
||||
if (!wasPresent) return;
|
||||
if (mPq.empty()) {
|
||||
VLOG("Queue is empty. Cancel any alarm.");
|
||||
cancelRegisteredAlarmTime_l();
|
||||
return;
|
||||
}
|
||||
uint32_t soonestAlarmTimeSec = mPq.top()->timestampSec;
|
||||
VLOG("Soonest alarm is %u", soonestAlarmTimeSec);
|
||||
if (soonestAlarmTimeSec > mRegisteredAlarmTimeSec + mMinUpdateTimeSec) {
|
||||
updateRegisteredAlarmTime_l(soonestAlarmTimeSec);
|
||||
}
|
||||
}
|
||||
|
||||
// More efficient than repeatedly calling remove(mPq.top()) since it batches the
|
||||
// updates to the registered alarm.
|
||||
unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>> AlarmMonitor::popSoonerThan(
|
||||
uint32_t timestampSec) {
|
||||
VLOG("Removing alarms with time <= %u", timestampSec);
|
||||
unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>> oldAlarms;
|
||||
std::lock_guard<std::mutex> lock(mLock);
|
||||
|
||||
for (sp<const InternalAlarm> t = mPq.top(); t != nullptr && t->timestampSec <= timestampSec;
|
||||
t = mPq.top()) {
|
||||
oldAlarms.insert(t);
|
||||
mPq.pop(); // remove t
|
||||
}
|
||||
// Always update registered alarm time (if anything has changed).
|
||||
if (!oldAlarms.empty()) {
|
||||
if (mPq.empty()) {
|
||||
VLOG("Queue is empty. Cancel any alarm.");
|
||||
cancelRegisteredAlarmTime_l();
|
||||
} else {
|
||||
// Always update the registered alarm in this case (unlike remove()).
|
||||
updateRegisteredAlarmTime_l(mPq.top()->timestampSec);
|
||||
}
|
||||
}
|
||||
return oldAlarms;
|
||||
}
|
||||
|
||||
void AlarmMonitor::updateRegisteredAlarmTime_l(uint32_t timestampSec) {
|
||||
VLOG("Updating reg alarm time to %u", timestampSec);
|
||||
mRegisteredAlarmTimeSec = timestampSec;
|
||||
mUpdateAlarm(mStatsCompanionService, secToMs(mRegisteredAlarmTimeSec));
|
||||
}
|
||||
|
||||
void AlarmMonitor::cancelRegisteredAlarmTime_l() {
|
||||
VLOG("Cancelling reg alarm.");
|
||||
mRegisteredAlarmTimeSec = 0;
|
||||
mCancelAlarm(mStatsCompanionService);
|
||||
}
|
||||
|
||||
int64_t AlarmMonitor::secToMs(uint32_t timeSec) {
|
||||
return ((int64_t)timeSec) * 1000;
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,162 +0,0 @@
|
||||
/*
|
||||
* 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 "anomaly/indexed_priority_queue.h"
|
||||
|
||||
#include <aidl/android/os/IStatsCompanionService.h>
|
||||
#include <utils/RefBase.h>
|
||||
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
using namespace android;
|
||||
|
||||
using aidl::android::os::IStatsCompanionService;
|
||||
using std::function;
|
||||
using std::shared_ptr;
|
||||
using std::unordered_set;
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
/**
|
||||
* Represents an alarm, associated with some aggregate metric, holding a
|
||||
* projected time at which the metric is expected to exceed its anomaly
|
||||
* threshold.
|
||||
* Timestamps are in seconds since epoch in a uint32, so will fail in year 2106.
|
||||
*/
|
||||
struct InternalAlarm : public RefBase {
|
||||
explicit InternalAlarm(uint32_t timestampSec) : timestampSec(timestampSec) {
|
||||
}
|
||||
|
||||
const uint32_t timestampSec;
|
||||
|
||||
/** InternalAlarm a is smaller (higher priority) than b if its timestamp is sooner. */
|
||||
struct SmallerTimestamp {
|
||||
bool operator()(sp<const InternalAlarm> a, sp<const InternalAlarm> b) const {
|
||||
return (a->timestampSec < b->timestampSec);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Manages internal alarms that may get registered with the AlarmManager.
|
||||
*/
|
||||
class AlarmMonitor : public RefBase {
|
||||
public:
|
||||
/**
|
||||
* @param minDiffToUpdateRegisteredAlarmTimeSec If the soonest alarm differs
|
||||
* from the registered alarm by more than this amount, update the registered
|
||||
* alarm.
|
||||
*/
|
||||
AlarmMonitor(uint32_t minDiffToUpdateRegisteredAlarmTimeSec,
|
||||
const function<void(const shared_ptr<IStatsCompanionService>&, int64_t)>&
|
||||
updateAlarm,
|
||||
const function<void(const shared_ptr<IStatsCompanionService>&)>& cancelAlarm);
|
||||
~AlarmMonitor();
|
||||
|
||||
/**
|
||||
* Tells AnomalyMonitor what IStatsCompanionService to use and, if
|
||||
* applicable, immediately registers an existing alarm with it.
|
||||
* If nullptr, AnomalyMonitor will continue to add/remove alarms, but won't
|
||||
* update IStatsCompanionService (until such time as it is set non-null).
|
||||
*/
|
||||
void setStatsCompanionService(shared_ptr<IStatsCompanionService> statsCompanionService);
|
||||
|
||||
/**
|
||||
* Adds the given alarm (reference) to the queue.
|
||||
*/
|
||||
void add(sp<const InternalAlarm> alarm);
|
||||
|
||||
/**
|
||||
* Removes the given alarm (reference) from the queue.
|
||||
* Note that alarm comparison is reference-based; if another alarm exists
|
||||
* with the same timestampSec, that alarm will still remain in the queue.
|
||||
*/
|
||||
void remove(sp<const InternalAlarm> alarm);
|
||||
|
||||
/**
|
||||
* Returns and removes all alarms whose timestamp <= the given timestampSec.
|
||||
* Always updates the registered alarm if return is non-empty.
|
||||
*/
|
||||
unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>> popSoonerThan(
|
||||
uint32_t timestampSec);
|
||||
|
||||
/**
|
||||
* Returns the projected alarm timestamp that is registered with
|
||||
* StatsCompanionService. This may not be equal to the soonest alarm,
|
||||
* but should be within minDiffToUpdateRegisteredAlarmTimeSec of it.
|
||||
*/
|
||||
uint32_t getRegisteredAlarmTimeSec() const {
|
||||
return mRegisteredAlarmTimeSec;
|
||||
}
|
||||
|
||||
private:
|
||||
std::mutex mLock;
|
||||
|
||||
/**
|
||||
* Timestamp (seconds since epoch) of the alarm registered with
|
||||
* StatsCompanionService. This, in general, may not be equal to the soonest
|
||||
* alarm stored in mPq, but should be within minUpdateTimeSec of it.
|
||||
* A value of 0 indicates that no alarm is currently registered.
|
||||
*/
|
||||
uint32_t mRegisteredAlarmTimeSec;
|
||||
|
||||
/**
|
||||
* Priority queue of alarms, prioritized by soonest alarm.timestampSec.
|
||||
*/
|
||||
indexed_priority_queue<InternalAlarm, InternalAlarm::SmallerTimestamp> mPq;
|
||||
|
||||
/**
|
||||
* Binder interface for communicating with StatsCompanionService.
|
||||
*/
|
||||
shared_ptr<IStatsCompanionService> mStatsCompanionService = nullptr;
|
||||
|
||||
/**
|
||||
* Amount by which the soonest projected alarm must differ from
|
||||
* mRegisteredAlarmTimeSec before updateRegisteredAlarmTime_l is called.
|
||||
*/
|
||||
uint32_t mMinUpdateTimeSec;
|
||||
|
||||
/**
|
||||
* Updates the alarm registered with StatsCompanionService to the given time.
|
||||
* Also correspondingly updates mRegisteredAlarmTimeSec.
|
||||
*/
|
||||
void updateRegisteredAlarmTime_l(uint32_t timestampSec);
|
||||
|
||||
/**
|
||||
* Cancels the alarm registered with StatsCompanionService.
|
||||
* Also correspondingly sets mRegisteredAlarmTimeSec to 0.
|
||||
*/
|
||||
void cancelRegisteredAlarmTime_l();
|
||||
|
||||
/** Converts uint32 timestamp in seconds to a Java long in msec. */
|
||||
int64_t secToMs(uint32_t timeSec);
|
||||
|
||||
// Callback function to update the alarm via StatsCompanionService.
|
||||
std::function<void(const shared_ptr<IStatsCompanionService>, int64_t)> mUpdateAlarm;
|
||||
|
||||
// Callback function to cancel the alarm via StatsCompanionService.
|
||||
std::function<void(const shared_ptr<IStatsCompanionService>)> mCancelAlarm;
|
||||
|
||||
};
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,94 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#define DEBUG false // STOPSHIP if true
|
||||
#include "Log.h"
|
||||
|
||||
#include "anomaly/AlarmTracker.h"
|
||||
#include "anomaly/subscriber_util.h"
|
||||
#include "HashableDimensionKey.h"
|
||||
#include "stats_util.h"
|
||||
#include "storage/StorageManager.h"
|
||||
|
||||
#include <time.h>
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
AlarmTracker::AlarmTracker(const int64_t startMillis,
|
||||
const int64_t currentMillis,
|
||||
const Alarm& alarm, const ConfigKey& configKey,
|
||||
const sp<AlarmMonitor>& alarmMonitor)
|
||||
: mAlarmConfig(alarm),
|
||||
mConfigKey(configKey),
|
||||
mAlarmMonitor(alarmMonitor) {
|
||||
VLOG("AlarmTracker() called");
|
||||
mAlarmSec = (startMillis + mAlarmConfig.offset_millis()) / MS_PER_SEC;
|
||||
// startMillis is the time statsd is created. We need to find the 1st alarm timestamp after
|
||||
// the config is added to statsd.
|
||||
mAlarmSec = findNextAlarmSec(currentMillis / MS_PER_SEC); // round up
|
||||
mInternalAlarm = new InternalAlarm{static_cast<uint32_t>(mAlarmSec)};
|
||||
VLOG("AlarmTracker sets the periodic alarm at: %lld", (long long)mAlarmSec);
|
||||
if (mAlarmMonitor != nullptr) {
|
||||
mAlarmMonitor->add(mInternalAlarm);
|
||||
}
|
||||
}
|
||||
|
||||
AlarmTracker::~AlarmTracker() {
|
||||
VLOG("~AlarmTracker() called");
|
||||
if (mInternalAlarm != nullptr && mAlarmMonitor != nullptr) {
|
||||
mAlarmMonitor->remove(mInternalAlarm);
|
||||
}
|
||||
}
|
||||
|
||||
void AlarmTracker::addSubscription(const Subscription& subscription) {
|
||||
mSubscriptions.push_back(subscription);
|
||||
}
|
||||
|
||||
int64_t AlarmTracker::findNextAlarmSec(int64_t currentTimeSec) {
|
||||
if (currentTimeSec < mAlarmSec) {
|
||||
return mAlarmSec;
|
||||
}
|
||||
int64_t periodsForward =
|
||||
((currentTimeSec - mAlarmSec) * MS_PER_SEC) / mAlarmConfig.period_millis() + 1;
|
||||
return mAlarmSec + periodsForward * mAlarmConfig.period_millis() / MS_PER_SEC;
|
||||
}
|
||||
|
||||
void AlarmTracker::informAlarmsFired(
|
||||
const int64_t& timestampNs,
|
||||
unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>>& firedAlarms) {
|
||||
if (firedAlarms.empty() || mInternalAlarm == nullptr ||
|
||||
firedAlarms.find(mInternalAlarm) == firedAlarms.end()) {
|
||||
return;
|
||||
}
|
||||
if (!mSubscriptions.empty()) {
|
||||
VLOG("AlarmTracker triggers the subscribers.");
|
||||
triggerSubscribers(mAlarmConfig.id(), 0 /*metricId N/A*/, DEFAULT_METRIC_DIMENSION_KEY,
|
||||
0 /* metricValue N/A */, mConfigKey, mSubscriptions);
|
||||
}
|
||||
firedAlarms.erase(mInternalAlarm);
|
||||
mAlarmSec = findNextAlarmSec((timestampNs-1) / NS_PER_SEC + 1); // round up
|
||||
mInternalAlarm = new InternalAlarm{static_cast<uint32_t>(mAlarmSec)};
|
||||
VLOG("AlarmTracker sets the periodic alarm at: %lld", (long long)mAlarmSec);
|
||||
if (mAlarmMonitor != nullptr) {
|
||||
mAlarmMonitor->add(mInternalAlarm);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <gtest/gtest_prod.h>
|
||||
|
||||
#include "AlarmMonitor.h"
|
||||
#include "config/ConfigKey.h"
|
||||
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" // Alarm
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <utils/RefBase.h>
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
class AlarmTracker : public virtual RefBase {
|
||||
public:
|
||||
AlarmTracker(const int64_t startMillis,
|
||||
const int64_t currentMillis,
|
||||
const Alarm& alarm, const ConfigKey& configKey,
|
||||
const sp<AlarmMonitor>& subscriberAlarmMonitor);
|
||||
|
||||
virtual ~AlarmTracker();
|
||||
|
||||
void onAlarmFired();
|
||||
|
||||
void addSubscription(const Subscription& subscription);
|
||||
|
||||
void informAlarmsFired(const int64_t& timestampNs,
|
||||
unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>>& firedAlarms);
|
||||
|
||||
protected:
|
||||
// For test only. Returns the alarm timestamp in seconds. Otherwise returns 0.
|
||||
inline int32_t getAlarmTimestampSec() const {
|
||||
return mInternalAlarm == nullptr ? 0 : mInternalAlarm->timestampSec;
|
||||
}
|
||||
|
||||
int64_t findNextAlarmSec(int64_t currentTimeMillis);
|
||||
|
||||
// statsd_config.proto Alarm message that defines this tracker.
|
||||
const Alarm mAlarmConfig;
|
||||
|
||||
// A reference to the Alarm's config key.
|
||||
const ConfigKey mConfigKey;
|
||||
|
||||
// The subscriptions that depend on this alarm.
|
||||
std::vector<Subscription> mSubscriptions;
|
||||
|
||||
// Alarm monitor.
|
||||
sp<AlarmMonitor> mAlarmMonitor;
|
||||
|
||||
// The current expected alarm time in seconds.
|
||||
int64_t mAlarmSec;
|
||||
|
||||
// The current alarm.
|
||||
sp<const InternalAlarm> mInternalAlarm;
|
||||
|
||||
FRIEND_TEST(AlarmTrackerTest, TestTriggerTimestamp);
|
||||
FRIEND_TEST(AlarmE2eTest, TestMultipleAlarms);
|
||||
};
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,321 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#define DEBUG false // STOPSHIP if true
|
||||
#include "Log.h"
|
||||
|
||||
#include "AnomalyTracker.h"
|
||||
#include "external/Perfetto.h"
|
||||
#include "guardrail/StatsdStats.h"
|
||||
#include "metadata_util.h"
|
||||
#include "stats_log_util.h"
|
||||
#include "subscriber_util.h"
|
||||
#include "subscriber/IncidentdReporter.h"
|
||||
#include "subscriber/SubscriberReporter.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <statslog_statsd.h>
|
||||
#include <time.h>
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
AnomalyTracker::AnomalyTracker(const Alert& alert, const ConfigKey& configKey)
|
||||
: mAlert(alert), mConfigKey(configKey), mNumOfPastBuckets(mAlert.num_buckets() - 1) {
|
||||
VLOG("AnomalyTracker() called");
|
||||
if (mAlert.num_buckets() <= 0) {
|
||||
ALOGE("Cannot create AnomalyTracker with %lld buckets", (long long)mAlert.num_buckets());
|
||||
return;
|
||||
}
|
||||
if (!mAlert.has_trigger_if_sum_gt()) {
|
||||
ALOGE("Cannot create AnomalyTracker without threshold");
|
||||
return;
|
||||
}
|
||||
resetStorage(); // initialization
|
||||
}
|
||||
|
||||
AnomalyTracker::~AnomalyTracker() {
|
||||
VLOG("~AnomalyTracker() called");
|
||||
}
|
||||
|
||||
void AnomalyTracker::resetStorage() {
|
||||
VLOG("resetStorage() called.");
|
||||
mPastBuckets.clear();
|
||||
// Excludes the current bucket.
|
||||
mPastBuckets.resize(mNumOfPastBuckets);
|
||||
mSumOverPastBuckets.clear();
|
||||
}
|
||||
|
||||
size_t AnomalyTracker::index(int64_t bucketNum) const {
|
||||
if (bucketNum < 0) {
|
||||
ALOGE("index() was passed a negative bucket number (%lld)!", (long long)bucketNum);
|
||||
}
|
||||
return bucketNum % mNumOfPastBuckets;
|
||||
}
|
||||
|
||||
void AnomalyTracker::advanceMostRecentBucketTo(const int64_t& bucketNum) {
|
||||
VLOG("advanceMostRecentBucketTo() called.");
|
||||
if (mNumOfPastBuckets <= 0) {
|
||||
return;
|
||||
}
|
||||
if (bucketNum <= mMostRecentBucketNum) {
|
||||
ALOGW("Cannot advance buckets backwards (bucketNum=%lld but mMostRecentBucketNum=%lld)",
|
||||
(long long)bucketNum, (long long)mMostRecentBucketNum);
|
||||
return;
|
||||
}
|
||||
// If in the future (i.e. buckets are ancient), just empty out all past info.
|
||||
if (bucketNum >= mMostRecentBucketNum + mNumOfPastBuckets) {
|
||||
resetStorage();
|
||||
mMostRecentBucketNum = bucketNum;
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear out space by emptying out old mPastBuckets[i] values and update mSumOverPastBuckets.
|
||||
for (int64_t i = mMostRecentBucketNum + 1; i <= bucketNum; i++) {
|
||||
const int idx = index(i);
|
||||
subtractBucketFromSum(mPastBuckets[idx]);
|
||||
mPastBuckets[idx] = nullptr; // release (but not clear) the old bucket.
|
||||
}
|
||||
mMostRecentBucketNum = bucketNum;
|
||||
}
|
||||
|
||||
void AnomalyTracker::addPastBucket(const MetricDimensionKey& key,
|
||||
const int64_t& bucketValue,
|
||||
const int64_t& bucketNum) {
|
||||
VLOG("addPastBucket(bucketValue) called.");
|
||||
if (mNumOfPastBuckets == 0 ||
|
||||
bucketNum < 0 || bucketNum <= mMostRecentBucketNum - mNumOfPastBuckets) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int bucketIndex = index(bucketNum);
|
||||
if (bucketNum <= mMostRecentBucketNum && (mPastBuckets[bucketIndex] != nullptr)) {
|
||||
// We need to insert into an already existing past bucket.
|
||||
std::shared_ptr<DimToValMap>& bucket = mPastBuckets[bucketIndex];
|
||||
auto itr = bucket->find(key);
|
||||
if (itr != bucket->end()) {
|
||||
// Old entry already exists; update it.
|
||||
subtractValueFromSum(key, itr->second);
|
||||
itr->second = bucketValue;
|
||||
} else {
|
||||
bucket->insert({key, bucketValue});
|
||||
}
|
||||
mSumOverPastBuckets[key] += bucketValue;
|
||||
} else {
|
||||
// Bucket does not exist yet (in future or was never made), so we must make it.
|
||||
std::shared_ptr<DimToValMap> bucket = std::make_shared<DimToValMap>();
|
||||
bucket->insert({key, bucketValue});
|
||||
addPastBucket(bucket, bucketNum);
|
||||
}
|
||||
}
|
||||
|
||||
void AnomalyTracker::addPastBucket(std::shared_ptr<DimToValMap> bucket,
|
||||
const int64_t& bucketNum) {
|
||||
VLOG("addPastBucket(bucket) called.");
|
||||
if (mNumOfPastBuckets == 0 ||
|
||||
bucketNum < 0 || bucketNum <= mMostRecentBucketNum - mNumOfPastBuckets) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (bucketNum <= mMostRecentBucketNum) {
|
||||
// We are updating an old bucket, not adding a new one.
|
||||
subtractBucketFromSum(mPastBuckets[index(bucketNum)]);
|
||||
} else {
|
||||
// Clear space for the new bucket to be at bucketNum.
|
||||
advanceMostRecentBucketTo(bucketNum);
|
||||
}
|
||||
mPastBuckets[index(bucketNum)] = bucket;
|
||||
addBucketToSum(bucket);
|
||||
}
|
||||
|
||||
void AnomalyTracker::subtractBucketFromSum(const shared_ptr<DimToValMap>& bucket) {
|
||||
if (bucket == nullptr) {
|
||||
return;
|
||||
}
|
||||
for (const auto& keyValuePair : *bucket) {
|
||||
subtractValueFromSum(keyValuePair.first, keyValuePair.second);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void AnomalyTracker::subtractValueFromSum(const MetricDimensionKey& key,
|
||||
const int64_t& bucketValue) {
|
||||
auto itr = mSumOverPastBuckets.find(key);
|
||||
if (itr == mSumOverPastBuckets.end()) {
|
||||
return;
|
||||
}
|
||||
itr->second -= bucketValue;
|
||||
if (itr->second == 0) {
|
||||
mSumOverPastBuckets.erase(itr);
|
||||
}
|
||||
}
|
||||
|
||||
void AnomalyTracker::addBucketToSum(const shared_ptr<DimToValMap>& bucket) {
|
||||
if (bucket == nullptr) {
|
||||
return;
|
||||
}
|
||||
// For each dimension present in the bucket, add its value to its corresponding sum.
|
||||
for (const auto& keyValuePair : *bucket) {
|
||||
mSumOverPastBuckets[keyValuePair.first] += keyValuePair.second;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t AnomalyTracker::getPastBucketValue(const MetricDimensionKey& key,
|
||||
const int64_t& bucketNum) const {
|
||||
if (bucketNum < 0 || mMostRecentBucketNum < 0
|
||||
|| bucketNum <= mMostRecentBucketNum - mNumOfPastBuckets
|
||||
|| bucketNum > mMostRecentBucketNum) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto& bucket = mPastBuckets[index(bucketNum)];
|
||||
if (bucket == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
const auto& itr = bucket->find(key);
|
||||
return itr == bucket->end() ? 0 : itr->second;
|
||||
}
|
||||
|
||||
int64_t AnomalyTracker::getSumOverPastBuckets(const MetricDimensionKey& key) const {
|
||||
const auto& itr = mSumOverPastBuckets.find(key);
|
||||
if (itr != mSumOverPastBuckets.end()) {
|
||||
return itr->second;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool AnomalyTracker::detectAnomaly(const int64_t& currentBucketNum,
|
||||
const MetricDimensionKey& key,
|
||||
const int64_t& currentBucketValue) {
|
||||
|
||||
// currentBucketNum should be the next bucket after pastBuckets. If not, advance so that it is.
|
||||
if (currentBucketNum > mMostRecentBucketNum + 1) {
|
||||
advanceMostRecentBucketTo(currentBucketNum - 1);
|
||||
}
|
||||
return mAlert.has_trigger_if_sum_gt() &&
|
||||
getSumOverPastBuckets(key) + currentBucketValue > mAlert.trigger_if_sum_gt();
|
||||
}
|
||||
|
||||
void AnomalyTracker::declareAnomaly(const int64_t& timestampNs, int64_t metricId,
|
||||
const MetricDimensionKey& key, int64_t metricValue) {
|
||||
// TODO(b/110563466): Why receive timestamp? RefractoryPeriod should always be based on
|
||||
// real time right now.
|
||||
if (isInRefractoryPeriod(timestampNs, key)) {
|
||||
VLOG("Skipping anomaly declaration since within refractory period");
|
||||
return;
|
||||
}
|
||||
if (mAlert.has_refractory_period_secs()) {
|
||||
mRefractoryPeriodEndsSec[key] = ((timestampNs + NS_PER_SEC - 1) / NS_PER_SEC) // round up
|
||||
+ mAlert.refractory_period_secs();
|
||||
// TODO(b/110563466): If we had access to the bucket_size_millis, consider
|
||||
// calling resetStorage()
|
||||
// if (mAlert.refractory_period_secs() > mNumOfPastBuckets * bucketSizeNs) {resetStorage();}
|
||||
}
|
||||
|
||||
if (!mSubscriptions.empty()) {
|
||||
ALOGI("An anomaly (%" PRId64 ") %s has occurred! Informing subscribers.",
|
||||
mAlert.id(), key.toString().c_str());
|
||||
informSubscribers(key, metricId, metricValue);
|
||||
} else {
|
||||
ALOGI("An anomaly has occurred! (But no subscriber for that alert.)");
|
||||
}
|
||||
|
||||
StatsdStats::getInstance().noteAnomalyDeclared(mConfigKey, mAlert.id());
|
||||
|
||||
// TODO(b/110564268): This should also take in the const MetricDimensionKey& key?
|
||||
util::stats_write(util::ANOMALY_DETECTED, mConfigKey.GetUid(),
|
||||
mConfigKey.GetId(), mAlert.id());
|
||||
}
|
||||
|
||||
void AnomalyTracker::detectAndDeclareAnomaly(const int64_t& timestampNs,
|
||||
const int64_t& currBucketNum, int64_t metricId,
|
||||
const MetricDimensionKey& key,
|
||||
const int64_t& currentBucketValue) {
|
||||
if (detectAnomaly(currBucketNum, key, currentBucketValue)) {
|
||||
declareAnomaly(timestampNs, metricId, key, currentBucketValue);
|
||||
}
|
||||
}
|
||||
|
||||
bool AnomalyTracker::isInRefractoryPeriod(const int64_t& timestampNs,
|
||||
const MetricDimensionKey& key) const {
|
||||
const auto& it = mRefractoryPeriodEndsSec.find(key);
|
||||
if (it != mRefractoryPeriodEndsSec.end()) {
|
||||
return timestampNs < (it->second * (int64_t)NS_PER_SEC);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void AnomalyTracker::informSubscribers(const MetricDimensionKey& key, int64_t metric_id,
|
||||
int64_t metricValue) {
|
||||
triggerSubscribers(mAlert.id(), metric_id, key, metricValue, mConfigKey, mSubscriptions);
|
||||
}
|
||||
|
||||
bool AnomalyTracker::writeAlertMetadataToProto(int64_t currentWallClockTimeNs,
|
||||
int64_t systemElapsedTimeNs,
|
||||
metadata::AlertMetadata* alertMetadata) {
|
||||
bool metadataWritten = false;
|
||||
|
||||
if (mRefractoryPeriodEndsSec.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto& it: mRefractoryPeriodEndsSec) {
|
||||
// Do not write the timestamp to disk if it has already expired
|
||||
if (it.second < systemElapsedTimeNs / NS_PER_SEC) {
|
||||
continue;
|
||||
}
|
||||
|
||||
metadataWritten = true;
|
||||
if (alertMetadata->alert_dim_keyed_data_size() == 0) {
|
||||
alertMetadata->set_alert_id(mAlert.id());
|
||||
}
|
||||
|
||||
metadata::AlertDimensionKeyedData* keyedData = alertMetadata->add_alert_dim_keyed_data();
|
||||
// We convert and write the refractory_end_sec to wall clock time because we do not know
|
||||
// when statsd will start again.
|
||||
int32_t refractoryEndWallClockSec = (int32_t) ((currentWallClockTimeNs / NS_PER_SEC) +
|
||||
(it.second - systemElapsedTimeNs / NS_PER_SEC));
|
||||
|
||||
keyedData->set_last_refractory_ends_sec(refractoryEndWallClockSec);
|
||||
writeMetricDimensionKeyToMetadataDimensionKey(
|
||||
it.first, keyedData->mutable_dimension_key());
|
||||
}
|
||||
|
||||
return metadataWritten;
|
||||
}
|
||||
|
||||
void AnomalyTracker::loadAlertMetadata(
|
||||
const metadata::AlertMetadata& alertMetadata,
|
||||
int64_t currentWallClockTimeNs,
|
||||
int64_t systemElapsedTimeNs) {
|
||||
for (const metadata::AlertDimensionKeyedData& keyedData :
|
||||
alertMetadata.alert_dim_keyed_data()) {
|
||||
if ((uint64_t) keyedData.last_refractory_ends_sec() < currentWallClockTimeNs / NS_PER_SEC) {
|
||||
// Do not update the timestamp if it has already expired.
|
||||
continue;
|
||||
}
|
||||
MetricDimensionKey metricKey = loadMetricDimensionKeyFromProto(
|
||||
keyedData.dimension_key());
|
||||
int32_t refractoryPeriodEndsSec = (int32_t) keyedData.last_refractory_ends_sec() -
|
||||
currentWallClockTimeNs / NS_PER_SEC + systemElapsedTimeNs / NS_PER_SEC;
|
||||
mRefractoryPeriodEndsSec[metricKey] = refractoryPeriodEndsSec;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,204 +0,0 @@
|
||||
/*
|
||||
* 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 <stdlib.h>
|
||||
|
||||
#include <gtest/gtest_prod.h>
|
||||
#include <utils/RefBase.h>
|
||||
|
||||
#include "AlarmMonitor.h"
|
||||
#include "config/ConfigKey.h"
|
||||
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" // Alert
|
||||
#include "frameworks/base/cmds/statsd/src/statsd_metadata.pb.h" // AlertMetadata
|
||||
#include "stats_util.h" // HashableDimensionKey and DimToValMap
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
using std::shared_ptr;
|
||||
using std::unordered_map;
|
||||
|
||||
// Does NOT allow negative values.
|
||||
class AnomalyTracker : public virtual RefBase {
|
||||
public:
|
||||
AnomalyTracker(const Alert& alert, const ConfigKey& configKey);
|
||||
|
||||
virtual ~AnomalyTracker();
|
||||
|
||||
// Add subscriptions that depend on this alert.
|
||||
void addSubscription(const Subscription& subscription) {
|
||||
mSubscriptions.push_back(subscription);
|
||||
}
|
||||
|
||||
// Adds a bucket for the given bucketNum (index starting at 0).
|
||||
// If a bucket for bucketNum already exists, it will be replaced.
|
||||
// Also, advances to bucketNum (if not in the past), effectively filling any intervening
|
||||
// buckets with 0s.
|
||||
void addPastBucket(std::shared_ptr<DimToValMap> bucket, const int64_t& bucketNum);
|
||||
|
||||
// Inserts (or replaces) the bucket entry for the given bucketNum at the given key to be the
|
||||
// given bucketValue. If the bucket does not exist, it will be created.
|
||||
// Also, advances to bucketNum (if not in the past), effectively filling any intervening
|
||||
// buckets with 0s.
|
||||
void addPastBucket(const MetricDimensionKey& key, const int64_t& bucketValue,
|
||||
const int64_t& bucketNum);
|
||||
|
||||
// Returns true if, based on past buckets plus the new currentBucketValue (which generally
|
||||
// represents the partially-filled current bucket), an anomaly has happened.
|
||||
// Also advances to currBucketNum-1.
|
||||
bool detectAnomaly(const int64_t& currBucketNum, const MetricDimensionKey& key,
|
||||
const int64_t& currentBucketValue);
|
||||
|
||||
// Informs incidentd about the detected alert.
|
||||
void declareAnomaly(const int64_t& timestampNs, int64_t metricId, const MetricDimensionKey& key,
|
||||
int64_t metricValue);
|
||||
|
||||
// Detects if, based on past buckets plus the new currentBucketValue (which generally
|
||||
// represents the partially-filled current bucket), an anomaly has happened, and if so,
|
||||
// declares an anomaly and informs relevant subscribers.
|
||||
// Also advances to currBucketNum-1.
|
||||
void detectAndDeclareAnomaly(const int64_t& timestampNs, const int64_t& currBucketNum,
|
||||
int64_t metricId, const MetricDimensionKey& key,
|
||||
const int64_t& currentBucketValue);
|
||||
|
||||
// Init the AlarmMonitor which is shared across anomaly trackers.
|
||||
virtual void setAlarmMonitor(const sp<AlarmMonitor>& alarmMonitor) {
|
||||
return; // Base AnomalyTracker class has no need for the AlarmMonitor.
|
||||
}
|
||||
|
||||
// Returns the sum of all past bucket values for the given dimension key.
|
||||
int64_t getSumOverPastBuckets(const MetricDimensionKey& key) const;
|
||||
|
||||
// Returns the value for a past bucket, or 0 if that bucket doesn't exist.
|
||||
int64_t getPastBucketValue(const MetricDimensionKey& key, const int64_t& bucketNum) const;
|
||||
|
||||
// Returns the anomaly threshold set in the configuration.
|
||||
inline int64_t getAnomalyThreshold() const {
|
||||
return mAlert.trigger_if_sum_gt();
|
||||
}
|
||||
|
||||
// Returns the refractory period ending timestamp (in seconds) for the given key.
|
||||
// Before this moment, any detected anomaly will be ignored.
|
||||
// If there is no stored refractory period ending timestamp, returns 0.
|
||||
uint32_t getRefractoryPeriodEndsSec(const MetricDimensionKey& key) const {
|
||||
const auto& it = mRefractoryPeriodEndsSec.find(key);
|
||||
return it != mRefractoryPeriodEndsSec.end() ? it->second : 0;
|
||||
}
|
||||
|
||||
// Returns the (constant) number of past buckets this anomaly tracker can store.
|
||||
inline int getNumOfPastBuckets() const {
|
||||
return mNumOfPastBuckets;
|
||||
}
|
||||
|
||||
// Declares an anomaly for each alarm in firedAlarms that belongs to this AnomalyTracker,
|
||||
// and removes it from firedAlarms. Does NOT remove the alarm from the AlarmMonitor.
|
||||
virtual void informAlarmsFired(const int64_t& timestampNs,
|
||||
unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>>& firedAlarms) {
|
||||
return; // The base AnomalyTracker class doesn't have alarms.
|
||||
}
|
||||
|
||||
// Writes metadata of the alert (refractory_period_end_sec) to AlertMetadata.
|
||||
// Returns true if at least one element is written to alertMetadata.
|
||||
bool writeAlertMetadataToProto(
|
||||
int64_t currentWallClockTimeNs,
|
||||
int64_t systemElapsedTimeNs, metadata::AlertMetadata* alertMetadata);
|
||||
|
||||
void loadAlertMetadata(
|
||||
const metadata::AlertMetadata& alertMetadata,
|
||||
int64_t currentWallClockTimeNs,
|
||||
int64_t systemElapsedTimeNs);
|
||||
|
||||
protected:
|
||||
// For testing only.
|
||||
// Returns the alarm timestamp in seconds for the query dimension if it exists. Otherwise
|
||||
// returns 0.
|
||||
virtual uint32_t getAlarmTimestampSec(const MetricDimensionKey& dimensionKey) const {
|
||||
return 0; // The base AnomalyTracker class doesn't have alarms.
|
||||
}
|
||||
|
||||
// statsd_config.proto Alert message that defines this tracker.
|
||||
const Alert mAlert;
|
||||
|
||||
// The subscriptions that depend on this alert.
|
||||
std::vector<Subscription> mSubscriptions;
|
||||
|
||||
// A reference to the Alert's config key.
|
||||
const ConfigKey mConfigKey;
|
||||
|
||||
// Number of past buckets. One less than the total number of buckets needed
|
||||
// for the anomaly detection (since the current bucket is not in the past).
|
||||
const int mNumOfPastBuckets;
|
||||
|
||||
// Values for each of the past mNumOfPastBuckets buckets. Always of size mNumOfPastBuckets.
|
||||
// mPastBuckets[i] can be null, meaning that no data is present in that bucket.
|
||||
std::vector<shared_ptr<DimToValMap>> mPastBuckets;
|
||||
|
||||
// Cached sum over all existing buckets in mPastBuckets.
|
||||
// Its buckets never contain entries of 0.
|
||||
DimToValMap mSumOverPastBuckets;
|
||||
|
||||
// The bucket number of the last added bucket.
|
||||
int64_t mMostRecentBucketNum = -1;
|
||||
|
||||
// Map from each dimension to the timestamp that its refractory period (if this anomaly was
|
||||
// declared for that dimension) ends, in seconds. From this moment and onwards, anomalies
|
||||
// can be declared again.
|
||||
// Entries may be, but are not guaranteed to be, removed after the period is finished.
|
||||
unordered_map<MetricDimensionKey, uint32_t> mRefractoryPeriodEndsSec;
|
||||
|
||||
// Advances mMostRecentBucketNum to bucketNum, deleting any data that is now too old.
|
||||
// Specifically, since it is now too old, removes the data for
|
||||
// [mMostRecentBucketNum - mNumOfPastBuckets + 1, bucketNum - mNumOfPastBuckets].
|
||||
void advanceMostRecentBucketTo(const int64_t& bucketNum);
|
||||
|
||||
// Add the information in the given bucket to mSumOverPastBuckets.
|
||||
void addBucketToSum(const shared_ptr<DimToValMap>& bucket);
|
||||
|
||||
// Subtract the information in the given bucket from mSumOverPastBuckets
|
||||
// and remove any items with value 0.
|
||||
void subtractBucketFromSum(const shared_ptr<DimToValMap>& bucket);
|
||||
|
||||
// From mSumOverPastBuckets[key], subtracts bucketValue, removing it if it is now 0.
|
||||
void subtractValueFromSum(const MetricDimensionKey& key, const int64_t& bucketValue);
|
||||
|
||||
// Returns true if in the refractory period, else false.
|
||||
bool isInRefractoryPeriod(const int64_t& timestampNs, const MetricDimensionKey& key) const;
|
||||
|
||||
// Calculates the corresponding bucket index within the circular array.
|
||||
// Requires bucketNum >= 0.
|
||||
size_t index(int64_t bucketNum) const;
|
||||
|
||||
// Resets all bucket data. For use when all the data gets stale.
|
||||
virtual void resetStorage();
|
||||
|
||||
// Informs the subscribers (incidentd, perfetto, broadcasts, etc) that an anomaly has occurred.
|
||||
void informSubscribers(const MetricDimensionKey& key, int64_t metricId, int64_t metricValue);
|
||||
|
||||
FRIEND_TEST(AnomalyTrackerTest, TestConsecutiveBuckets);
|
||||
FRIEND_TEST(AnomalyTrackerTest, TestSparseBuckets);
|
||||
FRIEND_TEST(GaugeMetricProducerTest, TestAnomalyDetection);
|
||||
FRIEND_TEST(CountMetricProducerTest, TestAnomalyDetectionUnSliced);
|
||||
FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_single_bucket);
|
||||
FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_multiple_buckets);
|
||||
FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_long_refractory_period);
|
||||
};
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,115 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#define DEBUG false // STOPSHIP if true
|
||||
#include "Log.h"
|
||||
|
||||
#include "DurationAnomalyTracker.h"
|
||||
#include "guardrail/StatsdStats.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
DurationAnomalyTracker::DurationAnomalyTracker(const Alert& alert, const ConfigKey& configKey,
|
||||
const sp<AlarmMonitor>& alarmMonitor)
|
||||
: AnomalyTracker(alert, configKey), mAlarmMonitor(alarmMonitor) {
|
||||
VLOG("DurationAnomalyTracker() called");
|
||||
}
|
||||
|
||||
DurationAnomalyTracker::~DurationAnomalyTracker() {
|
||||
VLOG("~DurationAnomalyTracker() called");
|
||||
cancelAllAlarms();
|
||||
}
|
||||
|
||||
void DurationAnomalyTracker::startAlarm(const MetricDimensionKey& dimensionKey,
|
||||
const int64_t& timestampNs) {
|
||||
// Alarms are stored in secs. Must round up, since if it fires early, it is ignored completely.
|
||||
uint32_t timestampSec = static_cast<uint32_t>((timestampNs -1) / NS_PER_SEC) + 1; // round up
|
||||
if (isInRefractoryPeriod(timestampNs, dimensionKey)) {
|
||||
VLOG("Not setting anomaly alarm since it would fall in the refractory period.");
|
||||
return;
|
||||
}
|
||||
|
||||
auto itr = mAlarms.find(dimensionKey);
|
||||
if (itr != mAlarms.end() && mAlarmMonitor != nullptr) {
|
||||
mAlarmMonitor->remove(itr->second);
|
||||
}
|
||||
|
||||
sp<const InternalAlarm> alarm = new InternalAlarm{timestampSec};
|
||||
mAlarms[dimensionKey] = alarm;
|
||||
if (mAlarmMonitor != nullptr) {
|
||||
mAlarmMonitor->add(alarm);
|
||||
}
|
||||
}
|
||||
|
||||
void DurationAnomalyTracker::stopAlarm(const MetricDimensionKey& dimensionKey,
|
||||
const int64_t& timestampNs) {
|
||||
const auto itr = mAlarms.find(dimensionKey);
|
||||
if (itr == mAlarms.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the alarm is set in the past but hasn't fired yet (due to lag), catch it now.
|
||||
if (itr->second != nullptr && timestampNs >= (int64_t)NS_PER_SEC * itr->second->timestampSec) {
|
||||
declareAnomaly(timestampNs, mAlert.metric_id(), dimensionKey,
|
||||
mAlert.trigger_if_sum_gt() + (timestampNs / NS_PER_SEC) -
|
||||
itr->second->timestampSec);
|
||||
}
|
||||
if (mAlarmMonitor != nullptr) {
|
||||
mAlarmMonitor->remove(itr->second);
|
||||
}
|
||||
mAlarms.erase(dimensionKey);
|
||||
}
|
||||
|
||||
void DurationAnomalyTracker::cancelAllAlarms() {
|
||||
if (mAlarmMonitor != nullptr) {
|
||||
for (const auto& itr : mAlarms) {
|
||||
mAlarmMonitor->remove(itr.second);
|
||||
}
|
||||
}
|
||||
mAlarms.clear();
|
||||
}
|
||||
|
||||
void DurationAnomalyTracker::informAlarmsFired(const int64_t& timestampNs,
|
||||
unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>>& firedAlarms) {
|
||||
|
||||
if (firedAlarms.empty() || mAlarms.empty()) return;
|
||||
// Find the intersection of firedAlarms and mAlarms.
|
||||
// The for loop is inefficient, since it loops over all keys, but that's okay since it is very
|
||||
// seldomly called. The alternative would be having InternalAlarms store information about the
|
||||
// DurationAnomalyTracker and key, but that's a lot of data overhead to speed up something that
|
||||
// is rarely ever called.
|
||||
unordered_map<MetricDimensionKey, sp<const InternalAlarm>> matchedAlarms;
|
||||
for (const auto& kv : mAlarms) {
|
||||
if (firedAlarms.count(kv.second) > 0) {
|
||||
matchedAlarms.insert({kv.first, kv.second});
|
||||
}
|
||||
}
|
||||
|
||||
// Now declare each of these alarms to have fired.
|
||||
for (const auto& kv : matchedAlarms) {
|
||||
declareAnomaly(
|
||||
timestampNs, mAlert.metric_id(), kv.first,
|
||||
mAlert.trigger_if_sum_gt() + (timestampNs / NS_PER_SEC) - kv.second->timestampSec);
|
||||
mAlarms.erase(kv.first);
|
||||
firedAlarms.erase(kv.second); // No one else can also own it, so we're done with it.
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,79 +0,0 @@
|
||||
/*
|
||||
* 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 "AlarmMonitor.h"
|
||||
#include "AnomalyTracker.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
using std::unordered_map;
|
||||
|
||||
class DurationAnomalyTracker : public virtual AnomalyTracker {
|
||||
public:
|
||||
DurationAnomalyTracker(const Alert& alert, const ConfigKey& configKey,
|
||||
const sp<AlarmMonitor>& alarmMonitor);
|
||||
|
||||
virtual ~DurationAnomalyTracker();
|
||||
|
||||
// Sets an alarm for the given timestamp.
|
||||
// Replaces previous alarm if one already exists.
|
||||
void startAlarm(const MetricDimensionKey& dimensionKey, const int64_t& eventTime);
|
||||
|
||||
// Stops the alarm.
|
||||
// If it should have already fired, but hasn't yet (e.g. because the AlarmManager is delayed),
|
||||
// declare the anomaly now.
|
||||
void stopAlarm(const MetricDimensionKey& dimensionKey, const int64_t& timestampNs);
|
||||
|
||||
// Stop all the alarms owned by this tracker. Does not declare any anomalies.
|
||||
void cancelAllAlarms();
|
||||
|
||||
// Declares an anomaly for each alarm in firedAlarms that belongs to this DurationAnomalyTracker
|
||||
// and removes it from firedAlarms. The AlarmMonitor is not informed.
|
||||
// Note that this will generally be called from a different thread from the other functions;
|
||||
// the caller is responsible for thread safety.
|
||||
void informAlarmsFired(const int64_t& timestampNs,
|
||||
unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>>& firedAlarms) override;
|
||||
|
||||
protected:
|
||||
// Returns the alarm timestamp in seconds for the query dimension if it exists. Otherwise
|
||||
// returns 0.
|
||||
uint32_t getAlarmTimestampSec(const MetricDimensionKey& dimensionKey) const override {
|
||||
auto it = mAlarms.find(dimensionKey);
|
||||
return it == mAlarms.end() ? 0 : it->second->timestampSec;
|
||||
}
|
||||
|
||||
// The alarms owned by this tracker. The alarm monitor also shares the alarm pointers when they
|
||||
// are still active.
|
||||
std::unordered_map<MetricDimensionKey, sp<const InternalAlarm>> mAlarms;
|
||||
|
||||
// Anomaly alarm monitor.
|
||||
sp<AlarmMonitor> mAlarmMonitor;
|
||||
|
||||
FRIEND_TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp);
|
||||
FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm);
|
||||
FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm);
|
||||
FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyDetection);
|
||||
FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp);
|
||||
FRIEND_TEST(MaxDurationTrackerTest, TestAnomalyPredictedTimestamp_UpdatedOnStop);
|
||||
};
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,224 +0,0 @@
|
||||
/*
|
||||
* 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 <utils/RefBase.h>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
using namespace android;
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
/** Defines a hash function for sp<const AA>, returning the hash of the underlying pointer. */
|
||||
template <class AA>
|
||||
struct SpHash {
|
||||
size_t operator()(const sp<const AA>& k) const {
|
||||
return std::hash<const AA*>()(k.get());
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Min priority queue for generic type AA.
|
||||
* Unlike a regular priority queue, this class is also capable of removing interior elements.
|
||||
* @tparam Comparator must implement [bool operator()(sp<const AA> a, sp<const AA> b)], returning
|
||||
* whether a should be closer to the top of the queue than b.
|
||||
*/
|
||||
template <class AA, class Comparator>
|
||||
class indexed_priority_queue {
|
||||
public:
|
||||
indexed_priority_queue();
|
||||
/** Adds a into the priority queue. If already present or a==nullptr, does nothing. */
|
||||
void push(sp<const AA> a);
|
||||
/*
|
||||
* Removes a from the priority queue. If not present or a==nullptr, does nothing.
|
||||
* Returns true if a had been present (and is now removed), else false.
|
||||
*/
|
||||
bool remove(sp<const AA> a);
|
||||
/** Removes the top element, if there is one. */
|
||||
void pop();
|
||||
/** Removes all elements. */
|
||||
void clear();
|
||||
/** Returns whether priority queue contains a (not just a copy of a, but a itself). */
|
||||
bool contains(sp<const AA> a) const;
|
||||
/** Returns min element. Returns nullptr iff empty(). */
|
||||
sp<const AA> top() const;
|
||||
/** Returns number of elements in priority queue. */
|
||||
size_t size() const {
|
||||
return pq.size() - 1;
|
||||
} // pq is 1-indexed
|
||||
/** Returns true iff priority queue is empty. */
|
||||
bool empty() const {
|
||||
return size() < 1;
|
||||
}
|
||||
|
||||
private:
|
||||
/** Vector representing a min-heap (1-indexed, with nullptr at 0). */
|
||||
std::vector<sp<const AA>> pq;
|
||||
/** Mapping of each element in pq to its index in pq (i.e. the inverse of a=pq[i]). */
|
||||
std::unordered_map<sp<const AA>, size_t, SpHash<AA>> indices;
|
||||
|
||||
void init();
|
||||
void sift_up(size_t idx);
|
||||
void sift_down(size_t idx);
|
||||
/** Returns whether pq[idx1] is considered higher than pq[idx2], according to Comparator. */
|
||||
bool higher(size_t idx1, size_t idx2) const;
|
||||
void swap_indices(size_t i, size_t j);
|
||||
};
|
||||
|
||||
// Implementation must be done in this file due to use of template.
|
||||
|
||||
template <class AA, class Comparator>
|
||||
indexed_priority_queue<AA, Comparator>::indexed_priority_queue() {
|
||||
init();
|
||||
}
|
||||
|
||||
template <class AA, class Comparator>
|
||||
void indexed_priority_queue<AA, Comparator>::push(sp<const AA> a) {
|
||||
if (a == nullptr) return;
|
||||
if (contains(a)) return;
|
||||
pq.push_back(a);
|
||||
size_t idx = size(); // index of last element since 1-indexed
|
||||
indices.insert({a, idx});
|
||||
sift_up(idx); // get the pq back in order
|
||||
}
|
||||
|
||||
template <class AA, class Comparator>
|
||||
bool indexed_priority_queue<AA, Comparator>::remove(sp<const AA> a) {
|
||||
if (a == nullptr) return false;
|
||||
if (!contains(a)) return false;
|
||||
size_t idx = indices[a];
|
||||
if (idx >= pq.size()) {
|
||||
return false;
|
||||
}
|
||||
if (idx == size()) { // if a is the last element, i.e. at index idx == size() == (pq.size()-1)
|
||||
pq.pop_back();
|
||||
indices.erase(a);
|
||||
return true;
|
||||
}
|
||||
// move last element (guaranteed not to be at idx) to idx, then delete a
|
||||
sp<const AA> last_a = pq.back();
|
||||
pq[idx] = last_a;
|
||||
pq.pop_back();
|
||||
indices[last_a] = idx;
|
||||
indices.erase(a);
|
||||
|
||||
// get the heap back in order (since the element at idx is not in order)
|
||||
sift_up(idx);
|
||||
sift_down(idx);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// The same as, but slightly more efficient than, remove(top()).
|
||||
template <class AA, class Comparator>
|
||||
void indexed_priority_queue<AA, Comparator>::pop() {
|
||||
sp<const AA> a = top();
|
||||
if (a == nullptr) return;
|
||||
const size_t idx = 1;
|
||||
if (idx == size()) { // if a is the last element
|
||||
pq.pop_back();
|
||||
indices.erase(a);
|
||||
return;
|
||||
}
|
||||
// move last element (guaranteed not to be at idx) to idx, then delete a
|
||||
sp<const AA> last_a = pq.back();
|
||||
pq[idx] = last_a;
|
||||
pq.pop_back();
|
||||
indices[last_a] = idx;
|
||||
indices.erase(a);
|
||||
|
||||
// get the heap back in order (since the element at idx is not in order)
|
||||
sift_down(idx);
|
||||
}
|
||||
|
||||
template <class AA, class Comparator>
|
||||
void indexed_priority_queue<AA, Comparator>::clear() {
|
||||
pq.clear();
|
||||
indices.clear();
|
||||
init();
|
||||
}
|
||||
|
||||
template <class AA, class Comparator>
|
||||
sp<const AA> indexed_priority_queue<AA, Comparator>::top() const {
|
||||
if (empty()) return nullptr;
|
||||
return pq[1];
|
||||
}
|
||||
|
||||
template <class AA, class Comparator>
|
||||
void indexed_priority_queue<AA, Comparator>::init() {
|
||||
pq.push_back(nullptr); // so that pq is 1-indexed.
|
||||
indices.insert({nullptr, 0}); // just to be consistent with pq.
|
||||
}
|
||||
|
||||
template <class AA, class Comparator>
|
||||
void indexed_priority_queue<AA, Comparator>::sift_up(size_t idx) {
|
||||
while (idx > 1) {
|
||||
size_t parent = idx / 2;
|
||||
if (higher(idx, parent))
|
||||
swap_indices(idx, parent);
|
||||
else
|
||||
break;
|
||||
idx = parent;
|
||||
}
|
||||
}
|
||||
|
||||
template <class AA, class Comparator>
|
||||
void indexed_priority_queue<AA, Comparator>::sift_down(size_t idx) {
|
||||
while (2 * idx <= size()) {
|
||||
size_t child = 2 * idx;
|
||||
if (child < size() && higher(child + 1, child)) child++;
|
||||
if (higher(child, idx))
|
||||
swap_indices(child, idx);
|
||||
else
|
||||
break;
|
||||
idx = child;
|
||||
}
|
||||
}
|
||||
|
||||
template <class AA, class Comparator>
|
||||
bool indexed_priority_queue<AA, Comparator>::higher(size_t idx1, size_t idx2) const {
|
||||
if (!(0u < idx1 && idx1 < pq.size() && 0u < idx2 && idx2 < pq.size())) {
|
||||
return false; // got to do something.
|
||||
}
|
||||
return Comparator()(pq[idx1], pq[idx2]);
|
||||
}
|
||||
|
||||
template <class AA, class Comparator>
|
||||
bool indexed_priority_queue<AA, Comparator>::contains(sp<const AA> a) const {
|
||||
if (a == nullptr) return false; // publicly, we pretend that nullptr is not actually in pq.
|
||||
return indices.count(a) > 0;
|
||||
}
|
||||
|
||||
template <class AA, class Comparator>
|
||||
void indexed_priority_queue<AA, Comparator>::swap_indices(size_t i, size_t j) {
|
||||
if (!(0u < i && i < pq.size() && 0u < j && j < pq.size())) {
|
||||
return;
|
||||
}
|
||||
sp<const AA> val_i = pq[i];
|
||||
sp<const AA> val_j = pq[j];
|
||||
pq[i] = val_j;
|
||||
pq[j] = val_i;
|
||||
indices[val_i] = j;
|
||||
indices[val_j] = i;
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#define DEBUG false // STOPSHIP if true
|
||||
#include "Log.h"
|
||||
|
||||
#include "external/Perfetto.h"
|
||||
#include "subscriber/IncidentdReporter.h"
|
||||
#include "subscriber/SubscriberReporter.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
void triggerSubscribers(int64_t ruleId, int64_t metricId, const MetricDimensionKey& dimensionKey,
|
||||
int64_t metricValue, const ConfigKey& configKey,
|
||||
const std::vector<Subscription>& subscriptions) {
|
||||
VLOG("informSubscribers called.");
|
||||
if (subscriptions.empty()) {
|
||||
VLOG("No Subscriptions were associated.");
|
||||
return;
|
||||
}
|
||||
|
||||
for (const Subscription& subscription : subscriptions) {
|
||||
if (subscription.probability_of_informing() < 1
|
||||
&& ((float)rand() / (float)RAND_MAX) >= subscription.probability_of_informing()) {
|
||||
// Note that due to float imprecision, 0.0 and 1.0 might not truly mean never/always.
|
||||
// The config writer was advised to use -0.1 and 1.1 for never/always.
|
||||
ALOGI("Fate decided that a subscriber would not be informed.");
|
||||
continue;
|
||||
}
|
||||
switch (subscription.subscriber_information_case()) {
|
||||
case Subscription::SubscriberInformationCase::kIncidentdDetails:
|
||||
if (!GenerateIncidentReport(subscription.incidentd_details(), ruleId, metricId,
|
||||
dimensionKey, metricValue, configKey)) {
|
||||
ALOGW("Failed to generate incident report.");
|
||||
}
|
||||
break;
|
||||
case Subscription::SubscriberInformationCase::kPerfettoDetails:
|
||||
if (!CollectPerfettoTraceAndUploadToDropbox(subscription.perfetto_details(),
|
||||
subscription.id(), ruleId, configKey)) {
|
||||
ALOGW("Failed to generate perfetto traces.");
|
||||
}
|
||||
break;
|
||||
case Subscription::SubscriberInformationCase::kBroadcastSubscriberDetails:
|
||||
SubscriberReporter::getInstance().alertBroadcastSubscriber(configKey, subscription,
|
||||
dimensionKey);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "config/ConfigKey.h"
|
||||
#include "HashableDimensionKey.h"
|
||||
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
void triggerSubscribers(const int64_t ruleId, const int64_t metricId,
|
||||
const MetricDimensionKey& dimensionKey, int64_t metricValue,
|
||||
const ConfigKey& configKey, const std::vector<Subscription>& subscriptions);
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,187 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#define DEBUG false // STOPSHIP if true
|
||||
#include "Log.h"
|
||||
#include "CombinationConditionTracker.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
using std::unordered_map;
|
||||
using std::vector;
|
||||
|
||||
CombinationConditionTracker::CombinationConditionTracker(const int64_t& id, const int index)
|
||||
: ConditionTracker(id, index) {
|
||||
VLOG("creating CombinationConditionTracker %lld", (long long)mConditionId);
|
||||
}
|
||||
|
||||
CombinationConditionTracker::~CombinationConditionTracker() {
|
||||
VLOG("~CombinationConditionTracker() %lld", (long long)mConditionId);
|
||||
}
|
||||
|
||||
bool CombinationConditionTracker::init(const vector<Predicate>& allConditionConfig,
|
||||
const vector<sp<ConditionTracker>>& allConditionTrackers,
|
||||
const unordered_map<int64_t, int>& conditionIdIndexMap,
|
||||
vector<bool>& stack,
|
||||
vector<ConditionState>& initialConditionCache) {
|
||||
VLOG("Combination predicate init() %lld", (long long)mConditionId);
|
||||
if (mInitialized) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// mark this node as visited in the recursion stack.
|
||||
stack[mIndex] = true;
|
||||
|
||||
Predicate_Combination combinationCondition = allConditionConfig[mIndex].combination();
|
||||
|
||||
if (!combinationCondition.has_operation()) {
|
||||
return false;
|
||||
}
|
||||
mLogicalOperation = combinationCondition.operation();
|
||||
|
||||
if (mLogicalOperation == LogicalOperation::NOT && combinationCondition.predicate_size() != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto child : combinationCondition.predicate()) {
|
||||
auto it = conditionIdIndexMap.find(child);
|
||||
|
||||
if (it == conditionIdIndexMap.end()) {
|
||||
ALOGW("Predicate %lld not found in the config", (long long)child);
|
||||
return false;
|
||||
}
|
||||
|
||||
int childIndex = it->second;
|
||||
const auto& childTracker = allConditionTrackers[childIndex];
|
||||
// if the child is a visited node in the recursion -> circle detected.
|
||||
if (stack[childIndex]) {
|
||||
ALOGW("Circle detected!!!");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool initChildSucceeded =
|
||||
childTracker->init(allConditionConfig, allConditionTrackers, conditionIdIndexMap,
|
||||
stack, initialConditionCache);
|
||||
|
||||
if (!initChildSucceeded) {
|
||||
ALOGW("Child initialization failed %lld ", (long long)child);
|
||||
return false;
|
||||
} else {
|
||||
VLOG("Child initialization success %lld ", (long long)child);
|
||||
}
|
||||
|
||||
if (allConditionTrackers[childIndex]->isSliced()) {
|
||||
setSliced(true);
|
||||
mSlicedChildren.push_back(childIndex);
|
||||
} else {
|
||||
mUnSlicedChildren.push_back(childIndex);
|
||||
}
|
||||
mChildren.push_back(childIndex);
|
||||
mTrackerIndex.insert(childTracker->getLogTrackerIndex().begin(),
|
||||
childTracker->getLogTrackerIndex().end());
|
||||
}
|
||||
|
||||
mUnSlicedPartCondition = evaluateCombinationCondition(mUnSlicedChildren, mLogicalOperation,
|
||||
initialConditionCache);
|
||||
initialConditionCache[mIndex] =
|
||||
evaluateCombinationCondition(mChildren, mLogicalOperation, initialConditionCache);
|
||||
|
||||
// unmark this node in the recursion stack.
|
||||
stack[mIndex] = false;
|
||||
|
||||
mInitialized = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CombinationConditionTracker::isConditionMet(
|
||||
const ConditionKey& conditionParameters, const vector<sp<ConditionTracker>>& allConditions,
|
||||
const bool isPartialLink,
|
||||
vector<ConditionState>& conditionCache) 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,
|
||||
isPartialLink,
|
||||
conditionCache);
|
||||
}
|
||||
}
|
||||
conditionCache[mIndex] =
|
||||
evaluateCombinationCondition(mChildren, mLogicalOperation, conditionCache);
|
||||
}
|
||||
|
||||
void CombinationConditionTracker::evaluateCondition(
|
||||
const LogEvent& event, const std::vector<MatchingState>& eventMatcherValues,
|
||||
const std::vector<sp<ConditionTracker>>& mAllConditions,
|
||||
std::vector<ConditionState>& nonSlicedConditionCache,
|
||||
std::vector<bool>& conditionChangedCache) {
|
||||
// value is up to date.
|
||||
if (nonSlicedConditionCache[mIndex] != ConditionState::kNotEvaluated) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const int childIndex : mChildren) {
|
||||
// So far, this is fine as there is at most one child having sliced output.
|
||||
if (nonSlicedConditionCache[childIndex] == ConditionState::kNotEvaluated) {
|
||||
const sp<ConditionTracker>& child = mAllConditions[childIndex];
|
||||
child->evaluateCondition(event, eventMatcherValues, mAllConditions,
|
||||
nonSlicedConditionCache, conditionChangedCache);
|
||||
}
|
||||
}
|
||||
|
||||
ConditionState newCondition =
|
||||
evaluateCombinationCondition(mChildren, mLogicalOperation, nonSlicedConditionCache);
|
||||
if (!mSliced) {
|
||||
bool nonSlicedChanged = (mUnSlicedPartCondition != newCondition);
|
||||
mUnSlicedPartCondition = newCondition;
|
||||
|
||||
nonSlicedConditionCache[mIndex] = mUnSlicedPartCondition;
|
||||
conditionChangedCache[mIndex] = nonSlicedChanged;
|
||||
} else {
|
||||
mUnSlicedPartCondition = 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.
|
||||
if (conditionChangedCache[childIndex]) {
|
||||
conditionChangedCache[mIndex] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
nonSlicedConditionCache[mIndex] = newCondition;
|
||||
VLOG("CombinationPredicate %lld sliced may changed? %d", (long long)mConditionId,
|
||||
conditionChangedCache[mIndex] == true);
|
||||
}
|
||||
}
|
||||
|
||||
bool CombinationConditionTracker::equalOutputDimensions(
|
||||
const std::vector<sp<ConditionTracker>>& allConditions,
|
||||
const vector<Matcher>& dimensions) const {
|
||||
if (mSlicedChildren.size() != 1 ||
|
||||
mSlicedChildren.front() >= (int)allConditions.size() ||
|
||||
mLogicalOperation != LogicalOperation::AND) {
|
||||
return false;
|
||||
}
|
||||
const sp<ConditionTracker>& slicedChild = allConditions.at(mSlicedChildren.front());
|
||||
return slicedChild->equalOutputDimensions(allConditions, dimensions);
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,111 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef COMBINATION_CONDITION_TRACKER_H
|
||||
#define COMBINATION_CONDITION_TRACKER_H
|
||||
|
||||
#include "ConditionTracker.h"
|
||||
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
class CombinationConditionTracker : public virtual ConditionTracker {
|
||||
public:
|
||||
CombinationConditionTracker(const int64_t& id, const int index);
|
||||
|
||||
~CombinationConditionTracker();
|
||||
|
||||
bool init(const std::vector<Predicate>& allConditionConfig,
|
||||
const std::vector<sp<ConditionTracker>>& allConditionTrackers,
|
||||
const std::unordered_map<int64_t, int>& conditionIdIndexMap, std::vector<bool>& stack,
|
||||
std::vector<ConditionState>& initialConditionCache) override;
|
||||
|
||||
void evaluateCondition(const LogEvent& event,
|
||||
const std::vector<MatchingState>& eventMatcherValues,
|
||||
const std::vector<sp<ConditionTracker>>& mAllConditions,
|
||||
std::vector<ConditionState>& conditionCache,
|
||||
std::vector<bool>& changedCache) override;
|
||||
|
||||
void isConditionMet(const ConditionKey& conditionParameters,
|
||||
const std::vector<sp<ConditionTracker>>& allConditions,
|
||||
const bool isPartialLink,
|
||||
std::vector<ConditionState>& conditionCache) const override;
|
||||
|
||||
// Only one child predicate can have dimension.
|
||||
const std::set<HashableDimensionKey>* getChangedToTrueDimensions(
|
||||
const std::vector<sp<ConditionTracker>>& allConditions) const override {
|
||||
for (const auto& child : mChildren) {
|
||||
auto result = allConditions[child]->getChangedToTrueDimensions(allConditions);
|
||||
if (result != nullptr) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Only one child predicate can have dimension.
|
||||
const std::set<HashableDimensionKey>* getChangedToFalseDimensions(
|
||||
const std::vector<sp<ConditionTracker>>& allConditions) const override {
|
||||
for (const auto& child : mChildren) {
|
||||
auto result = allConditions[child]->getChangedToFalseDimensions(allConditions);
|
||||
if (result != nullptr) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool IsSimpleCondition() const override { return false; }
|
||||
|
||||
bool IsChangedDimensionTrackable() const override {
|
||||
return mLogicalOperation == LogicalOperation::AND && mSlicedChildren.size() == 1;
|
||||
}
|
||||
|
||||
bool equalOutputDimensions(
|
||||
const std::vector<sp<ConditionTracker>>& allConditions,
|
||||
const vector<Matcher>& dimensions) const override;
|
||||
|
||||
void getTrueSlicedDimensions(
|
||||
const std::vector<sp<ConditionTracker>>& allConditions,
|
||||
std::set<HashableDimensionKey>* dimensions) const override {
|
||||
if (mSlicedChildren.size() == 1) {
|
||||
return allConditions[mSlicedChildren.front()]->getTrueSlicedDimensions(
|
||||
allConditions, dimensions);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
LogicalOperation mLogicalOperation;
|
||||
|
||||
// Store index of the children Predicates.
|
||||
// We don't store string name of the Children, because we want to get rid of the hash map to
|
||||
// map the name to object. We don't want to store smart pointers to children, because it
|
||||
// increases the risk of circular dependency and memory leak.
|
||||
std::vector<int> mChildren;
|
||||
|
||||
std::vector<int> mSlicedChildren;
|
||||
std::vector<int> mUnSlicedChildren;
|
||||
|
||||
};
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
|
||||
#endif // COMBINATION_CONDITION_TRACKER_H
|
||||
@@ -1,83 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <gtest/gtest_prod.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
/**
|
||||
* A simple stopwatch to time the duration of condition being true.
|
||||
*
|
||||
* The owner of the stopwatch (MetricProducer) is responsible to notify the stopwatch when condition
|
||||
* changes (start/pause), and when to start a new bucket (a new lap basically). All timestamps
|
||||
* should be elapsedRealTime in nano seconds.
|
||||
*
|
||||
* Keep the timer simple and inline everything. This class is *NOT* thread safe. Caller is
|
||||
* responsible for thread safety.
|
||||
*/
|
||||
class ConditionTimer {
|
||||
public:
|
||||
explicit ConditionTimer(bool initCondition, int64_t bucketStartNs) : mCondition(initCondition) {
|
||||
if (initCondition) {
|
||||
mLastConditionTrueTimestampNs = bucketStartNs;
|
||||
}
|
||||
};
|
||||
|
||||
// Tracks how long the condition has been stayed true in the *current* bucket.
|
||||
// When a new bucket is created, this value will be reset to 0.
|
||||
int64_t mTimerNs = 0;
|
||||
|
||||
// Last elapsed real timestamp when condition turned to true
|
||||
// When a new bucket is created and the condition is true, then the timestamp is set
|
||||
// to be the bucket start timestamp.
|
||||
int64_t mLastConditionTrueTimestampNs = 0;
|
||||
|
||||
bool mCondition = false;
|
||||
|
||||
int64_t newBucketStart(int64_t nextBucketStartNs) {
|
||||
if (mCondition) {
|
||||
mTimerNs += (nextBucketStartNs - mLastConditionTrueTimestampNs);
|
||||
mLastConditionTrueTimestampNs = nextBucketStartNs;
|
||||
}
|
||||
|
||||
int64_t temp = mTimerNs;
|
||||
mTimerNs = 0;
|
||||
return temp;
|
||||
}
|
||||
|
||||
void onConditionChanged(bool newCondition, int64_t timestampNs) {
|
||||
if (newCondition == mCondition) {
|
||||
return;
|
||||
}
|
||||
mCondition = newCondition;
|
||||
if (newCondition) {
|
||||
mLastConditionTrueTimestampNs = timestampNs;
|
||||
} else {
|
||||
mTimerNs += (timestampNs - mLastConditionTrueTimestampNs);
|
||||
}
|
||||
}
|
||||
|
||||
FRIEND_TEST(ConditionTimerTest, TestTimer_Inital_False);
|
||||
FRIEND_TEST(ConditionTimerTest, TestTimer_Inital_True);
|
||||
};
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,156 +0,0 @@
|
||||
/*
|
||||
* 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 "condition/condition_util.h"
|
||||
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
|
||||
#include "matchers/LogMatchingTracker.h"
|
||||
#include "matchers/matcher_util.h"
|
||||
|
||||
#include <utils/RefBase.h>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
class ConditionTracker : public virtual RefBase {
|
||||
public:
|
||||
ConditionTracker(const int64_t& id, const int index)
|
||||
: mConditionId(id),
|
||||
mIndex(index),
|
||||
mInitialized(false),
|
||||
mTrackerIndex(),
|
||||
mUnSlicedPartCondition(ConditionState::kUnknown),
|
||||
mSliced(false){};
|
||||
|
||||
virtual ~ConditionTracker(){};
|
||||
|
||||
inline const int64_t& getId() { return mConditionId; }
|
||||
|
||||
// Initialize this ConditionTracker. This initialization is done recursively (DFS). It can also
|
||||
// be done in the constructor, but we do it separately because (1) easy to return a bool to
|
||||
// indicate whether the initialization is successful. (2) makes unit test easier.
|
||||
// allConditionConfig: the list of all Predicate config from statsd_config.
|
||||
// allConditionTrackers: the list of all ConditionTrackers (this is needed because we may also
|
||||
// need to call init() on children conditions)
|
||||
// conditionIdIndexMap: the mapping from condition id to its index.
|
||||
// stack: a bit map to keep track which nodes have been visited on the stack in the recursion.
|
||||
// initialConditionCache: tracks initial conditions of all ConditionTrackers.
|
||||
virtual bool init(const std::vector<Predicate>& allConditionConfig,
|
||||
const std::vector<sp<ConditionTracker>>& allConditionTrackers,
|
||||
const std::unordered_map<int64_t, int>& conditionIdIndexMap,
|
||||
std::vector<bool>& stack,
|
||||
std::vector<ConditionState>& initialConditionCache) = 0;
|
||||
|
||||
// evaluate current condition given the new event.
|
||||
// event: the new log event
|
||||
// eventMatcherValues: the results of the LogMatcherTrackers. LogMatcherTrackers always process
|
||||
// event before ConditionTrackers, because ConditionTracker depends on
|
||||
// LogMatchingTrackers.
|
||||
// mAllConditions: the list of all ConditionTracker
|
||||
// conditionCache: the cached non-sliced condition of the ConditionTrackers for this new event.
|
||||
// conditionChanged: the bit map to record whether the condition has changed.
|
||||
// If the condition has dimension, then any sub condition changes will report
|
||||
// conditionChanged.
|
||||
virtual void evaluateCondition(const LogEvent& event,
|
||||
const std::vector<MatchingState>& eventMatcherValues,
|
||||
const std::vector<sp<ConditionTracker>>& mAllConditions,
|
||||
std::vector<ConditionState>& conditionCache,
|
||||
std::vector<bool>& conditionChanged) = 0;
|
||||
|
||||
// Query the condition with parameters.
|
||||
// [conditionParameters]: a map from condition name to the HashableDimensionKey to query the
|
||||
// condition.
|
||||
// [allConditions]: all condition trackers. This is needed because the condition evaluation is
|
||||
// done recursively
|
||||
// [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.
|
||||
virtual void isConditionMet(
|
||||
const ConditionKey& conditionParameters,
|
||||
const std::vector<sp<ConditionTracker>>& allConditions,
|
||||
const bool isPartialLink,
|
||||
std::vector<ConditionState>& conditionCache) const = 0;
|
||||
|
||||
// return the list of LogMatchingTracker index that this ConditionTracker uses.
|
||||
virtual const std::set<int>& getLogTrackerIndex() const {
|
||||
return mTrackerIndex;
|
||||
}
|
||||
|
||||
virtual void setSliced(bool sliced) {
|
||||
mSliced = mSliced | sliced;
|
||||
}
|
||||
|
||||
inline bool isSliced() const {
|
||||
return mSliced;
|
||||
}
|
||||
|
||||
virtual const std::set<HashableDimensionKey>* getChangedToTrueDimensions(
|
||||
const std::vector<sp<ConditionTracker>>& allConditions) const = 0;
|
||||
virtual const std::set<HashableDimensionKey>* getChangedToFalseDimensions(
|
||||
const std::vector<sp<ConditionTracker>>& allConditions) const = 0;
|
||||
|
||||
inline int64_t getConditionId() const {
|
||||
return mConditionId;
|
||||
}
|
||||
|
||||
virtual void getTrueSlicedDimensions(
|
||||
const std::vector<sp<ConditionTracker>>& allConditions,
|
||||
std::set<HashableDimensionKey>* dimensions) const = 0;
|
||||
|
||||
virtual bool IsChangedDimensionTrackable() const = 0;
|
||||
|
||||
virtual bool IsSimpleCondition() const = 0;
|
||||
|
||||
virtual bool equalOutputDimensions(
|
||||
const std::vector<sp<ConditionTracker>>& allConditions,
|
||||
const vector<Matcher>& dimensions) const = 0;
|
||||
|
||||
// Return the current condition state of the unsliced part of the condition.
|
||||
inline ConditionState getUnSlicedPartConditionState() const {
|
||||
return mUnSlicedPartCondition;
|
||||
}
|
||||
|
||||
protected:
|
||||
const int64_t mConditionId;
|
||||
|
||||
// the index of this condition in the manager's condition list.
|
||||
const int mIndex;
|
||||
|
||||
// if it's properly initialized.
|
||||
bool mInitialized;
|
||||
|
||||
// the list of LogMatchingTracker index that this ConditionTracker uses.
|
||||
std::set<int> mTrackerIndex;
|
||||
|
||||
// This variable is only used for CombinationConditionTrackers.
|
||||
// SimpleConditionTrackers technically don't have an unsliced part because
|
||||
// they are either sliced or unsliced.
|
||||
//
|
||||
// CombinationConditionTrackers have multiple children ConditionTrackers
|
||||
// that can be a mixture of sliced or unsliced. This tracks the
|
||||
// condition of the unsliced part of the combination condition.
|
||||
ConditionState mUnSlicedPartCondition;
|
||||
|
||||
bool mSliced;
|
||||
};
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
* 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 "ConditionWizard.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
using std::vector;
|
||||
|
||||
ConditionState ConditionWizard::query(const int index, const ConditionKey& parameters,
|
||||
const bool isPartialLink) {
|
||||
vector<ConditionState> cache(mAllConditions.size(), ConditionState::kNotEvaluated);
|
||||
|
||||
mAllConditions[index]->isConditionMet(
|
||||
parameters, mAllConditions, isPartialLink,
|
||||
cache);
|
||||
return cache[index];
|
||||
}
|
||||
|
||||
const set<HashableDimensionKey>* ConditionWizard::getChangedToTrueDimensions(
|
||||
const int index) const {
|
||||
return mAllConditions[index]->getChangedToTrueDimensions(mAllConditions);
|
||||
}
|
||||
|
||||
const set<HashableDimensionKey>* ConditionWizard::getChangedToFalseDimensions(
|
||||
const int index) const {
|
||||
return mAllConditions[index]->getChangedToFalseDimensions(mAllConditions);
|
||||
}
|
||||
|
||||
bool ConditionWizard::IsChangedDimensionTrackable(const int index) {
|
||||
if (index >= 0 && index < (int)mAllConditions.size()) {
|
||||
return mAllConditions[index]->IsChangedDimensionTrackable();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool ConditionWizard::IsSimpleCondition(const int index) {
|
||||
if (index >= 0 && index < (int)mAllConditions.size()) {
|
||||
return mAllConditions[index]->IsSimpleCondition();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool ConditionWizard::equalOutputDimensions(const int index, const vector<Matcher>& dimensions) {
|
||||
if (index >= 0 && index < (int)mAllConditions.size()) {
|
||||
return mAllConditions[index]->equalOutputDimensions(mAllConditions, dimensions);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,68 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef CONDITION_WIZARD_H
|
||||
#define CONDITION_WIZARD_H
|
||||
|
||||
#include "ConditionTracker.h"
|
||||
#include "condition_util.h"
|
||||
#include "stats_util.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
// Held by MetricProducer, to query a condition state with input defined in MetricConditionLink.
|
||||
class ConditionWizard : public virtual android::RefBase {
|
||||
public:
|
||||
ConditionWizard(){}; // for testing
|
||||
explicit ConditionWizard(std::vector<sp<ConditionTracker>>& conditionTrackers)
|
||||
: mAllConditions(conditionTrackers){};
|
||||
|
||||
virtual ~ConditionWizard(){};
|
||||
|
||||
// Query condition state, for a ConditionTracker at [conditionIndex], with [conditionParameters]
|
||||
// [conditionParameters] mapping from condition name to the HashableDimensionKey to query the
|
||||
// condition.
|
||||
// The ConditionTracker at [conditionIndex] can be a CombinationConditionTracker. In this case,
|
||||
// the conditionParameters contains the parameters for it's children SimpleConditionTrackers.
|
||||
virtual ConditionState query(const int conditionIndex, const ConditionKey& conditionParameters,
|
||||
const bool isPartialLink);
|
||||
|
||||
virtual const std::set<HashableDimensionKey>* getChangedToTrueDimensions(const int index) const;
|
||||
virtual const std::set<HashableDimensionKey>* getChangedToFalseDimensions(
|
||||
const int index) const;
|
||||
bool equalOutputDimensions(const int index, const vector<Matcher>& dimensions);
|
||||
|
||||
bool IsChangedDimensionTrackable(const int index);
|
||||
bool IsSimpleCondition(const int index);
|
||||
|
||||
ConditionState getUnSlicedPartConditionState(const int index) {
|
||||
return mAllConditions[index]->getUnSlicedPartConditionState();
|
||||
}
|
||||
void getTrueSlicedDimensions(const int index,
|
||||
std::set<HashableDimensionKey>* trueDimensions) const {
|
||||
return mAllConditions[index]->getTrueSlicedDimensions(mAllConditions, trueDimensions);
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<sp<ConditionTracker>> mAllConditions;
|
||||
};
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
#endif // CONDITION_WIZARD_H
|
||||
@@ -1,386 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#define DEBUG false // STOPSHIP if true
|
||||
#include "Log.h"
|
||||
|
||||
#include "SimpleConditionTracker.h"
|
||||
#include "guardrail/StatsdStats.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
using std::unordered_map;
|
||||
|
||||
SimpleConditionTracker::SimpleConditionTracker(
|
||||
const ConfigKey& key, const int64_t& id, const int index,
|
||||
const SimplePredicate& simplePredicate,
|
||||
const unordered_map<int64_t, int>& trackerNameIndexMap)
|
||||
: ConditionTracker(id, index), mConfigKey(key), mContainANYPositionInInternalDimensions(false) {
|
||||
VLOG("creating SimpleConditionTracker %lld", (long long)mConditionId);
|
||||
mCountNesting = simplePredicate.count_nesting();
|
||||
|
||||
if (simplePredicate.has_start()) {
|
||||
auto pair = trackerNameIndexMap.find(simplePredicate.start());
|
||||
if (pair == trackerNameIndexMap.end()) {
|
||||
ALOGW("Start matcher %lld not found in the config", (long long)simplePredicate.start());
|
||||
return;
|
||||
}
|
||||
mStartLogMatcherIndex = pair->second;
|
||||
mTrackerIndex.insert(mStartLogMatcherIndex);
|
||||
} else {
|
||||
mStartLogMatcherIndex = -1;
|
||||
}
|
||||
|
||||
if (simplePredicate.has_stop()) {
|
||||
auto pair = trackerNameIndexMap.find(simplePredicate.stop());
|
||||
if (pair == trackerNameIndexMap.end()) {
|
||||
ALOGW("Stop matcher %lld not found in the config", (long long)simplePredicate.stop());
|
||||
return;
|
||||
}
|
||||
mStopLogMatcherIndex = pair->second;
|
||||
mTrackerIndex.insert(mStopLogMatcherIndex);
|
||||
} else {
|
||||
mStopLogMatcherIndex = -1;
|
||||
}
|
||||
|
||||
if (simplePredicate.has_stop_all()) {
|
||||
auto pair = trackerNameIndexMap.find(simplePredicate.stop_all());
|
||||
if (pair == trackerNameIndexMap.end()) {
|
||||
ALOGW("Stop all matcher %lld found in the config", (long long)simplePredicate.stop_all());
|
||||
return;
|
||||
}
|
||||
mStopAllLogMatcherIndex = pair->second;
|
||||
mTrackerIndex.insert(mStopAllLogMatcherIndex);
|
||||
} else {
|
||||
mStopAllLogMatcherIndex = -1;
|
||||
}
|
||||
|
||||
if (simplePredicate.has_dimensions()) {
|
||||
translateFieldMatcher(simplePredicate.dimensions(), &mOutputDimensions);
|
||||
if (mOutputDimensions.size() > 0) {
|
||||
mSliced = true;
|
||||
mDimensionTag = mOutputDimensions[0].mMatcher.getTag();
|
||||
}
|
||||
mContainANYPositionInInternalDimensions = HasPositionANY(simplePredicate.dimensions());
|
||||
}
|
||||
|
||||
if (simplePredicate.initial_value() == SimplePredicate_InitialValue_FALSE) {
|
||||
mInitialValue = ConditionState::kFalse;
|
||||
} else {
|
||||
mInitialValue = ConditionState::kUnknown;
|
||||
}
|
||||
|
||||
mInitialized = true;
|
||||
}
|
||||
|
||||
SimpleConditionTracker::~SimpleConditionTracker() {
|
||||
VLOG("~SimpleConditionTracker()");
|
||||
}
|
||||
|
||||
bool SimpleConditionTracker::init(const vector<Predicate>& allConditionConfig,
|
||||
const vector<sp<ConditionTracker>>& allConditionTrackers,
|
||||
const unordered_map<int64_t, int>& conditionIdIndexMap,
|
||||
vector<bool>& stack,
|
||||
vector<ConditionState>& initialConditionCache) {
|
||||
// SimpleConditionTracker does not have dependency on other conditions, thus we just return
|
||||
// if the initialization was successful.
|
||||
initialConditionCache[mIndex] = mInitialValue;
|
||||
return mInitialized;
|
||||
}
|
||||
|
||||
void SimpleConditionTracker::dumpState() {
|
||||
VLOG("%lld DUMP:", (long long)mConditionId);
|
||||
for (const auto& pair : mSlicedConditionState) {
|
||||
VLOG("\t%s : %d", pair.first.toString().c_str(), pair.second);
|
||||
}
|
||||
|
||||
VLOG("Changed to true keys: \n");
|
||||
for (const auto& key : mLastChangedToTrueDimensions) {
|
||||
VLOG("%s", key.toString().c_str());
|
||||
}
|
||||
VLOG("Changed to false keys: \n");
|
||||
for (const auto& key : mLastChangedToFalseDimensions) {
|
||||
VLOG("%s", key.toString().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void SimpleConditionTracker::handleStopAll(std::vector<ConditionState>& conditionCache,
|
||||
std::vector<bool>& conditionChangedCache) {
|
||||
// Unless the default condition is false, and there was nothing started, otherwise we have
|
||||
// triggered a condition change.
|
||||
conditionChangedCache[mIndex] =
|
||||
(mInitialValue == ConditionState::kFalse && mSlicedConditionState.empty()) ? false
|
||||
: true;
|
||||
|
||||
for (const auto& cond : mSlicedConditionState) {
|
||||
if (cond.second > 0) {
|
||||
mLastChangedToFalseDimensions.insert(cond.first);
|
||||
}
|
||||
}
|
||||
|
||||
// After StopAll, we know everything has stopped. From now on, default condition is false.
|
||||
mInitialValue = ConditionState::kFalse;
|
||||
mSlicedConditionState.clear();
|
||||
conditionCache[mIndex] = ConditionState::kFalse;
|
||||
}
|
||||
|
||||
bool SimpleConditionTracker::hitGuardRail(const HashableDimensionKey& newKey) {
|
||||
if (!mSliced || mSlicedConditionState.find(newKey) != mSlicedConditionState.end()) {
|
||||
// if the condition is not sliced or the key is not new, we are good!
|
||||
return false;
|
||||
}
|
||||
// 1. Report the tuple count if the tuple count > soft limit
|
||||
if (mSlicedConditionState.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
|
||||
size_t newTupleCount = mSlicedConditionState.size() + 1;
|
||||
StatsdStats::getInstance().noteConditionDimensionSize(mConfigKey, mConditionId, newTupleCount);
|
||||
// 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.toString().c_str());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SimpleConditionTracker::handleConditionEvent(const HashableDimensionKey& outputKey,
|
||||
bool matchStart, ConditionState* conditionCache,
|
||||
bool* conditionChangedCache) {
|
||||
bool changed = false;
|
||||
auto outputIt = mSlicedConditionState.find(outputKey);
|
||||
ConditionState newCondition;
|
||||
if (hitGuardRail(outputKey)) {
|
||||
(*conditionChangedCache) = false;
|
||||
// Tells the caller it's evaluated.
|
||||
(*conditionCache) = ConditionState::kUnknown;
|
||||
return;
|
||||
}
|
||||
if (outputIt == mSlicedConditionState.end()) {
|
||||
// We get a new output key.
|
||||
newCondition = matchStart ? ConditionState::kTrue : ConditionState::kFalse;
|
||||
if (matchStart && mInitialValue != ConditionState::kTrue) {
|
||||
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[outputKey] = 0;
|
||||
mLastChangedToFalseDimensions.insert(outputKey);
|
||||
changed = true;
|
||||
}
|
||||
} else {
|
||||
// we have history about this output key.
|
||||
auto& startedCount = outputIt->second;
|
||||
// assign the old value first.
|
||||
newCondition = startedCount > 0 ? ConditionState::kTrue : ConditionState::kFalse;
|
||||
if (matchStart) {
|
||||
if (startedCount == 0) {
|
||||
mLastChangedToTrueDimensions.insert(outputKey);
|
||||
// This condition for this output key will change from false -> true
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// it's ok to do ++ here, even if we don't count nesting. The >1 counts will be treated
|
||||
// as 1 if not counting nesting.
|
||||
startedCount++;
|
||||
newCondition = ConditionState::kTrue;
|
||||
} else {
|
||||
// This is a stop event.
|
||||
if (startedCount > 0) {
|
||||
if (mCountNesting) {
|
||||
startedCount--;
|
||||
if (startedCount == 0) {
|
||||
newCondition = ConditionState::kFalse;
|
||||
}
|
||||
} else {
|
||||
// not counting nesting, so ignore the number of starts, stop now.
|
||||
startedCount = 0;
|
||||
newCondition = ConditionState::kFalse;
|
||||
}
|
||||
// if everything has stopped for this output key, condition true -> false;
|
||||
if (startedCount == 0) {
|
||||
mLastChangedToFalseDimensions.insert(outputKey);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 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.toString().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dump all dimensions for debugging
|
||||
if (DEBUG) {
|
||||
dumpState();
|
||||
}
|
||||
|
||||
(*conditionChangedCache) = changed;
|
||||
(*conditionCache) = newCondition;
|
||||
|
||||
VLOG("SimplePredicate %lld nonSlicedChange? %d", (long long)mConditionId,
|
||||
conditionChangedCache[mIndex] == true);
|
||||
}
|
||||
|
||||
void SimpleConditionTracker::evaluateCondition(
|
||||
const LogEvent& event,
|
||||
const vector<MatchingState>& eventMatcherValues,
|
||||
const vector<sp<ConditionTracker>>& mAllConditions,
|
||||
vector<ConditionState>& conditionCache,
|
||||
vector<bool>& conditionChangedCache) {
|
||||
if (conditionCache[mIndex] != ConditionState::kNotEvaluated) {
|
||||
// it has been evaluated.
|
||||
VLOG("Yes, already evaluated, %lld %d",
|
||||
(long long)mConditionId, conditionCache[mIndex]);
|
||||
return;
|
||||
}
|
||||
mLastChangedToTrueDimensions.clear();
|
||||
mLastChangedToFalseDimensions.clear();
|
||||
|
||||
if (mStopAllLogMatcherIndex >= 0 && mStopAllLogMatcherIndex < int(eventMatcherValues.size()) &&
|
||||
eventMatcherValues[mStopAllLogMatcherIndex] == MatchingState::kMatched) {
|
||||
handleStopAll(conditionCache, conditionChangedCache);
|
||||
return;
|
||||
}
|
||||
|
||||
int matchedState = -1;
|
||||
// Note: The order to evaluate the following start, stop, stop_all matters.
|
||||
// The priority of overwrite is stop_all > stop > start.
|
||||
if (mStartLogMatcherIndex >= 0 &&
|
||||
eventMatcherValues[mStartLogMatcherIndex] == MatchingState::kMatched) {
|
||||
matchedState = 1;
|
||||
}
|
||||
|
||||
if (mStopLogMatcherIndex >= 0 &&
|
||||
eventMatcherValues[mStopLogMatcherIndex] == MatchingState::kMatched) {
|
||||
matchedState = 0;
|
||||
}
|
||||
|
||||
if (matchedState < 0) {
|
||||
// The event doesn't match this condition. So we just report existing condition values.
|
||||
conditionChangedCache[mIndex] = false;
|
||||
if (mSliced) {
|
||||
// if the condition result is sliced. The overall condition is true if any of the sliced
|
||||
// condition is true
|
||||
conditionCache[mIndex] = mInitialValue;
|
||||
for (const auto& slicedCondition : mSlicedConditionState) {
|
||||
if (slicedCondition.second > 0) {
|
||||
conditionCache[mIndex] = ConditionState::kTrue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const auto& itr = mSlicedConditionState.find(DEFAULT_DIMENSION_KEY);
|
||||
if (itr == mSlicedConditionState.end()) {
|
||||
// condition not sliced, but we haven't seen the matched start or stop yet. so
|
||||
// return initial value.
|
||||
conditionCache[mIndex] = mInitialValue;
|
||||
} else {
|
||||
// return the cached condition.
|
||||
conditionCache[mIndex] =
|
||||
itr->second > 0 ? ConditionState::kTrue : ConditionState::kFalse;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ConditionState overallState = mInitialValue;
|
||||
bool overallChanged = false;
|
||||
|
||||
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 {
|
||||
ALOGE("The condition tracker should not be sliced by ANY position matcher.");
|
||||
}
|
||||
conditionCache[mIndex] = overallState;
|
||||
conditionChangedCache[mIndex] = overallChanged;
|
||||
}
|
||||
|
||||
void SimpleConditionTracker::isConditionMet(
|
||||
const ConditionKey& conditionParameters, const vector<sp<ConditionTracker>>& allConditions,
|
||||
const bool isPartialLink,
|
||||
vector<ConditionState>& conditionCache) const {
|
||||
|
||||
if (conditionCache[mIndex] != ConditionState::kNotEvaluated) {
|
||||
// it has been evaluated.
|
||||
VLOG("Yes, already evaluated, %lld %d",
|
||||
(long long)mConditionId, conditionCache[mIndex]);
|
||||
return;
|
||||
}
|
||||
const auto pair = conditionParameters.find(mConditionId);
|
||||
|
||||
if (pair == conditionParameters.end()) {
|
||||
ConditionState conditionState = ConditionState::kNotEvaluated;
|
||||
conditionState = conditionState | mInitialValue;
|
||||
if (!mSliced) {
|
||||
const auto& itr = mSlicedConditionState.find(DEFAULT_DIMENSION_KEY);
|
||||
if (itr != mSlicedConditionState.end()) {
|
||||
ConditionState sliceState =
|
||||
itr->second > 0 ? ConditionState::kTrue : ConditionState::kFalse;
|
||||
conditionState = conditionState | sliceState;
|
||||
}
|
||||
}
|
||||
conditionCache[mIndex] = conditionState;
|
||||
return;
|
||||
}
|
||||
|
||||
ConditionState conditionState = ConditionState::kNotEvaluated;
|
||||
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;
|
||||
}
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
|
||||
}
|
||||
conditionCache[mIndex] = conditionState;
|
||||
VLOG("Predicate %lld return %d", (long long)mConditionId, conditionCache[mIndex]);
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,138 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef SIMPLE_CONDITION_TRACKER_H
|
||||
#define SIMPLE_CONDITION_TRACKER_H
|
||||
|
||||
#include <gtest/gtest_prod.h>
|
||||
#include "ConditionTracker.h"
|
||||
#include "config/ConfigKey.h"
|
||||
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
|
||||
#include "stats_util.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
class SimpleConditionTracker : public virtual ConditionTracker {
|
||||
public:
|
||||
SimpleConditionTracker(const ConfigKey& key, const int64_t& id, const int index,
|
||||
const SimplePredicate& simplePredicate,
|
||||
const std::unordered_map<int64_t, int>& trackerNameIndexMap);
|
||||
|
||||
~SimpleConditionTracker();
|
||||
|
||||
bool init(const std::vector<Predicate>& allConditionConfig,
|
||||
const std::vector<sp<ConditionTracker>>& allConditionTrackers,
|
||||
const std::unordered_map<int64_t, int>& conditionIdIndexMap, std::vector<bool>& stack,
|
||||
std::vector<ConditionState>& initialConditionCache) override;
|
||||
|
||||
void evaluateCondition(const LogEvent& event,
|
||||
const std::vector<MatchingState>& eventMatcherValues,
|
||||
const std::vector<sp<ConditionTracker>>& mAllConditions,
|
||||
std::vector<ConditionState>& conditionCache,
|
||||
std::vector<bool>& changedCache) override;
|
||||
|
||||
void isConditionMet(const ConditionKey& conditionParameters,
|
||||
const std::vector<sp<ConditionTracker>>& allConditions,
|
||||
const bool isPartialLink,
|
||||
std::vector<ConditionState>& conditionCache) const override;
|
||||
|
||||
virtual const std::set<HashableDimensionKey>* getChangedToTrueDimensions(
|
||||
const std::vector<sp<ConditionTracker>>& allConditions) const {
|
||||
if (mSliced) {
|
||||
return &mLastChangedToTrueDimensions;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
virtual const std::set<HashableDimensionKey>* getChangedToFalseDimensions(
|
||||
const std::vector<sp<ConditionTracker>>& allConditions) const {
|
||||
if (mSliced) {
|
||||
return &mLastChangedToFalseDimensions;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void getTrueSlicedDimensions(
|
||||
const std::vector<sp<ConditionTracker>>& allConditions,
|
||||
std::set<HashableDimensionKey>* dimensions) const override {
|
||||
for (const auto& itr : mSlicedConditionState) {
|
||||
if (itr.second > 0) {
|
||||
dimensions->insert(itr.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool IsChangedDimensionTrackable() const override { return true; }
|
||||
|
||||
bool IsSimpleCondition() const override { return true; }
|
||||
|
||||
bool equalOutputDimensions(
|
||||
const std::vector<sp<ConditionTracker>>& allConditions,
|
||||
const vector<Matcher>& dimensions) const override {
|
||||
return equalDimensions(mOutputDimensions, dimensions);
|
||||
}
|
||||
|
||||
private:
|
||||
const ConfigKey mConfigKey;
|
||||
// The index of the LogEventMatcher which defines the start.
|
||||
int mStartLogMatcherIndex;
|
||||
|
||||
// The index of the LogEventMatcher which defines the end.
|
||||
int mStopLogMatcherIndex;
|
||||
|
||||
// if the start end needs to be nested.
|
||||
bool mCountNesting;
|
||||
|
||||
// The index of the LogEventMatcher which defines the stop all.
|
||||
int mStopAllLogMatcherIndex;
|
||||
|
||||
ConditionState mInitialValue;
|
||||
|
||||
std::vector<Matcher> mOutputDimensions;
|
||||
|
||||
bool mContainANYPositionInInternalDimensions;
|
||||
|
||||
std::set<HashableDimensionKey> mLastChangedToTrueDimensions;
|
||||
std::set<HashableDimensionKey> mLastChangedToFalseDimensions;
|
||||
|
||||
int mDimensionTag;
|
||||
|
||||
std::map<HashableDimensionKey, int> mSlicedConditionState;
|
||||
|
||||
void handleStopAll(std::vector<ConditionState>& conditionCache,
|
||||
std::vector<bool>& changedCache);
|
||||
|
||||
void handleConditionEvent(const HashableDimensionKey& outputKey, bool matchStart,
|
||||
ConditionState* conditionCache, bool* changedCache);
|
||||
|
||||
bool hitGuardRail(const HashableDimensionKey& newKey);
|
||||
|
||||
void dumpState();
|
||||
|
||||
FRIEND_TEST(SimpleConditionTrackerTest, TestSlicedCondition);
|
||||
FRIEND_TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim);
|
||||
FRIEND_TEST(SimpleConditionTrackerTest, TestStopAll);
|
||||
};
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
|
||||
#endif // SIMPLE_CONDITION_TRACKER_H
|
||||
@@ -1,93 +0,0 @@
|
||||
/*
|
||||
* 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 "Log.h"
|
||||
|
||||
#include "condition_util.h"
|
||||
|
||||
#include "../matchers/matcher_util.h"
|
||||
#include "ConditionTracker.h"
|
||||
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
|
||||
#include "stats_util.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
using std::vector;
|
||||
|
||||
|
||||
ConditionState evaluateCombinationCondition(const std::vector<int>& children,
|
||||
const LogicalOperation& operation,
|
||||
const std::vector<ConditionState>& conditionCache) {
|
||||
ConditionState newCondition;
|
||||
|
||||
bool hasUnknown = false;
|
||||
bool hasFalse = false;
|
||||
bool hasTrue = false;
|
||||
|
||||
for (auto childIndex : children) {
|
||||
ConditionState childState = conditionCache[childIndex];
|
||||
if (childState == ConditionState::kUnknown) {
|
||||
hasUnknown = true;
|
||||
break;
|
||||
}
|
||||
if (childState == ConditionState::kFalse) {
|
||||
hasFalse = true;
|
||||
}
|
||||
if (childState == ConditionState::kTrue) {
|
||||
hasTrue = true;
|
||||
}
|
||||
}
|
||||
|
||||
// If any child condition is in unknown state, the condition is unknown too.
|
||||
if (hasUnknown) {
|
||||
return ConditionState::kUnknown;
|
||||
}
|
||||
|
||||
switch (operation) {
|
||||
case LogicalOperation::AND: {
|
||||
newCondition = hasFalse ? ConditionState::kFalse : ConditionState::kTrue;
|
||||
break;
|
||||
}
|
||||
case LogicalOperation::OR: {
|
||||
newCondition = hasTrue ? ConditionState::kTrue : ConditionState::kFalse;
|
||||
break;
|
||||
}
|
||||
case LogicalOperation::NOT:
|
||||
newCondition = children.empty() ? ConditionState::kUnknown :
|
||||
((conditionCache[children[0]] == ConditionState::kFalse) ?
|
||||
ConditionState::kTrue : ConditionState::kFalse);
|
||||
break;
|
||||
case LogicalOperation::NAND:
|
||||
newCondition = hasFalse ? ConditionState::kTrue : ConditionState::kFalse;
|
||||
break;
|
||||
case LogicalOperation::NOR:
|
||||
newCondition = hasTrue ? ConditionState::kFalse : ConditionState::kTrue;
|
||||
break;
|
||||
case LogicalOperation::LOGICAL_OPERATION_UNSPECIFIED:
|
||||
newCondition = ConditionState::kFalse;
|
||||
break;
|
||||
}
|
||||
return newCondition;
|
||||
}
|
||||
|
||||
ConditionState operator|(ConditionState l, ConditionState r) {
|
||||
return l >= r ? l : r;
|
||||
}
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef CONDITION_UTIL_H
|
||||
#define CONDITION_UTIL_H
|
||||
|
||||
#include <vector>
|
||||
#include "../matchers/matcher_util.h"
|
||||
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
enum ConditionState {
|
||||
kNotEvaluated = -2,
|
||||
kUnknown = -1,
|
||||
kFalse = 0,
|
||||
kTrue = 1,
|
||||
};
|
||||
|
||||
ConditionState operator|(ConditionState l, ConditionState r);
|
||||
|
||||
ConditionState evaluateCombinationCondition(const std::vector<int>& children,
|
||||
const LogicalOperation& operation,
|
||||
const std::vector<ConditionState>& conditionCache);
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
#endif // CONDITION_UTIL_H
|
||||
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* 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 "config/ConfigKey.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
ConfigKey::ConfigKey() {
|
||||
}
|
||||
|
||||
ConfigKey::ConfigKey(const ConfigKey& that) : mId(that.mId), mUid(that.mUid) {
|
||||
}
|
||||
|
||||
ConfigKey::ConfigKey(int uid, const int64_t& id) : mId(id), mUid(uid) {
|
||||
}
|
||||
|
||||
ConfigKey::~ConfigKey() {
|
||||
}
|
||||
|
||||
string ConfigKey::ToString() const {
|
||||
string s;
|
||||
s += "(" + std::to_string(mUid) + " " + std::to_string(mId) + ")";
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
int64_t StrToInt64(const string& str) {
|
||||
char* endp;
|
||||
int64_t value;
|
||||
value = strtoll(str.c_str(), &endp, 0);
|
||||
if (endp == str.c_str() || *endp != '\0') {
|
||||
value = 0;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
* 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/statsd_config.pb.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
using std::hash;
|
||||
using std::string;
|
||||
|
||||
/**
|
||||
* Uniquely identifies a configuration.
|
||||
*/
|
||||
class ConfigKey {
|
||||
public:
|
||||
ConfigKey();
|
||||
ConfigKey(const ConfigKey& that);
|
||||
ConfigKey(int uid, const int64_t& id);
|
||||
~ConfigKey();
|
||||
|
||||
inline int GetUid() const {
|
||||
return mUid;
|
||||
}
|
||||
inline const int64_t& GetId() const {
|
||||
return mId;
|
||||
}
|
||||
|
||||
inline bool operator<(const ConfigKey& that) const {
|
||||
if (mUid < that.mUid) {
|
||||
return true;
|
||||
}
|
||||
if (mUid > that.mUid) {
|
||||
return false;
|
||||
}
|
||||
return mId < that.mId;
|
||||
};
|
||||
|
||||
inline bool operator==(const ConfigKey& that) const {
|
||||
return mUid == that.mUid && mId == that.mId;
|
||||
};
|
||||
|
||||
string ToString() const;
|
||||
|
||||
private:
|
||||
int64_t mId;
|
||||
int mUid;
|
||||
};
|
||||
|
||||
int64_t StrToInt64(const string& str);
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
|
||||
/**
|
||||
* A hash function for ConfigKey so it can be used for unordered_map/set.
|
||||
* Unfortunately this has to go in std namespace because C++ is fun!
|
||||
*/
|
||||
namespace std {
|
||||
|
||||
using android::os::statsd::ConfigKey;
|
||||
|
||||
template <>
|
||||
struct hash<ConfigKey> {
|
||||
std::size_t operator()(const ConfigKey& key) const {
|
||||
return (7 * key.GetUid()) ^ ((hash<long long>()(key.GetId())));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
@@ -1,31 +0,0 @@
|
||||
/*
|
||||
* 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 "config/ConfigListener.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
ConfigListener::ConfigListener() {
|
||||
}
|
||||
|
||||
ConfigListener::~ConfigListener() {
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* 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 "config/ConfigKey.h"
|
||||
|
||||
#include <utils/RefBase.h>
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
using android::RefBase;
|
||||
|
||||
/**
|
||||
* Callback for different subsystems inside statsd to implement to find out
|
||||
* when a configuration has been added, updated or removed.
|
||||
*/
|
||||
class ConfigListener : public virtual RefBase {
|
||||
public:
|
||||
ConfigListener();
|
||||
virtual ~ConfigListener();
|
||||
|
||||
/**
|
||||
* A configuration was added or updated.
|
||||
*/
|
||||
virtual void OnConfigUpdated(const int64_t timestampNs, const ConfigKey& key,
|
||||
const StatsdConfig& config) = 0;
|
||||
|
||||
/**
|
||||
* A configuration was removed.
|
||||
*/
|
||||
virtual void OnConfigRemoved(const ConfigKey& key) = 0;
|
||||
};
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,375 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#define DEBUG false // STOPSHIP if true
|
||||
#include "Log.h"
|
||||
|
||||
#include "config/ConfigManager.h"
|
||||
#include "storage/StorageManager.h"
|
||||
|
||||
#include "guardrail/StatsdStats.h"
|
||||
#include "stats_log_util.h"
|
||||
#include "stats_util.h"
|
||||
#include "stats_log_util.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <vector>
|
||||
#include "android-base/stringprintf.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
using std::pair;
|
||||
using std::string;
|
||||
using std::vector;
|
||||
|
||||
#define STATS_SERVICE_DIR "/data/misc/stats-service"
|
||||
|
||||
using android::base::StringPrintf;
|
||||
using std::unique_ptr;
|
||||
|
||||
struct ConfigReceiverDeathCookie {
|
||||
ConfigReceiverDeathCookie(const wp<ConfigManager>& configManager, const ConfigKey& configKey,
|
||||
const shared_ptr<IPendingIntentRef>& pir) :
|
||||
mConfigManager(configManager), mConfigKey(configKey), mPir(pir) {
|
||||
}
|
||||
|
||||
wp<ConfigManager> mConfigManager;
|
||||
ConfigKey mConfigKey;
|
||||
shared_ptr<IPendingIntentRef> mPir;
|
||||
};
|
||||
|
||||
void ConfigManager::configReceiverDied(void* cookie) {
|
||||
auto cookie_ = static_cast<ConfigReceiverDeathCookie*>(cookie);
|
||||
sp<ConfigManager> thiz = cookie_->mConfigManager.promote();
|
||||
if (!thiz) {
|
||||
return;
|
||||
}
|
||||
|
||||
ConfigKey& configKey = cookie_->mConfigKey;
|
||||
shared_ptr<IPendingIntentRef>& pir = cookie_->mPir;
|
||||
|
||||
// Erase the mapping from the config key to the config receiver (pir) if the
|
||||
// mapping still exists.
|
||||
lock_guard<mutex> lock(thiz->mMutex);
|
||||
auto it = thiz->mConfigReceivers.find(configKey);
|
||||
if (it != thiz->mConfigReceivers.end() && it->second == pir) {
|
||||
thiz->mConfigReceivers.erase(configKey);
|
||||
}
|
||||
|
||||
// The death recipient corresponding to this specific pir can never be
|
||||
// triggered again, so free up resources.
|
||||
delete cookie_;
|
||||
}
|
||||
|
||||
struct ActiveConfigChangedReceiverDeathCookie {
|
||||
ActiveConfigChangedReceiverDeathCookie(const wp<ConfigManager>& configManager, const int uid,
|
||||
const shared_ptr<IPendingIntentRef>& pir) :
|
||||
mConfigManager(configManager), mUid(uid), mPir(pir) {
|
||||
}
|
||||
|
||||
wp<ConfigManager> mConfigManager;
|
||||
int mUid;
|
||||
shared_ptr<IPendingIntentRef> mPir;
|
||||
};
|
||||
|
||||
void ConfigManager::activeConfigChangedReceiverDied(void* cookie) {
|
||||
auto cookie_ = static_cast<ActiveConfigChangedReceiverDeathCookie*>(cookie);
|
||||
sp<ConfigManager> thiz = cookie_->mConfigManager.promote();
|
||||
if (!thiz) {
|
||||
return;
|
||||
}
|
||||
|
||||
int uid = cookie_->mUid;
|
||||
shared_ptr<IPendingIntentRef>& pir = cookie_->mPir;
|
||||
|
||||
// Erase the mapping from the config key to the active config changed
|
||||
// receiver (pir) if the mapping still exists.
|
||||
lock_guard<mutex> lock(thiz->mMutex);
|
||||
auto it = thiz->mActiveConfigsChangedReceivers.find(uid);
|
||||
if (it != thiz->mActiveConfigsChangedReceivers.end() && it->second == pir) {
|
||||
thiz->mActiveConfigsChangedReceivers.erase(uid);
|
||||
}
|
||||
|
||||
// The death recipient corresponding to this specific pir can never
|
||||
// be triggered again, so free up resources.
|
||||
delete cookie_;
|
||||
}
|
||||
|
||||
ConfigManager::ConfigManager() :
|
||||
mConfigReceiverDeathRecipient(AIBinder_DeathRecipient_new(configReceiverDied)),
|
||||
mActiveConfigChangedReceiverDeathRecipient(
|
||||
AIBinder_DeathRecipient_new(activeConfigChangedReceiverDied)) {
|
||||
}
|
||||
|
||||
ConfigManager::~ConfigManager() {
|
||||
}
|
||||
|
||||
void ConfigManager::Startup() {
|
||||
map<ConfigKey, StatsdConfig> configsFromDisk;
|
||||
StorageManager::readConfigFromDisk(configsFromDisk);
|
||||
for (const auto& pair : configsFromDisk) {
|
||||
UpdateConfig(pair.first, pair.second);
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigManager::StartupForTest() {
|
||||
// Dummy function to avoid reading configs from disks for tests.
|
||||
}
|
||||
|
||||
void ConfigManager::AddListener(const sp<ConfigListener>& listener) {
|
||||
lock_guard<mutex> lock(mMutex);
|
||||
mListeners.push_back(listener);
|
||||
}
|
||||
|
||||
void ConfigManager::UpdateConfig(const ConfigKey& key, const StatsdConfig& config) {
|
||||
vector<sp<ConfigListener>> broadcastList;
|
||||
{
|
||||
lock_guard <mutex> lock(mMutex);
|
||||
|
||||
const int numBytes = config.ByteSize();
|
||||
vector<uint8_t> buffer(numBytes);
|
||||
config.SerializeToArray(&buffer[0], numBytes);
|
||||
|
||||
auto uidIt = mConfigs.find(key.GetUid());
|
||||
// GuardRail: Limit the number of configs per uid.
|
||||
if (uidIt != mConfigs.end()) {
|
||||
auto it = uidIt->second.find(key);
|
||||
if (it == uidIt->second.end() &&
|
||||
uidIt->second.size() >= StatsdStats::kMaxConfigCountPerUid) {
|
||||
ALOGE("ConfigManager: uid %d has exceeded the config count limit", key.GetUid());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if it's a duplicate config.
|
||||
if (uidIt != mConfigs.end() && uidIt->second.find(key) != uidIt->second.end() &&
|
||||
StorageManager::hasIdenticalConfig(key, buffer)) {
|
||||
// This is a duplicate config.
|
||||
ALOGI("ConfigManager This is a duplicate config %s", key.ToString().c_str());
|
||||
// Update saved file on disk. We still update timestamp of file when
|
||||
// there exists a duplicate configuration to avoid garbage collection.
|
||||
update_saved_configs_locked(key, buffer, numBytes);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update saved file on disk.
|
||||
update_saved_configs_locked(key, buffer, numBytes);
|
||||
|
||||
// Add to set.
|
||||
mConfigs[key.GetUid()].insert(key);
|
||||
|
||||
for (const sp<ConfigListener>& listener : mListeners) {
|
||||
broadcastList.push_back(listener);
|
||||
}
|
||||
}
|
||||
|
||||
const int64_t timestampNs = getElapsedRealtimeNs();
|
||||
// Tell everyone
|
||||
for (const sp<ConfigListener>& listener : broadcastList) {
|
||||
listener->OnConfigUpdated(timestampNs, key, config);
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigManager::SetConfigReceiver(const ConfigKey& key,
|
||||
const shared_ptr<IPendingIntentRef>& pir) {
|
||||
lock_guard<mutex> lock(mMutex);
|
||||
mConfigReceivers[key] = pir;
|
||||
AIBinder_linkToDeath(pir->asBinder().get(), mConfigReceiverDeathRecipient.get(),
|
||||
new ConfigReceiverDeathCookie(this, key, pir));
|
||||
}
|
||||
|
||||
void ConfigManager::RemoveConfigReceiver(const ConfigKey& key) {
|
||||
lock_guard<mutex> lock(mMutex);
|
||||
mConfigReceivers.erase(key);
|
||||
}
|
||||
|
||||
void ConfigManager::SetActiveConfigsChangedReceiver(const int uid,
|
||||
const shared_ptr<IPendingIntentRef>& pir) {
|
||||
{
|
||||
lock_guard<mutex> lock(mMutex);
|
||||
mActiveConfigsChangedReceivers[uid] = pir;
|
||||
}
|
||||
AIBinder_linkToDeath(pir->asBinder().get(), mActiveConfigChangedReceiverDeathRecipient.get(),
|
||||
new ActiveConfigChangedReceiverDeathCookie(this, uid, pir));
|
||||
}
|
||||
|
||||
void ConfigManager::RemoveActiveConfigsChangedReceiver(const int uid) {
|
||||
lock_guard<mutex> lock(mMutex);
|
||||
mActiveConfigsChangedReceivers.erase(uid);
|
||||
}
|
||||
|
||||
void ConfigManager::RemoveConfig(const ConfigKey& key) {
|
||||
vector<sp<ConfigListener>> broadcastList;
|
||||
{
|
||||
lock_guard <mutex> lock(mMutex);
|
||||
|
||||
auto uid = key.GetUid();
|
||||
auto uidIt = mConfigs.find(uid);
|
||||
if (uidIt != mConfigs.end() && uidIt->second.find(key) != uidIt->second.end()) {
|
||||
// Remove from map
|
||||
uidIt->second.erase(key);
|
||||
|
||||
for (const sp<ConfigListener>& listener : mListeners) {
|
||||
broadcastList.push_back(listener);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove from disk. There can still be a lingering file on disk so we check
|
||||
// whether or not the config was on memory.
|
||||
remove_saved_configs(key);
|
||||
}
|
||||
|
||||
for (const sp<ConfigListener>& listener:broadcastList) {
|
||||
listener->OnConfigRemoved(key);
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigManager::remove_saved_configs(const ConfigKey& key) {
|
||||
string suffix = StringPrintf("%d_%lld", key.GetUid(), (long long)key.GetId());
|
||||
StorageManager::deleteSuffixedFiles(STATS_SERVICE_DIR, suffix.c_str());
|
||||
}
|
||||
|
||||
void ConfigManager::RemoveConfigs(int uid) {
|
||||
vector<ConfigKey> removed;
|
||||
vector<sp<ConfigListener>> broadcastList;
|
||||
{
|
||||
lock_guard <mutex> lock(mMutex);
|
||||
|
||||
auto uidIt = mConfigs.find(uid);
|
||||
if (uidIt == mConfigs.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto it = uidIt->second.begin(); it != uidIt->second.end(); ++it) {
|
||||
// Remove from map
|
||||
remove_saved_configs(*it);
|
||||
removed.push_back(*it);
|
||||
}
|
||||
|
||||
mConfigs.erase(uidIt);
|
||||
|
||||
for (const sp<ConfigListener>& listener : mListeners) {
|
||||
broadcastList.push_back(listener);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove separately so if they do anything in the callback they can't mess up our iteration.
|
||||
for (auto& key : removed) {
|
||||
// Tell everyone
|
||||
for (const sp<ConfigListener>& listener:broadcastList) {
|
||||
listener->OnConfigRemoved(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigManager::RemoveAllConfigs() {
|
||||
vector<ConfigKey> removed;
|
||||
vector<sp<ConfigListener>> broadcastList;
|
||||
{
|
||||
lock_guard <mutex> lock(mMutex);
|
||||
|
||||
for (auto uidIt = mConfigs.begin(); uidIt != mConfigs.end();) {
|
||||
for (auto it = uidIt->second.begin(); it != uidIt->second.end();) {
|
||||
// Remove from map
|
||||
removed.push_back(*it);
|
||||
it = uidIt->second.erase(it);
|
||||
}
|
||||
uidIt = mConfigs.erase(uidIt);
|
||||
}
|
||||
|
||||
for (const sp<ConfigListener>& listener : mListeners) {
|
||||
broadcastList.push_back(listener);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove separately so if they do anything in the callback they can't mess up our iteration.
|
||||
for (auto& key : removed) {
|
||||
// Tell everyone
|
||||
for (const sp<ConfigListener>& listener:broadcastList) {
|
||||
listener->OnConfigRemoved(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vector<ConfigKey> ConfigManager::GetAllConfigKeys() const {
|
||||
lock_guard<mutex> lock(mMutex);
|
||||
|
||||
vector<ConfigKey> ret;
|
||||
for (auto uidIt = mConfigs.cbegin(); uidIt != mConfigs.cend(); ++uidIt) {
|
||||
for (auto it = uidIt->second.cbegin(); it != uidIt->second.cend(); ++it) {
|
||||
ret.push_back(*it);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
const shared_ptr<IPendingIntentRef> ConfigManager::GetConfigReceiver(const ConfigKey& key) const {
|
||||
lock_guard<mutex> lock(mMutex);
|
||||
|
||||
auto it = mConfigReceivers.find(key);
|
||||
if (it == mConfigReceivers.end()) {
|
||||
return nullptr;
|
||||
} else {
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
|
||||
const shared_ptr<IPendingIntentRef> ConfigManager::GetActiveConfigsChangedReceiver(const int uid)
|
||||
const {
|
||||
lock_guard<mutex> lock(mMutex);
|
||||
|
||||
auto it = mActiveConfigsChangedReceivers.find(uid);
|
||||
if (it == mActiveConfigsChangedReceivers.end()) {
|
||||
return nullptr;
|
||||
} else {
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigManager::Dump(FILE* out) {
|
||||
lock_guard<mutex> lock(mMutex);
|
||||
|
||||
fprintf(out, "CONFIGURATIONS\n");
|
||||
fprintf(out, " uid name\n");
|
||||
for (auto uidIt = mConfigs.cbegin(); uidIt != mConfigs.cend(); ++uidIt) {
|
||||
for (auto it = uidIt->second.cbegin(); it != uidIt->second.cend(); ++it) {
|
||||
fprintf(out, " %6d %lld\n", it->GetUid(), (long long)it->GetId());
|
||||
auto receiverIt = mConfigReceivers.find(*it);
|
||||
if (receiverIt != mConfigReceivers.end()) {
|
||||
fprintf(out, " -> received by PendingIntent as binder\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigManager::update_saved_configs_locked(const ConfigKey& key,
|
||||
const vector<uint8_t>& buffer,
|
||||
const int numBytes) {
|
||||
// If there is a pre-existing config with same key we should first delete it.
|
||||
remove_saved_configs(key);
|
||||
|
||||
// Then we save the latest config.
|
||||
string file_name =
|
||||
StringPrintf("%s/%ld_%d_%lld", STATS_SERVICE_DIR, time(nullptr),
|
||||
key.GetUid(), (long long)key.GetId());
|
||||
StorageManager::writeFile(file_name.c_str(), &buffer[0], numBytes);
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,181 +0,0 @@
|
||||
/*
|
||||
* 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 "config/ConfigKey.h"
|
||||
#include "config/ConfigListener.h"
|
||||
|
||||
#include <aidl/android/os/IPendingIntentRef.h>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
using aidl::android::os::IPendingIntentRef;
|
||||
using std::shared_ptr;
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
/**
|
||||
* Keeps track of which configurations have been set from various sources.
|
||||
*/
|
||||
class ConfigManager : public virtual android::RefBase {
|
||||
public:
|
||||
ConfigManager();
|
||||
virtual ~ConfigManager();
|
||||
|
||||
/**
|
||||
* Initialize ConfigListener by reading from disk and get updates.
|
||||
*/
|
||||
void Startup();
|
||||
|
||||
/*
|
||||
* Dummy initializer for tests.
|
||||
*/
|
||||
void StartupForTest();
|
||||
|
||||
/**
|
||||
* Someone else wants to know about the configs.
|
||||
*/
|
||||
void AddListener(const sp<ConfigListener>& listener);
|
||||
|
||||
/**
|
||||
* A configuration was added or updated.
|
||||
*
|
||||
* Reports this to listeners.
|
||||
*/
|
||||
void UpdateConfig(const ConfigKey& key, const StatsdConfig& data);
|
||||
|
||||
/**
|
||||
* Sets the broadcast receiver for a configuration key.
|
||||
*/
|
||||
void SetConfigReceiver(const ConfigKey& key, const shared_ptr<IPendingIntentRef>& pir);
|
||||
|
||||
/**
|
||||
* Returns the package name and class name representing the broadcast receiver for this config.
|
||||
*/
|
||||
const shared_ptr<IPendingIntentRef> GetConfigReceiver(const ConfigKey& key) const;
|
||||
|
||||
/**
|
||||
* Returns all config keys registered.
|
||||
*/
|
||||
std::vector<ConfigKey> GetAllConfigKeys() const;
|
||||
|
||||
/**
|
||||
* Erase any broadcast receiver associated with this config key.
|
||||
*/
|
||||
void RemoveConfigReceiver(const ConfigKey& key);
|
||||
|
||||
/**
|
||||
* Sets the broadcast receiver that is notified whenever the list of active configs
|
||||
* changes for this uid.
|
||||
*/
|
||||
void SetActiveConfigsChangedReceiver(const int uid, const shared_ptr<IPendingIntentRef>& pir);
|
||||
|
||||
/**
|
||||
* Returns the broadcast receiver for active configs changed for this uid.
|
||||
*/
|
||||
|
||||
const shared_ptr<IPendingIntentRef> GetActiveConfigsChangedReceiver(const int uid) const;
|
||||
|
||||
/**
|
||||
* Erase any active configs changed broadcast receiver associated with this uid.
|
||||
*/
|
||||
void RemoveActiveConfigsChangedReceiver(const int uid);
|
||||
|
||||
/**
|
||||
* A configuration was removed.
|
||||
*
|
||||
* Reports this to listeners.
|
||||
*/
|
||||
void RemoveConfig(const ConfigKey& key);
|
||||
|
||||
/**
|
||||
* Remove all of the configs for the given uid.
|
||||
*/
|
||||
void RemoveConfigs(int uid);
|
||||
|
||||
/**
|
||||
* Remove all of the configs from memory.
|
||||
*/
|
||||
void RemoveAllConfigs();
|
||||
|
||||
/**
|
||||
* Text dump of our state for debugging.
|
||||
*/
|
||||
void Dump(FILE* out);
|
||||
|
||||
private:
|
||||
mutable std::mutex mMutex;
|
||||
|
||||
/**
|
||||
* Save the configs to disk.
|
||||
*/
|
||||
void update_saved_configs_locked(const ConfigKey& key,
|
||||
const std::vector<uint8_t>& buffer,
|
||||
const int numBytes);
|
||||
|
||||
/**
|
||||
* Remove saved configs from disk.
|
||||
*/
|
||||
void remove_saved_configs(const ConfigKey& key);
|
||||
|
||||
/**
|
||||
* Maps from uid to the config keys that have been set.
|
||||
*/
|
||||
std::map<int, std::set<ConfigKey>> mConfigs;
|
||||
|
||||
/**
|
||||
* Each config key can be subscribed by up to one receiver, specified as IPendingIntentRef.
|
||||
*/
|
||||
std::map<ConfigKey, shared_ptr<IPendingIntentRef>> mConfigReceivers;
|
||||
|
||||
/**
|
||||
* Each uid can be subscribed by up to one receiver to notify that the list of active configs
|
||||
* for this uid has changed. The receiver is specified as IPendingIntentRef.
|
||||
*/
|
||||
std::map<int, shared_ptr<IPendingIntentRef>> mActiveConfigsChangedReceivers;
|
||||
|
||||
/**
|
||||
* The ConfigListeners that will be told about changes.
|
||||
*/
|
||||
std::vector<sp<ConfigListener>> mListeners;
|
||||
|
||||
// Death recipients that are triggered when the host process holding an
|
||||
// IPendingIntentRef dies.
|
||||
::ndk::ScopedAIBinder_DeathRecipient mConfigReceiverDeathRecipient;
|
||||
::ndk::ScopedAIBinder_DeathRecipient mActiveConfigChangedReceiverDeathRecipient;
|
||||
|
||||
/**
|
||||
* Death recipient callback that is called when a config receiver dies.
|
||||
* The cookie is a pointer to a ConfigReceiverDeathCookie.
|
||||
*/
|
||||
static void configReceiverDied(void* cookie);
|
||||
|
||||
/**
|
||||
* Death recipient callback that is called when an active config changed
|
||||
* receiver dies. The cookie is a pointer to an
|
||||
* ActiveConfigChangedReceiverDeathCookie.
|
||||
*/
|
||||
static void activeConfigChangedReceiverDied(void* cookie);
|
||||
};
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
syntax = "proto2";
|
||||
|
||||
package android.os.statsd;
|
||||
|
||||
option java_package = "com.android.internal.os";
|
||||
option java_outer_classname = "ExperimentIdsProto";
|
||||
|
||||
// StatsLogProcessor uses the proto to parse experiment ids from
|
||||
// BinaryPushStateChanged atoms. This needs to be in sync with
|
||||
// TrainExperimentIds in atoms.proto.
|
||||
message ExperimentIds {
|
||||
repeated int64 experiment_id = 1;
|
||||
}
|
||||
139
cmds/statsd/src/external/Perfetto.cpp
vendored
139
cmds/statsd/src/external/Perfetto.cpp
vendored
@@ -1,139 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#define DEBUG false // STOPSHIP if true
|
||||
#include "config/ConfigKey.h"
|
||||
#include "Log.h"
|
||||
|
||||
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" // Alert
|
||||
|
||||
#include <android-base/unique_fd.h>
|
||||
#include <inttypes.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
const char kDropboxTag[] = "perfetto";
|
||||
}
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
bool CollectPerfettoTraceAndUploadToDropbox(const PerfettoDetails& config,
|
||||
int64_t subscription_id,
|
||||
int64_t alert_id,
|
||||
const ConfigKey& configKey) {
|
||||
VLOG("Starting trace collection through perfetto");
|
||||
|
||||
if (!config.has_trace_config()) {
|
||||
ALOGE("The perfetto trace config is empty, aborting");
|
||||
return false;
|
||||
}
|
||||
|
||||
char subscriptionId[25];
|
||||
char alertId[25];
|
||||
char configId[25];
|
||||
char configUid[25];
|
||||
snprintf(subscriptionId, sizeof(subscriptionId), "%" PRId64, subscription_id);
|
||||
snprintf(alertId, sizeof(alertId), "%" PRId64, alert_id);
|
||||
snprintf(configId, sizeof(configId), "%" PRId64, configKey.GetId());
|
||||
snprintf(configUid, sizeof(configUid), "%d", configKey.GetUid());
|
||||
|
||||
android::base::unique_fd readPipe;
|
||||
android::base::unique_fd writePipe;
|
||||
if (!android::base::Pipe(&readPipe, &writePipe)) {
|
||||
ALOGE("pipe() failed while calling the Perfetto client: %s", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
pid_t pid = fork();
|
||||
if (pid < 0) {
|
||||
ALOGE("fork() failed while calling the Perfetto client: %s", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pid == 0) {
|
||||
// Child process.
|
||||
|
||||
// No malloc calls or library calls after this point. Remember that even
|
||||
// ALOGx (aka android_printLog()) can use dynamic memory for vsprintf().
|
||||
|
||||
writePipe.reset(); // Close the write end (owned by the main process).
|
||||
|
||||
// Replace stdin with |readPipe| so the main process can write into it.
|
||||
if (dup2(readPipe.get(), STDIN_FILENO) < 0) _exit(1);
|
||||
readPipe.reset();
|
||||
|
||||
// Replace stdout/stderr with /dev/null and close any other file
|
||||
// descriptor. This is to avoid SELinux complaining about perfetto
|
||||
// trying to access files accidentally left open by statsd (i.e. files
|
||||
// that have been opened without the O_CLOEXEC flag).
|
||||
int devNullFd = open("/dev/null", O_RDWR | O_CLOEXEC);
|
||||
if (dup2(devNullFd, STDOUT_FILENO) < 0) _exit(2);
|
||||
if (dup2(devNullFd, STDERR_FILENO) < 0) _exit(3);
|
||||
close(devNullFd);
|
||||
for (int i = 0; i < 1024; i++) {
|
||||
if (i != STDIN_FILENO && i != STDOUT_FILENO && i != STDERR_FILENO) close(i);
|
||||
}
|
||||
|
||||
execl("/system/bin/perfetto", "perfetto", "--background", "--config", "-", "--dropbox",
|
||||
kDropboxTag, "--alert-id", alertId, "--config-id", configId, "--config-uid",
|
||||
configUid, "--subscription-id", subscriptionId, nullptr);
|
||||
|
||||
// execl() doesn't return in case of success, if we get here something
|
||||
// failed.
|
||||
_exit(4);
|
||||
}
|
||||
|
||||
// Main process.
|
||||
|
||||
readPipe.reset(); // Close the read end (owned by the child process).
|
||||
|
||||
// Using fdopen() because fwrite() has the right logic to chunking write()
|
||||
// over a pipe (see __sfvwrite()).
|
||||
FILE* writePipeStream = android::base::Fdopen(std::move(writePipe), "wb");
|
||||
if (!writePipeStream) {
|
||||
ALOGE("fdopen() failed while calling the Perfetto client: %s", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string& cfgProto = config.trace_config();
|
||||
size_t bytesWritten = fwrite(cfgProto.data(), 1, cfgProto.size(), writePipeStream);
|
||||
fclose(writePipeStream);
|
||||
if (bytesWritten != cfgProto.size() || cfgProto.size() == 0) {
|
||||
ALOGE("fwrite() failed (ret: %zd) while calling the Perfetto client: %s", bytesWritten,
|
||||
strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
// This does NOT wait for the full duration of the trace. It just waits until
|
||||
// the process has read the config from stdin and detached.
|
||||
int childStatus = 0;
|
||||
waitpid(pid, &childStatus, 0);
|
||||
if (!WIFEXITED(childStatus) || WEXITSTATUS(childStatus) != 0) {
|
||||
ALOGE("Child process failed (0x%x) while calling the Perfetto client", childStatus);
|
||||
return false;
|
||||
}
|
||||
|
||||
VLOG("CollectPerfettoTraceAndUploadToDropbox() succeeded");
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
37
cmds/statsd/src/external/Perfetto.h
vendored
37
cmds/statsd/src/external/Perfetto.h
vendored
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
class ConfigKey;
|
||||
class PerfettoDetails; // Declared in statsd_config.pb.h
|
||||
|
||||
// Starts the collection of a Perfetto trace with the given |config|.
|
||||
// The trace is uploaded to Dropbox by the perfetto cmdline util once done.
|
||||
// This method returns immediately after passing the config and does NOT wait
|
||||
// for the full duration of the trace.
|
||||
bool CollectPerfettoTraceAndUploadToDropbox(const PerfettoDetails& config,
|
||||
int64_t subscription_id,
|
||||
int64_t alert_id,
|
||||
const ConfigKey& configKey);
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
41
cmds/statsd/src/external/PullDataReceiver.h
vendored
41
cmds/statsd/src/external/PullDataReceiver.h
vendored
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* 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 <utils/RefBase.h>
|
||||
#include "StatsPuller.h"
|
||||
#include "logd/LogEvent.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
class PullDataReceiver : virtual public RefBase{
|
||||
public:
|
||||
virtual ~PullDataReceiver() {}
|
||||
/**
|
||||
* @param data The pulled data.
|
||||
* @param pullSuccess Whether the pull succeeded. If the pull does not succeed, the data for the
|
||||
* bucket should be invalidated.
|
||||
* @param originalPullTimeNs This is when all the pulls have been initiated (elapsed time).
|
||||
*/
|
||||
virtual void onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& data,
|
||||
bool pullSuccess, int64_t originalPullTimeNs) = 0;
|
||||
};
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
39
cmds/statsd/src/external/PullResultReceiver.cpp
vendored
39
cmds/statsd/src/external/PullResultReceiver.cpp
vendored
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "PullResultReceiver.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
PullResultReceiver::PullResultReceiver(
|
||||
std::function<void(int32_t, bool, const vector<StatsEventParcel>&)> pullFinishCb)
|
||||
: pullFinishCallback(std::move(pullFinishCb)) {
|
||||
}
|
||||
|
||||
Status PullResultReceiver::pullFinished(int32_t atomTag, bool success,
|
||||
const vector<StatsEventParcel>& output) {
|
||||
pullFinishCallback(atomTag, success, output);
|
||||
return Status::ok();
|
||||
}
|
||||
|
||||
PullResultReceiver::~PullResultReceiver() {
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
48
cmds/statsd/src/external/PullResultReceiver.h
vendored
48
cmds/statsd/src/external/PullResultReceiver.h
vendored
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <aidl/android/os/BnPullAtomResultReceiver.h>
|
||||
#include <aidl/android/util/StatsEventParcel.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
using Status = ::ndk::ScopedAStatus;
|
||||
using aidl::android::os::BnPullAtomResultReceiver;
|
||||
using aidl::android::util::StatsEventParcel;
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
class PullResultReceiver : public BnPullAtomResultReceiver {
|
||||
public:
|
||||
PullResultReceiver(function<void(int32_t, bool, const vector<StatsEventParcel>&)>
|
||||
pullFinishCallback);
|
||||
~PullResultReceiver();
|
||||
|
||||
/**
|
||||
* Binder call for finishing a pull.
|
||||
*/
|
||||
Status pullFinished(int32_t atomTag, bool success,
|
||||
const vector<StatsEventParcel>& output) override;
|
||||
|
||||
private:
|
||||
function<void(int32_t, bool, const vector<StatsEventParcel>&)> pullFinishCallback;
|
||||
};
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
39
cmds/statsd/src/external/PullUidProvider.h
vendored
39
cmds/statsd/src/external/PullUidProvider.h
vendored
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <utils/RefBase.h>
|
||||
|
||||
#include "StatsPuller.h"
|
||||
#include "logd/LogEvent.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
class PullUidProvider : virtual public RefBase {
|
||||
public:
|
||||
virtual ~PullUidProvider() {}
|
||||
|
||||
/**
|
||||
* @param atomId The atom for which to get the uids.
|
||||
*/
|
||||
virtual vector<int32_t> getPullAtomUids(int32_t atomId) = 0;
|
||||
};
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
116
cmds/statsd/src/external/StatsCallbackPuller.cpp
vendored
116
cmds/statsd/src/external/StatsCallbackPuller.cpp
vendored
@@ -1,116 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#define DEBUG false // STOPSHIP if true
|
||||
#include "Log.h"
|
||||
|
||||
#include "StatsCallbackPuller.h"
|
||||
#include "PullResultReceiver.h"
|
||||
#include "StatsPullerManager.h"
|
||||
#include "logd/LogEvent.h"
|
||||
#include "stats_log_util.h"
|
||||
|
||||
#include <aidl/android/util/StatsEventParcel.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
using Status = ::ndk::ScopedAStatus;
|
||||
using aidl::android::util::StatsEventParcel;
|
||||
using ::ndk::SharedRefBase;
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
StatsCallbackPuller::StatsCallbackPuller(int tagId, const shared_ptr<IPullAtomCallback>& callback,
|
||||
const int64_t coolDownNs, int64_t timeoutNs,
|
||||
const vector<int> additiveFields)
|
||||
: StatsPuller(tagId, coolDownNs, timeoutNs, additiveFields), mCallback(callback) {
|
||||
VLOG("StatsCallbackPuller created for tag %d", tagId);
|
||||
}
|
||||
|
||||
bool StatsCallbackPuller::PullInternal(vector<shared_ptr<LogEvent>>* data) {
|
||||
VLOG("StatsCallbackPuller called for tag %d", mTagId);
|
||||
if(mCallback == nullptr) {
|
||||
ALOGW("No callback registered");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Shared variables needed in the result receiver.
|
||||
shared_ptr<mutex> cv_mutex = make_shared<mutex>();
|
||||
shared_ptr<condition_variable> cv = make_shared<condition_variable>();
|
||||
shared_ptr<bool> pullFinish = make_shared<bool>(false);
|
||||
shared_ptr<bool> pullSuccess = make_shared<bool>(false);
|
||||
shared_ptr<vector<shared_ptr<LogEvent>>> sharedData =
|
||||
make_shared<vector<shared_ptr<LogEvent>>>();
|
||||
|
||||
shared_ptr<PullResultReceiver> resultReceiver = SharedRefBase::make<PullResultReceiver>(
|
||||
[cv_mutex, cv, pullFinish, pullSuccess, sharedData](
|
||||
int32_t atomTag, bool success, const vector<StatsEventParcel>& output) {
|
||||
// This is the result of the pull, executing in a statsd binder thread.
|
||||
// The pull could have taken a long time, and we should only modify
|
||||
// data (the output param) if the pointer is in scope and the pull did not time out.
|
||||
{
|
||||
lock_guard<mutex> lk(*cv_mutex);
|
||||
for (const StatsEventParcel& parcel: output) {
|
||||
shared_ptr<LogEvent> event = make_shared<LogEvent>(/*uid=*/-1, /*pid=*/-1);
|
||||
bool valid = event->parseBuffer((uint8_t*)parcel.buffer.data(),
|
||||
parcel.buffer.size());
|
||||
if (valid) {
|
||||
sharedData->push_back(event);
|
||||
} else {
|
||||
StatsdStats::getInstance().noteAtomError(event->GetTagId(),
|
||||
/*pull=*/true);
|
||||
}
|
||||
}
|
||||
*pullSuccess = success;
|
||||
*pullFinish = true;
|
||||
}
|
||||
cv->notify_one();
|
||||
});
|
||||
|
||||
// Initiate the pull. This is a oneway call to a different process, except
|
||||
// in unit tests. In process calls are not oneway.
|
||||
Status status = mCallback->onPullAtom(mTagId, resultReceiver);
|
||||
if (!status.isOk()) {
|
||||
StatsdStats::getInstance().notePullBinderCallFailed(mTagId);
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
unique_lock<mutex> unique_lk(*cv_mutex);
|
||||
// Wait until the pull finishes, or until the pull timeout.
|
||||
cv->wait_for(unique_lk, chrono::nanoseconds(mPullTimeoutNs),
|
||||
[pullFinish] { return *pullFinish; });
|
||||
if (!*pullFinish) {
|
||||
// Note: The parent stats puller will also note that there was a timeout and that the
|
||||
// cache should be cleared. Once we migrate all pullers to this callback, we could
|
||||
// consolidate the logic.
|
||||
return true;
|
||||
} else {
|
||||
// Only copy the data if we did not timeout and the pull was successful.
|
||||
if (*pullSuccess) {
|
||||
*data = std::move(*sharedData);
|
||||
}
|
||||
VLOG("StatsCallbackPuller::pull succeeded for %d", mTagId);
|
||||
return *pullSuccess;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
46
cmds/statsd/src/external/StatsCallbackPuller.h
vendored
46
cmds/statsd/src/external/StatsCallbackPuller.h
vendored
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <aidl/android/os/IPullAtomCallback.h>
|
||||
#include "StatsPuller.h"
|
||||
|
||||
using aidl::android::os::IPullAtomCallback;
|
||||
using std::shared_ptr;
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
class StatsCallbackPuller : public StatsPuller {
|
||||
public:
|
||||
explicit StatsCallbackPuller(int tagId, const shared_ptr<IPullAtomCallback>& callback,
|
||||
const int64_t coolDownNs, const int64_t timeoutNs,
|
||||
const std::vector<int> additiveFields);
|
||||
|
||||
private:
|
||||
bool PullInternal(vector<std::shared_ptr<LogEvent>>* data) override;
|
||||
const shared_ptr<IPullAtomCallback> mCallback;
|
||||
|
||||
FRIEND_TEST(StatsCallbackPullerTest, PullFail);
|
||||
FRIEND_TEST(StatsCallbackPullerTest, PullSuccess);
|
||||
FRIEND_TEST(StatsCallbackPullerTest, PullTimeout);
|
||||
};
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
126
cmds/statsd/src/external/StatsPuller.cpp
vendored
126
cmds/statsd/src/external/StatsPuller.cpp
vendored
@@ -1,126 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#define DEBUG false // STOPSHIP if true
|
||||
#include "Log.h"
|
||||
|
||||
#include "StatsPuller.h"
|
||||
#include "StatsPullerManager.h"
|
||||
#include "guardrail/StatsdStats.h"
|
||||
#include "puller_util.h"
|
||||
#include "stats_log_util.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
using std::lock_guard;
|
||||
|
||||
sp<UidMap> StatsPuller::mUidMap = nullptr;
|
||||
void StatsPuller::SetUidMap(const sp<UidMap>& uidMap) { mUidMap = uidMap; }
|
||||
|
||||
StatsPuller::StatsPuller(const int tagId, const int64_t coolDownNs, const int64_t pullTimeoutNs,
|
||||
const std::vector<int> additiveFields)
|
||||
: mTagId(tagId),
|
||||
mPullTimeoutNs(pullTimeoutNs),
|
||||
mCoolDownNs(coolDownNs),
|
||||
mAdditiveFields(additiveFields),
|
||||
mLastPullTimeNs(0),
|
||||
mLastEventTimeNs(0) {
|
||||
}
|
||||
|
||||
bool StatsPuller::Pull(const int64_t eventTimeNs, std::vector<std::shared_ptr<LogEvent>>* data) {
|
||||
lock_guard<std::mutex> lock(mLock);
|
||||
const int64_t elapsedTimeNs = getElapsedRealtimeNs();
|
||||
const int64_t systemUptimeMillis = getSystemUptimeMillis();
|
||||
StatsdStats::getInstance().notePull(mTagId);
|
||||
const bool shouldUseCache =
|
||||
(mLastEventTimeNs == eventTimeNs) || (elapsedTimeNs - mLastPullTimeNs < mCoolDownNs);
|
||||
if (shouldUseCache) {
|
||||
if (mHasGoodData) {
|
||||
(*data) = mCachedData;
|
||||
StatsdStats::getInstance().notePullFromCache(mTagId);
|
||||
|
||||
}
|
||||
return mHasGoodData;
|
||||
}
|
||||
if (mLastPullTimeNs > 0) {
|
||||
StatsdStats::getInstance().updateMinPullIntervalSec(
|
||||
mTagId, (elapsedTimeNs - mLastPullTimeNs) / NS_PER_SEC);
|
||||
}
|
||||
mCachedData.clear();
|
||||
mLastPullTimeNs = elapsedTimeNs;
|
||||
mLastEventTimeNs = eventTimeNs;
|
||||
mHasGoodData = PullInternal(&mCachedData);
|
||||
if (!mHasGoodData) {
|
||||
return mHasGoodData;
|
||||
}
|
||||
const int64_t pullElapsedDurationNs = getElapsedRealtimeNs() - elapsedTimeNs;
|
||||
const int64_t pullSystemUptimeDurationMillis = getSystemUptimeMillis() - systemUptimeMillis;
|
||||
StatsdStats::getInstance().notePullTime(mTagId, pullElapsedDurationNs);
|
||||
const bool pullTimeOut = pullElapsedDurationNs > mPullTimeoutNs;
|
||||
if (pullTimeOut) {
|
||||
// Something went wrong. Discard the data.
|
||||
mCachedData.clear();
|
||||
mHasGoodData = false;
|
||||
StatsdStats::getInstance().notePullTimeout(
|
||||
mTagId, pullSystemUptimeDurationMillis, NanoToMillis(pullElapsedDurationNs));
|
||||
ALOGW("Pull for atom %d exceeds timeout %lld nano seconds.", mTagId,
|
||||
(long long)pullElapsedDurationNs);
|
||||
return mHasGoodData;
|
||||
}
|
||||
|
||||
if (mCachedData.size() > 0) {
|
||||
mapAndMergeIsolatedUidsToHostUid(mCachedData, mUidMap, mTagId, mAdditiveFields);
|
||||
}
|
||||
|
||||
if (mCachedData.empty()) {
|
||||
VLOG("Data pulled is empty");
|
||||
StatsdStats::getInstance().noteEmptyData(mTagId);
|
||||
}
|
||||
|
||||
(*data) = mCachedData;
|
||||
return mHasGoodData;
|
||||
}
|
||||
|
||||
int StatsPuller::ForceClearCache() {
|
||||
return clearCache();
|
||||
}
|
||||
|
||||
int StatsPuller::clearCache() {
|
||||
lock_guard<std::mutex> lock(mLock);
|
||||
return clearCacheLocked();
|
||||
}
|
||||
|
||||
int StatsPuller::clearCacheLocked() {
|
||||
int ret = mCachedData.size();
|
||||
mCachedData.clear();
|
||||
mLastPullTimeNs = 0;
|
||||
mLastEventTimeNs = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
int StatsPuller::ClearCacheIfNecessary(int64_t timestampNs) {
|
||||
if (timestampNs - mLastPullTimeNs > mCoolDownNs) {
|
||||
return clearCache();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
119
cmds/statsd/src/external/StatsPuller.h
vendored
119
cmds/statsd/src/external/StatsPuller.h
vendored
@@ -1,119 +0,0 @@
|
||||
/*
|
||||
* 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 <aidl/android/os/IStatsCompanionService.h>
|
||||
#include <utils/RefBase.h>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include "packages/UidMap.h"
|
||||
|
||||
#include "guardrail/StatsdStats.h"
|
||||
#include "logd/LogEvent.h"
|
||||
#include "puller_util.h"
|
||||
|
||||
using aidl::android::os::IStatsCompanionService;
|
||||
using std::shared_ptr;
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
class StatsPuller : public virtual RefBase {
|
||||
public:
|
||||
explicit StatsPuller(const int tagId,
|
||||
const int64_t coolDownNs = NS_PER_SEC,
|
||||
const int64_t pullTimeoutNs = StatsdStats::kPullMaxDelayNs,
|
||||
const std::vector<int> additiveFields = std::vector<int>());
|
||||
|
||||
virtual ~StatsPuller() {}
|
||||
|
||||
// Pulls the most recent data.
|
||||
// The data may be served from cache if consecutive pulls come within
|
||||
// predefined cooldown time.
|
||||
// Returns true if the pull was successful.
|
||||
// Returns false when
|
||||
// 1) the pull fails
|
||||
// 2) pull takes longer than mPullTimeoutNs (intrinsic to puller)
|
||||
// If a metric wants to make any change to the data, like timestamps, it
|
||||
// should make a copy as this data may be shared with multiple metrics.
|
||||
bool Pull(const int64_t eventTimeNs, std::vector<std::shared_ptr<LogEvent>>* data);
|
||||
|
||||
// Clear cache immediately
|
||||
int ForceClearCache();
|
||||
|
||||
// Clear cache if elapsed time is more than cooldown time
|
||||
int ClearCacheIfNecessary(int64_t timestampNs);
|
||||
|
||||
static void SetUidMap(const sp<UidMap>& uidMap);
|
||||
|
||||
virtual void SetStatsCompanionService(
|
||||
shared_ptr<IStatsCompanionService> statsCompanionService) {};
|
||||
|
||||
protected:
|
||||
const int mTagId;
|
||||
|
||||
// Max time allowed to pull this atom.
|
||||
// We cannot reliably kill a pull thread. So we don't terminate the puller.
|
||||
// The data is discarded if the pull takes longer than this and mHasGoodData
|
||||
// marked as false.
|
||||
const int64_t mPullTimeoutNs = StatsdStats::kPullMaxDelayNs;
|
||||
|
||||
private:
|
||||
mutable std::mutex mLock;
|
||||
|
||||
// Real puller impl.
|
||||
virtual bool PullInternal(std::vector<std::shared_ptr<LogEvent>>* data) = 0;
|
||||
|
||||
bool mHasGoodData = false;
|
||||
|
||||
// Minimum time before this puller does actual pull again.
|
||||
// Pullers can cause significant impact to system health and battery.
|
||||
// So that we don't pull too frequently.
|
||||
// If a pull request comes before cooldown, a cached version from previous pull
|
||||
// will be returned.
|
||||
const int64_t mCoolDownNs = 1 * NS_PER_SEC;
|
||||
|
||||
// The field numbers of the fields that need to be summed when merging
|
||||
// isolated uid with host uid.
|
||||
const std::vector<int> mAdditiveFields;
|
||||
|
||||
int64_t mLastPullTimeNs;
|
||||
|
||||
// All pulls happen due to an event (app upgrade, bucket boundary, condition change, etc).
|
||||
// If multiple pulls need to be done at the same event time, we will always use the cache after
|
||||
// the first pull.
|
||||
int64_t mLastEventTimeNs;
|
||||
|
||||
// Cache of data from last pull. If next request comes before cool down finishes,
|
||||
// cached data will be returned.
|
||||
// Cached data is cleared when
|
||||
// 1) A pull fails
|
||||
// 2) A new pull request comes after cooldown time.
|
||||
// 3) clearCache is called.
|
||||
std::vector<std::shared_ptr<LogEvent>> mCachedData;
|
||||
|
||||
int clearCache();
|
||||
|
||||
int clearCacheLocked();
|
||||
|
||||
static sp<UidMap> mUidMap;
|
||||
};
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
371
cmds/statsd/src/external/StatsPullerManager.cpp
vendored
371
cmds/statsd/src/external/StatsPullerManager.cpp
vendored
@@ -1,371 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#define DEBUG false
|
||||
#include "Log.h"
|
||||
|
||||
#include "StatsPullerManager.h"
|
||||
|
||||
#include <cutils/log.h>
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
|
||||
#include "../StatsService.h"
|
||||
#include "../logd/LogEvent.h"
|
||||
#include "../stats_log_util.h"
|
||||
#include "../statscompanion_util.h"
|
||||
#include "StatsCallbackPuller.h"
|
||||
#include "TrainInfoPuller.h"
|
||||
#include "statslog_statsd.h"
|
||||
|
||||
using std::shared_ptr;
|
||||
using std::vector;
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
// Stores the puller as a wp to avoid holding a reference in case it is unregistered and
|
||||
// pullAtomCallbackDied is never called.
|
||||
struct PullAtomCallbackDeathCookie {
|
||||
PullAtomCallbackDeathCookie(const wp<StatsPullerManager>& pullerManager,
|
||||
const PullerKey& pullerKey, const wp<StatsPuller>& puller) :
|
||||
mPullerManager(pullerManager), mPullerKey(pullerKey), mPuller(puller) {
|
||||
}
|
||||
|
||||
wp<StatsPullerManager> mPullerManager;
|
||||
PullerKey mPullerKey;
|
||||
wp<StatsPuller> mPuller;
|
||||
};
|
||||
|
||||
void StatsPullerManager::pullAtomCallbackDied(void* cookie) {
|
||||
PullAtomCallbackDeathCookie* cookie_ = static_cast<PullAtomCallbackDeathCookie*>(cookie);
|
||||
sp<StatsPullerManager> thiz = cookie_->mPullerManager.promote();
|
||||
if (!thiz) {
|
||||
return;
|
||||
}
|
||||
|
||||
const PullerKey& pullerKey = cookie_->mPullerKey;
|
||||
wp<StatsPuller> puller = cookie_->mPuller;
|
||||
|
||||
// Erase the mapping from the puller key to the puller if the mapping still exists.
|
||||
// Note that we are removing the StatsPuller object, which internally holds the binder
|
||||
// IPullAtomCallback. However, each new registration creates a new StatsPuller, so this works.
|
||||
lock_guard<mutex> lock(thiz->mLock);
|
||||
const auto& it = thiz->kAllPullAtomInfo.find(pullerKey);
|
||||
if (it != thiz->kAllPullAtomInfo.end() && puller != nullptr && puller == it->second) {
|
||||
StatsdStats::getInstance().notePullerCallbackRegistrationChanged(pullerKey.atomTag,
|
||||
/*registered=*/false);
|
||||
thiz->kAllPullAtomInfo.erase(pullerKey);
|
||||
}
|
||||
// The death recipient corresponding to this specific IPullAtomCallback can never
|
||||
// be triggered again, so free up resources.
|
||||
delete cookie_;
|
||||
}
|
||||
|
||||
// Values smaller than this may require to update the alarm.
|
||||
const int64_t NO_ALARM_UPDATE = INT64_MAX;
|
||||
|
||||
StatsPullerManager::StatsPullerManager()
|
||||
: kAllPullAtomInfo({
|
||||
// TrainInfo.
|
||||
{{.atomTag = util::TRAIN_INFO, .uid = AID_STATSD}, new TrainInfoPuller()},
|
||||
}),
|
||||
mNextPullTimeNs(NO_ALARM_UPDATE),
|
||||
mPullAtomCallbackDeathRecipient(AIBinder_DeathRecipient_new(pullAtomCallbackDied)) {
|
||||
}
|
||||
|
||||
bool StatsPullerManager::Pull(int tagId, const ConfigKey& configKey, const int64_t eventTimeNs,
|
||||
vector<shared_ptr<LogEvent>>* data) {
|
||||
std::lock_guard<std::mutex> _l(mLock);
|
||||
return PullLocked(tagId, configKey, eventTimeNs, data);
|
||||
}
|
||||
|
||||
bool StatsPullerManager::Pull(int tagId, const vector<int32_t>& uids, const int64_t eventTimeNs,
|
||||
vector<std::shared_ptr<LogEvent>>* data) {
|
||||
std::lock_guard<std::mutex> _l(mLock);
|
||||
return PullLocked(tagId, uids, eventTimeNs, data);
|
||||
}
|
||||
|
||||
bool StatsPullerManager::PullLocked(int tagId, const ConfigKey& configKey,
|
||||
const int64_t eventTimeNs, vector<shared_ptr<LogEvent>>* data) {
|
||||
vector<int32_t> uids;
|
||||
const auto& uidProviderIt = mPullUidProviders.find(configKey);
|
||||
if (uidProviderIt == mPullUidProviders.end()) {
|
||||
ALOGE("Error pulling tag %d. No pull uid provider for config key %s", tagId,
|
||||
configKey.ToString().c_str());
|
||||
StatsdStats::getInstance().notePullUidProviderNotFound(tagId);
|
||||
return false;
|
||||
}
|
||||
sp<PullUidProvider> pullUidProvider = uidProviderIt->second.promote();
|
||||
if (pullUidProvider == nullptr) {
|
||||
ALOGE("Error pulling tag %d, pull uid provider for config %s is gone.", tagId,
|
||||
configKey.ToString().c_str());
|
||||
StatsdStats::getInstance().notePullUidProviderNotFound(tagId);
|
||||
return false;
|
||||
}
|
||||
uids = pullUidProvider->getPullAtomUids(tagId);
|
||||
return PullLocked(tagId, uids, eventTimeNs, data);
|
||||
}
|
||||
|
||||
bool StatsPullerManager::PullLocked(int tagId, const vector<int32_t>& uids,
|
||||
const int64_t eventTimeNs, vector<shared_ptr<LogEvent>>* data) {
|
||||
VLOG("Initiating pulling %d", tagId);
|
||||
for (int32_t uid : uids) {
|
||||
PullerKey key = {.atomTag = tagId, .uid = uid};
|
||||
auto pullerIt = kAllPullAtomInfo.find(key);
|
||||
if (pullerIt != kAllPullAtomInfo.end()) {
|
||||
bool ret = pullerIt->second->Pull(eventTimeNs, data);
|
||||
VLOG("pulled %zu items", data->size());
|
||||
if (!ret) {
|
||||
StatsdStats::getInstance().notePullFailed(tagId);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
StatsdStats::getInstance().notePullerNotFound(tagId);
|
||||
ALOGW("StatsPullerManager: Unknown tagId %d", tagId);
|
||||
return false; // Return early since we don't know what to pull.
|
||||
}
|
||||
|
||||
bool StatsPullerManager::PullerForMatcherExists(int tagId) const {
|
||||
// Pulled atoms might be registered after we parse the config, so just make sure the id is in
|
||||
// an appropriate range.
|
||||
return isVendorPulledAtom(tagId) || isPulledAtom(tagId);
|
||||
}
|
||||
|
||||
void StatsPullerManager::updateAlarmLocked() {
|
||||
if (mNextPullTimeNs == NO_ALARM_UPDATE) {
|
||||
VLOG("No need to set alarms. Skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO(b/151045771): do not hold a lock while making a binder call
|
||||
if (mStatsCompanionService != nullptr) {
|
||||
mStatsCompanionService->setPullingAlarm(mNextPullTimeNs / 1000000);
|
||||
} else {
|
||||
VLOG("StatsCompanionService not available. Alarm not set.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
void StatsPullerManager::SetStatsCompanionService(
|
||||
shared_ptr<IStatsCompanionService> statsCompanionService) {
|
||||
std::lock_guard<std::mutex> _l(mLock);
|
||||
shared_ptr<IStatsCompanionService> tmpForLock = mStatsCompanionService;
|
||||
mStatsCompanionService = statsCompanionService;
|
||||
for (const auto& pulledAtom : kAllPullAtomInfo) {
|
||||
pulledAtom.second->SetStatsCompanionService(statsCompanionService);
|
||||
}
|
||||
if (mStatsCompanionService != nullptr) {
|
||||
updateAlarmLocked();
|
||||
}
|
||||
}
|
||||
|
||||
void StatsPullerManager::RegisterReceiver(int tagId, const ConfigKey& configKey,
|
||||
wp<PullDataReceiver> receiver, int64_t nextPullTimeNs,
|
||||
int64_t intervalNs) {
|
||||
std::lock_guard<std::mutex> _l(mLock);
|
||||
auto& receivers = mReceivers[{.atomTag = tagId, .configKey = configKey}];
|
||||
for (auto it = receivers.begin(); it != receivers.end(); it++) {
|
||||
if (it->receiver == receiver) {
|
||||
VLOG("Receiver already registered of %d", (int)receivers.size());
|
||||
return;
|
||||
}
|
||||
}
|
||||
ReceiverInfo receiverInfo;
|
||||
receiverInfo.receiver = receiver;
|
||||
|
||||
// Round it to the nearest minutes. This is the limit of alarm manager.
|
||||
// In practice, we should always have larger buckets.
|
||||
int64_t roundedIntervalNs = intervalNs / NS_PER_SEC / 60 * NS_PER_SEC * 60;
|
||||
// Scheduled pulling should be at least 1 min apart.
|
||||
// This can be lower in cts tests, in which case we round it to 1 min.
|
||||
if (roundedIntervalNs < 60 * (int64_t)NS_PER_SEC) {
|
||||
roundedIntervalNs = 60 * (int64_t)NS_PER_SEC;
|
||||
}
|
||||
|
||||
receiverInfo.intervalNs = roundedIntervalNs;
|
||||
receiverInfo.nextPullTimeNs = nextPullTimeNs;
|
||||
receivers.push_back(receiverInfo);
|
||||
|
||||
// There is only one alarm for all pulled events. So only set it to the smallest denom.
|
||||
if (nextPullTimeNs < mNextPullTimeNs) {
|
||||
VLOG("Updating next pull time %lld", (long long)mNextPullTimeNs);
|
||||
mNextPullTimeNs = nextPullTimeNs;
|
||||
updateAlarmLocked();
|
||||
}
|
||||
VLOG("Puller for tagId %d registered of %d", tagId, (int)receivers.size());
|
||||
}
|
||||
|
||||
void StatsPullerManager::UnRegisterReceiver(int tagId, const ConfigKey& configKey,
|
||||
wp<PullDataReceiver> receiver) {
|
||||
std::lock_guard<std::mutex> _l(mLock);
|
||||
auto receiversIt = mReceivers.find({.atomTag = tagId, .configKey = configKey});
|
||||
if (receiversIt == mReceivers.end()) {
|
||||
VLOG("Unknown pull code or no receivers: %d", tagId);
|
||||
return;
|
||||
}
|
||||
std::list<ReceiverInfo>& receivers = receiversIt->second;
|
||||
for (auto it = receivers.begin(); it != receivers.end(); it++) {
|
||||
if (receiver == it->receiver) {
|
||||
receivers.erase(it);
|
||||
VLOG("Puller for tagId %d unregistered of %d", tagId, (int)receivers.size());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StatsPullerManager::RegisterPullUidProvider(const ConfigKey& configKey,
|
||||
wp<PullUidProvider> provider) {
|
||||
std::lock_guard<std::mutex> _l(mLock);
|
||||
mPullUidProviders[configKey] = provider;
|
||||
}
|
||||
|
||||
void StatsPullerManager::UnregisterPullUidProvider(const ConfigKey& configKey,
|
||||
wp<PullUidProvider> provider) {
|
||||
std::lock_guard<std::mutex> _l(mLock);
|
||||
const auto& it = mPullUidProviders.find(configKey);
|
||||
if (it != mPullUidProviders.end() && it->second == provider) {
|
||||
mPullUidProviders.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void StatsPullerManager::OnAlarmFired(int64_t elapsedTimeNs) {
|
||||
std::lock_guard<std::mutex> _l(mLock);
|
||||
int64_t wallClockNs = getWallClockNs();
|
||||
|
||||
int64_t minNextPullTimeNs = NO_ALARM_UPDATE;
|
||||
|
||||
vector<pair<const ReceiverKey*, vector<ReceiverInfo*>>> needToPull;
|
||||
for (auto& pair : mReceivers) {
|
||||
vector<ReceiverInfo*> receivers;
|
||||
if (pair.second.size() != 0) {
|
||||
for (ReceiverInfo& receiverInfo : pair.second) {
|
||||
if (receiverInfo.nextPullTimeNs <= elapsedTimeNs) {
|
||||
receivers.push_back(&receiverInfo);
|
||||
} else {
|
||||
if (receiverInfo.nextPullTimeNs < minNextPullTimeNs) {
|
||||
minNextPullTimeNs = receiverInfo.nextPullTimeNs;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (receivers.size() > 0) {
|
||||
needToPull.push_back(make_pair(&pair.first, receivers));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto& pullInfo : needToPull) {
|
||||
vector<shared_ptr<LogEvent>> data;
|
||||
bool pullSuccess = PullLocked(pullInfo.first->atomTag, pullInfo.first->configKey,
|
||||
elapsedTimeNs, &data);
|
||||
if (!pullSuccess) {
|
||||
VLOG("pull failed at %lld, will try again later", (long long)elapsedTimeNs);
|
||||
}
|
||||
|
||||
// Convention is to mark pull atom timestamp at request time.
|
||||
// If we pull at t0, puller starts at t1, finishes at t2, and send back
|
||||
// at t3, we mark t0 as its timestamp, which should correspond to its
|
||||
// triggering event, such as condition change at t0.
|
||||
// Here the triggering event is alarm fired from AlarmManager.
|
||||
// In ValueMetricProducer and GaugeMetricProducer we do same thing
|
||||
// when pull on condition change, etc.
|
||||
for (auto& event : data) {
|
||||
event->setElapsedTimestampNs(elapsedTimeNs);
|
||||
event->setLogdWallClockTimestampNs(wallClockNs);
|
||||
}
|
||||
|
||||
for (const auto& receiverInfo : pullInfo.second) {
|
||||
sp<PullDataReceiver> receiverPtr = receiverInfo->receiver.promote();
|
||||
if (receiverPtr != nullptr) {
|
||||
receiverPtr->onDataPulled(data, pullSuccess, elapsedTimeNs);
|
||||
// We may have just come out of a coma, compute next pull time.
|
||||
int numBucketsAhead =
|
||||
(elapsedTimeNs - receiverInfo->nextPullTimeNs) / receiverInfo->intervalNs;
|
||||
receiverInfo->nextPullTimeNs += (numBucketsAhead + 1) * receiverInfo->intervalNs;
|
||||
if (receiverInfo->nextPullTimeNs < minNextPullTimeNs) {
|
||||
minNextPullTimeNs = receiverInfo->nextPullTimeNs;
|
||||
}
|
||||
} else {
|
||||
VLOG("receiver already gone.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VLOG("mNextPullTimeNs: %lld updated to %lld", (long long)mNextPullTimeNs,
|
||||
(long long)minNextPullTimeNs);
|
||||
mNextPullTimeNs = minNextPullTimeNs;
|
||||
updateAlarmLocked();
|
||||
}
|
||||
|
||||
int StatsPullerManager::ForceClearPullerCache() {
|
||||
std::lock_guard<std::mutex> _l(mLock);
|
||||
int totalCleared = 0;
|
||||
for (const auto& pulledAtom : kAllPullAtomInfo) {
|
||||
totalCleared += pulledAtom.second->ForceClearCache();
|
||||
}
|
||||
return totalCleared;
|
||||
}
|
||||
|
||||
int StatsPullerManager::ClearPullerCacheIfNecessary(int64_t timestampNs) {
|
||||
std::lock_guard<std::mutex> _l(mLock);
|
||||
int totalCleared = 0;
|
||||
for (const auto& pulledAtom : kAllPullAtomInfo) {
|
||||
totalCleared += pulledAtom.second->ClearCacheIfNecessary(timestampNs);
|
||||
}
|
||||
return totalCleared;
|
||||
}
|
||||
|
||||
void StatsPullerManager::RegisterPullAtomCallback(const int uid, const int32_t atomTag,
|
||||
const int64_t coolDownNs, const int64_t timeoutNs,
|
||||
const vector<int32_t>& additiveFields,
|
||||
const shared_ptr<IPullAtomCallback>& callback) {
|
||||
std::lock_guard<std::mutex> _l(mLock);
|
||||
VLOG("RegisterPullerCallback: adding puller for tag %d", atomTag);
|
||||
|
||||
if (callback == nullptr) {
|
||||
ALOGW("SetPullAtomCallback called with null callback for atom %d.", atomTag);
|
||||
return;
|
||||
}
|
||||
|
||||
StatsdStats::getInstance().notePullerCallbackRegistrationChanged(atomTag, /*registered=*/true);
|
||||
int64_t actualCoolDownNs = coolDownNs < kMinCoolDownNs ? kMinCoolDownNs : coolDownNs;
|
||||
int64_t actualTimeoutNs = timeoutNs > kMaxTimeoutNs ? kMaxTimeoutNs : timeoutNs;
|
||||
|
||||
sp<StatsCallbackPuller> puller = new StatsCallbackPuller(atomTag, callback, actualCoolDownNs,
|
||||
actualTimeoutNs, additiveFields);
|
||||
PullerKey key = {.atomTag = atomTag, .uid = uid};
|
||||
AIBinder_linkToDeath(callback->asBinder().get(), mPullAtomCallbackDeathRecipient.get(),
|
||||
new PullAtomCallbackDeathCookie(this, key, puller));
|
||||
kAllPullAtomInfo[key] = puller;
|
||||
}
|
||||
|
||||
void StatsPullerManager::UnregisterPullAtomCallback(const int uid, const int32_t atomTag) {
|
||||
std::lock_guard<std::mutex> _l(mLock);
|
||||
PullerKey key = {.atomTag = atomTag, .uid = uid};
|
||||
if (kAllPullAtomInfo.find(key) != kAllPullAtomInfo.end()) {
|
||||
StatsdStats::getInstance().notePullerCallbackRegistrationChanged(atomTag,
|
||||
/*registered=*/false);
|
||||
kAllPullAtomInfo.erase(key);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
189
cmds/statsd/src/external/StatsPullerManager.h
vendored
189
cmds/statsd/src/external/StatsPullerManager.h
vendored
@@ -1,189 +0,0 @@
|
||||
/*
|
||||
* 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 <aidl/android/os/IPullAtomCallback.h>
|
||||
#include <aidl/android/os/IStatsCompanionService.h>
|
||||
#include <utils/RefBase.h>
|
||||
|
||||
#include <list>
|
||||
#include <vector>
|
||||
|
||||
#include "PullDataReceiver.h"
|
||||
#include "PullUidProvider.h"
|
||||
#include "StatsPuller.h"
|
||||
#include "guardrail/StatsdStats.h"
|
||||
#include "logd/LogEvent.h"
|
||||
#include "packages/UidMap.h"
|
||||
|
||||
using aidl::android::os::IPullAtomCallback;
|
||||
using aidl::android::os::IStatsCompanionService;
|
||||
using std::shared_ptr;
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
typedef struct PullerKey {
|
||||
// The uid of the process that registers this puller.
|
||||
const int uid = -1;
|
||||
// The atom that this puller is for.
|
||||
const int atomTag;
|
||||
|
||||
bool operator<(const PullerKey& that) const {
|
||||
if (uid < that.uid) {
|
||||
return true;
|
||||
}
|
||||
if (uid > that.uid) {
|
||||
return false;
|
||||
}
|
||||
return atomTag < that.atomTag;
|
||||
};
|
||||
|
||||
bool operator==(const PullerKey& that) const {
|
||||
return uid == that.uid && atomTag == that.atomTag;
|
||||
};
|
||||
} PullerKey;
|
||||
|
||||
class StatsPullerManager : public virtual RefBase {
|
||||
public:
|
||||
StatsPullerManager();
|
||||
|
||||
virtual ~StatsPullerManager() {
|
||||
}
|
||||
|
||||
|
||||
// Registers a receiver for tagId. It will be pulled on the nextPullTimeNs
|
||||
// and then every intervalNs thereafter.
|
||||
virtual void RegisterReceiver(int tagId, const ConfigKey& configKey,
|
||||
wp<PullDataReceiver> receiver, int64_t nextPullTimeNs,
|
||||
int64_t intervalNs);
|
||||
|
||||
// Stop listening on a tagId.
|
||||
virtual void UnRegisterReceiver(int tagId, const ConfigKey& configKey,
|
||||
wp<PullDataReceiver> receiver);
|
||||
|
||||
// Registers a pull uid provider for the config key. When pulling atoms, it will be used to
|
||||
// determine which uids to pull from.
|
||||
virtual void RegisterPullUidProvider(const ConfigKey& configKey, wp<PullUidProvider> provider);
|
||||
|
||||
// Unregister a pull uid provider.
|
||||
virtual void UnregisterPullUidProvider(const ConfigKey& configKey,
|
||||
wp<PullUidProvider> provider);
|
||||
|
||||
// Verify if we know how to pull for this matcher
|
||||
bool PullerForMatcherExists(int tagId) const;
|
||||
|
||||
void OnAlarmFired(int64_t elapsedTimeNs);
|
||||
|
||||
// Pulls the most recent data.
|
||||
// The data may be served from cache if consecutive pulls come within
|
||||
// mCoolDownNs.
|
||||
// Returns true if the pull was successful.
|
||||
// Returns false when
|
||||
// 1) the pull fails
|
||||
// 2) pull takes longer than mPullTimeoutNs (intrinsic to puller)
|
||||
// 3) Either a PullUidProvider was not registered for the config, or the there was no puller
|
||||
// registered for any of the uids for this atom.
|
||||
// If the metric wants to make any change to the data, like timestamps, they
|
||||
// should make a copy as this data may be shared with multiple metrics.
|
||||
virtual bool Pull(int tagId, const ConfigKey& configKey, const int64_t eventTimeNs,
|
||||
vector<std::shared_ptr<LogEvent>>* data);
|
||||
|
||||
// Same as above, but directly specify the allowed uids to pull from.
|
||||
virtual bool Pull(int tagId, const vector<int32_t>& uids, const int64_t eventTimeNs,
|
||||
vector<std::shared_ptr<LogEvent>>* data);
|
||||
|
||||
// Clear pull data cache immediately.
|
||||
int ForceClearPullerCache();
|
||||
|
||||
// Clear pull data cache if it is beyond respective cool down time.
|
||||
int ClearPullerCacheIfNecessary(int64_t timestampNs);
|
||||
|
||||
void SetStatsCompanionService(shared_ptr<IStatsCompanionService> statsCompanionService);
|
||||
|
||||
void RegisterPullAtomCallback(const int uid, const int32_t atomTag, const int64_t coolDownNs,
|
||||
const int64_t timeoutNs, const vector<int32_t>& additiveFields,
|
||||
const shared_ptr<IPullAtomCallback>& callback);
|
||||
|
||||
void UnregisterPullAtomCallback(const int uid, const int32_t atomTag);
|
||||
|
||||
std::map<const PullerKey, sp<StatsPuller>> kAllPullAtomInfo;
|
||||
|
||||
private:
|
||||
const static int64_t kMinCoolDownNs = NS_PER_SEC;
|
||||
const static int64_t kMaxTimeoutNs = 10 * NS_PER_SEC;
|
||||
shared_ptr<IStatsCompanionService> mStatsCompanionService = nullptr;
|
||||
|
||||
// A struct containing an atom id and a Config Key
|
||||
typedef struct ReceiverKey {
|
||||
const int atomTag;
|
||||
const ConfigKey configKey;
|
||||
|
||||
inline bool operator<(const ReceiverKey& that) const {
|
||||
return atomTag == that.atomTag ? configKey < that.configKey : atomTag < that.atomTag;
|
||||
}
|
||||
} ReceiverKey;
|
||||
|
||||
typedef struct {
|
||||
int64_t nextPullTimeNs;
|
||||
int64_t intervalNs;
|
||||
wp<PullDataReceiver> receiver;
|
||||
} ReceiverInfo;
|
||||
|
||||
// mapping from Receiver Key to receivers
|
||||
std::map<ReceiverKey, std::list<ReceiverInfo>> mReceivers;
|
||||
|
||||
// mapping from Config Key to the PullUidProvider for that config
|
||||
std::map<ConfigKey, wp<PullUidProvider>> mPullUidProviders;
|
||||
|
||||
bool PullLocked(int tagId, const ConfigKey& configKey, const int64_t eventTimeNs,
|
||||
vector<std::shared_ptr<LogEvent>>* data);
|
||||
|
||||
bool PullLocked(int tagId, const vector<int32_t>& uids, const int64_t eventTimeNs,
|
||||
vector<std::shared_ptr<LogEvent>>* data);
|
||||
|
||||
// locks for data receiver and StatsCompanionService changes
|
||||
std::mutex mLock;
|
||||
|
||||
void updateAlarmLocked();
|
||||
|
||||
int64_t mNextPullTimeNs;
|
||||
|
||||
// Death recipient that is triggered when the process holding the IPullAtomCallback has died.
|
||||
::ndk::ScopedAIBinder_DeathRecipient mPullAtomCallbackDeathRecipient;
|
||||
|
||||
/**
|
||||
* Death recipient callback that is called when a pull atom callback dies.
|
||||
* The cookie is a pointer to a PullAtomCallbackDeathCookie.
|
||||
*/
|
||||
static void pullAtomCallbackDied(void* cookie);
|
||||
|
||||
FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvents);
|
||||
FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvent_LateAlarm);
|
||||
FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsWithActivation);
|
||||
FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsNoCondition);
|
||||
FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents);
|
||||
FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm);
|
||||
FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_WithActivation);
|
||||
|
||||
FRIEND_TEST(StatsLogProcessorTest, TestPullUidProviderSetOnConfigUpdate);
|
||||
};
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
55
cmds/statsd/src/external/TrainInfoPuller.cpp
vendored
55
cmds/statsd/src/external/TrainInfoPuller.cpp
vendored
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#define DEBUG false // STOPSHIP if true
|
||||
#include "Log.h"
|
||||
|
||||
#include "external/StatsPuller.h"
|
||||
|
||||
#include "TrainInfoPuller.h"
|
||||
#include "logd/LogEvent.h"
|
||||
#include "stats_log_util.h"
|
||||
#include "statslog_statsd.h"
|
||||
#include "storage/StorageManager.h"
|
||||
|
||||
using std::make_shared;
|
||||
using std::shared_ptr;
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
TrainInfoPuller::TrainInfoPuller() :
|
||||
StatsPuller(util::TRAIN_INFO) {
|
||||
}
|
||||
|
||||
bool TrainInfoPuller::PullInternal(vector<shared_ptr<LogEvent>>* data) {
|
||||
vector<InstallTrainInfo> trainInfoList =
|
||||
StorageManager::readAllTrainInfo();
|
||||
if (trainInfoList.empty()) {
|
||||
ALOGW("Train info was empty.");
|
||||
return true;
|
||||
}
|
||||
for (InstallTrainInfo& trainInfo : trainInfoList) {
|
||||
auto event = make_shared<LogEvent>(getWallClockNs(), getElapsedRealtimeNs(), trainInfo);
|
||||
data->push_back(event);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
38
cmds/statsd/src/external/TrainInfoPuller.h
vendored
38
cmds/statsd/src/external/TrainInfoPuller.h
vendored
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "StatsPuller.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
/**
|
||||
* Reads train info from disk.
|
||||
*/
|
||||
class TrainInfoPuller : public StatsPuller {
|
||||
public:
|
||||
TrainInfoPuller();
|
||||
|
||||
private:
|
||||
bool PullInternal(vector<std::shared_ptr<LogEvent>>* data) override;
|
||||
};
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
153
cmds/statsd/src/external/puller_util.cpp
vendored
153
cmds/statsd/src/external/puller_util.cpp
vendored
@@ -1,153 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#define DEBUG false // STOPSHIP if true
|
||||
#include "Log.h"
|
||||
|
||||
#include "puller_util.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
using namespace std;
|
||||
|
||||
/**
|
||||
* Process all data and merge isolated with host if necessary.
|
||||
* For example:
|
||||
* NetworkBytesAtom {
|
||||
* int uid = 1;
|
||||
* State process_state = 2;
|
||||
* int byte_send = 3;
|
||||
* int byte_recv = 4;
|
||||
* }
|
||||
* additive fields are {3, 4}
|
||||
* If we pulled the following events (uid1_child is an isolated uid which maps to uid1):
|
||||
* [uid1, fg, 100, 200]
|
||||
* [uid1_child, fg, 100, 200]
|
||||
* [uid1, bg, 100, 200]
|
||||
*
|
||||
* We want to merge them and results should be:
|
||||
* [uid1, fg, 200, 400]
|
||||
* [uid1, bg, 100, 200]
|
||||
*
|
||||
* All atoms should be of the same tagId. All fields should be present.
|
||||
*/
|
||||
void mapAndMergeIsolatedUidsToHostUid(vector<shared_ptr<LogEvent>>& data, const sp<UidMap>& uidMap,
|
||||
int tagId, const vector<int>& additiveFieldsVec) {
|
||||
// Check the first LogEvent for attribution chain or a uid field as either all atoms with this
|
||||
// tagId have them or none of them do.
|
||||
std::pair<int, int> attrIndexRange;
|
||||
const bool hasAttributionChain = data[0]->hasAttributionChain(&attrIndexRange);
|
||||
bool hasUidField = (data[0]->getUidFieldIndex() != -1);
|
||||
|
||||
if (!hasAttributionChain && !hasUidField) {
|
||||
VLOG("No uid or attribution chain to merge, atom %d", tagId);
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. Map all isolated uid in-place to host uid
|
||||
for (shared_ptr<LogEvent>& event : data) {
|
||||
if (event->GetTagId() != tagId) {
|
||||
ALOGE("Wrong atom. Expecting %d, got %d", tagId, event->GetTagId());
|
||||
return;
|
||||
}
|
||||
if (hasAttributionChain) {
|
||||
vector<FieldValue>* const fieldValues = event->getMutableValues();
|
||||
for (int i = attrIndexRange.first; i <= attrIndexRange.second; i++) {
|
||||
FieldValue& fieldValue = fieldValues->at(i);
|
||||
if (isAttributionUidField(fieldValue)) {
|
||||
const int hostUid = uidMap->getHostUidOrSelf(fieldValue.mValue.int_value);
|
||||
fieldValue.mValue.setInt(hostUid);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
int uidFieldIndex = event->getUidFieldIndex();
|
||||
if (uidFieldIndex != -1) {
|
||||
Value& value = (*event->getMutableValues())[uidFieldIndex].mValue;
|
||||
const int hostUid = uidMap->getHostUidOrSelf(value.int_value);
|
||||
value.setInt(hostUid);
|
||||
} else {
|
||||
ALOGE("Malformed log, uid not found. %s", event->ToString().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. sort the data, bit-wise
|
||||
sort(data.begin(), data.end(),
|
||||
[](const shared_ptr<LogEvent>& lhs, const shared_ptr<LogEvent>& rhs) {
|
||||
if (lhs->size() != rhs->size()) {
|
||||
return lhs->size() < rhs->size();
|
||||
}
|
||||
const std::vector<FieldValue>& lhsValues = lhs->getValues();
|
||||
const std::vector<FieldValue>& rhsValues = rhs->getValues();
|
||||
for (int i = 0; i < (int)lhs->size(); i++) {
|
||||
if (lhsValues[i] != rhsValues[i]) {
|
||||
return lhsValues[i] < rhsValues[i];
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
vector<shared_ptr<LogEvent>> mergedData;
|
||||
const set<int> additiveFields(additiveFieldsVec.begin(), additiveFieldsVec.end());
|
||||
bool needMerge = true;
|
||||
|
||||
// 3. do the merge.
|
||||
// The loop invariant is this: for every event, check if it differs on
|
||||
// non-additive fields, or have different attribution chain length.
|
||||
// If so, no need to merge, add itself to the result.
|
||||
// Otherwise, merge the value onto the one immediately next to it.
|
||||
for (int i = 0; i < (int)data.size() - 1; i++) {
|
||||
// Size different, must be different chains.
|
||||
if (data[i]->size() != data[i + 1]->size()) {
|
||||
mergedData.push_back(data[i]);
|
||||
continue;
|
||||
}
|
||||
vector<FieldValue>* lhsValues = data[i]->getMutableValues();
|
||||
vector<FieldValue>* rhsValues = data[i + 1]->getMutableValues();
|
||||
needMerge = true;
|
||||
for (int p = 0; p < (int)lhsValues->size(); p++) {
|
||||
if ((*lhsValues)[p] != (*rhsValues)[p]) {
|
||||
int pos = (*lhsValues)[p].mField.getPosAtDepth(0);
|
||||
// Differ on non-additive field, abort.
|
||||
if (additiveFields.find(pos) == additiveFields.end()) {
|
||||
needMerge = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!needMerge) {
|
||||
mergedData.push_back(data[i]);
|
||||
continue;
|
||||
}
|
||||
// This should be infrequent operation.
|
||||
for (int p = 0; p < (int)lhsValues->size(); p++) {
|
||||
int pos = (*lhsValues)[p].mField.getPosAtDepth(0);
|
||||
if (additiveFields.find(pos) != additiveFields.end()) {
|
||||
(*rhsValues)[p].mValue += (*lhsValues)[p].mValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
mergedData.push_back(data.back());
|
||||
|
||||
data.clear();
|
||||
data = mergedData;
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
34
cmds/statsd/src/external/puller_util.h
vendored
34
cmds/statsd/src/external/puller_util.h
vendored
@@ -1,34 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include "StatsPuller.h"
|
||||
#include "logd/LogEvent.h"
|
||||
#include "packages/UidMap.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
void mapAndMergeIsolatedUidsToHostUid(std::vector<std::shared_ptr<LogEvent>>& data,
|
||||
const sp<UidMap>& uidMap, int tagId,
|
||||
const vector<int>& additiveFieldsVec);
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,678 +0,0 @@
|
||||
/*
|
||||
* Copyright 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 "config/ConfigKey.h"
|
||||
|
||||
#include <gtest/gtest_prod.h>
|
||||
#include <log/log_time.h>
|
||||
#include <list>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
struct ConfigStats {
|
||||
int32_t uid;
|
||||
int64_t id;
|
||||
int32_t creation_time_sec;
|
||||
int32_t deletion_time_sec = 0;
|
||||
int32_t reset_time_sec = 0;
|
||||
int32_t metric_count;
|
||||
int32_t condition_count;
|
||||
int32_t matcher_count;
|
||||
int32_t alert_count;
|
||||
bool is_valid;
|
||||
|
||||
std::list<int32_t> broadcast_sent_time_sec;
|
||||
|
||||
// Times at which this config is activated.
|
||||
std::list<int32_t> activation_time_sec;
|
||||
|
||||
// Times at which this config is deactivated.
|
||||
std::list<int32_t> deactivation_time_sec;
|
||||
|
||||
std::list<int32_t> data_drop_time_sec;
|
||||
// Number of bytes dropped at corresponding time.
|
||||
std::list<int64_t> data_drop_bytes;
|
||||
std::list<std::pair<int32_t, int64_t>> dump_report_stats;
|
||||
|
||||
// Stores how many times a matcher have been matched. The map size is capped by kMaxConfigCount.
|
||||
std::map<const int64_t, int> matcher_stats;
|
||||
|
||||
// Stores the number of output tuple of condition trackers when it's bigger than
|
||||
// kDimensionKeySizeSoftLimit. When you see the number is kDimensionKeySizeHardLimit +1,
|
||||
// it means some data has been dropped. The map size is capped by kMaxConfigCount.
|
||||
std::map<const int64_t, int> condition_stats;
|
||||
|
||||
// Stores the number of output tuple of metric producers when it's bigger than
|
||||
// kDimensionKeySizeSoftLimit. When you see the number is kDimensionKeySizeHardLimit +1,
|
||||
// it means some data has been dropped. The map size is capped by kMaxConfigCount.
|
||||
std::map<const int64_t, int> metric_stats;
|
||||
|
||||
// Stores the max number of output tuple of dimensions in condition across dimensions in what
|
||||
// when it's bigger than kDimensionKeySizeSoftLimit. When you see the number is
|
||||
// kDimensionKeySizeHardLimit +1, it means some data has been dropped. The map size is capped by
|
||||
// kMaxConfigCount.
|
||||
std::map<const int64_t, int> metric_dimension_in_condition_stats;
|
||||
|
||||
// Stores the number of times an anomaly detection alert has been declared.
|
||||
// The map size is capped by kMaxConfigCount.
|
||||
std::map<const int64_t, int> alert_stats;
|
||||
|
||||
// Stores the config ID for each sub-config used.
|
||||
std::list<std::pair<const int64_t, const int32_t>> annotations;
|
||||
};
|
||||
|
||||
struct UidMapStats {
|
||||
int32_t changes;
|
||||
int32_t bytes_used;
|
||||
int32_t dropped_changes;
|
||||
int32_t deleted_apps = 0;
|
||||
};
|
||||
|
||||
// Keeps track of stats of statsd.
|
||||
// Single instance shared across the process. All public methods are thread safe.
|
||||
class StatsdStats {
|
||||
public:
|
||||
static StatsdStats& getInstance();
|
||||
~StatsdStats(){};
|
||||
|
||||
const static int kDimensionKeySizeSoftLimit = 500;
|
||||
const static int kDimensionKeySizeHardLimit = 800;
|
||||
|
||||
// Per atom dimension key size limit
|
||||
static const std::map<int, std::pair<size_t, size_t>> kAtomDimensionKeySizeLimitMap;
|
||||
|
||||
const static int kMaxConfigCountPerUid = 20;
|
||||
const static int kMaxAlertCountPerConfig = 100;
|
||||
const static int kMaxConditionCountPerConfig = 300;
|
||||
const static int kMaxMetricCountPerConfig = 1000;
|
||||
const static int kMaxMatcherCountPerConfig = 800;
|
||||
|
||||
// The max number of old config stats we keep.
|
||||
const static int kMaxIceBoxSize = 20;
|
||||
|
||||
const static int kMaxLoggerErrors = 20;
|
||||
|
||||
const static int kMaxSystemServerRestarts = 20;
|
||||
|
||||
const static int kMaxTimestampCount = 20;
|
||||
|
||||
const static int kMaxLogSourceCount = 50;
|
||||
|
||||
const static int kMaxPullAtomPackages = 100;
|
||||
|
||||
// Max memory allowed for storing metrics per configuration. If this limit is exceeded, statsd
|
||||
// drops the metrics data in memory.
|
||||
static const size_t kMaxMetricsBytesPerConfig = 2 * 1024 * 1024;
|
||||
|
||||
// Soft memory limit per configuration. Once this limit is exceeded, we begin notifying the
|
||||
// data subscriber that it's time to call getData.
|
||||
static const size_t kBytesPerConfigTriggerGetData = 192 * 1024;
|
||||
|
||||
// Cap the UID map's memory usage to this. This should be fairly high since the UID information
|
||||
// is critical for understanding the metrics.
|
||||
const static size_t kMaxBytesUsedUidMap = 50 * 1024;
|
||||
|
||||
// The number of deleted apps that are stored in the uid map.
|
||||
const static int kMaxDeletedAppsInUidMap = 100;
|
||||
|
||||
/* Minimum period between two broadcasts in nanoseconds. */
|
||||
static const int64_t kMinBroadcastPeriodNs = 60 * NS_PER_SEC;
|
||||
|
||||
/* Min period between two checks of byte size per config key in nanoseconds. */
|
||||
static const int64_t kMinByteSizeCheckPeriodNs = 60 * NS_PER_SEC;
|
||||
|
||||
/* Minimum period between two activation broadcasts in nanoseconds. */
|
||||
static const int64_t kMinActivationBroadcastPeriodNs = 10 * NS_PER_SEC;
|
||||
|
||||
// Maximum age (30 days) that files on disk can exist in seconds.
|
||||
static const int kMaxAgeSecond = 60 * 60 * 24 * 30;
|
||||
|
||||
// Maximum age (2 days) that local history files on disk can exist in seconds.
|
||||
static const int kMaxLocalHistoryAgeSecond = 60 * 60 * 24 * 2;
|
||||
|
||||
// Maximum number of files (1000) that can be in stats directory on disk.
|
||||
static const int kMaxFileNumber = 1000;
|
||||
|
||||
// Maximum size of all files that can be written to stats directory on disk.
|
||||
static const int kMaxFileSize = 50 * 1024 * 1024;
|
||||
|
||||
// How long to try to clear puller cache from last time
|
||||
static const long kPullerCacheClearIntervalSec = 1;
|
||||
|
||||
// Max time to do a pull.
|
||||
static const int64_t kPullMaxDelayNs = 30 * NS_PER_SEC;
|
||||
|
||||
// Maximum number of pushed atoms statsd stats will track above kMaxPushedAtomId.
|
||||
static const int kMaxNonPlatformPushedAtoms = 100;
|
||||
|
||||
// Maximum atom id value that we consider a platform pushed atom.
|
||||
// This should be updated once highest pushed atom id in atoms.proto approaches this value.
|
||||
static const int kMaxPushedAtomId = 500;
|
||||
|
||||
// Atom id that is the start of the pulled atoms.
|
||||
static const int kPullAtomStartTag = 10000;
|
||||
|
||||
// Atom id that is the start of vendor atoms.
|
||||
static const int kVendorAtomStartTag = 100000;
|
||||
|
||||
// Vendor pulled atom start id.
|
||||
static const int32_t kVendorPulledAtomStartTag = 150000;
|
||||
|
||||
// Beginning of range for timestamp truncation.
|
||||
static const int32_t kTimestampTruncationStartTag = 300000;
|
||||
|
||||
// End of range for timestamp truncation.
|
||||
static const int32_t kTimestampTruncationEndTag = 304999;
|
||||
|
||||
// Max accepted atom id.
|
||||
static const int32_t kMaxAtomTag = 200000;
|
||||
|
||||
static const int64_t kInt64Max = 0x7fffffffffffffffLL;
|
||||
|
||||
static const int32_t kMaxLoggedBucketDropEvents = 10;
|
||||
|
||||
/**
|
||||
* Report a new config has been received and report the static stats about the config.
|
||||
*
|
||||
* The static stats include: the count of metrics, conditions, matchers, and alerts.
|
||||
* If the config is not valid, this config stats will be put into icebox immediately.
|
||||
*/
|
||||
void noteConfigReceived(const ConfigKey& key, int metricsCount, int conditionsCount,
|
||||
int matchersCount, int alertCount,
|
||||
const std::list<std::pair<const int64_t, const int32_t>>& annotations,
|
||||
bool isValid);
|
||||
/**
|
||||
* Report a config has been removed.
|
||||
*/
|
||||
void noteConfigRemoved(const ConfigKey& key);
|
||||
/**
|
||||
* Report a config has been reset when ttl expires.
|
||||
*/
|
||||
void noteConfigReset(const ConfigKey& key);
|
||||
|
||||
/**
|
||||
* Report a broadcast has been sent to a config owner to collect the data.
|
||||
*/
|
||||
void noteBroadcastSent(const ConfigKey& key);
|
||||
|
||||
/**
|
||||
* Report that a config has become activated or deactivated.
|
||||
* This can be different from whether or not a broadcast is sent if the
|
||||
* guardrail prevented the broadcast from being sent.
|
||||
*/
|
||||
void noteActiveStatusChanged(const ConfigKey& key, bool activate);
|
||||
|
||||
/**
|
||||
* Report a config's metrics data has been dropped.
|
||||
*/
|
||||
void noteDataDropped(const ConfigKey& key, const size_t totalBytes);
|
||||
|
||||
/**
|
||||
* Report metrics data report has been sent.
|
||||
*
|
||||
* The report may be requested via StatsManager API, or through adb cmd.
|
||||
*/
|
||||
void noteMetricsReportSent(const ConfigKey& key, const size_t num_bytes);
|
||||
|
||||
/**
|
||||
* Report the size of output tuple of a condition.
|
||||
*
|
||||
* Note: only report when the condition has an output dimension, and the tuple
|
||||
* count > kDimensionKeySizeSoftLimit.
|
||||
*
|
||||
* [key]: The config key that this condition belongs to.
|
||||
* [id]: The id of the condition.
|
||||
* [size]: The output tuple size.
|
||||
*/
|
||||
void noteConditionDimensionSize(const ConfigKey& key, const int64_t& id, int size);
|
||||
|
||||
/**
|
||||
* Report the size of output tuple of a metric.
|
||||
*
|
||||
* Note: only report when the metric has an output dimension, and the tuple
|
||||
* count > kDimensionKeySizeSoftLimit.
|
||||
*
|
||||
* [key]: The config key that this metric belongs to.
|
||||
* [id]: The id of the metric.
|
||||
* [size]: The output tuple size.
|
||||
*/
|
||||
void noteMetricDimensionSize(const ConfigKey& key, const int64_t& id, int size);
|
||||
|
||||
/**
|
||||
* Report the max size of output tuple of dimension in condition across dimensions in what.
|
||||
*
|
||||
* Note: only report when the metric has an output dimension in condition, and the max tuple
|
||||
* count > kDimensionKeySizeSoftLimit.
|
||||
*
|
||||
* [key]: The config key that this metric belongs to.
|
||||
* [id]: The id of the metric.
|
||||
* [size]: The output tuple size.
|
||||
*/
|
||||
void noteMetricDimensionInConditionSize(const ConfigKey& key, const int64_t& id, int size);
|
||||
|
||||
/**
|
||||
* Report a matcher has been matched.
|
||||
*
|
||||
* [key]: The config key that this matcher belongs to.
|
||||
* [id]: The id of the matcher.
|
||||
*/
|
||||
void noteMatcherMatched(const ConfigKey& key, const int64_t& id);
|
||||
|
||||
/**
|
||||
* Report that an anomaly detection alert has been declared.
|
||||
*
|
||||
* [key]: The config key that this alert belongs to.
|
||||
* [id]: The id of the alert.
|
||||
*/
|
||||
void noteAnomalyDeclared(const ConfigKey& key, const int64_t& id);
|
||||
|
||||
/**
|
||||
* Report an atom event has been logged.
|
||||
*/
|
||||
void noteAtomLogged(int atomId, int32_t timeSec);
|
||||
|
||||
/**
|
||||
* Report that statsd modified the anomaly alarm registered with StatsCompanionService.
|
||||
*/
|
||||
void noteRegisteredAnomalyAlarmChanged();
|
||||
|
||||
/**
|
||||
* Report that statsd modified the periodic alarm registered with StatsCompanionService.
|
||||
*/
|
||||
void noteRegisteredPeriodicAlarmChanged();
|
||||
|
||||
/**
|
||||
* Records the number of delta entries that are being dropped from the uid map.
|
||||
*/
|
||||
void noteUidMapDropped(int deltas);
|
||||
|
||||
/**
|
||||
* Records that an app was deleted (from statsd's map).
|
||||
*/
|
||||
void noteUidMapAppDeletionDropped();
|
||||
|
||||
/**
|
||||
* Updates the number of changes currently stored in the uid map.
|
||||
*/
|
||||
void setUidMapChanges(int changes);
|
||||
void setCurrentUidMapMemory(int bytes);
|
||||
|
||||
/*
|
||||
* Updates minimum interval between pulls for an pulled atom.
|
||||
*/
|
||||
void updateMinPullIntervalSec(int pullAtomId, long intervalSec);
|
||||
|
||||
/*
|
||||
* Notes an atom is pulled.
|
||||
*/
|
||||
void notePull(int pullAtomId);
|
||||
|
||||
/*
|
||||
* Notes an atom is served from puller cache.
|
||||
*/
|
||||
void notePullFromCache(int pullAtomId);
|
||||
|
||||
/*
|
||||
* Notify data error for pulled atom.
|
||||
*/
|
||||
void notePullDataError(int pullAtomId);
|
||||
|
||||
/*
|
||||
* Records time for actual pulling, not including those served from cache and not including
|
||||
* statsd processing delays.
|
||||
*/
|
||||
void notePullTime(int pullAtomId, int64_t pullTimeNs);
|
||||
|
||||
/*
|
||||
* Records pull delay for a pulled atom, including those served from cache and including statsd
|
||||
* processing delays.
|
||||
*/
|
||||
void notePullDelay(int pullAtomId, int64_t pullDelayNs);
|
||||
|
||||
/*
|
||||
* Records pull exceeds timeout for the puller.
|
||||
*/
|
||||
void notePullTimeout(int pullAtomId, int64_t pullUptimeMillis, int64_t pullElapsedMillis);
|
||||
|
||||
/*
|
||||
* Records pull exceeds max delay for a metric.
|
||||
*/
|
||||
void notePullExceedMaxDelay(int pullAtomId);
|
||||
|
||||
/*
|
||||
* Records when system server restarts.
|
||||
*/
|
||||
void noteSystemServerRestart(int32_t timeSec);
|
||||
|
||||
/**
|
||||
* Records statsd skipped an event.
|
||||
*/
|
||||
void noteLogLost(int32_t wallClockTimeSec, int32_t count, int32_t lastError,
|
||||
int32_t lastAtomTag, int32_t uid, int32_t pid);
|
||||
|
||||
/**
|
||||
* Records that the pull of an atom has failed. Eg, if the client indicated the pull failed, if
|
||||
* the pull timed out, or if the outgoing binder call failed.
|
||||
* This count will only increment if the puller was actually invoked.
|
||||
*
|
||||
* It does not include a pull not occurring due to not finding the appropriate
|
||||
* puller. These cases are covered in other counts.
|
||||
*/
|
||||
void notePullFailed(int atomId);
|
||||
|
||||
/**
|
||||
* Records that the pull of an atom has failed due to not having a uid provider.
|
||||
*/
|
||||
void notePullUidProviderNotFound(int atomId);
|
||||
|
||||
/**
|
||||
* Records that the pull of an atom has failed due not finding a puller registered by a
|
||||
* trusted uid.
|
||||
*/
|
||||
void notePullerNotFound(int atomId);
|
||||
|
||||
/**
|
||||
* Records that the pull has failed due to the outgoing binder call failing.
|
||||
*/
|
||||
void notePullBinderCallFailed(int atomId);
|
||||
|
||||
/**
|
||||
* A pull with no data occurred
|
||||
*/
|
||||
void noteEmptyData(int atomId);
|
||||
|
||||
/**
|
||||
* Records that a puller callback for the given atomId was registered or unregistered.
|
||||
*
|
||||
* @param registered True if the callback was registered, false if was unregistered.
|
||||
*/
|
||||
void notePullerCallbackRegistrationChanged(int atomId, bool registered);
|
||||
|
||||
/**
|
||||
* Hard limit was reached in the cardinality of an atom
|
||||
*/
|
||||
void noteHardDimensionLimitReached(int64_t metricId);
|
||||
|
||||
/**
|
||||
* A log event was too late, arrived in the wrong bucket and was skipped
|
||||
*/
|
||||
void noteLateLogEventSkipped(int64_t metricId);
|
||||
|
||||
/**
|
||||
* Buckets were skipped as time elapsed without any data for them
|
||||
*/
|
||||
void noteSkippedForwardBuckets(int64_t metricId);
|
||||
|
||||
/**
|
||||
* An unsupported value type was received
|
||||
*/
|
||||
void noteBadValueType(int64_t metricId);
|
||||
|
||||
/**
|
||||
* Buckets were dropped due to reclaim memory.
|
||||
*/
|
||||
void noteBucketDropped(int64_t metricId);
|
||||
|
||||
/**
|
||||
* A condition change was too late, arrived in the wrong bucket and was skipped
|
||||
*/
|
||||
void noteConditionChangeInNextBucket(int64_t metricId);
|
||||
|
||||
/**
|
||||
* A bucket has been tagged as invalid.
|
||||
*/
|
||||
void noteInvalidatedBucket(int64_t metricId);
|
||||
|
||||
/**
|
||||
* Tracks the total number of buckets (include skipped/invalid buckets).
|
||||
*/
|
||||
void noteBucketCount(int64_t metricId);
|
||||
|
||||
/**
|
||||
* For pulls at bucket boundaries, it represents the misalignment between the real timestamp and
|
||||
* the end of the bucket.
|
||||
*/
|
||||
void noteBucketBoundaryDelayNs(int64_t metricId, int64_t timeDelayNs);
|
||||
|
||||
/**
|
||||
* Number of buckets with unknown condition.
|
||||
*/
|
||||
void noteBucketUnknownCondition(int64_t metricId);
|
||||
|
||||
/* Reports one event has been dropped due to queue overflow, and the oldest event timestamp in
|
||||
* the queue */
|
||||
void noteEventQueueOverflow(int64_t oldestEventTimestampNs);
|
||||
|
||||
/**
|
||||
* Reports that the activation broadcast guardrail was hit for this uid. Namely, the broadcast
|
||||
* should have been sent, but instead was skipped due to hitting the guardrail.
|
||||
*/
|
||||
void noteActivationBroadcastGuardrailHit(const int uid);
|
||||
|
||||
/**
|
||||
* Reports that an atom is erroneous or cannot be parsed successfully by
|
||||
* statsd. An atom tag of 0 indicates that the client did not supply the
|
||||
* atom id within the encoding.
|
||||
*
|
||||
* For pushed atoms only, this call should be preceded by a call to
|
||||
* noteAtomLogged.
|
||||
*/
|
||||
void noteAtomError(int atomTag, bool pull=false);
|
||||
|
||||
/**
|
||||
* Reset the historical stats. Including all stats in icebox, and the tracked stats about
|
||||
* metrics, matchers, and atoms. The active configs will be kept and StatsdStats will continue
|
||||
* to collect stats after reset() has been called.
|
||||
*/
|
||||
void reset();
|
||||
|
||||
/**
|
||||
* Output the stats in protobuf binary format to [buffer].
|
||||
*
|
||||
* [reset]: whether to clear the historical stats after the call.
|
||||
*/
|
||||
void dumpStats(std::vector<uint8_t>* buffer, bool reset);
|
||||
|
||||
/**
|
||||
* Output statsd stats in human readable format to [out] file descriptor.
|
||||
*/
|
||||
void dumpStats(int outFd) const;
|
||||
|
||||
typedef struct PullTimeoutMetadata {
|
||||
int64_t pullTimeoutUptimeMillis;
|
||||
int64_t pullTimeoutElapsedMillis;
|
||||
PullTimeoutMetadata(int64_t uptimeMillis, int64_t elapsedMillis) :
|
||||
pullTimeoutUptimeMillis(uptimeMillis),
|
||||
pullTimeoutElapsedMillis(elapsedMillis) {/* do nothing */}
|
||||
} PullTimeoutMetadata;
|
||||
|
||||
typedef struct {
|
||||
long totalPull = 0;
|
||||
long totalPullFromCache = 0;
|
||||
long minPullIntervalSec = LONG_MAX;
|
||||
int64_t avgPullTimeNs = 0;
|
||||
int64_t maxPullTimeNs = 0;
|
||||
long numPullTime = 0;
|
||||
int64_t avgPullDelayNs = 0;
|
||||
int64_t maxPullDelayNs = 0;
|
||||
long numPullDelay = 0;
|
||||
long dataError = 0;
|
||||
long pullTimeout = 0;
|
||||
long pullExceedMaxDelay = 0;
|
||||
long pullFailed = 0;
|
||||
long pullUidProviderNotFound = 0;
|
||||
long pullerNotFound = 0;
|
||||
long emptyData = 0;
|
||||
long registeredCount = 0;
|
||||
long unregisteredCount = 0;
|
||||
int32_t atomErrorCount = 0;
|
||||
long binderCallFailCount = 0;
|
||||
std::list<PullTimeoutMetadata> pullTimeoutMetadata;
|
||||
} PulledAtomStats;
|
||||
|
||||
typedef struct {
|
||||
long hardDimensionLimitReached = 0;
|
||||
long lateLogEventSkipped = 0;
|
||||
long skippedForwardBuckets = 0;
|
||||
long badValueType = 0;
|
||||
long conditionChangeInNextBucket = 0;
|
||||
long invalidatedBucket = 0;
|
||||
long bucketDropped = 0;
|
||||
int64_t minBucketBoundaryDelayNs = 0;
|
||||
int64_t maxBucketBoundaryDelayNs = 0;
|
||||
long bucketUnknownCondition = 0;
|
||||
long bucketCount = 0;
|
||||
} AtomMetricStats;
|
||||
|
||||
private:
|
||||
StatsdStats();
|
||||
|
||||
mutable std::mutex mLock;
|
||||
|
||||
int32_t mStartTimeSec;
|
||||
|
||||
// Track the number of dropped entries used by the uid map.
|
||||
UidMapStats mUidMapStats;
|
||||
|
||||
// The stats about the configs that are still in use.
|
||||
// The map size is capped by kMaxConfigCount.
|
||||
std::map<const ConfigKey, std::shared_ptr<ConfigStats>> mConfigStats;
|
||||
|
||||
// Stores the stats for the configs that are no longer in use.
|
||||
// The size of the vector is capped by kMaxIceBoxSize.
|
||||
std::list<const std::shared_ptr<ConfigStats>> mIceBox;
|
||||
|
||||
// Stores the number of times a pushed atom is logged.
|
||||
// The size of the vector is the largest pushed atom id in atoms.proto + 1. Atoms
|
||||
// out of that range will be put in mNonPlatformPushedAtomStats.
|
||||
// This is a vector, not a map because it will be accessed A LOT -- for each stats log.
|
||||
std::vector<int> mPushedAtomStats;
|
||||
|
||||
// Stores the number of times a pushed atom is logged for atom ids above kMaxPushedAtomId.
|
||||
// The max size of the map is kMaxNonPlatformPushedAtoms.
|
||||
std::unordered_map<int, int> mNonPlatformPushedAtomStats;
|
||||
|
||||
// Maps PullAtomId to its stats. The size is capped by the puller atom counts.
|
||||
std::map<int, PulledAtomStats> mPulledAtomStats;
|
||||
|
||||
// Stores the number of times a pushed atom was logged erroneously. The
|
||||
// corresponding counts for pulled atoms are stored in PulledAtomStats.
|
||||
// The max size of this map is kMaxAtomErrorsStatsSize.
|
||||
std::map<int, int> mPushedAtomErrorStats;
|
||||
int kMaxPushedAtomErrorStatsSize = 100;
|
||||
|
||||
// Maps metric ID to its stats. The size is capped by the number of metrics.
|
||||
std::map<int64_t, AtomMetricStats> mAtomMetricStats;
|
||||
|
||||
// Maps uids to times when the activation changed broadcast not sent due to hitting the
|
||||
// guardrail. The size is capped by the number of configs, and up to 20 times per uid.
|
||||
std::map<int, std::list<int32_t>> mActivationBroadcastGuardrailStats;
|
||||
|
||||
struct LogLossStats {
|
||||
LogLossStats(int32_t sec, int32_t count, int32_t error, int32_t tag, int32_t uid,
|
||||
int32_t pid)
|
||||
: mWallClockSec(sec),
|
||||
mCount(count),
|
||||
mLastError(error),
|
||||
mLastTag(tag),
|
||||
mUid(uid),
|
||||
mPid(pid) {
|
||||
}
|
||||
int32_t mWallClockSec;
|
||||
int32_t mCount;
|
||||
// error code defined in linux/errno.h
|
||||
int32_t mLastError;
|
||||
int32_t mLastTag;
|
||||
int32_t mUid;
|
||||
int32_t mPid;
|
||||
};
|
||||
|
||||
// Max of {(now - oldestEventTimestamp) when overflow happens}.
|
||||
// This number is helpful to understand how SLOW statsd can be.
|
||||
int64_t mMaxQueueHistoryNs = 0;
|
||||
|
||||
// Min of {(now - oldestEventTimestamp) when overflow happens}.
|
||||
// This number is helpful to understand how FAST the events floods to statsd.
|
||||
int64_t mMinQueueHistoryNs = kInt64Max;
|
||||
|
||||
// Total number of events that are lost due to queue overflow.
|
||||
int32_t mOverflowCount = 0;
|
||||
|
||||
// Timestamps when we detect log loss, and the number of logs lost.
|
||||
std::list<LogLossStats> mLogLossStats;
|
||||
|
||||
std::list<int32_t> mSystemServerRestartSec;
|
||||
|
||||
// Stores the number of times statsd modified the anomaly alarm registered with
|
||||
// StatsCompanionService.
|
||||
int mAnomalyAlarmRegisteredStats = 0;
|
||||
|
||||
// Stores the number of times statsd registers the periodic alarm changes
|
||||
int mPeriodicAlarmRegisteredStats = 0;
|
||||
|
||||
void noteConfigResetInternalLocked(const ConfigKey& key);
|
||||
|
||||
void noteConfigRemovedInternalLocked(const ConfigKey& key);
|
||||
|
||||
void resetInternalLocked();
|
||||
|
||||
void noteDataDropped(const ConfigKey& key, const size_t totalBytes, int32_t timeSec);
|
||||
|
||||
void noteMetricsReportSent(const ConfigKey& key, const size_t num_bytes, int32_t timeSec);
|
||||
|
||||
void noteBroadcastSent(const ConfigKey& key, int32_t timeSec);
|
||||
|
||||
void noteActiveStatusChanged(const ConfigKey& key, bool activate, int32_t timeSec);
|
||||
|
||||
void noteActivationBroadcastGuardrailHit(const int uid, int32_t timeSec);
|
||||
|
||||
void addToIceBoxLocked(std::shared_ptr<ConfigStats>& stats);
|
||||
|
||||
int getPushedAtomErrors(int atomId) const;
|
||||
|
||||
/**
|
||||
* Get a reference to AtomMetricStats for a metric. If none exists, create it. The reference
|
||||
* will live as long as `this`.
|
||||
*/
|
||||
StatsdStats::AtomMetricStats& getAtomMetricStats(int64_t metricId);
|
||||
|
||||
FRIEND_TEST(StatsdStatsTest, TestValidConfigAdd);
|
||||
FRIEND_TEST(StatsdStatsTest, TestInvalidConfigAdd);
|
||||
FRIEND_TEST(StatsdStatsTest, TestConfigRemove);
|
||||
FRIEND_TEST(StatsdStatsTest, TestSubStats);
|
||||
FRIEND_TEST(StatsdStatsTest, TestAtomLog);
|
||||
FRIEND_TEST(StatsdStatsTest, TestNonPlatformAtomLog);
|
||||
FRIEND_TEST(StatsdStatsTest, TestTimestampThreshold);
|
||||
FRIEND_TEST(StatsdStatsTest, TestAnomalyMonitor);
|
||||
FRIEND_TEST(StatsdStatsTest, TestSystemServerCrash);
|
||||
FRIEND_TEST(StatsdStatsTest, TestPullAtomStats);
|
||||
FRIEND_TEST(StatsdStatsTest, TestAtomMetricsStats);
|
||||
FRIEND_TEST(StatsdStatsTest, TestActivationBroadcastGuardrailHit);
|
||||
FRIEND_TEST(StatsdStatsTest, TestAtomErrorStats);
|
||||
|
||||
FRIEND_TEST(StatsLogProcessorTest, InvalidConfigRemoved);
|
||||
};
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,142 +0,0 @@
|
||||
/*
|
||||
* 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 "hash.h"
|
||||
|
||||
#ifndef FALLTHROUGH_INTENDED
|
||||
#define FALLTHROUGH_INTENDED [[fallthrough]]
|
||||
#endif
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
namespace {
|
||||
// Lower-level versions of Get... that read directly from a character buffer
|
||||
// without any bounds checking.
|
||||
inline uint32_t DecodeFixed32(const char *ptr) {
|
||||
return ((static_cast<uint32_t>(static_cast<unsigned char>(ptr[0]))) |
|
||||
(static_cast<uint32_t>(static_cast<unsigned char>(ptr[1])) << 8) |
|
||||
(static_cast<uint32_t>(static_cast<unsigned char>(ptr[2])) << 16) |
|
||||
(static_cast<uint32_t>(static_cast<unsigned char>(ptr[3])) << 24));
|
||||
}
|
||||
|
||||
inline uint64_t DecodeFixed64(const char* ptr) {
|
||||
uint64_t lo = DecodeFixed32(ptr);
|
||||
uint64_t hi = DecodeFixed32(ptr + 4);
|
||||
return (hi << 32) | lo;
|
||||
}
|
||||
|
||||
// 0xff is in case char is signed.
|
||||
static inline uint32_t ByteAs32(char c) { return static_cast<uint32_t>(c) & 0xff; }
|
||||
static inline uint64_t ByteAs64(char c) { return static_cast<uint64_t>(c) & 0xff; }
|
||||
|
||||
} // namespace
|
||||
|
||||
uint32_t Hash32(const char *data, size_t n, uint32_t seed) {
|
||||
// 'm' and 'r' are mixing constants generated offline.
|
||||
// They're not really 'magic', they just happen to work well.
|
||||
const uint32_t m = 0x5bd1e995;
|
||||
const int r = 24;
|
||||
|
||||
// Initialize the hash to a 'random' value
|
||||
uint32_t h = static_cast<uint32_t>(seed ^ n);
|
||||
|
||||
// Mix 4 bytes at a time into the hash
|
||||
while (n >= 4) {
|
||||
uint32_t k = DecodeFixed32(data);
|
||||
k *= m;
|
||||
k ^= k >> r;
|
||||
k *= m;
|
||||
h *= m;
|
||||
h ^= k;
|
||||
data += 4;
|
||||
n -= 4;
|
||||
}
|
||||
|
||||
// Handle the last few bytes of the input array
|
||||
switch (n) {
|
||||
case 3:
|
||||
h ^= ByteAs32(data[2]) << 16;
|
||||
FALLTHROUGH_INTENDED;
|
||||
case 2:
|
||||
h ^= ByteAs32(data[1]) << 8;
|
||||
FALLTHROUGH_INTENDED;
|
||||
case 1:
|
||||
h ^= ByteAs32(data[0]);
|
||||
h *= m;
|
||||
}
|
||||
|
||||
// Do a few final mixes of the hash to ensure the last few
|
||||
// bytes are well-incorporated.
|
||||
h ^= h >> 13;
|
||||
h *= m;
|
||||
h ^= h >> 15;
|
||||
return h;
|
||||
}
|
||||
|
||||
uint64_t Hash64(const char* data, size_t n, uint64_t seed) {
|
||||
const uint64_t m = 0xc6a4a7935bd1e995;
|
||||
const int r = 47;
|
||||
|
||||
uint64_t h = seed ^ (n * m);
|
||||
|
||||
while (n >= 8) {
|
||||
uint64_t k = DecodeFixed64(data);
|
||||
data += 8;
|
||||
n -= 8;
|
||||
|
||||
k *= m;
|
||||
k ^= k >> r;
|
||||
k *= m;
|
||||
|
||||
h ^= k;
|
||||
h *= m;
|
||||
}
|
||||
|
||||
switch (n) {
|
||||
case 7:
|
||||
h ^= ByteAs64(data[6]) << 48;
|
||||
FALLTHROUGH_INTENDED;
|
||||
case 6:
|
||||
h ^= ByteAs64(data[5]) << 40;
|
||||
FALLTHROUGH_INTENDED;
|
||||
case 5:
|
||||
h ^= ByteAs64(data[4]) << 32;
|
||||
FALLTHROUGH_INTENDED;
|
||||
case 4:
|
||||
h ^= ByteAs64(data[3]) << 24;
|
||||
FALLTHROUGH_INTENDED;
|
||||
case 3:
|
||||
h ^= ByteAs64(data[2]) << 16;
|
||||
FALLTHROUGH_INTENDED;
|
||||
case 2:
|
||||
h ^= ByteAs64(data[1]) << 8;
|
||||
FALLTHROUGH_INTENDED;
|
||||
case 1:
|
||||
h ^= ByteAs64(data[0]);
|
||||
h *= m;
|
||||
}
|
||||
|
||||
h ^= h >> r;
|
||||
h *= m;
|
||||
h ^= h >> r;
|
||||
|
||||
return h;
|
||||
}
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* 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 <string>
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
extern uint32_t Hash32(const char *data, size_t n, uint32_t seed);
|
||||
extern uint64_t Hash64(const char* data, size_t n, uint64_t seed);
|
||||
|
||||
inline uint32_t Hash32(const char *data, size_t n) {
|
||||
return Hash32(data, n, 0xBEEF);
|
||||
}
|
||||
|
||||
inline uint32_t Hash32(const std::string &input) {
|
||||
return Hash32(input.data(), input.size());
|
||||
}
|
||||
|
||||
inline uint64_t Hash64(const char* data, size_t n) {
|
||||
return Hash64(data, n, 0xDECAFCAFFE);
|
||||
}
|
||||
|
||||
inline uint64_t Hash64(const std::string& str) {
|
||||
return Hash64(str.data(), str.size());
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,599 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#define DEBUG false // STOPSHIP if true
|
||||
#include "logd/LogEvent.h"
|
||||
|
||||
#include <android-base/stringprintf.h>
|
||||
#include <android/binder_ibinder.h>
|
||||
#include <private/android_filesystem_config.h>
|
||||
|
||||
#include "annotations.h"
|
||||
#include "stats_log_util.h"
|
||||
#include "statslog_statsd.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
// for TrainInfo experiment id serialization
|
||||
const int FIELD_ID_EXPERIMENT_ID = 1;
|
||||
|
||||
using namespace android::util;
|
||||
using android::base::StringPrintf;
|
||||
using android::util::ProtoOutputStream;
|
||||
using std::string;
|
||||
using std::vector;
|
||||
|
||||
// stats_event.h socket types. Keep in sync.
|
||||
/* ERRORS */
|
||||
#define ERROR_NO_TIMESTAMP 0x1
|
||||
#define ERROR_NO_ATOM_ID 0x2
|
||||
#define ERROR_OVERFLOW 0x4
|
||||
#define ERROR_ATTRIBUTION_CHAIN_TOO_LONG 0x8
|
||||
#define ERROR_TOO_MANY_KEY_VALUE_PAIRS 0x10
|
||||
#define ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD 0x20
|
||||
#define ERROR_INVALID_ANNOTATION_ID 0x40
|
||||
#define ERROR_ANNOTATION_ID_TOO_LARGE 0x80
|
||||
#define ERROR_TOO_MANY_ANNOTATIONS 0x100
|
||||
#define ERROR_TOO_MANY_FIELDS 0x200
|
||||
#define ERROR_INVALID_VALUE_TYPE 0x400
|
||||
#define ERROR_STRING_NOT_NULL_TERMINATED 0x800
|
||||
|
||||
/* TYPE IDS */
|
||||
#define INT32_TYPE 0x00
|
||||
#define INT64_TYPE 0x01
|
||||
#define STRING_TYPE 0x02
|
||||
#define LIST_TYPE 0x03
|
||||
#define FLOAT_TYPE 0x04
|
||||
#define BOOL_TYPE 0x05
|
||||
#define BYTE_ARRAY_TYPE 0x06
|
||||
#define OBJECT_TYPE 0x07
|
||||
#define KEY_VALUE_PAIRS_TYPE 0x08
|
||||
#define ATTRIBUTION_CHAIN_TYPE 0x09
|
||||
#define ERROR_TYPE 0x0F
|
||||
|
||||
LogEvent::LogEvent(int32_t uid, int32_t pid)
|
||||
: mLogdTimestampNs(time(nullptr)), mLogUid(uid), mLogPid(pid) {
|
||||
}
|
||||
|
||||
LogEvent::LogEvent(const string& trainName, int64_t trainVersionCode, bool requiresStaging,
|
||||
bool rollbackEnabled, bool requiresLowLatencyMonitor, int32_t state,
|
||||
const std::vector<uint8_t>& experimentIds, int32_t userId) {
|
||||
mLogdTimestampNs = getWallClockNs();
|
||||
mElapsedTimestampNs = getElapsedRealtimeNs();
|
||||
mTagId = util::BINARY_PUSH_STATE_CHANGED;
|
||||
mLogUid = AIBinder_getCallingUid();
|
||||
mLogPid = AIBinder_getCallingPid();
|
||||
|
||||
mValues.push_back(FieldValue(Field(mTagId, getSimpleField(1)), Value(trainName)));
|
||||
mValues.push_back(FieldValue(Field(mTagId, getSimpleField(2)), Value(trainVersionCode)));
|
||||
mValues.push_back(FieldValue(Field(mTagId, getSimpleField(3)), Value((int)requiresStaging)));
|
||||
mValues.push_back(FieldValue(Field(mTagId, getSimpleField(4)), Value((int)rollbackEnabled)));
|
||||
mValues.push_back(
|
||||
FieldValue(Field(mTagId, getSimpleField(5)), Value((int)requiresLowLatencyMonitor)));
|
||||
mValues.push_back(FieldValue(Field(mTagId, getSimpleField(6)), Value(state)));
|
||||
mValues.push_back(FieldValue(Field(mTagId, getSimpleField(7)), Value(experimentIds)));
|
||||
mValues.push_back(FieldValue(Field(mTagId, getSimpleField(8)), Value(userId)));
|
||||
}
|
||||
|
||||
LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
|
||||
const InstallTrainInfo& trainInfo) {
|
||||
mLogdTimestampNs = wallClockTimestampNs;
|
||||
mElapsedTimestampNs = elapsedTimestampNs;
|
||||
mTagId = util::TRAIN_INFO;
|
||||
|
||||
mValues.push_back(
|
||||
FieldValue(Field(mTagId, getSimpleField(1)), Value(trainInfo.trainVersionCode)));
|
||||
std::vector<uint8_t> experimentIdsProto;
|
||||
writeExperimentIdsToProto(trainInfo.experimentIds, &experimentIdsProto);
|
||||
mValues.push_back(FieldValue(Field(mTagId, getSimpleField(2)), Value(experimentIdsProto)));
|
||||
mValues.push_back(FieldValue(Field(mTagId, getSimpleField(3)), Value(trainInfo.trainName)));
|
||||
mValues.push_back(FieldValue(Field(mTagId, getSimpleField(4)), Value(trainInfo.status)));
|
||||
}
|
||||
|
||||
void LogEvent::parseInt32(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) {
|
||||
int32_t value = readNextValue<int32_t>();
|
||||
addToValues(pos, depth, value, last);
|
||||
parseAnnotations(numAnnotations);
|
||||
}
|
||||
|
||||
void LogEvent::parseInt64(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) {
|
||||
int64_t value = readNextValue<int64_t>();
|
||||
addToValues(pos, depth, value, last);
|
||||
parseAnnotations(numAnnotations);
|
||||
}
|
||||
|
||||
void LogEvent::parseString(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) {
|
||||
int32_t numBytes = readNextValue<int32_t>();
|
||||
if ((uint32_t)numBytes > mRemainingLen) {
|
||||
mValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
string value = string((char*)mBuf, numBytes);
|
||||
mBuf += numBytes;
|
||||
mRemainingLen -= numBytes;
|
||||
addToValues(pos, depth, value, last);
|
||||
parseAnnotations(numAnnotations);
|
||||
}
|
||||
|
||||
void LogEvent::parseFloat(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) {
|
||||
float value = readNextValue<float>();
|
||||
addToValues(pos, depth, value, last);
|
||||
parseAnnotations(numAnnotations);
|
||||
}
|
||||
|
||||
void LogEvent::parseBool(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) {
|
||||
// cast to int32_t because FieldValue does not support bools
|
||||
int32_t value = (int32_t)readNextValue<uint8_t>();
|
||||
addToValues(pos, depth, value, last);
|
||||
parseAnnotations(numAnnotations);
|
||||
}
|
||||
|
||||
void LogEvent::parseByteArray(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) {
|
||||
int32_t numBytes = readNextValue<int32_t>();
|
||||
if ((uint32_t)numBytes > mRemainingLen) {
|
||||
mValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
vector<uint8_t> value(mBuf, mBuf + numBytes);
|
||||
mBuf += numBytes;
|
||||
mRemainingLen -= numBytes;
|
||||
addToValues(pos, depth, value, last);
|
||||
parseAnnotations(numAnnotations);
|
||||
}
|
||||
|
||||
void LogEvent::parseKeyValuePairs(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) {
|
||||
int32_t numPairs = readNextValue<uint8_t>();
|
||||
|
||||
for (pos[1] = 1; pos[1] <= numPairs; pos[1]++) {
|
||||
last[1] = (pos[1] == numPairs);
|
||||
|
||||
// parse key
|
||||
pos[2] = 1;
|
||||
parseInt32(pos, /*depth=*/2, last, /*numAnnotations=*/0);
|
||||
|
||||
// parse value
|
||||
last[2] = true;
|
||||
|
||||
uint8_t typeInfo = readNextValue<uint8_t>();
|
||||
switch (getTypeId(typeInfo)) {
|
||||
case INT32_TYPE:
|
||||
pos[2] = 2; // pos[2] determined by index of type in KeyValuePair in atoms.proto
|
||||
parseInt32(pos, /*depth=*/2, last, /*numAnnotations=*/0);
|
||||
break;
|
||||
case INT64_TYPE:
|
||||
pos[2] = 3;
|
||||
parseInt64(pos, /*depth=*/2, last, /*numAnnotations=*/0);
|
||||
break;
|
||||
case STRING_TYPE:
|
||||
pos[2] = 4;
|
||||
parseString(pos, /*depth=*/2, last, /*numAnnotations=*/0);
|
||||
break;
|
||||
case FLOAT_TYPE:
|
||||
pos[2] = 5;
|
||||
parseFloat(pos, /*depth=*/2, last, /*numAnnotations=*/0);
|
||||
break;
|
||||
default:
|
||||
mValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
parseAnnotations(numAnnotations);
|
||||
|
||||
pos[1] = pos[2] = 1;
|
||||
last[1] = last[2] = false;
|
||||
}
|
||||
|
||||
void LogEvent::parseAttributionChain(int32_t* pos, int32_t depth, bool* last,
|
||||
uint8_t numAnnotations) {
|
||||
const unsigned int firstUidInChainIndex = mValues.size();
|
||||
const int32_t numNodes = readNextValue<uint8_t>();
|
||||
for (pos[1] = 1; pos[1] <= numNodes; pos[1]++) {
|
||||
last[1] = (pos[1] == numNodes);
|
||||
|
||||
// parse uid
|
||||
pos[2] = 1;
|
||||
parseInt32(pos, /*depth=*/2, last, /*numAnnotations=*/0);
|
||||
|
||||
// parse tag
|
||||
pos[2] = 2;
|
||||
last[2] = true;
|
||||
parseString(pos, /*depth=*/2, last, /*numAnnotations=*/0);
|
||||
}
|
||||
// Check if at least one node was successfully parsed.
|
||||
if (mValues.size() - 1 > firstUidInChainIndex) {
|
||||
mAttributionChainStartIndex = static_cast<int8_t>(firstUidInChainIndex);
|
||||
mAttributionChainEndIndex = static_cast<int8_t>(mValues.size() - 1);
|
||||
}
|
||||
|
||||
parseAnnotations(numAnnotations, firstUidInChainIndex);
|
||||
|
||||
pos[1] = pos[2] = 1;
|
||||
last[1] = last[2] = false;
|
||||
}
|
||||
|
||||
// Assumes that mValues is not empty
|
||||
bool LogEvent::checkPreviousValueType(Type expected) {
|
||||
return mValues[mValues.size() - 1].mValue.getType() == expected;
|
||||
}
|
||||
|
||||
void LogEvent::parseIsUidAnnotation(uint8_t annotationType) {
|
||||
if (mValues.empty() || !checkPreviousValueType(INT) || annotationType != BOOL_TYPE) {
|
||||
mValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
bool isUid = readNextValue<uint8_t>();
|
||||
if (isUid) mUidFieldIndex = static_cast<int8_t>(mValues.size() - 1);
|
||||
mValues[mValues.size() - 1].mAnnotations.setUidField(isUid);
|
||||
}
|
||||
|
||||
void LogEvent::parseTruncateTimestampAnnotation(uint8_t annotationType) {
|
||||
if (!mValues.empty() || annotationType != BOOL_TYPE) {
|
||||
mValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
mTruncateTimestamp = readNextValue<uint8_t>();
|
||||
}
|
||||
|
||||
void LogEvent::parsePrimaryFieldAnnotation(uint8_t annotationType) {
|
||||
if (mValues.empty() || annotationType != BOOL_TYPE) {
|
||||
mValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const bool primaryField = readNextValue<uint8_t>();
|
||||
mValues[mValues.size() - 1].mAnnotations.setPrimaryField(primaryField);
|
||||
}
|
||||
|
||||
void LogEvent::parsePrimaryFieldFirstUidAnnotation(uint8_t annotationType,
|
||||
int firstUidInChainIndex) {
|
||||
if (mValues.empty() || annotationType != BOOL_TYPE || -1 == firstUidInChainIndex) {
|
||||
mValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const bool primaryField = readNextValue<uint8_t>();
|
||||
mValues[firstUidInChainIndex].mAnnotations.setPrimaryField(primaryField);
|
||||
}
|
||||
|
||||
void LogEvent::parseExclusiveStateAnnotation(uint8_t annotationType) {
|
||||
if (mValues.empty() || annotationType != BOOL_TYPE) {
|
||||
mValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const bool exclusiveState = readNextValue<uint8_t>();
|
||||
mExclusiveStateFieldIndex = static_cast<int8_t>(mValues.size() - 1);
|
||||
mValues[getExclusiveStateFieldIndex()].mAnnotations.setExclusiveState(exclusiveState);
|
||||
}
|
||||
|
||||
void LogEvent::parseTriggerStateResetAnnotation(uint8_t annotationType) {
|
||||
if (mValues.empty() || annotationType != INT32_TYPE) {
|
||||
mValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
mResetState = readNextValue<int32_t>();
|
||||
}
|
||||
|
||||
void LogEvent::parseStateNestedAnnotation(uint8_t annotationType) {
|
||||
if (mValues.empty() || annotationType != BOOL_TYPE) {
|
||||
mValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
bool nested = readNextValue<uint8_t>();
|
||||
mValues[mValues.size() - 1].mAnnotations.setNested(nested);
|
||||
}
|
||||
|
||||
// firstUidInChainIndex is a default parameter that is only needed when parsing
|
||||
// annotations for attribution chains.
|
||||
void LogEvent::parseAnnotations(uint8_t numAnnotations, int firstUidInChainIndex) {
|
||||
for (uint8_t i = 0; i < numAnnotations; i++) {
|
||||
uint8_t annotationId = readNextValue<uint8_t>();
|
||||
uint8_t annotationType = readNextValue<uint8_t>();
|
||||
|
||||
switch (annotationId) {
|
||||
case ANNOTATION_ID_IS_UID:
|
||||
parseIsUidAnnotation(annotationType);
|
||||
break;
|
||||
case ANNOTATION_ID_TRUNCATE_TIMESTAMP:
|
||||
parseTruncateTimestampAnnotation(annotationType);
|
||||
break;
|
||||
case ANNOTATION_ID_PRIMARY_FIELD:
|
||||
parsePrimaryFieldAnnotation(annotationType);
|
||||
break;
|
||||
case ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID:
|
||||
parsePrimaryFieldFirstUidAnnotation(annotationType, firstUidInChainIndex);
|
||||
break;
|
||||
case ANNOTATION_ID_EXCLUSIVE_STATE:
|
||||
parseExclusiveStateAnnotation(annotationType);
|
||||
break;
|
||||
case ANNOTATION_ID_TRIGGER_STATE_RESET:
|
||||
parseTriggerStateResetAnnotation(annotationType);
|
||||
break;
|
||||
case ANNOTATION_ID_STATE_NESTED:
|
||||
parseStateNestedAnnotation(annotationType);
|
||||
break;
|
||||
default:
|
||||
mValid = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This parsing logic is tied to the encoding scheme used in StatsEvent.java and
|
||||
// stats_event.c
|
||||
bool LogEvent::parseBuffer(uint8_t* buf, size_t len) {
|
||||
mBuf = buf;
|
||||
mRemainingLen = (uint32_t)len;
|
||||
|
||||
int32_t pos[] = {1, 1, 1};
|
||||
bool last[] = {false, false, false};
|
||||
|
||||
// Beginning of buffer is OBJECT_TYPE | NUM_FIELDS | TIMESTAMP | ATOM_ID
|
||||
uint8_t typeInfo = readNextValue<uint8_t>();
|
||||
if (getTypeId(typeInfo) != OBJECT_TYPE) mValid = false;
|
||||
|
||||
uint8_t numElements = readNextValue<uint8_t>();
|
||||
if (numElements < 2 || numElements > 127) mValid = false;
|
||||
|
||||
typeInfo = readNextValue<uint8_t>();
|
||||
if (getTypeId(typeInfo) != INT64_TYPE) mValid = false;
|
||||
mElapsedTimestampNs = readNextValue<int64_t>();
|
||||
numElements--;
|
||||
|
||||
typeInfo = readNextValue<uint8_t>();
|
||||
if (getTypeId(typeInfo) != INT32_TYPE) mValid = false;
|
||||
mTagId = readNextValue<int32_t>();
|
||||
numElements--;
|
||||
parseAnnotations(getNumAnnotations(typeInfo)); // atom-level annotations
|
||||
|
||||
for (pos[0] = 1; pos[0] <= numElements && mValid; pos[0]++) {
|
||||
last[0] = (pos[0] == numElements);
|
||||
|
||||
typeInfo = readNextValue<uint8_t>();
|
||||
uint8_t typeId = getTypeId(typeInfo);
|
||||
|
||||
switch (typeId) {
|
||||
case BOOL_TYPE:
|
||||
parseBool(pos, /*depth=*/0, last, getNumAnnotations(typeInfo));
|
||||
break;
|
||||
case INT32_TYPE:
|
||||
parseInt32(pos, /*depth=*/0, last, getNumAnnotations(typeInfo));
|
||||
break;
|
||||
case INT64_TYPE:
|
||||
parseInt64(pos, /*depth=*/0, last, getNumAnnotations(typeInfo));
|
||||
break;
|
||||
case FLOAT_TYPE:
|
||||
parseFloat(pos, /*depth=*/0, last, getNumAnnotations(typeInfo));
|
||||
break;
|
||||
case BYTE_ARRAY_TYPE:
|
||||
parseByteArray(pos, /*depth=*/0, last, getNumAnnotations(typeInfo));
|
||||
break;
|
||||
case STRING_TYPE:
|
||||
parseString(pos, /*depth=*/0, last, getNumAnnotations(typeInfo));
|
||||
break;
|
||||
case KEY_VALUE_PAIRS_TYPE:
|
||||
parseKeyValuePairs(pos, /*depth=*/0, last, getNumAnnotations(typeInfo));
|
||||
break;
|
||||
case ATTRIBUTION_CHAIN_TYPE:
|
||||
parseAttributionChain(pos, /*depth=*/0, last, getNumAnnotations(typeInfo));
|
||||
break;
|
||||
case ERROR_TYPE:
|
||||
/* mErrorBitmask =*/ readNextValue<int32_t>();
|
||||
mValid = false;
|
||||
break;
|
||||
default:
|
||||
mValid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (mRemainingLen != 0) mValid = false;
|
||||
mBuf = nullptr;
|
||||
return mValid;
|
||||
}
|
||||
|
||||
uint8_t LogEvent::getTypeId(uint8_t typeInfo) {
|
||||
return typeInfo & 0x0F; // type id in lower 4 bytes
|
||||
}
|
||||
|
||||
uint8_t LogEvent::getNumAnnotations(uint8_t typeInfo) {
|
||||
return (typeInfo >> 4) & 0x0F; // num annotations in upper 4 bytes
|
||||
}
|
||||
|
||||
int64_t LogEvent::GetLong(size_t key, status_t* err) const {
|
||||
// TODO(b/110561208): encapsulate the magical operations in Field struct as static functions
|
||||
int field = getSimpleField(key);
|
||||
for (const auto& value : mValues) {
|
||||
if (value.mField.getField() == field) {
|
||||
if (value.mValue.getType() == LONG) {
|
||||
return value.mValue.long_value;
|
||||
} else if (value.mValue.getType() == INT) {
|
||||
return value.mValue.int_value;
|
||||
} else {
|
||||
*err = BAD_TYPE;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if ((size_t)value.mField.getPosAtDepth(0) > key) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
*err = BAD_INDEX;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int LogEvent::GetInt(size_t key, status_t* err) const {
|
||||
int field = getSimpleField(key);
|
||||
for (const auto& value : mValues) {
|
||||
if (value.mField.getField() == field) {
|
||||
if (value.mValue.getType() == INT) {
|
||||
return value.mValue.int_value;
|
||||
} else {
|
||||
*err = BAD_TYPE;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if ((size_t)value.mField.getPosAtDepth(0) > key) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
*err = BAD_INDEX;
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char* LogEvent::GetString(size_t key, status_t* err) const {
|
||||
int field = getSimpleField(key);
|
||||
for (const auto& value : mValues) {
|
||||
if (value.mField.getField() == field) {
|
||||
if (value.mValue.getType() == STRING) {
|
||||
return value.mValue.str_value.c_str();
|
||||
} else {
|
||||
*err = BAD_TYPE;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if ((size_t)value.mField.getPosAtDepth(0) > key) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
*err = BAD_INDEX;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool LogEvent::GetBool(size_t key, status_t* err) const {
|
||||
int field = getSimpleField(key);
|
||||
for (const auto& value : mValues) {
|
||||
if (value.mField.getField() == field) {
|
||||
if (value.mValue.getType() == INT) {
|
||||
return value.mValue.int_value != 0;
|
||||
} else if (value.mValue.getType() == LONG) {
|
||||
return value.mValue.long_value != 0;
|
||||
} else {
|
||||
*err = BAD_TYPE;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if ((size_t)value.mField.getPosAtDepth(0) > key) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
*err = BAD_INDEX;
|
||||
return false;
|
||||
}
|
||||
|
||||
float LogEvent::GetFloat(size_t key, status_t* err) const {
|
||||
int field = getSimpleField(key);
|
||||
for (const auto& value : mValues) {
|
||||
if (value.mField.getField() == field) {
|
||||
if (value.mValue.getType() == FLOAT) {
|
||||
return value.mValue.float_value;
|
||||
} else {
|
||||
*err = BAD_TYPE;
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
if ((size_t)value.mField.getPosAtDepth(0) > key) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
*err = BAD_INDEX;
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> LogEvent::GetStorage(size_t key, status_t* err) const {
|
||||
int field = getSimpleField(key);
|
||||
for (const auto& value : mValues) {
|
||||
if (value.mField.getField() == field) {
|
||||
if (value.mValue.getType() == STORAGE) {
|
||||
return value.mValue.storage_value;
|
||||
} else {
|
||||
*err = BAD_TYPE;
|
||||
return vector<uint8_t>();
|
||||
}
|
||||
}
|
||||
if ((size_t)value.mField.getPosAtDepth(0) > key) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
*err = BAD_INDEX;
|
||||
return vector<uint8_t>();
|
||||
}
|
||||
|
||||
string LogEvent::ToString() const {
|
||||
string result;
|
||||
result += StringPrintf("{ uid(%d) %lld %lld (%d)", mLogUid, (long long)mLogdTimestampNs,
|
||||
(long long)mElapsedTimestampNs, mTagId);
|
||||
for (const auto& value : mValues) {
|
||||
result +=
|
||||
StringPrintf("%#x", value.mField.getField()) + "->" + value.mValue.toString() + " ";
|
||||
}
|
||||
result += " }";
|
||||
return result;
|
||||
}
|
||||
|
||||
void LogEvent::ToProto(ProtoOutputStream& protoOutput) const {
|
||||
writeFieldValueTreeToStream(mTagId, getValues(), &protoOutput);
|
||||
}
|
||||
|
||||
bool LogEvent::hasAttributionChain(std::pair<int, int>* indexRange) const {
|
||||
if (mAttributionChainStartIndex == -1 || mAttributionChainEndIndex == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (nullptr != indexRange) {
|
||||
indexRange->first = static_cast<int>(mAttributionChainStartIndex);
|
||||
indexRange->second = static_cast<int>(mAttributionChainEndIndex);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void writeExperimentIdsToProto(const std::vector<int64_t>& experimentIds,
|
||||
std::vector<uint8_t>* protoOut) {
|
||||
ProtoOutputStream proto;
|
||||
for (const auto& expId : experimentIds) {
|
||||
proto.write(FIELD_TYPE_INT64 | FIELD_COUNT_REPEATED | FIELD_ID_EXPERIMENT_ID,
|
||||
(long long)expId);
|
||||
}
|
||||
|
||||
protoOut->resize(proto.size());
|
||||
size_t pos = 0;
|
||||
sp<ProtoReader> reader = proto.data();
|
||||
while (reader->readBuffer() != NULL) {
|
||||
size_t toRead = reader->currentToRead();
|
||||
std::memcpy(protoOut->data() + pos, reader->readBuffer(), toRead);
|
||||
pos += toRead;
|
||||
reader->move(toRead);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,331 +0,0 @@
|
||||
/*
|
||||
* 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 "FieldValue.h"
|
||||
|
||||
#include <android/util/ProtoOutputStream.h>
|
||||
#include <private/android_logger.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
struct InstallTrainInfo {
|
||||
int64_t trainVersionCode;
|
||||
std::string trainName;
|
||||
int32_t status;
|
||||
std::vector<int64_t> experimentIds;
|
||||
bool requiresStaging;
|
||||
bool rollbackEnabled;
|
||||
bool requiresLowLatencyMonitor;
|
||||
};
|
||||
|
||||
/**
|
||||
* This class decodes the structured, serialized encoding of an atom into a
|
||||
* vector of FieldValues.
|
||||
*/
|
||||
class LogEvent {
|
||||
public:
|
||||
/**
|
||||
* \param uid user id of the logging caller
|
||||
* \param pid process id of the logging caller
|
||||
*/
|
||||
explicit LogEvent(int32_t uid, int32_t pid);
|
||||
|
||||
/**
|
||||
* Parses the atomId, timestamp, and vector of values from a buffer
|
||||
* containing the StatsEvent/AStatsEvent encoding of an atom.
|
||||
*
|
||||
* \param buf a buffer that begins at the start of the serialized atom (it
|
||||
* should not include the android_log_header_t or the StatsEventTag)
|
||||
* \param len size of the buffer
|
||||
*
|
||||
* \return success of the initialization
|
||||
*/
|
||||
bool parseBuffer(uint8_t* buf, size_t len);
|
||||
|
||||
// Constructs a BinaryPushStateChanged LogEvent from API call.
|
||||
explicit LogEvent(const std::string& trainName, int64_t trainVersionCode, bool requiresStaging,
|
||||
bool rollbackEnabled, bool requiresLowLatencyMonitor, int32_t state,
|
||||
const std::vector<uint8_t>& experimentIds, int32_t userId);
|
||||
|
||||
explicit LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
|
||||
const InstallTrainInfo& installTrainInfo);
|
||||
|
||||
~LogEvent() {}
|
||||
|
||||
/**
|
||||
* Get the timestamp associated with this event.
|
||||
*/
|
||||
inline int64_t GetLogdTimestampNs() const { return mLogdTimestampNs; }
|
||||
inline int64_t GetElapsedTimestampNs() const { return mElapsedTimestampNs; }
|
||||
|
||||
/**
|
||||
* Get the tag for this event.
|
||||
*/
|
||||
inline int GetTagId() const { return mTagId; }
|
||||
|
||||
/**
|
||||
* Get the uid of the logging client.
|
||||
* Returns -1 if the uid is unknown/has not been set.
|
||||
*/
|
||||
inline int32_t GetUid() const { return mLogUid; }
|
||||
|
||||
/**
|
||||
* Get the pid of the logging client.
|
||||
* Returns -1 if the pid is unknown/has not been set.
|
||||
*/
|
||||
inline int32_t GetPid() const { return mLogPid; }
|
||||
|
||||
/**
|
||||
* Get the nth value, starting at 1.
|
||||
*
|
||||
* Returns BAD_INDEX if the index is larger than the number of elements.
|
||||
* Returns BAD_TYPE if the index is available but the data is the wrong type.
|
||||
*/
|
||||
int64_t GetLong(size_t key, status_t* err) const;
|
||||
int GetInt(size_t key, status_t* err) const;
|
||||
const char* GetString(size_t key, status_t* err) const;
|
||||
bool GetBool(size_t key, status_t* err) const;
|
||||
float GetFloat(size_t key, status_t* err) const;
|
||||
std::vector<uint8_t> GetStorage(size_t key, status_t* err) const;
|
||||
|
||||
/**
|
||||
* Return a string representation of this event.
|
||||
*/
|
||||
std::string ToString() const;
|
||||
|
||||
/**
|
||||
* Write this object to a ProtoOutputStream.
|
||||
*/
|
||||
void ToProto(android::util::ProtoOutputStream& out) const;
|
||||
|
||||
/**
|
||||
* Set elapsed timestamp if the original timestamp is missing.
|
||||
*/
|
||||
void setElapsedTimestampNs(int64_t timestampNs) {
|
||||
mElapsedTimestampNs = timestampNs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the timestamp if the original logd timestamp is missing.
|
||||
*/
|
||||
void setLogdWallClockTimestampNs(int64_t timestampNs) {
|
||||
mLogdTimestampNs = timestampNs;
|
||||
}
|
||||
|
||||
inline int size() const {
|
||||
return mValues.size();
|
||||
}
|
||||
|
||||
const std::vector<FieldValue>& getValues() const {
|
||||
return mValues;
|
||||
}
|
||||
|
||||
std::vector<FieldValue>* getMutableValues() {
|
||||
return &mValues;
|
||||
}
|
||||
|
||||
// Default value = false
|
||||
inline bool shouldTruncateTimestamp() const {
|
||||
return mTruncateTimestamp;
|
||||
}
|
||||
|
||||
// Returns the index of the uid field within the FieldValues vector if the
|
||||
// uid exists. If there is no uid field, returns -1.
|
||||
//
|
||||
// If the index within the atom definition is desired, do the following:
|
||||
// int vectorIndex = LogEvent.getUidFieldIndex();
|
||||
// if (vectorIndex != -1) {
|
||||
// FieldValue& v = LogEvent.getValues()[vectorIndex];
|
||||
// int atomIndex = v.mField.getPosAtDepth(0);
|
||||
// }
|
||||
// Note that atomIndex is 1-indexed.
|
||||
inline int getUidFieldIndex() {
|
||||
return static_cast<int>(mUidFieldIndex);
|
||||
}
|
||||
|
||||
// Returns whether this LogEvent has an AttributionChain.
|
||||
// If it does and indexRange is not a nullptr, populate indexRange with the start and end index
|
||||
// of the AttributionChain within mValues.
|
||||
bool hasAttributionChain(std::pair<int, int>* indexRange = nullptr) const;
|
||||
|
||||
// Returns the index of the exclusive state field within the FieldValues vector if
|
||||
// an exclusive state exists. If there is no exclusive state field, returns -1.
|
||||
//
|
||||
// If the index within the atom definition is desired, do the following:
|
||||
// int vectorIndex = LogEvent.getExclusiveStateFieldIndex();
|
||||
// if (vectorIndex != -1) {
|
||||
// FieldValue& v = LogEvent.getValues()[vectorIndex];
|
||||
// int atomIndex = v.mField.getPosAtDepth(0);
|
||||
// }
|
||||
// Note that atomIndex is 1-indexed.
|
||||
inline int getExclusiveStateFieldIndex() const {
|
||||
return static_cast<int>(mExclusiveStateFieldIndex);
|
||||
}
|
||||
|
||||
// If a reset state is not sent in the StatsEvent, returns -1. Note that a
|
||||
// reset state is sent if and only if a reset should be triggered.
|
||||
inline int getResetState() const {
|
||||
return mResetState;
|
||||
}
|
||||
|
||||
inline LogEvent makeCopy() {
|
||||
return LogEvent(*this);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
status_t updateValue(size_t key, T& value, Type type) {
|
||||
int field = getSimpleField(key);
|
||||
for (auto& fieldValue : mValues) {
|
||||
if (fieldValue.mField.getField() == field) {
|
||||
if (fieldValue.mValue.getType() == type) {
|
||||
fieldValue.mValue = Value(value);
|
||||
return OK;
|
||||
} else {
|
||||
return BAD_TYPE;
|
||||
}
|
||||
}
|
||||
}
|
||||
return BAD_INDEX;
|
||||
}
|
||||
|
||||
bool isValid() const {
|
||||
return mValid;
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* Only use this if copy is absolutely needed.
|
||||
*/
|
||||
LogEvent(const LogEvent&) = default;
|
||||
|
||||
void parseInt32(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
|
||||
void parseInt64(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
|
||||
void parseString(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
|
||||
void parseFloat(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
|
||||
void parseBool(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
|
||||
void parseByteArray(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
|
||||
void parseKeyValuePairs(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
|
||||
void parseAttributionChain(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
|
||||
|
||||
void parseAnnotations(uint8_t numAnnotations, int firstUidInChainIndex = -1);
|
||||
void parseIsUidAnnotation(uint8_t annotationType);
|
||||
void parseTruncateTimestampAnnotation(uint8_t annotationType);
|
||||
void parsePrimaryFieldAnnotation(uint8_t annotationType);
|
||||
void parsePrimaryFieldFirstUidAnnotation(uint8_t annotationType, int firstUidInChainIndex);
|
||||
void parseExclusiveStateAnnotation(uint8_t annotationType);
|
||||
void parseTriggerStateResetAnnotation(uint8_t annotationType);
|
||||
void parseStateNestedAnnotation(uint8_t annotationType);
|
||||
bool checkPreviousValueType(Type expected);
|
||||
|
||||
/**
|
||||
* The below two variables are only valid during the execution of
|
||||
* parseBuffer. There are no guarantees about the state of these variables
|
||||
* before/after.
|
||||
*/
|
||||
uint8_t* mBuf;
|
||||
uint32_t mRemainingLen; // number of valid bytes left in the buffer being parsed
|
||||
|
||||
bool mValid = true; // stores whether the event we received from the socket is valid
|
||||
|
||||
/**
|
||||
* Side-effects:
|
||||
* If there is enough space in buffer to read value of type T
|
||||
* - move mBuf past the value that was just read
|
||||
* - decrement mRemainingLen by size of T
|
||||
* Else
|
||||
* - set mValid to false
|
||||
*/
|
||||
template <class T>
|
||||
T readNextValue() {
|
||||
T value;
|
||||
if (mRemainingLen < sizeof(T)) {
|
||||
mValid = false;
|
||||
value = 0; // all primitive types can successfully cast 0
|
||||
} else {
|
||||
// When alignof(T) == 1, hopefully the compiler can optimize away
|
||||
// this conditional as always true.
|
||||
if ((reinterpret_cast<uintptr_t>(mBuf) % alignof(T)) == 0) {
|
||||
// We're properly aligned, and can safely make this assignment.
|
||||
value = *((T*)mBuf);
|
||||
} else {
|
||||
// We need to use memcpy. It's slower, but safe.
|
||||
memcpy(&value, mBuf, sizeof(T));
|
||||
}
|
||||
mBuf += sizeof(T);
|
||||
mRemainingLen -= sizeof(T);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void addToValues(int32_t* pos, int32_t depth, T& value, bool* last) {
|
||||
Field f = Field(mTagId, pos, depth);
|
||||
// do not decorate last position at depth 0
|
||||
for (int i = 1; i < depth; i++) {
|
||||
if (last[i]) f.decorateLastPos(i);
|
||||
}
|
||||
|
||||
Value v = Value(value);
|
||||
mValues.push_back(FieldValue(f, v));
|
||||
}
|
||||
|
||||
uint8_t getTypeId(uint8_t typeInfo);
|
||||
uint8_t getNumAnnotations(uint8_t typeInfo);
|
||||
|
||||
// The items are naturally sorted in DFS order as we read them. this allows us to do fast
|
||||
// matching.
|
||||
std::vector<FieldValue> mValues;
|
||||
|
||||
// The timestamp set by the logd.
|
||||
int64_t mLogdTimestampNs;
|
||||
|
||||
// The elapsed timestamp set by statsd log writer.
|
||||
int64_t mElapsedTimestampNs;
|
||||
|
||||
// The atom tag of the event (defaults to 0 if client does not
|
||||
// appropriately set the atom id).
|
||||
int mTagId = 0;
|
||||
|
||||
// The uid of the logging client (defaults to -1).
|
||||
int32_t mLogUid = -1;
|
||||
|
||||
// The pid of the logging client (defaults to -1).
|
||||
int32_t mLogPid = -1;
|
||||
|
||||
// Annotations
|
||||
bool mTruncateTimestamp = false;
|
||||
int mResetState = -1;
|
||||
|
||||
// Indexes within the FieldValue vector can be stored in 7 bits because
|
||||
// that's the assumption enforced by the encoding used in FieldValue.
|
||||
int8_t mUidFieldIndex = -1;
|
||||
int8_t mAttributionChainStartIndex = -1;
|
||||
int8_t mAttributionChainEndIndex = -1;
|
||||
int8_t mExclusiveStateFieldIndex = -1;
|
||||
};
|
||||
|
||||
void writeExperimentIdsToProto(const std::vector<int64_t>& experimentIds, std::vector<uint8_t>* protoOut);
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#define DEBUG false // STOPSHIP if true
|
||||
#include "Log.h"
|
||||
|
||||
#include "LogEventQueue.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
using std::unique_lock;
|
||||
using std::unique_ptr;
|
||||
|
||||
unique_ptr<LogEvent> LogEventQueue::waitPop() {
|
||||
std::unique_lock<std::mutex> lock(mMutex);
|
||||
|
||||
if (mQueue.empty()) {
|
||||
mCondition.wait(lock, [this] { return !this->mQueue.empty(); });
|
||||
}
|
||||
|
||||
unique_ptr<LogEvent> item = std::move(mQueue.front());
|
||||
mQueue.pop();
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
bool LogEventQueue::push(unique_ptr<LogEvent> item, int64_t* oldestTimestampNs) {
|
||||
bool success;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mMutex);
|
||||
if (mQueue.size() < mQueueLimit) {
|
||||
mQueue.push(std::move(item));
|
||||
success = true;
|
||||
} else {
|
||||
// safe operation as queue must not be empty.
|
||||
*oldestTimestampNs = mQueue.front()->GetElapsedTimestampNs();
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
mCondition.notify_one();
|
||||
return success;
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "LogEvent.h"
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
/**
|
||||
* A zero copy thread safe queue buffer for producing and consuming LogEvent.
|
||||
*/
|
||||
class LogEventQueue {
|
||||
public:
|
||||
explicit LogEventQueue(size_t maxSize) : mQueueLimit(maxSize){};
|
||||
|
||||
/**
|
||||
* Blocking read one event from the queue.
|
||||
*/
|
||||
std::unique_ptr<LogEvent> waitPop();
|
||||
|
||||
/**
|
||||
* Puts a LogEvent ptr to the end of the queue.
|
||||
* Returns false on failure when the queue is full, and output the oldest event timestamp
|
||||
* in the queue.
|
||||
*/
|
||||
bool push(std::unique_ptr<LogEvent> event, int64_t* oldestTimestampNs);
|
||||
|
||||
private:
|
||||
const size_t mQueueLimit;
|
||||
std::condition_variable mCondition;
|
||||
std::mutex mMutex;
|
||||
std::queue<std::unique_ptr<LogEvent>> mQueue;
|
||||
};
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,113 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#define DEBUG false // STOPSHIP if true
|
||||
#include "Log.h"
|
||||
|
||||
#include "StatsService.h"
|
||||
#include "socket/StatsSocketListener.h"
|
||||
|
||||
#include <android/binder_interface_utils.h>
|
||||
#include <android/binder_process.h>
|
||||
#include <android/binder_manager.h>
|
||||
#include <utils/Looper.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
using namespace android;
|
||||
using namespace android::os::statsd;
|
||||
using ::ndk::SharedRefBase;
|
||||
using std::shared_ptr;
|
||||
using std::make_shared;
|
||||
|
||||
shared_ptr<StatsService> gStatsService = nullptr;
|
||||
sp<StatsSocketListener> gSocketListener = nullptr;
|
||||
|
||||
void signalHandler(int sig) {
|
||||
if (sig == SIGPIPE) {
|
||||
// ShellSubscriber uses SIGPIPE as a signal to detect the end of the
|
||||
// client process. Don't prematurely exit(1) here. Instead, ignore the
|
||||
// signal and allow the write call to return EPIPE.
|
||||
ALOGI("statsd received SIGPIPE. Ignoring signal.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (gSocketListener != nullptr) gSocketListener->stopListener();
|
||||
if (gStatsService != nullptr) gStatsService->Terminate();
|
||||
ALOGW("statsd terminated on receiving signal %d.", sig);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void registerSignalHandlers()
|
||||
{
|
||||
struct sigaction sa;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sa.sa_flags = 0;
|
||||
sa.sa_handler = signalHandler;
|
||||
sigaction(SIGPIPE, &sa, nullptr);
|
||||
sigaction(SIGHUP, &sa, nullptr);
|
||||
sigaction(SIGINT, &sa, nullptr);
|
||||
sigaction(SIGQUIT, &sa, nullptr);
|
||||
sigaction(SIGTERM, &sa, nullptr);
|
||||
}
|
||||
|
||||
int main(int /*argc*/, char** /*argv*/) {
|
||||
// Set up the looper
|
||||
sp<Looper> looper(Looper::prepare(0 /* opts */));
|
||||
|
||||
// Set up the binder
|
||||
ABinderProcess_setThreadPoolMaxThreadCount(9);
|
||||
ABinderProcess_startThreadPool();
|
||||
|
||||
std::shared_ptr<LogEventQueue> eventQueue =
|
||||
std::make_shared<LogEventQueue>(4000 /*buffer limit. Buffer is NOT pre-allocated*/);
|
||||
|
||||
// Create the service
|
||||
gStatsService = SharedRefBase::make<StatsService>(looper, eventQueue);
|
||||
// TODO(b/149582373): Set DUMP_FLAG_PROTO once libbinder_ndk supports
|
||||
// setting dumpsys priorities.
|
||||
binder_status_t status = AServiceManager_addService(gStatsService->asBinder().get(), "stats");
|
||||
if (status != STATUS_OK) {
|
||||
ALOGE("Failed to add service as AIDL service");
|
||||
return -1;
|
||||
}
|
||||
|
||||
registerSignalHandlers();
|
||||
|
||||
gStatsService->sayHiToStatsCompanion();
|
||||
|
||||
gStatsService->Startup();
|
||||
|
||||
gSocketListener = new StatsSocketListener(eventQueue);
|
||||
|
||||
ALOGI("Statsd starts to listen to socket.");
|
||||
// Backlog and /proc/sys/net/unix/max_dgram_qlen set to large value
|
||||
if (gSocketListener->startListener(600)) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Loop forever -- the reports run on this thread in a handler, and the
|
||||
// binder calls remain responsive in their pool of one thread.
|
||||
while (true) {
|
||||
looper->pollAll(-1 /* timeoutMillis */);
|
||||
}
|
||||
ALOGW("statsd escaped from its loop.");
|
||||
|
||||
return 1;
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
/*
|
||||
* 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 "Log.h"
|
||||
|
||||
#include "CombinationLogMatchingTracker.h"
|
||||
#include "matchers/matcher_util.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
using std::set;
|
||||
using std::unordered_map;
|
||||
using std::vector;
|
||||
|
||||
CombinationLogMatchingTracker::CombinationLogMatchingTracker(const int64_t& id, const int index)
|
||||
: LogMatchingTracker(id, index) {
|
||||
}
|
||||
|
||||
CombinationLogMatchingTracker::~CombinationLogMatchingTracker() {
|
||||
}
|
||||
|
||||
bool CombinationLogMatchingTracker::init(const vector<AtomMatcher>& allLogMatchers,
|
||||
const vector<sp<LogMatchingTracker>>& allTrackers,
|
||||
const unordered_map<int64_t, int>& matcherMap,
|
||||
vector<bool>& stack) {
|
||||
if (mInitialized) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// mark this node as visited in the recursion stack.
|
||||
stack[mIndex] = true;
|
||||
|
||||
AtomMatcher_Combination matcher = allLogMatchers[mIndex].combination();
|
||||
|
||||
// LogicalOperation is missing in the config
|
||||
if (!matcher.has_operation()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mLogicalOperation = matcher.operation();
|
||||
|
||||
if (mLogicalOperation == LogicalOperation::NOT && matcher.matcher_size() != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto& child : matcher.matcher()) {
|
||||
auto pair = matcherMap.find(child);
|
||||
if (pair == matcherMap.end()) {
|
||||
ALOGW("Matcher %lld not found in the config", (long long)child);
|
||||
return false;
|
||||
}
|
||||
|
||||
int childIndex = pair->second;
|
||||
|
||||
// if the child is a visited node in the recursion -> circle detected.
|
||||
if (stack[childIndex]) {
|
||||
ALOGE("Circle detected in matcher config");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!allTrackers[childIndex]->init(allLogMatchers, allTrackers, matcherMap, stack)) {
|
||||
ALOGW("child matcher init failed %lld", (long long)child);
|
||||
return false;
|
||||
}
|
||||
|
||||
mChildren.push_back(childIndex);
|
||||
|
||||
const set<int>& childTagIds = allTrackers[childIndex]->getAtomIds();
|
||||
mAtomIds.insert(childTagIds.begin(), childTagIds.end());
|
||||
}
|
||||
|
||||
mInitialized = true;
|
||||
// unmark this node in the recursion stack.
|
||||
stack[mIndex] = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void CombinationLogMatchingTracker::onLogEvent(const LogEvent& event,
|
||||
const vector<sp<LogMatchingTracker>>& allTrackers,
|
||||
vector<MatchingState>& matcherResults) {
|
||||
// this event has been processed.
|
||||
if (matcherResults[mIndex] != MatchingState::kNotComputed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mAtomIds.find(event.GetTagId()) == mAtomIds.end()) {
|
||||
matcherResults[mIndex] = MatchingState::kNotMatched;
|
||||
return;
|
||||
}
|
||||
|
||||
// evaluate children matchers if they haven't been evaluated.
|
||||
for (const int childIndex : mChildren) {
|
||||
if (matcherResults[childIndex] == MatchingState::kNotComputed) {
|
||||
const sp<LogMatchingTracker>& child = allTrackers[childIndex];
|
||||
child->onLogEvent(event, allTrackers, matcherResults);
|
||||
}
|
||||
}
|
||||
|
||||
bool matched = combinationMatch(mChildren, mLogicalOperation, matcherResults);
|
||||
matcherResults[mIndex] = matched ? MatchingState::kMatched : MatchingState::kNotMatched;
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
#ifndef COMBINATION_LOG_MATCHING_TRACKER_H
|
||||
#define COMBINATION_LOG_MATCHING_TRACKER_H
|
||||
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include "LogMatchingTracker.h"
|
||||
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
// Represents a AtomMatcher_Combination in the StatsdConfig.
|
||||
class CombinationLogMatchingTracker : public virtual LogMatchingTracker {
|
||||
public:
|
||||
CombinationLogMatchingTracker(const int64_t& id, const int index);
|
||||
|
||||
bool init(const std::vector<AtomMatcher>& allLogMatchers,
|
||||
const std::vector<sp<LogMatchingTracker>>& allTrackers,
|
||||
const std::unordered_map<int64_t, int>& matcherMap,
|
||||
std::vector<bool>& stack);
|
||||
|
||||
~CombinationLogMatchingTracker();
|
||||
|
||||
void onLogEvent(const LogEvent& event,
|
||||
const std::vector<sp<LogMatchingTracker>>& allTrackers,
|
||||
std::vector<MatchingState>& matcherResults) override;
|
||||
|
||||
private:
|
||||
LogicalOperation mLogicalOperation;
|
||||
|
||||
std::vector<int> mChildren;
|
||||
};
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
#endif // COMBINATION_LOG_MATCHING_TRACKER_H
|
||||
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* 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 "EventMatcherWizard.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
using std::vector;
|
||||
|
||||
MatchingState EventMatcherWizard::matchLogEvent(const LogEvent& event, int matcher_index) {
|
||||
if (matcher_index < 0 || matcher_index >= (int)mAllEventMatchers.size()) {
|
||||
return MatchingState::kNotComputed;
|
||||
}
|
||||
vector<MatchingState> matcherCache(mAllEventMatchers.size(), MatchingState::kNotComputed);
|
||||
mAllEventMatchers[matcher_index]->onLogEvent(event, mAllEventMatchers, matcherCache);
|
||||
return matcherCache[matcher_index];
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "LogMatchingTracker.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
class EventMatcherWizard : public virtual android::RefBase {
|
||||
public:
|
||||
EventMatcherWizard(){}; // for testing
|
||||
EventMatcherWizard(const std::vector<sp<LogMatchingTracker>>& eventTrackers)
|
||||
: mAllEventMatchers(eventTrackers){};
|
||||
|
||||
virtual ~EventMatcherWizard(){};
|
||||
|
||||
MatchingState matchLogEvent(const LogEvent& event, int matcher_index);
|
||||
|
||||
private:
|
||||
std::vector<sp<LogMatchingTracker>> mAllEventMatchers;
|
||||
};
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,96 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef LOG_MATCHING_TRACKER_H
|
||||
#define LOG_MATCHING_TRACKER_H
|
||||
|
||||
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
|
||||
#include "logd/LogEvent.h"
|
||||
#include "matchers/matcher_util.h"
|
||||
|
||||
#include <utils/RefBase.h>
|
||||
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
class LogMatchingTracker : public virtual RefBase {
|
||||
public:
|
||||
LogMatchingTracker(const int64_t& id, const int index)
|
||||
: mId(id), mIndex(index), mInitialized(false){};
|
||||
|
||||
virtual ~LogMatchingTracker(){};
|
||||
|
||||
// Initialize this LogMatchingTracker.
|
||||
// allLogMatchers: the list of the AtomMatcher proto config. This is needed because we don't
|
||||
// store the proto object in memory. We only need it during initilization.
|
||||
// allTrackers: the list of the LogMatchingTracker objects. It's a one-to-one mapping with
|
||||
// allLogMatchers. This is needed because the initialization is done recursively
|
||||
// for CombinationLogMatchingTrackers using DFS.
|
||||
// stack: a bit map to record which matcher has been visited on the stack. This is for detecting
|
||||
// circle dependency.
|
||||
virtual bool init(const std::vector<AtomMatcher>& allLogMatchers,
|
||||
const std::vector<sp<LogMatchingTracker>>& allTrackers,
|
||||
const std::unordered_map<int64_t, int>& matcherMap,
|
||||
std::vector<bool>& stack) = 0;
|
||||
|
||||
// Called when a log event comes.
|
||||
// event: the log event.
|
||||
// allTrackers: the list of all LogMatchingTrackers. This is needed because the log processing
|
||||
// is done recursively.
|
||||
// matcherResults: The cached results for all matchers for this event. Parent matchers can
|
||||
// directly access the children's matching results if they have been evaluated.
|
||||
// Otherwise, call children matchers' onLogEvent.
|
||||
virtual void onLogEvent(const LogEvent& event,
|
||||
const std::vector<sp<LogMatchingTracker>>& allTrackers,
|
||||
std::vector<MatchingState>& matcherResults) = 0;
|
||||
|
||||
// Get the tagIds that this matcher cares about. The combined collection is stored
|
||||
// in MetricMananger, so that we can pass any LogEvents that are not interest of us. It uses
|
||||
// some memory but hopefully it can save us much CPU time when there is flood of events.
|
||||
virtual const std::set<int>& getAtomIds() const {
|
||||
return mAtomIds;
|
||||
}
|
||||
|
||||
const int64_t& getId() const {
|
||||
return mId;
|
||||
}
|
||||
|
||||
protected:
|
||||
// Name of this matching. We don't really need the name, but it makes log message easy to debug.
|
||||
const int64_t mId;
|
||||
|
||||
// Index of this LogMatchingTracker in MetricsManager's container.
|
||||
const int mIndex;
|
||||
|
||||
// Whether this LogMatchingTracker has been properly initialized.
|
||||
bool mInitialized;
|
||||
|
||||
// The collection of the event tag ids that this LogMatchingTracker cares. So we can quickly
|
||||
// return kNotMatched when we receive an event with an id not in the list. This is especially
|
||||
// useful when we have a complex CombinationLogMatcherTracker.
|
||||
std::set<int> mAtomIds;
|
||||
};
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
|
||||
#endif // LOG_MATCHING_TRACKER_H
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#define DEBUG false // STOPSHIP if true
|
||||
#include "Log.h"
|
||||
|
||||
#include "SimpleLogMatchingTracker.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
using std::unordered_map;
|
||||
using std::vector;
|
||||
|
||||
|
||||
SimpleLogMatchingTracker::SimpleLogMatchingTracker(const int64_t& id, const int index,
|
||||
const SimpleAtomMatcher& matcher,
|
||||
const UidMap& uidMap)
|
||||
: LogMatchingTracker(id, index), mMatcher(matcher), mUidMap(uidMap) {
|
||||
if (!matcher.has_atom_id()) {
|
||||
mInitialized = false;
|
||||
} else {
|
||||
mAtomIds.insert(matcher.atom_id());
|
||||
mInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
SimpleLogMatchingTracker::~SimpleLogMatchingTracker() {
|
||||
}
|
||||
|
||||
bool SimpleLogMatchingTracker::init(const vector<AtomMatcher>& allLogMatchers,
|
||||
const vector<sp<LogMatchingTracker>>& allTrackers,
|
||||
const unordered_map<int64_t, int>& matcherMap,
|
||||
vector<bool>& stack) {
|
||||
// no need to do anything.
|
||||
return mInitialized;
|
||||
}
|
||||
|
||||
void SimpleLogMatchingTracker::onLogEvent(const LogEvent& event,
|
||||
const vector<sp<LogMatchingTracker>>& allTrackers,
|
||||
vector<MatchingState>& matcherResults) {
|
||||
if (matcherResults[mIndex] != MatchingState::kNotComputed) {
|
||||
VLOG("Matcher %lld already evaluated ", (long long)mId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mAtomIds.find(event.GetTagId()) == mAtomIds.end()) {
|
||||
matcherResults[mIndex] = MatchingState::kNotMatched;
|
||||
return;
|
||||
}
|
||||
|
||||
bool matched = matchesSimple(mUidMap, mMatcher, event);
|
||||
matcherResults[mIndex] = matched ? MatchingState::kMatched : MatchingState::kNotMatched;
|
||||
VLOG("Stats SimpleLogMatcher %lld matched? %d", (long long)mId, matched);
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef SIMPLE_LOG_MATCHING_TRACKER_H
|
||||
#define SIMPLE_LOG_MATCHING_TRACKER_H
|
||||
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include "LogMatchingTracker.h"
|
||||
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
|
||||
#include "packages/UidMap.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
class SimpleLogMatchingTracker : public virtual LogMatchingTracker {
|
||||
public:
|
||||
SimpleLogMatchingTracker(const int64_t& id, const int index,
|
||||
const SimpleAtomMatcher& matcher,
|
||||
const UidMap& uidMap);
|
||||
|
||||
~SimpleLogMatchingTracker();
|
||||
|
||||
bool init(const std::vector<AtomMatcher>& allLogMatchers,
|
||||
const std::vector<sp<LogMatchingTracker>>& allTrackers,
|
||||
const std::unordered_map<int64_t, int>& matcherMap,
|
||||
std::vector<bool>& stack) override;
|
||||
|
||||
void onLogEvent(const LogEvent& event,
|
||||
const std::vector<sp<LogMatchingTracker>>& allTrackers,
|
||||
std::vector<MatchingState>& matcherResults) override;
|
||||
|
||||
private:
|
||||
const SimpleAtomMatcher mMatcher;
|
||||
const UidMap& mUidMap;
|
||||
};
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
#endif // SIMPLE_LOG_MATCHING_TRACKER_H
|
||||
@@ -1,372 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
#define DEBUG false // STOPSHIP if true
|
||||
#include "Log.h"
|
||||
|
||||
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
|
||||
#include "matchers/LogMatchingTracker.h"
|
||||
#include "matchers/matcher_util.h"
|
||||
#include "stats_util.h"
|
||||
|
||||
using std::set;
|
||||
using std::string;
|
||||
using std::vector;
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
bool combinationMatch(const vector<int>& children, const LogicalOperation& operation,
|
||||
const vector<MatchingState>& matcherResults) {
|
||||
bool matched;
|
||||
switch (operation) {
|
||||
case LogicalOperation::AND: {
|
||||
matched = true;
|
||||
for (const int childIndex : children) {
|
||||
if (matcherResults[childIndex] != MatchingState::kMatched) {
|
||||
matched = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LogicalOperation::OR: {
|
||||
matched = false;
|
||||
for (const int childIndex : children) {
|
||||
if (matcherResults[childIndex] == MatchingState::kMatched) {
|
||||
matched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LogicalOperation::NOT:
|
||||
matched = matcherResults[children[0]] == MatchingState::kNotMatched;
|
||||
break;
|
||||
case LogicalOperation::NAND:
|
||||
matched = false;
|
||||
for (const int childIndex : children) {
|
||||
if (matcherResults[childIndex] != MatchingState::kMatched) {
|
||||
matched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case LogicalOperation::NOR:
|
||||
matched = true;
|
||||
for (const int childIndex : children) {
|
||||
if (matcherResults[childIndex] == MatchingState::kMatched) {
|
||||
matched = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case LogicalOperation::LOGICAL_OPERATION_UNSPECIFIED:
|
||||
matched = false;
|
||||
break;
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
|
||||
bool tryMatchString(const UidMap& uidMap, const FieldValue& fieldValue, const string& str_match) {
|
||||
if (isAttributionUidField(fieldValue) || isUidField(fieldValue)) {
|
||||
int uid = fieldValue.mValue.int_value;
|
||||
auto aidIt = UidMap::sAidToUidMapping.find(str_match);
|
||||
if (aidIt != UidMap::sAidToUidMapping.end()) {
|
||||
return ((int)aidIt->second) == uid;
|
||||
}
|
||||
std::set<string> packageNames = uidMap.getAppNamesFromUid(uid, true /* normalize*/);
|
||||
return packageNames.find(str_match) != packageNames.end();
|
||||
} else if (fieldValue.mValue.getType() == STRING) {
|
||||
return fieldValue.mValue.str_value == str_match;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool matchesSimple(const UidMap& uidMap, const FieldValueMatcher& matcher,
|
||||
const vector<FieldValue>& values, int start, int end, int depth) {
|
||||
if (depth > 2) {
|
||||
ALOGE("Depth > 3 not supported");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (start >= end) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Filter by entry field first
|
||||
int newStart = -1;
|
||||
int newEnd = end;
|
||||
// because the fields are naturally sorted in the DFS order. we can safely
|
||||
// break when pos is larger than the one we are searching for.
|
||||
for (int i = start; i < end; i++) {
|
||||
int pos = values[i].mField.getPosAtDepth(depth);
|
||||
if (pos == matcher.field()) {
|
||||
if (newStart == -1) {
|
||||
newStart = i;
|
||||
}
|
||||
newEnd = i + 1;
|
||||
} else if (pos > matcher.field()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Now we have zoomed in to a new range
|
||||
start = newStart;
|
||||
end = newEnd;
|
||||
|
||||
if (start == -1) {
|
||||
// No such field found.
|
||||
return false;
|
||||
}
|
||||
|
||||
vector<pair<int, int>> ranges; // the ranges are for matching ANY position
|
||||
if (matcher.has_position()) {
|
||||
// Repeated fields position is stored as a node in the path.
|
||||
depth++;
|
||||
if (depth > 2) {
|
||||
return false;
|
||||
}
|
||||
switch (matcher.position()) {
|
||||
case Position::FIRST: {
|
||||
for (int i = start; i < end; i++) {
|
||||
int pos = values[i].mField.getPosAtDepth(depth);
|
||||
if (pos != 1) {
|
||||
// Again, the log elements are stored in sorted order. so
|
||||
// once the position is > 1, we break;
|
||||
end = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ranges.push_back(std::make_pair(start, end));
|
||||
break;
|
||||
}
|
||||
case Position::LAST: {
|
||||
// move the starting index to the first LAST field at the depth.
|
||||
for (int i = start; i < end; i++) {
|
||||
if (values[i].mField.isLastPos(depth)) {
|
||||
start = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ranges.push_back(std::make_pair(start, end));
|
||||
break;
|
||||
}
|
||||
case Position::ANY: {
|
||||
// ANY means all the children matchers match in any of the sub trees, it's a match
|
||||
newStart = start;
|
||||
newEnd = end;
|
||||
// Here start is guaranteed to be a valid index.
|
||||
int currentPos = values[start].mField.getPosAtDepth(depth);
|
||||
// Now find all sub trees ranges.
|
||||
for (int i = start; i < end; i++) {
|
||||
int newPos = values[i].mField.getPosAtDepth(depth);
|
||||
if (newPos != currentPos) {
|
||||
ranges.push_back(std::make_pair(newStart, i));
|
||||
newStart = i;
|
||||
currentPos = newPos;
|
||||
}
|
||||
}
|
||||
ranges.push_back(std::make_pair(newStart, end));
|
||||
break;
|
||||
}
|
||||
case Position::ALL:
|
||||
ALOGE("Not supported: field matcher with ALL position.");
|
||||
break;
|
||||
case Position::POSITION_UNKNOWN:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// No position
|
||||
ranges.push_back(std::make_pair(start, end));
|
||||
}
|
||||
// start and end are still pointing to the matched range.
|
||||
switch (matcher.value_matcher_case()) {
|
||||
case FieldValueMatcher::kMatchesTuple: {
|
||||
++depth;
|
||||
// If any range matches all matchers, good.
|
||||
for (const auto& range : ranges) {
|
||||
bool matched = true;
|
||||
for (const auto& subMatcher : matcher.matches_tuple().field_value_matcher()) {
|
||||
if (!matchesSimple(uidMap, subMatcher, values, range.first, range.second,
|
||||
depth)) {
|
||||
matched = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (matched) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// Finally, we get to the point of real value matching.
|
||||
// If the field matcher ends with ANY, then we have [start, end) range > 1.
|
||||
// In the following, we should return true, when ANY of the values matches.
|
||||
case FieldValueMatcher::ValueMatcherCase::kEqBool: {
|
||||
for (int i = start; i < end; i++) {
|
||||
if ((values[i].mValue.getType() == INT &&
|
||||
(values[i].mValue.int_value != 0) == matcher.eq_bool()) ||
|
||||
(values[i].mValue.getType() == LONG &&
|
||||
(values[i].mValue.long_value != 0) == matcher.eq_bool())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case FieldValueMatcher::ValueMatcherCase::kEqString: {
|
||||
for (int i = start; i < end; i++) {
|
||||
if (tryMatchString(uidMap, values[i], matcher.eq_string())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case FieldValueMatcher::ValueMatcherCase::kNeqAnyString: {
|
||||
const auto& str_list = matcher.neq_any_string();
|
||||
for (int i = start; i < end; i++) {
|
||||
bool notEqAll = true;
|
||||
for (const auto& str : str_list.str_value()) {
|
||||
if (tryMatchString(uidMap, values[i], str)) {
|
||||
notEqAll = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (notEqAll) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case FieldValueMatcher::ValueMatcherCase::kEqAnyString: {
|
||||
const auto& str_list = matcher.eq_any_string();
|
||||
for (int i = start; i < end; i++) {
|
||||
for (const auto& str : str_list.str_value()) {
|
||||
if (tryMatchString(uidMap, values[i], str)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case FieldValueMatcher::ValueMatcherCase::kEqInt: {
|
||||
for (int i = start; i < end; i++) {
|
||||
if (values[i].mValue.getType() == INT &&
|
||||
(matcher.eq_int() == values[i].mValue.int_value)) {
|
||||
return true;
|
||||
}
|
||||
// eq_int covers both int and long.
|
||||
if (values[i].mValue.getType() == LONG &&
|
||||
(matcher.eq_int() == values[i].mValue.long_value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case FieldValueMatcher::ValueMatcherCase::kLtInt: {
|
||||
for (int i = start; i < end; i++) {
|
||||
if (values[i].mValue.getType() == INT &&
|
||||
(values[i].mValue.int_value < matcher.lt_int())) {
|
||||
return true;
|
||||
}
|
||||
// lt_int covers both int and long.
|
||||
if (values[i].mValue.getType() == LONG &&
|
||||
(values[i].mValue.long_value < matcher.lt_int())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case FieldValueMatcher::ValueMatcherCase::kGtInt: {
|
||||
for (int i = start; i < end; i++) {
|
||||
if (values[i].mValue.getType() == INT &&
|
||||
(values[i].mValue.int_value > matcher.gt_int())) {
|
||||
return true;
|
||||
}
|
||||
// gt_int covers both int and long.
|
||||
if (values[i].mValue.getType() == LONG &&
|
||||
(values[i].mValue.long_value > matcher.gt_int())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case FieldValueMatcher::ValueMatcherCase::kLtFloat: {
|
||||
for (int i = start; i < end; i++) {
|
||||
if (values[i].mValue.getType() == FLOAT &&
|
||||
(values[i].mValue.float_value < matcher.lt_float())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case FieldValueMatcher::ValueMatcherCase::kGtFloat: {
|
||||
for (int i = start; i < end; i++) {
|
||||
if (values[i].mValue.getType() == FLOAT &&
|
||||
(values[i].mValue.float_value > matcher.gt_float())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case FieldValueMatcher::ValueMatcherCase::kLteInt: {
|
||||
for (int i = start; i < end; i++) {
|
||||
if (values[i].mValue.getType() == INT &&
|
||||
(values[i].mValue.int_value <= matcher.lte_int())) {
|
||||
return true;
|
||||
}
|
||||
// lte_int covers both int and long.
|
||||
if (values[i].mValue.getType() == LONG &&
|
||||
(values[i].mValue.long_value <= matcher.lte_int())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case FieldValueMatcher::ValueMatcherCase::kGteInt: {
|
||||
for (int i = start; i < end; i++) {
|
||||
if (values[i].mValue.getType() == INT &&
|
||||
(values[i].mValue.int_value >= matcher.gte_int())) {
|
||||
return true;
|
||||
}
|
||||
// gte_int covers both int and long.
|
||||
if (values[i].mValue.getType() == LONG &&
|
||||
(values[i].mValue.long_value >= matcher.gte_int())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool matchesSimple(const UidMap& uidMap, const SimpleAtomMatcher& simpleMatcher,
|
||||
const LogEvent& event) {
|
||||
if (event.GetTagId() != simpleMatcher.atom_id()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto& matcher : simpleMatcher.field_value_matcher()) {
|
||||
if (!matchesSimple(uidMap, matcher, event.getValues(), 0, event.getValues().size(), 0)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* 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 "logd/LogEvent.h"
|
||||
|
||||
#include <vector>
|
||||
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
|
||||
#include "packages/UidMap.h"
|
||||
#include "stats_util.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
enum MatchingState {
|
||||
kNotComputed = -1,
|
||||
kNotMatched = 0,
|
||||
kMatched = 1,
|
||||
};
|
||||
|
||||
bool combinationMatch(const std::vector<int>& children, const LogicalOperation& operation,
|
||||
const std::vector<MatchingState>& matcherResults);
|
||||
|
||||
bool matchesSimple(const UidMap& uidMap,
|
||||
const SimpleAtomMatcher& simpleMatcher, const LogEvent& wrapper);
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,122 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "FieldValue.h"
|
||||
#include "metadata_util.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
using google::protobuf::RepeatedPtrField;
|
||||
|
||||
void writeValueToProto(metadata::FieldValue* metadataFieldValue, const Value& value) {
|
||||
std::string storage_value;
|
||||
switch (value.getType()) {
|
||||
case INT:
|
||||
metadataFieldValue->set_value_int(value.int_value);
|
||||
break;
|
||||
case LONG:
|
||||
metadataFieldValue->set_value_long(value.long_value);
|
||||
break;
|
||||
case FLOAT:
|
||||
metadataFieldValue->set_value_float(value.float_value);
|
||||
break;
|
||||
case DOUBLE:
|
||||
metadataFieldValue->set_value_double(value.double_value);
|
||||
break;
|
||||
case STRING:
|
||||
metadataFieldValue->set_value_str(value.str_value.c_str());
|
||||
break;
|
||||
case STORAGE: // byte array
|
||||
storage_value = ((char*) value.storage_value.data());
|
||||
metadataFieldValue->set_value_storage(storage_value);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void writeMetricDimensionKeyToMetadataDimensionKey(
|
||||
const MetricDimensionKey& metricKey,
|
||||
metadata::MetricDimensionKey* metadataMetricKey) {
|
||||
for (const FieldValue& fieldValue : metricKey.getDimensionKeyInWhat().getValues()) {
|
||||
metadata::FieldValue* metadataFieldValue = metadataMetricKey->add_dimension_key_in_what();
|
||||
metadata::Field* metadataField = metadataFieldValue->mutable_field();
|
||||
metadataField->set_tag(fieldValue.mField.getTag());
|
||||
metadataField->set_field(fieldValue.mField.getField());
|
||||
writeValueToProto(metadataFieldValue, fieldValue.mValue);
|
||||
}
|
||||
|
||||
for (const FieldValue& fieldValue : metricKey.getStateValuesKey().getValues()) {
|
||||
metadata::FieldValue* metadataFieldValue = metadataMetricKey->add_state_values_key();
|
||||
metadata::Field* metadataField = metadataFieldValue->mutable_field();
|
||||
metadataField->set_tag(fieldValue.mField.getTag());
|
||||
metadataField->set_field(fieldValue.mField.getField());
|
||||
writeValueToProto(metadataFieldValue, fieldValue.mValue);
|
||||
}
|
||||
}
|
||||
|
||||
void writeFieldValuesFromMetadata(
|
||||
const RepeatedPtrField<metadata::FieldValue>& repeatedFieldValueList,
|
||||
std::vector<FieldValue>* fieldValues) {
|
||||
for (const metadata::FieldValue& metadataFieldValue : repeatedFieldValueList) {
|
||||
Field field(metadataFieldValue.field().tag(), metadataFieldValue.field().field());
|
||||
Value value;
|
||||
switch (metadataFieldValue.value_case()) {
|
||||
case metadata::FieldValue::ValueCase::kValueInt:
|
||||
value = Value(metadataFieldValue.value_int());
|
||||
break;
|
||||
case metadata::FieldValue::ValueCase::kValueLong:
|
||||
value = Value(metadataFieldValue.value_long());
|
||||
break;
|
||||
case metadata::FieldValue::ValueCase::kValueFloat:
|
||||
value = Value(metadataFieldValue.value_float());
|
||||
break;
|
||||
case metadata::FieldValue::ValueCase::kValueDouble:
|
||||
value = Value(metadataFieldValue.value_double());
|
||||
break;
|
||||
case metadata::FieldValue::ValueCase::kValueStr:
|
||||
value = Value(metadataFieldValue.value_str());
|
||||
break;
|
||||
case metadata::FieldValue::ValueCase::kValueStorage:
|
||||
value = Value(metadataFieldValue.value_storage());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
FieldValue fieldValue(field, value);
|
||||
fieldValues->emplace_back(field, value);
|
||||
}
|
||||
}
|
||||
|
||||
MetricDimensionKey loadMetricDimensionKeyFromProto(
|
||||
const metadata::MetricDimensionKey& metricDimensionKey) {
|
||||
std::vector<FieldValue> dimKeyInWhatFieldValues;
|
||||
writeFieldValuesFromMetadata(metricDimensionKey.dimension_key_in_what(),
|
||||
&dimKeyInWhatFieldValues);
|
||||
std::vector<FieldValue> stateValuesFieldValues;
|
||||
writeFieldValuesFromMetadata(metricDimensionKey.state_values_key(), &stateValuesFieldValues);
|
||||
|
||||
HashableDimensionKey dimKeyInWhat(dimKeyInWhatFieldValues);
|
||||
HashableDimensionKey stateValues(stateValuesFieldValues);
|
||||
MetricDimensionKey metricKey(dimKeyInWhat, stateValues);
|
||||
return metricKey;
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#include "HashableDimensionKey.h"
|
||||
|
||||
#include "frameworks/base/cmds/statsd/src/statsd_metadata.pb.h" // AlertMetadata
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
void writeMetricDimensionKeyToMetadataDimensionKey(const MetricDimensionKey& metricKey,
|
||||
metadata::MetricDimensionKey* metadataMetricKey);
|
||||
|
||||
MetricDimensionKey loadMetricDimensionKeyFromProto(
|
||||
const metadata::MetricDimensionKey& metricDimensionKey);
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,397 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#define DEBUG false // STOPSHIP if true
|
||||
#include "Log.h"
|
||||
|
||||
#include "CountMetricProducer.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <limits.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "guardrail/StatsdStats.h"
|
||||
#include "stats_log_util.h"
|
||||
#include "stats_util.h"
|
||||
|
||||
using android::util::FIELD_COUNT_REPEATED;
|
||||
using android::util::FIELD_TYPE_BOOL;
|
||||
using android::util::FIELD_TYPE_FLOAT;
|
||||
using android::util::FIELD_TYPE_INT32;
|
||||
using android::util::FIELD_TYPE_INT64;
|
||||
using android::util::FIELD_TYPE_MESSAGE;
|
||||
using android::util::FIELD_TYPE_STRING;
|
||||
using android::util::ProtoOutputStream;
|
||||
using std::map;
|
||||
using std::string;
|
||||
using std::unordered_map;
|
||||
using std::vector;
|
||||
using std::shared_ptr;
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
// for StatsLogReport
|
||||
const int FIELD_ID_ID = 1;
|
||||
const int FIELD_ID_COUNT_METRICS = 5;
|
||||
const int FIELD_ID_TIME_BASE = 9;
|
||||
const int FIELD_ID_BUCKET_SIZE = 10;
|
||||
const int FIELD_ID_DIMENSION_PATH_IN_WHAT = 11;
|
||||
const int FIELD_ID_IS_ACTIVE = 14;
|
||||
|
||||
// for CountMetricDataWrapper
|
||||
const int FIELD_ID_DATA = 1;
|
||||
// for CountMetricData
|
||||
const int FIELD_ID_DIMENSION_IN_WHAT = 1;
|
||||
const int FIELD_ID_SLICE_BY_STATE = 6;
|
||||
const int FIELD_ID_BUCKET_INFO = 3;
|
||||
const int FIELD_ID_DIMENSION_LEAF_IN_WHAT = 4;
|
||||
// for CountBucketInfo
|
||||
const int FIELD_ID_COUNT = 3;
|
||||
const int FIELD_ID_BUCKET_NUM = 4;
|
||||
const int FIELD_ID_START_BUCKET_ELAPSED_MILLIS = 5;
|
||||
const int FIELD_ID_END_BUCKET_ELAPSED_MILLIS = 6;
|
||||
|
||||
CountMetricProducer::CountMetricProducer(
|
||||
const ConfigKey& key, const CountMetric& metric, const int conditionIndex,
|
||||
const vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& wizard,
|
||||
const int64_t timeBaseNs, const int64_t startTimeNs,
|
||||
const unordered_map<int, shared_ptr<Activation>>& eventActivationMap,
|
||||
const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap,
|
||||
const vector<int>& slicedStateAtoms,
|
||||
const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap)
|
||||
: MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, initialConditionCache, wizard,
|
||||
eventActivationMap, eventDeactivationMap, slicedStateAtoms, stateGroupMap) {
|
||||
if (metric.has_bucket()) {
|
||||
mBucketSizeNs =
|
||||
TimeUnitToBucketSizeInMillisGuardrailed(key.GetUid(), metric.bucket()) * 1000000;
|
||||
} else {
|
||||
mBucketSizeNs = LLONG_MAX;
|
||||
}
|
||||
|
||||
if (metric.has_dimensions_in_what()) {
|
||||
translateFieldMatcher(metric.dimensions_in_what(), &mDimensionsInWhat);
|
||||
mContainANYPositionInDimensionsInWhat = HasPositionANY(metric.dimensions_in_what());
|
||||
}
|
||||
|
||||
mSliceByPositionALL = HasPositionALL(metric.dimensions_in_what());
|
||||
|
||||
if (metric.links().size() > 0) {
|
||||
for (const auto& link : metric.links()) {
|
||||
Metric2Condition mc;
|
||||
mc.conditionId = link.condition();
|
||||
translateFieldMatcher(link.fields_in_what(), &mc.metricFields);
|
||||
translateFieldMatcher(link.fields_in_condition(), &mc.conditionFields);
|
||||
mMetric2ConditionLinks.push_back(mc);
|
||||
}
|
||||
mConditionSliced = true;
|
||||
}
|
||||
|
||||
for (const auto& stateLink : metric.state_link()) {
|
||||
Metric2State ms;
|
||||
ms.stateAtomId = stateLink.state_atom_id();
|
||||
translateFieldMatcher(stateLink.fields_in_what(), &ms.metricFields);
|
||||
translateFieldMatcher(stateLink.fields_in_state(), &ms.stateFields);
|
||||
mMetric2StateLinks.push_back(ms);
|
||||
}
|
||||
|
||||
flushIfNeededLocked(startTimeNs);
|
||||
// Adjust start for partial bucket
|
||||
mCurrentBucketStartTimeNs = startTimeNs;
|
||||
|
||||
VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(),
|
||||
(long long)mBucketSizeNs, (long long)mTimeBaseNs);
|
||||
}
|
||||
|
||||
CountMetricProducer::~CountMetricProducer() {
|
||||
VLOG("~CountMetricProducer() called");
|
||||
}
|
||||
|
||||
void CountMetricProducer::onStateChanged(const int64_t eventTimeNs, const int32_t atomId,
|
||||
const HashableDimensionKey& primaryKey,
|
||||
const FieldValue& oldState, const FieldValue& newState) {
|
||||
VLOG("CountMetric %lld onStateChanged time %lld, State%d, key %s, %d -> %d",
|
||||
(long long)mMetricId, (long long)eventTimeNs, atomId, primaryKey.toString().c_str(),
|
||||
oldState.mValue.int_value, newState.mValue.int_value);
|
||||
}
|
||||
|
||||
void CountMetricProducer::dumpStatesLocked(FILE* out, bool verbose) const {
|
||||
if (mCurrentSlicedCounter == nullptr ||
|
||||
mCurrentSlicedCounter->size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
fprintf(out, "CountMetric %lld dimension size %lu\n", (long long)mMetricId,
|
||||
(unsigned long)mCurrentSlicedCounter->size());
|
||||
if (verbose) {
|
||||
for (const auto& it : *mCurrentSlicedCounter) {
|
||||
fprintf(out, "\t(what)%s\t(state)%s %lld\n",
|
||||
it.first.getDimensionKeyInWhat().toString().c_str(),
|
||||
it.first.getStateValuesKey().toString().c_str(), (unsigned long long)it.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CountMetricProducer::onSlicedConditionMayChangeLocked(bool overallCondition,
|
||||
const int64_t eventTime) {
|
||||
VLOG("Metric %lld onSlicedConditionMayChange", (long long)mMetricId);
|
||||
}
|
||||
|
||||
|
||||
void CountMetricProducer::clearPastBucketsLocked(const int64_t dumpTimeNs) {
|
||||
mPastBuckets.clear();
|
||||
}
|
||||
|
||||
void CountMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs,
|
||||
const bool include_current_partial_bucket,
|
||||
const bool erase_data,
|
||||
const DumpLatency dumpLatency,
|
||||
std::set<string> *str_set,
|
||||
ProtoOutputStream* protoOutput) {
|
||||
if (include_current_partial_bucket) {
|
||||
flushLocked(dumpTimeNs);
|
||||
} else {
|
||||
flushIfNeededLocked(dumpTimeNs);
|
||||
}
|
||||
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId);
|
||||
protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_IS_ACTIVE, isActiveLocked());
|
||||
|
||||
|
||||
if (mPastBuckets.empty()) {
|
||||
return;
|
||||
}
|
||||
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_TIME_BASE, (long long)mTimeBaseNs);
|
||||
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_SIZE, (long long)mBucketSizeNs);
|
||||
|
||||
// Fills the dimension path if not slicing by ALL.
|
||||
if (!mSliceByPositionALL) {
|
||||
if (!mDimensionsInWhat.empty()) {
|
||||
uint64_t dimenPathToken = protoOutput->start(
|
||||
FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_PATH_IN_WHAT);
|
||||
writeDimensionPathToProto(mDimensionsInWhat, protoOutput);
|
||||
protoOutput->end(dimenPathToken);
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_COUNT_METRICS);
|
||||
|
||||
for (const auto& counter : mPastBuckets) {
|
||||
const MetricDimensionKey& dimensionKey = counter.first;
|
||||
VLOG(" dimension key %s", dimensionKey.toString().c_str());
|
||||
|
||||
uint64_t wrapperToken =
|
||||
protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
|
||||
|
||||
// First fill dimension.
|
||||
if (mSliceByPositionALL) {
|
||||
uint64_t dimensionToken = protoOutput->start(
|
||||
FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT);
|
||||
writeDimensionToProto(dimensionKey.getDimensionKeyInWhat(), str_set, protoOutput);
|
||||
protoOutput->end(dimensionToken);
|
||||
} else {
|
||||
writeDimensionLeafNodesToProto(dimensionKey.getDimensionKeyInWhat(),
|
||||
FIELD_ID_DIMENSION_LEAF_IN_WHAT, str_set, protoOutput);
|
||||
}
|
||||
// Then fill slice_by_state.
|
||||
for (auto state : dimensionKey.getStateValuesKey().getValues()) {
|
||||
uint64_t stateToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED |
|
||||
FIELD_ID_SLICE_BY_STATE);
|
||||
writeStateToProto(state, protoOutput);
|
||||
protoOutput->end(stateToken);
|
||||
}
|
||||
// Then fill bucket_info (CountBucketInfo).
|
||||
for (const auto& bucket : counter.second) {
|
||||
uint64_t bucketInfoToken = protoOutput->start(
|
||||
FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_BUCKET_INFO);
|
||||
// Partial bucket.
|
||||
if (bucket.mBucketEndNs - bucket.mBucketStartNs != mBucketSizeNs) {
|
||||
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_BUCKET_ELAPSED_MILLIS,
|
||||
(long long)NanoToMillis(bucket.mBucketStartNs));
|
||||
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_END_BUCKET_ELAPSED_MILLIS,
|
||||
(long long)NanoToMillis(bucket.mBucketEndNs));
|
||||
} else {
|
||||
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_NUM,
|
||||
(long long)(getBucketNumFromEndTimeNs(bucket.mBucketEndNs)));
|
||||
}
|
||||
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_COUNT, (long long)bucket.mCount);
|
||||
protoOutput->end(bucketInfoToken);
|
||||
VLOG("\t bucket [%lld - %lld] count: %lld", (long long)bucket.mBucketStartNs,
|
||||
(long long)bucket.mBucketEndNs, (long long)bucket.mCount);
|
||||
}
|
||||
protoOutput->end(wrapperToken);
|
||||
}
|
||||
|
||||
protoOutput->end(protoToken);
|
||||
|
||||
if (erase_data) {
|
||||
mPastBuckets.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void CountMetricProducer::dropDataLocked(const int64_t dropTimeNs) {
|
||||
flushIfNeededLocked(dropTimeNs);
|
||||
StatsdStats::getInstance().noteBucketDropped(mMetricId);
|
||||
mPastBuckets.clear();
|
||||
}
|
||||
|
||||
void CountMetricProducer::onConditionChangedLocked(const bool conditionMet,
|
||||
const int64_t eventTime) {
|
||||
VLOG("Metric %lld onConditionChanged", (long long)mMetricId);
|
||||
mCondition = conditionMet ? ConditionState::kTrue : ConditionState::kFalse;
|
||||
}
|
||||
|
||||
bool CountMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) {
|
||||
if (mCurrentSlicedCounter->find(newKey) != mCurrentSlicedCounter->end()) {
|
||||
return false;
|
||||
}
|
||||
// ===========GuardRail==============
|
||||
// 1. Report the tuple count if the tuple count > soft limit
|
||||
if (mCurrentSlicedCounter->size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
|
||||
size_t newTupleCount = mCurrentSlicedCounter->size() + 1;
|
||||
StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mMetricId, newTupleCount);
|
||||
// 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.toString().c_str());
|
||||
StatsdStats::getInstance().noteHardDimensionLimitReached(mMetricId);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void CountMetricProducer::onMatchedLogEventInternalLocked(
|
||||
const size_t matcherIndex, const MetricDimensionKey& eventKey,
|
||||
const ConditionKey& conditionKey, bool condition, const LogEvent& event,
|
||||
const map<int, HashableDimensionKey>& statePrimaryKeys) {
|
||||
int64_t eventTimeNs = event.GetElapsedTimestampNs();
|
||||
flushIfNeededLocked(eventTimeNs);
|
||||
|
||||
if (!condition) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto it = mCurrentSlicedCounter->find(eventKey);
|
||||
if (it == mCurrentSlicedCounter->end()) {
|
||||
// ===========GuardRail==============
|
||||
if (hitGuardRailLocked(eventKey)) {
|
||||
return;
|
||||
}
|
||||
// create a counter for the new key
|
||||
(*mCurrentSlicedCounter)[eventKey] = 1;
|
||||
} else {
|
||||
// increment the existing value
|
||||
auto& count = it->second;
|
||||
count++;
|
||||
}
|
||||
for (auto& tracker : mAnomalyTrackers) {
|
||||
int64_t countWholeBucket = mCurrentSlicedCounter->find(eventKey)->second;
|
||||
auto prev = mCurrentFullCounters->find(eventKey);
|
||||
if (prev != mCurrentFullCounters->end()) {
|
||||
countWholeBucket += prev->second;
|
||||
}
|
||||
tracker->detectAndDeclareAnomaly(eventTimeNs, mCurrentBucketNum, mMetricId, eventKey,
|
||||
countWholeBucket);
|
||||
}
|
||||
|
||||
VLOG("metric %lld %s->%lld", (long long)mMetricId, eventKey.toString().c_str(),
|
||||
(long long)(*mCurrentSlicedCounter)[eventKey]);
|
||||
}
|
||||
|
||||
// When a new matched event comes in, we check if event falls into the current
|
||||
// bucket. If not, flush the old counter to past buckets and initialize the new bucket.
|
||||
void CountMetricProducer::flushIfNeededLocked(const int64_t& eventTimeNs) {
|
||||
int64_t currentBucketEndTimeNs = getCurrentBucketEndTimeNs();
|
||||
if (eventTimeNs < currentBucketEndTimeNs) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Setup the bucket start time and number.
|
||||
int64_t numBucketsForward = 1 + (eventTimeNs - currentBucketEndTimeNs) / mBucketSizeNs;
|
||||
int64_t nextBucketNs = currentBucketEndTimeNs + (numBucketsForward - 1) * mBucketSizeNs;
|
||||
flushCurrentBucketLocked(eventTimeNs, nextBucketNs);
|
||||
|
||||
mCurrentBucketNum += numBucketsForward;
|
||||
VLOG("metric %lld: new bucket start time: %lld", (long long)mMetricId,
|
||||
(long long)mCurrentBucketStartTimeNs);
|
||||
}
|
||||
|
||||
void CountMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs,
|
||||
const int64_t& nextBucketStartTimeNs) {
|
||||
int64_t fullBucketEndTimeNs = getCurrentBucketEndTimeNs();
|
||||
CountBucket info;
|
||||
info.mBucketStartNs = mCurrentBucketStartTimeNs;
|
||||
if (eventTimeNs < fullBucketEndTimeNs) {
|
||||
info.mBucketEndNs = eventTimeNs;
|
||||
} else {
|
||||
info.mBucketEndNs = fullBucketEndTimeNs;
|
||||
}
|
||||
for (const auto& counter : *mCurrentSlicedCounter) {
|
||||
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.toString().c_str(),
|
||||
(long long)counter.second);
|
||||
}
|
||||
|
||||
// If we have finished a full bucket, then send this to anomaly tracker.
|
||||
if (eventTimeNs > fullBucketEndTimeNs) {
|
||||
// Accumulate partial buckets with current value and then send to anomaly tracker.
|
||||
if (mCurrentFullCounters->size() > 0) {
|
||||
for (const auto& keyValuePair : *mCurrentSlicedCounter) {
|
||||
(*mCurrentFullCounters)[keyValuePair.first] += keyValuePair.second;
|
||||
}
|
||||
for (auto& tracker : mAnomalyTrackers) {
|
||||
tracker->addPastBucket(mCurrentFullCounters, mCurrentBucketNum);
|
||||
}
|
||||
mCurrentFullCounters = std::make_shared<DimToValMap>();
|
||||
} else {
|
||||
// Skip aggregating the partial buckets since there's no previous partial bucket.
|
||||
for (auto& tracker : mAnomalyTrackers) {
|
||||
tracker->addPastBucket(mCurrentSlicedCounter, mCurrentBucketNum);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Accumulate partial bucket.
|
||||
for (const auto& keyValuePair : *mCurrentSlicedCounter) {
|
||||
(*mCurrentFullCounters)[keyValuePair.first] += keyValuePair.second;
|
||||
}
|
||||
}
|
||||
|
||||
StatsdStats::getInstance().noteBucketCount(mMetricId);
|
||||
// Only resets the counters, but doesn't setup the times nor numbers.
|
||||
// (Do not clear since the old one is still referenced in mAnomalyTrackers).
|
||||
mCurrentSlicedCounter = std::make_shared<DimToValMap>();
|
||||
mCurrentBucketStartTimeNs = nextBucketStartTimeNs;
|
||||
}
|
||||
|
||||
// Rough estimate of CountMetricProducer buffer stored. This number will be
|
||||
// greater than actual data size as it contains each dimension of
|
||||
// CountMetricData is duplicated.
|
||||
size_t CountMetricProducer::byteSizeLocked() const {
|
||||
size_t totalSize = 0;
|
||||
for (const auto& pair : mPastBuckets) {
|
||||
totalSize += pair.second.size() * kBucketSize;
|
||||
}
|
||||
return totalSize;
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,123 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef COUNT_METRIC_PRODUCER_H
|
||||
#define COUNT_METRIC_PRODUCER_H
|
||||
|
||||
#include <android/util/ProtoOutputStream.h>
|
||||
#include <gtest/gtest_prod.h>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#include "MetricProducer.h"
|
||||
#include "anomaly/AnomalyTracker.h"
|
||||
#include "condition/ConditionTracker.h"
|
||||
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
|
||||
#include "matchers/matcher_util.h"
|
||||
#include "stats_util.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
struct CountBucket {
|
||||
int64_t mBucketStartNs;
|
||||
int64_t mBucketEndNs;
|
||||
int64_t mCount;
|
||||
};
|
||||
|
||||
class CountMetricProducer : public MetricProducer {
|
||||
public:
|
||||
CountMetricProducer(
|
||||
const ConfigKey& key, const CountMetric& countMetric, const int conditionIndex,
|
||||
const vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& wizard,
|
||||
const int64_t timeBaseNs, const int64_t startTimeNs,
|
||||
const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap = {},
|
||||
const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
|
||||
eventDeactivationMap = {},
|
||||
const vector<int>& slicedStateAtoms = {},
|
||||
const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap = {});
|
||||
|
||||
virtual ~CountMetricProducer();
|
||||
|
||||
void onStateChanged(const int64_t eventTimeNs, const int32_t atomId,
|
||||
const HashableDimensionKey& primaryKey, const FieldValue& oldState,
|
||||
const FieldValue& newState) override;
|
||||
|
||||
protected:
|
||||
void onMatchedLogEventInternalLocked(
|
||||
const size_t matcherIndex, const MetricDimensionKey& eventKey,
|
||||
const ConditionKey& conditionKey, bool condition, const LogEvent& event,
|
||||
const std::map<int, HashableDimensionKey>& statePrimaryKeys) override;
|
||||
|
||||
private:
|
||||
|
||||
void onDumpReportLocked(const int64_t dumpTimeNs,
|
||||
const bool include_current_partial_bucket,
|
||||
const bool erase_data,
|
||||
const DumpLatency dumpLatency,
|
||||
std::set<string> *str_set,
|
||||
android::util::ProtoOutputStream* protoOutput) override;
|
||||
|
||||
void clearPastBucketsLocked(const int64_t dumpTimeNs) override;
|
||||
|
||||
// Internal interface to handle condition change.
|
||||
void onConditionChangedLocked(const bool conditionMet, const int64_t eventTime) override;
|
||||
|
||||
// Internal interface to handle sliced condition change.
|
||||
void onSlicedConditionMayChangeLocked(bool overallCondition, const int64_t eventTime) override;
|
||||
|
||||
// Internal function to calculate the current used bytes.
|
||||
size_t byteSizeLocked() const override;
|
||||
|
||||
void dumpStatesLocked(FILE* out, bool verbose) const override;
|
||||
|
||||
void dropDataLocked(const int64_t dropTimeNs) override;
|
||||
|
||||
// Util function to flush the old packet.
|
||||
void flushIfNeededLocked(const int64_t& newEventTime) override;
|
||||
|
||||
void flushCurrentBucketLocked(const int64_t& eventTimeNs,
|
||||
const int64_t& nextBucketStartTimeNs) override;
|
||||
|
||||
std::unordered_map<MetricDimensionKey, std::vector<CountBucket>> mPastBuckets;
|
||||
|
||||
// The current bucket (may be a partial bucket).
|
||||
std::shared_ptr<DimToValMap> mCurrentSlicedCounter = std::make_shared<DimToValMap>();
|
||||
|
||||
// The sum of previous partial buckets in the current full bucket (excluding the current
|
||||
// partial bucket). This is only updated while flushing the current bucket.
|
||||
std::shared_ptr<DimToValMap> mCurrentFullCounters = std::make_shared<DimToValMap>();
|
||||
|
||||
static const size_t kBucketSize = sizeof(CountBucket{});
|
||||
|
||||
bool hitGuardRailLocked(const MetricDimensionKey& newKey);
|
||||
|
||||
FRIEND_TEST(CountMetricProducerTest, TestNonDimensionalEvents);
|
||||
FRIEND_TEST(CountMetricProducerTest, TestEventsWithNonSlicedCondition);
|
||||
FRIEND_TEST(CountMetricProducerTest, TestEventsWithSlicedCondition);
|
||||
FRIEND_TEST(CountMetricProducerTest, TestAnomalyDetectionUnSliced);
|
||||
FRIEND_TEST(CountMetricProducerTest, TestFirstBucket);
|
||||
FRIEND_TEST(CountMetricProducerTest, TestOneWeekTimeUnit);
|
||||
|
||||
FRIEND_TEST(CountMetricProducerTest_PartialBucket, TestSplitInCurrentBucket);
|
||||
FRIEND_TEST(CountMetricProducerTest_PartialBucket, TestSplitInNextBucket);
|
||||
};
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
#endif // COUNT_METRIC_PRODUCER_H
|
||||
@@ -1,653 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#define DEBUG false
|
||||
|
||||
#include "Log.h"
|
||||
#include "DurationMetricProducer.h"
|
||||
#include "guardrail/StatsdStats.h"
|
||||
#include "stats_util.h"
|
||||
#include "stats_log_util.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
using android::util::FIELD_COUNT_REPEATED;
|
||||
using android::util::FIELD_TYPE_BOOL;
|
||||
using android::util::FIELD_TYPE_FLOAT;
|
||||
using android::util::FIELD_TYPE_INT32;
|
||||
using android::util::FIELD_TYPE_INT64;
|
||||
using android::util::FIELD_TYPE_MESSAGE;
|
||||
using android::util::FIELD_TYPE_STRING;
|
||||
using android::util::ProtoOutputStream;
|
||||
using std::string;
|
||||
using std::unordered_map;
|
||||
using std::vector;
|
||||
using std::shared_ptr;
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
// for StatsLogReport
|
||||
const int FIELD_ID_ID = 1;
|
||||
const int FIELD_ID_DURATION_METRICS = 6;
|
||||
const int FIELD_ID_TIME_BASE = 9;
|
||||
const int FIELD_ID_BUCKET_SIZE = 10;
|
||||
const int FIELD_ID_DIMENSION_PATH_IN_WHAT = 11;
|
||||
const int FIELD_ID_IS_ACTIVE = 14;
|
||||
// for DurationMetricDataWrapper
|
||||
const int FIELD_ID_DATA = 1;
|
||||
// for DurationMetricData
|
||||
const int FIELD_ID_DIMENSION_IN_WHAT = 1;
|
||||
const int FIELD_ID_BUCKET_INFO = 3;
|
||||
const int FIELD_ID_DIMENSION_LEAF_IN_WHAT = 4;
|
||||
const int FIELD_ID_SLICE_BY_STATE = 6;
|
||||
// for DurationBucketInfo
|
||||
const int FIELD_ID_DURATION = 3;
|
||||
const int FIELD_ID_BUCKET_NUM = 4;
|
||||
const int FIELD_ID_START_BUCKET_ELAPSED_MILLIS = 5;
|
||||
const int FIELD_ID_END_BUCKET_ELAPSED_MILLIS = 6;
|
||||
|
||||
DurationMetricProducer::DurationMetricProducer(
|
||||
const ConfigKey& key, const DurationMetric& metric, const int conditionIndex,
|
||||
const vector<ConditionState>& initialConditionCache, const size_t startIndex,
|
||||
const size_t stopIndex, const size_t stopAllIndex, const bool nesting,
|
||||
const sp<ConditionWizard>& wizard, const FieldMatcher& internalDimensions,
|
||||
const int64_t timeBaseNs, const int64_t startTimeNs,
|
||||
const unordered_map<int, shared_ptr<Activation>>& eventActivationMap,
|
||||
const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap,
|
||||
const vector<int>& slicedStateAtoms,
|
||||
const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap)
|
||||
: MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, initialConditionCache, wizard,
|
||||
eventActivationMap, eventDeactivationMap, slicedStateAtoms, stateGroupMap),
|
||||
mAggregationType(metric.aggregation_type()),
|
||||
mStartIndex(startIndex),
|
||||
mStopIndex(stopIndex),
|
||||
mStopAllIndex(stopAllIndex),
|
||||
mNested(nesting),
|
||||
mContainANYPositionInInternalDimensions(false) {
|
||||
if (metric.has_bucket()) {
|
||||
mBucketSizeNs =
|
||||
TimeUnitToBucketSizeInMillisGuardrailed(key.GetUid(), metric.bucket()) * 1000000;
|
||||
} else {
|
||||
mBucketSizeNs = LLONG_MAX;
|
||||
}
|
||||
|
||||
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 (mContainANYPositionInInternalDimensions) {
|
||||
ALOGE("Position ANY in internal dimension not supported.");
|
||||
}
|
||||
if (mContainANYPositionInDimensionsInWhat) {
|
||||
ALOGE("Position ANY in dimension_in_what not supported.");
|
||||
}
|
||||
|
||||
mSliceByPositionALL = HasPositionALL(metric.dimensions_in_what());
|
||||
|
||||
if (metric.links().size() > 0) {
|
||||
for (const auto& link : metric.links()) {
|
||||
Metric2Condition mc;
|
||||
mc.conditionId = link.condition();
|
||||
translateFieldMatcher(link.fields_in_what(), &mc.metricFields);
|
||||
translateFieldMatcher(link.fields_in_condition(), &mc.conditionFields);
|
||||
mMetric2ConditionLinks.push_back(mc);
|
||||
}
|
||||
mConditionSliced = true;
|
||||
}
|
||||
mUnSlicedPartCondition = ConditionState::kUnknown;
|
||||
|
||||
for (const auto& stateLink : metric.state_link()) {
|
||||
Metric2State ms;
|
||||
ms.stateAtomId = stateLink.state_atom_id();
|
||||
translateFieldMatcher(stateLink.fields_in_what(), &ms.metricFields);
|
||||
translateFieldMatcher(stateLink.fields_in_state(), &ms.stateFields);
|
||||
mMetric2StateLinks.push_back(ms);
|
||||
}
|
||||
|
||||
mUseWhatDimensionAsInternalDimension = equalDimensions(mDimensionsInWhat, mInternalDimensions);
|
||||
if (mWizard != nullptr && mConditionTrackerIndex >= 0 &&
|
||||
mMetric2ConditionLinks.size() == 1) {
|
||||
mHasLinksToAllConditionDimensionsInTracker = mWizard->equalOutputDimensions(
|
||||
mConditionTrackerIndex, mMetric2ConditionLinks.begin()->conditionFields);
|
||||
}
|
||||
flushIfNeededLocked(startTimeNs);
|
||||
// Adjust start for partial bucket
|
||||
mCurrentBucketStartTimeNs = startTimeNs;
|
||||
VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(),
|
||||
(long long)mBucketSizeNs, (long long)mTimeBaseNs);
|
||||
}
|
||||
|
||||
DurationMetricProducer::~DurationMetricProducer() {
|
||||
VLOG("~DurationMetric() called");
|
||||
}
|
||||
|
||||
sp<AnomalyTracker> DurationMetricProducer::addAnomalyTracker(
|
||||
const Alert &alert, const sp<AlarmMonitor>& anomalyAlarmMonitor) {
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
if (mAggregationType == DurationMetric_AggregationType_SUM) {
|
||||
if (alert.trigger_if_sum_gt() > alert.num_buckets() * mBucketSizeNs) {
|
||||
ALOGW("invalid alert for SUM: threshold (%f) > possible recordable value (%d x %lld)",
|
||||
alert.trigger_if_sum_gt(), alert.num_buckets(), (long long)mBucketSizeNs);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
sp<DurationAnomalyTracker> anomalyTracker =
|
||||
new DurationAnomalyTracker(alert, mConfigKey, anomalyAlarmMonitor);
|
||||
if (anomalyTracker != nullptr) {
|
||||
mAnomalyTrackers.push_back(anomalyTracker);
|
||||
}
|
||||
return anomalyTracker;
|
||||
}
|
||||
|
||||
void DurationMetricProducer::onStateChanged(const int64_t eventTimeNs, const int32_t atomId,
|
||||
const HashableDimensionKey& primaryKey,
|
||||
const FieldValue& oldState,
|
||||
const FieldValue& newState) {
|
||||
// Check if this metric has a StateMap. If so, map the new state value to
|
||||
// the correct state group id.
|
||||
FieldValue newStateCopy = newState;
|
||||
mapStateValue(atomId, &newStateCopy);
|
||||
|
||||
flushIfNeededLocked(eventTimeNs);
|
||||
|
||||
// Each duration tracker is mapped to a different whatKey (a set of values from the
|
||||
// dimensionsInWhat fields). We notify all trackers iff the primaryKey field values from the
|
||||
// state change event are a subset of the tracker's whatKey field values.
|
||||
//
|
||||
// Ex. For a duration metric dimensioned on uid and tag:
|
||||
// DurationTracker1 whatKey = uid: 1001, tag: 1
|
||||
// DurationTracker2 whatKey = uid: 1002, tag 1
|
||||
//
|
||||
// If the state change primaryKey = uid: 1001, we only notify DurationTracker1 of a state
|
||||
// change.
|
||||
for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
|
||||
if (!containsLinkedStateValues(whatIt.first, primaryKey, mMetric2StateLinks, atomId)) {
|
||||
continue;
|
||||
}
|
||||
whatIt.second->onStateChanged(eventTimeNs, atomId, newStateCopy);
|
||||
}
|
||||
}
|
||||
|
||||
unique_ptr<DurationTracker> DurationMetricProducer::createDurationTracker(
|
||||
const MetricDimensionKey& eventKey) const {
|
||||
switch (mAggregationType) {
|
||||
case DurationMetric_AggregationType_SUM:
|
||||
return make_unique<OringDurationTracker>(
|
||||
mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, mNested,
|
||||
mCurrentBucketStartTimeNs, mCurrentBucketNum, mTimeBaseNs, mBucketSizeNs,
|
||||
mConditionSliced, mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers);
|
||||
case DurationMetric_AggregationType_MAX_SPARSE:
|
||||
return make_unique<MaxDurationTracker>(
|
||||
mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, mNested,
|
||||
mCurrentBucketStartTimeNs, mCurrentBucketNum, mTimeBaseNs, mBucketSizeNs,
|
||||
mConditionSliced, mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers);
|
||||
}
|
||||
}
|
||||
|
||||
// SlicedConditionChange optimization case 1:
|
||||
// 1. If combination condition, logical operation is AND, only one sliced child predicate.
|
||||
// 2. The links covers all dimension fields in the sliced child condition predicate.
|
||||
void DurationMetricProducer::onSlicedConditionMayChangeLocked_opt1(bool condition,
|
||||
const int64_t eventTime) {
|
||||
if (mMetric2ConditionLinks.size() != 1 ||
|
||||
!mHasLinksToAllConditionDimensionsInTracker) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool currentUnSlicedPartCondition = true;
|
||||
if (!mWizard->IsSimpleCondition(mConditionTrackerIndex)) {
|
||||
ConditionState unslicedPartState =
|
||||
mWizard->getUnSlicedPartConditionState(mConditionTrackerIndex);
|
||||
// When the unsliced part is still false, return directly.
|
||||
if (mUnSlicedPartCondition == ConditionState::kFalse &&
|
||||
unslicedPartState == ConditionState::kFalse) {
|
||||
return;
|
||||
}
|
||||
mUnSlicedPartCondition = unslicedPartState;
|
||||
currentUnSlicedPartCondition = mUnSlicedPartCondition > 0;
|
||||
}
|
||||
|
||||
auto dimensionsChangedToTrue = mWizard->getChangedToTrueDimensions(mConditionTrackerIndex);
|
||||
auto dimensionsChangedToFalse = mWizard->getChangedToFalseDimensions(mConditionTrackerIndex);
|
||||
|
||||
// The condition change is from the unsliced predicates.
|
||||
// We need to find out the true dimensions from the sliced predicate and flip their condition
|
||||
// state based on the new unsliced condition state.
|
||||
if (dimensionsChangedToTrue == nullptr || dimensionsChangedToFalse == nullptr ||
|
||||
(dimensionsChangedToTrue->empty() && dimensionsChangedToFalse->empty())) {
|
||||
std::set<HashableDimensionKey> trueConditionDimensions;
|
||||
mWizard->getTrueSlicedDimensions(mConditionTrackerIndex, &trueConditionDimensions);
|
||||
for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
|
||||
HashableDimensionKey linkedConditionDimensionKey;
|
||||
getDimensionForCondition(whatIt.first.getValues(), mMetric2ConditionLinks[0],
|
||||
&linkedConditionDimensionKey);
|
||||
if (trueConditionDimensions.find(linkedConditionDimensionKey) !=
|
||||
trueConditionDimensions.end()) {
|
||||
whatIt.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()) {
|
||||
whatIt.second->onConditionChanged(true, eventTime);
|
||||
}
|
||||
if (dimensionsChangedToFalse->find(linkedConditionDimensionKey) !=
|
||||
dimensionsChangedToFalse->end()) {
|
||||
whatIt.second->onConditionChanged(false, eventTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DurationMetricProducer::onSlicedConditionMayChangeInternalLocked(bool overallCondition,
|
||||
const int64_t eventTimeNs) {
|
||||
bool changeDimTrackable = mWizard->IsChangedDimensionTrackable(mConditionTrackerIndex);
|
||||
if (changeDimTrackable && mHasLinksToAllConditionDimensionsInTracker) {
|
||||
onSlicedConditionMayChangeLocked_opt1(overallCondition, eventTimeNs);
|
||||
return;
|
||||
}
|
||||
|
||||
// Now for each of the on-going event, check if the condition has changed for them.
|
||||
for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
|
||||
whatIt.second->onSlicedConditionMayChange(overallCondition, eventTimeNs);
|
||||
}
|
||||
}
|
||||
|
||||
void DurationMetricProducer::onSlicedConditionMayChangeLocked(bool overallCondition,
|
||||
const int64_t eventTime) {
|
||||
VLOG("Metric %lld onSlicedConditionMayChange", (long long)mMetricId);
|
||||
|
||||
if (!mIsActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
flushIfNeededLocked(eventTime);
|
||||
|
||||
if (!mConditionSliced) {
|
||||
return;
|
||||
}
|
||||
|
||||
onSlicedConditionMayChangeInternalLocked(overallCondition, eventTime);
|
||||
}
|
||||
|
||||
void DurationMetricProducer::onActiveStateChangedLocked(const int64_t& eventTimeNs) {
|
||||
MetricProducer::onActiveStateChangedLocked(eventTimeNs);
|
||||
|
||||
if (!mConditionSliced) {
|
||||
if (ConditionState::kTrue != mCondition) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mIsActive) {
|
||||
flushIfNeededLocked(eventTimeNs);
|
||||
}
|
||||
|
||||
for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
|
||||
whatIt.second->onConditionChanged(mIsActive, eventTimeNs);
|
||||
}
|
||||
} else if (mIsActive) {
|
||||
flushIfNeededLocked(eventTimeNs);
|
||||
onSlicedConditionMayChangeInternalLocked(mIsActive, eventTimeNs);
|
||||
} else { // mConditionSliced == true && !mIsActive
|
||||
for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
|
||||
whatIt.second->onConditionChanged(mIsActive, eventTimeNs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DurationMetricProducer::onConditionChangedLocked(const bool conditionMet,
|
||||
const int64_t eventTime) {
|
||||
VLOG("Metric %lld onConditionChanged", (long long)mMetricId);
|
||||
mCondition = conditionMet ? ConditionState::kTrue : ConditionState::kFalse;
|
||||
|
||||
if (!mIsActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
flushIfNeededLocked(eventTime);
|
||||
for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
|
||||
whatIt.second->onConditionChanged(conditionMet, eventTime);
|
||||
}
|
||||
}
|
||||
|
||||
void DurationMetricProducer::dropDataLocked(const int64_t dropTimeNs) {
|
||||
flushIfNeededLocked(dropTimeNs);
|
||||
StatsdStats::getInstance().noteBucketDropped(mMetricId);
|
||||
mPastBuckets.clear();
|
||||
}
|
||||
|
||||
void DurationMetricProducer::clearPastBucketsLocked(const int64_t dumpTimeNs) {
|
||||
flushIfNeededLocked(dumpTimeNs);
|
||||
mPastBuckets.clear();
|
||||
}
|
||||
|
||||
void DurationMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs,
|
||||
const bool include_current_partial_bucket,
|
||||
const bool erase_data,
|
||||
const DumpLatency dumpLatency,
|
||||
std::set<string> *str_set,
|
||||
ProtoOutputStream* protoOutput) {
|
||||
if (include_current_partial_bucket) {
|
||||
flushLocked(dumpTimeNs);
|
||||
} else {
|
||||
flushIfNeededLocked(dumpTimeNs);
|
||||
}
|
||||
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId);
|
||||
protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_IS_ACTIVE, isActiveLocked());
|
||||
|
||||
if (mPastBuckets.empty()) {
|
||||
VLOG(" Duration metric, empty return");
|
||||
return;
|
||||
}
|
||||
|
||||
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_TIME_BASE, (long long)mTimeBaseNs);
|
||||
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_SIZE, (long long)mBucketSizeNs);
|
||||
|
||||
if (!mSliceByPositionALL) {
|
||||
if (!mDimensionsInWhat.empty()) {
|
||||
uint64_t dimenPathToken = protoOutput->start(
|
||||
FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_PATH_IN_WHAT);
|
||||
writeDimensionPathToProto(mDimensionsInWhat, protoOutput);
|
||||
protoOutput->end(dimenPathToken);
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_DURATION_METRICS);
|
||||
|
||||
VLOG("Duration metric %lld dump report now...", (long long)mMetricId);
|
||||
|
||||
for (const auto& pair : mPastBuckets) {
|
||||
const MetricDimensionKey& dimensionKey = pair.first;
|
||||
VLOG(" dimension key %s", dimensionKey.toString().c_str());
|
||||
|
||||
uint64_t wrapperToken =
|
||||
protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
|
||||
|
||||
// First fill dimension.
|
||||
if (mSliceByPositionALL) {
|
||||
uint64_t dimensionToken = protoOutput->start(
|
||||
FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT);
|
||||
writeDimensionToProto(dimensionKey.getDimensionKeyInWhat(), str_set, protoOutput);
|
||||
protoOutput->end(dimensionToken);
|
||||
} else {
|
||||
writeDimensionLeafNodesToProto(dimensionKey.getDimensionKeyInWhat(),
|
||||
FIELD_ID_DIMENSION_LEAF_IN_WHAT, str_set, protoOutput);
|
||||
}
|
||||
// Then fill slice_by_state.
|
||||
for (auto state : dimensionKey.getStateValuesKey().getValues()) {
|
||||
uint64_t stateToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED |
|
||||
FIELD_ID_SLICE_BY_STATE);
|
||||
writeStateToProto(state, protoOutput);
|
||||
protoOutput->end(stateToken);
|
||||
}
|
||||
// Then fill bucket_info (DurationBucketInfo).
|
||||
for (const auto& bucket : pair.second) {
|
||||
uint64_t bucketInfoToken = protoOutput->start(
|
||||
FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_BUCKET_INFO);
|
||||
if (bucket.mBucketEndNs - bucket.mBucketStartNs != mBucketSizeNs) {
|
||||
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_BUCKET_ELAPSED_MILLIS,
|
||||
(long long)NanoToMillis(bucket.mBucketStartNs));
|
||||
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_END_BUCKET_ELAPSED_MILLIS,
|
||||
(long long)NanoToMillis(bucket.mBucketEndNs));
|
||||
} else {
|
||||
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_NUM,
|
||||
(long long)(getBucketNumFromEndTimeNs(bucket.mBucketEndNs)));
|
||||
}
|
||||
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_DURATION, (long long)bucket.mDuration);
|
||||
protoOutput->end(bucketInfoToken);
|
||||
VLOG("\t bucket [%lld - %lld] duration: %lld", (long long)bucket.mBucketStartNs,
|
||||
(long long)bucket.mBucketEndNs, (long long)bucket.mDuration);
|
||||
}
|
||||
|
||||
protoOutput->end(wrapperToken);
|
||||
}
|
||||
|
||||
protoOutput->end(protoToken);
|
||||
if (erase_data) {
|
||||
mPastBuckets.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void DurationMetricProducer::flushIfNeededLocked(const int64_t& eventTimeNs) {
|
||||
int64_t currentBucketEndTimeNs = getCurrentBucketEndTimeNs();
|
||||
|
||||
if (currentBucketEndTimeNs > eventTimeNs) {
|
||||
return;
|
||||
}
|
||||
VLOG("flushing...........");
|
||||
int numBucketsForward = 1 + (eventTimeNs - currentBucketEndTimeNs) / mBucketSizeNs;
|
||||
int64_t nextBucketNs = currentBucketEndTimeNs + (numBucketsForward - 1) * mBucketSizeNs;
|
||||
flushCurrentBucketLocked(eventTimeNs, nextBucketNs);
|
||||
|
||||
mCurrentBucketNum += numBucketsForward;
|
||||
}
|
||||
|
||||
void DurationMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs,
|
||||
const int64_t& nextBucketStartTimeNs) {
|
||||
for (auto whatIt = mCurrentSlicedDurationTrackerMap.begin();
|
||||
whatIt != mCurrentSlicedDurationTrackerMap.end();) {
|
||||
if (whatIt->second->flushCurrentBucket(eventTimeNs, &mPastBuckets)) {
|
||||
VLOG("erase bucket for key %s", whatIt->first.toString().c_str());
|
||||
whatIt = mCurrentSlicedDurationTrackerMap.erase(whatIt);
|
||||
} else {
|
||||
++whatIt;
|
||||
}
|
||||
}
|
||||
StatsdStats::getInstance().noteBucketCount(mMetricId);
|
||||
mCurrentBucketStartTimeNs = nextBucketStartTimeNs;
|
||||
}
|
||||
|
||||
void DurationMetricProducer::dumpStatesLocked(FILE* out, bool verbose) const {
|
||||
if (mCurrentSlicedDurationTrackerMap.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
fprintf(out, "DurationMetric %lld dimension size %lu\n", (long long)mMetricId,
|
||||
(unsigned long)mCurrentSlicedDurationTrackerMap.size());
|
||||
if (verbose) {
|
||||
for (const auto& whatIt : mCurrentSlicedDurationTrackerMap) {
|
||||
fprintf(out, "\t(what)%s\n", whatIt.first.toString().c_str());
|
||||
whatIt.second->dumpStates(out, verbose);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DurationMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) {
|
||||
auto whatIt = mCurrentSlicedDurationTrackerMap.find(newKey.getDimensionKeyInWhat());
|
||||
if (whatIt == mCurrentSlicedDurationTrackerMap.end()) {
|
||||
// 1. Report the tuple count if the tuple count > soft limit
|
||||
if (mCurrentSlicedDurationTrackerMap.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
|
||||
size_t newTupleCount = mCurrentSlicedDurationTrackerMap.size() + 1;
|
||||
StatsdStats::getInstance().noteMetricDimensionSize(
|
||||
mConfigKey, mMetricId, newTupleCount);
|
||||
// 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 what dimension key %s",
|
||||
(long long)mMetricId, newKey.getDimensionKeyInWhat().toString().c_str());
|
||||
StatsdStats::getInstance().noteHardDimensionLimitReached(mMetricId);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void DurationMetricProducer::handleStartEvent(const MetricDimensionKey& eventKey,
|
||||
const ConditionKey& conditionKeys,
|
||||
bool condition, const LogEvent& event) {
|
||||
const auto& whatKey = eventKey.getDimensionKeyInWhat();
|
||||
auto whatIt = mCurrentSlicedDurationTrackerMap.find(whatKey);
|
||||
if (whatIt == mCurrentSlicedDurationTrackerMap.end()) {
|
||||
if (hitGuardRailLocked(eventKey)) {
|
||||
return;
|
||||
}
|
||||
mCurrentSlicedDurationTrackerMap[whatKey] = createDurationTracker(eventKey);
|
||||
}
|
||||
|
||||
auto it = mCurrentSlicedDurationTrackerMap.find(whatKey);
|
||||
if (mUseWhatDimensionAsInternalDimension) {
|
||||
it->second->noteStart(whatKey, condition, event.GetElapsedTimestampNs(), conditionKeys);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mInternalDimensions.empty()) {
|
||||
it->second->noteStart(DEFAULT_DIMENSION_KEY, condition, event.GetElapsedTimestampNs(),
|
||||
conditionKeys);
|
||||
} else {
|
||||
HashableDimensionKey dimensionKey = DEFAULT_DIMENSION_KEY;
|
||||
filterValues(mInternalDimensions, event.getValues(), &dimensionKey);
|
||||
it->second->noteStart(dimensionKey, condition, event.GetElapsedTimestampNs(),
|
||||
conditionKeys);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void DurationMetricProducer::onMatchedLogEventInternalLocked(
|
||||
const size_t matcherIndex, const MetricDimensionKey& eventKey,
|
||||
const ConditionKey& conditionKeys, bool condition, const LogEvent& event,
|
||||
const map<int, HashableDimensionKey>& statePrimaryKeys) {
|
||||
ALOGW("Not used in duration tracker.");
|
||||
}
|
||||
|
||||
void DurationMetricProducer::onMatchedLogEventLocked(const size_t matcherIndex,
|
||||
const LogEvent& event) {
|
||||
int64_t eventTimeNs = event.GetElapsedTimestampNs();
|
||||
if (eventTimeNs < mTimeBaseNs) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mIsActive) {
|
||||
flushIfNeededLocked(event.GetElapsedTimestampNs());
|
||||
}
|
||||
|
||||
// Handles Stopall events.
|
||||
if (matcherIndex == mStopAllIndex) {
|
||||
for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
|
||||
whatIt.second->noteStopAll(event.GetElapsedTimestampNs());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
HashableDimensionKey dimensionInWhat = DEFAULT_DIMENSION_KEY;
|
||||
if (!mDimensionsInWhat.empty()) {
|
||||
filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhat);
|
||||
}
|
||||
|
||||
// Stores atom id to primary key pairs for each state atom that the metric is
|
||||
// sliced by.
|
||||
std::map<int, HashableDimensionKey> statePrimaryKeys;
|
||||
|
||||
// For states with primary fields, use MetricStateLinks to get the primary
|
||||
// field values from the log event. These values will form a primary key
|
||||
// that will be used to query StateTracker for the correct state value.
|
||||
for (const auto& stateLink : mMetric2StateLinks) {
|
||||
getDimensionForState(event.getValues(), stateLink,
|
||||
&statePrimaryKeys[stateLink.stateAtomId]);
|
||||
}
|
||||
|
||||
// For each sliced state, query StateTracker for the state value using
|
||||
// either the primary key from the previous step or the DEFAULT_DIMENSION_KEY.
|
||||
//
|
||||
// Expected functionality: for any case where the MetricStateLinks are
|
||||
// initialized incorrectly (ex. # of state links != # of primary fields, no
|
||||
// links are provided for a state with primary fields, links are provided
|
||||
// in the wrong order, etc.), StateTracker will simply return kStateUnknown
|
||||
// when queried using an incorrect key.
|
||||
HashableDimensionKey stateValuesKey = DEFAULT_DIMENSION_KEY;
|
||||
for (auto atomId : mSlicedStateAtoms) {
|
||||
FieldValue value;
|
||||
if (statePrimaryKeys.find(atomId) != statePrimaryKeys.end()) {
|
||||
// found a primary key for this state, query using the key
|
||||
queryStateValue(atomId, statePrimaryKeys[atomId], &value);
|
||||
} else {
|
||||
// if no MetricStateLinks exist for this state atom,
|
||||
// query using the default dimension key (empty HashableDimensionKey)
|
||||
queryStateValue(atomId, DEFAULT_DIMENSION_KEY, &value);
|
||||
}
|
||||
mapStateValue(atomId, &value);
|
||||
stateValuesKey.addValue(value);
|
||||
}
|
||||
|
||||
// Handles Stop events.
|
||||
if (matcherIndex == mStopIndex) {
|
||||
if (mUseWhatDimensionAsInternalDimension) {
|
||||
auto whatIt = mCurrentSlicedDurationTrackerMap.find(dimensionInWhat);
|
||||
if (whatIt != mCurrentSlicedDurationTrackerMap.end()) {
|
||||
whatIt->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()) {
|
||||
whatIt->second->noteStop(internalDimensionKey, event.GetElapsedTimestampNs(), false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
bool condition;
|
||||
ConditionKey conditionKey;
|
||||
if (mConditionSliced) {
|
||||
for (const auto& link : mMetric2ConditionLinks) {
|
||||
getDimensionForCondition(event.getValues(), link, &conditionKey[link.conditionId]);
|
||||
}
|
||||
|
||||
auto conditionState =
|
||||
mWizard->query(mConditionTrackerIndex, conditionKey,
|
||||
!mHasLinksToAllConditionDimensionsInTracker);
|
||||
condition = conditionState == ConditionState::kTrue;
|
||||
} else {
|
||||
// TODO: The unknown condition state is not handled here, we should fix it.
|
||||
condition = mCondition == ConditionState::kTrue;
|
||||
}
|
||||
|
||||
condition = condition && mIsActive;
|
||||
|
||||
handleStartEvent(MetricDimensionKey(dimensionInWhat, stateValuesKey), conditionKey, condition,
|
||||
event);
|
||||
}
|
||||
|
||||
size_t DurationMetricProducer::byteSizeLocked() const {
|
||||
size_t totalSize = 0;
|
||||
for (const auto& pair : mPastBuckets) {
|
||||
totalSize += pair.second.size() * kBucketSize;
|
||||
}
|
||||
return totalSize;
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,169 +0,0 @@
|
||||
/*
|
||||
* 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 <unordered_map>
|
||||
|
||||
#include <android/util/ProtoOutputStream.h>
|
||||
#include "../anomaly/DurationAnomalyTracker.h"
|
||||
#include "../condition/ConditionTracker.h"
|
||||
#include "../matchers/matcher_util.h"
|
||||
#include "MetricProducer.h"
|
||||
#include "duration_helper/DurationTracker.h"
|
||||
#include "duration_helper/MaxDurationTracker.h"
|
||||
#include "duration_helper/OringDurationTracker.h"
|
||||
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
|
||||
#include "stats_util.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
class DurationMetricProducer : public MetricProducer {
|
||||
public:
|
||||
DurationMetricProducer(
|
||||
const ConfigKey& key, const DurationMetric& durationMetric, const int conditionIndex,
|
||||
const vector<ConditionState>& initialConditionCache, const size_t startIndex,
|
||||
const size_t stopIndex, const size_t stopAllIndex, const bool nesting,
|
||||
const sp<ConditionWizard>& wizard, const FieldMatcher& internalDimensions,
|
||||
const int64_t timeBaseNs, const int64_t startTimeNs,
|
||||
const unordered_map<int, shared_ptr<Activation>>& eventActivationMap = {},
|
||||
const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap = {},
|
||||
const vector<int>& slicedStateAtoms = {},
|
||||
const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap = {});
|
||||
|
||||
virtual ~DurationMetricProducer();
|
||||
|
||||
sp<AnomalyTracker> addAnomalyTracker(const Alert &alert,
|
||||
const sp<AlarmMonitor>& anomalyAlarmMonitor) override;
|
||||
|
||||
void onStateChanged(const int64_t eventTimeNs, const int32_t atomId,
|
||||
const HashableDimensionKey& primaryKey, const FieldValue& oldState,
|
||||
const FieldValue& newState) override;
|
||||
|
||||
protected:
|
||||
void onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event) override;
|
||||
|
||||
void onMatchedLogEventInternalLocked(
|
||||
const size_t matcherIndex, const MetricDimensionKey& eventKey,
|
||||
const ConditionKey& conditionKeys, bool condition, const LogEvent& event,
|
||||
const std::map<int, HashableDimensionKey>& statePrimaryKeys) override;
|
||||
|
||||
private:
|
||||
void handleStartEvent(const MetricDimensionKey& eventKey, const ConditionKey& conditionKeys,
|
||||
bool condition, const LogEvent& event);
|
||||
|
||||
void onDumpReportLocked(const int64_t dumpTimeNs,
|
||||
const bool include_current_partial_bucket,
|
||||
const bool erase_data,
|
||||
const DumpLatency dumpLatency,
|
||||
std::set<string> *str_set,
|
||||
android::util::ProtoOutputStream* protoOutput) override;
|
||||
|
||||
void clearPastBucketsLocked(const int64_t dumpTimeNs) override;
|
||||
|
||||
// Internal interface to handle condition change.
|
||||
void onConditionChangedLocked(const bool conditionMet, const int64_t eventTime) override;
|
||||
|
||||
// Internal interface to handle active state change.
|
||||
void onActiveStateChangedLocked(const int64_t& eventTimeNs) override;
|
||||
|
||||
// Internal interface to handle sliced condition change.
|
||||
void onSlicedConditionMayChangeLocked(bool overallCondition, const int64_t eventTime) override;
|
||||
|
||||
void onSlicedConditionMayChangeInternalLocked(bool overallCondition,
|
||||
const int64_t eventTimeNs);
|
||||
|
||||
void onSlicedConditionMayChangeLocked_opt1(bool overallCondition, const int64_t eventTime);
|
||||
void onSlicedConditionMayChangeLocked_opt2(bool overallCondition, const int64_t eventTime);
|
||||
|
||||
// Internal function to calculate the current used bytes.
|
||||
size_t byteSizeLocked() const override;
|
||||
|
||||
void dumpStatesLocked(FILE* out, bool verbose) const override;
|
||||
|
||||
void dropDataLocked(const int64_t dropTimeNs) override;
|
||||
|
||||
// Util function to flush the old packet.
|
||||
void flushIfNeededLocked(const int64_t& eventTime);
|
||||
|
||||
void flushCurrentBucketLocked(const int64_t& eventTimeNs,
|
||||
const int64_t& nextBucketStartTimeNs) override;
|
||||
|
||||
const DurationMetric_AggregationType mAggregationType;
|
||||
|
||||
// Index of the SimpleAtomMatcher which defines the start.
|
||||
const size_t mStartIndex;
|
||||
|
||||
// Index of the SimpleAtomMatcher which defines the stop.
|
||||
const size_t mStopIndex;
|
||||
|
||||
// Index of the SimpleAtomMatcher which defines the stop all for all dimensions.
|
||||
const size_t mStopAllIndex;
|
||||
|
||||
// nest counting -- for the same key, stops must match the number of starts to make real stop
|
||||
const bool mNested;
|
||||
|
||||
// The dimension from the atom predicate. e.g., uid, wakelock name.
|
||||
vector<Matcher> mInternalDimensions;
|
||||
|
||||
bool mContainANYPositionInInternalDimensions;
|
||||
|
||||
// This boolean is true iff When mInternalDimensions == mDimensionsInWhat
|
||||
bool mUseWhatDimensionAsInternalDimension;
|
||||
|
||||
// Caches the current unsliced part condition.
|
||||
ConditionState mUnSlicedPartCondition;
|
||||
|
||||
// Save the past buckets and we can clear when the StatsLogReport is dumped.
|
||||
std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>> mPastBuckets;
|
||||
|
||||
// The duration trackers in the current bucket.
|
||||
std::unordered_map<HashableDimensionKey, std::unique_ptr<DurationTracker>>
|
||||
mCurrentSlicedDurationTrackerMap;
|
||||
|
||||
// Helper function to create a duration tracker given the metric aggregation type.
|
||||
std::unique_ptr<DurationTracker> createDurationTracker(
|
||||
const MetricDimensionKey& eventKey) const;
|
||||
|
||||
// This hides the base class's std::vector<sp<AnomalyTracker>> mAnomalyTrackers
|
||||
std::vector<sp<DurationAnomalyTracker>> mAnomalyTrackers;
|
||||
|
||||
// Util function to check whether the specified dimension hits the guardrail.
|
||||
bool hitGuardRailLocked(const MetricDimensionKey& newKey);
|
||||
|
||||
static const size_t kBucketSize = sizeof(DurationBucket{});
|
||||
|
||||
FRIEND_TEST(DurationMetricTrackerTest, TestNoCondition);
|
||||
FRIEND_TEST(DurationMetricTrackerTest, TestNonSlicedCondition);
|
||||
FRIEND_TEST(DurationMetricTrackerTest, TestNonSlicedConditionUnknownState);
|
||||
FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicates);
|
||||
FRIEND_TEST(DurationMetricTrackerTest, TestFirstBucket);
|
||||
|
||||
FRIEND_TEST(DurationMetricProducerTest_PartialBucket, TestSumDuration);
|
||||
FRIEND_TEST(DurationMetricProducerTest_PartialBucket,
|
||||
TestSumDurationWithSplitInFollowingBucket);
|
||||
FRIEND_TEST(DurationMetricProducerTest_PartialBucket, TestMaxDuration);
|
||||
FRIEND_TEST(DurationMetricProducerTest_PartialBucket, TestMaxDurationWithSplitInNextBucket);
|
||||
};
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,170 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#define DEBUG false // STOPSHIP if true
|
||||
#include "Log.h"
|
||||
|
||||
#include "EventMetricProducer.h"
|
||||
#include "stats_util.h"
|
||||
#include "stats_log_util.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
using android::util::FIELD_COUNT_REPEATED;
|
||||
using android::util::FIELD_TYPE_BOOL;
|
||||
using android::util::FIELD_TYPE_FLOAT;
|
||||
using android::util::FIELD_TYPE_INT32;
|
||||
using android::util::FIELD_TYPE_INT64;
|
||||
using android::util::FIELD_TYPE_STRING;
|
||||
using android::util::FIELD_TYPE_MESSAGE;
|
||||
using android::util::ProtoOutputStream;
|
||||
using std::map;
|
||||
using std::string;
|
||||
using std::unordered_map;
|
||||
using std::vector;
|
||||
using std::shared_ptr;
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
// for StatsLogReport
|
||||
const int FIELD_ID_ID = 1;
|
||||
const int FIELD_ID_EVENT_METRICS = 4;
|
||||
const int FIELD_ID_IS_ACTIVE = 14;
|
||||
// for EventMetricDataWrapper
|
||||
const int FIELD_ID_DATA = 1;
|
||||
// for EventMetricData
|
||||
const int FIELD_ID_ELAPSED_TIMESTAMP_NANOS = 1;
|
||||
const int FIELD_ID_ATOMS = 2;
|
||||
|
||||
EventMetricProducer::EventMetricProducer(
|
||||
const ConfigKey& key, const EventMetric& metric, const int conditionIndex,
|
||||
const vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& wizard,
|
||||
const int64_t startTimeNs,
|
||||
const unordered_map<int, shared_ptr<Activation>>& eventActivationMap,
|
||||
const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap,
|
||||
const vector<int>& slicedStateAtoms,
|
||||
const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap)
|
||||
: MetricProducer(metric.id(), key, startTimeNs, conditionIndex, initialConditionCache, wizard,
|
||||
eventActivationMap, eventDeactivationMap, slicedStateAtoms, stateGroupMap) {
|
||||
if (metric.links().size() > 0) {
|
||||
for (const auto& link : metric.links()) {
|
||||
Metric2Condition mc;
|
||||
mc.conditionId = link.condition();
|
||||
translateFieldMatcher(link.fields_in_what(), &mc.metricFields);
|
||||
translateFieldMatcher(link.fields_in_condition(), &mc.conditionFields);
|
||||
mMetric2ConditionLinks.push_back(mc);
|
||||
}
|
||||
mConditionSliced = true;
|
||||
}
|
||||
mProto = std::make_unique<ProtoOutputStream>();
|
||||
VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(),
|
||||
(long long)mBucketSizeNs, (long long)mTimeBaseNs);
|
||||
}
|
||||
|
||||
EventMetricProducer::~EventMetricProducer() {
|
||||
VLOG("~EventMetricProducer() called");
|
||||
}
|
||||
|
||||
void EventMetricProducer::dropDataLocked(const int64_t dropTimeNs) {
|
||||
mProto->clear();
|
||||
StatsdStats::getInstance().noteBucketDropped(mMetricId);
|
||||
}
|
||||
|
||||
void EventMetricProducer::onSlicedConditionMayChangeLocked(bool overallCondition,
|
||||
const int64_t eventTime) {
|
||||
}
|
||||
|
||||
std::unique_ptr<std::vector<uint8_t>> serializeProtoLocked(ProtoOutputStream& protoOutput) {
|
||||
size_t bufferSize = protoOutput.size();
|
||||
|
||||
std::unique_ptr<std::vector<uint8_t>> buffer(new std::vector<uint8_t>(bufferSize));
|
||||
|
||||
size_t pos = 0;
|
||||
sp<android::util::ProtoReader> reader = protoOutput.data();
|
||||
while (reader->readBuffer() != NULL) {
|
||||
size_t toRead = reader->currentToRead();
|
||||
std::memcpy(&((*buffer)[pos]), reader->readBuffer(), toRead);
|
||||
pos += toRead;
|
||||
reader->move(toRead);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void EventMetricProducer::clearPastBucketsLocked(const int64_t dumpTimeNs) {
|
||||
mProto->clear();
|
||||
}
|
||||
|
||||
void EventMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs,
|
||||
const bool include_current_partial_bucket,
|
||||
const bool erase_data,
|
||||
const DumpLatency dumpLatency,
|
||||
std::set<string> *str_set,
|
||||
ProtoOutputStream* protoOutput) {
|
||||
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId);
|
||||
protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_IS_ACTIVE, isActiveLocked());
|
||||
if (mProto->size() <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t bufferSize = mProto->size();
|
||||
VLOG("metric %lld dump report now... proto size: %zu ",
|
||||
(long long)mMetricId, bufferSize);
|
||||
std::unique_ptr<std::vector<uint8_t>> buffer = serializeProtoLocked(*mProto);
|
||||
|
||||
protoOutput->write(FIELD_TYPE_MESSAGE | FIELD_ID_EVENT_METRICS,
|
||||
reinterpret_cast<char*>(buffer.get()->data()), buffer.get()->size());
|
||||
|
||||
if (erase_data) {
|
||||
mProto->clear();
|
||||
}
|
||||
}
|
||||
|
||||
void EventMetricProducer::onConditionChangedLocked(const bool conditionMet,
|
||||
const int64_t eventTime) {
|
||||
VLOG("Metric %lld onConditionChanged", (long long)mMetricId);
|
||||
mCondition = conditionMet ? ConditionState::kTrue : ConditionState::kFalse;
|
||||
}
|
||||
|
||||
void EventMetricProducer::onMatchedLogEventInternalLocked(
|
||||
const size_t matcherIndex, const MetricDimensionKey& eventKey,
|
||||
const ConditionKey& conditionKey, bool condition, const LogEvent& event,
|
||||
const map<int, HashableDimensionKey>& statePrimaryKeys) {
|
||||
if (!condition) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t wrapperToken =
|
||||
mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
|
||||
const int64_t elapsedTimeNs = truncateTimestampIfNecessary(event);
|
||||
mProto->write(FIELD_TYPE_INT64 | FIELD_ID_ELAPSED_TIMESTAMP_NANOS, (long long) elapsedTimeNs);
|
||||
|
||||
uint64_t eventToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_ATOMS);
|
||||
event.ToProto(*mProto);
|
||||
mProto->end(eventToken);
|
||||
mProto->end(wrapperToken);
|
||||
}
|
||||
|
||||
size_t EventMetricProducer::byteSizeLocked() const {
|
||||
return mProto->bytesWritten();
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,83 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef EVENT_METRIC_PRODUCER_H
|
||||
#define EVENT_METRIC_PRODUCER_H
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#include <android/util/ProtoOutputStream.h>
|
||||
|
||||
#include "../condition/ConditionTracker.h"
|
||||
#include "../matchers/matcher_util.h"
|
||||
#include "MetricProducer.h"
|
||||
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
|
||||
#include "stats_util.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
class EventMetricProducer : public MetricProducer {
|
||||
public:
|
||||
EventMetricProducer(
|
||||
const ConfigKey& key, const EventMetric& eventMetric, const int conditionIndex,
|
||||
const vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& wizard,
|
||||
const int64_t startTimeNs,
|
||||
const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap = {},
|
||||
const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
|
||||
eventDeactivationMap = {},
|
||||
const vector<int>& slicedStateAtoms = {},
|
||||
const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap = {});
|
||||
|
||||
virtual ~EventMetricProducer();
|
||||
|
||||
private:
|
||||
void onMatchedLogEventInternalLocked(
|
||||
const size_t matcherIndex, const MetricDimensionKey& eventKey,
|
||||
const ConditionKey& conditionKey, bool condition, const LogEvent& event,
|
||||
const std::map<int, HashableDimensionKey>& statePrimaryKeys) override;
|
||||
|
||||
void onDumpReportLocked(const int64_t dumpTimeNs,
|
||||
const bool include_current_partial_bucket,
|
||||
const bool erase_data,
|
||||
const DumpLatency dumpLatency,
|
||||
std::set<string> *str_set,
|
||||
android::util::ProtoOutputStream* protoOutput) override;
|
||||
void clearPastBucketsLocked(const int64_t dumpTimeNs) override;
|
||||
|
||||
// Internal interface to handle condition change.
|
||||
void onConditionChangedLocked(const bool conditionMet, const int64_t eventTime) override;
|
||||
|
||||
// Internal interface to handle sliced condition change.
|
||||
void onSlicedConditionMayChangeLocked(bool overallCondition, const int64_t eventTime) override;
|
||||
|
||||
void dropDataLocked(const int64_t dropTimeNs) override;
|
||||
|
||||
// Internal function to calculate the current used bytes.
|
||||
size_t byteSizeLocked() const override;
|
||||
|
||||
void dumpStatesLocked(FILE* out, bool verbose) const override{};
|
||||
|
||||
// Maps to a EventMetricDataWrapper. Storing atom events in ProtoOutputStream
|
||||
// is more space efficient than storing LogEvent.
|
||||
std::unique_ptr<android::util::ProtoOutputStream> mProto;
|
||||
};
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
#endif // EVENT_METRIC_PRODUCER_H
|
||||
@@ -1,620 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#define DEBUG false // STOPSHIP if true
|
||||
#include "Log.h"
|
||||
|
||||
#include "../guardrail/StatsdStats.h"
|
||||
#include "GaugeMetricProducer.h"
|
||||
#include "../stats_log_util.h"
|
||||
|
||||
using android::util::FIELD_COUNT_REPEATED;
|
||||
using android::util::FIELD_TYPE_BOOL;
|
||||
using android::util::FIELD_TYPE_FLOAT;
|
||||
using android::util::FIELD_TYPE_INT32;
|
||||
using android::util::FIELD_TYPE_INT64;
|
||||
using android::util::FIELD_TYPE_MESSAGE;
|
||||
using android::util::FIELD_TYPE_STRING;
|
||||
using android::util::ProtoOutputStream;
|
||||
using std::map;
|
||||
using std::string;
|
||||
using std::unordered_map;
|
||||
using std::vector;
|
||||
using std::make_shared;
|
||||
using std::shared_ptr;
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
// for StatsLogReport
|
||||
const int FIELD_ID_ID = 1;
|
||||
const int FIELD_ID_GAUGE_METRICS = 8;
|
||||
const int FIELD_ID_TIME_BASE = 9;
|
||||
const int FIELD_ID_BUCKET_SIZE = 10;
|
||||
const int FIELD_ID_DIMENSION_PATH_IN_WHAT = 11;
|
||||
const int FIELD_ID_IS_ACTIVE = 14;
|
||||
// for GaugeMetricDataWrapper
|
||||
const int FIELD_ID_DATA = 1;
|
||||
const int FIELD_ID_SKIPPED = 2;
|
||||
// for SkippedBuckets
|
||||
const int FIELD_ID_SKIPPED_START_MILLIS = 3;
|
||||
const int FIELD_ID_SKIPPED_END_MILLIS = 4;
|
||||
const int FIELD_ID_SKIPPED_DROP_EVENT = 5;
|
||||
// for DumpEvent Proto
|
||||
const int FIELD_ID_BUCKET_DROP_REASON = 1;
|
||||
const int FIELD_ID_DROP_TIME = 2;
|
||||
// for GaugeMetricData
|
||||
const int FIELD_ID_DIMENSION_IN_WHAT = 1;
|
||||
const int FIELD_ID_BUCKET_INFO = 3;
|
||||
const int FIELD_ID_DIMENSION_LEAF_IN_WHAT = 4;
|
||||
// for GaugeBucketInfo
|
||||
const int FIELD_ID_ATOM = 3;
|
||||
const int FIELD_ID_ELAPSED_ATOM_TIMESTAMP = 4;
|
||||
const int FIELD_ID_BUCKET_NUM = 6;
|
||||
const int FIELD_ID_START_BUCKET_ELAPSED_MILLIS = 7;
|
||||
const int FIELD_ID_END_BUCKET_ELAPSED_MILLIS = 8;
|
||||
|
||||
GaugeMetricProducer::GaugeMetricProducer(
|
||||
const ConfigKey& key, const GaugeMetric& metric, const int conditionIndex,
|
||||
const vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& wizard,
|
||||
const int whatMatcherIndex, const sp<EventMatcherWizard>& matcherWizard,
|
||||
const int pullTagId, const int triggerAtomId, const int atomId, const int64_t timeBaseNs,
|
||||
const int64_t startTimeNs, const sp<StatsPullerManager>& pullerManager,
|
||||
const unordered_map<int, shared_ptr<Activation>>& eventActivationMap,
|
||||
const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap)
|
||||
: MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, initialConditionCache, wizard,
|
||||
eventActivationMap, eventDeactivationMap, /*slicedStateAtoms=*/{},
|
||||
/*stateGroupMap=*/{}),
|
||||
mWhatMatcherIndex(whatMatcherIndex),
|
||||
mEventMatcherWizard(matcherWizard),
|
||||
mPullerManager(pullerManager),
|
||||
mPullTagId(pullTagId),
|
||||
mTriggerAtomId(triggerAtomId),
|
||||
mAtomId(atomId),
|
||||
mIsPulled(pullTagId != -1),
|
||||
mMinBucketSizeNs(metric.min_bucket_size_nanos()),
|
||||
mMaxPullDelayNs(metric.max_pull_delay_sec() > 0 ? metric.max_pull_delay_sec() * NS_PER_SEC
|
||||
: StatsdStats::kPullMaxDelayNs),
|
||||
mDimensionSoftLimit(StatsdStats::kAtomDimensionKeySizeLimitMap.find(pullTagId) !=
|
||||
StatsdStats::kAtomDimensionKeySizeLimitMap.end()
|
||||
? StatsdStats::kAtomDimensionKeySizeLimitMap.at(pullTagId).first
|
||||
: StatsdStats::kDimensionKeySizeSoftLimit),
|
||||
mDimensionHardLimit(StatsdStats::kAtomDimensionKeySizeLimitMap.find(pullTagId) !=
|
||||
StatsdStats::kAtomDimensionKeySizeLimitMap.end()
|
||||
? StatsdStats::kAtomDimensionKeySizeLimitMap.at(pullTagId).second
|
||||
: StatsdStats::kDimensionKeySizeHardLimit),
|
||||
mGaugeAtomsPerDimensionLimit(metric.max_num_gauge_atoms_per_bucket()),
|
||||
mSplitBucketForAppUpgrade(metric.split_bucket_for_app_upgrade()) {
|
||||
mCurrentSlicedBucket = std::make_shared<DimToGaugeAtomsMap>();
|
||||
mCurrentSlicedBucketForAnomaly = std::make_shared<DimToValMap>();
|
||||
int64_t bucketSizeMills = 0;
|
||||
if (metric.has_bucket()) {
|
||||
bucketSizeMills = TimeUnitToBucketSizeInMillisGuardrailed(key.GetUid(), metric.bucket());
|
||||
} else {
|
||||
bucketSizeMills = TimeUnitToBucketSizeInMillis(ONE_HOUR);
|
||||
}
|
||||
mBucketSizeNs = bucketSizeMills * 1000000;
|
||||
|
||||
mSamplingType = metric.sampling_type();
|
||||
if (!metric.gauge_fields_filter().include_all()) {
|
||||
translateFieldMatcher(metric.gauge_fields_filter().fields(), &mFieldMatchers);
|
||||
}
|
||||
|
||||
if (metric.has_dimensions_in_what()) {
|
||||
translateFieldMatcher(metric.dimensions_in_what(), &mDimensionsInWhat);
|
||||
mContainANYPositionInDimensionsInWhat = HasPositionANY(metric.dimensions_in_what());
|
||||
}
|
||||
|
||||
if (metric.links().size() > 0) {
|
||||
for (const auto& link : metric.links()) {
|
||||
Metric2Condition mc;
|
||||
mc.conditionId = link.condition();
|
||||
translateFieldMatcher(link.fields_in_what(), &mc.metricFields);
|
||||
translateFieldMatcher(link.fields_in_condition(), &mc.conditionFields);
|
||||
mMetric2ConditionLinks.push_back(mc);
|
||||
}
|
||||
mConditionSliced = true;
|
||||
}
|
||||
mSliceByPositionALL = HasPositionALL(metric.dimensions_in_what());
|
||||
|
||||
flushIfNeededLocked(startTimeNs);
|
||||
// Kicks off the puller immediately.
|
||||
if (mIsPulled && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) {
|
||||
mPullerManager->RegisterReceiver(mPullTagId, mConfigKey, this, getCurrentBucketEndTimeNs(),
|
||||
mBucketSizeNs);
|
||||
}
|
||||
|
||||
// Adjust start for partial first bucket and then pull if needed
|
||||
mCurrentBucketStartTimeNs = startTimeNs;
|
||||
|
||||
VLOG("Gauge metric %lld created. bucket size %lld start_time: %lld sliced %d",
|
||||
(long long)metric.id(), (long long)mBucketSizeNs, (long long)mTimeBaseNs,
|
||||
mConditionSliced);
|
||||
}
|
||||
|
||||
GaugeMetricProducer::~GaugeMetricProducer() {
|
||||
VLOG("~GaugeMetricProducer() called");
|
||||
if (mIsPulled && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) {
|
||||
mPullerManager->UnRegisterReceiver(mPullTagId, mConfigKey, this);
|
||||
}
|
||||
}
|
||||
|
||||
void GaugeMetricProducer::dumpStatesLocked(FILE* out, bool verbose) const {
|
||||
if (mCurrentSlicedBucket == nullptr ||
|
||||
mCurrentSlicedBucket->size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
fprintf(out, "GaugeMetric %lld dimension size %lu\n", (long long)mMetricId,
|
||||
(unsigned long)mCurrentSlicedBucket->size());
|
||||
if (verbose) {
|
||||
for (const auto& it : *mCurrentSlicedBucket) {
|
||||
fprintf(out, "\t(what)%s\t(states)%s %d atoms\n",
|
||||
it.first.getDimensionKeyInWhat().toString().c_str(),
|
||||
it.first.getStateValuesKey().toString().c_str(), (int)it.second.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GaugeMetricProducer::clearPastBucketsLocked(const int64_t dumpTimeNs) {
|
||||
flushIfNeededLocked(dumpTimeNs);
|
||||
mPastBuckets.clear();
|
||||
mSkippedBuckets.clear();
|
||||
}
|
||||
|
||||
void GaugeMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs,
|
||||
const bool include_current_partial_bucket,
|
||||
const bool erase_data,
|
||||
const DumpLatency dumpLatency,
|
||||
std::set<string> *str_set,
|
||||
ProtoOutputStream* protoOutput) {
|
||||
VLOG("Gauge metric %lld report now...", (long long)mMetricId);
|
||||
if (include_current_partial_bucket) {
|
||||
flushLocked(dumpTimeNs);
|
||||
} else {
|
||||
flushIfNeededLocked(dumpTimeNs);
|
||||
}
|
||||
|
||||
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId);
|
||||
protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_IS_ACTIVE, isActiveLocked());
|
||||
|
||||
if (mPastBuckets.empty() && mSkippedBuckets.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_TIME_BASE, (long long)mTimeBaseNs);
|
||||
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_SIZE, (long long)mBucketSizeNs);
|
||||
|
||||
// Fills the dimension path if not slicing by ALL.
|
||||
if (!mSliceByPositionALL) {
|
||||
if (!mDimensionsInWhat.empty()) {
|
||||
uint64_t dimenPathToken = protoOutput->start(
|
||||
FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_PATH_IN_WHAT);
|
||||
writeDimensionPathToProto(mDimensionsInWhat, protoOutput);
|
||||
protoOutput->end(dimenPathToken);
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_GAUGE_METRICS);
|
||||
|
||||
for (const auto& skippedBucket : mSkippedBuckets) {
|
||||
uint64_t wrapperToken =
|
||||
protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_SKIPPED);
|
||||
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_SKIPPED_START_MILLIS,
|
||||
(long long)(NanoToMillis(skippedBucket.bucketStartTimeNs)));
|
||||
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_SKIPPED_END_MILLIS,
|
||||
(long long)(NanoToMillis(skippedBucket.bucketEndTimeNs)));
|
||||
|
||||
for (const auto& dropEvent : skippedBucket.dropEvents) {
|
||||
uint64_t dropEventToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED |
|
||||
FIELD_ID_SKIPPED_DROP_EVENT);
|
||||
protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_BUCKET_DROP_REASON, dropEvent.reason);
|
||||
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_DROP_TIME, (long long) (NanoToMillis(dropEvent.dropTimeNs)));
|
||||
protoOutput->end(dropEventToken);
|
||||
}
|
||||
protoOutput->end(wrapperToken);
|
||||
}
|
||||
|
||||
for (const auto& pair : mPastBuckets) {
|
||||
const MetricDimensionKey& dimensionKey = pair.first;
|
||||
|
||||
VLOG("Gauge dimension key %s", dimensionKey.toString().c_str());
|
||||
uint64_t wrapperToken =
|
||||
protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
|
||||
|
||||
// First fill dimension.
|
||||
if (mSliceByPositionALL) {
|
||||
uint64_t dimensionToken = protoOutput->start(
|
||||
FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT);
|
||||
writeDimensionToProto(dimensionKey.getDimensionKeyInWhat(), str_set, protoOutput);
|
||||
protoOutput->end(dimensionToken);
|
||||
} else {
|
||||
writeDimensionLeafNodesToProto(dimensionKey.getDimensionKeyInWhat(),
|
||||
FIELD_ID_DIMENSION_LEAF_IN_WHAT, str_set, protoOutput);
|
||||
}
|
||||
|
||||
// Then fill bucket_info (GaugeBucketInfo).
|
||||
for (const auto& bucket : pair.second) {
|
||||
uint64_t bucketInfoToken = protoOutput->start(
|
||||
FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_BUCKET_INFO);
|
||||
|
||||
if (bucket.mBucketEndNs - bucket.mBucketStartNs != mBucketSizeNs) {
|
||||
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_START_BUCKET_ELAPSED_MILLIS,
|
||||
(long long)NanoToMillis(bucket.mBucketStartNs));
|
||||
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_END_BUCKET_ELAPSED_MILLIS,
|
||||
(long long)NanoToMillis(bucket.mBucketEndNs));
|
||||
} else {
|
||||
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_NUM,
|
||||
(long long)(getBucketNumFromEndTimeNs(bucket.mBucketEndNs)));
|
||||
}
|
||||
|
||||
if (!bucket.mGaugeAtoms.empty()) {
|
||||
for (const auto& atom : bucket.mGaugeAtoms) {
|
||||
uint64_t atomsToken =
|
||||
protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED |
|
||||
FIELD_ID_ATOM);
|
||||
writeFieldValueTreeToStream(mAtomId, *(atom.mFields), protoOutput);
|
||||
protoOutput->end(atomsToken);
|
||||
}
|
||||
for (const auto& atom : bucket.mGaugeAtoms) {
|
||||
protoOutput->write(FIELD_TYPE_INT64 | FIELD_COUNT_REPEATED |
|
||||
FIELD_ID_ELAPSED_ATOM_TIMESTAMP,
|
||||
(long long)atom.mElapsedTimestampNs);
|
||||
}
|
||||
}
|
||||
protoOutput->end(bucketInfoToken);
|
||||
VLOG("Gauge \t bucket [%lld - %lld] includes %d atoms.",
|
||||
(long long)bucket.mBucketStartNs, (long long)bucket.mBucketEndNs,
|
||||
(int)bucket.mGaugeAtoms.size());
|
||||
}
|
||||
protoOutput->end(wrapperToken);
|
||||
}
|
||||
protoOutput->end(protoToken);
|
||||
|
||||
|
||||
if (erase_data) {
|
||||
mPastBuckets.clear();
|
||||
mSkippedBuckets.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void GaugeMetricProducer::prepareFirstBucketLocked() {
|
||||
if (mIsActive && mIsPulled && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) {
|
||||
pullAndMatchEventsLocked(mCurrentBucketStartTimeNs);
|
||||
}
|
||||
}
|
||||
|
||||
void GaugeMetricProducer::pullAndMatchEventsLocked(const int64_t timestampNs) {
|
||||
bool triggerPuller = false;
|
||||
switch(mSamplingType) {
|
||||
// When the metric wants to do random sampling and there is already one gauge atom for the
|
||||
// current bucket, do not do it again.
|
||||
case GaugeMetric::RANDOM_ONE_SAMPLE: {
|
||||
triggerPuller = mCondition == ConditionState::kTrue && mCurrentSlicedBucket->empty();
|
||||
break;
|
||||
}
|
||||
case GaugeMetric::CONDITION_CHANGE_TO_TRUE: {
|
||||
triggerPuller = mCondition == ConditionState::kTrue;
|
||||
break;
|
||||
}
|
||||
case GaugeMetric::FIRST_N_SAMPLES: {
|
||||
triggerPuller = mCondition == ConditionState::kTrue;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (!triggerPuller) {
|
||||
return;
|
||||
}
|
||||
vector<std::shared_ptr<LogEvent>> allData;
|
||||
if (!mPullerManager->Pull(mPullTagId, mConfigKey, timestampNs, &allData)) {
|
||||
ALOGE("Gauge Stats puller failed for tag: %d at %lld", mPullTagId, (long long)timestampNs);
|
||||
return;
|
||||
}
|
||||
const int64_t pullDelayNs = getElapsedRealtimeNs() - timestampNs;
|
||||
StatsdStats::getInstance().notePullDelay(mPullTagId, pullDelayNs);
|
||||
if (pullDelayNs > mMaxPullDelayNs) {
|
||||
ALOGE("Pull finish too late for atom %d", mPullTagId);
|
||||
StatsdStats::getInstance().notePullExceedMaxDelay(mPullTagId);
|
||||
return;
|
||||
}
|
||||
for (const auto& data : allData) {
|
||||
LogEvent localCopy = data->makeCopy();
|
||||
localCopy.setElapsedTimestampNs(timestampNs);
|
||||
if (mEventMatcherWizard->matchLogEvent(localCopy, mWhatMatcherIndex) ==
|
||||
MatchingState::kMatched) {
|
||||
onMatchedLogEventLocked(mWhatMatcherIndex, localCopy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GaugeMetricProducer::onActiveStateChangedLocked(const int64_t& eventTimeNs) {
|
||||
MetricProducer::onActiveStateChangedLocked(eventTimeNs);
|
||||
if (ConditionState::kTrue != mCondition || !mIsPulled) {
|
||||
return;
|
||||
}
|
||||
if (mTriggerAtomId == -1 || (mIsActive && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE)) {
|
||||
pullAndMatchEventsLocked(eventTimeNs);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void GaugeMetricProducer::onConditionChangedLocked(const bool conditionMet,
|
||||
const int64_t eventTimeNs) {
|
||||
VLOG("GaugeMetric %lld onConditionChanged", (long long)mMetricId);
|
||||
|
||||
mCondition = conditionMet ? ConditionState::kTrue : ConditionState::kFalse;
|
||||
if (!mIsActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
flushIfNeededLocked(eventTimeNs);
|
||||
if (mIsPulled && mTriggerAtomId == -1) {
|
||||
pullAndMatchEventsLocked(eventTimeNs);
|
||||
} // else: Push mode. No need to proactively pull the gauge data.
|
||||
}
|
||||
|
||||
void GaugeMetricProducer::onSlicedConditionMayChangeLocked(bool overallCondition,
|
||||
const int64_t eventTimeNs) {
|
||||
VLOG("GaugeMetric %lld onSlicedConditionMayChange overall condition %d", (long long)mMetricId,
|
||||
overallCondition);
|
||||
mCondition = overallCondition ? ConditionState::kTrue : ConditionState::kFalse;
|
||||
if (!mIsActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
flushIfNeededLocked(eventTimeNs);
|
||||
// If the condition is sliced, mCondition is true if any of the dimensions is true. And we will
|
||||
// pull for every dimension.
|
||||
if (mIsPulled && mTriggerAtomId == -1) {
|
||||
pullAndMatchEventsLocked(eventTimeNs);
|
||||
} // else: Push mode. No need to proactively pull the gauge data.
|
||||
}
|
||||
|
||||
std::shared_ptr<vector<FieldValue>> GaugeMetricProducer::getGaugeFields(const LogEvent& event) {
|
||||
std::shared_ptr<vector<FieldValue>> gaugeFields;
|
||||
if (mFieldMatchers.size() > 0) {
|
||||
gaugeFields = std::make_shared<vector<FieldValue>>();
|
||||
filterGaugeValues(mFieldMatchers, event.getValues(), gaugeFields.get());
|
||||
} else {
|
||||
gaugeFields = std::make_shared<vector<FieldValue>>(event.getValues());
|
||||
}
|
||||
// Trim all dimension fields from output. Dimensions will appear in output report and will
|
||||
// benefit from dictionary encoding. For large pulled atoms, this can give the benefit of
|
||||
// optional repeated field.
|
||||
for (const auto& field : mDimensionsInWhat) {
|
||||
for (auto it = gaugeFields->begin(); it != gaugeFields->end();) {
|
||||
if (it->mField.matches(field)) {
|
||||
it = gaugeFields->erase(it);
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return gaugeFields;
|
||||
}
|
||||
|
||||
void GaugeMetricProducer::onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& allData,
|
||||
bool pullSuccess, int64_t originalPullTimeNs) {
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
if (!pullSuccess || allData.size() == 0) {
|
||||
return;
|
||||
}
|
||||
const int64_t pullDelayNs = getElapsedRealtimeNs() - originalPullTimeNs;
|
||||
StatsdStats::getInstance().notePullDelay(mPullTagId, pullDelayNs);
|
||||
if (pullDelayNs > mMaxPullDelayNs) {
|
||||
ALOGE("Pull finish too late for atom %d", mPullTagId);
|
||||
StatsdStats::getInstance().notePullExceedMaxDelay(mPullTagId);
|
||||
return;
|
||||
}
|
||||
for (const auto& data : allData) {
|
||||
if (mEventMatcherWizard->matchLogEvent(
|
||||
*data, mWhatMatcherIndex) == MatchingState::kMatched) {
|
||||
onMatchedLogEventLocked(mWhatMatcherIndex, *data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool GaugeMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) {
|
||||
if (mCurrentSlicedBucket->find(newKey) != mCurrentSlicedBucket->end()) {
|
||||
return false;
|
||||
}
|
||||
// 1. Report the tuple count if the tuple count > soft limit
|
||||
if (mCurrentSlicedBucket->size() > mDimensionSoftLimit - 1) {
|
||||
size_t newTupleCount = mCurrentSlicedBucket->size() + 1;
|
||||
StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mMetricId, newTupleCount);
|
||||
// 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
|
||||
if (newTupleCount > mDimensionHardLimit) {
|
||||
ALOGE("GaugeMetric %lld dropping data for dimension key %s",
|
||||
(long long)mMetricId, newKey.toString().c_str());
|
||||
StatsdStats::getInstance().noteHardDimensionLimitReached(mMetricId);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void GaugeMetricProducer::onMatchedLogEventInternalLocked(
|
||||
const size_t matcherIndex, const MetricDimensionKey& eventKey,
|
||||
const ConditionKey& conditionKey, bool condition, const LogEvent& event,
|
||||
const map<int, HashableDimensionKey>& statePrimaryKeys) {
|
||||
if (condition == false) {
|
||||
return;
|
||||
}
|
||||
int64_t eventTimeNs = event.GetElapsedTimestampNs();
|
||||
if (eventTimeNs < mCurrentBucketStartTimeNs) {
|
||||
VLOG("Gauge Skip event due to late arrival: %lld vs %lld", (long long)eventTimeNs,
|
||||
(long long)mCurrentBucketStartTimeNs);
|
||||
return;
|
||||
}
|
||||
flushIfNeededLocked(eventTimeNs);
|
||||
|
||||
if (mTriggerAtomId == event.GetTagId()) {
|
||||
pullAndMatchEventsLocked(eventTimeNs);
|
||||
return;
|
||||
}
|
||||
|
||||
// When gauge metric wants to randomly sample the output atom, we just simply use the first
|
||||
// gauge in the given bucket.
|
||||
if (mCurrentSlicedBucket->find(eventKey) != mCurrentSlicedBucket->end() &&
|
||||
mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) {
|
||||
return;
|
||||
}
|
||||
if (hitGuardRailLocked(eventKey)) {
|
||||
return;
|
||||
}
|
||||
if ((*mCurrentSlicedBucket)[eventKey].size() >= mGaugeAtomsPerDimensionLimit) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int64_t truncatedElapsedTimestampNs = truncateTimestampIfNecessary(event);
|
||||
GaugeAtom gaugeAtom(getGaugeFields(event), truncatedElapsedTimestampNs);
|
||||
(*mCurrentSlicedBucket)[eventKey].push_back(gaugeAtom);
|
||||
// Anomaly detection on gauge metric only works when there is one numeric
|
||||
// field specified.
|
||||
if (mAnomalyTrackers.size() > 0) {
|
||||
if (gaugeAtom.mFields->size() == 1) {
|
||||
const Value& value = gaugeAtom.mFields->begin()->mValue;
|
||||
long gaugeVal = 0;
|
||||
if (value.getType() == INT) {
|
||||
gaugeVal = (long)value.int_value;
|
||||
} else if (value.getType() == LONG) {
|
||||
gaugeVal = value.long_value;
|
||||
}
|
||||
for (auto& tracker : mAnomalyTrackers) {
|
||||
tracker->detectAndDeclareAnomaly(eventTimeNs, mCurrentBucketNum, mMetricId,
|
||||
eventKey, gaugeVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GaugeMetricProducer::updateCurrentSlicedBucketForAnomaly() {
|
||||
for (const auto& slice : *mCurrentSlicedBucket) {
|
||||
if (slice.second.empty()) {
|
||||
continue;
|
||||
}
|
||||
const Value& value = slice.second.front().mFields->front().mValue;
|
||||
long gaugeVal = 0;
|
||||
if (value.getType() == INT) {
|
||||
gaugeVal = (long)value.int_value;
|
||||
} else if (value.getType() == LONG) {
|
||||
gaugeVal = value.long_value;
|
||||
}
|
||||
(*mCurrentSlicedBucketForAnomaly)[slice.first] = gaugeVal;
|
||||
}
|
||||
}
|
||||
|
||||
void GaugeMetricProducer::dropDataLocked(const int64_t dropTimeNs) {
|
||||
flushIfNeededLocked(dropTimeNs);
|
||||
StatsdStats::getInstance().noteBucketDropped(mMetricId);
|
||||
mPastBuckets.clear();
|
||||
}
|
||||
|
||||
// When a new matched event comes in, we check if event falls into the current
|
||||
// bucket. If not, flush the old counter to past buckets and initialize the new
|
||||
// bucket.
|
||||
// if data is pushed, onMatchedLogEvent will only be called through onConditionChanged() inside
|
||||
// the GaugeMetricProducer while holding the lock.
|
||||
void GaugeMetricProducer::flushIfNeededLocked(const int64_t& eventTimeNs) {
|
||||
int64_t currentBucketEndTimeNs = getCurrentBucketEndTimeNs();
|
||||
|
||||
if (eventTimeNs < currentBucketEndTimeNs) {
|
||||
VLOG("Gauge eventTime is %lld, less than next bucket start time %lld",
|
||||
(long long)eventTimeNs, (long long)(mCurrentBucketStartTimeNs + mBucketSizeNs));
|
||||
return;
|
||||
}
|
||||
|
||||
// Adjusts the bucket start and end times.
|
||||
int64_t numBucketsForward = 1 + (eventTimeNs - currentBucketEndTimeNs) / mBucketSizeNs;
|
||||
int64_t nextBucketNs = currentBucketEndTimeNs + (numBucketsForward - 1) * mBucketSizeNs;
|
||||
flushCurrentBucketLocked(eventTimeNs, nextBucketNs);
|
||||
|
||||
mCurrentBucketNum += numBucketsForward;
|
||||
VLOG("Gauge metric %lld: new bucket start time: %lld", (long long)mMetricId,
|
||||
(long long)mCurrentBucketStartTimeNs);
|
||||
}
|
||||
|
||||
void GaugeMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs,
|
||||
const int64_t& nextBucketStartTimeNs) {
|
||||
int64_t fullBucketEndTimeNs = getCurrentBucketEndTimeNs();
|
||||
int64_t bucketEndTime = eventTimeNs < fullBucketEndTimeNs ? eventTimeNs : fullBucketEndTimeNs;
|
||||
|
||||
GaugeBucket info;
|
||||
info.mBucketStartNs = mCurrentBucketStartTimeNs;
|
||||
info.mBucketEndNs = bucketEndTime;
|
||||
|
||||
// Add bucket to mPastBuckets if bucket is large enough.
|
||||
// Otherwise, drop the bucket data and add bucket metadata to mSkippedBuckets.
|
||||
bool isBucketLargeEnough = info.mBucketEndNs - mCurrentBucketStartTimeNs >= mMinBucketSizeNs;
|
||||
if (isBucketLargeEnough) {
|
||||
for (const auto& slice : *mCurrentSlicedBucket) {
|
||||
info.mGaugeAtoms = slice.second;
|
||||
auto& bucketList = mPastBuckets[slice.first];
|
||||
bucketList.push_back(info);
|
||||
VLOG("Gauge gauge metric %lld, dump key value: %s", (long long)mMetricId,
|
||||
slice.first.toString().c_str());
|
||||
}
|
||||
} else {
|
||||
mCurrentSkippedBucket.bucketStartTimeNs = mCurrentBucketStartTimeNs;
|
||||
mCurrentSkippedBucket.bucketEndTimeNs = bucketEndTime;
|
||||
if (!maxDropEventsReached()) {
|
||||
mCurrentSkippedBucket.dropEvents.emplace_back(
|
||||
buildDropEvent(eventTimeNs, BucketDropReason::BUCKET_TOO_SMALL));
|
||||
}
|
||||
mSkippedBuckets.emplace_back(mCurrentSkippedBucket);
|
||||
}
|
||||
|
||||
// If we have anomaly trackers, we need to update the partial bucket values.
|
||||
if (mAnomalyTrackers.size() > 0) {
|
||||
updateCurrentSlicedBucketForAnomaly();
|
||||
|
||||
if (eventTimeNs > fullBucketEndTimeNs) {
|
||||
// This is known to be a full bucket, so send this data to the anomaly tracker.
|
||||
for (auto& tracker : mAnomalyTrackers) {
|
||||
tracker->addPastBucket(mCurrentSlicedBucketForAnomaly, mCurrentBucketNum);
|
||||
}
|
||||
mCurrentSlicedBucketForAnomaly = std::make_shared<DimToValMap>();
|
||||
}
|
||||
}
|
||||
|
||||
StatsdStats::getInstance().noteBucketCount(mMetricId);
|
||||
mCurrentSlicedBucket = std::make_shared<DimToGaugeAtomsMap>();
|
||||
mCurrentBucketStartTimeNs = nextBucketStartTimeNs;
|
||||
mCurrentSkippedBucket.reset();
|
||||
}
|
||||
|
||||
size_t GaugeMetricProducer::byteSizeLocked() const {
|
||||
size_t totalSize = 0;
|
||||
for (const auto& pair : mPastBuckets) {
|
||||
for (const auto& bucket : pair.second) {
|
||||
totalSize += bucket.mGaugeAtoms.size() * sizeof(GaugeAtom);
|
||||
for (const auto& atom : bucket.mGaugeAtoms) {
|
||||
if (atom.mFields != nullptr) {
|
||||
totalSize += atom.mFields->size() * sizeof(FieldValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return totalSize;
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,211 +0,0 @@
|
||||
/*
|
||||
* 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 <unordered_map>
|
||||
|
||||
#include <android/util/ProtoOutputStream.h>
|
||||
#include <gtest/gtest_prod.h>
|
||||
#include "../condition/ConditionTracker.h"
|
||||
#include "../external/PullDataReceiver.h"
|
||||
#include "../external/StatsPullerManager.h"
|
||||
#include "../matchers/matcher_util.h"
|
||||
#include "../matchers/EventMatcherWizard.h"
|
||||
#include "MetricProducer.h"
|
||||
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
|
||||
#include "../stats_util.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
struct GaugeAtom {
|
||||
GaugeAtom(std::shared_ptr<vector<FieldValue>> fields, int64_t elapsedTimeNs)
|
||||
: mFields(fields), mElapsedTimestampNs(elapsedTimeNs) {
|
||||
}
|
||||
std::shared_ptr<vector<FieldValue>> mFields;
|
||||
int64_t mElapsedTimestampNs;
|
||||
};
|
||||
|
||||
struct GaugeBucket {
|
||||
int64_t mBucketStartNs;
|
||||
int64_t mBucketEndNs;
|
||||
std::vector<GaugeAtom> mGaugeAtoms;
|
||||
};
|
||||
|
||||
typedef std::unordered_map<MetricDimensionKey, std::vector<GaugeAtom>>
|
||||
DimToGaugeAtomsMap;
|
||||
|
||||
// This gauge metric producer first register the puller to automatically pull the gauge at the
|
||||
// beginning of each bucket. If the condition is met, insert it to the bucket info. Otherwise
|
||||
// proactively pull the gauge when the condition is changed to be true. Therefore, the gauge metric
|
||||
// producer always reports the guage at the earliest time of the bucket when the condition is met.
|
||||
class GaugeMetricProducer : public virtual MetricProducer, public virtual PullDataReceiver {
|
||||
public:
|
||||
GaugeMetricProducer(
|
||||
const ConfigKey& key, const GaugeMetric& gaugeMetric, const int conditionIndex,
|
||||
const vector<ConditionState>& initialConditionCache,
|
||||
const sp<ConditionWizard>& conditionWizard, const int whatMatcherIndex,
|
||||
const sp<EventMatcherWizard>& matcherWizard, const int pullTagId,
|
||||
const int triggerAtomId, const int atomId, const int64_t timeBaseNs,
|
||||
const int64_t startTimeNs, const sp<StatsPullerManager>& pullerManager,
|
||||
const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap = {},
|
||||
const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
|
||||
eventDeactivationMap = {});
|
||||
|
||||
virtual ~GaugeMetricProducer();
|
||||
|
||||
// Handles when the pulled data arrives.
|
||||
void onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& data,
|
||||
bool pullSuccess, int64_t originalPullTimeNs) override;
|
||||
|
||||
// GaugeMetric needs to immediately trigger another pull when we create the partial bucket.
|
||||
void notifyAppUpgrade(const int64_t& eventTimeNs) override {
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
|
||||
if (!mSplitBucketForAppUpgrade) {
|
||||
return;
|
||||
}
|
||||
flushLocked(eventTimeNs);
|
||||
if (mIsPulled && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) {
|
||||
pullAndMatchEventsLocked(eventTimeNs);
|
||||
}
|
||||
};
|
||||
|
||||
// GaugeMetric needs to immediately trigger another pull when we create the partial bucket.
|
||||
void onStatsdInitCompleted(const int64_t& eventTimeNs) override {
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
|
||||
flushLocked(eventTimeNs);
|
||||
if (mIsPulled && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) {
|
||||
pullAndMatchEventsLocked(eventTimeNs);
|
||||
}
|
||||
};
|
||||
|
||||
protected:
|
||||
void onMatchedLogEventInternalLocked(
|
||||
const size_t matcherIndex, const MetricDimensionKey& eventKey,
|
||||
const ConditionKey& conditionKey, bool condition, const LogEvent& event,
|
||||
const std::map<int, HashableDimensionKey>& statePrimaryKeys) override;
|
||||
|
||||
private:
|
||||
void onDumpReportLocked(const int64_t dumpTimeNs,
|
||||
const bool include_current_partial_bucket,
|
||||
const bool erase_data,
|
||||
const DumpLatency dumpLatency,
|
||||
std::set<string> *str_set,
|
||||
android::util::ProtoOutputStream* protoOutput) override;
|
||||
void clearPastBucketsLocked(const int64_t dumpTimeNs) override;
|
||||
|
||||
// Internal interface to handle condition change.
|
||||
void onConditionChangedLocked(const bool conditionMet, const int64_t eventTime) override;
|
||||
|
||||
// Internal interface to handle active state change.
|
||||
void onActiveStateChangedLocked(const int64_t& eventTimeNs) override;
|
||||
|
||||
// Internal interface to handle sliced condition change.
|
||||
void onSlicedConditionMayChangeLocked(bool overallCondition, const int64_t eventTime) override;
|
||||
|
||||
// Internal function to calculate the current used bytes.
|
||||
size_t byteSizeLocked() const override;
|
||||
|
||||
void dumpStatesLocked(FILE* out, bool verbose) const override;
|
||||
|
||||
void dropDataLocked(const int64_t dropTimeNs) override;
|
||||
|
||||
// Util function to flush the old packet.
|
||||
void flushIfNeededLocked(const int64_t& eventTime) override;
|
||||
|
||||
void flushCurrentBucketLocked(const int64_t& eventTimeNs,
|
||||
const int64_t& nextBucketStartTimeNs) override;
|
||||
|
||||
void prepareFirstBucketLocked() override;
|
||||
|
||||
void pullAndMatchEventsLocked(const int64_t timestampNs);
|
||||
|
||||
const int mWhatMatcherIndex;
|
||||
|
||||
sp<EventMatcherWizard> mEventMatcherWizard;
|
||||
|
||||
sp<StatsPullerManager> mPullerManager;
|
||||
// tagId for pulled data. -1 if this is not pulled
|
||||
const int mPullTagId;
|
||||
|
||||
// tagId for atoms that trigger the pulling, if any
|
||||
const int mTriggerAtomId;
|
||||
|
||||
// tagId for output atom
|
||||
const int mAtomId;
|
||||
|
||||
// if this is pulled metric
|
||||
const bool mIsPulled;
|
||||
|
||||
// Save the past buckets and we can clear when the StatsLogReport is dumped.
|
||||
std::unordered_map<MetricDimensionKey, std::vector<GaugeBucket>> mPastBuckets;
|
||||
|
||||
// The current partial bucket.
|
||||
std::shared_ptr<DimToGaugeAtomsMap> mCurrentSlicedBucket;
|
||||
|
||||
// The current full bucket for anomaly detection. This is updated to the latest value seen for
|
||||
// this slice (ie, for partial buckets, we use the last partial bucket in this full bucket).
|
||||
std::shared_ptr<DimToValMap> mCurrentSlicedBucketForAnomaly;
|
||||
|
||||
const int64_t mMinBucketSizeNs;
|
||||
|
||||
// Translate Atom based bucket to single numeric value bucket for anomaly and updates the map
|
||||
// for each slice with the latest value.
|
||||
void updateCurrentSlicedBucketForAnomaly();
|
||||
|
||||
// Whitelist of fields to report. Empty means all are reported.
|
||||
std::vector<Matcher> mFieldMatchers;
|
||||
|
||||
GaugeMetric::SamplingType mSamplingType;
|
||||
|
||||
const int64_t mMaxPullDelayNs;
|
||||
|
||||
// apply a whitelist on the original input
|
||||
std::shared_ptr<vector<FieldValue>> getGaugeFields(const LogEvent& event);
|
||||
|
||||
// Util function to check whether the specified dimension hits the guardrail.
|
||||
bool hitGuardRailLocked(const MetricDimensionKey& newKey);
|
||||
|
||||
static const size_t kBucketSize = sizeof(GaugeBucket{});
|
||||
|
||||
const size_t mDimensionSoftLimit;
|
||||
|
||||
const size_t mDimensionHardLimit;
|
||||
|
||||
const size_t mGaugeAtomsPerDimensionLimit;
|
||||
|
||||
const bool mSplitBucketForAppUpgrade;
|
||||
|
||||
FRIEND_TEST(GaugeMetricProducerTest, TestPulledEventsWithCondition);
|
||||
FRIEND_TEST(GaugeMetricProducerTest, TestPulledEventsWithSlicedCondition);
|
||||
FRIEND_TEST(GaugeMetricProducerTest, TestPulledEventsNoCondition);
|
||||
FRIEND_TEST(GaugeMetricProducerTest, TestPulledWithAppUpgradeDisabled);
|
||||
FRIEND_TEST(GaugeMetricProducerTest, TestPulledEventsAnomalyDetection);
|
||||
FRIEND_TEST(GaugeMetricProducerTest, TestFirstBucket);
|
||||
FRIEND_TEST(GaugeMetricProducerTest, TestPullOnTrigger);
|
||||
FRIEND_TEST(GaugeMetricProducerTest, TestRemoveDimensionInOutput);
|
||||
|
||||
FRIEND_TEST(GaugeMetricProducerTest_PartialBucket, TestPushedEvents);
|
||||
FRIEND_TEST(GaugeMetricProducerTest_PartialBucket, TestPulled);
|
||||
};
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,321 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#define DEBUG false // STOPSHIP if true
|
||||
#include "Log.h"
|
||||
|
||||
#include "MetricProducer.h"
|
||||
|
||||
#include "../guardrail/StatsdStats.h"
|
||||
#include "state/StateTracker.h"
|
||||
|
||||
using android::util::FIELD_COUNT_REPEATED;
|
||||
using android::util::FIELD_TYPE_ENUM;
|
||||
using android::util::FIELD_TYPE_INT32;
|
||||
using android::util::FIELD_TYPE_INT64;
|
||||
using android::util::FIELD_TYPE_MESSAGE;
|
||||
using android::util::ProtoOutputStream;
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
|
||||
// for ActiveMetric
|
||||
const int FIELD_ID_ACTIVE_METRIC_ID = 1;
|
||||
const int FIELD_ID_ACTIVE_METRIC_ACTIVATION = 2;
|
||||
|
||||
// for ActiveEventActivation
|
||||
const int FIELD_ID_ACTIVE_EVENT_ACTIVATION_ATOM_MATCHER_INDEX = 1;
|
||||
const int FIELD_ID_ACTIVE_EVENT_ACTIVATION_REMAINING_TTL_NANOS = 2;
|
||||
const int FIELD_ID_ACTIVE_EVENT_ACTIVATION_STATE = 3;
|
||||
|
||||
MetricProducer::MetricProducer(
|
||||
const int64_t& metricId, const ConfigKey& key, const int64_t timeBaseNs,
|
||||
const int conditionIndex, const vector<ConditionState>& initialConditionCache,
|
||||
const sp<ConditionWizard>& wizard,
|
||||
const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap,
|
||||
const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
|
||||
eventDeactivationMap,
|
||||
const vector<int>& slicedStateAtoms,
|
||||
const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap)
|
||||
: mMetricId(metricId),
|
||||
mConfigKey(key),
|
||||
mTimeBaseNs(timeBaseNs),
|
||||
mCurrentBucketStartTimeNs(timeBaseNs),
|
||||
mCurrentBucketNum(0),
|
||||
mCondition(initialCondition(conditionIndex, initialConditionCache)),
|
||||
mConditionTrackerIndex(conditionIndex),
|
||||
mConditionSliced(false),
|
||||
mWizard(wizard),
|
||||
mContainANYPositionInDimensionsInWhat(false),
|
||||
mSliceByPositionALL(false),
|
||||
mHasLinksToAllConditionDimensionsInTracker(false),
|
||||
mEventActivationMap(eventActivationMap),
|
||||
mEventDeactivationMap(eventDeactivationMap),
|
||||
mIsActive(mEventActivationMap.empty()),
|
||||
mSlicedStateAtoms(slicedStateAtoms),
|
||||
mStateGroupMap(stateGroupMap) {
|
||||
}
|
||||
|
||||
void MetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event) {
|
||||
if (!mIsActive) {
|
||||
return;
|
||||
}
|
||||
int64_t eventTimeNs = event.GetElapsedTimestampNs();
|
||||
// this is old event, maybe statsd restarted?
|
||||
if (eventTimeNs < mTimeBaseNs) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool condition;
|
||||
ConditionKey conditionKey;
|
||||
if (mConditionSliced) {
|
||||
for (const auto& link : mMetric2ConditionLinks) {
|
||||
getDimensionForCondition(event.getValues(), link, &conditionKey[link.conditionId]);
|
||||
}
|
||||
auto conditionState =
|
||||
mWizard->query(mConditionTrackerIndex, conditionKey,
|
||||
!mHasLinksToAllConditionDimensionsInTracker);
|
||||
condition = (conditionState == ConditionState::kTrue);
|
||||
} else {
|
||||
// TODO: The unknown condition state is not handled here, we should fix it.
|
||||
condition = mCondition == ConditionState::kTrue;
|
||||
}
|
||||
|
||||
// Stores atom id to primary key pairs for each state atom that the metric is
|
||||
// sliced by.
|
||||
std::map<int32_t, HashableDimensionKey> statePrimaryKeys;
|
||||
|
||||
// For states with primary fields, use MetricStateLinks to get the primary
|
||||
// field values from the log event. These values will form a primary key
|
||||
// that will be used to query StateTracker for the correct state value.
|
||||
for (const auto& stateLink : mMetric2StateLinks) {
|
||||
getDimensionForState(event.getValues(), stateLink,
|
||||
&statePrimaryKeys[stateLink.stateAtomId]);
|
||||
}
|
||||
|
||||
// For each sliced state, query StateTracker for the state value using
|
||||
// either the primary key from the previous step or the DEFAULT_DIMENSION_KEY.
|
||||
//
|
||||
// Expected functionality: for any case where the MetricStateLinks are
|
||||
// initialized incorrectly (ex. # of state links != # of primary fields, no
|
||||
// links are provided for a state with primary fields, links are provided
|
||||
// in the wrong order, etc.), StateTracker will simply return kStateUnknown
|
||||
// when queried using an incorrect key.
|
||||
HashableDimensionKey stateValuesKey;
|
||||
for (auto atomId : mSlicedStateAtoms) {
|
||||
FieldValue value;
|
||||
if (statePrimaryKeys.find(atomId) != statePrimaryKeys.end()) {
|
||||
// found a primary key for this state, query using the key
|
||||
queryStateValue(atomId, statePrimaryKeys[atomId], &value);
|
||||
} else {
|
||||
// if no MetricStateLinks exist for this state atom,
|
||||
// query using the default dimension key (empty HashableDimensionKey)
|
||||
queryStateValue(atomId, DEFAULT_DIMENSION_KEY, &value);
|
||||
}
|
||||
mapStateValue(atomId, &value);
|
||||
stateValuesKey.addValue(value);
|
||||
}
|
||||
|
||||
HashableDimensionKey dimensionInWhat;
|
||||
filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhat);
|
||||
MetricDimensionKey metricKey(dimensionInWhat, stateValuesKey);
|
||||
onMatchedLogEventInternalLocked(matcherIndex, metricKey, conditionKey, condition, event,
|
||||
statePrimaryKeys);
|
||||
}
|
||||
|
||||
bool MetricProducer::evaluateActiveStateLocked(int64_t elapsedTimestampNs) {
|
||||
bool isActive = mEventActivationMap.empty();
|
||||
for (auto& it : mEventActivationMap) {
|
||||
if (it.second->state == ActivationState::kActive &&
|
||||
elapsedTimestampNs > it.second->ttl_ns + it.second->start_ns) {
|
||||
it.second->state = ActivationState::kNotActive;
|
||||
}
|
||||
if (it.second->state == ActivationState::kActive) {
|
||||
isActive = true;
|
||||
}
|
||||
}
|
||||
return isActive;
|
||||
}
|
||||
|
||||
void MetricProducer::flushIfExpire(int64_t elapsedTimestampNs) {
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
if (!mIsActive) {
|
||||
return;
|
||||
}
|
||||
mIsActive = evaluateActiveStateLocked(elapsedTimestampNs);
|
||||
if (!mIsActive) {
|
||||
onActiveStateChangedLocked(elapsedTimestampNs);
|
||||
}
|
||||
}
|
||||
|
||||
void MetricProducer::activateLocked(int activationTrackerIndex, int64_t elapsedTimestampNs) {
|
||||
auto it = mEventActivationMap.find(activationTrackerIndex);
|
||||
if (it == mEventActivationMap.end()) {
|
||||
return;
|
||||
}
|
||||
auto& activation = it->second;
|
||||
if (ACTIVATE_ON_BOOT == activation->activationType) {
|
||||
if (ActivationState::kNotActive == activation->state) {
|
||||
activation->state = ActivationState::kActiveOnBoot;
|
||||
}
|
||||
// If the Activation is already active or set to kActiveOnBoot, do nothing.
|
||||
return;
|
||||
}
|
||||
activation->start_ns = elapsedTimestampNs;
|
||||
activation->state = ActivationState::kActive;
|
||||
bool oldActiveState = mIsActive;
|
||||
mIsActive = true;
|
||||
if (!oldActiveState) { // Metric went from not active to active.
|
||||
onActiveStateChangedLocked(elapsedTimestampNs);
|
||||
}
|
||||
}
|
||||
|
||||
void MetricProducer::cancelEventActivationLocked(int deactivationTrackerIndex) {
|
||||
auto it = mEventDeactivationMap.find(deactivationTrackerIndex);
|
||||
if (it == mEventDeactivationMap.end()) {
|
||||
return;
|
||||
}
|
||||
for (auto activationToCancelIt : it->second) {
|
||||
activationToCancelIt->state = ActivationState::kNotActive;
|
||||
}
|
||||
}
|
||||
|
||||
void MetricProducer::loadActiveMetricLocked(const ActiveMetric& activeMetric,
|
||||
int64_t currentTimeNs) {
|
||||
if (mEventActivationMap.size() == 0) {
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < activeMetric.activation_size(); i++) {
|
||||
const auto& activeEventActivation = activeMetric.activation(i);
|
||||
auto it = mEventActivationMap.find(activeEventActivation.atom_matcher_index());
|
||||
if (it == mEventActivationMap.end()) {
|
||||
ALOGE("Saved event activation not found");
|
||||
continue;
|
||||
}
|
||||
auto& activation = it->second;
|
||||
// If the event activation does not have a state, assume it is active.
|
||||
if (!activeEventActivation.has_state() ||
|
||||
activeEventActivation.state() == ActiveEventActivation::ACTIVE) {
|
||||
// We don't want to change the ttl for future activations, so we set the start_ns
|
||||
// such that start_ns + ttl_ns == currentTimeNs + remaining_ttl_nanos
|
||||
activation->start_ns =
|
||||
currentTimeNs + activeEventActivation.remaining_ttl_nanos() - activation->ttl_ns;
|
||||
activation->state = ActivationState::kActive;
|
||||
mIsActive = true;
|
||||
} else if (activeEventActivation.state() == ActiveEventActivation::ACTIVATE_ON_BOOT) {
|
||||
activation->state = ActivationState::kActiveOnBoot;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MetricProducer::writeActiveMetricToProtoOutputStream(
|
||||
int64_t currentTimeNs, const DumpReportReason reason, ProtoOutputStream* proto) {
|
||||
proto->write(FIELD_TYPE_INT64 | FIELD_ID_ACTIVE_METRIC_ID, (long long)mMetricId);
|
||||
for (auto& it : mEventActivationMap) {
|
||||
const int atom_matcher_index = it.first;
|
||||
const std::shared_ptr<Activation>& activation = it.second;
|
||||
|
||||
if (ActivationState::kNotActive == activation->state ||
|
||||
(ActivationState::kActive == activation->state &&
|
||||
activation->start_ns + activation->ttl_ns < currentTimeNs)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const uint64_t activationToken = proto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED |
|
||||
FIELD_ID_ACTIVE_METRIC_ACTIVATION);
|
||||
proto->write(FIELD_TYPE_INT32 | FIELD_ID_ACTIVE_EVENT_ACTIVATION_ATOM_MATCHER_INDEX,
|
||||
atom_matcher_index);
|
||||
if (ActivationState::kActive == activation->state) {
|
||||
const int64_t remainingTtlNs =
|
||||
activation->start_ns + activation->ttl_ns - currentTimeNs;
|
||||
proto->write(FIELD_TYPE_INT64 | FIELD_ID_ACTIVE_EVENT_ACTIVATION_REMAINING_TTL_NANOS,
|
||||
(long long)remainingTtlNs);
|
||||
proto->write(FIELD_TYPE_ENUM | FIELD_ID_ACTIVE_EVENT_ACTIVATION_STATE,
|
||||
ActiveEventActivation::ACTIVE);
|
||||
|
||||
} else if (ActivationState::kActiveOnBoot == activation->state) {
|
||||
if (reason == DEVICE_SHUTDOWN || reason == TERMINATION_SIGNAL_RECEIVED) {
|
||||
proto->write(
|
||||
FIELD_TYPE_INT64 | FIELD_ID_ACTIVE_EVENT_ACTIVATION_REMAINING_TTL_NANOS,
|
||||
(long long)activation->ttl_ns);
|
||||
proto->write(FIELD_TYPE_ENUM | FIELD_ID_ACTIVE_EVENT_ACTIVATION_STATE,
|
||||
ActiveEventActivation::ACTIVE);
|
||||
} else if (reason == STATSCOMPANION_DIED) {
|
||||
// We are saving because of system server death, not due to a device shutdown.
|
||||
// Next time we load, we do not want to activate metrics that activate on boot.
|
||||
proto->write(FIELD_TYPE_ENUM | FIELD_ID_ACTIVE_EVENT_ACTIVATION_STATE,
|
||||
ActiveEventActivation::ACTIVATE_ON_BOOT);
|
||||
}
|
||||
}
|
||||
proto->end(activationToken);
|
||||
}
|
||||
}
|
||||
|
||||
void MetricProducer::queryStateValue(const int32_t atomId, const HashableDimensionKey& queryKey,
|
||||
FieldValue* value) {
|
||||
if (!StateManager::getInstance().getStateValue(atomId, queryKey, value)) {
|
||||
value->mValue = Value(StateTracker::kStateUnknown);
|
||||
value->mField.setTag(atomId);
|
||||
ALOGW("StateTracker not found for state atom %d", atomId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void MetricProducer::mapStateValue(const int32_t atomId, FieldValue* value) {
|
||||
// check if there is a state map for this atom
|
||||
auto atomIt = mStateGroupMap.find(atomId);
|
||||
if (atomIt == mStateGroupMap.end()) {
|
||||
return;
|
||||
}
|
||||
auto valueIt = atomIt->second.find(value->mValue.int_value);
|
||||
if (valueIt == atomIt->second.end()) {
|
||||
// state map exists, but value was not put in a state group
|
||||
// so set mValue to kStateUnknown
|
||||
// TODO(tsaichristine): handle incomplete state maps
|
||||
value->mValue.setInt(StateTracker::kStateUnknown);
|
||||
} else {
|
||||
// set mValue to group_id
|
||||
value->mValue.setLong(valueIt->second);
|
||||
}
|
||||
}
|
||||
|
||||
HashableDimensionKey MetricProducer::getUnknownStateKey() {
|
||||
HashableDimensionKey stateKey;
|
||||
for (auto atom : mSlicedStateAtoms) {
|
||||
FieldValue fieldValue;
|
||||
fieldValue.mField.setTag(atom);
|
||||
fieldValue.mValue.setInt(StateTracker::kStateUnknown);
|
||||
stateKey.addValue(fieldValue);
|
||||
}
|
||||
return stateKey;
|
||||
}
|
||||
|
||||
DropEvent MetricProducer::buildDropEvent(const int64_t dropTimeNs, const BucketDropReason reason) {
|
||||
DropEvent event;
|
||||
event.reason = reason;
|
||||
event.dropTimeNs = dropTimeNs;
|
||||
return event;
|
||||
}
|
||||
|
||||
bool MetricProducer::maxDropEventsReached() {
|
||||
return mCurrentSkippedBucket.dropEvents.size() >= StatsdStats::kMaxLoggedBucketDropEvents;
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
@@ -1,508 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef METRIC_PRODUCER_H
|
||||
#define METRIC_PRODUCER_H
|
||||
|
||||
#include <frameworks/base/cmds/statsd/src/active_config_list.pb.h>
|
||||
#include <utils/RefBase.h>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#include "HashableDimensionKey.h"
|
||||
#include "anomaly/AnomalyTracker.h"
|
||||
#include "condition/ConditionWizard.h"
|
||||
#include "config/ConfigKey.h"
|
||||
#include "matchers/matcher_util.h"
|
||||
#include "packages/PackageInfoListener.h"
|
||||
#include "state/StateListener.h"
|
||||
#include "state/StateManager.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
// Keep this in sync with DumpReportReason enum in stats_log.proto
|
||||
enum DumpReportReason {
|
||||
DEVICE_SHUTDOWN = 1,
|
||||
CONFIG_UPDATED = 2,
|
||||
CONFIG_REMOVED = 3,
|
||||
GET_DATA_CALLED = 4,
|
||||
ADB_DUMP = 5,
|
||||
CONFIG_RESET = 6,
|
||||
STATSCOMPANION_DIED = 7,
|
||||
TERMINATION_SIGNAL_RECEIVED = 8
|
||||
};
|
||||
|
||||
// If the metric has no activation requirement, it will be active once the metric producer is
|
||||
// created.
|
||||
// If the metric needs to be activated by atoms, the metric producer will start
|
||||
// with kNotActive state, turn to kActive or kActiveOnBoot when the activation event arrives, become
|
||||
// kNotActive when it reaches the duration limit (timebomb). If the activation event arrives again
|
||||
// before or after it expires, the event producer will be re-activated and ttl will be reset.
|
||||
enum ActivationState {
|
||||
kNotActive = 0,
|
||||
kActive = 1,
|
||||
kActiveOnBoot = 2,
|
||||
};
|
||||
|
||||
enum DumpLatency {
|
||||
// In some cases, we only have a short time range to do the dump, e.g. statsd is being killed.
|
||||
// We might be able to return all the data in this mode. For instance, pull metrics might need
|
||||
// to be pulled when the current bucket is requested.
|
||||
FAST = 1,
|
||||
// In other cases, it is fine for a dump to take more than a few milliseconds, e.g. config
|
||||
// updates.
|
||||
NO_TIME_CONSTRAINTS = 2
|
||||
};
|
||||
|
||||
// Keep this in sync with BucketDropReason enum in stats_log.proto
|
||||
enum BucketDropReason {
|
||||
// For ValueMetric, a bucket is dropped during a dump report request iff
|
||||
// current bucket should be included, a pull is needed (pulled metric and
|
||||
// condition is true), and we are under fast time constraints.
|
||||
DUMP_REPORT_REQUESTED = 1,
|
||||
EVENT_IN_WRONG_BUCKET = 2,
|
||||
CONDITION_UNKNOWN = 3,
|
||||
PULL_FAILED = 4,
|
||||
PULL_DELAYED = 5,
|
||||
DIMENSION_GUARDRAIL_REACHED = 6,
|
||||
MULTIPLE_BUCKETS_SKIPPED = 7,
|
||||
// Not an invalid bucket case, but the bucket is dropped.
|
||||
BUCKET_TOO_SMALL = 8,
|
||||
// Not an invalid bucket case, but the bucket is skipped.
|
||||
NO_DATA = 9
|
||||
};
|
||||
|
||||
struct Activation {
|
||||
Activation(const ActivationType& activationType, const int64_t ttlNs)
|
||||
: ttl_ns(ttlNs),
|
||||
start_ns(0),
|
||||
state(ActivationState::kNotActive),
|
||||
activationType(activationType) {}
|
||||
|
||||
const int64_t ttl_ns;
|
||||
int64_t start_ns;
|
||||
ActivationState state;
|
||||
const ActivationType activationType;
|
||||
};
|
||||
|
||||
struct DropEvent {
|
||||
// Reason for dropping the bucket and/or marking the bucket invalid.
|
||||
BucketDropReason reason;
|
||||
// The timestamp of the drop event.
|
||||
int64_t dropTimeNs;
|
||||
};
|
||||
|
||||
struct SkippedBucket {
|
||||
// Start time of the dropped bucket.
|
||||
int64_t bucketStartTimeNs;
|
||||
// End time of the dropped bucket.
|
||||
int64_t bucketEndTimeNs;
|
||||
// List of events that invalidated this bucket.
|
||||
std::vector<DropEvent> dropEvents;
|
||||
|
||||
void reset() {
|
||||
bucketStartTimeNs = 0;
|
||||
bucketEndTimeNs = 0;
|
||||
dropEvents.clear();
|
||||
}
|
||||
};
|
||||
|
||||
// A MetricProducer is responsible for compute one single metrics, creating stats log report, and
|
||||
// writing the report to dropbox. MetricProducers should respond to package changes as required in
|
||||
// PackageInfoListener, but if none of the metrics are slicing by package name, then the update can
|
||||
// be a no-op.
|
||||
class MetricProducer : public virtual android::RefBase, public virtual StateListener {
|
||||
public:
|
||||
MetricProducer(const int64_t& metricId, const ConfigKey& key, const int64_t timeBaseNs,
|
||||
const int conditionIndex, const vector<ConditionState>& initialConditionCache,
|
||||
const sp<ConditionWizard>& wizard,
|
||||
const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap,
|
||||
const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
|
||||
eventDeactivationMap,
|
||||
const vector<int>& slicedStateAtoms,
|
||||
const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap);
|
||||
|
||||
virtual ~MetricProducer(){};
|
||||
|
||||
ConditionState initialCondition(const int conditionIndex,
|
||||
const vector<ConditionState>& initialConditionCache) const {
|
||||
return conditionIndex >= 0 ? initialConditionCache[conditionIndex] : ConditionState::kTrue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Force a partial bucket split on app upgrade
|
||||
*/
|
||||
virtual void notifyAppUpgrade(const int64_t& eventTimeNs) {
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
flushLocked(eventTimeNs);
|
||||
};
|
||||
|
||||
void notifyAppRemoved(const int64_t& eventTimeNs) {
|
||||
// Force buckets to split on removal also.
|
||||
notifyAppUpgrade(eventTimeNs);
|
||||
};
|
||||
|
||||
/**
|
||||
* Force a partial bucket split on boot complete.
|
||||
*/
|
||||
virtual void onStatsdInitCompleted(const int64_t& eventTimeNs) {
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
flushLocked(eventTimeNs);
|
||||
}
|
||||
// Consume the parsed stats log entry that already matched the "what" of the metric.
|
||||
void onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) {
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
onMatchedLogEventLocked(matcherIndex, event);
|
||||
}
|
||||
|
||||
void onConditionChanged(const bool condition, const int64_t eventTime) {
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
onConditionChangedLocked(condition, eventTime);
|
||||
}
|
||||
|
||||
void onSlicedConditionMayChange(bool overallCondition, const int64_t eventTime) {
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
onSlicedConditionMayChangeLocked(overallCondition, eventTime);
|
||||
}
|
||||
|
||||
bool isConditionSliced() const {
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
return mConditionSliced;
|
||||
};
|
||||
|
||||
void onStateChanged(const int64_t eventTimeNs, const int32_t atomId,
|
||||
const HashableDimensionKey& primaryKey, const FieldValue& oldState,
|
||||
const FieldValue& newState){};
|
||||
|
||||
// Output the metrics data to [protoOutput]. All metrics reports end with the same timestamp.
|
||||
// This method clears all the past buckets.
|
||||
void onDumpReport(const int64_t dumpTimeNs,
|
||||
const bool include_current_partial_bucket,
|
||||
const bool erase_data,
|
||||
const DumpLatency dumpLatency,
|
||||
std::set<string> *str_set,
|
||||
android::util::ProtoOutputStream* protoOutput) {
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
return onDumpReportLocked(dumpTimeNs, include_current_partial_bucket, erase_data,
|
||||
dumpLatency, str_set, protoOutput);
|
||||
}
|
||||
|
||||
void clearPastBuckets(const int64_t dumpTimeNs) {
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
return clearPastBucketsLocked(dumpTimeNs);
|
||||
}
|
||||
|
||||
void prepareFirstBucket() {
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
prepareFirstBucketLocked();
|
||||
}
|
||||
|
||||
// Returns the memory in bytes currently used to store this metric's data. Does not change
|
||||
// state.
|
||||
size_t byteSize() const {
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
return byteSizeLocked();
|
||||
}
|
||||
|
||||
void dumpStates(FILE* out, bool verbose) const {
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
dumpStatesLocked(out, verbose);
|
||||
}
|
||||
|
||||
// Let MetricProducer drop in-memory data to save memory.
|
||||
// We still need to keep future data valid and anomaly tracking work, which means we will
|
||||
// have to flush old data, informing anomaly trackers then safely drop old data.
|
||||
// We still keep current bucket data for future metrics' validity.
|
||||
void dropData(const int64_t dropTimeNs) {
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
dropDataLocked(dropTimeNs);
|
||||
}
|
||||
|
||||
void loadActiveMetric(const ActiveMetric& activeMetric, int64_t currentTimeNs) {
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
loadActiveMetricLocked(activeMetric, currentTimeNs);
|
||||
}
|
||||
|
||||
void activate(int activationTrackerIndex, int64_t elapsedTimestampNs) {
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
activateLocked(activationTrackerIndex, elapsedTimestampNs);
|
||||
}
|
||||
|
||||
void cancelEventActivation(int deactivationTrackerIndex) {
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
cancelEventActivationLocked(deactivationTrackerIndex);
|
||||
}
|
||||
|
||||
bool isActive() const {
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
return isActiveLocked();
|
||||
}
|
||||
|
||||
void flushIfExpire(int64_t elapsedTimestampNs);
|
||||
|
||||
void writeActiveMetricToProtoOutputStream(
|
||||
int64_t currentTimeNs, const DumpReportReason reason, ProtoOutputStream* proto);
|
||||
|
||||
// Start: getters/setters
|
||||
inline const int64_t& getMetricId() const {
|
||||
return mMetricId;
|
||||
}
|
||||
|
||||
// For test only.
|
||||
inline int64_t getCurrentBucketNum() const {
|
||||
return mCurrentBucketNum;
|
||||
}
|
||||
|
||||
int64_t getBucketSizeInNs() const {
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
return mBucketSizeNs;
|
||||
}
|
||||
|
||||
inline const std::vector<int> getSlicedStateAtoms() {
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
return mSlicedStateAtoms;
|
||||
}
|
||||
|
||||
/* If alert is valid, adds an AnomalyTracker and returns it. If invalid, returns nullptr. */
|
||||
virtual sp<AnomalyTracker> addAnomalyTracker(const Alert &alert,
|
||||
const sp<AlarmMonitor>& anomalyAlarmMonitor) {
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, mConfigKey);
|
||||
if (anomalyTracker != nullptr) {
|
||||
mAnomalyTrackers.push_back(anomalyTracker);
|
||||
}
|
||||
return anomalyTracker;
|
||||
}
|
||||
// End: getters/setters
|
||||
protected:
|
||||
/**
|
||||
* Flushes the current bucket if the eventTime is after the current bucket's end time.
|
||||
*/
|
||||
virtual void flushIfNeededLocked(const int64_t& eventTime){};
|
||||
|
||||
/**
|
||||
* For metrics that aggregate (ie, every metric producer except for EventMetricProducer),
|
||||
* we need to be able to flush the current buckets on demand (ie, end the current bucket and
|
||||
* start new bucket). If this function is called when eventTimeNs is greater than the current
|
||||
* bucket's end timestamp, than we flush up to the end of the latest full bucket; otherwise,
|
||||
* we assume that we want to flush a partial bucket. The bucket start timestamp and bucket
|
||||
* number are not changed by this function. This method should only be called by
|
||||
* flushIfNeededLocked or flushLocked or the app upgrade handler; the caller MUST update the
|
||||
* bucket timestamp and bucket number as needed.
|
||||
*/
|
||||
virtual void flushCurrentBucketLocked(const int64_t& eventTimeNs,
|
||||
const int64_t& nextBucketStartTimeNs) {};
|
||||
|
||||
/**
|
||||
* Flushes all the data including the current partial bucket.
|
||||
*/
|
||||
virtual void flushLocked(const int64_t& eventTimeNs) {
|
||||
flushIfNeededLocked(eventTimeNs);
|
||||
flushCurrentBucketLocked(eventTimeNs, eventTimeNs);
|
||||
};
|
||||
|
||||
/*
|
||||
* Individual metrics can implement their own business logic here. All pre-processing is done.
|
||||
*
|
||||
* [matcherIndex]: the index of the matcher which matched this event. This is interesting to
|
||||
* DurationMetric, because it has start/stop/stop_all 3 matchers.
|
||||
* [eventKey]: the extracted dimension key for the final output. if the metric doesn't have
|
||||
* dimensions, it will be DEFAULT_DIMENSION_KEY
|
||||
* [conditionKey]: the keys of conditions which should be used to query the condition for this
|
||||
* target event (from MetricConditionLink). This is passed to individual metrics
|
||||
* because DurationMetric needs it to be cached.
|
||||
* [condition]: whether condition is met. If condition is sliced, this is the result coming from
|
||||
* query with ConditionWizard; If condition is not sliced, this is the
|
||||
* nonSlicedCondition.
|
||||
* [event]: the log event, just in case the metric needs its data, e.g., EventMetric.
|
||||
*/
|
||||
virtual void onMatchedLogEventInternalLocked(
|
||||
const size_t matcherIndex, const MetricDimensionKey& eventKey,
|
||||
const ConditionKey& conditionKey, bool condition, const LogEvent& event,
|
||||
const map<int, HashableDimensionKey>& statePrimaryKeys) = 0;
|
||||
|
||||
// Consume the parsed stats log entry that already matched the "what" of the metric.
|
||||
virtual void onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event);
|
||||
virtual void onConditionChangedLocked(const bool condition, const int64_t eventTime) = 0;
|
||||
virtual void onSlicedConditionMayChangeLocked(bool overallCondition,
|
||||
const int64_t eventTime) = 0;
|
||||
virtual void onDumpReportLocked(const int64_t dumpTimeNs,
|
||||
const bool include_current_partial_bucket,
|
||||
const bool erase_data,
|
||||
const DumpLatency dumpLatency,
|
||||
std::set<string> *str_set,
|
||||
android::util::ProtoOutputStream* protoOutput) = 0;
|
||||
virtual void clearPastBucketsLocked(const int64_t dumpTimeNs) = 0;
|
||||
virtual void prepareFirstBucketLocked(){};
|
||||
virtual size_t byteSizeLocked() const = 0;
|
||||
virtual void dumpStatesLocked(FILE* out, bool verbose) const = 0;
|
||||
virtual void dropDataLocked(const int64_t dropTimeNs) = 0;
|
||||
void loadActiveMetricLocked(const ActiveMetric& activeMetric, int64_t currentTimeNs);
|
||||
void activateLocked(int activationTrackerIndex, int64_t elapsedTimestampNs);
|
||||
void cancelEventActivationLocked(int deactivationTrackerIndex);
|
||||
|
||||
bool evaluateActiveStateLocked(int64_t elapsedTimestampNs);
|
||||
|
||||
virtual void onActiveStateChangedLocked(const int64_t& eventTimeNs) {
|
||||
if (!mIsActive) {
|
||||
flushLocked(eventTimeNs);
|
||||
}
|
||||
}
|
||||
|
||||
inline bool isActiveLocked() const {
|
||||
return mIsActive;
|
||||
}
|
||||
|
||||
// Convenience to compute the current bucket's end time, which is always aligned with the
|
||||
// start time of the metric.
|
||||
int64_t getCurrentBucketEndTimeNs() const {
|
||||
return mTimeBaseNs + (mCurrentBucketNum + 1) * mBucketSizeNs;
|
||||
}
|
||||
|
||||
int64_t getBucketNumFromEndTimeNs(const int64_t endNs) {
|
||||
return (endNs - mTimeBaseNs) / mBucketSizeNs - 1;
|
||||
}
|
||||
|
||||
// Query StateManager for original state value using the queryKey.
|
||||
// The field and value are output.
|
||||
void queryStateValue(const int32_t atomId, const HashableDimensionKey& queryKey,
|
||||
FieldValue* value);
|
||||
|
||||
// If a state map exists for the given atom, replace the original state
|
||||
// value with the group id mapped to the value.
|
||||
// If no state map exists, keep the original state value.
|
||||
void mapStateValue(const int32_t atomId, FieldValue* value);
|
||||
|
||||
// Returns a HashableDimensionKey with unknown state value for each state
|
||||
// atom.
|
||||
HashableDimensionKey getUnknownStateKey();
|
||||
|
||||
DropEvent buildDropEvent(const int64_t dropTimeNs, const BucketDropReason reason);
|
||||
|
||||
// Returns true if the number of drop events in the current bucket has
|
||||
// exceeded the maximum number allowed, which is currently capped at 10.
|
||||
bool maxDropEventsReached();
|
||||
|
||||
const int64_t mMetricId;
|
||||
|
||||
const ConfigKey mConfigKey;
|
||||
|
||||
// The time when this metric producer was first created. The end time for the current bucket
|
||||
// can be computed from this based on mCurrentBucketNum.
|
||||
int64_t mTimeBaseNs;
|
||||
|
||||
// Start time may not be aligned with the start of statsd if there is an app upgrade in the
|
||||
// middle of a bucket.
|
||||
int64_t mCurrentBucketStartTimeNs;
|
||||
|
||||
// Used by anomaly detector to track which bucket we are in. This is not sent with the produced
|
||||
// report.
|
||||
int64_t mCurrentBucketNum;
|
||||
|
||||
int64_t mBucketSizeNs;
|
||||
|
||||
ConditionState mCondition;
|
||||
|
||||
int mConditionTrackerIndex;
|
||||
|
||||
bool mConditionSliced;
|
||||
|
||||
sp<ConditionWizard> mWizard;
|
||||
|
||||
bool mContainANYPositionInDimensionsInWhat;
|
||||
|
||||
bool mSliceByPositionALL;
|
||||
|
||||
vector<Matcher> mDimensionsInWhat; // The dimensions_in_what defined in statsd_config
|
||||
|
||||
// True iff the metric to condition links cover all dimension fields in the condition tracker.
|
||||
// This field is always false for combinational condition trackers.
|
||||
bool mHasLinksToAllConditionDimensionsInTracker;
|
||||
|
||||
std::vector<Metric2Condition> mMetric2ConditionLinks;
|
||||
|
||||
std::vector<sp<AnomalyTracker>> mAnomalyTrackers;
|
||||
|
||||
mutable std::mutex mMutex;
|
||||
|
||||
// When the metric producer has multiple activations, these activations are ORed to determine
|
||||
// whether the metric producer is ready to generate metrics.
|
||||
std::unordered_map<int, std::shared_ptr<Activation>> mEventActivationMap;
|
||||
|
||||
// Maps index of atom matcher for deactivation to a list of Activation structs.
|
||||
std::unordered_map<int, std::vector<std::shared_ptr<Activation>>> mEventDeactivationMap;
|
||||
|
||||
bool mIsActive;
|
||||
|
||||
// The slice_by_state atom ids defined in statsd_config.
|
||||
const std::vector<int32_t> mSlicedStateAtoms;
|
||||
|
||||
// Maps atom ids and state values to group_ids (<atom_id, <value, group_id>>).
|
||||
const std::unordered_map<int32_t, std::unordered_map<int, int64_t>> mStateGroupMap;
|
||||
|
||||
// MetricStateLinks defined in statsd_config that link fields in the state
|
||||
// atom to fields in the "what" atom.
|
||||
std::vector<Metric2State> mMetric2StateLinks;
|
||||
|
||||
SkippedBucket mCurrentSkippedBucket;
|
||||
// Buckets that were invalidated and had their data dropped.
|
||||
std::vector<SkippedBucket> mSkippedBuckets;
|
||||
|
||||
FRIEND_TEST(CountMetricE2eTest, TestSlicedState);
|
||||
FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithMap);
|
||||
FRIEND_TEST(CountMetricE2eTest, TestMultipleSlicedStates);
|
||||
FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithPrimaryFields);
|
||||
FRIEND_TEST(CountMetricE2eTest, TestInitialConditionChanges);
|
||||
|
||||
FRIEND_TEST(DurationMetricE2eTest, TestOneBucket);
|
||||
FRIEND_TEST(DurationMetricE2eTest, TestTwoBuckets);
|
||||
FRIEND_TEST(DurationMetricE2eTest, TestWithActivation);
|
||||
FRIEND_TEST(DurationMetricE2eTest, TestWithCondition);
|
||||
FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedCondition);
|
||||
FRIEND_TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition);
|
||||
FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedState);
|
||||
FRIEND_TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState);
|
||||
FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStateMapped);
|
||||
FRIEND_TEST(DurationMetricE2eTest, TestSlicedStatePrimaryFieldsNotSubsetDimInWhat);
|
||||
FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset);
|
||||
|
||||
FRIEND_TEST(MetricActivationE2eTest, TestCountMetric);
|
||||
FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation);
|
||||
FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations);
|
||||
FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithSameDeactivation);
|
||||
FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations);
|
||||
|
||||
FRIEND_TEST(StatsLogProcessorTest, TestActiveConfigMetricDiskWriteRead);
|
||||
FRIEND_TEST(StatsLogProcessorTest, TestActivationOnBoot);
|
||||
FRIEND_TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivations);
|
||||
FRIEND_TEST(StatsLogProcessorTest,
|
||||
TestActivationOnBootMultipleActivationsDifferentActivationTypes);
|
||||
FRIEND_TEST(StatsLogProcessorTest, TestActivationsPersistAcrossSystemServerRestart);
|
||||
|
||||
FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState);
|
||||
FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions);
|
||||
FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithIncorrectDimensions);
|
||||
FRIEND_TEST(ValueMetricE2eTest, TestInitialConditionChanges);
|
||||
|
||||
FRIEND_TEST(MetricsManagerTest, TestInitialConditions);
|
||||
};
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
#endif // METRIC_PRODUCER_H
|
||||
@@ -1,695 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
#define DEBUG false // STOPSHIP if true
|
||||
#include "Log.h"
|
||||
|
||||
#include "MetricsManager.h"
|
||||
|
||||
#include <private/android_filesystem_config.h>
|
||||
|
||||
#include "CountMetricProducer.h"
|
||||
#include "condition/CombinationConditionTracker.h"
|
||||
#include "condition/SimpleConditionTracker.h"
|
||||
#include "guardrail/StatsdStats.h"
|
||||
#include "matchers/CombinationLogMatchingTracker.h"
|
||||
#include "matchers/SimpleLogMatchingTracker.h"
|
||||
#include "metrics_manager_util.h"
|
||||
#include "state/StateManager.h"
|
||||
#include "stats_log_util.h"
|
||||
#include "stats_util.h"
|
||||
#include "statslog_statsd.h"
|
||||
|
||||
using android::util::FIELD_COUNT_REPEATED;
|
||||
using android::util::FIELD_TYPE_INT32;
|
||||
using android::util::FIELD_TYPE_INT64;
|
||||
using android::util::FIELD_TYPE_MESSAGE;
|
||||
using android::util::FIELD_TYPE_STRING;
|
||||
using android::util::ProtoOutputStream;
|
||||
|
||||
using std::set;
|
||||
using std::string;
|
||||
using std::vector;
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
const int FIELD_ID_METRICS = 1;
|
||||
const int FIELD_ID_ANNOTATIONS = 7;
|
||||
const int FIELD_ID_ANNOTATIONS_INT64 = 1;
|
||||
const int FIELD_ID_ANNOTATIONS_INT32 = 2;
|
||||
|
||||
// for ActiveConfig
|
||||
const int FIELD_ID_ACTIVE_CONFIG_ID = 1;
|
||||
const int FIELD_ID_ACTIVE_CONFIG_UID = 2;
|
||||
const int FIELD_ID_ACTIVE_CONFIG_METRIC = 3;
|
||||
|
||||
MetricsManager::MetricsManager(const ConfigKey& key, const StatsdConfig& config,
|
||||
const int64_t timeBaseNs, const int64_t currentTimeNs,
|
||||
const sp<UidMap>& uidMap,
|
||||
const sp<StatsPullerManager>& pullerManager,
|
||||
const sp<AlarmMonitor>& anomalyAlarmMonitor,
|
||||
const sp<AlarmMonitor>& periodicAlarmMonitor)
|
||||
: mConfigKey(key),
|
||||
mUidMap(uidMap),
|
||||
mTtlNs(config.has_ttl_in_seconds() ? config.ttl_in_seconds() * NS_PER_SEC : -1),
|
||||
mTtlEndNs(-1),
|
||||
mLastReportTimeNs(currentTimeNs),
|
||||
mLastReportWallClockNs(getWallClockNs()),
|
||||
mPullerManager(pullerManager),
|
||||
mWhitelistedAtomIds(config.whitelisted_atom_ids().begin(),
|
||||
config.whitelisted_atom_ids().end()),
|
||||
mShouldPersistHistory(config.persist_locally()) {
|
||||
// Init the ttl end timestamp.
|
||||
refreshTtl(timeBaseNs);
|
||||
|
||||
mConfigValid = initStatsdConfig(
|
||||
key, config, *uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
|
||||
timeBaseNs, currentTimeNs, mTagIds, mAllAtomMatchers, mAllConditionTrackers,
|
||||
mAllMetricProducers, mAllAnomalyTrackers, mAllPeriodicAlarmTrackers,
|
||||
mConditionToMetricMap, mTrackerToMetricMap, mTrackerToConditionMap,
|
||||
mActivationAtomTrackerToMetricMap, mDeactivationAtomTrackerToMetricMap,
|
||||
mAlertTrackerMap, mMetricIndexesWithActivation, mNoReportMetricIds);
|
||||
|
||||
mHashStringsInReport = config.hash_strings_in_metric_report();
|
||||
mVersionStringsInReport = config.version_strings_in_metric_report();
|
||||
mInstallerInReport = config.installer_in_metric_report();
|
||||
|
||||
// Init allowed pushed atom uids.
|
||||
if (config.allowed_log_source_size() == 0) {
|
||||
mConfigValid = false;
|
||||
ALOGE("Log source whitelist is empty! This config won't get any data. Suggest adding at "
|
||||
"least AID_SYSTEM and AID_STATSD to the allowed_log_source field.");
|
||||
} else {
|
||||
for (const auto& source : config.allowed_log_source()) {
|
||||
auto it = UidMap::sAidToUidMapping.find(source);
|
||||
if (it != UidMap::sAidToUidMapping.end()) {
|
||||
mAllowedUid.push_back(it->second);
|
||||
} else {
|
||||
mAllowedPkg.push_back(source);
|
||||
}
|
||||
}
|
||||
|
||||
if (mAllowedUid.size() + mAllowedPkg.size() > StatsdStats::kMaxLogSourceCount) {
|
||||
ALOGE("Too many log sources. This is likely to be an error in the config.");
|
||||
mConfigValid = false;
|
||||
} else {
|
||||
initLogSourceWhiteList();
|
||||
}
|
||||
}
|
||||
|
||||
// Init default allowed pull atom uids.
|
||||
int numPullPackages = 0;
|
||||
for (const string& pullSource : config.default_pull_packages()) {
|
||||
auto it = UidMap::sAidToUidMapping.find(pullSource);
|
||||
if (it != UidMap::sAidToUidMapping.end()) {
|
||||
numPullPackages++;
|
||||
mDefaultPullUids.insert(it->second);
|
||||
} else {
|
||||
ALOGE("Default pull atom packages must be in sAidToUidMapping");
|
||||
mConfigValid = false;
|
||||
}
|
||||
}
|
||||
// Init per-atom pull atom packages.
|
||||
for (const PullAtomPackages& pullAtomPackages : config.pull_atom_packages()) {
|
||||
int32_t atomId = pullAtomPackages.atom_id();
|
||||
for (const string& pullPackage : pullAtomPackages.packages()) {
|
||||
numPullPackages++;
|
||||
auto it = UidMap::sAidToUidMapping.find(pullPackage);
|
||||
if (it != UidMap::sAidToUidMapping.end()) {
|
||||
mPullAtomUids[atomId].insert(it->second);
|
||||
} else {
|
||||
mPullAtomPackages[atomId].insert(pullPackage);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (numPullPackages > StatsdStats::kMaxPullAtomPackages) {
|
||||
ALOGE("Too many sources in default_pull_packages and pull_atom_packages. This is likely to "
|
||||
"be an error in the config");
|
||||
mConfigValid = false;
|
||||
} else {
|
||||
initPullAtomSources();
|
||||
}
|
||||
mPullerManager->RegisterPullUidProvider(mConfigKey, this);
|
||||
|
||||
// Store the sub-configs used.
|
||||
for (const auto& annotation : config.annotation()) {
|
||||
mAnnotations.emplace_back(annotation.field_int64(), annotation.field_int32());
|
||||
}
|
||||
|
||||
// Guardrail. Reject the config if it's too big.
|
||||
if (mAllMetricProducers.size() > StatsdStats::kMaxMetricCountPerConfig ||
|
||||
mAllConditionTrackers.size() > StatsdStats::kMaxConditionCountPerConfig ||
|
||||
mAllAtomMatchers.size() > StatsdStats::kMaxMatcherCountPerConfig) {
|
||||
ALOGE("This config is too big! Reject!");
|
||||
mConfigValid = false;
|
||||
}
|
||||
if (mAllAnomalyTrackers.size() > StatsdStats::kMaxAlertCountPerConfig) {
|
||||
ALOGE("This config has too many alerts! Reject!");
|
||||
mConfigValid = false;
|
||||
}
|
||||
|
||||
mIsAlwaysActive = (mMetricIndexesWithActivation.size() != mAllMetricProducers.size()) ||
|
||||
(mAllMetricProducers.size() == 0);
|
||||
bool isActive = mIsAlwaysActive;
|
||||
for (int metric : mMetricIndexesWithActivation) {
|
||||
isActive |= mAllMetricProducers[metric]->isActive();
|
||||
}
|
||||
mIsActive = isActive;
|
||||
VLOG("mIsActive is initialized to %d", mIsActive)
|
||||
|
||||
// no matter whether this config is valid, log it in the stats.
|
||||
StatsdStats::getInstance().noteConfigReceived(
|
||||
key, mAllMetricProducers.size(), mAllConditionTrackers.size(), mAllAtomMatchers.size(),
|
||||
mAllAnomalyTrackers.size(), mAnnotations, mConfigValid);
|
||||
// Check active
|
||||
for (const auto& metric : mAllMetricProducers) {
|
||||
if (metric->isActive()) {
|
||||
mIsActive = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MetricsManager::~MetricsManager() {
|
||||
for (auto it : mAllMetricProducers) {
|
||||
for (int atomId : it->getSlicedStateAtoms()) {
|
||||
StateManager::getInstance().unregisterListener(atomId, it);
|
||||
}
|
||||
}
|
||||
mPullerManager->UnregisterPullUidProvider(mConfigKey, this);
|
||||
|
||||
VLOG("~MetricsManager()");
|
||||
}
|
||||
|
||||
void MetricsManager::initLogSourceWhiteList() {
|
||||
std::lock_guard<std::mutex> lock(mAllowedLogSourcesMutex);
|
||||
mAllowedLogSources.clear();
|
||||
mAllowedLogSources.insert(mAllowedUid.begin(), mAllowedUid.end());
|
||||
|
||||
for (const auto& pkg : mAllowedPkg) {
|
||||
auto uids = mUidMap->getAppUid(pkg);
|
||||
mAllowedLogSources.insert(uids.begin(), uids.end());
|
||||
}
|
||||
if (DEBUG) {
|
||||
for (const auto& uid : mAllowedLogSources) {
|
||||
VLOG("Allowed uid %d", uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MetricsManager::initPullAtomSources() {
|
||||
std::lock_guard<std::mutex> lock(mAllowedLogSourcesMutex);
|
||||
mCombinedPullAtomUids.clear();
|
||||
for (const auto& [atomId, uids] : mPullAtomUids) {
|
||||
mCombinedPullAtomUids[atomId].insert(uids.begin(), uids.end());
|
||||
}
|
||||
for (const auto& [atomId, packages] : mPullAtomPackages) {
|
||||
for (const string& pkg : packages) {
|
||||
set<int32_t> uids = mUidMap->getAppUid(pkg);
|
||||
mCombinedPullAtomUids[atomId].insert(uids.begin(), uids.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool MetricsManager::isConfigValid() const {
|
||||
return mConfigValid;
|
||||
}
|
||||
|
||||
void MetricsManager::notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk, const int uid,
|
||||
const int64_t version) {
|
||||
// Inform all metric producers.
|
||||
for (const auto& it : mAllMetricProducers) {
|
||||
it->notifyAppUpgrade(eventTimeNs);
|
||||
}
|
||||
// check if we care this package
|
||||
if (std::find(mAllowedPkg.begin(), mAllowedPkg.end(), apk) != mAllowedPkg.end()) {
|
||||
// We will re-initialize the whole list because we don't want to keep the multi mapping of
|
||||
// UID<->pkg inside MetricsManager to reduce the memory usage.
|
||||
initLogSourceWhiteList();
|
||||
}
|
||||
|
||||
for (const auto& it : mPullAtomPackages) {
|
||||
if (it.second.find(apk) != it.second.end()) {
|
||||
initPullAtomSources();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MetricsManager::notifyAppRemoved(const int64_t& eventTimeNs, const string& apk,
|
||||
const int uid) {
|
||||
// Inform all metric producers.
|
||||
for (const auto& it : mAllMetricProducers) {
|
||||
it->notifyAppRemoved(eventTimeNs);
|
||||
}
|
||||
// check if we care this package
|
||||
if (std::find(mAllowedPkg.begin(), mAllowedPkg.end(), apk) != mAllowedPkg.end()) {
|
||||
// We will re-initialize the whole list because we don't want to keep the multi mapping of
|
||||
// UID<->pkg inside MetricsManager to reduce the memory usage.
|
||||
initLogSourceWhiteList();
|
||||
}
|
||||
|
||||
for (const auto& it : mPullAtomPackages) {
|
||||
if (it.second.find(apk) != it.second.end()) {
|
||||
initPullAtomSources();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MetricsManager::onUidMapReceived(const int64_t& eventTimeNs) {
|
||||
// Purposefully don't inform metric producers on a new snapshot
|
||||
// because we don't need to flush partial buckets.
|
||||
// This occurs if a new user is added/removed or statsd crashes.
|
||||
initPullAtomSources();
|
||||
|
||||
if (mAllowedPkg.size() == 0) {
|
||||
return;
|
||||
}
|
||||
initLogSourceWhiteList();
|
||||
}
|
||||
|
||||
void MetricsManager::onStatsdInitCompleted(const int64_t& eventTimeNs) {
|
||||
// Inform all metric producers.
|
||||
for (const auto& it : mAllMetricProducers) {
|
||||
it->onStatsdInitCompleted(eventTimeNs);
|
||||
}
|
||||
}
|
||||
|
||||
void MetricsManager::init() {
|
||||
for (const auto& producer : mAllMetricProducers) {
|
||||
producer->prepareFirstBucket();
|
||||
}
|
||||
}
|
||||
|
||||
vector<int32_t> MetricsManager::getPullAtomUids(int32_t atomId) {
|
||||
std::lock_guard<std::mutex> lock(mAllowedLogSourcesMutex);
|
||||
vector<int32_t> uids;
|
||||
const auto& it = mCombinedPullAtomUids.find(atomId);
|
||||
if (it != mCombinedPullAtomUids.end()) {
|
||||
uids.insert(uids.end(), it->second.begin(), it->second.end());
|
||||
}
|
||||
uids.insert(uids.end(), mDefaultPullUids.begin(), mDefaultPullUids.end());
|
||||
return uids;
|
||||
}
|
||||
|
||||
void MetricsManager::dumpStates(FILE* out, bool verbose) {
|
||||
fprintf(out, "ConfigKey %s, allowed source:", mConfigKey.ToString().c_str());
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mAllowedLogSourcesMutex);
|
||||
for (const auto& source : mAllowedLogSources) {
|
||||
fprintf(out, "%d ", source);
|
||||
}
|
||||
}
|
||||
fprintf(out, "\n");
|
||||
for (const auto& producer : mAllMetricProducers) {
|
||||
producer->dumpStates(out, verbose);
|
||||
}
|
||||
}
|
||||
|
||||
void MetricsManager::dropData(const int64_t dropTimeNs) {
|
||||
for (const auto& producer : mAllMetricProducers) {
|
||||
producer->dropData(dropTimeNs);
|
||||
}
|
||||
}
|
||||
|
||||
void MetricsManager::onDumpReport(const int64_t dumpTimeStampNs,
|
||||
const bool include_current_partial_bucket,
|
||||
const bool erase_data,
|
||||
const DumpLatency dumpLatency,
|
||||
std::set<string> *str_set,
|
||||
ProtoOutputStream* protoOutput) {
|
||||
VLOG("=========================Metric Reports Start==========================");
|
||||
// one StatsLogReport per MetricProduer
|
||||
for (const auto& producer : mAllMetricProducers) {
|
||||
if (mNoReportMetricIds.find(producer->getMetricId()) == mNoReportMetricIds.end()) {
|
||||
uint64_t token = protoOutput->start(
|
||||
FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_METRICS);
|
||||
if (mHashStringsInReport) {
|
||||
producer->onDumpReport(dumpTimeStampNs, include_current_partial_bucket, erase_data,
|
||||
dumpLatency, str_set, protoOutput);
|
||||
} else {
|
||||
producer->onDumpReport(dumpTimeStampNs, include_current_partial_bucket, erase_data,
|
||||
dumpLatency, nullptr, protoOutput);
|
||||
}
|
||||
protoOutput->end(token);
|
||||
} else {
|
||||
producer->clearPastBuckets(dumpTimeStampNs);
|
||||
}
|
||||
}
|
||||
for (const auto& annotation : mAnnotations) {
|
||||
uint64_t token = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED |
|
||||
FIELD_ID_ANNOTATIONS);
|
||||
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ANNOTATIONS_INT64,
|
||||
(long long)annotation.first);
|
||||
protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_ANNOTATIONS_INT32, annotation.second);
|
||||
protoOutput->end(token);
|
||||
}
|
||||
|
||||
// Do not update the timestamps when data is not cleared to avoid timestamps from being
|
||||
// misaligned.
|
||||
if (erase_data) {
|
||||
mLastReportTimeNs = dumpTimeStampNs;
|
||||
mLastReportWallClockNs = getWallClockNs();
|
||||
}
|
||||
VLOG("=========================Metric Reports End==========================");
|
||||
}
|
||||
|
||||
|
||||
bool MetricsManager::checkLogCredentials(const LogEvent& event) {
|
||||
if (mWhitelistedAtomIds.find(event.GetTagId()) != mWhitelistedAtomIds.end()) {
|
||||
return true;
|
||||
}
|
||||
std::lock_guard<std::mutex> lock(mAllowedLogSourcesMutex);
|
||||
if (mAllowedLogSources.find(event.GetUid()) == mAllowedLogSources.end()) {
|
||||
VLOG("log source %d not on the whitelist", event.GetUid());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MetricsManager::eventSanityCheck(const LogEvent& event) {
|
||||
if (event.GetTagId() == util::APP_BREADCRUMB_REPORTED) {
|
||||
// Check that app breadcrumb reported fields are valid.
|
||||
status_t err = NO_ERROR;
|
||||
|
||||
// Uid is 3rd from last field and must match the caller's uid,
|
||||
// unless that caller is statsd itself (statsd is allowed to spoof uids).
|
||||
long appHookUid = event.GetLong(event.size()-2, &err);
|
||||
if (err != NO_ERROR) {
|
||||
VLOG("APP_BREADCRUMB_REPORTED had error when parsing the uid");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Because the uid within the LogEvent may have been mapped from
|
||||
// isolated to host, map the loggerUid similarly before comparing.
|
||||
int32_t loggerUid = mUidMap->getHostUidOrSelf(event.GetUid());
|
||||
if (loggerUid != appHookUid && loggerUid != AID_STATSD) {
|
||||
VLOG("APP_BREADCRUMB_REPORTED has invalid uid: claimed %ld but caller is %d",
|
||||
appHookUid, loggerUid);
|
||||
return false;
|
||||
}
|
||||
|
||||
// The state must be from 0,3. This part of code must be manually updated.
|
||||
long appHookState = event.GetLong(event.size(), &err);
|
||||
if (err != NO_ERROR) {
|
||||
VLOG("APP_BREADCRUMB_REPORTED had error when parsing the state field");
|
||||
return false;
|
||||
} else if (appHookState < 0 || appHookState > 3) {
|
||||
VLOG("APP_BREADCRUMB_REPORTED does not have valid state %ld", appHookState);
|
||||
return false;
|
||||
}
|
||||
} else if (event.GetTagId() == util::DAVEY_OCCURRED) {
|
||||
// Daveys can be logged from any app since they are logged in libs/hwui/JankTracker.cpp.
|
||||
// Check that the davey duration is reasonable. Max length check is for privacy.
|
||||
status_t err = NO_ERROR;
|
||||
|
||||
// Uid is the first field provided.
|
||||
long jankUid = event.GetLong(1, &err);
|
||||
if (err != NO_ERROR) {
|
||||
VLOG("Davey occurred had error when parsing the uid");
|
||||
return false;
|
||||
}
|
||||
int32_t loggerUid = event.GetUid();
|
||||
if (loggerUid != jankUid && loggerUid != AID_STATSD) {
|
||||
VLOG("DAVEY_OCCURRED has invalid uid: claimed %ld but caller is %d", jankUid,
|
||||
loggerUid);
|
||||
return false;
|
||||
}
|
||||
|
||||
long duration = event.GetLong(event.size(), &err);
|
||||
if (err != NO_ERROR) {
|
||||
VLOG("Davey occurred had error when parsing the duration");
|
||||
return false;
|
||||
} else if (duration > 100000) {
|
||||
VLOG("Davey duration is unreasonably long: %ld", duration);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Consume the stats log if it's interesting to this metric.
|
||||
void MetricsManager::onLogEvent(const LogEvent& event) {
|
||||
if (!mConfigValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!checkLogCredentials(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!eventSanityCheck(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int tagId = event.GetTagId();
|
||||
int64_t eventTimeNs = event.GetElapsedTimestampNs();
|
||||
|
||||
bool isActive = mIsAlwaysActive;
|
||||
|
||||
// Set of metrics that are still active after flushing.
|
||||
unordered_set<int> activeMetricsIndices;
|
||||
|
||||
// Update state of all metrics w/ activation conditions as of eventTimeNs.
|
||||
for (int metricIndex : mMetricIndexesWithActivation) {
|
||||
const sp<MetricProducer>& metric = mAllMetricProducers[metricIndex];
|
||||
metric->flushIfExpire(eventTimeNs);
|
||||
if (metric->isActive()) {
|
||||
// If this metric w/ activation condition is still active after
|
||||
// flushing, remember it.
|
||||
activeMetricsIndices.insert(metricIndex);
|
||||
}
|
||||
}
|
||||
|
||||
mIsActive = isActive || !activeMetricsIndices.empty();
|
||||
|
||||
if (mTagIds.find(tagId) == mTagIds.end()) {
|
||||
// Not interesting...
|
||||
return;
|
||||
}
|
||||
|
||||
vector<MatchingState> matcherCache(mAllAtomMatchers.size(), MatchingState::kNotComputed);
|
||||
|
||||
// Evaluate all atom matchers.
|
||||
for (auto& matcher : mAllAtomMatchers) {
|
||||
matcher->onLogEvent(event, mAllAtomMatchers, matcherCache);
|
||||
}
|
||||
|
||||
// Set of metrics that received an activation cancellation.
|
||||
unordered_set<int> metricIndicesWithCanceledActivations;
|
||||
|
||||
// Determine which metric activations received a cancellation and cancel them.
|
||||
for (const auto& it : mDeactivationAtomTrackerToMetricMap) {
|
||||
if (matcherCache[it.first] == MatchingState::kMatched) {
|
||||
for (int metricIndex : it.second) {
|
||||
mAllMetricProducers[metricIndex]->cancelEventActivation(it.first);
|
||||
metricIndicesWithCanceledActivations.insert(metricIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Determine whether any metrics are no longer active after cancelling metric activations.
|
||||
for (const int metricIndex : metricIndicesWithCanceledActivations) {
|
||||
const sp<MetricProducer>& metric = mAllMetricProducers[metricIndex];
|
||||
metric->flushIfExpire(eventTimeNs);
|
||||
if (!metric->isActive()) {
|
||||
activeMetricsIndices.erase(metricIndex);
|
||||
}
|
||||
}
|
||||
|
||||
isActive |= !activeMetricsIndices.empty();
|
||||
|
||||
|
||||
// Determine which metric activations should be turned on and turn them on
|
||||
for (const auto& it : mActivationAtomTrackerToMetricMap) {
|
||||
if (matcherCache[it.first] == MatchingState::kMatched) {
|
||||
for (int metricIndex : it.second) {
|
||||
mAllMetricProducers[metricIndex]->activate(it.first, eventTimeNs);
|
||||
isActive |= mAllMetricProducers[metricIndex]->isActive();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mIsActive = isActive;
|
||||
|
||||
// A bitmap to see which ConditionTracker needs to be re-evaluated.
|
||||
vector<bool> conditionToBeEvaluated(mAllConditionTrackers.size(), false);
|
||||
|
||||
for (const auto& pair : mTrackerToConditionMap) {
|
||||
if (matcherCache[pair.first] == MatchingState::kMatched) {
|
||||
const auto& conditionList = pair.second;
|
||||
for (const int conditionIndex : conditionList) {
|
||||
conditionToBeEvaluated[conditionIndex] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vector<ConditionState> conditionCache(mAllConditionTrackers.size(),
|
||||
ConditionState::kNotEvaluated);
|
||||
// A bitmap to track if a condition has changed value.
|
||||
vector<bool> changedCache(mAllConditionTrackers.size(), false);
|
||||
for (size_t i = 0; i < mAllConditionTrackers.size(); i++) {
|
||||
if (conditionToBeEvaluated[i] == false) {
|
||||
continue;
|
||||
}
|
||||
sp<ConditionTracker>& condition = mAllConditionTrackers[i];
|
||||
condition->evaluateCondition(event, matcherCache, mAllConditionTrackers, conditionCache,
|
||||
changedCache);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < mAllConditionTrackers.size(); i++) {
|
||||
if (changedCache[i] == false) {
|
||||
continue;
|
||||
}
|
||||
auto pair = mConditionToMetricMap.find(i);
|
||||
if (pair != mConditionToMetricMap.end()) {
|
||||
auto& metricList = pair->second;
|
||||
for (auto metricIndex : metricList) {
|
||||
// Metric cares about non sliced condition, and it's changed.
|
||||
// Push the new condition to it directly.
|
||||
if (!mAllMetricProducers[metricIndex]->isConditionSliced()) {
|
||||
mAllMetricProducers[metricIndex]->onConditionChanged(conditionCache[i],
|
||||
eventTimeNs);
|
||||
// Metric cares about sliced conditions, and it may have changed. Send
|
||||
// notification, and the metric can query the sliced conditions that are
|
||||
// interesting to it.
|
||||
} else {
|
||||
mAllMetricProducers[metricIndex]->onSlicedConditionMayChange(conditionCache[i],
|
||||
eventTimeNs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For matched AtomMatchers, tell relevant metrics that a matched event has come.
|
||||
for (size_t i = 0; i < mAllAtomMatchers.size(); i++) {
|
||||
if (matcherCache[i] == MatchingState::kMatched) {
|
||||
StatsdStats::getInstance().noteMatcherMatched(mConfigKey,
|
||||
mAllAtomMatchers[i]->getId());
|
||||
auto pair = mTrackerToMetricMap.find(i);
|
||||
if (pair != mTrackerToMetricMap.end()) {
|
||||
auto& metricList = pair->second;
|
||||
for (const int metricIndex : metricList) {
|
||||
// pushed metrics are never scheduled pulls
|
||||
mAllMetricProducers[metricIndex]->onMatchedLogEvent(i, event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MetricsManager::onAnomalyAlarmFired(
|
||||
const int64_t& timestampNs,
|
||||
unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>>& alarmSet) {
|
||||
for (const auto& itr : mAllAnomalyTrackers) {
|
||||
itr->informAlarmsFired(timestampNs, alarmSet);
|
||||
}
|
||||
}
|
||||
|
||||
void MetricsManager::onPeriodicAlarmFired(
|
||||
const int64_t& timestampNs,
|
||||
unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>>& alarmSet) {
|
||||
for (const auto& itr : mAllPeriodicAlarmTrackers) {
|
||||
itr->informAlarmsFired(timestampNs, alarmSet);
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the total byte size of all metrics managed by a single config source.
|
||||
size_t MetricsManager::byteSize() {
|
||||
size_t totalSize = 0;
|
||||
for (const auto& metricProducer : mAllMetricProducers) {
|
||||
totalSize += metricProducer->byteSize();
|
||||
}
|
||||
return totalSize;
|
||||
}
|
||||
|
||||
void MetricsManager::loadActiveConfig(const ActiveConfig& config, int64_t currentTimeNs) {
|
||||
if (config.metric_size() == 0) {
|
||||
ALOGW("No active metric for config %s", mConfigKey.ToString().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < config.metric_size(); i++) {
|
||||
const auto& activeMetric = config.metric(i);
|
||||
for (int metricIndex : mMetricIndexesWithActivation) {
|
||||
const auto& metric = mAllMetricProducers[metricIndex];
|
||||
if (metric->getMetricId() == activeMetric.id()) {
|
||||
VLOG("Setting active metric: %lld", (long long)metric->getMetricId());
|
||||
metric->loadActiveMetric(activeMetric, currentTimeNs);
|
||||
if (!mIsActive && metric->isActive()) {
|
||||
StatsdStats::getInstance().noteActiveStatusChanged(mConfigKey,
|
||||
/*activate=*/ true);
|
||||
}
|
||||
mIsActive |= metric->isActive();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MetricsManager::writeActiveConfigToProtoOutputStream(
|
||||
int64_t currentTimeNs, const DumpReportReason reason, ProtoOutputStream* proto) {
|
||||
proto->write(FIELD_TYPE_INT64 | FIELD_ID_ACTIVE_CONFIG_ID, (long long)mConfigKey.GetId());
|
||||
proto->write(FIELD_TYPE_INT32 | FIELD_ID_ACTIVE_CONFIG_UID, mConfigKey.GetUid());
|
||||
for (int metricIndex : mMetricIndexesWithActivation) {
|
||||
const auto& metric = mAllMetricProducers[metricIndex];
|
||||
const uint64_t metricToken = proto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED |
|
||||
FIELD_ID_ACTIVE_CONFIG_METRIC);
|
||||
metric->writeActiveMetricToProtoOutputStream(currentTimeNs, reason, proto);
|
||||
proto->end(metricToken);
|
||||
}
|
||||
}
|
||||
|
||||
bool MetricsManager::writeMetadataToProto(int64_t currentWallClockTimeNs,
|
||||
int64_t systemElapsedTimeNs,
|
||||
metadata::StatsMetadata* statsMetadata) {
|
||||
bool metadataWritten = false;
|
||||
metadata::ConfigKey* configKey = statsMetadata->mutable_config_key();
|
||||
configKey->set_config_id(mConfigKey.GetId());
|
||||
configKey->set_uid(mConfigKey.GetUid());
|
||||
for (const auto& anomalyTracker : mAllAnomalyTrackers) {
|
||||
metadata::AlertMetadata* alertMetadata = statsMetadata->add_alert_metadata();
|
||||
bool alertWritten = anomalyTracker->writeAlertMetadataToProto(currentWallClockTimeNs,
|
||||
systemElapsedTimeNs, alertMetadata);
|
||||
if (!alertWritten) {
|
||||
statsMetadata->mutable_alert_metadata()->RemoveLast();
|
||||
}
|
||||
metadataWritten |= alertWritten;
|
||||
}
|
||||
return metadataWritten;
|
||||
}
|
||||
|
||||
void MetricsManager::loadMetadata(const metadata::StatsMetadata& metadata,
|
||||
int64_t currentWallClockTimeNs,
|
||||
int64_t systemElapsedTimeNs) {
|
||||
for (const metadata::AlertMetadata& alertMetadata : metadata.alert_metadata()) {
|
||||
int64_t alertId = alertMetadata.alert_id();
|
||||
auto it = mAlertTrackerMap.find(alertId);
|
||||
if (it == mAlertTrackerMap.end()) {
|
||||
ALOGE("No anomalyTracker found for alertId %lld", (long long) alertId);
|
||||
continue;
|
||||
}
|
||||
mAllAnomalyTrackers[it->second]->loadAlertMetadata(alertMetadata,
|
||||
currentWallClockTimeNs,
|
||||
systemElapsedTimeNs);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user