From ae08221cdb887ed4ffc831b833c17c97201743eb Mon Sep 17 00:00:00 2001 From: Julien Cretin Date: Thu, 10 Dec 2020 13:31:25 +0100 Subject: [PATCH 1/3] Add latency example --- deploy.py | 6 ++ examples/store_latency.rs | 126 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 examples/store_latency.rs diff --git a/deploy.py b/deploy.py index e1ec38f..42f3e44 100755 --- a/deploy.py +++ b/deploy.py @@ -907,6 +907,12 @@ if __name__ == "__main__": const="crypto_bench", help=("Compiles and installs the crypto_bench example that benchmarks " "the performance of the cryptographic algorithms on the board.")) + apps_group.add_argument( + "--store_latency", + dest="application", + action="store_const", + const="store_latency", + help=("Compiles and installs the store_latency example.")) apps_group.add_argument( "--panic_test", dest="application", diff --git a/examples/store_latency.rs b/examples/store_latency.rs new file mode 100644 index 0000000..f85d14d --- /dev/null +++ b/examples/store_latency.rs @@ -0,0 +1,126 @@ +// Copyright 2019-2020 Google LLC +// +// 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. + +#![no_std] + +extern crate alloc; +extern crate lang_items; + +use alloc::vec; +use core::fmt::Write; +use ctap2::embedded_flash::SyscallStorage; +use libtock_drivers::console::Console; +use libtock_drivers::timer::{self, Duration, Timer, Timestamp}; +use persistent_store::{Storage, Store}; + +fn timestamp(timer: &Timer) -> Timestamp { + Timestamp::::from_clock_value(timer.get_current_clock().ok().unwrap()) +} + +fn measure(timer: &Timer, operation: impl FnOnce() -> T) -> (T, Duration) { + let before = timestamp(timer); + let result = operation(); + let after = timestamp(timer); + (result, after - before) +} + +// Only use one store at a time. +unsafe fn boot_store(num_pages: usize, erase: bool) -> Store { + let mut storage = SyscallStorage::new(num_pages).unwrap(); + if erase { + for page in 0..storage.num_pages() { + storage.erase_page(page).unwrap(); + } + } + Store::new(storage).ok().unwrap() +} + +fn compute_latency(timer: &Timer, num_pages: usize, key_increment: usize, word_length: usize) { + let mut console = Console::new(); + writeln!( + console, + "\nLatency for num_pages={} key_increment={} word_length={}.", + num_pages, key_increment, word_length + ) + .unwrap(); + + let mut store = unsafe { boot_store(num_pages, true) }; + let total_capacity = store.capacity().unwrap().total(); + + // Burn N words to align the end of the user capacity with the virtual capacity. + store.insert(0, &vec![0; 4 * (num_pages - 1)]).unwrap(); + store.remove(0).unwrap(); + + // Insert entries until there is space for one more. + let count = total_capacity / (1 + word_length) - 1; + let ((), time) = measure(timer, || { + for i in 0..count { + let key = 1 + key_increment * i; + // For some reason the kernel sometimes fails. + while store.insert(key, &vec![0; 4 * word_length]).is_err() { + // We never enter this loop in practice, but we still need it for the kernel. + writeln!(console, "Retry insert.").unwrap(); + } + } + }); + writeln!(console, "Setup: {:.1}ms for {} entries.", time.ms(), count).unwrap(); + + // Measure latency of insert. + let key = 1 + key_increment * count; + let ((), time) = measure(&timer, || { + store.insert(key, &vec![0; 4 * word_length]).unwrap() + }); + writeln!(console, "Insert: {:.1}ms.", time.ms()).unwrap(); + + // Measure latency of boot. + let (mut store, time) = measure(&timer, || unsafe { boot_store(num_pages, false) }); + writeln!(console, "Boot: {:.1}ms.", time.ms()).unwrap(); + + // Measure latency of remove. + let ((), time) = measure(&timer, || store.remove(key).unwrap()); + writeln!(console, "Remove: {:.1}ms.", time.ms()).unwrap(); + + // Measure latency of compaction. + let length = total_capacity + num_pages - store.lifetime().unwrap().used(); + if length > 0 { + // Fill the store such that compaction is needed for one word. + store.insert(0, &vec![0; 4 * (length - 1)]).unwrap(); + store.remove(0).unwrap(); + } + let ((), time) = measure(timer, || store.prepare(1).unwrap()); + writeln!(console, "Compaction: {:.1}ms.", time.ms()).unwrap(); +} + +fn main() { + let mut with_callback = timer::with_callback(|_, _| {}); + let timer = with_callback.init().ok().unwrap(); + + writeln!(Console::new(), "\nRunning 4 tests...").unwrap(); + // Those non-overwritten 50 words entries simulate credentials. + compute_latency(&timer, 3, 1, 50); + compute_latency(&timer, 20, 1, 50); + // Those overwritten 1 word entries simulate counters. + compute_latency(&timer, 3, 0, 1); + compute_latency(&timer, 6, 0, 1); + writeln!(Console::new(), "\nDone.").unwrap(); + + // Results on nrf52840dk: + // + // | Pages | Overwrite | Length | Boot | Compaction | Insert | Remove | + // | ----- | --------- | --------- | ------- | ---------- | ------ | ------- | + // | 3 | no | 50 words | 2.0 ms | 132.5 ms | 4.8 ms | 1.2 ms | + // | 20 | no | 50 words | 7.4 ms | 135.5 ms | 10.2 ms | 3.9 ms | + // | 3 | yes | 1 word | 21.9 ms | 94.5 ms | 12.4 ms | 5.9 ms | + // | 6 | yes | 1 word | 55.2 ms | 100.8 ms | 24.8 ms | 12.1 ms | +} From 869e93234952fd09c5e85acd61430c5d079cb1fa Mon Sep 17 00:00:00 2001 From: Julien Cretin Date: Thu, 10 Dec 2020 16:44:00 +0100 Subject: [PATCH 2/3] Add asserts to make sure we compact --- examples/store_latency.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/examples/store_latency.rs b/examples/store_latency.rs index f85d14d..e664964 100644 --- a/examples/store_latency.rs +++ b/examples/store_latency.rs @@ -57,10 +57,14 @@ fn compute_latency(timer: &Timer, num_pages: usize, key_increment: usize, word_l let mut store = unsafe { boot_store(num_pages, true) }; let total_capacity = store.capacity().unwrap().total(); + assert_eq!(store.capacity().unwrap().used(), 0); + assert_eq!(store.lifetime().unwrap().used(), 0); // Burn N words to align the end of the user capacity with the virtual capacity. store.insert(0, &vec![0; 4 * (num_pages - 1)]).unwrap(); store.remove(0).unwrap(); + assert_eq!(store.capacity().unwrap().used(), 0); + assert_eq!(store.lifetime().unwrap().used(), num_pages); // Insert entries until there is space for one more. let count = total_capacity / (1 + word_length) - 1; @@ -82,6 +86,10 @@ fn compute_latency(timer: &Timer, num_pages: usize, key_increment: usize, word_l store.insert(key, &vec![0; 4 * word_length]).unwrap() }); writeln!(console, "Insert: {:.1}ms.", time.ms()).unwrap(); + assert_eq!( + store.lifetime().unwrap().used(), + num_pages + (1 + count) * (1 + word_length) + ); // Measure latency of boot. let (mut store, time) = measure(&timer, || unsafe { boot_store(num_pages, false) }); @@ -98,8 +106,11 @@ fn compute_latency(timer: &Timer, num_pages: usize, key_increment: usize, word_l store.insert(0, &vec![0; 4 * (length - 1)]).unwrap(); store.remove(0).unwrap(); } + assert!(store.capacity().unwrap().remaining() > 0); + assert_eq!(store.lifetime().unwrap().used(), num_pages + total_capacity); let ((), time) = measure(timer, || store.prepare(1).unwrap()); writeln!(console, "Compaction: {:.1}ms.", time.ms()).unwrap(); + assert!(store.lifetime().unwrap().used() > total_capacity + num_pages); } fn main() { From 371b8af22425689dc5af6ea4a8745a4cc86a1d8c Mon Sep 17 00:00:00 2001 From: Julien Cretin Date: Thu, 10 Dec 2020 18:04:25 +0100 Subject: [PATCH 3/3] Move choice between prod and test storage to embedded_flash module This way all users of storage can share the logic to choose between flash or RAM storage depending on the "std" feature. This is needed because the store_latency example assumes flash storage but is built when running `cargo test --features=std`. --- examples/store_latency.rs | 11 ++++++----- src/ctap/storage.rs | 32 ++------------------------------ src/embedded_flash/mod.rs | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 35 deletions(-) diff --git a/examples/store_latency.rs b/examples/store_latency.rs index e664964..c3f3e71 100644 --- a/examples/store_latency.rs +++ b/examples/store_latency.rs @@ -19,10 +19,10 @@ extern crate lang_items; use alloc::vec; use core::fmt::Write; -use ctap2::embedded_flash::SyscallStorage; +use ctap2::embedded_flash::{new_storage, Storage}; use libtock_drivers::console::Console; use libtock_drivers::timer::{self, Duration, Timer, Timestamp}; -use persistent_store::{Storage, Store}; +use persistent_store::Store; fn timestamp(timer: &Timer) -> Timestamp { Timestamp::::from_clock_value(timer.get_current_clock().ok().unwrap()) @@ -36,10 +36,11 @@ fn measure(timer: &Timer, operation: impl FnOnce() -> T) -> (T, Duration } // Only use one store at a time. -unsafe fn boot_store(num_pages: usize, erase: bool) -> Store { - let mut storage = SyscallStorage::new(num_pages).unwrap(); +unsafe fn boot_store(num_pages: usize, erase: bool) -> Store { + let mut storage = new_storage(num_pages); if erase { - for page in 0..storage.num_pages() { + for page in 0..num_pages { + use persistent_store::Storage; storage.erase_page(page).unwrap(); } } diff --git a/src/ctap/storage.rs b/src/ctap/storage.rs index 5e42ec7..5f17052 100644 --- a/src/ctap/storage.rs +++ b/src/ctap/storage.rs @@ -21,6 +21,7 @@ use crate::ctap::key_material; use crate::ctap::pin_protocol_v1::PIN_AUTH_LENGTH; use crate::ctap::status_code::Ctap2StatusCode; use crate::ctap::INITIAL_SIGNATURE_COUNTER; +use crate::embedded_flash::{new_storage, Storage}; #[cfg(feature = "with_ctap2_1")] use alloc::string::String; use alloc::vec; @@ -31,11 +32,6 @@ use cbor::cbor_array_vec; use core::convert::TryInto; use crypto::rng256::Rng256; -#[cfg(feature = "std")] -type Storage = persistent_store::BufferStorage; -#[cfg(not(feature = "std"))] -type Storage = crate::embedded_flash::SyscallStorage; - // Those constants may be modified before compilation to tune the behavior of the key. // // The number of pages should be at least 3 and at most what the flash can hold. There should be no @@ -89,10 +85,7 @@ impl PersistentStore { /// /// This should be at most one instance of persistent store per program lifetime. pub fn new(rng: &mut impl Rng256) -> PersistentStore { - #[cfg(not(feature = "std"))] - let storage = PersistentStore::new_prod_storage(); - #[cfg(feature = "std")] - let storage = PersistentStore::new_test_storage(); + let storage = new_storage(NUM_PAGES); let mut store = PersistentStore { store: persistent_store::Store::new(storage).ok().unwrap(), }; @@ -100,27 +93,6 @@ impl PersistentStore { store } - /// Creates a syscall storage in flash. - #[cfg(not(feature = "std"))] - fn new_prod_storage() -> Storage { - Storage::new(NUM_PAGES).unwrap() - } - - /// Creates a buffer storage in RAM. - #[cfg(feature = "std")] - fn new_test_storage() -> Storage { - const PAGE_SIZE: usize = 0x1000; - let store = vec![0xff; NUM_PAGES * PAGE_SIZE].into_boxed_slice(); - let options = persistent_store::BufferOptions { - word_size: 4, - page_size: PAGE_SIZE, - max_word_writes: 2, - max_page_erases: 10000, - strict_mode: true, - }; - Storage::new(store, options) - } - /// Initializes the store by creating missing objects. fn init(&mut self, rng: &mut impl Rng256) -> Result<(), Ctap2StatusCode> { // Generate and store the master keys if they are missing. diff --git a/src/embedded_flash/mod.rs b/src/embedded_flash/mod.rs index 862b37e..e307a55 100644 --- a/src/embedded_flash/mod.rs +++ b/src/embedded_flash/mod.rs @@ -17,3 +17,36 @@ mod syscall; #[cfg(not(feature = "std"))] pub use self::syscall::SyscallStorage; + +/// Storage definition for production. +#[cfg(not(feature = "std"))] +mod prod { + pub type Storage = super::SyscallStorage; + + pub fn new_storage(num_pages: usize) -> Storage { + Storage::new(num_pages).unwrap() + } +} +#[cfg(not(feature = "std"))] +pub use self::prod::{new_storage, Storage}; + +/// Storage definition for testing. +#[cfg(feature = "std")] +mod test { + pub type Storage = persistent_store::BufferStorage; + + pub fn new_storage(num_pages: usize) -> Storage { + const PAGE_SIZE: usize = 0x1000; + let store = vec![0xff; num_pages * PAGE_SIZE].into_boxed_slice(); + let options = persistent_store::BufferOptions { + word_size: 4, + page_size: PAGE_SIZE, + max_word_writes: 2, + max_page_erases: 10000, + strict_mode: true, + }; + Storage::new(store, options) + } +} +#[cfg(feature = "std")] +pub use self::test::{new_storage, Storage};