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

Commit e04b9991 authored by Yao Chen's avatar Yao Chen Committed by android-build-merger
Browse files

Merge "Add a condition timer to track the duration of condition being true." into qt-dev

am: 32fa3c23

Change-Id: Iae054510fb5511290fc51c4b2bccd8f99ccc20e4
parents 7771243d 32fa3c23
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -235,6 +235,7 @@ cc_test {
        "tests/condition/CombinationConditionTracker_test.cpp",
        "tests/condition/SimpleConditionTracker_test.cpp",
        "tests/condition/StateTracker_test.cpp",
        "tests/condition/ConditionTimer_test.cpp",
        "tests/metrics/OringDurationTracker_test.cpp",
        "tests/metrics/MaxDurationTracker_test.cpp",
        "tests/metrics/CountMetricProducer_test.cpp",
+83 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#pragma once

#include <gtest/gtest_prod.h>
#include <stdint.h>

namespace android {
namespace os {
namespace statsd {

/**
 * A simple stopwatch to time the duration of condition being true.
 *
 * The owner of the stopwatch (MetricProducer) is responsible to notify the stopwatch when condition
 * changes (start/pause), and when to start a new bucket (a new lap basically). All timestamps
 * should be elapsedRealTime in nano seconds.
 *
 * Keep the timer simple and inline everything. This class is *NOT* thread safe. Caller is
 * responsible for thread safety.
 */
class ConditionTimer {
public:
    explicit ConditionTimer(bool initCondition, int64_t bucketStartNs) : mCondition(initCondition) {
        if (initCondition) {
            mLastConditionTrueTimestampNs = bucketStartNs;
        }
    };

    // Tracks how long the condition has been stayed true in the *current* bucket.
    // When a new bucket is created, this value will be reset to 0.
    int64_t mTimerNs = 0;

    // Last elapsed real timestamp when condition turned to true
    // When a new bucket is created and the condition is true, then the timestamp is set
    // to be the bucket start timestamp.
    int64_t mLastConditionTrueTimestampNs = 0;

    bool mCondition = false;

    int64_t newBucketStart(int64_t nextBucketStartNs) {
        if (mCondition) {
            mTimerNs += (nextBucketStartNs - mLastConditionTrueTimestampNs);
            mLastConditionTrueTimestampNs = nextBucketStartNs;
        }

        int64_t temp = mTimerNs;
        mTimerNs = 0;
        return temp;
    }

    void onConditionChanged(bool newCondition, int64_t timestampNs) {
        if (newCondition == mCondition) {
            return;
        }
        mCondition = newCondition;
        if (newCondition) {
            mLastConditionTrueTimestampNs = timestampNs;
        } else {
            mTimerNs += (timestampNs - mLastConditionTrueTimestampNs);
        }
    }

    FRIEND_TEST(ConditionTimerTest, TestTimer_Inital_False);
    FRIEND_TEST(ConditionTimerTest, TestTimer_Inital_True);
};

}  // namespace statsd
}  // namespace os
}  // namespace android
 No newline at end of file
+16 −4
Original line number Diff line number Diff line
@@ -72,6 +72,7 @@ const int FIELD_ID_VALUES = 9;
const int FIELD_ID_BUCKET_NUM = 4;
const int FIELD_ID_START_BUCKET_ELAPSED_MILLIS = 5;
const int FIELD_ID_END_BUCKET_ELAPSED_MILLIS = 6;
const int FIELD_ID_CONDITION_TRUE_NS = 10;

const Value ZERO_LONG((int64_t)0);
const Value ZERO_DOUBLE((int64_t)0);
@@ -107,7 +108,8 @@ ValueMetricProducer::ValueMetricProducer(
      mCurrentBucketIsInvalid(false),
      mMaxPullDelayNs(metric.max_pull_delay_sec() > 0 ? metric.max_pull_delay_sec() * NS_PER_SEC
                                                      : StatsdStats::kPullMaxDelayNs),
      mSplitBucketForAppUpgrade(metric.split_bucket_for_app_upgrade()) {
      mSplitBucketForAppUpgrade(metric.split_bucket_for_app_upgrade()),
      mConditionTimer(mCondition == ConditionState::kTrue, timeBaseNs) {
    int64_t bucketSizeMills = 0;
    if (metric.has_bucket()) {
        bucketSizeMills = TimeUnitToBucketSizeInMillisGuardrailed(key.GetUid(), metric.bucket());
@@ -153,6 +155,7 @@ ValueMetricProducer::ValueMetricProducer(
    // flushIfNeeded to adjust start and end to bucket boundaries.
    // Adjust start for partial bucket
    mCurrentBucketStartTimeNs = startTimeNs;
    mConditionTimer.newBucketStart(mCurrentBucketStartTimeNs);
    // Kicks off the puller immediately if condition is true and diff based.
    if (mIsPulled && mCondition == ConditionState::kTrue && mUseDiff) {
        pullAndMatchEventsLocked(startTimeNs, mCondition);
@@ -293,6 +296,11 @@ void ValueMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs,
                protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_NUM,
                                   (long long)(getBucketNumFromEndTimeNs(bucket.mBucketEndNs)));
            }
            // only write the condition timer value if the metric has a condition.
            if (mConditionTrackerIndex >= 0) {
                protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_CONDITION_TRUE_NS,
                                   (long long)bucket.mConditionTrueNs);
            }
            for (int i = 0; i < (int)bucket.valueIndex.size(); i ++) {
                int index = bucket.valueIndex[i];
                const Value& value = bucket.values[i];
@@ -386,19 +394,19 @@ void ValueMetricProducer::onConditionChangedLocked(const bool condition,
            resetBase();
        }
        mCondition = newCondition;

    } else {
        VLOG("Skip event due to late arrival: %lld vs %lld", (long long)eventTimeNs,
             (long long)mCurrentBucketStartTimeNs);
        StatsdStats::getInstance().noteConditionChangeInNextBucket(mMetricId);
        invalidateCurrentBucket();
        // Something weird happened. If we received another event if the future, the condition might
        // Something weird happened. If we received another event in the future, the condition might
        // be wrong.
        mCondition = initialCondition(mConditionTrackerIndex);
    }

    // This part should alway be called.
    flushIfNeededLocked(eventTimeNs);
    mConditionTimer.onConditionChanged(mCondition, eventTimeNs);
}

void ValueMetricProducer::pullAndMatchEventsLocked(const int64_t timestampNs, ConditionState condition) {
@@ -799,12 +807,14 @@ void ValueMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs,
         (int)mCurrentSlicedBucket.size());
    int64_t fullBucketEndTimeNs = getCurrentBucketEndTimeNs();
    int64_t bucketEndTime = eventTimeNs < fullBucketEndTimeNs ? eventTimeNs : fullBucketEndTimeNs;

    // Close the current bucket.
    int64_t conditionTrueDuration = mConditionTimer.newBucketStart(bucketEndTime);
    bool isBucketLargeEnough = bucketEndTime - mCurrentBucketStartTimeNs >= mMinBucketSizeNs;
    if (isBucketLargeEnough && !mCurrentBucketIsInvalid) {
        // The current bucket is large enough to keep.
        for (const auto& slice : mCurrentSlicedBucket) {
            ValueBucket bucket = buildPartialBucket(bucketEndTime, slice.second);
            bucket.mConditionTrueNs = conditionTrueDuration;
            // it will auto create new vector of ValuebucketInfo if the key is not found.
            if (bucket.valueIndex.size() > 0) {
                auto& bucketList = mPastBuckets[slice.first];
@@ -817,6 +827,8 @@ void ValueMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs,

    appendToFullBucket(eventTimeNs, fullBucketEndTimeNs);
    initCurrentSlicedBucket(nextBucketStartTimeNs);
    // Update the condition timer again, in case we skipped buckets.
    mConditionTimer.newBucketStart(nextBucketStartTimeNs);
    mCurrentBucketNum += numBucketsForward;
}

+12 −6
Original line number Diff line number Diff line
@@ -19,12 +19,13 @@
#include <gtest/gtest_prod.h>
#include <utils/threads.h>
#include <list>
#include "../anomaly/AnomalyTracker.h"
#include "../condition/ConditionTracker.h"
#include "../external/PullDataReceiver.h"
#include "../external/StatsPullerManager.h"
#include "../matchers/EventMatcherWizard.h"
#include "../stats_log_util.h"
#include "anomaly/AnomalyTracker.h"
#include "condition/ConditionTimer.h"
#include "condition/ConditionTracker.h"
#include "external/PullDataReceiver.h"
#include "external/StatsPullerManager.h"
#include "matchers/EventMatcherWizard.h"
#include "stats_log_util.h"
#include "MetricProducer.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"

@@ -37,6 +38,9 @@ struct ValueBucket {
    int64_t mBucketEndNs;
    std::vector<int> valueIndex;
    std::vector<Value> values;
    // If the metric has no condition, then this field is just wasted.
    // When we tune statsd memory usage in the future, this is a candidate to optimize.
    int64_t mConditionTrueNs;
};


@@ -228,6 +232,8 @@ private:

    const bool mSplitBucketForAppUpgrade;

    ConditionTimer mConditionTimer;

    FRIEND_TEST(ValueMetricProducerTest, TestAnomalyDetection);
    FRIEND_TEST(ValueMetricProducerTest, TestBaseSetOnConditionChange);
    FRIEND_TEST(ValueMetricProducerTest, TestBucketBoundariesOnAppUpgrade);
+2 −0
Original line number Diff line number Diff line
@@ -129,6 +129,8 @@ message ValueBucketInfo {
  optional int64 start_bucket_elapsed_millis = 5;

  optional int64 end_bucket_elapsed_millis = 6;

  optional int64 condition_true_nanos = 10;
}

message ValueMetricData {
Loading