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 a limit for the maximal consumable weight.
54	pub fn with_limit(limit: Weight) -> Self {
55		Self { consumed: Weight::zero(), limit }
56	}
57
58	/// Creates [`Self`] with the maximal possible limit for the consumable weight.
59	pub fn new() -> Self {
60		Self::with_limit(Weight::MAX)
61	}
62
63	/// The already consumed weight.
64	pub fn consumed(&self) -> Weight {
65		self.consumed
66	}
67
68	/// The limit can ever be accrued.
69	pub fn limit(&self) -> Weight {
70		self.limit
71	}
72
73	/// The remaining weight that can still be consumed.
74	pub fn remaining(&self) -> Weight {
75		self.limit.saturating_sub(self.consumed)
76	}
77
78	/// The ratio of consumed weight to the limit.
79	///
80	/// Calculates one ratio per component and returns the largest.
81	///
82	/// # Example
83	/// ```rust
84	/// use sp_weights::{Weight, WeightMeter};
85	/// use sp_arithmetic::Perbill;
86	///
87	/// let mut meter = WeightMeter::with_limit(Weight::from_parts(10, 20));
88	/// // Nothing consumed so far:
89	/// assert_eq!(meter.consumed_ratio(), Perbill::from_percent(0));
90	/// meter.consume(Weight::from_parts(5, 5));
91	/// // The ref-time is the larger ratio:
92	/// assert_eq!(meter.consumed_ratio(), Perbill::from_percent(50));
93	/// meter.consume(Weight::from_parts(1, 10));
94	/// // Now the larger ratio is proof-size:
95	/// assert_eq!(meter.consumed_ratio(), Perbill::from_percent(75));
96	/// // Eventually it reaches 100%:
97	/// meter.consume(Weight::from_parts(4, 0));
98	/// assert_eq!(meter.consumed_ratio(), Perbill::from_percent(100));
99	/// // Saturating the second component won't change anything anymore:
100	/// meter.consume(Weight::from_parts(0, 5));
101	/// assert_eq!(meter.consumed_ratio(), Perbill::from_percent(100));
102	/// ```
103	pub fn consumed_ratio(&self) -> Perbill {
104		let time = Perbill::from_rational(self.consumed.ref_time(), self.limit.ref_time());
105		let pov = Perbill::from_rational(self.consumed.proof_size(), self.limit.proof_size());
106		time.max(pov)
107	}
108
109	/// Consume some weight and defensively fail if it is over the limit. Saturate in any case.
110	#[deprecated(note = "Use `consume` instead. Will be removed after December 2023.")]
111	pub fn defensive_saturating_accrue(&mut self, w: Weight) {
112		self.consume(w);
113	}
114
115	/// Consume some weight and defensively fail if it is over the limit. Saturate in any case.
116	pub fn consume(&mut self, w: Weight) {
117		self.consumed.saturating_accrue(w);
118		debug_assert!(self.consumed.all_lte(self.limit), "Weight counter overflow");
119	}
120
121	/// Consume the given weight after checking that it can be consumed.
122	///
123	/// Returns `Ok` if the weight can be consumed or otherwise an `Err`.
124	pub fn try_consume(&mut self, w: Weight) -> Result<(), ()> {
125		self.consumed.checked_add(&w).map_or(Err(()), |test| {
126			if test.any_gt(self.limit) {
127				Err(())
128			} else {
129				self.consumed = test;
130				Ok(())
131			}
132		})
133	}
134
135	/// Check if the given weight can be consumed.
136	pub fn can_consume(&self, w: Weight) -> bool {
137		self.consumed.checked_add(&w).map_or(false, |t| t.all_lte(self.limit))
138	}
139
140	/// Reclaim the given weight.
141	pub fn reclaim_proof_size(&mut self, s: u64) {
142		self.consumed.saturating_reduce(Weight::from_parts(0, s));
143	}
144}
145
146#[cfg(test)]
147mod tests {
148	use crate::*;
149	use sp_arithmetic::traits::Zero;
150
151	#[test]
152	fn weight_meter_remaining_works() {
153		let mut meter = WeightMeter::with_limit(Weight::from_parts(10, 20));
154
155		assert_eq!(meter.try_consume(Weight::from_parts(5, 0)), Ok(()));
156		assert_eq!(meter.consumed, Weight::from_parts(5, 0));
157		assert_eq!(meter.remaining(), Weight::from_parts(5, 20));
158
159		assert_eq!(meter.try_consume(Weight::from_parts(2, 10)), Ok(()));
160		assert_eq!(meter.consumed, Weight::from_parts(7, 10));
161		assert_eq!(meter.remaining(), Weight::from_parts(3, 10));
162
163		assert_eq!(meter.try_consume(Weight::from_parts(3, 10)), Ok(()));
164		assert_eq!(meter.consumed, Weight::from_parts(10, 20));
165		assert_eq!(meter.remaining(), Weight::from_parts(0, 0));
166	}
167
168	#[test]
169	fn weight_meter_can_consume_works() {
170		let meter = WeightMeter::with_limit(Weight::from_parts(1, 1));
171
172		assert!(meter.can_consume(Weight::from_parts(0, 0)));
173		assert!(meter.can_consume(Weight::from_parts(1, 1)));
174		assert!(!meter.can_consume(Weight::from_parts(0, 2)));
175		assert!(!meter.can_consume(Weight::from_parts(2, 0)));
176		assert!(!meter.can_consume(Weight::from_parts(2, 2)));
177	}
178
179	#[test]
180	fn weight_meter_try_consume_works() {
181		let mut meter = WeightMeter::with_limit(Weight::from_parts(2, 2));
182
183		assert_eq!(meter.try_consume(Weight::from_parts(0, 0)), Ok(()));
184		assert_eq!(meter.try_consume(Weight::from_parts(1, 1)), Ok(()));
185		assert_eq!(meter.try_consume(Weight::from_parts(0, 2)), Err(()));
186		assert_eq!(meter.try_consume(Weight::from_parts(2, 0)), Err(()));
187		assert_eq!(meter.try_consume(Weight::from_parts(2, 2)), Err(()));
188		assert_eq!(meter.try_consume(Weight::from_parts(0, 1)), Ok(()));
189		assert_eq!(meter.try_consume(Weight::from_parts(1, 0)), Ok(()));
190	}
191
192	#[test]
193	fn weight_meter_check_and_can_consume_works() {
194		let mut meter = WeightMeter::new();
195
196		assert!(meter.can_consume(Weight::from_parts(u64::MAX, 0)));
197		assert_eq!(meter.try_consume(Weight::from_parts(u64::MAX, 0)), Ok(()));
198
199		assert!(meter.can_consume(Weight::from_parts(0, u64::MAX)));
200		assert_eq!(meter.try_consume(Weight::from_parts(0, u64::MAX)), Ok(()));
201
202		assert!(!meter.can_consume(Weight::from_parts(0, 1)));
203		assert_eq!(meter.try_consume(Weight::from_parts(0, 1)), Err(()));
204
205		assert!(!meter.can_consume(Weight::from_parts(1, 0)));
206		assert_eq!(meter.try_consume(Weight::from_parts(1, 0)), Err(()));
207
208		assert!(meter.can_consume(Weight::zero()));
209		assert_eq!(meter.try_consume(Weight::zero()), Ok(()));
210	}
211
212	#[test]
213	fn consumed_ratio_works() {
214		let mut meter = WeightMeter::with_limit(Weight::from_parts(10, 20));
215
216		assert_eq!(meter.try_consume(Weight::from_parts(5, 0)), Ok(()));
217		assert_eq!(meter.consumed_ratio(), Perbill::from_percent(50));
218		assert_eq!(meter.try_consume(Weight::from_parts(0, 12)), Ok(()));
219		assert_eq!(meter.consumed_ratio(), Perbill::from_percent(60));
220
221		assert_eq!(meter.try_consume(Weight::from_parts(2, 0)), Ok(()));
222		assert_eq!(meter.consumed_ratio(), Perbill::from_percent(70));
223		assert_eq!(meter.try_consume(Weight::from_parts(0, 4)), Ok(()));
224		assert_eq!(meter.consumed_ratio(), Perbill::from_percent(80));
225
226		assert_eq!(meter.try_consume(Weight::from_parts(3, 0)), Ok(()));
227		assert_eq!(meter.consumed_ratio(), Perbill::from_percent(100));
228		assert_eq!(meter.try_consume(Weight::from_parts(0, 4)), Ok(()));
229		assert_eq!(meter.consumed_ratio(), Perbill::from_percent(100));
230	}
231
232	#[test]
233	fn try_consume_works() {
234		let mut meter = WeightMeter::with_limit(Weight::from_parts(10, 0));
235
236		assert!(meter.try_consume(Weight::from_parts(11, 0)).is_err());
237		assert!(meter.consumed().is_zero(), "No modification");
238
239		assert!(meter.try_consume(Weight::from_parts(9, 0)).is_ok());
240		assert!(meter.try_consume(Weight::from_parts(2, 0)).is_err());
241		assert!(meter.try_consume(Weight::from_parts(1, 0)).is_ok());
242		assert!(meter.remaining().is_zero());
243		assert_eq!(meter.consumed(), Weight::from_parts(10, 0));
244	}
245
246	#[test]
247	fn can_consume_works() {
248		let mut meter = WeightMeter::with_limit(Weight::from_parts(10, 0));
249
250		assert!(!meter.can_consume(Weight::from_parts(11, 0)));
251		assert!(meter.consumed().is_zero(), "No modification");
252
253		assert!(meter.can_consume(Weight::from_parts(9, 0)));
254		meter.consume(Weight::from_parts(9, 0));
255		assert!(!meter.can_consume(Weight::from_parts(2, 0)));
256		assert!(meter.can_consume(Weight::from_parts(1, 0)));
257	}
258
259	#[test]
260	#[cfg(debug_assertions)]
261	fn consume_works() {
262		let mut meter = WeightMeter::with_limit(Weight::from_parts(5, 10));
263
264		meter.consume(Weight::from_parts(4, 0));
265		assert_eq!(meter.remaining(), Weight::from_parts(1, 10));
266		meter.consume(Weight::from_parts(1, 0));
267		assert_eq!(meter.remaining(), Weight::from_parts(0, 10));
268		meter.consume(Weight::from_parts(0, 10));
269		assert_eq!(meter.consumed(), Weight::from_parts(5, 10));
270	}
271
272	#[test]
273	#[cfg(debug_assertions)]
274	fn reclaim_works() {
275		let mut meter = WeightMeter::with_limit(Weight::from_parts(5, 10));
276
277		meter.consume(Weight::from_parts(5, 10));
278		assert_eq!(meter.consumed(), Weight::from_parts(5, 10));
279
280		meter.reclaim_proof_size(3);
281		assert_eq!(meter.consumed(), Weight::from_parts(5, 7));
282
283		meter.reclaim_proof_size(10);
284		assert_eq!(meter.consumed(), Weight::from_parts(5, 0));
285	}
286
287	#[test]
288	#[cfg(debug_assertions)]
289	#[should_panic(expected = "Weight counter overflow")]
290	fn consume_defensive_fail() {
291		let mut meter = WeightMeter::with_limit(Weight::from_parts(10, 0));
292		let _ = meter.consume(Weight::from_parts(11, 0));
293	}
294}