sp_weights/
weight_meter.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Contains the `WeightMeter` primitive to meter weight usage.
19
20use super::Weight;
21
22use sp_arithmetic::Perbill;
23
24/// Meters consumed weight and a hard limit for the maximal consumable weight.
25///
26/// Can be used to check if enough weight for an operation is available before committing to it.
27///
28/// # Example
29///
30/// ```rust
31/// use sp_weights::{Weight, WeightMeter};
32///
33/// // The weight is limited to (10, 0).
34/// let mut meter = WeightMeter::with_limit(Weight::from_parts(10, 0));
35/// // There is enough weight remaining for an operation with (6, 0) weight.
36/// assert!(meter.try_consume(Weight::from_parts(6, 0)).is_ok());
37/// assert_eq!(meter.remaining(), Weight::from_parts(4, 0));
38/// // There is not enough weight remaining for an operation with (5, 0) weight.
39/// assert!(!meter.try_consume(Weight::from_parts(5, 0)).is_ok());
40/// // The total limit is obviously unchanged:
41/// assert_eq!(meter.limit(), Weight::from_parts(10, 0));
42/// ```
43#[derive(Debug, Clone)]
44pub struct WeightMeter {
45	/// The already consumed weight.
46	consumed: Weight,
47
48	/// The maximal consumable weight.
49	limit: Weight,
50}
51
52impl WeightMeter {
53	/// Creates [`Self`] from `consumed` and `limit`.
54	pub fn with_consumed_and_limit(consumed: Weight, limit: Weight) -> Self {
55		Self { consumed, limit }
56	}
57
58	/// Creates [`Self`] from a limit for the maximal consumable weight.
59	pub fn with_limit(limit: Weight) -> Self {
60		Self { consumed: Weight::zero(), limit }
61	}
62
63	/// Creates [`Self`] with the maximal possible limit for the consumable weight.
64	pub fn new() -> Self {
65		Self::with_limit(Weight::MAX)
66	}
67
68	/// Change the limit to the given `weight`.
69	///
70	/// The actual weight will be determined by `min(weight, self.remaining())`.
71	pub fn limit_to(self, weight: Weight) -> Self {
72		Self::with_limit(self.remaining().min(weight))
73	}
74
75	/// The already consumed weight.
76	pub fn consumed(&self) -> Weight {
77		self.consumed
78	}
79
80	/// The limit can ever be accrued.
81	pub fn limit(&self) -> Weight {
82		self.limit
83	}
84
85	/// The remaining weight that can still be consumed.
86	pub fn remaining(&self) -> Weight {
87		self.limit.saturating_sub(self.consumed)
88	}
89
90	/// The ratio of consumed weight to the limit.
91	///
92	/// Calculates one ratio per component and returns the largest.
93	///
94	/// # Example
95	/// ```rust
96	/// use sp_weights::{Weight, WeightMeter};
97	/// use sp_arithmetic::Perbill;
98	///
99	/// let mut meter = WeightMeter::with_limit(Weight::from_parts(10, 20));
100	/// // Nothing consumed so far:
101	/// assert_eq!(meter.consumed_ratio(), Perbill::from_percent(0));
102	/// meter.consume(Weight::from_parts(5, 5));
103	/// // The ref-time is the larger ratio:
104	/// assert_eq!(meter.consumed_ratio(), Perbill::from_percent(50));
105	/// meter.consume(Weight::from_parts(1, 10));
106	/// // Now the larger ratio is proof-size:
107	/// assert_eq!(meter.consumed_ratio(), Perbill::from_percent(75));
108	/// // Eventually it reaches 100%:
109	/// meter.consume(Weight::from_parts(4, 0));
110	/// assert_eq!(meter.consumed_ratio(), Perbill::from_percent(100));
111	/// // Saturating the second component won't change anything anymore:
112	/// meter.consume(Weight::from_parts(0, 5));
113	/// assert_eq!(meter.consumed_ratio(), Perbill::from_percent(100));
114	/// ```
115	pub fn consumed_ratio(&self) -> Perbill {
116		let time = Perbill::from_rational(self.consumed.ref_time(), self.limit.ref_time());
117		let pov = Perbill::from_rational(self.consumed.proof_size(), self.limit.proof_size());
118		time.max(pov)
119	}
120
121	/// Consume some weight and defensively fail if it is over the limit. Saturate in any case.
122	#[deprecated(note = "Use `consume` instead. Will be removed after December 2023.")]
123	pub fn defensive_saturating_accrue(&mut self, w: Weight) {
124		self.consume(w);
125	}
126
127	/// Consume some weight and defensively fail if it is over the limit. Saturate in any case.
128	pub fn consume(&mut self, w: Weight) {
129		self.consumed.saturating_accrue(w);
130		debug_assert!(self.consumed.all_lte(self.limit), "Weight counter overflow");
131	}
132
133	/// Consume the given weight after checking that it can be consumed.
134	///
135	/// Returns `Ok` if the weight can be consumed or otherwise an `Err`.
136	pub fn try_consume(&mut self, w: Weight) -> Result<(), ()> {
137		self.consumed.checked_add(&w).map_or(Err(()), |test| {
138			if test.any_gt(self.limit) {
139				Err(())
140			} else {
141				self.consumed = test;
142				Ok(())
143			}
144		})
145	}
146
147	/// Check if the given weight can be consumed.
148	pub fn can_consume(&self, w: Weight) -> bool {
149		self.consumed.checked_add(&w).map_or(false, |t| t.all_lte(self.limit))
150	}
151
152	/// Reclaim the given weight.
153	pub fn reclaim_proof_size(&mut self, s: u64) {
154		self.consumed.saturating_reduce(Weight::from_parts(0, s));
155	}
156}
157
158#[cfg(test)]
159mod tests {
160	use crate::*;
161	use sp_arithmetic::traits::Zero;
162
163	#[test]
164	fn weight_meter_remaining_works() {
165		let mut meter = WeightMeter::with_limit(Weight::from_parts(10, 20));
166
167		assert_eq!(meter.try_consume(Weight::from_parts(5, 0)), Ok(()));
168		assert_eq!(meter.consumed, Weight::from_parts(5, 0));
169		assert_eq!(meter.remaining(), Weight::from_parts(5, 20));
170
171		assert_eq!(meter.try_consume(Weight::from_parts(2, 10)), Ok(()));
172		assert_eq!(meter.consumed, Weight::from_parts(7, 10));
173		assert_eq!(meter.remaining(), Weight::from_parts(3, 10));
174
175		assert_eq!(meter.try_consume(Weight::from_parts(3, 10)), Ok(()));
176		assert_eq!(meter.consumed, Weight::from_parts(10, 20));
177		assert_eq!(meter.remaining(), Weight::from_parts(0, 0));
178	}
179
180	#[test]
181	fn weight_meter_can_consume_works() {
182		let meter = WeightMeter::with_limit(Weight::from_parts(1, 1));
183
184		assert!(meter.can_consume(Weight::from_parts(0, 0)));
185		assert!(meter.can_consume(Weight::from_parts(1, 1)));
186		assert!(!meter.can_consume(Weight::from_parts(0, 2)));
187		assert!(!meter.can_consume(Weight::from_parts(2, 0)));
188		assert!(!meter.can_consume(Weight::from_parts(2, 2)));
189	}
190
191	#[test]
192	fn weight_meter_try_consume_works() {
193		let mut meter = WeightMeter::with_limit(Weight::from_parts(2, 2));
194
195		assert_eq!(meter.try_consume(Weight::from_parts(0, 0)), Ok(()));
196		assert_eq!(meter.try_consume(Weight::from_parts(1, 1)), Ok(()));
197		assert_eq!(meter.try_consume(Weight::from_parts(0, 2)), Err(()));
198		assert_eq!(meter.try_consume(Weight::from_parts(2, 0)), Err(()));
199		assert_eq!(meter.try_consume(Weight::from_parts(2, 2)), Err(()));
200		assert_eq!(meter.try_consume(Weight::from_parts(0, 1)), Ok(()));
201		assert_eq!(meter.try_consume(Weight::from_parts(1, 0)), Ok(()));
202	}
203
204	#[test]
205	fn weight_meter_check_and_can_consume_works() {
206		let mut meter = WeightMeter::new();
207
208		assert!(meter.can_consume(Weight::from_parts(u64::MAX, 0)));
209		assert_eq!(meter.try_consume(Weight::from_parts(u64::MAX, 0)), Ok(()));
210
211		assert!(meter.can_consume(Weight::from_parts(0, u64::MAX)));
212		assert_eq!(meter.try_consume(Weight::from_parts(0, u64::MAX)), Ok(()));
213
214		assert!(!meter.can_consume(Weight::from_parts(0, 1)));
215		assert_eq!(meter.try_consume(Weight::from_parts(0, 1)), Err(()));
216
217		assert!(!meter.can_consume(Weight::from_parts(1, 0)));
218		assert_eq!(meter.try_consume(Weight::from_parts(1, 0)), Err(()));
219
220		assert!(meter.can_consume(Weight::zero()));
221		assert_eq!(meter.try_consume(Weight::zero()), Ok(()));
222	}
223
224	#[test]
225	fn consumed_ratio_works() {
226		let mut meter = WeightMeter::with_limit(Weight::from_parts(10, 20));
227
228		assert_eq!(meter.try_consume(Weight::from_parts(5, 0)), Ok(()));
229		assert_eq!(meter.consumed_ratio(), Perbill::from_percent(50));
230		assert_eq!(meter.try_consume(Weight::from_parts(0, 12)), Ok(()));
231		assert_eq!(meter.consumed_ratio(), Perbill::from_percent(60));
232
233		assert_eq!(meter.try_consume(Weight::from_parts(2, 0)), Ok(()));
234		assert_eq!(meter.consumed_ratio(), Perbill::from_percent(70));
235		assert_eq!(meter.try_consume(Weight::from_parts(0, 4)), Ok(()));
236		assert_eq!(meter.consumed_ratio(), Perbill::from_percent(80));
237
238		assert_eq!(meter.try_consume(Weight::from_parts(3, 0)), Ok(()));
239		assert_eq!(meter.consumed_ratio(), Perbill::from_percent(100));
240		assert_eq!(meter.try_consume(Weight::from_parts(0, 4)), Ok(()));
241		assert_eq!(meter.consumed_ratio(), Perbill::from_percent(100));
242	}
243
244	#[test]
245	fn try_consume_works() {
246		let mut meter = WeightMeter::with_limit(Weight::from_parts(10, 0));
247
248		assert!(meter.try_consume(Weight::from_parts(11, 0)).is_err());
249		assert!(meter.consumed().is_zero(), "No modification");
250
251		assert!(meter.try_consume(Weight::from_parts(9, 0)).is_ok());
252		assert!(meter.try_consume(Weight::from_parts(2, 0)).is_err());
253		assert!(meter.try_consume(Weight::from_parts(1, 0)).is_ok());
254		assert!(meter.remaining().is_zero());
255		assert_eq!(meter.consumed(), Weight::from_parts(10, 0));
256	}
257
258	#[test]
259	fn can_consume_works() {
260		let mut meter = WeightMeter::with_limit(Weight::from_parts(10, 0));
261
262		assert!(!meter.can_consume(Weight::from_parts(11, 0)));
263		assert!(meter.consumed().is_zero(), "No modification");
264
265		assert!(meter.can_consume(Weight::from_parts(9, 0)));
266		meter.consume(Weight::from_parts(9, 0));
267		assert!(!meter.can_consume(Weight::from_parts(2, 0)));
268		assert!(meter.can_consume(Weight::from_parts(1, 0)));
269	}
270
271	#[test]
272	#[cfg(debug_assertions)]
273	fn consume_works() {
274		let mut meter = WeightMeter::with_limit(Weight::from_parts(5, 10));
275
276		meter.consume(Weight::from_parts(4, 0));
277		assert_eq!(meter.remaining(), Weight::from_parts(1, 10));
278		meter.consume(Weight::from_parts(1, 0));
279		assert_eq!(meter.remaining(), Weight::from_parts(0, 10));
280		meter.consume(Weight::from_parts(0, 10));
281		assert_eq!(meter.consumed(), Weight::from_parts(5, 10));
282	}
283
284	#[test]
285	#[cfg(debug_assertions)]
286	fn reclaim_works() {
287		let mut meter = WeightMeter::with_limit(Weight::from_parts(5, 10));
288
289		meter.consume(Weight::from_parts(5, 10));
290		assert_eq!(meter.consumed(), Weight::from_parts(5, 10));
291
292		meter.reclaim_proof_size(3);
293		assert_eq!(meter.consumed(), Weight::from_parts(5, 7));
294
295		meter.reclaim_proof_size(10);
296		assert_eq!(meter.consumed(), Weight::from_parts(5, 0));
297	}
298
299	#[test]
300	#[cfg(debug_assertions)]
301	#[should_panic(expected = "Weight counter overflow")]
302	fn consume_defensive_fail() {
303		let mut meter = WeightMeter::with_limit(Weight::from_parts(10, 0));
304		let _ = meter.consume(Weight::from_parts(11, 0));
305	}
306}