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