1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #pragma once
18 
19 #include <stddef.h>
20 #include <stdint.h>
21 
22 #include <atomic>
23 #include <thread>
24 #include <unordered_map>
25 
26 class StatsSocketLossReporter {
27 public:
28     static StatsSocketLossReporter& getInstance();
29 
30     void noteDrop(int32_t error, int32_t atomId);
31 
32     /**
33      * @brief Dump loss info into statsd as a STATS_SOCKET_LOSS_REPORTED atom instance
34      *
35      * @param forceDump skip cooldown timer evaluation
36      * @return true if atom have been written into the socket successfully
37      * @return false if atom have been written into the socket with an error
38      */
39     void dumpAtomsLossStats(bool forceDump = false);
40 
41     ~StatsSocketLossReporter();
42 
43 private:
44     StatsSocketLossReporter();
45 
46     void startCooldownTimer(int64_t elapsedRealtimeNanos);
47     bool isCooldownTimerActive(int64_t elapsedRealtimeNanos) const;
48 
49     const int32_t mUid;
50     std::atomic_int64_t mFirstTsNanos = 0;
51     std::atomic_int64_t mLastTsNanos = 0;
52     std::atomic_int64_t mCooldownTimerFinishAtNanos = 0;
53 
54     // Loss info data will be logged to statsd as a regular AStatsEvent
55     // which means it needs to obey event size limitations (4kB)
56     // for N tag ids the loss info might take N * 12 + 8 + 8 + 4 bytes
57     // defining guardrail as a 100 tag ids should limit the atom size to
58     // 100 * 12 + 8 + 8 + 4 ~ 1.2kB
59     const size_t kMaxAtomTagsCount = 100;
60 
61     const int64_t kCoolDownTimerDurationNanos = 10 * 1000 * 1000;  // 10ms
62 
63     struct HashPair final {
64         template <class TFirst, class TSecond>
operatorfinal65         size_t operator()(const std::pair<TFirst, TSecond>& p) const noexcept {
66             uintmax_t hash = std::hash<TFirst>{}(p.first);
67             hash <<= sizeof(uintmax_t) * 4;
68             hash ^= std::hash<TSecond>{}(p.second);
69             return std::hash<uintmax_t>{}(hash);
70         }
71     };
72 
73     // guards access to below mLossInfo
74     mutable std::mutex mMutex;
75 
76     using LossInfoKey = std::pair<int, int>;  // [error, tag]
77 
78     // Represents loss info as a counter per [error, tag] pair
79     std::unordered_map<LossInfoKey, int, HashPair> mLossInfo;
80 
81     // tracks guardrail kMaxAtomTagsCount hit count
82     int32_t mOverflowCounter = 0;
83 };
84