// This file is part of Substrate. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! Benchmarking for the message queue pallet. #![cfg(feature = "runtime-benchmarks")] #![allow(unused_assignments)] // Needed for `ready_ring_knit`. use super::{mock_helpers::*, Pallet as MessageQueue, *}; use frame_benchmarking::v2::*; use frame_support::traits::Get; use frame_system::RawOrigin; use sp_io::hashing::blake2_256; use sp_std::prelude::*; #[benchmarks( where <::MessageProcessor as ProcessMessage>::Origin: From + PartialEq, ::Size: From, // NOTE: We need to generate multiple origins, therefore Origin is `From`. The // `PartialEq` is for asserting the outcome of the ring (un)knitting and *could* be // removed if really necessary. )] mod benchmarks { use super::*; // Worst case path of `ready_ring_knit`. #[benchmark] fn ready_ring_knit() { let mid: MessageOriginOf = 1.into(); build_ring::(&[0.into(), mid.clone(), 2.into()]); unknit::(&mid); assert_ring::(&[0.into(), 2.into()]); let mut neighbours = None; #[block] { neighbours = MessageQueue::::ready_ring_knit(&mid).ok(); } // The neighbours needs to be modified manually. BookStateFor::::mutate(&mid, |b| b.ready_neighbours = neighbours); assert_ring::(&[0.into(), 2.into(), mid]); } // Worst case path of `ready_ring_unknit`. #[benchmark] fn ready_ring_unknit() { build_ring::(&[0.into(), 1.into(), 2.into()]); assert_ring::(&[0.into(), 1.into(), 2.into()]); let o: MessageOriginOf = 0.into(); let neighbours = BookStateFor::::get(&o).ready_neighbours.unwrap(); #[block] { MessageQueue::::ready_ring_unknit(&o, neighbours); } assert_ring::(&[1.into(), 2.into()]); } // `service_queues` without any queue processing. #[benchmark] fn service_queue_base() { #[block] { MessageQueue::::service_queue(0.into(), &mut WeightMeter::new(), Weight::MAX); } } // `service_page` without any message processing but with page completion. #[benchmark] fn service_page_base_completion() { let origin: MessageOriginOf = 0.into(); let page = PageOf::::default(); Pages::::insert(&origin, 0, &page); let mut book_state = single_page_book::(); let mut meter = WeightMeter::new(); let limit = Weight::MAX; #[block] { MessageQueue::::service_page(&origin, &mut book_state, &mut meter, limit); } } // `service_page` without any message processing and without page completion. #[benchmark] fn service_page_base_no_completion() { let origin: MessageOriginOf = 0.into(); let mut page = PageOf::::default(); // Mock the storage such that `is_complete` returns `false` but `peek_first` returns `None`. page.first = 1.into(); page.remaining = 1.into(); Pages::::insert(&origin, 0, &page); let mut book_state = single_page_book::(); let mut meter = WeightMeter::new(); let limit = Weight::MAX; #[block] { MessageQueue::::service_page(&origin, &mut book_state, &mut meter, limit); } } // Processing a single message from a page. #[benchmark] fn service_page_item() { let msg = vec![1u8; MaxMessageLenOf::::get() as usize]; let mut page = page::(&msg.clone()); let mut book = book_for::(&page); assert!(page.peek_first().is_some(), "There is one message"); let mut weight = WeightMeter::new(); #[block] { let status = MessageQueue::::service_page_item( &0u32.into(), 0, &mut book, &mut page, &mut weight, Weight::MAX, ); assert_eq!(status, ItemExecutionStatus::Executed(true)); } // Check that it was processed. assert_last_event::( Event::Processed { id: blake2_256(&msg).into(), origin: 0.into(), weight_used: 1.into_weight(), success: true, } .into(), ); let (_, processed, _) = page.peek_index(0).unwrap(); assert!(processed); assert_eq!(book.message_count, 0); } // Worst case for calling `bump_service_head`. #[benchmark] fn bump_service_head() { setup_bump_service_head::(0.into(), 10.into()); let mut weight = WeightMeter::new(); #[block] { MessageQueue::::bump_service_head(&mut weight); } assert_eq!(ServiceHead::::get().unwrap(), 10u32.into()); assert_eq!(weight.consumed(), T::WeightInfo::bump_service_head()); } #[benchmark] fn reap_page() { // Mock the storage to get a *cullable* but not *reapable* page. let origin: MessageOriginOf = 0.into(); let mut book = single_page_book::(); let (page, msgs) = full_page::(); for p in 0..T::MaxStale::get() * T::MaxStale::get() { if p == 0 { Pages::::insert(&origin, p, &page); } book.end += 1; book.count += 1; book.message_count += msgs as u64; book.size += page.remaining_size.into() as u64; } book.begin = book.end - T::MaxStale::get(); BookStateFor::::insert(&origin, &book); assert!(Pages::::contains_key(&origin, 0)); #[extrinsic_call] _(RawOrigin::Signed(whitelisted_caller()), 0u32.into(), 0); assert_last_event::(Event::PageReaped { origin: 0.into(), index: 0 }.into()); assert!(!Pages::::contains_key(&origin, 0)); } // Worst case for `execute_overweight` where the page is removed as completed. // // The worst case occurs when executing the last message in a page of which all are skipped // since it is using `peek_index` which has linear complexities. #[benchmark] fn execute_overweight_page_removed() { let origin: MessageOriginOf = 0.into(); let (mut page, msgs) = full_page::(); // Skip all messages. for _ in 1..msgs { page.skip_first(true); } page.skip_first(false); let book = book_for::(&page); Pages::::insert(&origin, 0, &page); BookStateFor::::insert(&origin, &book); #[block] { MessageQueue::::execute_overweight( RawOrigin::Signed(whitelisted_caller()).into(), 0u32.into(), 0u32, ((msgs - 1) as u32).into(), Weight::MAX, ) .unwrap(); } assert_last_event::( Event::Processed { id: blake2_256(&((msgs - 1) as u32).encode()).into(), origin: 0.into(), weight_used: Weight::from_parts(1, 1), success: true, } .into(), ); assert!(!Pages::::contains_key(&origin, 0), "Page must be removed"); } // Worst case for `execute_overweight` where the page is updated. #[benchmark] fn execute_overweight_page_updated() { let origin: MessageOriginOf = 0.into(); let (mut page, msgs) = full_page::(); // Skip all messages. for _ in 0..msgs { page.skip_first(false); } let book = book_for::(&page); Pages::::insert(&origin, 0, &page); BookStateFor::::insert(&origin, &book); #[block] { MessageQueue::::execute_overweight( RawOrigin::Signed(whitelisted_caller()).into(), 0u32.into(), 0u32, ((msgs - 1) as u32).into(), Weight::MAX, ) .unwrap(); } assert_last_event::( Event::Processed { id: blake2_256(&((msgs - 1) as u32).encode()).into(), origin: 0.into(), weight_used: Weight::from_parts(1, 1), success: true, } .into(), ); assert!(Pages::::contains_key(&origin, 0), "Page must be updated"); } impl_benchmark_test_suite! { MessageQueue, crate::mock::new_test_ext::(), crate::integration_test::Test } }