Gauge metric producer.

Test: manual tests passed for pushed device temperature.
Change-Id: I7592a4c04666606b745cdb41db8f9d8a96a966da
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
index c5805fbb..86f353b 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -46,6 +46,7 @@
     src/metrics/duration_helper/OringDurationTracker.cpp \
     src/metrics/duration_helper/MaxDurationTracker.cpp \
     src/metrics/ValueMetricProducer.cpp \
+    src/metrics/GaugeMetricProducer.cpp \
     src/metrics/MetricsManager.cpp \
     src/metrics/metrics_manager_util.cpp \
     src/packages/UidMap.cpp \
diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp
index 890f44b..02e6903 100644
--- a/cmds/statsd/src/config/ConfigManager.cpp
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -142,6 +142,9 @@
     int KERNEL_WAKELOCK_TAG_ID = 1004;
     int KERNEL_WAKELOCK_NAME_KEY = 4;
 
+    int DEVICE_TEMPERATURE_TAG_ID = 33;
+    int DEVICE_TEMPERATURE_KEY = 1;
+
     // Count Screen ON events.
     CountMetric* metric = config.add_count_metric();
     metric->set_metric_id(1);
@@ -227,7 +230,7 @@
     // Duration of screen on time.
     durationMetric = config.add_duration_metric();
     durationMetric->set_metric_id(8);
-    durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
+    durationMetric->mutable_bucket()->set_bucket_size_millis(10 * 1000L);
     durationMetric->set_type(DurationMetric_AggregationType_DURATION_SUM);
     durationMetric->set_what("SCREEN_IS_ON");
 
@@ -247,7 +250,19 @@
     eventMetric->set_metric_id(9);
     eventMetric->set_what("SCREEN_TURNED_ON");
 
+    // Add an GaugeMetric.
+    GaugeMetric* gaugeMetric = config.add_gauge_metric();
+    gaugeMetric->set_metric_id(10);
+    gaugeMetric->set_what("DEVICE_TEMPERATURE");
+    gaugeMetric->set_gauge_field(DEVICE_TEMPERATURE_KEY);
+    gaugeMetric->mutable_bucket()->set_bucket_size_millis(60 * 1000L);
+
     // Event matchers............
+    LogEntryMatcher* temperatureEntryMatcher = config.add_log_entry_matcher();
+    temperatureEntryMatcher->set_name("DEVICE_TEMPERATURE");
+    temperatureEntryMatcher->mutable_simple_log_entry_matcher()->set_tag(
+        DEVICE_TEMPERATURE_TAG_ID);
+
     LogEntryMatcher* eventMatcher = config.add_log_entry_matcher();
     eventMatcher->set_name("SCREEN_TURNED_ON");
     SimpleLogEntryMatcher* simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
new file mode 100644
index 0000000..bd638b4
--- /dev/null
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -0,0 +1,232 @@
+/*
+* 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  // STOPSHIP if true
+#include "Log.h"
+
+#include "GaugeMetricProducer.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 {
+
+GaugeMetricProducer::GaugeMetricProducer(const GaugeMetric& metric, const int conditionIndex,
+                                         const sp<ConditionWizard>& wizard, const int pullTagId)
+    : MetricProducer((time(nullptr) * NANO_SECONDS_IN_A_SECOND), conditionIndex, wizard),
+      mMetric(metric),
+      mPullTagId(pullTagId) {
+    if (metric.has_bucket() && metric.bucket().has_bucket_size_millis()) {
+        mBucketSizeNs = metric.bucket().bucket_size_millis() * 1000 * 1000;
+    } else {
+        mBucketSizeNs = kDefaultGaugemBucketSizeNs;
+    }
+
+    // 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;
+    }
+
+    // Kicks off the puller immediately.
+    if (mPullTagId != -1) {
+        mStatsPullerManager.RegisterReceiver(mPullTagId, this,
+                                             metric.bucket().bucket_size_millis());
+    }
+
+    VLOG("metric %lld created. bucket size %lld start_time: %lld", metric.metric_id(),
+         (long long)mBucketSizeNs, (long long)mStartTimeNs);
+}
+
+GaugeMetricProducer::~GaugeMetricProducer() {
+    VLOG("~GaugeMetricProducer() called");
+}
+
+void GaugeMetricProducer::finish() {
+}
+
+static void addSlicedGaugeToReport(const vector<KeyValuePair>& key,
+                                   const vector<GaugeBucketInfo>& buckets,
+                                   StatsLogReport_GaugeMetricDataWrapper& wrapper) {
+    GaugeMetricData* 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] gauge: %lld", bucket.start_bucket_nanos(),
+             bucket.end_bucket_nanos(), bucket.gauge());
+    }
+}
+
+StatsLogReport GaugeMetricProducer::onDumpReport() {
+    VLOG("gauge 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.
+    flushGaugeIfNeededLocked(time(nullptr) * NANO_SECONDS_IN_A_SECOND);
+    report.set_end_report_nanos(mCurrentBucketStartTimeNs);
+
+    StatsLogReport_GaugeMetricDataWrapper* wrapper = report.mutable_gauge_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());
+        addSlicedGaugeToReport(it->second, pair.second, *wrapper);
+    }
+    return report;
+    // TODO: Clear mPastBuckets, mDimensionKeyMap once the report is dumped.
+}
+
+void GaugeMetricProducer::onConditionChanged(const bool conditionMet, const uint64_t eventTime) {
+    AutoMutex _l(mLock);
+    VLOG("Metric %lld onConditionChanged", mMetric.metric_id());
+    mCondition = conditionMet;
+
+    // Push mode. Nothing to do.
+    if (mPullTagId == -1) {
+        return;
+    }
+    // If (1) the condition is not met or (2) we already pulled the gauge metric in the current
+    // bucket, do not pull gauge again.
+    if (!mCondition || mCurrentSlicedBucket.size() > 0) {
+        return;
+    }
+    vector<std::shared_ptr<LogEvent>> allData;
+    if (!mStatsPullerManager.Pull(mPullTagId, &allData)) {
+        ALOGE("Stats puller failed for tag: %d", mPullTagId);
+        return;
+    }
+    for (const auto& data : allData) {
+        onMatchedLogEvent(0, *data, false /*scheduledPull*/);
+    }
+    flushGaugeIfNeededLocked(eventTime);
+}
+
+void GaugeMetricProducer::onSlicedConditionMayChange(const uint64_t eventTime) {
+    VLOG("Metric %lld onSlicedConditionMayChange", mMetric.metric_id());
+}
+
+long GaugeMetricProducer::getGauge(const LogEvent& event) {
+    status_t err = NO_ERROR;
+    long val = event.GetLong(mMetric.gauge_field(), &err);
+    if (err == NO_ERROR) {
+        return val;
+    } else {
+        VLOG("Can't find value in message.");
+        return -1;
+    }
+}
+
+void GaugeMetricProducer::onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& allData) {
+    AutoMutex mutex(mLock);
+    if (allData.size() == 0) {
+        return;
+    }
+    for (const auto& data : allData) {
+        onMatchedLogEvent(0, *data, true /*scheduledPull*/);
+    }
+    uint64_t eventTime = allData.at(0)->GetTimestampNs();
+    flushGaugeIfNeededLocked(eventTime);
+}
+
+void GaugeMetricProducer::onMatchedLogEventInternal(
+        const size_t matcherIndex, const HashableDimensionKey& eventKey,
+        const map<string, HashableDimensionKey>& conditionKey, bool condition,
+        const LogEvent& event, bool scheduledPull) {
+    if (condition == false) {
+        return;
+    }
+    uint64_t eventTimeNs = event.GetTimestampNs();
+    if (eventTimeNs < mCurrentBucketStartTimeNs) {
+        VLOG("Skip event due to late arrival: %lld vs %lld", (long long)eventTimeNs,
+             (long long)mCurrentBucketStartTimeNs);
+        return;
+    }
+
+    // For gauge metric, we just simply use the latest guage in the given bucket.
+    const long gauge = getGauge(event);
+    if (gauge < 0) {
+        VLOG("Invalid gauge at event Time: %lld", (long long)eventTimeNs);
+        return;
+    }
+    mCurrentSlicedBucket[eventKey] = gauge;
+    if (mPullTagId < 0) {
+        flushGaugeIfNeededLocked(eventTimeNs);
+    }
+}
+
+// When a new matched event comes in, we check if event falls into the current
+// bucket. If not, flush the old counter to past buckets and initialize the new
+// bucket.
+// if data is pushed, onMatchedLogEvent will only be called through onConditionChanged() inside
+// the GaugeMetricProducer while holding the lock.
+void GaugeMetricProducer::flushGaugeIfNeededLocked(const uint64_t eventTimeNs) {
+    if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTimeNs) {
+        VLOG("event time is %lld, less than next bucket start time %lld", (long long)eventTimeNs,
+             (long long)(mCurrentBucketStartTimeNs + mBucketSizeNs));
+        return;
+    }
+
+    // Adjusts the bucket start time
+    int64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
+
+    GaugeBucketInfo info;
+    info.set_start_bucket_nanos(mCurrentBucketStartTimeNs);
+    info.set_end_bucket_nanos(mCurrentBucketStartTimeNs + mBucketSizeNs);
+
+    for (const auto& slice : mCurrentSlicedBucket) {
+        info.set_gauge(slice.second);
+        auto& bucketList = mPastBuckets[slice.first];
+        bucketList.push_back(info);
+
+        VLOG("gauge metric %lld, dump key value: %s -> %ld", mMetric.metric_id(),
+             slice.first.c_str(), slice.second);
+    }
+    // Reset counters
+    mCurrentSlicedBucket.clear();
+
+    mCurrentBucketStartTimeNs = mCurrentBucketStartTimeNs + numBucketsForward * mBucketSizeNs;
+    VLOG("metric %lld: new bucket start time: %lld", mMetric.metric_id(),
+         (long long)mCurrentBucketStartTimeNs);
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.h b/cmds/statsd/src/metrics/GaugeMetricProducer.h
new file mode 100644
index 0000000..bf8a86f
--- /dev/null
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <unordered_map>
+
+#include "../condition/ConditionTracker.h"
+#include "../external/PullDataReceiver.h"
+#include "../external/StatsPullerManager.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"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+// This gauge metric producer first register the puller to automatically pull the gauge at the
+// beginning of each bucket. If the condition is met, insert it to the bucket info. Otherwise
+// proactively pull the gauge when the condition is changed to be true. Therefore, the gauge metric
+// producer always reports the guage at the earliest time of the bucket when the condition is met.
+class GaugeMetricProducer : public virtual MetricProducer, public virtual PullDataReceiver {
+public:
+    // TODO: Pass in the start time from MetricsManager, it should be consistent
+    // for all metrics.
+    GaugeMetricProducer(const GaugeMetric& countMetric, const int conditionIndex,
+                        const sp<ConditionWizard>& wizard, const int pullTagId);
+
+    virtual ~GaugeMetricProducer();
+
+    // Handles when the pulled data arrives.
+    void onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& data) override;
+
+    void onConditionChanged(const bool conditionMet, const uint64_t eventTime) override;
+    void onSlicedConditionMayChange(const uint64_t eventTime) override;
+
+    void finish() override;
+
+    StatsLogReport onDumpReport() override;
+
+    // TODO: implements it when supporting proto stream.
+    size_t byteSize() override {
+        return 0;
+    };
+
+    // TODO: Implement this later.
+    virtual void notifyAppUpgrade(const string& apk, const int uid, const int version) override{};
+    // TODO: Implement this later.
+    virtual void notifyAppRemoved(const string& apk, const int uid) override{};
+
+protected:
+    void onMatchedLogEventInternal(const size_t matcherIndex, const HashableDimensionKey& eventKey,
+                                   const std::map<std::string, HashableDimensionKey>& conditionKey,
+                                   bool condition, const LogEvent& event,
+                                   bool scheduledPull) override;
+
+private:
+    // The default bucket size for gauge metric is 1 second.
+    static const uint64_t kDefaultGaugemBucketSizeNs = 1000 * 1000 * 1000;
+    const GaugeMetric mMetric;
+
+    StatsPullerManager& mStatsPullerManager = StatsPullerManager::GetInstance();
+    // tagId for pulled data. -1 if this is not pulled
+    const int mPullTagId;
+
+    Mutex mLock;
+
+    // Save the past buckets and we can clear when the StatsLogReport is dumped.
+    std::unordered_map<HashableDimensionKey, std::vector<GaugeBucketInfo>> mPastBuckets;
+
+    // The current bucket.
+    std::unordered_map<HashableDimensionKey, long> mCurrentSlicedBucket;
+
+    void flushGaugeIfNeededLocked(const uint64_t newEventTime);
+
+    long getGauge(const LogEvent& event);
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index 521fcc3..9b708dd6 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -146,7 +146,8 @@
                 auto& metricList = pair->second;
                 for (const int metricIndex : metricList) {
                     // pushed metrics are never scheduled pulls
-                    mAllMetricProducers[metricIndex]->onMatchedLogEvent(i, event, false);
+                    mAllMetricProducers[metricIndex]->onMatchedLogEvent(
+                        i, event, false /* schedulePull */);
                 }
             }
         }
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index 3ff2a77..a0fdcdc 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -22,6 +22,7 @@
 #include "CountMetricProducer.h"
 #include "DurationMetricProducer.h"
 #include "EventMetricProducer.h"
+#include "GaugeMetricProducer.h"
 #include "ValueMetricProducer.h"
 #include "stats_util.h"
 
@@ -308,7 +309,6 @@
         }
 
         sp<MetricProducer> eventMetric = new EventMetricProducer(metric, conditionIndex, wizard);
-
         allMetricProducers.push_back(eventMetric);
     }
 
@@ -351,6 +351,46 @@
                 new ValueMetricProducer(metric, conditionIndex, wizard, pullTagId);
         allMetricProducers.push_back(valueProducer);
     }
+
+    // Gauge metrics.
+    for (int i = 0; i < config.gauge_metric_size(); i++) {
+        const GaugeMetric& metric = config.gauge_metric(i);
+        if (!metric.has_what()) {
+            ALOGW("cannot find what in ValueMetric %lld", metric.metric_id());
+            return false;
+        }
+
+        int metricIndex = allMetricProducers.size();
+        int trackerIndex;
+        if (!handleMetricWithLogTrackers(metric.what(), metricIndex, metric.dimension_size() > 0,
+                                         allLogEntryMatchers, logTrackerMap, trackerToMetricMap,
+                                         trackerIndex)) {
+            return false;
+        }
+
+        sp<LogMatchingTracker> atomMatcher = allLogEntryMatchers.at(trackerIndex);
+        // If it is pulled atom, it should be simple matcher with one tagId.
+        int pullTagId = -1;
+        for (int tagId : atomMatcher->getTagIds()) {
+            if (statsPullerManager.PullerForMatcherExists(tagId)) {
+                if (atomMatcher->getTagIds().size() != 1) {
+                    return false;
+                }
+                pullTagId = tagId;
+            }
+        }
+
+        int conditionIndex = -1;
+        if (metric.has_condition()) {
+            handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap,
+                                       metric.links(), allConditionTrackers, conditionIndex,
+                                       conditionToMetricMap);
+        }
+
+        sp<MetricProducer> gaugeProducer =
+                new GaugeMetricProducer(metric, conditionIndex, wizard, pullTagId);
+        allMetricProducers.push_back(gaugeProducer);
+    }
     return true;
 }
 
@@ -368,6 +408,7 @@
         ALOGE("initLogMatchingTrackers failed");
         return false;
     }
+    ALOGD("initLogMatchingTrackers succeed...");
 
     if (!initConditions(config, logTrackerMap, conditionTrackerMap, allConditionTrackers,
                         trackerToConditionMap)) {
diff --git a/cmds/statsd/src/stats_events_copy.proto b/cmds/statsd/src/stats_events_copy.proto
index 9470372..898856b 100644
--- a/cmds/statsd/src/stats_events_copy.proto
+++ b/cmds/statsd/src/stats_events_copy.proto
@@ -61,6 +61,7 @@
         UidProcessStateChanged uid_process_state_changed = 27;
         ProcessLifeCycleStateChanged process_life_cycle_state_changed = 28;
         ScreenStateChanged screen_state_changed = 29;
+        DeviceTemperatureReported device_temperature_reported = 33;
         // TODO: Reorder the numbering so that the most frequent occur events occur in the first 15.
     }
 }
@@ -101,6 +102,17 @@
  */
 
 /**
+ * Logs the temperature of the device, in tenths of a degree Celsius.
+ *
+ * Logged from:
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message DeviceTemperatureReported {
+    // Temperature in tenths of a degree C.
+    optional int32 temperature = 1;
+}
+
+/**
  * Logs when the screen state changes.
  *
  * Logged from:
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index 87884b33..f3e6894 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -230,7 +230,9 @@
 
   repeated DurationMetric duration_metric = 5;
 
-  repeated LogEntryMatcher log_entry_matcher = 6;
+  repeated GaugeMetric gauge_metric = 6;
 
-  repeated Condition condition = 7;
+  repeated LogEntryMatcher log_entry_matcher = 7;
+
+  repeated Condition condition = 8;
 }
diff --git a/cmds/statsd/tests/MetricsManager_test.cpp b/cmds/statsd/tests/MetricsManager_test.cpp
index e8e4d8b..7333785 100644
--- a/cmds/statsd/tests/MetricsManager_test.cpp
+++ b/cmds/statsd/tests/MetricsManager_test.cpp
@@ -17,6 +17,7 @@
 #include "src/condition/ConditionTracker.h"
 #include "src/matchers/LogMatchingTracker.h"
 #include "src/metrics/CountMetricProducer.h"
+#include "src/metrics/GaugeMetricProducer.h"
 #include "src/metrics/MetricProducer.h"
 #include "src/metrics/ValueMetricProducer.h"
 #include "src/metrics/metrics_manager_util.h"