Add support for dimension, and link with condition and added DurationMetric
Now we support following metrics:
<Duration> of [app holding a wake lock], while [*this app*] is [in background] [AND] [screen is off]
[Slice] the output by [app name, wake lock name], with bucket size [30sec]
+ Also added onDumpReport() api in MetricsManager, it can be called from client to fetch the data from
statsd
+ Also added command line tool to dump the StatsLogReport from all metrics for debugging.
+ Synced proto from google3. with a pending cl (cr/172359050)
TODO: We need to add tons of tests to test the Metrics. I will work on it after this CL so people
can be unblocked.
I locally test the duration metric with wake lock with an app that generates StatsLog events.
Test: statsd_test
and manual test, and run:
adb shell cmd stats dump-report
We have a default config, which contains a metrics to count PROCESS_START event sliced by
package name.
Change-Id: I4838cc6cf025c143b7e84f43040703a78121fd25
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index 3def13f..e7825cf 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -73,6 +73,16 @@
}
}
+vector<StatsLogReport> StatsLogProcessor::onDumpReport(const ConfigKey& key) {
+ auto it = mMetricsManagers.find(key);
+ if (it == mMetricsManagers.end()) {
+ ALOGW("Config source %s does not exist", key.ToString().c_str());
+ return vector<StatsLogReport>();
+ }
+
+ return it->second->onDumpReport();
+}
+
void StatsLogProcessor::OnConfigRemoved(const ConfigKey& key) {
auto it = mMetricsManagers.find(key);
if (it != mMetricsManagers.end()) {
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index dc60485..3cefd29 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -33,7 +33,7 @@
class StatsLogProcessor : public ConfigListener {
public:
- StatsLogProcessor(const sp<UidMap> &uidMap);
+ StatsLogProcessor(const sp<UidMap>& uidMap);
virtual ~StatsLogProcessor();
virtual void OnLogEvent(const LogEvent& event);
@@ -41,6 +41,9 @@
void OnConfigUpdated(const ConfigKey& key, const StatsdConfig& config);
void OnConfigRemoved(const ConfigKey& key);
+ // TODO: Once we have the ProtoOutputStream in c++, we can just return byte array.
+ std::vector<StatsLogReport> onDumpReport(const ConfigKey& key);
+
private:
// TODO: use EventMetrics to log the events.
DropboxWriter m_dropbox_writer;
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index b72e04e..87616d3 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -64,7 +64,6 @@
// ======================================================================
StatsService::StatsService(const sp<Looper>& handlerLooper)
: mStatsPullerManager(),
-
mAnomalyMonitor(new AnomalyMonitor(2)) // TODO: Put this comment somewhere better
{
mUidMap = new UidMap();
@@ -89,7 +88,7 @@
void StatsService::init_build_type_callback(void* cookie, const char* /*name*/, const char* value,
uint32_t serial) {
- if (0 == strcmp("eng", value)) {
+ if (0 == strcmp("eng", value) || 0 == strcmp("userdebug", value)) {
reinterpret_cast<StatsService*>(cookie)->mEngBuild = true;
}
}
@@ -187,10 +186,13 @@
return cmd_print_stats_log(out, args);
}
- // adb shell cmd stats print-stats-log
if (!args[0].compare(String8("print-uid-map"))) {
return cmd_print_uid_map(out);
}
+
+ if (!args[0].compare(String8("dump-report"))) {
+ return cmd_dump_report(out, err, args);
+ }
}
print_cmd_help(out);
@@ -248,7 +250,9 @@
}
}
} else {
- fprintf(err, "The config can only be set for other UIDs on eng builds.\n");
+ fprintf(err,
+ "The config can only be set for other UIDs on eng or userdebug "
+ "builds.\n");
}
}
@@ -287,6 +291,54 @@
return UNKNOWN_ERROR;
}
+status_t StatsService::cmd_dump_report(FILE* out, FILE* err, const Vector<String8>& args) {
+ if (mProcessor != nullptr) {
+ const int argCount = args.size();
+ bool good = false;
+ int uid;
+ string name;
+ if (argCount == 2) {
+ // Automatically pick the UID
+ uid = IPCThreadState::self()->getCallingUid();
+ // TODO: What if this isn't a binder call? Should we fail?
+ name.assign(args[2].c_str(), args[2].size());
+ good = true;
+ } else if (argCount == 3) {
+ // If it's a userdebug or eng build, then the shell user can
+ // impersonate other uids.
+ if (mEngBuild) {
+ const char* s = args[1].c_str();
+ if (*s != '\0') {
+ char* end = NULL;
+ uid = strtol(s, &end, 0);
+ if (*end == '\0') {
+ name.assign(args[2].c_str(), args[2].size());
+ good = true;
+ }
+ }
+ } else {
+ fprintf(out,
+ "The metrics can only be dumped for other UIDs on eng or userdebug "
+ "builds.\n");
+ }
+ }
+ if (good) {
+ mProcessor->onDumpReport(ConfigKey(uid, name));
+ // TODO: print the returned StatsLogReport to file instead of printing to logcat.
+ fprintf(out, "Dump report for Config [%d,%s]\n", uid, name.c_str());
+ fprintf(out, "See the StatsLogReport in logcat...\n");
+ return android::OK;
+ } else {
+ // If arg parsing failed, print the help text and return an error.
+ print_cmd_help(out);
+ return UNKNOWN_ERROR;
+ }
+ } else {
+ fprintf(out, "Log processor does not exist...\n");
+ return UNKNOWN_ERROR;
+ }
+}
+
status_t StatsService::cmd_print_stats_log(FILE* out, const Vector<String8>& args) {
long msec = 0;
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index 7d305e9..294aec8 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -111,6 +111,11 @@
status_t cmd_print_stats_log(FILE* out, const Vector<String8>& args);
/**
+ * Print the event log.
+ */
+ status_t cmd_dump_report(FILE* out, FILE* err, const Vector<String8>& args);
+
+ /**
* Print the mapping of uids to package names.
*/
status_t cmd_print_uid_map(FILE* out);
diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.cpp b/cmds/statsd/src/condition/CombinationConditionTracker.cpp
index 014747f..f56c15a 100644
--- a/cmds/statsd/src/condition/CombinationConditionTracker.cpp
+++ b/cmds/statsd/src/condition/CombinationConditionTracker.cpp
@@ -25,6 +25,7 @@
namespace os {
namespace statsd {
+using std::map;
using std::string;
using std::unique_ptr;
using std::unordered_map;
@@ -102,33 +103,62 @@
return true;
}
+void CombinationConditionTracker::isConditionMet(
+ const map<string, HashableDimensionKey>& conditionParameters,
+ const vector<sp<ConditionTracker>>& allConditions, vector<ConditionState>& conditionCache) {
+ for (const int childIndex : mChildren) {
+ if (conditionCache[childIndex] == ConditionState::kNotEvaluated) {
+ allConditions[childIndex]->isConditionMet(conditionParameters, allConditions,
+ conditionCache);
+ }
+ }
+ conditionCache[mIndex] =
+ evaluateCombinationCondition(mChildren, mLogicalOperation, conditionCache);
+}
+
bool CombinationConditionTracker::evaluateCondition(
const LogEvent& event, const std::vector<MatchingState>& eventMatcherValues,
const std::vector<sp<ConditionTracker>>& mAllConditions,
- std::vector<ConditionState>& conditionCache, std::vector<bool>& changedCache) {
+ std::vector<ConditionState>& nonSlicedConditionCache,
+ std::vector<bool>& nonSlicedChangedCache, vector<bool>& slicedConditionChanged) {
// value is up to date.
- if (conditionCache[mIndex] != ConditionState::kNotEvaluated) {
+ if (nonSlicedConditionCache[mIndex] != ConditionState::kNotEvaluated) {
return false;
}
for (const int childIndex : mChildren) {
- if (conditionCache[childIndex] == ConditionState::kNotEvaluated) {
+ if (nonSlicedConditionCache[childIndex] == ConditionState::kNotEvaluated) {
const sp<ConditionTracker>& child = mAllConditions[childIndex];
- child->evaluateCondition(event, eventMatcherValues, mAllConditions, conditionCache,
- changedCache);
+ child->evaluateCondition(event, eventMatcherValues, mAllConditions,
+ nonSlicedConditionCache, nonSlicedChangedCache,
+ slicedConditionChanged);
}
}
ConditionState newCondition =
- evaluateCombinationCondition(mChildren, mLogicalOperation, conditionCache);
+ evaluateCombinationCondition(mChildren, mLogicalOperation, nonSlicedConditionCache);
- bool changed = (mConditionState != newCondition);
- mConditionState = newCondition;
+ bool nonSlicedChanged = (mNonSlicedConditionState != newCondition);
+ mNonSlicedConditionState = newCondition;
- conditionCache[mIndex] = mConditionState;
+ nonSlicedConditionCache[mIndex] = mNonSlicedConditionState;
- changedCache[mIndex] = changed;
- return changed;
+ nonSlicedChangedCache[mIndex] = nonSlicedChanged;
+
+ if (mSliced) {
+ for (const int childIndex : mChildren) {
+ // If any of the sliced condition in children condition changes, the combination
+ // condition may be changed too.
+ if (slicedConditionChanged[childIndex]) {
+ slicedConditionChanged[mIndex] = true;
+ break;
+ }
+ }
+ ALOGD("CombinationCondition %s sliced may changed? %d", mName.c_str(),
+ slicedConditionChanged[mIndex] == true);
+ }
+
+ return nonSlicedChanged;
}
} // namespace statsd
diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.h b/cmds/statsd/src/condition/CombinationConditionTracker.h
index 5d2d77e..fc88a88 100644
--- a/cmds/statsd/src/condition/CombinationConditionTracker.h
+++ b/cmds/statsd/src/condition/CombinationConditionTracker.h
@@ -39,7 +39,14 @@
const std::vector<MatchingState>& eventMatcherValues,
const std::vector<sp<ConditionTracker>>& mAllConditions,
std::vector<ConditionState>& conditionCache,
- std::vector<bool>& changedCache) override;
+ std::vector<bool>& changedCache,
+ std::vector<bool>& slicedConditionMayChanged) override;
+
+ void isConditionMet(const std::map<std::string, HashableDimensionKey>& conditionParameters,
+ const std::vector<sp<ConditionTracker>>& allConditions,
+ std::vector<ConditionState>& conditionCache) override;
+
+ void addDimensions(const std::vector<KeyMatcher>& keyMatchers) override{};
private:
LogicalOperation mLogicalOperation;
diff --git a/cmds/statsd/src/condition/ConditionTracker.h b/cmds/statsd/src/condition/ConditionTracker.h
index 8cc7e23..055b478 100644
--- a/cmds/statsd/src/condition/ConditionTracker.h
+++ b/cmds/statsd/src/condition/ConditionTracker.h
@@ -38,8 +38,9 @@
: mName(name),
mIndex(index),
mInitialized(false),
- mConditionState(ConditionState::kUnknown),
- mTrackerIndex(){};
+ mTrackerIndex(),
+ mNonSlicedConditionState(ConditionState::kUnknown),
+ mSliced(false){};
virtual ~ConditionTracker(){};
@@ -63,25 +64,47 @@
// event before ConditionTrackers, because ConditionTracker depends on
// LogMatchingTrackers.
// mAllConditions: the list of all ConditionTracker
- // conditionCache: the cached results of the ConditionTrackers for this new event.
- // changedCache: the bit map to record whether the condition has changed.
+ // conditionCache: the cached non-sliced condition of the ConditionTrackers for this new event.
+ // nonSlicedConditionChanged: the bit map to record whether non-sliced condition has changed.
+ // slicedConditionMayChanged: the bit map to record whether sliced condition may have changed.
+ // Because sliced condition needs parameters to determine the value. So the sliced
+ // condition is not pushed to metrics. We only inform the relevant metrics that the sliced
+ // condition may have changed, and metrics should pull the conditions that they are
+ // interested in.
virtual bool evaluateCondition(const LogEvent& event,
const std::vector<MatchingState>& eventMatcherValues,
const std::vector<sp<ConditionTracker>>& mAllConditions,
std::vector<ConditionState>& conditionCache,
- std::vector<bool>& changedCache) = 0;
+ std::vector<bool>& nonSlicedConditionChanged,
+ std::vector<bool>& slicedConditionMayChanged) = 0;
// Return the current condition state.
virtual ConditionState isConditionMet() {
- ALOGW("Condition %s value %d", mName.c_str(), mConditionState);
- return mConditionState;
+ return mNonSlicedConditionState;
};
+ // 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
+ // [conditionCache]: the cache holding the condition evaluation values.
+ virtual void isConditionMet(
+ const std::map<std::string, HashableDimensionKey>& conditionParameters,
+ const std::vector<sp<ConditionTracker>>& allConditions,
+ std::vector<ConditionState>& conditionCache) = 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;
+ }
+
+ virtual void addDimensions(const std::vector<KeyMatcher>& keyMatchers) = 0;
+
protected:
// We don't really need the string name, but having a name here makes log messages
// easy to debug.
@@ -93,11 +116,12 @@
// if it's properly initialized.
bool mInitialized;
- // current condition state.
- ConditionState mConditionState;
-
// the list of LogMatchingTracker index that this ConditionTracker uses.
std::set<int> mTrackerIndex;
+
+ ConditionState mNonSlicedConditionState;
+
+ bool mSliced;
};
} // namespace statsd
diff --git a/cmds/statsd/src/condition/ConditionWizard.cpp b/cmds/statsd/src/condition/ConditionWizard.cpp
new file mode 100644
index 0000000..411f7e5
--- /dev/null
+++ b/cmds/statsd/src/condition/ConditionWizard.cpp
@@ -0,0 +1,36 @@
+/*
+ * 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::map;
+using std::string;
+using std::vector;
+
+ConditionState ConditionWizard::query(const int index,
+ const map<string, HashableDimensionKey>& parameters) {
+ vector<ConditionState> cache(mAllConditions.size(), ConditionState::kNotEvaluated);
+
+ mAllConditions[index]->isConditionMet(parameters, mAllConditions, cache);
+ return cache[index];
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/condition/ConditionWizard.h b/cmds/statsd/src/condition/ConditionWizard.h
new file mode 100644
index 0000000..4889b64
--- /dev/null
+++ b/cmds/statsd/src/condition/ConditionWizard.h
@@ -0,0 +1,48 @@
+/*
+ * 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"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+// Held by MetricProducer, to query a condition state with input defined in EventConditionLink.
+class ConditionWizard : public virtual android::RefBase {
+public:
+ ConditionWizard(std::vector<sp<ConditionTracker>>& conditionTrackers)
+ : mAllConditions(conditionTrackers){};
+
+ // 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.
+ ConditionState query(const int conditionIndex,
+ const std::map<std::string, HashableDimensionKey>& conditionParameters);
+
+private:
+ std::vector<sp<ConditionTracker>>& mAllConditions;
+};
+
+} // namespace statsd
+} // namespace os
+} // namespace android
+#endif // CONDITION_WIZARD_H
diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.cpp b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
index fa583bf..bde3846 100644
--- a/cmds/statsd/src/condition/SimpleConditionTracker.cpp
+++ b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
@@ -25,6 +25,7 @@
namespace os {
namespace statsd {
+using std::map;
using std::string;
using std::unique_ptr;
using std::unordered_map;
@@ -64,7 +65,7 @@
if (simpleCondition.has_stop_all()) {
auto pair = trackerNameIndexMap.find(simpleCondition.stop_all());
if (pair == trackerNameIndexMap.end()) {
- ALOGW("Stop matcher %s not found in the config", simpleCondition.stop().c_str());
+ ALOGW("Stop all matcher %s not found in the config", simpleCondition.stop().c_str());
return;
}
mStopAllLogMatcherIndex = pair->second;
@@ -89,41 +90,136 @@
return mInitialized;
}
+void print(unordered_map<HashableDimensionKey, ConditionState>& conditions, const string& name) {
+ VLOG("%s DUMP:", name.c_str());
+
+ for (const auto& pair : conditions) {
+ VLOG("\t%s %d", pair.first.c_str(), pair.second);
+ }
+}
+
+void SimpleConditionTracker::addDimensions(const std::vector<KeyMatcher>& keyMatchers) {
+ VLOG("Added dimensions size %lu", (unsigned long)keyMatchers.size());
+ mDimensionsList.push_back(keyMatchers);
+ mSliced = true;
+}
+
bool SimpleConditionTracker::evaluateCondition(const LogEvent& event,
const vector<MatchingState>& eventMatcherValues,
const vector<sp<ConditionTracker>>& mAllConditions,
vector<ConditionState>& conditionCache,
- vector<bool>& changedCache) {
+ vector<bool>& nonSlicedConditionChanged,
+ std::vector<bool>& slicedConditionChanged) {
if (conditionCache[mIndex] != ConditionState::kNotEvaluated) {
// it has been evaluated.
- VLOG("Yes, already evaluated, %s %d", mName.c_str(), mConditionState);
+ VLOG("Yes, already evaluated, %s %d", mName.c_str(), mNonSlicedConditionState);
return false;
}
// Ignore nesting, because we know we cannot trust ourselves on tracking nesting conditions.
- ConditionState newCondition = mConditionState;
+
+ ConditionState newCondition = mNonSlicedConditionState;
+ bool matched = false;
// 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) {
+ matched = true;
newCondition = ConditionState::kTrue;
}
if (mStopLogMatcherIndex >= 0 &&
eventMatcherValues[mStopLogMatcherIndex] == MatchingState::kMatched) {
+ matched = true;
newCondition = ConditionState::kFalse;
}
+ bool stopAll = false;
if (mStopAllLogMatcherIndex >= 0 &&
eventMatcherValues[mStopAllLogMatcherIndex] == MatchingState::kMatched) {
+ matched = true;
newCondition = ConditionState::kFalse;
+ stopAll = true;
}
- bool changed = (mConditionState != newCondition);
- mConditionState = newCondition;
- conditionCache[mIndex] = mConditionState;
- changedCache[mIndex] = changed;
- return changed;
+ if (matched == false) {
+ slicedConditionChanged[mIndex] = false;
+ nonSlicedConditionChanged[mIndex] = false;
+ conditionCache[mIndex] = mNonSlicedConditionState;
+ return false;
+ }
+
+ bool nonSlicedChanged = mNonSlicedConditionState != newCondition;
+
+ bool slicedChanged = false;
+
+ if (stopAll) {
+ // TODO: handle stop all; all dimension should be cleared.
+ }
+
+ if (mDimensionsList.size() > 0) {
+ for (size_t i = 0; i < mDimensionsList.size(); i++) {
+ const auto& dim = mDimensionsList[i];
+ vector<KeyValuePair> key = getDimensionKey(event, dim);
+ HashableDimensionKey hashableKey = getHashableKey(key);
+ if (mSlicedConditionState.find(hashableKey) == mSlicedConditionState.end() ||
+ mSlicedConditionState[hashableKey] != newCondition) {
+ slicedChanged = true;
+ mSlicedConditionState[hashableKey] = newCondition;
+ }
+ VLOG("key: %s %d", hashableKey.c_str(), newCondition);
+ }
+ // dump all dimensions for debugging
+ if (DEBUG) {
+ print(mSlicedConditionState, mName);
+ }
+ }
+
+ // even if this SimpleCondition is not sliced, it may be part of a sliced CombinationCondition
+ // if the nonSliced condition changed, it may affect the sliced condition in the parent node.
+ // so mark the slicedConditionChanged to be true.
+ // For example: APP_IN_BACKGROUND_OR_SCREEN_OFF
+ // APP_IN_BACKGROUND is sliced [App_A->True, App_B->False].
+ // SCREEN_OFF is not sliced, and it changes from False -> True;
+ // We need to populate this change to parent condition. Because for App_B,
+ // the APP_IN_BACKGROUND_OR_SCREEN_OFF condition would change from False->True.
+ slicedConditionChanged[mIndex] = mSliced ? slicedChanged : nonSlicedChanged;
+ nonSlicedConditionChanged[mIndex] = nonSlicedChanged;
+
+ VLOG("SimpleCondition %s nonSlicedChange? %d SlicedChanged? %d", mName.c_str(),
+ nonSlicedConditionChanged[mIndex] == true, slicedConditionChanged[mIndex] == true);
+ mNonSlicedConditionState = newCondition;
+ conditionCache[mIndex] = mNonSlicedConditionState;
+
+ return nonSlicedConditionChanged[mIndex];
+}
+
+void SimpleConditionTracker::isConditionMet(
+ const map<string, HashableDimensionKey>& conditionParameters,
+ const vector<sp<ConditionTracker>>& allConditions, vector<ConditionState>& conditionCache) {
+ const auto pair = conditionParameters.find(mName);
+ if (pair == conditionParameters.end()) {
+ // the query does not need my sliced condition. just return the non sliced condition.
+ conditionCache[mIndex] = mNonSlicedConditionState;
+ VLOG("Condition %s return %d", mName.c_str(), mNonSlicedConditionState);
+ return;
+ }
+
+ const HashableDimensionKey& key = pair->second;
+ VLOG("simpleCondition %s query key: %s", mName.c_str(), key.c_str());
+
+ if (mSlicedConditionState.find(key) == mSlicedConditionState.end()) {
+ // never seen this key before. the condition is unknown to us.
+ conditionCache[mIndex] = ConditionState::kUnknown;
+ } else {
+ conditionCache[mIndex] = mSlicedConditionState[key];
+ }
+
+ VLOG("Condition %s return %d", mName.c_str(), conditionCache[mIndex]);
+
+ if (DEBUG) {
+ print(mSlicedConditionState, mName);
+ }
}
} // namespace statsd
diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.h b/cmds/statsd/src/condition/SimpleConditionTracker.h
index 9dd06a1..1f357f0 100644
--- a/cmds/statsd/src/condition/SimpleConditionTracker.h
+++ b/cmds/statsd/src/condition/SimpleConditionTracker.h
@@ -19,6 +19,7 @@
#include "ConditionTracker.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "stats_util.h"
namespace android {
namespace os {
@@ -26,6 +27,8 @@
class SimpleConditionTracker : public virtual ConditionTracker {
public:
+ // dimensions is a vector of vector because for one single condition, different metrics may be
+ // interested in slicing in different ways. one vector<KeyMatcher> defines one type of slicing.
SimpleConditionTracker(const std::string& name, const int index,
const SimpleCondition& simpleCondition,
const std::unordered_map<std::string, int>& trackerNameIndexMap);
@@ -41,7 +44,14 @@
const std::vector<MatchingState>& eventMatcherValues,
const std::vector<sp<ConditionTracker>>& mAllConditions,
std::vector<ConditionState>& conditionCache,
- std::vector<bool>& changedCache) override;
+ std::vector<bool>& changedCache,
+ std::vector<bool>& slicedChangedCache) override;
+
+ void isConditionMet(const std::map<std::string, HashableDimensionKey>& conditionParameters,
+ const std::vector<sp<ConditionTracker>>& allConditions,
+ std::vector<ConditionState>& conditionCache) override;
+
+ void addDimensions(const std::vector<KeyMatcher>& keyMatchers) override;
private:
// The index of the LogEventMatcher which defines the start.
@@ -55,6 +65,13 @@
// The index of the LogEventMatcher which defines the stop all.
int mStopAllLogMatcherIndex;
+
+ // Different metrics may subscribe to different types of slicings. So it's a vector of vector.
+ std::vector<std::vector<KeyMatcher>> mDimensionsList;
+
+ // Keep the map from the internal HashableDimensionKey to std::vector<KeyValuePair>
+ // that StatsLogReport wants.
+ std::unordered_map<HashableDimensionKey, ConditionState> mSlicedConditionState;
};
} // namespace statsd
diff --git a/cmds/statsd/src/condition/condition_util.cpp b/cmds/statsd/src/condition/condition_util.cpp
index c7c8fcc..40d41be 100644
--- a/cmds/statsd/src/condition/condition_util.cpp
+++ b/cmds/statsd/src/condition/condition_util.cpp
@@ -23,6 +23,7 @@
#include <log/logprint.h>
#include <utils/Errors.h>
#include <unordered_map>
+#include "../matchers/matcher_util.h"
#include "ConditionTracker.h"
#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
@@ -90,6 +91,25 @@
return newCondition;
}
+HashableDimensionKey getDimensionKeyForCondition(const LogEvent& event,
+ const EventConditionLink& link) {
+ vector<KeyMatcher> eventKey;
+ eventKey.reserve(link.key_in_main().size());
+
+ for (const auto& key : link.key_in_main()) {
+ eventKey.push_back(key);
+ }
+
+ vector<KeyValuePair> dimensionKey = getDimensionKey(event, eventKey);
+
+ for (int i = 0; i < link.key_in_main_size(); i++) {
+ auto& kv = dimensionKey[i];
+ kv.set_key(link.key_in_condition(i).key());
+ }
+
+ return getHashableKey(dimensionKey);
+}
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/src/condition/condition_util.h b/cmds/statsd/src/condition/condition_util.h
index a4fcea3..47e245e 100644
--- a/cmds/statsd/src/condition/condition_util.h
+++ b/cmds/statsd/src/condition/condition_util.h
@@ -18,6 +18,7 @@
#define CONDITION_UTIL_H
#include <vector>
+#include "../matchers/matcher_util.h"
#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
@@ -35,6 +36,9 @@
ConditionState evaluateCombinationCondition(const std::vector<int>& children,
const LogicalOperation& operation,
const std::vector<ConditionState>& conditionCache);
+
+HashableDimensionKey getDimensionKeyForCondition(const LogEvent& event,
+ const EventConditionLink& link);
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp
index 2a4d6e2..038edd3 100644
--- a/cmds/statsd/src/config/ConfigManager.cpp
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -118,130 +118,156 @@
StatsdConfig config;
config.set_config_id(12345L);
- // One count metric to count screen on
+ int WAKE_LOCK_TAG_ID = 11;
+ int WAKE_LOCK_UID_KEY_ID = 1;
+ int WAKE_LOCK_STATE_KEY = 2;
+ int WAKE_LOCK_ACQUIRE_VALUE = 1;
+ int WAKE_LOCK_RELEASE_VALUE = 0;
+
+ int APP_USAGE_ID = 12345;
+ int APP_USAGE_UID_KEY_ID = 1;
+ int APP_USAGE_STATE_KEY = 2;
+ int APP_USAGE_FOREGROUND = 1;
+ int APP_USAGE_BACKGROUND = 0;
+
+ int SCREEN_EVENT_TAG_ID = 2;
+ int SCREEN_EVENT_STATE_KEY = 1;
+ int SCREEN_EVENT_ON_VALUE = 2;
+ int SCREEN_EVENT_OFF_VALUE = 1;
+
+ int UID_PROCESS_STATE_TAG_ID = 3;
+ int UID_PROCESS_STATE_UID_KEY = 1;
+
+ // Count Screen ON events.
CountMetric* metric = config.add_count_metric();
- metric->set_metric_id(20150717L);
- metric->set_what("SCREEN_IS_ON");
+ metric->set_metric_id(1);
+ metric->set_what("SCREEN_TURNED_ON");
metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
- // One count metric to count PHOTO_CHANGE_OR_CHROME_CRASH
+ // Count process state changes, slice by uid.
metric = config.add_count_metric();
- metric->set_metric_id(20150718L);
- metric->set_what("PHOTO_PROCESS_STATE_CHANGE");
- metric->mutable_bucket()->set_bucket_size_millis(60 * 1000L);
- metric->set_condition("SCREEN_IS_ON");
+ metric->set_metric_id(2);
+ metric->set_what("PROCESS_STATE_CHANGE");
+ metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
+ KeyMatcher* keyMatcher = metric->add_dimension();
+ keyMatcher->set_key(UID_PROCESS_STATE_UID_KEY);
+ // Count process state changes, slice by uid, while SCREEN_IS_OFF
+ metric = config.add_count_metric();
+ metric->set_metric_id(3);
+ metric->set_what("PROCESS_STATE_CHANGE");
+ metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
+ keyMatcher = metric->add_dimension();
+ keyMatcher->set_key(UID_PROCESS_STATE_UID_KEY);
+ metric->set_condition("SCREEN_IS_OFF");
+
+ // Count wake lock, slice by uid, while SCREEN_IS_OFF and app in background
+ metric = config.add_count_metric();
+ metric->set_metric_id(4);
+ metric->set_what("APP_GET_WL");
+ metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
+ keyMatcher = metric->add_dimension();
+ keyMatcher->set_key(WAKE_LOCK_UID_KEY_ID);
+ metric->set_condition("APP_IS_BACKGROUND_AND_SCREEN_ON");
+ EventConditionLink* link = metric->add_links();
+ link->set_condition("APP_IS_BACKGROUND");
+ link->add_key_in_main()->set_key(WAKE_LOCK_UID_KEY_ID);
+ link->add_key_in_condition()->set_key(APP_USAGE_UID_KEY_ID);
+
+ // Duration of an app holding wl, while screen on and app in background
+ DurationMetric* durationMetric = config.add_duration_metric();
+ durationMetric->set_metric_id(5);
+ durationMetric->set_start("APP_GET_WL");
+ durationMetric->set_stop("APP_RELEASE_WL");
+ durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
+ durationMetric->set_type(DurationMetric_AggregationType_DURATION_SUM);
+ keyMatcher = durationMetric->add_dimension();
+ keyMatcher->set_key(WAKE_LOCK_UID_KEY_ID);
+ durationMetric->set_predicate("APP_IS_BACKGROUND_AND_SCREEN_ON");
+ link = durationMetric->add_links();
+ link->set_condition("APP_IS_BACKGROUND");
+ link->add_key_in_main()->set_key(WAKE_LOCK_UID_KEY_ID);
+ link->add_key_in_condition()->set_key(APP_USAGE_UID_KEY_ID);
+
+ // Event matchers............
LogEntryMatcher* eventMatcher = config.add_log_entry_matcher();
- eventMatcher->set_name("SCREEN_IS_ON");
-
+ eventMatcher->set_name("SCREEN_TURNED_ON");
SimpleLogEntryMatcher* simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
- simpleLogEntryMatcher->add_tag(2 /*SCREEN_STATE_CHANGE*/);
- simpleLogEntryMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
- 1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
- simpleLogEntryMatcher->mutable_key_value_matcher(0)->set_eq_int(
- 2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/);
+ simpleLogEntryMatcher->set_tag(SCREEN_EVENT_TAG_ID);
+ KeyValueMatcher* keyValueMatcher = simpleLogEntryMatcher->add_key_value_matcher();
+ keyValueMatcher->mutable_key_matcher()->set_key(SCREEN_EVENT_STATE_KEY);
+ keyValueMatcher->set_eq_int(SCREEN_EVENT_ON_VALUE);
eventMatcher = config.add_log_entry_matcher();
- eventMatcher->set_name("SCREEN_IS_OFF");
-
+ eventMatcher->set_name("SCREEN_TURNED_OFF");
simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
- simpleLogEntryMatcher->add_tag(2 /*SCREEN_STATE_CHANGE*/);
- simpleLogEntryMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
- 1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
- simpleLogEntryMatcher->mutable_key_value_matcher(0)->set_eq_int(
- 1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_OFF*/);
+ simpleLogEntryMatcher->set_tag(SCREEN_EVENT_TAG_ID);
+ keyValueMatcher = simpleLogEntryMatcher->add_key_value_matcher();
+ keyValueMatcher->mutable_key_matcher()->set_key(SCREEN_EVENT_STATE_KEY);
+ keyValueMatcher->set_eq_int(SCREEN_EVENT_OFF_VALUE);
- LogEntryMatcher* procEventMatcher = config.add_log_entry_matcher();
- procEventMatcher->set_name("PHOTO_CRASH");
+ eventMatcher = config.add_log_entry_matcher();
+ eventMatcher->set_name("PROCESS_STATE_CHANGE");
+ simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
+ simpleLogEntryMatcher->set_tag(UID_PROCESS_STATE_TAG_ID);
- SimpleLogEntryMatcher* simpleLogMatcher2 = procEventMatcher->mutable_simple_log_entry_matcher();
- simpleLogMatcher2->add_tag(1112 /*PROCESS_STATE_CHANGE*/);
- KeyValueMatcher* keyValueMatcher = simpleLogMatcher2->add_key_value_matcher();
- keyValueMatcher->mutable_key_matcher()->set_key(1002 /*pkg*/);
- keyValueMatcher->set_eq_string(
- "com.google.android.apps.photos" /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/);
+ eventMatcher = config.add_log_entry_matcher();
+ eventMatcher->set_name("APP_GOES_BACKGROUND");
+ simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
+ simpleLogEntryMatcher->set_tag(APP_USAGE_ID);
+ keyValueMatcher = simpleLogEntryMatcher->add_key_value_matcher();
+ keyValueMatcher->mutable_key_matcher()->set_key(APP_USAGE_STATE_KEY);
+ keyValueMatcher->set_eq_int(APP_USAGE_BACKGROUND);
- keyValueMatcher = simpleLogMatcher2->add_key_value_matcher();
- keyValueMatcher->mutable_key_matcher()->set_key(1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
- keyValueMatcher->set_eq_int(2);
+ eventMatcher = config.add_log_entry_matcher();
+ eventMatcher->set_name("APP_GOES_FOREGROUND");
+ simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
+ simpleLogEntryMatcher->set_tag(APP_USAGE_ID);
+ keyValueMatcher = simpleLogEntryMatcher->add_key_value_matcher();
+ keyValueMatcher->mutable_key_matcher()->set_key(APP_USAGE_STATE_KEY);
+ keyValueMatcher->set_eq_int(APP_USAGE_FOREGROUND);
- procEventMatcher = config.add_log_entry_matcher();
- procEventMatcher->set_name("PHOTO_START");
+ eventMatcher = config.add_log_entry_matcher();
+ eventMatcher->set_name("APP_GET_WL");
+ simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
+ simpleLogEntryMatcher->set_tag(WAKE_LOCK_TAG_ID);
+ keyValueMatcher = simpleLogEntryMatcher->add_key_value_matcher();
+ keyValueMatcher->mutable_key_matcher()->set_key(WAKE_LOCK_STATE_KEY);
+ keyValueMatcher->set_eq_int(WAKE_LOCK_ACQUIRE_VALUE);
- simpleLogMatcher2 = procEventMatcher->mutable_simple_log_entry_matcher();
- simpleLogMatcher2->add_tag(1112 /*PROCESS_STATE_CHANGE*/);
- keyValueMatcher = simpleLogMatcher2->add_key_value_matcher();
- keyValueMatcher->mutable_key_matcher()->set_key(1002 /*pkg*/);
- keyValueMatcher->set_eq_string(
- "com.google.android.apps.photos" /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/);
+ eventMatcher = config.add_log_entry_matcher();
+ eventMatcher->set_name("APP_RELEASE_WL");
+ simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
+ simpleLogEntryMatcher->set_tag(WAKE_LOCK_TAG_ID);
+ keyValueMatcher = simpleLogEntryMatcher->add_key_value_matcher();
+ keyValueMatcher->mutable_key_matcher()->set_key(WAKE_LOCK_STATE_KEY);
+ keyValueMatcher->set_eq_int(WAKE_LOCK_RELEASE_VALUE);
- keyValueMatcher = simpleLogMatcher2->add_key_value_matcher();
- keyValueMatcher->mutable_key_matcher()->set_key(1 /*STATE*/);
- keyValueMatcher->set_eq_int(1);
-
- procEventMatcher = config.add_log_entry_matcher();
- procEventMatcher->set_name("PHOTO_PROCESS_STATE_CHANGE");
- LogEntryMatcher_Combination* combinationMatcher = procEventMatcher->mutable_combination();
- combinationMatcher->set_operation(LogicalOperation::OR);
- combinationMatcher->add_matcher("PHOTO_START");
- combinationMatcher->add_matcher("PHOTO_CRASH");
-
- procEventMatcher = config.add_log_entry_matcher();
- procEventMatcher->set_name("CHROME_CRASH");
-
- simpleLogMatcher2 = procEventMatcher->mutable_simple_log_entry_matcher();
- simpleLogMatcher2->add_tag(1112 /*PROCESS_STATE_CHANGE*/);
- keyValueMatcher = simpleLogMatcher2->add_key_value_matcher();
- keyValueMatcher->mutable_key_matcher()->set_key(1002 /*pkg*/);
- keyValueMatcher->set_eq_string(
- "com.android.chrome" /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/);
-
- keyValueMatcher = simpleLogMatcher2->add_key_value_matcher();
- keyValueMatcher->mutable_key_matcher()->set_key(1 /*STATE*/);
- keyValueMatcher->set_eq_int(2);
-
- procEventMatcher = config.add_log_entry_matcher();
- procEventMatcher->set_name("PHOTO_CHANGE_OR_CHROME_CRASH");
- combinationMatcher = procEventMatcher->mutable_combination();
- combinationMatcher->set_operation(LogicalOperation::OR);
- combinationMatcher->add_matcher("PHOTO_PROCESS_STATE_CHANGE");
- combinationMatcher->add_matcher("CHROME_CRASH");
-
+ // Conditions.............
Condition* condition = config.add_condition();
condition->set_name("SCREEN_IS_ON");
SimpleCondition* simpleCondition = condition->mutable_simple_condition();
- simpleCondition->set_start("SCREEN_IS_ON");
- simpleCondition->set_stop("SCREEN_IS_OFF");
-
- condition = config.add_condition();
- condition->set_name("PHOTO_STARTED");
-
- simpleCondition = condition->mutable_simple_condition();
- simpleCondition->set_start("PHOTO_START");
- simpleCondition->set_stop("PHOTO_CRASH");
+ simpleCondition->set_start("SCREEN_TURNED_ON");
+ simpleCondition->set_stop("SCREEN_TURNED_OFF");
condition = config.add_condition();
condition->set_name("SCREEN_IS_OFF");
-
simpleCondition = condition->mutable_simple_condition();
- simpleCondition->set_start("SCREEN_IS_OFF");
- simpleCondition->set_stop("SCREEN_IS_ON");
+ simpleCondition->set_start("SCREEN_TURNED_OFF");
+ simpleCondition->set_stop("SCREEN_TURNED_ON");
condition = config.add_condition();
- condition->set_name("SCREEN_IS_EITHER_ON_OFF");
-
- Condition_Combination* combination = condition->mutable_combination();
- combination->set_operation(LogicalOperation::OR);
- combination->add_condition("SCREEN_IS_ON");
- combination->add_condition("SCREEN_IS_OFF");
+ condition->set_name("APP_IS_BACKGROUND");
+ simpleCondition = condition->mutable_simple_condition();
+ simpleCondition->set_start("APP_GOES_BACKGROUND");
+ simpleCondition->set_stop("APP_GOES_FOREGROUND");
condition = config.add_condition();
- condition->set_name("SCREEN_IS_NEITHER_ON_OFF");
-
- combination = condition->mutable_combination();
- combination->set_operation(LogicalOperation::NOR);
- combination->add_condition("SCREEN_IS_ON");
- combination->add_condition("SCREEN_IS_OFF");
+ condition->set_name("APP_IS_BACKGROUND_AND_SCREEN_ON");
+ Condition_Combination* combination_condition = condition->mutable_combination();
+ combination_condition->set_operation(LogicalOperation::AND);
+ combination_condition->add_condition("APP_IS_BACKGROUND");
+ combination_condition->add_condition("SCREEN_IS_ON");
return config;
}
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index 9fa2baf..032b4b8 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -150,6 +150,29 @@
}
}
+KeyValuePair LogEvent::GetKeyValueProto(size_t key) const {
+ KeyValuePair pair;
+ pair.set_key(key);
+ // If the value is not valid, return the KeyValuePair without assigning the value.
+ // Caller can detect the error by checking the enum for "one of" proto type.
+ if (key < 1 || (key - 1) >= mElements.size()) {
+ return pair;
+ }
+ key--;
+
+ const android_log_list_element& elem = mElements[key];
+ if (elem.type == EVENT_TYPE_INT) {
+ pair.set_value_int(elem.data.int32);
+ } else if (elem.type == EVENT_TYPE_LONG) {
+ pair.set_value_int(elem.data.int64);
+ } else if (elem.type == EVENT_TYPE_STRING) {
+ pair.set_value_str(elem.data.string);
+ } else if (elem.type == EVENT_TYPE_FLOAT) {
+ pair.set_value_float(elem.data.float32);
+ }
+ return pair;
+}
+
string LogEvent::ToString() const {
ostringstream result;
result << "{ " << mTimestampNs << " (" << mTagId << ")";
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index b523201..464afca 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -80,6 +80,11 @@
*/
void ToProto(EventMetricData* out) const;
+ /*
+ * Get a KeyValuePair proto object.
+ */
+ KeyValuePair GetKeyValueProto(size_t key) const;
+
private:
/**
* Don't copy, it's slower. If we really need this we can add it but let's try to
diff --git a/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp b/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp
index 71078ea..b2c88a0 100644
--- a/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp
+++ b/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#define DEBUG true // STOPSHIP if true
+#define DEBUG false // STOPSHIP if true
#include "Log.h"
#include "SimpleLogMatchingTracker.h"
@@ -34,10 +34,12 @@
SimpleLogMatchingTracker::SimpleLogMatchingTracker(const string& name, const int index,
const SimpleLogEntryMatcher& matcher)
: LogMatchingTracker(name, index), mMatcher(matcher) {
- for (int i = 0; i < matcher.tag_size(); i++) {
- mTagIds.insert(matcher.tag(i));
+ if (!matcher.has_tag()) {
+ mInitialized = false;
+ } else {
+ mTagIds.insert(matcher.tag());
+ mInitialized = true;
}
- mInitialized = true;
}
SimpleLogMatchingTracker::~SimpleLogMatchingTracker() {
@@ -48,7 +50,7 @@
const unordered_map<string, int>& matcherMap,
vector<bool>& stack) {
// no need to do anything.
- return true;
+ return mInitialized;
}
void SimpleLogMatchingTracker::onLogEvent(const LogEvent& event,
diff --git a/cmds/statsd/src/matchers/matcher_util.cpp b/cmds/statsd/src/matchers/matcher_util.cpp
index 1c1e3c7..6aa2211 100644
--- a/cmds/statsd/src/matchers/matcher_util.cpp
+++ b/cmds/statsd/src/matchers/matcher_util.cpp
@@ -30,9 +30,9 @@
#include <sstream>
#include <unordered_map>
+using std::ostringstream;
using std::set;
using std::string;
-using std::ostringstream;
using std::unordered_map;
using std::vector;
@@ -91,109 +91,103 @@
bool matchesSimple(const SimpleLogEntryMatcher& simpleMatcher, const LogEvent& event) {
const int tagId = event.GetTagId();
- /*
- const unordered_map<int, long>& intMap = event.intMap;
- const unordered_map<int, string>& strMap = event.strMap;
- const unordered_map<int, float>& floatMap = event.floatMap;
- const unordered_map<int, bool>& boolMap = event.boolMap;
- */
- for (int i = 0; i < simpleMatcher.tag_size(); i++) {
- if (simpleMatcher.tag(i) != tagId) {
- continue;
- }
+ if (simpleMatcher.tag() != tagId) {
+ return false;
+ }
+ // now see if this event is interesting to us -- matches ALL the matchers
+ // defined in the metrics.
+ bool allMatched = true;
+ for (int j = 0; allMatched && j < simpleMatcher.key_value_matcher_size(); j++) {
+ auto cur = simpleMatcher.key_value_matcher(j);
- // TODO Is this right? Shouldn't this second loop be outside the outer loop?
- // If I understand correctly, the event matches if one of the tags match,
- // and ALL of the key-value matchers match. --joeo
+ // TODO: Check if this key is a magic key (eg package name).
+ // TODO: Maybe make packages a different type in the config?
+ int key = cur.key_matcher().key();
- // now see if this event is interesting to us -- matches ALL the matchers
- // defined in the metrics.
- bool allMatched = true;
- for (int j = 0; allMatched && j < simpleMatcher.key_value_matcher_size(); j++) {
- auto cur = simpleMatcher.key_value_matcher(j);
-
- // TODO: Check if this key is a magic key (eg package name).
- // TODO: Maybe make packages a different type in the config?
- int key = cur.key_matcher().key();
-
- const KeyValueMatcher::ValueMatcherCase matcherCase = cur.value_matcher_case();
- if (matcherCase == KeyValueMatcher::ValueMatcherCase::kEqString) {
- // String fields
- status_t err = NO_ERROR;
- const char* val = event.GetString(key, &err);
- if (err == NO_ERROR && val != NULL) {
- if (!(cur.eq_string() == val)) {
- allMatched = false;
- }
+ const KeyValueMatcher::ValueMatcherCase matcherCase = cur.value_matcher_case();
+ if (matcherCase == KeyValueMatcher::ValueMatcherCase::kEqString) {
+ // String fields
+ status_t err = NO_ERROR;
+ const char* val = event.GetString(key, &err);
+ if (err == NO_ERROR && val != NULL) {
+ if (!(cur.eq_string() == val)) {
+ allMatched = false;
}
- } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kEqInt
- || matcherCase == KeyValueMatcher::ValueMatcherCase::kLtInt
- || matcherCase == KeyValueMatcher::ValueMatcherCase::kGtInt
- || matcherCase == KeyValueMatcher::ValueMatcherCase::kLteInt
- || matcherCase == KeyValueMatcher::ValueMatcherCase::kGteInt) {
- // Integer fields
- status_t err = NO_ERROR;
- int64_t val = event.GetLong(key, &err);
- if (err == NO_ERROR) {
- if (matcherCase == KeyValueMatcher::ValueMatcherCase::kEqInt) {
- if (!(val == cur.eq_int())) {
- allMatched = false;
- }
- } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kLtInt) {
- if (!(val < cur.lt_int())) {
- allMatched = false;
- }
- } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kGtInt) {
- if (!(val > cur.gt_int())) {
- allMatched = false;
- }
- } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kLteInt) {
- if (!(val <= cur.lte_int())) {
- allMatched = false;
- }
- } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kGteInt) {
- if (!(val >= cur.gte_int())) {
- allMatched = false;
- }
- }
- }
- break;
- } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kEqBool) {
- // Boolean fields
- status_t err = NO_ERROR;
- bool val = event.GetBool(key, &err);
- if (err == NO_ERROR) {
- if (!(cur.eq_bool() == val)) {
- allMatched = false;
- }
- }
- } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kLtFloat
- || matcherCase == KeyValueMatcher::ValueMatcherCase::kGtFloat) {
- // Float fields
- status_t err = NO_ERROR;
- bool val = event.GetFloat(key, &err);
- if (err == NO_ERROR) {
- if (matcherCase == KeyValueMatcher::ValueMatcherCase::kLtFloat) {
- if (!(cur.lt_float() <= val)) {
- allMatched = false;
- }
- } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kGtFloat) {
- if (!(cur.gt_float() >= val)) {
- allMatched = false;
- }
- }
- }
- } else {
- // If value matcher is not present, assume that we match.
}
- }
-
- if (allMatched) {
- return true;
+ } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kEqInt ||
+ matcherCase == KeyValueMatcher::ValueMatcherCase::kLtInt ||
+ matcherCase == KeyValueMatcher::ValueMatcherCase::kGtInt ||
+ matcherCase == KeyValueMatcher::ValueMatcherCase::kLteInt ||
+ matcherCase == KeyValueMatcher::ValueMatcherCase::kGteInt) {
+ // Integer fields
+ status_t err = NO_ERROR;
+ int64_t val = event.GetLong(key, &err);
+ if (err == NO_ERROR) {
+ if (matcherCase == KeyValueMatcher::ValueMatcherCase::kEqInt) {
+ if (!(val == cur.eq_int())) {
+ allMatched = false;
+ }
+ } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kLtInt) {
+ if (!(val < cur.lt_int())) {
+ allMatched = false;
+ }
+ } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kGtInt) {
+ if (!(val > cur.gt_int())) {
+ allMatched = false;
+ }
+ } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kLteInt) {
+ if (!(val <= cur.lte_int())) {
+ allMatched = false;
+ }
+ } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kGteInt) {
+ if (!(val >= cur.gte_int())) {
+ allMatched = false;
+ }
+ }
+ }
+ break;
+ } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kEqBool) {
+ // Boolean fields
+ status_t err = NO_ERROR;
+ bool val = event.GetBool(key, &err);
+ if (err == NO_ERROR) {
+ if (!(cur.eq_bool() == val)) {
+ allMatched = false;
+ }
+ }
+ } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kLtFloat ||
+ matcherCase == KeyValueMatcher::ValueMatcherCase::kGtFloat) {
+ // Float fields
+ status_t err = NO_ERROR;
+ bool val = event.GetFloat(key, &err);
+ if (err == NO_ERROR) {
+ if (matcherCase == KeyValueMatcher::ValueMatcherCase::kLtFloat) {
+ if (!(cur.lt_float() <= val)) {
+ allMatched = false;
+ }
+ } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kGtFloat) {
+ if (!(cur.gt_float() >= val)) {
+ allMatched = false;
+ }
+ }
+ }
+ } else {
+ // If value matcher is not present, assume that we match.
}
}
- return false;
+ return allMatched;
+}
+
+vector<KeyValuePair> getDimensionKey(const LogEvent& event,
+ const std::vector<KeyMatcher>& dimensions) {
+ vector<KeyValuePair> key;
+ key.reserve(dimensions.size());
+ for (const KeyMatcher& dimension : dimensions) {
+ KeyValuePair k = event.GetKeyValueProto(dimension.key());
+ key.push_back(k);
+ }
+ return key;
}
} // namespace statsd
diff --git a/cmds/statsd/src/matchers/matcher_util.h b/cmds/statsd/src/matchers/matcher_util.h
index 3a5925c..4ea6f0b 100644
--- a/cmds/statsd/src/matchers/matcher_util.h
+++ b/cmds/statsd/src/matchers/matcher_util.h
@@ -27,6 +27,7 @@
#include <vector>
#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "stats_util.h"
namespace android {
namespace os {
@@ -43,6 +44,9 @@
bool matchesSimple(const SimpleLogEntryMatcher& simpleMatcher, const LogEvent& wrapper);
+std::vector<KeyValuePair> getDimensionKey(const LogEvent& event,
+ const std::vector<KeyMatcher>& dimensions);
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp
index 7df62fb..1f07914 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp
@@ -17,35 +17,49 @@
#define DEBUG true // STOPSHIP if true
#include "Log.h"
-#include "CountMetricProducer.h"
#include "CountAnomalyTracker.h"
+#include "CountMetricProducer.h"
+#include "stats_util.h"
#include <cutils/log.h>
#include <limits.h>
#include <stdlib.h>
+using std::map;
+using std::string;
using std::unordered_map;
+using std::vector;
namespace android {
namespace os {
namespace statsd {
-CountMetricProducer::CountMetricProducer(const CountMetric& metric, const bool hasCondition)
- : mMetric(metric),
- mStartTime(time(nullptr)),
- mCounter(0),
- mCurrentBucketStartTime(mStartTime),
+// TODO: add back AnomalyTracker.
+CountMetricProducer::CountMetricProducer(const CountMetric& metric, const int conditionIndex,
+ const sp<ConditionWizard>& wizard)
+ // TODO: Pass in the start time from MetricsManager, instead of calling time() here.
+ : MetricProducer((time(nullptr) * NANO_SECONDS_IN_A_SECOND), conditionIndex, wizard),
+ mMetric(metric),
// TODO: read mAnomalyTracker parameters from config file.
- mAnomalyTracker(6, 10),
- mCondition(hasCondition ? ConditionState::kUnknown : ConditionState::kTrue) {
+ mAnomalyTracker(6, 10) {
// TODO: evaluate initial conditions. and set mConditionMet.
if (metric.has_bucket() && metric.bucket().has_bucket_size_millis()) {
- mBucketSize_sec = metric.bucket().bucket_size_millis() / 1000;
+ mBucketSizeNs = metric.bucket().bucket_size_millis() * 1000 * 1000;
} else {
- mBucketSize_sec = LONG_MAX;
+ mBucketSizeNs = LLONG_MAX;
}
- VLOG("created. bucket size %lu start_time: %lu", mBucketSize_sec, mStartTime);
+ // TODO: use UidMap if uid->pkg_name is required
+ mDimension.insert(mDimension.begin(), metric.dimension().begin(), metric.dimension().end());
+
+ if (metric.links().size() > 0) {
+ mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(),
+ metric.links().end());
+ mConditionSliced = true;
+ }
+
+ VLOG("metric %lld created. bucket size %lld start_time: %lld", metric.metric_id(),
+ (long long)mBucketSizeNs, (long long)mStartTimeNs);
}
CountMetricProducer::~CountMetricProducer() {
@@ -57,54 +71,146 @@
// DropboxWriter.
}
-void CountMetricProducer::onDumpReport() {
- VLOG("dump report now...");
+static void addSlicedCounterToReport(StatsLogReport_CountMetricDataWrapper& wrapper,
+ const vector<KeyValuePair>& key,
+ const vector<CountBucketInfo>& buckets) {
+ CountMetricData* data = wrapper.add_data();
+ for (const auto& kv : key) {
+ data->add_dimension()->CopyFrom(kv);
+ }
+ for (const auto& bucket : buckets) {
+ data->add_bucket_info()->CopyFrom(bucket);
+ VLOG("\t bucket [%lld - %lld] count: %lld", bucket.start_bucket_nanos(),
+ bucket.end_bucket_nanos(), bucket.count());
+ }
+}
+
+void CountMetricProducer::onSlicedConditionMayChange() {
+ VLOG("Metric %lld onSlicedConditionMayChange", mMetric.metric_id());
+}
+
+StatsLogReport CountMetricProducer::onDumpReport() {
+ VLOG("metric %lld dump report now...", mMetric.metric_id());
+
+ StatsLogReport report;
+ report.set_metric_id(mMetric.metric_id());
+ report.set_start_report_nanos(mStartTimeNs);
+
+ // Dump current bucket if it's stale.
+ // If current bucket is still on-going, don't force dump current bucket.
+ // In finish(), We can force dump current bucket.
+ flushCounterIfNeeded(time(nullptr) * NANO_SECONDS_IN_A_SECOND);
+ report.set_end_report_nanos(mCurrentBucketStartTimeNs);
+
+ StatsLogReport_CountMetricDataWrapper* wrapper = report.mutable_count_metrics();
+
+ for (const auto& pair : mPastBuckets) {
+ const HashableDimensionKey& hashableKey = pair.first;
+ auto it = mDimensionKeyMap.find(hashableKey);
+ if (it == mDimensionKeyMap.end()) {
+ ALOGE("Dimension key %s not found?!?! skip...", hashableKey.c_str());
+ continue;
+ }
+
+ VLOG(" dimension key %s", hashableKey.c_str());
+ addSlicedCounterToReport(*wrapper, it->second, pair.second);
+ }
+ return report;
+ // TODO: Clear mPastBuckets, mDimensionKeyMap once the report is dumped.
}
void CountMetricProducer::onConditionChanged(const bool conditionMet) {
- VLOG("onConditionChanged");
+ VLOG("Metric %lld onConditionChanged", mMetric.metric_id());
mCondition = conditionMet;
}
-void CountMetricProducer::onMatchedLogEvent(const LogEvent& event) {
- time_t eventTime = event.GetTimestampNs() / 1000000000;
-
+void CountMetricProducer::onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) {
+ uint64_t eventTimeNs = event.GetTimestampNs();
// this is old event, maybe statsd restarted?
- if (eventTime < mStartTime) {
+ if (eventTimeNs < mStartTimeNs) {
return;
}
- if (mCondition == ConditionState::kTrue) {
- flushCounterIfNeeded(eventTime);
- mCounter++;
- mAnomalyTracker.checkAnomaly(mCounter);
- VLOG("metric %lld count %d", mMetric.metric_id(), mCounter);
+ flushCounterIfNeeded(eventTimeNs);
+
+ if (mConditionSliced) {
+ map<string, HashableDimensionKey> conditionKeys;
+ for (const auto& link : mConditionLinks) {
+ VLOG("Condition link key_in_main size %d", link.key_in_main_size());
+ HashableDimensionKey conditionKey = getDimensionKeyForCondition(event, link);
+ conditionKeys[link.condition()] = conditionKey;
+ }
+ if (mWizard->query(mConditionTrackerIndex, conditionKeys) != ConditionState::kTrue) {
+ VLOG("metric %lld sliced condition not met", mMetric.metric_id());
+ return;
+ }
+ } else {
+ if (!mCondition) {
+ VLOG("metric %lld condition not met", mMetric.metric_id());
+ return;
+ }
}
+
+ HashableDimensionKey hashableKey;
+
+ if (mDimension.size() > 0) {
+ vector<KeyValuePair> key = getDimensionKey(event, mDimension);
+ hashableKey = getHashableKey(key);
+ // Add the HashableDimensionKey->vector<KeyValuePair> to the map, because StatsLogReport
+ // expects vector<KeyValuePair>.
+ if (mDimensionKeyMap.find(hashableKey) == mDimensionKeyMap.end()) {
+ mDimensionKeyMap[hashableKey] = key;
+ }
+ } else {
+ hashableKey = DEFAULT_DIMENSION_KEY;
+ }
+
+ auto it = mCurrentSlicedCounter.find(hashableKey);
+
+ if (it == mCurrentSlicedCounter.end()) {
+ // create a counter for the new key
+ mCurrentSlicedCounter[hashableKey] = 1;
+
+ } else {
+ // increment the existing value
+ auto& count = it->second;
+ count++;
+ }
+
+ VLOG("metric %lld %s->%d", mMetric.metric_id(), hashableKey.c_str(),
+ mCurrentSlicedCounter[hashableKey]);
}
-// When a new matched event comes in, we check if it falls into the current
-// bucket. And flush the counter to the StatsLogReport and adjust the bucket if
-// needed.
-void CountMetricProducer::flushCounterIfNeeded(const time_t& eventTime) {
- if (mCurrentBucketStartTime + mBucketSize_sec > eventTime) {
+// 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::flushCounterIfNeeded(const uint64_t eventTimeNs) {
+ if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTimeNs) {
return;
}
- // TODO: add a KeyValuePair to StatsLogReport.
- ALOGD("%lld: dump counter %d", mMetric.metric_id(), mCounter);
-
// adjust the bucket start time
- time_t numBucketsForward = (eventTime - mCurrentBucketStartTime)
- / mBucketSize_sec;
+ int64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
- mCurrentBucketStartTime = mCurrentBucketStartTime +
- (numBucketsForward) * mBucketSize_sec;
+ CountBucketInfo info;
+ info.set_start_bucket_nanos(mCurrentBucketStartTimeNs);
+ info.set_end_bucket_nanos(mCurrentBucketStartTimeNs + mBucketSizeNs);
- // reset counter
- mAnomalyTracker.addPastBucket(mCounter, numBucketsForward);
- mCounter = 0;
+ for (const auto& counter : mCurrentSlicedCounter) {
+ info.set_count(counter.second);
+ // it will auto create new vector of CountbucketInfo if the key is not found.
+ auto& bucketList = mPastBuckets[counter.first];
+ bucketList.push_back(info);
- VLOG("%lld: new bucket start time: %lu", mMetric.metric_id(), mCurrentBucketStartTime);
+ VLOG("metric %lld, dump key value: %s -> %d", mMetric.metric_id(), counter.first.c_str(),
+ counter.second);
+ }
+
+ // Reset counters
+ mCurrentSlicedCounter.clear();
+
+ mCurrentBucketStartTimeNs = mCurrentBucketStartTimeNs + numBucketsForward * mBucketSizeNs;
+ VLOG("metric %lld: new bucket start time: %lld", mMetric.metric_id(),
+ (long long)mCurrentBucketStartTimeNs);
}
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h
index 94abd62..f0d6025 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.h
+++ b/cmds/statsd/src/metrics/CountMetricProducer.h
@@ -19,12 +19,13 @@
#include <unordered_map>
-#include "CountAnomalyTracker.h"
#include "../condition/ConditionTracker.h"
#include "../matchers/matcher_util.h"
+#include "CountAnomalyTracker.h"
#include "MetricProducer.h"
#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "stats_util.h"
using namespace std;
@@ -34,38 +35,37 @@
class CountMetricProducer : public MetricProducer {
public:
- CountMetricProducer(const CountMetric& countMetric, const bool hasCondition);
+ // TODO: Pass in the start time from MetricsManager, it should be consistent for all metrics.
+ CountMetricProducer(const CountMetric& countMetric, const int conditionIndex,
+ const sp<ConditionWizard>& wizard);
virtual ~CountMetricProducer();
- void onMatchedLogEvent(const LogEvent& event) override;
+ void onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) override;
void onConditionChanged(const bool conditionMet) override;
void finish() override;
- void onDumpReport() override;
+ StatsLogReport onDumpReport() override;
+
+ void onSlicedConditionMayChange() override;
// TODO: Implement this later.
- virtual void notifyAppUpgrade(const string& apk, const int uid, const int version) override {};
+ virtual void notifyAppUpgrade(const string& apk, const int uid, const int version) override{};
private:
const CountMetric mMetric;
- const time_t mStartTime;
- // TODO: Add dimensions.
- // Counter value for the current bucket.
- int mCounter;
-
- time_t mCurrentBucketStartTime;
-
- long mBucketSize_sec;
-
CountAnomalyTracker mAnomalyTracker;
- bool mCondition;
+ // Save the past buckets and we can clear when the StatsLogReport is dumped.
+ std::unordered_map<HashableDimensionKey, std::vector<CountBucketInfo>> mPastBuckets;
- void flushCounterIfNeeded(const time_t& newEventTime);
+ // The current bucket.
+ std::unordered_map<HashableDimensionKey, int> mCurrentSlicedCounter;
+
+ void flushCounterIfNeeded(const uint64_t newEventTime);
};
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
new file mode 100644
index 0000000..aa597f4
--- /dev/null
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -0,0 +1,384 @@
+/*
+ * 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 true
+#include "DurationMetricProducer.h"
+#include "Log.h"
+#include "stats_util.h"
+
+#include <cutils/log.h>
+#include <limits.h>
+#include <stdlib.h>
+
+using std::string;
+using std::unordered_map;
+using std::vector;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+DurationMetricProducer::DurationMetricProducer(const DurationMetric& metric,
+ const int conditionIndex, const size_t startIndex,
+ const size_t stopIndex, const size_t stopAllIndex,
+ const sp<ConditionWizard>& wizard)
+ // TODO: Pass in the start time from MetricsManager, instead of calling time() here.
+ : MetricProducer(time(nullptr) * NANO_SECONDS_IN_A_SECOND, conditionIndex, wizard),
+ mMetric(metric),
+ mStartIndex(startIndex),
+ mStopIndex(stopIndex),
+ mStopAllIndex(stopAllIndex) {
+ // TODO: The following boiler plate code appears in all MetricProducers, but we can't abstract
+ // them in the base class, because the proto generated CountMetric, and DurationMetric are
+ // not related. Maybe we should add a template in the future??
+ if (metric.has_bucket() && metric.bucket().has_bucket_size_millis()) {
+ mBucketSizeNs = metric.bucket().bucket_size_millis() * 1000000;
+ } else {
+ mBucketSizeNs = LLONG_MAX;
+ }
+
+ // TODO: use UidMap if uid->pkg_name is required
+ mDimension.insert(mDimension.begin(), metric.dimension().begin(), metric.dimension().end());
+
+ if (metric.links().size() > 0) {
+ mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(),
+ metric.links().end());
+ mConditionSliced = true;
+ }
+
+ VLOG("metric %lld created. bucket size %lld start_time: %lld", metric.metric_id(),
+ (long long)mBucketSizeNs, (long long)mStartTimeNs);
+}
+
+DurationMetricProducer::~DurationMetricProducer() {
+ VLOG("~DurationMetric() called");
+}
+
+void DurationMetricProducer::finish() {
+ // TODO: write the StatsLogReport to dropbox using
+ // DropboxWriter.
+}
+
+void DurationMetricProducer::onSlicedConditionMayChange() {
+ VLOG("Metric %lld onSlicedConditionMayChange", mMetric.metric_id());
+ // Now for each of the on-going event, check if the condition has changed for them.
+ for (auto& pair : mCurrentSlicedDuration) {
+ VLOG("Metric %lld current %s state: %d", mMetric.metric_id(), pair.first.c_str(),
+ pair.second.state);
+ if (pair.second.state == kStopped) {
+ continue;
+ }
+ bool conditionMet = mWizard->query(mConditionTrackerIndex, pair.second.conditionKeys) ==
+ ConditionState::kTrue;
+ VLOG("key: %s, condition: %d", pair.first.c_str(), conditionMet);
+ noteConditionChanged(pair.first, conditionMet, time(nullptr) * 1000000000);
+ }
+}
+
+void DurationMetricProducer::onConditionChanged(const bool conditionMet) {
+ VLOG("Metric %lld onConditionChanged", mMetric.metric_id());
+ mCondition = conditionMet;
+ // TODO: need to populate the condition change time from the event which triggers the condition
+ // change, instead of using current time.
+ for (auto& pair : mCurrentSlicedDuration) {
+ noteConditionChanged(pair.first, conditionMet, time(nullptr) * 1000000000);
+ }
+}
+
+static void addDurationBucketsToReport(StatsLogReport_DurationMetricDataWrapper& wrapper,
+ const vector<KeyValuePair>& key,
+ const vector<DurationBucketInfo>& buckets) {
+ DurationMetricData* data = wrapper.add_data();
+ for (const auto& kv : key) {
+ data->add_dimension()->CopyFrom(kv);
+ }
+ for (const auto& bucket : buckets) {
+ data->add_bucket_info()->CopyFrom(bucket);
+ VLOG("\t bucket [%lld - %lld] count: %lld", bucket.start_bucket_nanos(),
+ bucket.end_bucket_nanos(), bucket.duration_nanos());
+ }
+}
+
+StatsLogReport DurationMetricProducer::onDumpReport() {
+ VLOG("metric %lld dump report now...", mMetric.metric_id());
+ StatsLogReport report;
+ report.set_metric_id(mMetric.metric_id());
+ report.set_start_report_nanos(mStartTimeNs);
+ // Dump current bucket if it's stale.
+ // If current bucket is still on-going, don't force dump current bucket.
+ // In finish(), We can force dump current bucket.
+ flushDurationIfNeeded(time(nullptr) * NANO_SECONDS_IN_A_SECOND);
+ report.set_end_report_nanos(mCurrentBucketStartTimeNs);
+
+ StatsLogReport_DurationMetricDataWrapper* wrapper = report.mutable_duration_metrics();
+ for (const auto& pair : mPastBuckets) {
+ const HashableDimensionKey& hashableKey = pair.first;
+ auto it = mDimensionKeyMap.find(hashableKey);
+ if (it == mDimensionKeyMap.end()) {
+ ALOGW("Dimension key %s not found?!?! skip...", hashableKey.c_str());
+ continue;
+ }
+ VLOG(" dimension key %s", hashableKey.c_str());
+ addDurationBucketsToReport(*wrapper, it->second, pair.second);
+ }
+ return report;
+};
+
+void DurationMetricProducer::onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) {
+ if (event.GetTimestampNs() < mStartTimeNs) {
+ return;
+ }
+
+ flushDurationIfNeeded(event.GetTimestampNs());
+
+ if (matcherIndex == mStopAllIndex) {
+ noteStopAll(event.GetTimestampNs());
+ return;
+ }
+
+ HashableDimensionKey hashableKey;
+ if (mDimension.size() > 0) {
+ // hook up sliced counter with AnomalyMonitor.
+ vector<KeyValuePair> key = getDimensionKey(event, mDimension);
+ hashableKey = getHashableKey(key);
+ // Add the HashableDimensionKey->DimensionKey to the map, because StatsLogReport expects
+ // vector<KeyValuePair>.
+ if (mDimensionKeyMap.find(hashableKey) == mDimensionKeyMap.end()) {
+ mDimensionKeyMap[hashableKey] = key;
+ }
+ } else {
+ hashableKey = DEFAULT_DIMENSION_KEY;
+ }
+
+ if (mCurrentSlicedDuration.find(hashableKey) == mCurrentSlicedDuration.end() &&
+ mConditionSliced) {
+ // add the durationInfo for the current bucket.
+ auto& durationInfo = mCurrentSlicedDuration[hashableKey];
+ auto& conditionKeys = durationInfo.conditionKeys;
+ // get and cache the keys for query condition.
+ for (const auto& link : mConditionLinks) {
+ HashableDimensionKey conditionKey = getDimensionKeyForCondition(event, link);
+ conditionKeys[link.condition()] = conditionKey;
+ }
+ }
+
+ bool conditionMet;
+ if (mConditionSliced) {
+ const auto& conditionKeys = mCurrentSlicedDuration[hashableKey].conditionKeys;
+ conditionMet =
+ mWizard->query(mConditionTrackerIndex, conditionKeys) == ConditionState::kTrue;
+ } else {
+ conditionMet = mCondition;
+ }
+
+ if (matcherIndex == mStartIndex) {
+ VLOG("Metric %lld Key: %s Start, Condition %d", mMetric.metric_id(), hashableKey.c_str(),
+ conditionMet);
+ noteStart(hashableKey, conditionMet, event.GetTimestampNs());
+ } else if (matcherIndex == mStopIndex) {
+ VLOG("Metric %lld Key: %s Stop, Condition %d", mMetric.metric_id(), hashableKey.c_str(),
+ conditionMet);
+ noteStop(hashableKey, event.GetTimestampNs());
+ }
+}
+
+void DurationMetricProducer::noteConditionChanged(const HashableDimensionKey& key,
+ const bool conditionMet,
+ const uint64_t eventTime) {
+ flushDurationIfNeeded(eventTime);
+
+ auto it = mCurrentSlicedDuration.find(key);
+ if (it == mCurrentSlicedDuration.end()) {
+ return;
+ }
+
+ switch (it->second.state) {
+ case kStarted:
+ // if condition becomes false, kStarted -> kPaused. Record the current duration.
+ if (!conditionMet) {
+ it->second.state = DurationState::kPaused;
+ it->second.lastDuration =
+ updateDuration(it->second.lastDuration,
+ eventTime - it->second.lastStartTime, mMetric.type());
+ VLOG("Metric %lld Key: %s Paused because condition is false ", mMetric.metric_id(),
+ key.c_str());
+ }
+ break;
+ case kStopped:
+ // nothing to do if it's stopped.
+ break;
+ case kPaused:
+ // if condition becomes true, kPaused -> kStarted. and the start time is the condition
+ // change time.
+ if (conditionMet) {
+ it->second.state = DurationState::kStarted;
+ it->second.lastStartTime = eventTime;
+ VLOG("Metric %lld Key: %s Paused->Started", mMetric.metric_id(), key.c_str());
+ }
+ break;
+ }
+}
+
+void DurationMetricProducer::noteStart(const HashableDimensionKey& key, const bool conditionMet,
+ const uint64_t eventTime) {
+ // this will add an empty bucket for this key if it didn't exist before.
+ DurationInfo& duration = mCurrentSlicedDuration[key];
+
+ switch (duration.state) {
+ case kStarted:
+ // It's safe to do nothing here. even if condition is not true, it means we are about
+ // to receive the condition change event.
+ break;
+ case kPaused:
+ // Safe to do nothing here. kPaused is waiting for the condition change.
+ break;
+ case kStopped:
+ if (!conditionMet) {
+ // event started, but we need to wait for the condition to become true.
+ duration.state = DurationState::kPaused;
+ break;
+ }
+ duration.state = DurationState::kStarted;
+ duration.lastStartTime = eventTime;
+ break;
+ }
+}
+
+void DurationMetricProducer::noteStop(const HashableDimensionKey& key, const uint64_t eventTime) {
+ if (mCurrentSlicedDuration.find(key) == mCurrentSlicedDuration.end()) {
+ // we didn't see a start event before. do nothing.
+ return;
+ }
+ DurationInfo& duration = mCurrentSlicedDuration[key];
+
+ switch (duration.state) {
+ case DurationState::kStopped:
+ // already stopped, do nothing.
+ break;
+ case DurationState::kStarted: {
+ duration.state = DurationState::kStopped;
+ int64_t durationTime = eventTime - duration.lastStartTime;
+ VLOG("Metric %lld, key %s, Stop %lld %lld %lld", mMetric.metric_id(), key.c_str(),
+ (long long)duration.lastStartTime, (long long)eventTime, (long long)durationTime);
+ duration.lastDuration =
+ updateDuration(duration.lastDuration, durationTime, mMetric.type());
+ VLOG(" record duration: %lld ", (long long)duration.lastDuration);
+ break;
+ }
+ case DurationState::kPaused: {
+ duration.state = DurationState::kStopped;
+ break;
+ }
+ }
+}
+
+int64_t DurationMetricProducer::updateDuration(const int64_t lastDuration,
+ const int64_t durationTime,
+ const DurationMetric_AggregationType type) {
+ int64_t result = lastDuration;
+ switch (type) {
+ case DurationMetric_AggregationType_DURATION_SUM:
+ result += durationTime;
+ break;
+ case DurationMetric_AggregationType_DURATION_MAX_SPARSE:
+ if (lastDuration < durationTime) {
+ result = durationTime;
+ }
+ break;
+ case DurationMetric_AggregationType_DURATION_MIN_SPARSE:
+ if (lastDuration > durationTime) {
+ result = durationTime;
+ }
+ break;
+ }
+ return result;
+}
+
+void DurationMetricProducer::noteStopAll(const uint64_t eventTime) {
+ for (auto& duration : mCurrentSlicedDuration) {
+ noteStop(duration.first, eventTime);
+ }
+}
+
+// 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 current buckt.
+void DurationMetricProducer::flushDurationIfNeeded(const uint64_t eventTime) {
+ if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTime) {
+ return;
+ }
+
+ // adjust the bucket start time
+ int numBucketsForward = (eventTime - mCurrentBucketStartTimeNs) / mBucketSizeNs;
+
+ DurationBucketInfo info;
+ uint64_t endTime = mCurrentBucketStartTimeNs + mBucketSizeNs;
+ info.set_start_bucket_nanos(mCurrentBucketStartTimeNs);
+ info.set_end_bucket_nanos(endTime);
+
+ uint64_t oldBucketStartTimeNs = mCurrentBucketStartTimeNs;
+ mCurrentBucketStartTimeNs += (numBucketsForward)*mBucketSizeNs;
+ VLOG("Metric %lld: new bucket start time: %lld", mMetric.metric_id(),
+ (long long)mCurrentBucketStartTimeNs);
+
+ for (auto it = mCurrentSlicedDuration.begin(); it != mCurrentSlicedDuration.end(); ++it) {
+ int64_t finalDuration = it->second.lastDuration;
+ if (it->second.state == kStarted) {
+ // the event is still on-going, duration needs to be updated.
+ int64_t durationTime = endTime - it->second.lastStartTime;
+ finalDuration = updateDuration(it->second.lastDuration, durationTime, mMetric.type());
+ }
+
+ VLOG(" final duration for last bucket: %lld", (long long)finalDuration);
+
+ // Don't record empty bucket.
+ if (finalDuration != 0) {
+ info.set_duration_nanos(finalDuration);
+ // it will auto create new vector of CountbucketInfo if the key is not found.
+ auto& bucketList = mPastBuckets[it->first];
+ bucketList.push_back(info);
+ }
+
+ // if the event is still on-going, add the buckets between previous bucket and now. Because
+ // the event has been going on across all the buckets in between.
+ // |prev_bucket|...|..|...|now_bucket|
+ if (it->second.state == kStarted) {
+ for (int i = 1; i < numBucketsForward; i++) {
+ DurationBucketInfo info;
+ info.set_start_bucket_nanos(oldBucketStartTimeNs + mBucketSizeNs * i);
+ info.set_end_bucket_nanos(endTime + mBucketSizeNs * i);
+ info.set_duration_nanos(mBucketSizeNs);
+ auto& bucketList = mPastBuckets[it->first];
+ bucketList.push_back(info);
+ VLOG(" add filling bucket with duration %lld", (long long)mBucketSizeNs);
+ }
+ }
+
+ if (it->second.state == DurationState::kStopped) {
+ // No need to keep buckets for events that were stopped before. If the event starts
+ // again, we will add it back.
+ mCurrentSlicedDuration.erase(it);
+ } else {
+ // for kPaused, and kStarted event, we will keep the buckets, and reset the start time
+ // and duration.
+ it->second.lastStartTime = mCurrentBucketStartTimeNs;
+ it->second.lastDuration = 0;
+ }
+ }
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
new file mode 100644
index 0000000..44c3254
--- /dev/null
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -0,0 +1,115 @@
+/*
+ * 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 DURATION_METRIC_PRODUCER_H
+#define DURATION_METRIC_PRODUCER_H
+
+#include <unordered_map>
+
+#include "../condition/ConditionTracker.h"
+#include "../matchers/matcher_util.h"
+#include "MetricProducer.h"
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "stats_util.h"
+
+using namespace std;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+enum DurationState {
+ kStopped = 0, // The event is stopped.
+ kStarted = 1, // The event is on going.
+ kPaused = 2, // The event is started, but condition is false, clock is paused. When condition
+ // turns to true, kPaused will become kStarted.
+};
+
+// Hold duration information for current on-going bucket.
+struct DurationInfo {
+ DurationState state;
+ // most recent start time.
+ int64_t lastStartTime;
+ // existing duration in current bucket. Eventually, the duration will be aggregated in
+ // the way specified in AggregateType (Sum, Max, or Min).
+ int64_t lastDuration;
+ // cache the HashableDimensionKeys we need to query the condition for this duration event.
+ std::map<string, HashableDimensionKey> conditionKeys;
+
+ DurationInfo() : state(kStopped), lastStartTime(0), lastDuration(0){};
+};
+
+class DurationMetricProducer : public MetricProducer {
+public:
+ DurationMetricProducer(const DurationMetric& durationMetric, const int conditionIndex,
+ const size_t startIndex, const size_t stopIndex,
+ const size_t stopAllIndex, const sp<ConditionWizard>& wizard);
+
+ virtual ~DurationMetricProducer();
+
+ void onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) override;
+
+ void onConditionChanged(const bool conditionMet) override;
+
+ void finish() override;
+
+ StatsLogReport onDumpReport() override;
+
+ void onSlicedConditionMayChange() override;
+
+ // TODO: Implement this later.
+ virtual void notifyAppUpgrade(const string& apk, const int uid, const int version) override{};
+
+private:
+ const DurationMetric mMetric;
+
+ // Index of the SimpleLogEntryMatcher which defines the start.
+ const size_t mStartIndex;
+
+ // Index of the SimpleLogEntryMatcher which defines the stop.
+ const size_t mStopIndex;
+
+ // Index of the SimpleLogEntryMatcher which defines the stop all for all dimensions.
+ const size_t mStopAllIndex;
+
+ // Save the past buckets and we can clear when the StatsLogReport is dumped.
+ std::unordered_map<HashableDimensionKey, std::vector<DurationBucketInfo>> mPastBuckets;
+
+ // The current bucket.
+ std::unordered_map<HashableDimensionKey, DurationInfo> mCurrentSlicedDuration;
+
+ void flushDurationIfNeeded(const uint64_t newEventTime);
+
+ void noteStart(const HashableDimensionKey& key, const bool conditionMet,
+ const uint64_t eventTime);
+
+ void noteStop(const HashableDimensionKey& key, const uint64_t eventTime);
+
+ void noteStopAll(const uint64_t eventTime);
+
+ static int64_t updateDuration(const int64_t lastDuration, const int64_t durationTime,
+ const DurationMetric_AggregationType type);
+
+ void noteConditionChanged(const HashableDimensionKey& key, const bool conditionMet,
+ const uint64_t eventTime);
+};
+
+} // namespace statsd
+} // namespace os
+} // namespace android
+
+#endif // DURATION_METRIC_PRODUCER_H
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index 589df84..afaab648 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -17,11 +17,13 @@
#ifndef METRIC_PRODUCER_H
#define METRIC_PRODUCER_H
+#include "condition/ConditionWizard.h"
#include "matchers/matcher_util.h"
#include "packages/PackageInfoListener.h"
#include <log/logprint.h>
#include <utils/RefBase.h>
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
namespace android {
namespace os {
@@ -33,18 +35,57 @@
// be a no-op.
class MetricProducer : public virtual PackageInfoListener {
public:
+ MetricProducer(const int64_t startTimeNs, const int conditionIndex,
+ const sp<ConditionWizard>& wizard)
+ : mStartTimeNs(startTimeNs),
+ mCurrentBucketStartTimeNs(startTimeNs),
+ mCondition(conditionIndex >= 0 ? false : true),
+ mWizard(wizard),
+ mConditionTrackerIndex(conditionIndex) {
+ // reuse the same map for non-sliced metrics too. this way, we avoid too many if-else.
+ mDimensionKeyMap[DEFAULT_DIMENSION_KEY] = std::vector<KeyValuePair>();
+ };
virtual ~MetricProducer(){};
// Consume the parsed stats log entry that already matched the "what" of the metric.
- virtual void onMatchedLogEvent(const LogEvent& event) = 0;
+ virtual void onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) = 0;
virtual void onConditionChanged(const bool condition) = 0;
+ virtual void onSlicedConditionMayChange() = 0;
+
// This is called when the metric collecting is done, e.g., when there is a new configuration
// coming. MetricProducer should do the clean up, and dump existing data to dropbox.
virtual void finish() = 0;
- virtual void onDumpReport() = 0;
+ virtual StatsLogReport onDumpReport() = 0;
+
+ virtual bool isConditionSliced() const {
+ return mConditionSliced;
+ };
+
+protected:
+ const uint64_t mStartTimeNs;
+
+ uint64_t mCurrentBucketStartTimeNs;
+
+ int64_t mBucketSizeNs;
+
+ bool mCondition;
+
+ bool mConditionSliced;
+
+ sp<ConditionWizard> mWizard;
+
+ int mConditionTrackerIndex;
+
+ std::vector<KeyMatcher> mDimension; // The dimension defined in statsd_config
+
+ // Keep the map from the internal HashableDimensionKey to std::vector<KeyValuePair>
+ // that StatsLogReport wants.
+ std::unordered_map<HashableDimensionKey, std::vector<KeyValuePair>> mDimensionKeyMap;
+
+ std::vector<EventConditionLink> mConditionLinks;
};
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index 5b4ca80..c19d462 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -15,8 +15,6 @@
*/
#define DEBUG true // STOPSHIP if true
#include "Log.h"
-#define VLOG(...) \
- if (DEBUG) ALOGD(__VA_ARGS__);
#include "MetricsManager.h"
#include <log/logprint.h>
@@ -58,6 +56,17 @@
}
}
+vector<StatsLogReport> MetricsManager::onDumpReport() {
+ VLOG("=========================Metric Reports Start==========================");
+ // one StatsLogReport per MetricProduer
+ vector<StatsLogReport> reportList;
+ for (auto& metric : mAllMetricProducers) {
+ reportList.push_back(metric->onDumpReport());
+ }
+ VLOG("=========================Metric Reports End==========================");
+ return reportList;
+}
+
// Consume the stats log if it's interesting to this metric.
void MetricsManager::onLogEvent(const LogEvent& event) {
if (!mConfigValid) {
@@ -71,6 +80,7 @@
}
// Since at least one of the metrics is interested in this event, we parse it now.
+ ALOGD("%s", event.ToString().c_str());
vector<MatchingState> matcherCache(mAllLogEntryMatchers.size(), MatchingState::kNotComputed);
for (auto& matcher : mAllLogEntryMatchers) {
@@ -93,20 +103,34 @@
ConditionState::kNotEvaluated);
// A bitmap to track if a condition has changed value.
vector<bool> changedCache(mAllConditionTrackers.size(), false);
+ vector<bool> slicedChangedCache(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);
- if (changedCache[i]) {
- auto pair = mConditionToMetricMap.find(i);
- if (pair != mConditionToMetricMap.end()) {
- auto& metricList = pair->second;
- for (auto metricIndex : metricList) {
+ changedCache, slicedChangedCache);
+ }
+
+ for (size_t i = 0; i < mAllConditionTrackers.size(); i++) {
+ if (changedCache[i] == false && slicedChangedCache[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() && changedCache[i]) {
mAllMetricProducers[metricIndex]->onConditionChanged(conditionCache[i]);
+ // 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 if (mAllMetricProducers[metricIndex]->isConditionSliced() &&
+ slicedChangedCache[i]) {
+ mAllMetricProducers[metricIndex]->onSlicedConditionMayChange();
}
}
}
@@ -119,7 +143,7 @@
if (pair != mTrackerToMetricMap.end()) {
auto& metricList = pair->second;
for (const int metricIndex : metricList) {
- mAllMetricProducers[metricIndex]->onMatchedLogEvent(event);
+ mAllMetricProducers[metricIndex]->onMatchedLogEvent(i, event);
}
}
}
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index 7aca0b5..56f57d3 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -43,17 +43,19 @@
// Called when everything should wrap up. We are about to finish (e.g., new config comes).
void finish();
+ // Config source owner can call onDumpReport() to get all the metrics collected.
+ std::vector<StatsLogReport> onDumpReport();
+
private:
// All event tags that are interesting to my metrics.
std::set<int> mTagIds;
// We only store the sp of LogMatchingTracker, MetricProducer, and ConditionTracker in
// MetricManager. There are relationship between them, and the relationship are denoted by index
- // instead of poiters. The reasons for this are: (1) the relationship between them are
+ // instead of pointers. The reasons for this are: (1) the relationship between them are
// complicated, store index instead of pointers reduce the risk of A holds B's sp, and B holds
// A's sp. (2) When we evaluate matcher results, or condition results, we can quickly get the
// related results from a cache using the index.
- // TODO: using unique_ptr may be more appriopreate?
// Hold all the log entry matchers from the config.
std::vector<sp<LogMatchingTracker>> mAllLogEntryMatchers;
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index 6fdd228..23071aa 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -19,6 +19,7 @@
#include "../matchers/CombinationLogMatchingTracker.h"
#include "../matchers/SimpleLogMatchingTracker.h"
#include "CountMetricProducer.h"
+#include "DurationMetricProducer.h"
#include "stats_util.h"
using std::set;
@@ -30,11 +31,23 @@
namespace os {
namespace statsd {
+int getTrackerIndex(const string& name, const unordered_map<string, int>& logTrackerMap) {
+ auto logTrackerIt = logTrackerMap.find(name);
+ if (logTrackerIt == logTrackerMap.end()) {
+ ALOGW("cannot find the LogEventMatcher %s in config", name.c_str());
+ return MATCHER_NOT_FOUND;
+ }
+ return logTrackerIt->second;
+}
+
bool initLogTrackers(const StatsdConfig& config, unordered_map<string, int>& logTrackerMap,
vector<sp<LogMatchingTracker>>& allLogEntryMatchers, set<int>& allTagIds) {
vector<LogEntryMatcher> matcherConfigs;
+ const int logEntryMatcherCount = config.log_entry_matcher_size();
+ matcherConfigs.reserve(logEntryMatcherCount);
+ allLogEntryMatchers.reserve(logEntryMatcherCount);
- for (int i = 0; i < config.log_entry_matcher_size(); i++) {
+ for (int i = 0; i < logEntryMatcherCount; i++) {
const LogEntryMatcher& logMatcher = config.log_entry_matcher(i);
int index = allLogEntryMatchers.size();
@@ -77,8 +90,11 @@
vector<sp<ConditionTracker>>& allConditionTrackers,
unordered_map<int, std::vector<int>>& trackerToConditionMap) {
vector<Condition> conditionConfigs;
+ const int conditionTrackerCount = config.condition_size();
+ conditionConfigs.reserve(conditionTrackerCount);
+ allConditionTrackers.reserve(conditionTrackerCount);
- for (int i = 0; i < config.condition_size(); i++) {
+ for (int i = 0; i < conditionTrackerCount; i++) {
const Condition& condition = config.condition(i);
int index = allConditionTrackers.size();
switch (condition.contents_case()) {
@@ -121,9 +137,14 @@
bool initMetrics(const StatsdConfig& config, const unordered_map<string, int>& logTrackerMap,
const unordered_map<string, int>& conditionTrackerMap,
+ vector<sp<ConditionTracker>>& allConditionTrackers,
vector<sp<MetricProducer>>& allMetricProducers,
unordered_map<int, std::vector<int>>& conditionToMetricMap,
unordered_map<int, std::vector<int>>& trackerToMetricMap) {
+ sp<ConditionWizard> wizard = new ConditionWizard(allConditionTrackers);
+ const int allMetricsCount = config.count_metric_size() + config.duration_metric_size();
+ allMetricProducers.reserve(allMetricsCount);
+
// Build MetricProducers for each metric defined in config.
// (1) build CountMetricProducer
for (int i = 0; i < config.count_metric_size(); i++) {
@@ -133,36 +154,109 @@
return false;
}
+ int metricIndex = allMetricProducers.size();
+
auto logTrackerIt = logTrackerMap.find(metric.what());
if (logTrackerIt == logTrackerMap.end()) {
ALOGW("cannot find the LogEntryMatcher %s in config", metric.what().c_str());
return false;
}
+ int logTrackerIndex = logTrackerIt->second;
+ auto& metric_list = trackerToMetricMap[logTrackerIndex];
+ metric_list.push_back(metricIndex);
sp<MetricProducer> countProducer;
- int metricIndex = allMetricProducers.size();
+
if (metric.has_condition()) {
auto condition_it = conditionTrackerMap.find(metric.condition());
if (condition_it == conditionTrackerMap.end()) {
ALOGW("cannot find the Condition %s in the config", metric.condition().c_str());
return false;
}
- countProducer = new CountMetricProducer(metric, true /*has condition*/);
+
+ for (const auto& link : metric.links()) {
+ auto it = conditionTrackerMap.find(link.condition());
+ if (it == conditionTrackerMap.end()) {
+ ALOGW("cannot find the Condition %s in the config", link.condition().c_str());
+ return false;
+ }
+ allConditionTrackers[condition_it->second]->setSliced(true);
+ allConditionTrackers[it->second]->setSliced(true);
+ allConditionTrackers[it->second]->addDimensions(vector<KeyMatcher>(
+ link.key_in_condition().begin(), link.key_in_condition().end()));
+ }
+
+ countProducer = new CountMetricProducer(metric, condition_it->second, wizard);
// will create new vector if not exist before.
auto& metricList = conditionToMetricMap[condition_it->second];
metricList.push_back(metricIndex);
} else {
- countProducer = new CountMetricProducer(metric, false /*no condition*/);
+ countProducer = new CountMetricProducer(metric, -1 /*no condition*/, wizard);
}
-
- int logTrackerIndex = logTrackerIt->second;
- auto& metric_list = trackerToMetricMap[logTrackerIndex];
- metric_list.push_back(metricIndex);
allMetricProducers.push_back(countProducer);
}
- // TODO: build other types of metrics too.
+ for (int i = 0; i < config.duration_metric_size(); i++) {
+ int metricIndex = allMetricProducers.size();
+ const DurationMetric metric = config.duration_metric(i);
+ if (!metric.has_start()) {
+ ALOGW("cannot find start in DurationMetric %lld", metric.metric_id());
+ return false;
+ }
+ int trackerIndices[] = {-1, -1, -1};
+ trackerIndices[0] = getTrackerIndex(metric.start(), logTrackerMap);
+
+ if (metric.has_stop()) {
+ trackerIndices[1] = getTrackerIndex(metric.stop(), logTrackerMap);
+ }
+
+ if (metric.has_stop_all()) {
+ trackerIndices[2] = getTrackerIndex(metric.stop_all(), logTrackerMap);
+ }
+
+ for (const int& index : trackerIndices) {
+ if (index == MATCHER_NOT_FOUND) {
+ return false;
+ }
+ if (index >= 0) {
+ auto& metric_list = trackerToMetricMap[index];
+ metric_list.push_back(metricIndex);
+ }
+ }
+
+ int conditionIndex = -1;
+
+ if (metric.has_predicate()) {
+ auto condition_it = conditionTrackerMap.find(metric.predicate());
+ if (condition_it == conditionTrackerMap.end()) {
+ ALOGW("cannot find the Condition %s in the config", metric.predicate().c_str());
+ return false;
+ }
+ conditionIndex = condition_it->second;
+
+ for (const auto& link : metric.links()) {
+ auto it = conditionTrackerMap.find(link.condition());
+ if (it == conditionTrackerMap.end()) {
+ ALOGW("cannot find the Condition %s in the config", link.condition().c_str());
+ return false;
+ }
+ allConditionTrackers[condition_it->second]->setSliced(true);
+ allConditionTrackers[it->second]->setSliced(true);
+ allConditionTrackers[it->second]->addDimensions(vector<KeyMatcher>(
+ link.key_in_condition().begin(), link.key_in_condition().end()));
+ }
+
+ auto& metricList = conditionToMetricMap[conditionIndex];
+ metricList.push_back(metricIndex);
+ }
+
+ sp<MetricProducer> durationMetric =
+ new DurationMetricProducer(metric, conditionIndex, trackerIndices[0],
+ trackerIndices[1], trackerIndices[2], wizard);
+
+ allMetricProducers.push_back(durationMetric);
+ }
return true;
}
@@ -187,8 +281,8 @@
return false;
}
- if (!initMetrics(config, logTrackerMap, conditionTrackerMap, allMetricProducers,
- conditionToMetricMap, trackerToMetricMap)) {
+ if (!initMetrics(config, logTrackerMap, conditionTrackerMap, allConditionTrackers,
+ allMetricProducers, conditionToMetricMap, trackerToMetricMap)) {
ALOGE("initMetricProducers failed");
return false;
}
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.h b/cmds/statsd/src/metrics/metrics_manager_util.h
index 5f1f295..38149a6 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.h
+++ b/cmds/statsd/src/metrics/metrics_manager_util.h
@@ -59,7 +59,8 @@
const std::unordered_map<std::string, int>& logTrackerMap,
std::unordered_map<std::string, int>& conditionTrackerMap,
std::vector<sp<ConditionTracker>>& allConditionTrackers,
- std::unordered_map<int, std::vector<int>>& trackerToConditionMap);
+ std::unordered_map<int, std::vector<int>>& trackerToConditionMap,
+ std::unordered_map<int, std::vector<EventConditionLink>>& eventConditionLinks);
// Initialize MetricProducers.
// input:
@@ -71,12 +72,14 @@
// [conditionToMetricMap]: contains the mapping from condition tracker index to
// the list of MetricProducer index
// [trackerToMetricMap]: contains the mapping from log tracker to MetricProducer index.
-bool initMetrics(const StatsdConfig& config,
- const std::unordered_map<std::string, int>& logTrackerMap,
- const std::unordered_map<std::string, int>& conditionTrackerMap,
- std::vector<sp<MetricProducer>>& allMetricProducers,
- std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
- std::unordered_map<int, std::vector<int>>& trackerToMetricMap);
+bool initMetrics(
+ const StatsdConfig& config, const std::unordered_map<std::string, int>& logTrackerMap,
+ const std::unordered_map<std::string, int>& conditionTrackerMap,
+ const std::unordered_map<int, std::vector<EventConditionLink>>& eventConditionLinks,
+ vector<sp<ConditionTracker>>& allConditionTrackers,
+ std::vector<sp<MetricProducer>>& allMetricProducers,
+ std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
+ std::unordered_map<int, std::vector<int>>& trackerToMetricMap);
// Initialize MetricManager from StatsdConfig.
// Parameters are the members of MetricsManager. See MetricsManager for declaration.
@@ -88,6 +91,8 @@
std::unordered_map<int, std::vector<int>>& trackerToMetricMap,
std::unordered_map<int, std::vector<int>>& trackerToConditionMap);
+int getTrackerIndex(const std::string& name, const std::unordered_map<string, int>& logTrackerMap);
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto
index 4ca06fa..29cd94b 100644
--- a/cmds/statsd/src/stats_log.proto
+++ b/cmds/statsd/src/stats_log.proto
@@ -106,7 +106,7 @@
repeated CountMetricData data = 1;
}
message DurationMetricDataWrapper {
- repeated CountMetricData data = 1;
+ repeated DurationMetricData data = 1;
}
oneof data {
EventMetricDataWrapper event_metrics = 4;
diff --git a/cmds/statsd/src/stats_util.cpp b/cmds/statsd/src/stats_util.cpp
index 5157adf..fcce2ff 100644
--- a/cmds/statsd/src/stats_util.cpp
+++ b/cmds/statsd/src/stats_util.cpp
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-#include <log/log_event_list.h>
#include "stats_util.h"
+#include <log/log_event_list.h>
namespace android {
namespace os {
@@ -128,6 +128,35 @@
return eventMetricData;
}
+// There is no existing hash function for the dimension key ("repeated KeyValuePair").
+// Temporarily use a string concatenation as the hashable key.
+// TODO: Find a better hash function for std::vector<KeyValuePair>.
+HashableDimensionKey getHashableKey(std::vector<KeyValuePair> keys) {
+ std::string flattened;
+ for (const KeyValuePair& pair : keys) {
+ flattened += std::to_string(pair.key());
+ flattened += ":";
+ switch (pair.value_case()) {
+ case KeyValuePair::ValueCase::kValueStr:
+ flattened += pair.value_str();
+ break;
+ case KeyValuePair::ValueCase::kValueInt:
+ flattened += std::to_string(pair.value_int());
+ break;
+ case KeyValuePair::ValueCase::kValueBool:
+ flattened += std::to_string(pair.value_bool());
+ break;
+ case KeyValuePair::ValueCase::kValueFloat:
+ flattened += std::to_string(pair.value_float());
+ break;
+ default:
+ break;
+ }
+ flattened += "|";
+ }
+ return flattened;
+}
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/src/stats_util.h b/cmds/statsd/src/stats_util.h
index 38174bf..575588b 100644
--- a/cmds/statsd/src/stats_util.h
+++ b/cmds/statsd/src/stats_util.h
@@ -13,8 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-#ifndef PARSE_UTIL_H
-#define PARSE_UTIL_H
+#ifndef STATS_UTIL_H
+#define STATS_UTIL_H
#include "logd/LogReader.h"
#include "storage/DropboxWriter.h"
@@ -26,12 +26,19 @@
namespace os {
namespace statsd {
+#define DEFAULT_DIMENSION_KEY ""
+#define MATCHER_NOT_FOUND -2
+#define NANO_SECONDS_IN_A_SECOND (1000 * 1000 * 1000)
+
+typedef std::string HashableDimensionKey;
+
EventMetricData parse(log_msg msg);
int getTagId(log_msg msg);
+std::string getHashableKey(std::vector<KeyValuePair> key);
} // namespace statsd
} // namespace os
} // namespace android
-#endif // PARSE_UTIL_H
+#endif // STATS_UTIL_H
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index d7702cd..afb3f2b 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -55,7 +55,7 @@
}
message SimpleLogEntryMatcher {
- repeated int32 tag = 1;
+ optional int32 tag = 1;
repeated KeyValueMatcher key_value_matcher = 2;
}
@@ -103,12 +103,28 @@
optional int64 bucket_size_millis = 1;
}
+message Alert {
+ message IncidentdDetails {
+ optional string alert_name = 1;
+ repeated int32 incidentd_sections = 2;
+ }
+ optional IncidentdDetails incidentd_details = 1;
+
+ optional int32 number_of_buckets = 3;
+
+ optional int32 refractory_period_secs = 4;
+
+ optional int64 trigger_if_gt = 5;
+}
+
message EventMetric {
optional int64 metric_id = 1;
optional string what = 2;
optional string condition = 3;
+
+ repeated EventConditionLink links = 4;
}
message CountMetric {
@@ -121,26 +137,73 @@
repeated KeyMatcher dimension = 4;
optional Bucket bucket = 5;
+
+ repeated Alert alerts = 6;
+
+ optional bool include_in_output = 7;
+
+ repeated EventConditionLink links = 8;
}
message DurationMetric {
optional int64 metric_id = 1;
+ optional string start = 2;
+
+ optional string stop = 3;
+
+ optional string stop_all = 4;
+
enum AggregationType {
DURATION_SUM = 1;
DURATION_MAX_SPARSE = 2;
DURATION_MIN_SPARSE = 3;
}
- optional AggregationType type = 2;
+ optional AggregationType type = 5;
- optional string predicate = 3;
+ optional string predicate = 6;
- repeated KeyMatcher dimension = 4;
+ repeated KeyMatcher dimension = 7;
- optional Bucket bucket = 5;
+ optional Bucket bucket = 8;
+
+ repeated EventConditionLink links = 9;
}
+message ValueMetric {
+ optional int64 metric_id = 1;
+
+ optional string what = 2;
+
+ optional int32 value_field = 3;
+
+ optional string condition = 4;
+
+ repeated KeyMatcher dimension = 5;
+
+ optional Bucket bucket = 6;
+
+ enum Operation {
+ SUM_DIFF = 1;
+ MIN_DIFF = 2;
+ MAX_DIFF = 3;
+ SUM = 4;
+ MIN = 5;
+ MAX = 6;
+ FIRST = 7;
+ LAST = 8;
+ }
+ optional Operation operation = 7;
+}
+
+message EventConditionLink {
+ optional string condition = 1;
+
+ repeated KeyMatcher key_in_main = 2;
+ repeated KeyMatcher key_in_condition = 3;
+};
+
message StatsdConfig {
optional int64 config_id = 1;
@@ -148,7 +211,11 @@
repeated CountMetric count_metric = 3;
- repeated LogEntryMatcher log_entry_matcher = 4;
+ repeated ValueMetric value_metric = 4;
- repeated Condition condition = 5;
+ repeated DurationMetric duration_metric = 5;
+
+ repeated LogEntryMatcher log_entry_matcher = 6;
+
+ repeated Condition condition = 7;
}