Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1d4d6860 authored by Yangster's avatar Yangster
Browse files

Gauge metric producer.

Test: manual tests passed for pushed device temperature.
Change-Id: I7592a4c04666606b745cdb41db8f9d8a96a966da
parent f8a6f1da
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -46,6 +46,7 @@ statsd_common_src := \
    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 \
+16 −1
Original line number Diff line number Diff line
@@ -142,6 +142,9 @@ static StatsdConfig build_fake_config() {
    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 @@ static StatsdConfig build_fake_config() {
    // 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 @@ static StatsdConfig build_fake_config() {
    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();
+232 −0
Original line number Diff line number Diff line
/*
* 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
+97 −0
Original line number Diff line number Diff line
/*
 * 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
+2 −1
Original line number Diff line number Diff line
@@ -146,7 +146,8 @@ void MetricsManager::onLogEvent(const LogEvent& event) {
                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 */);
                }
            }
        }
Loading