summaryrefslogtreecommitdiff
path: root/statsd/src/metrics/GaugeMetricProducer.h
blob: 51df9e656d937114d0e00628311fd8dbf7038592 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
/*
 * 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 <android/util/ProtoOutputStream.h>
#include <gtest/gtest_prod.h>
#include "../condition/ConditionTracker.h"
#include "../external/PullDataReceiver.h"
#include "../external/StatsPullerManager.h"
#include "../matchers/matcher_util.h"
#include "../matchers/EventMatcherWizard.h"
#include "MetricProducer.h"
#include "src/statsd_config.pb.h"
#include "../stats_util.h"

namespace android {
namespace os {
namespace statsd {

struct GaugeAtom {
    GaugeAtom(std::shared_ptr<vector<FieldValue>> fields, int64_t elapsedTimeNs)
        : mFields(fields), mElapsedTimestampNs(elapsedTimeNs) {
    }
    std::shared_ptr<vector<FieldValue>> mFields;
    int64_t mElapsedTimestampNs;
};

struct GaugeBucket {
    int64_t mBucketStartNs;
    int64_t mBucketEndNs;
    std::vector<GaugeAtom> mGaugeAtoms;

    // Maps the field/value pairs of an atom to a list of timestamps used to deduplicate atoms.
    std::unordered_map<AtomDimensionKey, std::vector<int64_t>> mAggregatedAtoms;
};

typedef std::unordered_map<MetricDimensionKey, std::vector<GaugeAtom>>
    DimToGaugeAtomsMap;

// 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 gauge at the earliest time of the bucket when the condition is met.
class GaugeMetricProducer : public MetricProducer, public virtual PullDataReceiver {
public:
    GaugeMetricProducer(
            const ConfigKey& key, const GaugeMetric& gaugeMetric, const int conditionIndex,
            const vector<ConditionState>& initialConditionCache,
            const sp<ConditionWizard>& conditionWizard, const uint64_t protoHash,
            const int whatMatcherIndex, const sp<EventMatcherWizard>& matcherWizard,
            const int pullTagId, const int triggerAtomId, const int atomId,
            const int64_t timeBaseNs, const int64_t startTimeNs,
            const sp<StatsPullerManager>& pullerManager,
            const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap = {},
            const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
                    eventDeactivationMap = {},
            const size_t dimensionSoftLimit = StatsdStats::kDimensionKeySizeSoftLimit,
            const size_t dimensionHardLimit = StatsdStats::kDimensionKeySizeHardLimit);

    virtual ~GaugeMetricProducer();

    // Handles when the pulled data arrives.
    void onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& data, PullResult pullResult,
                      int64_t originalPullTimeNs) override;

    // Determine if metric needs to pull
    bool isPullNeeded() const override {
        std::lock_guard<std::mutex> lock(mMutex);
        return mIsActive && (mCondition == ConditionState::kTrue);
    };

    // GaugeMetric needs to immediately trigger another pull when we create the partial bucket.
    void notifyAppUpgradeInternalLocked(const int64_t eventTimeNs) override {
        flushLocked(eventTimeNs);
        if (mIsPulled && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE && mIsActive) {
            pullAndMatchEventsLocked(eventTimeNs);
        }
    };

    // GaugeMetric needs to immediately trigger another pull when we create the partial bucket.
    void onStatsdInitCompleted(const int64_t& eventTimeNs) override {
        std::lock_guard<std::mutex> lock(mMutex);

        flushLocked(eventTimeNs);
        if (mIsPulled && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE && mIsActive) {
            pullAndMatchEventsLocked(eventTimeNs);
        }
    };

    MetricType getMetricType() const override {
        return METRIC_TYPE_GAUGE;
    }

protected:
    void onMatchedLogEventInternalLocked(
            const size_t matcherIndex, const MetricDimensionKey& eventKey,
            const ConditionKey& conditionKey, bool condition, const LogEvent& event,
            const std::map<int, HashableDimensionKey>& statePrimaryKeys) override;

private:
    void onDumpReportLocked(const int64_t dumpTimeNs,
                            const bool include_current_partial_bucket,
                            const bool erase_data,
                            const DumpLatency dumpLatency,
                            std::set<string> *str_set,
                            android::util::ProtoOutputStream* protoOutput) override;
    void clearPastBucketsLocked(const int64_t dumpTimeNs) override;

    // Internal interface to handle condition change.
    void onConditionChangedLocked(const bool conditionMet, const int64_t eventTime) override;

    // Internal interface to handle active state change.
    void onActiveStateChangedLocked(const int64_t eventTimeNs, const bool isActive) override;

    // Internal interface to handle sliced condition change.
    void onSlicedConditionMayChangeLocked(bool overallCondition, const int64_t eventTime) override;

    // Internal function to calculate the current used bytes.
    size_t byteSizeLocked() const override;

    void dumpStatesLocked(int out, bool verbose) const override;

    void dropDataLocked(const int64_t dropTimeNs) override;

    // Util function to flush the old packet.
    void flushIfNeededLocked(const int64_t& eventTime) override;

    void flushCurrentBucketLocked(const int64_t& eventTimeNs,
                                  const int64_t& nextBucketStartTimeNs) override;

    void prepareFirstBucketLocked() override;

    // Only call if mCondition == ConditionState::kTrue && metric is active.
    void pullAndMatchEventsLocked(const int64_t timestampNs);

    optional<InvalidConfigReason> onConfigUpdatedLocked(
            const StatsdConfig& config, const int configIndex, const int metricIndex,
            const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
            const std::unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
            const std::unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
            const sp<EventMatcherWizard>& matcherWizard,
            const std::vector<sp<ConditionTracker>>& allConditionTrackers,
            const std::unordered_map<int64_t, int>& conditionTrackerMap,
            const sp<ConditionWizard>& wizard,
            const std::unordered_map<int64_t, int>& metricToActivationMap,
            std::unordered_map<int, std::vector<int>>& trackerToMetricMap,
            std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
            std::unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap,
            std::unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
            std::vector<int>& metricsWithActivation) override;

    inline bool isRandomNSamples() const {
        return (mTriggerAtomId == -1 && mSamplingType == GaugeMetric::FIRST_N_SAMPLES) ||
               mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE;
    }

    int mWhatMatcherIndex;

    sp<EventMatcherWizard> mEventMatcherWizard;

    sp<StatsPullerManager> mPullerManager;
    // tagId for pulled data. -1 if this is not pulled
    const int mPullTagId;

    // tagId for atoms that trigger the pulling, if any
    const int mTriggerAtomId;

    // tagId for output atom
    const int mAtomId;

    // if this is pulled metric
    const bool mIsPulled;

    // Save the past buckets and we can clear when the StatsLogReport is dumped.
    std::unordered_map<MetricDimensionKey, std::vector<GaugeBucket>> mPastBuckets;

    // The current partial bucket.
    std::shared_ptr<DimToGaugeAtomsMap> mCurrentSlicedBucket;

    // The current full bucket for anomaly detection. This is updated to the latest value seen for
    // this slice (ie, for partial buckets, we use the last partial bucket in this full bucket).
    std::shared_ptr<DimToValMap> mCurrentSlicedBucketForAnomaly;

    const int64_t mMinBucketSizeNs;

    // Translate Atom based bucket to single numeric value bucket for anomaly and updates the map
    // for each slice with the latest value.
    void updateCurrentSlicedBucketForAnomaly();

    // Allowlist of fields to report. Empty means all are reported.
    std::vector<Matcher> mFieldMatchers;

    GaugeMetric::SamplingType mSamplingType;

    const int64_t mMaxPullDelayNs;

    // apply an allowlist on the original input
    std::shared_ptr<vector<FieldValue>> getGaugeFields(const LogEvent& event);

    // Util function to check whether the specified dimension hits the guardrail.
    bool hitGuardRailLocked(const MetricDimensionKey& newKey);

    static const size_t kBucketSize = sizeof(GaugeBucket{});

    const size_t mDimensionSoftLimit;

    const size_t mDimensionHardLimit;

    const size_t mGaugeAtomsPerDimensionLimit;

    // Tracks if the dimension guardrail has been hit in the current report.
    bool mDimensionGuardrailHit;

    FRIEND_TEST(GaugeMetricProducerTest, TestPulledEventsWithCondition);
    FRIEND_TEST(GaugeMetricProducerTest, TestPulledEventsWithSlicedCondition);
    FRIEND_TEST(GaugeMetricProducerTest, TestPulledEventsNoCondition);
    FRIEND_TEST(GaugeMetricProducerTest, TestPulledWithAppUpgradeDisabled);
    FRIEND_TEST(GaugeMetricProducerTest, TestPulledEventsAnomalyDetection);
    FRIEND_TEST(GaugeMetricProducerTest, TestFirstBucket);
    FRIEND_TEST(GaugeMetricProducerTest, TestPullOnTrigger);
    FRIEND_TEST(GaugeMetricProducerTest, TestPullNWithoutTrigger);
    FRIEND_TEST(GaugeMetricProducerTest, TestRemoveDimensionInOutput);
    FRIEND_TEST(GaugeMetricProducerTest, TestPullDimensionalSampling);

    FRIEND_TEST(GaugeMetricProducerTest_PartialBucket, TestPushedEvents);
    FRIEND_TEST(GaugeMetricProducerTest_PartialBucket, TestPulled);

    FRIEND_TEST(ConfigUpdateTest, TestUpdateGaugeMetrics);

    FRIEND_TEST(MetricsManagerUtilDimLimitTest, TestDimLimit);

    FRIEND_TEST(ConfigUpdateDimLimitTest, TestDimLimit);
};

}  // namespace statsd
}  // namespace os
}  // namespace android