Duration tracker optimization.
* Avoid querying sliced condition for stop/stopAll events for duration metric.
* Avoid extracting the internal dimension key when it is identical to the what dimension.
Test: statsd test
Change-Id: I664e8d3b1a68960d05c9ce4789caefb60b1ab502
diff --git a/cmds/statsd/src/FieldValue.h b/cmds/statsd/src/FieldValue.h
index 21f30e2..b0e2c43 100644
--- a/cmds/statsd/src/FieldValue.h
+++ b/cmds/statsd/src/FieldValue.h
@@ -217,6 +217,14 @@
const Field mMatcher;
const int32_t mMask;
+ inline const Field& getMatcher() const {
+ return mMatcher;
+ }
+
+ inline int32_t getMask() const {
+ return mMask;
+ }
+
bool hasAnyPositionMatcher(int* prefix) const {
if (mMatcher.getDepth() == 2 && mMatcher.getRawPosAtDepth(2) == 0) {
(*prefix) = mMatcher.getPrefix(2);
@@ -224,6 +232,10 @@
}
return false;
}
+
+ inline bool operator!=(const Matcher& that) const {
+ return mMatcher != that.getMatcher() || mMask != that.getMask();
+ };
};
/**
diff --git a/cmds/statsd/src/HashableDimensionKey.cpp b/cmds/statsd/src/HashableDimensionKey.cpp
index 1502a00..cc706313 100644
--- a/cmds/statsd/src/HashableDimensionKey.cpp
+++ b/cmds/statsd/src/HashableDimensionKey.cpp
@@ -166,10 +166,11 @@
}
}
-void getDimensionForCondition(const LogEvent& event, Metric2Condition links,
+void getDimensionForCondition(const std::vector<FieldValue>& eventValues,
+ const Metric2Condition& links,
vector<HashableDimensionKey>* conditionDimension) {
// Get the dimension first by using dimension from what.
- filterValues(links.metricFields, event.getValues(), conditionDimension);
+ filterValues(links.metricFields, eventValues, conditionDimension);
// Then replace the field with the dimension from condition.
for (auto& dim : *conditionDimension) {
diff --git a/cmds/statsd/src/HashableDimensionKey.h b/cmds/statsd/src/HashableDimensionKey.h
index 5d016e9..57bdf68 100644
--- a/cmds/statsd/src/HashableDimensionKey.h
+++ b/cmds/statsd/src/HashableDimensionKey.h
@@ -144,7 +144,8 @@
void filterGaugeValues(const std::vector<Matcher>& matchers, const std::vector<FieldValue>& values,
std::vector<FieldValue>* output);
-void getDimensionForCondition(const LogEvent& event, Metric2Condition links,
+void getDimensionForCondition(const std::vector<FieldValue>& eventValues,
+ const Metric2Condition& links,
std::vector<HashableDimensionKey>* conditionDimension);
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index 16cac99..80329c3 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -101,6 +101,18 @@
}
mConditionSliced = (metric.links().size() > 0) || (mDimensionsInCondition.size() > 0);
+ if (mDimensionsInWhat.size() == mInternalDimensions.size()) {
+ bool mUseWhatDimensionAsInternalDimension = true;
+ for (size_t i = 0; mUseWhatDimensionAsInternalDimension &&
+ i < mDimensionsInWhat.size(); ++i) {
+ if (mDimensionsInWhat[i] != mInternalDimensions[i]) {
+ mUseWhatDimensionAsInternalDimension = false;
+ }
+ }
+ } else {
+ mUseWhatDimensionAsInternalDimension = false;
+ }
+
VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(),
(long long)mBucketSizeNs, (long long)mStartTimeNs);
}
@@ -141,29 +153,56 @@
flushIfNeededLocked(eventTime);
// Now for each of the on-going event, check if the condition has changed for them.
- for (auto& pair : mCurrentSlicedDurationTrackerMap) {
- pair.second->onSlicedConditionMayChange(eventTime);
+ for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
+ for (auto& pair : whatIt.second) {
+ pair.second->onSlicedConditionMayChange(eventTime);
+ }
}
-
- std::unordered_set<HashableDimensionKey> conditionDimensionsKeySet;
- mWizard->getMetConditionDimension(mConditionTrackerIndex, mDimensionsInCondition,
- &conditionDimensionsKeySet);
-
- for (auto& pair : mCurrentSlicedDurationTrackerMap) {
- conditionDimensionsKeySet.erase(pair.first.getDimensionKeyInCondition());
+ if (mDimensionsInCondition.empty()) {
+ return;
}
- std::unordered_set<MetricDimensionKey> newKeys;
- for (const auto& conditionDimensionsKey : conditionDimensionsKeySet) {
- for (auto& pair : mCurrentSlicedDurationTrackerMap) {
- auto newKey =
- MetricDimensionKey(pair.first.getDimensionKeyInWhat(), conditionDimensionsKey);
- if (newKeys.find(newKey) == newKeys.end()) {
- mCurrentSlicedDurationTrackerMap[newKey] = pair.second->clone(eventTime);
- mCurrentSlicedDurationTrackerMap[newKey]->setEventKey(newKey);
- mCurrentSlicedDurationTrackerMap[newKey]->onSlicedConditionMayChange(eventTime);
+
+ if (mMetric2ConditionLinks.empty()) {
+ std::unordered_set<HashableDimensionKey> conditionDimensionsKeySet;
+ mWizard->getMetConditionDimension(mConditionTrackerIndex, mDimensionsInCondition,
+ &conditionDimensionsKeySet);
+ for (const auto& whatIt : mCurrentSlicedDurationTrackerMap) {
+ for (const auto& pair : whatIt.second) {
+ conditionDimensionsKeySet.erase(pair.first);
}
- newKeys.insert(newKey);
+ }
+ for (const auto& conditionDimension : conditionDimensionsKeySet) {
+ for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
+ if (!whatIt.second.empty()) {
+ unique_ptr<DurationTracker> newTracker =
+ whatIt.second.begin()->second->clone(eventTime);
+ newTracker->setEventKey(MetricDimensionKey(whatIt.first, conditionDimension));
+ newTracker->onSlicedConditionMayChange(eventTime);
+ whatIt.second[conditionDimension] = std::move(newTracker);
+ }
+ }
+ }
+ } else {
+ for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
+ ConditionKey conditionKey;
+ for (const auto& link : mMetric2ConditionLinks) {
+ getDimensionForCondition(whatIt.first.getValues(), link,
+ &conditionKey[link.conditionId]);
+ }
+ std::unordered_set<HashableDimensionKey> conditionDimensionsKeys;
+ mWizard->query(mConditionTrackerIndex, conditionKey, mDimensionsInCondition,
+ &conditionDimensionsKeys);
+
+ for (const auto& conditionDimension : conditionDimensionsKeys) {
+ if (!whatIt.second.empty() &&
+ whatIt.second.find(conditionDimension) == whatIt.second.end()) {
+ auto newTracker = whatIt.second.begin()->second->clone(eventTime);
+ newTracker->setEventKey(MetricDimensionKey(whatIt.first, conditionDimension));
+ newTracker->onSlicedConditionMayChange(eventTime);
+ whatIt.second[conditionDimension] = std::move(newTracker);
+ }
+ }
}
}
}
@@ -175,8 +214,10 @@
flushIfNeededLocked(eventTime);
// TODO: need to populate the condition change time from the event which triggers the condition
// change, instead of using current time.
- for (auto& pair : mCurrentSlicedDurationTrackerMap) {
- pair.second->onConditionChanged(conditionMet, eventTime);
+ for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
+ for (auto& pair : whatIt.second) {
+ pair.second->onConditionChanged(conditionMet, eventTime);
+ }
}
}
@@ -241,13 +282,20 @@
return;
}
VLOG("flushing...........");
- for (auto it = mCurrentSlicedDurationTrackerMap.begin();
- it != mCurrentSlicedDurationTrackerMap.end();) {
- if (it->second->flushIfNeeded(eventTimeNs, &mPastBuckets)) {
- VLOG("erase bucket for key %s", it->first.c_str());
- it = mCurrentSlicedDurationTrackerMap.erase(it);
+ for (auto whatIt = mCurrentSlicedDurationTrackerMap.begin();
+ whatIt != mCurrentSlicedDurationTrackerMap.end();) {
+ for (auto it = whatIt->second.begin(); it != whatIt->second.end();) {
+ if (it->second->flushIfNeeded(eventTimeNs, &mPastBuckets)) {
+ VLOG("erase bucket for key %s %s", whatIt->first.c_str(), it->first.c_str());
+ it = whatIt->second.erase(it);
+ } else {
+ ++it;
+ }
+ }
+ if (whatIt->second.empty()) {
+ whatIt = mCurrentSlicedDurationTrackerMap.erase(whatIt);
} else {
- ++it;
+ whatIt++;
}
}
@@ -257,13 +305,20 @@
}
void DurationMetricProducer::flushCurrentBucketLocked(const uint64_t& eventTimeNs) {
- for (auto it = mCurrentSlicedDurationTrackerMap.begin();
- it != mCurrentSlicedDurationTrackerMap.end();) {
- if (it->second->flushCurrentBucket(eventTimeNs, &mPastBuckets)) {
- VLOG("erase bucket for key %s", it->first.c_str());
- it = mCurrentSlicedDurationTrackerMap.erase(it);
+ for (auto whatIt = mCurrentSlicedDurationTrackerMap.begin();
+ whatIt != mCurrentSlicedDurationTrackerMap.end();) {
+ for (auto it = whatIt->second.begin(); it != whatIt->second.end();) {
+ if (it->second->flushCurrentBucket(eventTimeNs, &mPastBuckets)) {
+ VLOG("erase bucket for key %s %s", whatIt->first.c_str(), it->first.c_str());
+ it = whatIt->second.erase(it);
+ } else {
+ ++it;
+ }
+ }
+ if (whatIt->second.empty()) {
+ whatIt = mCurrentSlicedDurationTrackerMap.erase(whatIt);
} else {
- ++it;
+ whatIt++;
}
}
}
@@ -276,18 +331,16 @@
fprintf(out, "DurationMetric %lld dimension size %lu\n", (long long)mMetricId,
(unsigned long)mCurrentSlicedDurationTrackerMap.size());
if (verbose) {
- for (const auto& slice : mCurrentSlicedDurationTrackerMap) {
- fprintf(out, "\t%s\n", slice.first.c_str());
- slice.second->dumpStates(out, verbose);
+ for (const auto& whatIt : mCurrentSlicedDurationTrackerMap) {
+ for (const auto& slice : whatIt.second) {
+ fprintf(out, "\t%s\t%s\n", whatIt.first.c_str(), slice.first.c_str());
+ slice.second->dumpStates(out, verbose);
+ }
}
}
}
bool DurationMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) {
- // the key is not new, we are good.
- if (mCurrentSlicedDurationTrackerMap.find(newKey) != mCurrentSlicedDurationTrackerMap.end()) {
- return false;
- }
// 1. Report the tuple count if the tuple count > soft limit
if (mCurrentSlicedDurationTrackerMap.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
size_t newTupleCount = mCurrentSlicedDurationTrackerMap.size() + 1;
@@ -302,49 +355,162 @@
return false;
}
-void DurationMetricProducer::onMatchedLogEventInternalLocked(
- const size_t matcherIndex, const MetricDimensionKey& eventKey,
- const ConditionKey& conditionKeys, bool condition,
- const LogEvent& event) {
- flushIfNeededLocked(event.GetElapsedTimestampNs());
+void DurationMetricProducer::handleStartEvent(const MetricDimensionKey& eventKey,
+ const ConditionKey& conditionKeys,
+ bool condition, const LogEvent& event) {
+ const auto& whatKey = eventKey.getDimensionKeyInWhat();
+ const auto& condKey = eventKey.getDimensionKeyInCondition();
- if (matcherIndex == mStopAllIndex) {
- for (auto& pair : mCurrentSlicedDurationTrackerMap) {
- pair.second->noteStopAll(event.GetElapsedTimestampNs());
- }
- return;
- }
-
- if (mCurrentSlicedDurationTrackerMap.find(eventKey) == mCurrentSlicedDurationTrackerMap.end()) {
+ auto whatIt = mCurrentSlicedDurationTrackerMap.find(whatKey);
+ if (whatIt == mCurrentSlicedDurationTrackerMap.end()) {
if (hitGuardRailLocked(eventKey)) {
return;
}
- mCurrentSlicedDurationTrackerMap[eventKey] = createDurationTracker(eventKey);
+ mCurrentSlicedDurationTrackerMap[whatKey][condKey] = createDurationTracker(eventKey);
+ } else {
+ if (whatIt->second.find(condKey) == whatIt->second.end()) {
+ if (hitGuardRailLocked(eventKey)) {
+ return;
+ }
+ mCurrentSlicedDurationTrackerMap[whatKey][condKey] = createDurationTracker(eventKey);
+ }
}
- auto it = mCurrentSlicedDurationTrackerMap.find(eventKey);
+ auto it = mCurrentSlicedDurationTrackerMap.find(whatKey)->second.find(condKey);
+ if (mUseWhatDimensionAsInternalDimension) {
+ it->second->noteStart(whatKey, condition,
+ event.GetElapsedTimestampNs(), conditionKeys);
+ return;
+ }
std::vector<HashableDimensionKey> values;
filterValues(mInternalDimensions, event.getValues(), &values);
if (values.empty()) {
- if (matcherIndex == mStartIndex) {
- it->second->noteStart(DEFAULT_DIMENSION_KEY, condition,
- event.GetElapsedTimestampNs(), conditionKeys);
- } else if (matcherIndex == mStopIndex) {
- it->second->noteStop(DEFAULT_DIMENSION_KEY, event.GetElapsedTimestampNs(), false);
- }
+ it->second->noteStart(DEFAULT_DIMENSION_KEY, condition,
+ event.GetElapsedTimestampNs(), conditionKeys);
} else {
for (const auto& value : values) {
- if (matcherIndex == mStartIndex) {
- it->second->noteStart(value, condition, event.GetElapsedTimestampNs(), conditionKeys);
- } else if (matcherIndex == mStopIndex) {
- it->second->noteStop(value, event.GetElapsedTimestampNs(), false);
- }
+ it->second->noteStart(value, condition, event.GetElapsedTimestampNs(), conditionKeys);
}
}
}
+void DurationMetricProducer::onMatchedLogEventInternalLocked(
+ const size_t matcherIndex, const MetricDimensionKey& eventKey,
+ const ConditionKey& conditionKeys, bool condition,
+ const LogEvent& event) {
+ ALOGW("Not used in duration tracker.");
+}
+
+void DurationMetricProducer::onMatchedLogEventLocked(const size_t matcherIndex,
+ const LogEvent& event) {
+ uint64_t eventTimeNs = event.GetElapsedTimestampNs();
+ if (eventTimeNs < mStartTimeNs) {
+ return;
+ }
+
+ flushIfNeededLocked(event.GetElapsedTimestampNs());
+
+ // Handles Stopall events.
+ if (matcherIndex == mStopAllIndex) {
+ for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
+ for (auto& pair : whatIt.second) {
+ pair.second->noteStopAll(event.GetElapsedTimestampNs());
+ }
+ }
+ return;
+ }
+
+ vector<HashableDimensionKey> dimensionInWhatValues;
+ if (!mDimensionsInWhat.empty()) {
+ filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhatValues);
+ } else {
+ dimensionInWhatValues.push_back(DEFAULT_DIMENSION_KEY);
+ }
+
+ // Handles Stop events.
+ if (matcherIndex == mStopIndex) {
+ if (mUseWhatDimensionAsInternalDimension) {
+ for (const HashableDimensionKey& whatKey : dimensionInWhatValues) {
+ auto whatIt = mCurrentSlicedDurationTrackerMap.find(whatKey);
+ if (whatIt != mCurrentSlicedDurationTrackerMap.end()) {
+ for (const auto& condIt : whatIt->second) {
+ condIt.second->noteStop(whatKey, event.GetElapsedTimestampNs(), false);
+ }
+ }
+ }
+ return;
+ }
+
+ std::vector<HashableDimensionKey> internalDimensionKeys;
+ filterValues(mInternalDimensions, event.getValues(), &internalDimensionKeys);
+ if (internalDimensionKeys.empty()) {
+ internalDimensionKeys.push_back(DEFAULT_DIMENSION_KEY);
+ }
+ for (const HashableDimensionKey& whatDimension : dimensionInWhatValues) {
+ auto whatIt = mCurrentSlicedDurationTrackerMap.find(whatDimension);
+ if (whatIt != mCurrentSlicedDurationTrackerMap.end()) {
+ for (const auto& condIt : whatIt->second) {
+ for (const auto& internalDimensionKey : internalDimensionKeys) {
+ condIt.second->noteStop(
+ internalDimensionKey, event.GetElapsedTimestampNs(), false);
+ }
+ }
+ }
+ }
+ return;
+ }
+
+ bool condition;
+ ConditionKey conditionKey;
+ std::unordered_set<HashableDimensionKey> dimensionKeysInCondition;
+ if (mConditionSliced) {
+ for (const auto& link : mMetric2ConditionLinks) {
+ getDimensionForCondition(event.getValues(), link, &conditionKey[link.conditionId]);
+ }
+
+ auto conditionState =
+ mWizard->query(mConditionTrackerIndex, conditionKey, mDimensionsInCondition,
+ &dimensionKeysInCondition);
+ condition = (conditionState == ConditionState::kTrue);
+ if (mDimensionsInCondition.empty() && condition) {
+ dimensionKeysInCondition.insert(DEFAULT_DIMENSION_KEY);
+ }
+ } else {
+ condition = mCondition;
+ if (condition) {
+ dimensionKeysInCondition.insert(DEFAULT_DIMENSION_KEY);
+ }
+ }
+
+ for (const auto& whatDimension : dimensionInWhatValues) {
+ auto whatIt = mCurrentSlicedDurationTrackerMap.find(whatDimension);
+ // If the what dimension is already there, we should update all the trackers even
+ // the condition is false.
+ if (whatIt != mCurrentSlicedDurationTrackerMap.end()) {
+ for (const auto& condIt : whatIt->second) {
+ const bool cond = dimensionKeysInCondition.find(condIt.first) !=
+ dimensionKeysInCondition.end();
+ handleStartEvent(MetricDimensionKey(whatDimension, condIt.first),
+ conditionKey, cond, event);
+ }
+ } else {
+ // If it is a new what dimension key, we need to handle the start events for all current
+ // condition dimensions.
+ for (const auto& conditionDimension : dimensionKeysInCondition) {
+ handleStartEvent(MetricDimensionKey(whatDimension, conditionDimension),
+ conditionKey, condition, event);
+ }
+ }
+ if (dimensionKeysInCondition.empty()) {
+ handleStartEvent(MetricDimensionKey(whatDimension, DEFAULT_DIMENSION_KEY),
+ conditionKey, condition, event);
+ }
+ }
+}
+
+
size_t DurationMetricProducer::byteSizeLocked() const {
size_t totalSize = 0;
for (const auto& pair : mPastBuckets) {
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
index f41c278..73d074f 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -50,12 +50,16 @@
const sp<AlarmMonitor>& anomalyAlarmMonitor) override;
protected:
+ void onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event) override;
void onMatchedLogEventInternalLocked(
const size_t matcherIndex, const MetricDimensionKey& eventKey,
const ConditionKey& conditionKeys, bool condition,
const LogEvent& event) override;
private:
+ void handleStartEvent(const MetricDimensionKey& eventKey, const ConditionKey& conditionKeys,
+ bool condition, const LogEvent& event);
+
void onDumpReportLocked(const uint64_t dumpTimeNs,
android::util::ProtoOutputStream* protoOutput) override;
@@ -92,12 +96,16 @@
// The dimension from the atom predicate. e.g., uid, wakelock name.
vector<Matcher> mInternalDimensions;
+ // This boolean is true iff When mInternalDimensions == mDimensionsInWhat
+ bool mUseWhatDimensionAsInternalDimension;
+
// Save the past buckets and we can clear when the StatsLogReport is dumped.
// TODO: Add a lock to mPastBuckets.
std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>> mPastBuckets;
- // The current bucket.
- std::unordered_map<MetricDimensionKey, std::unique_ptr<DurationTracker>>
+ // The duration trackers in the current bucket.
+ std::unordered_map<HashableDimensionKey,
+ std::unordered_map<HashableDimensionKey, std::unique_ptr<DurationTracker>>>
mCurrentSlicedDurationTrackerMap;
// Helper function to create a duration tracker given the metric aggregation type.
diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp
index f3307dc..18694a1 100644
--- a/cmds/statsd/src/metrics/MetricProducer.cpp
+++ b/cmds/statsd/src/metrics/MetricProducer.cpp
@@ -37,7 +37,7 @@
std::unordered_set<HashableDimensionKey> dimensionKeysInCondition;
if (mConditionSliced) {
for (const auto& link : mMetric2ConditionLinks) {
- getDimensionForCondition(event, link, &conditionKey[link.conditionId]);
+ getDimensionForCondition(event.getValues(), link, &conditionKey[link.conditionId]);
}
auto conditionState =
@@ -48,37 +48,30 @@
condition = mCondition;
}
- vector<HashableDimensionKey> dimensionInWhatValues;
- if (mDimensionsInWhat.size() > 0) {
- filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhatValues);
+ if (mDimensionsInCondition.empty() && condition) {
+ dimensionKeysInCondition.insert(DEFAULT_DIMENSION_KEY);
}
- if (dimensionInWhatValues.empty() && dimensionKeysInCondition.empty()) {
- onMatchedLogEventInternalLocked(
- matcherIndex, DEFAULT_METRIC_DIMENSION_KEY, conditionKey, condition, event);
- } else if (dimensionKeysInCondition.empty()) {
- for (const HashableDimensionKey& whatValue : dimensionInWhatValues) {
- onMatchedLogEventInternalLocked(matcherIndex,
- MetricDimensionKey(whatValue, DEFAULT_DIMENSION_KEY),
- conditionKey, condition, event);
- }
- } else if (dimensionInWhatValues.empty()) {
+ vector<HashableDimensionKey> dimensionInWhatValues;
+ if (!mDimensionsInWhat.empty()) {
+ filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhatValues);
+ } else {
+ dimensionInWhatValues.push_back(DEFAULT_DIMENSION_KEY);
+ }
+
+ for (const auto& whatDimension : dimensionInWhatValues) {
for (const auto& conditionDimensionKey : dimensionKeysInCondition) {
onMatchedLogEventInternalLocked(
- matcherIndex,
- MetricDimensionKey(DEFAULT_DIMENSION_KEY, conditionDimensionKey),
- conditionKey, condition, event);
+ matcherIndex, MetricDimensionKey(whatDimension, conditionDimensionKey),
+ conditionKey, condition, event);
}
- } else {
- for (const auto& whatValue : dimensionInWhatValues) {
- for (const auto& conditionDimensionKey : dimensionKeysInCondition) {
- onMatchedLogEventInternalLocked(
- matcherIndex, MetricDimensionKey(whatValue, conditionDimensionKey),
- conditionKey, condition, event);
- }
+ if (dimensionKeysInCondition.empty()) {
+ onMatchedLogEventInternalLocked(
+ matcherIndex, MetricDimensionKey(whatDimension, DEFAULT_DIMENSION_KEY),
+ conditionKey, condition, event);
}
}
-}
+ }
} // namespace statsd
} // namespace os
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index 83e1740..2bf6241 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -233,7 +233,7 @@
const LogEvent& event) = 0;
// Consume the parsed stats log entry that already matched the "what" of the metric.
- void onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event);
+ virtual void onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event);
mutable std::mutex mMutex;
};