xref: /aosp_15_r20/external/pigweed/pw_log/public/pw_log/rate_limited.h (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1 // Copyright 2024 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 
15 #pragma once
16 
17 #include "pw_chrono/system_clock.h"
18 #include "pw_log/log.h"
19 
20 // rate_limited adds a wrapper around a normal PW_LOG call to provide a rate
21 // limitor parameter to suppress chatty logs and provide info on how many logs
22 // were suppressed
23 //   PW_LOG_EVERY_N_DURATION(level, min_interval_between_logs, msg, ...)
24 //     - Required.
25 //       level - An integer level as defined by pw_log/levels.h
26 //       min_interval_between_logs - A std::chrono::duration of the minimum
27 //       interval between
28 //              two of the same logs.
29 //       msg - Formattable message, same as you would use for PW_LOG or variants
30 //
31 // Does not check that input parameters have changed to un-suppress logs.
32 
33 namespace pw::log::internal {
34 
35 class RateLimiter {
36  public:
37   struct PollResult {
38     uint16_t count;
39     uint16_t logs_per_s;
40   };
41 
RateLimiter()42   explicit RateLimiter() {}
43   ~RateLimiter() = default;
44 
45   PollResult Poll(chrono::SystemClock::duration min_interval_between_logs);
46 
47  private:
48   uint32_t count_ = 0;
49   chrono::SystemClock::time_point last_timestamp_;
50 };
51 
52 }  // namespace pw::log::internal
53 
54 // PW_LOG_EVERY_N_DURATION(level, min_interval_between_logs, msg, ...)
55 //
56 // Logs a message at the given level, only it hasn't been logged within
57 // `min_interval_between_logs`.
58 //
59 // Inputs:
60 //    level - An integer level as devifen by pw_log/levels.h
61 //    min_interval_between_logs - A pw::chrono::SystemClock::duration that
62 //      defines the minimum time interval between unsuppressed logs
63 //    msg - Formattable message, same as you would use for PW_LOG or variants
64 //
65 // Includes a summary of how many logs were skipped, and a rough rate in
66 // integer seconds.
67 //
68 // NOTE: This macro is NOT threadsafe. The underlying object being modified by
69 // multiple threads calling the macro context may result in undefined behavior.
70 //
71 // Intended to supplement and replace widespread use of EVERY_N for logging. The
72 // main benefit this provides is responsiveness for bursty logs.
73 // LOG_RATE_LIMITED will log as soon as a burst starts - provided the
74 // `min_interval_between_logs` has elapsed - while EVERY_N may sit idle for a
75 // full period depending on the count state.
76 //
77 // Note that this will not log until called again, so the summary may include
78 // skipped logs from a prior burst.
79 #define PW_LOG_EVERY_N_DURATION(level, min_interval_between_logs, msg, ...) \
80   do {                                                                      \
81     static pw::log::internal::RateLimiter rate_limiter;                     \
82                                                                             \
83     if (auto result = rate_limiter.Poll(min_interval_between_logs);         \
84         result.count == std::numeric_limits<uint16_t>::max()) {             \
85       PW_LOG(level,                                                         \
86              PW_LOG_LEVEL,                                                  \
87              PW_LOG_MODULE_NAME,                                            \
88              PW_LOG_FLAGS,                                                  \
89              msg " (skipped %d or more, %d/s)",                             \
90              ##__VA_ARGS__,                                                 \
91              static_cast<unsigned>(result.count),                           \
92              static_cast<unsigned>(result.logs_per_s));                     \
93     } else if (result.count != 0) {                                         \
94       PW_LOG(level,                                                         \
95              PW_LOG_LEVEL,                                                  \
96              PW_LOG_MODULE_NAME,                                            \
97              PW_LOG_FLAGS,                                                  \
98              msg " (skipped %d, %d/s)",                                     \
99              ##__VA_ARGS__,                                                 \
100              static_cast<unsigned>(result.count - 1),                       \
101              static_cast<unsigned>(result.logs_per_s));                     \
102     }                                                                       \
103   } while (0)
104