1 // Copyright 2023 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 #include <optional>
17 #include <queue>
18 
19 #include "pw_bluetooth_sapphire/internal/host/common/assert.h"
20 #include "pw_bluetooth_sapphire/internal/host/common/inspect.h"
21 #include "pw_bluetooth_sapphire/internal/host/common/smart_task.h"
22 
23 namespace bt {
24 
25 // This class is a utility for wrapping a numeric Inspect property, such as
26 // IntProperty or UintProperty, such that value updates are reversed after
27 // |expiry_duration|. This is useful for creating properties like "disconnects
28 // in the past 10 minutes". Note that this is not very space efficient and
29 // should not be used for properties that get updated extremely frequently.
30 //
31 // |NumericPropertyT| is an inspect property like inspect::IntProperty, and
32 // |ValueT| is the internal value like int64_t. Use the convenience types below
33 // to simplify type declaration.
34 template <typename NumericPropertyT, typename ValueT>
35 class WindowedInspectNumericProperty {
36  public:
37   // |expiry_duration| is the time duration after which changes should be
38   // reversed. |min_resolution| is the smallest duration between changes such
39   // that they are reversed independently. Changes closer to this interval may
40   // be batched together for expiry, biased towards earlier expiry than
41   // |expiry_duration|. May be 0 (default) to disable batching.
42   explicit WindowedInspectNumericProperty(
43       pw::async::Dispatcher& pw_dispatcher,
44       pw::chrono::SystemClock::duration expiry_duration =
45           std::chrono::minutes(10),
46       pw::chrono::SystemClock::duration min_resolution =
47           pw::chrono::SystemClock::duration())
expiry_duration_(expiry_duration)48       : expiry_duration_(expiry_duration),
49         min_resolution_(min_resolution),
50         expiry_task_(pw_dispatcher,
51                      [this](pw::async::Context /*ctx*/, pw::Status status) {
52                        if (!status.ok()) {
53                          return;
54                        }
55 
56                        PW_CHECK(!values_.empty());
57                        auto oldest_value = values_.front();
58                        // Undo expiring value.
59                        property_.Subtract(oldest_value.second);
60                        values_.pop();
61                        StartExpiryTimeout();
62                      }),
63         pw_dispatcher_(pw_dispatcher) {}
64   virtual ~WindowedInspectNumericProperty() = default;
65 
66   // Allow moving, disallow copying.
67   WindowedInspectNumericProperty(const WindowedInspectNumericProperty& other) =
68       delete;
69   WindowedInspectNumericProperty(
70       WindowedInspectNumericProperty&& other) noexcept = default;
71   WindowedInspectNumericProperty& operator=(
72       const WindowedInspectNumericProperty& other) = delete;
73   WindowedInspectNumericProperty& operator=(
74       WindowedInspectNumericProperty&& other) noexcept = default;
75 
76   // Set the underlying inspect property, and reset the expiry timer. This is
77   // used by the convenience types that implement AttachInspect() below.
SetProperty(NumericPropertyT property)78   void SetProperty(NumericPropertyT property) {
79     property_ = std::move(property);
80     expiry_task_.Cancel();
81     // Clear queue without running pop() in a loop.
82     values_ = decltype(values_)();
83   }
84 
85   // Create an inspect property named "name" as a child of "node".
86   //
87   // AttachInspect is only supported for the convenience types declared below.
AttachInspect(::inspect::Node &,std::string)88   virtual void AttachInspect(::inspect::Node& /*node*/, std::string /*name*/) {
89     PW_CHECK(false, "AttachInspect not implemented for NumericPropertyT");
90   }
91 
92   // Add the given value to the value of this numeric metric.
Add(ValueT value)93   void Add(ValueT value) {
94     property_.Add(value);
95     pw::chrono::SystemClock::time_point now = pw_dispatcher_.now();
96     if (!values_.empty()) {
97       // If the most recent change's age is less than |min_resolution_|, merge
98       // this change to it
99       if (now < values_.back().first + min_resolution_) {
100         values_.back().second += value;
101         return;
102       }
103     }
104     values_.push({now, value});
105     StartExpiryTimeout();
106   }
107 
108   // Return true if property is valid.
109   explicit operator bool() { return property_; }
110 
111  private:
StartExpiryTimeout()112   void StartExpiryTimeout() {
113     if (values_.empty() || expiry_task_.is_pending()) {
114       return;
115     }
116 
117     auto oldest_value = values_.front();
118     pw::chrono::SystemClock::time_point oldest_value_time = oldest_value.first;
119     pw::chrono::SystemClock::time_point expiry_time =
120         expiry_duration_ + oldest_value_time;
121     expiry_task_.PostAt(expiry_time);
122   }
123 
124   // This is not very space efficient, requiring a node for every value during
125   // the expiry_duration_.
126   std::queue<std::pair<pw::chrono::SystemClock::time_point, ValueT>> values_;
127 
128   NumericPropertyT property_;
129   pw::chrono::SystemClock::duration expiry_duration_;
130   pw::chrono::SystemClock::duration min_resolution_;
131 
132   using SelfT = WindowedInspectNumericProperty<NumericPropertyT, ValueT>;
133   SmartTask expiry_task_;
134   pw::async::Dispatcher& pw_dispatcher_;
135 };
136 
137 // Convenience WindowedInspectNumericProperty types:
138 
139 #define CREATE_WINDOWED_TYPE(property_t, inner_t)                              \
140   class WindowedInspect##property_t##Property final                            \
141       : public WindowedInspectNumericProperty<::inspect::property_t##Property, \
142                                               inner_t> {                       \
143    public:                                                                     \
144     using WindowedInspectNumericProperty<                                      \
145         ::inspect::property_t##Property,                                       \
146         inner_t>::WindowedInspectNumericProperty;                              \
147     void AttachInspect(::inspect::Node& node, std::string name) override {     \
148       this->SetProperty(node.Create##property_t(name, inner_t()));             \
149     }                                                                          \
150   };
151 
152 // WindowedInspectIntProperty
153 CREATE_WINDOWED_TYPE(Int, int64_t)
154 // WindowedInspectUintProperty
155 CREATE_WINDOWED_TYPE(Uint, uint64_t)
156 
157 #undef CREATE_WINDOWED_TYPE
158 
159 }  // namespace bt
160