diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 27cd7af83ef0..fd163bb84dd2 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -55,8 +55,8 @@ go_deps.bzl @dfinity/idx /packages/ic-dummy-getrandom-for-wasm/ @dfinity/crypto-team /packages/ic-ed25519/ @dfinity/crypto-team /packages/ic-ethereum-types/ @dfinity/cross-chain-team -/packages/ic-deterministic-heap-bytes/ @dfinity/execution -/packages/ic-deterministic-heap-bytes-derive/ @dfinity/execution +/packages/ic-heap-bytes/ @dfinity/execution +/packages/ic-heap-bytes-derive/ @dfinity/execution /packages/ic-hpke/ @dfinity/crypto-team /packages/ic-http-types/ @dfinity/cross-chain-team /packages/ic-metrics-assert/ @dfinity/cross-chain-team diff --git a/Cargo.lock b/Cargo.lock index 1d2a47619149..c0f2bf825097 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6457,6 +6457,7 @@ dependencies = [ "hex", "ic-base-types-protobuf-generator", "ic-crypto-sha2", + "ic-heap-bytes", "ic-protobuf", "ic-test-utilities-compare-dirs", "phantom_newtype", @@ -9014,25 +9015,6 @@ dependencies = [ "wat", ] -[[package]] -name = "ic-deterministic-heap-bytes" -version = "0.9.0" -dependencies = [ - "candid", - "ic-deterministic-heap-bytes-derive", - "paste", -] - -[[package]] -name = "ic-deterministic-heap-bytes-derive" -version = "0.9.0" -dependencies = [ - "darling 0.20.11", - "proc-macro2", - "quote", - "syn 2.0.101", -] - [[package]] name = "ic-dummy-getrandom-for-wasm" version = "0.1.0" @@ -9075,6 +9057,7 @@ dependencies = [ "ic-config", "ic-cycles-account-manager", "ic-error-types 0.2.0", + "ic-heap-bytes", "ic-interfaces", "ic-limits", "ic-logger", @@ -9134,6 +9117,7 @@ dependencies = [ name = "ic-error-types" version = "0.2.0" dependencies = [ + "ic-heap-bytes", "serde", "strum 0.26.3", "strum_macros 0.26.4", @@ -9187,6 +9171,7 @@ dependencies = [ "ic-cycles-account-manager", "ic-embedders", "ic-error-types 0.2.0", + "ic-heap-bytes", "ic-interfaces", "ic-interfaces-state-manager", "ic-interfaces-state-manager-mocks", @@ -9338,6 +9323,27 @@ dependencies = [ "zstd", ] +[[package]] +name = "ic-heap-bytes" +version = "0.9.0" +dependencies = [ + "candid", + "ic-heap-bytes-derive", + "paste", + "prometheus 0.13.4", + "tempfile", +] + +[[package]] +name = "ic-heap-bytes-derive" +version = "0.9.0" +dependencies = [ + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.101", +] + [[package]] name = "ic-hpke" version = "0.1.0" @@ -10192,6 +10198,7 @@ dependencies = [ "ic-crypto-interfaces-sig-verification", "ic-crypto-internal-csp-proptest-utils", "ic-error-types 0.2.0", + "ic-heap-bytes", "ic-interfaces-state-manager", "ic-management-canister-types-private", "ic-protobuf", @@ -14653,6 +14660,7 @@ dependencies = [ "ic-crypto-tree-hash", "ic-error-types 0.2.0", "ic-exhaustive-derive", + "ic-heap-bytes", "ic-limits", "ic-management-canister-types-private", "ic-protobuf", @@ -14744,6 +14752,7 @@ version = "0.9.0" name = "ic-utils-lru-cache" version = "0.9.0" dependencies = [ + "ic-heap-bytes", "ic-types", "lru", "proptest 1.6.0", @@ -14964,6 +14973,7 @@ name = "ic-wasm-types" version = "0.9.0" dependencies = [ "ic-crypto-sha2", + "ic-heap-bytes", "ic-sys", "ic-types", "ic-utils 0.9.0", @@ -18769,6 +18779,7 @@ name = "phantom_newtype" version = "0.9.0" dependencies = [ "candid", + "ic-heap-bytes", "num-traits", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 6a194013dc2a..bcda3d4e79fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,12 +3,12 @@ members = [ "packages/canlog", "packages/canlog_derive", - "packages/ic-deterministic-heap-bytes", - "packages/ic-deterministic-heap-bytes-derive", "packages/ic-dummy-getrandom-for-wasm", "packages/ic-ed25519", "packages/ic-error-types", "packages/ic-ethereum-types", + "packages/ic-heap-bytes", + "packages/ic-heap-bytes-derive", "packages/ic-hpke", "packages/ic-http-types", "packages/ic-ledger-hash-of", diff --git a/packages/ic-error-types/BUILD.bazel b/packages/ic-error-types/BUILD.bazel index 5e4881adcc0a..c9e03863a5fc 100644 --- a/packages/ic-error-types/BUILD.bazel +++ b/packages/ic-error-types/BUILD.bazel @@ -2,27 +2,22 @@ load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") package(default_visibility = ["//visibility:public"]) -DEPENDENCIES = [ - # Keep sorted. - "@crate_index//:serde", - "@crate_index//:strum", -] - -MACRO_DEPENDENCIES = [ - "@crate_index//:strum_macros", -] - rust_library( name = "ic-error-types", srcs = glob(["src/**"]), - proc_macro_deps = MACRO_DEPENDENCIES, + proc_macro_deps = [ + "@crate_index//:strum_macros", + ], version = "0.2.0", - deps = DEPENDENCIES, + deps = [ + # Keep sorted. + "//packages/ic-heap-bytes", + "@crate_index//:serde", + "@crate_index//:strum", + ], ) rust_test( name = "ic-error-types-tests", - srcs = glob(["src/**/*.rs"]), - proc_macro_deps = MACRO_DEPENDENCIES, - deps = DEPENDENCIES, + crate = ":ic-error-types", ) diff --git a/packages/ic-error-types/Cargo.toml b/packages/ic-error-types/Cargo.toml index 5f74a384deba..6a31c39ce639 100644 --- a/packages/ic-error-types/Cargo.toml +++ b/packages/ic-error-types/Cargo.toml @@ -13,6 +13,7 @@ authors.workspace = true edition.workspace = true [dependencies] +ic-heap-bytes = { path = "../ic-heap-bytes" } serde = { workspace = true } strum = { workspace = true } strum_macros = { workspace = true } diff --git a/packages/ic-error-types/src/lib.rs b/packages/ic-error-types/src/lib.rs index 788924458f65..eb23c946439f 100644 --- a/packages/ic-error-types/src/lib.rs +++ b/packages/ic-error-types/src/lib.rs @@ -1,6 +1,7 @@ //! A crate that groups user-facing and internal error types and codes produced //! by the Internet Computer. +use ic_heap_bytes::DeterministicHeapBytes; use serde::{Deserialize, Serialize}; use std::{convert::TryFrom, fmt}; use str_traits::StrEllipsize; @@ -141,7 +142,18 @@ impl From for RejectCode { /// code and the rest is just a sequentially assigned two-digit /// number. #[derive( - Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Deserialize, EnumIter, Serialize, + Copy, + Clone, + Eq, + DeterministicHeapBytes, + PartialEq, + Ord, + PartialOrd, + Hash, + Debug, + Deserialize, + EnumIter, + Serialize, )] pub enum ErrorCode { // 1xx -- `RejectCode::SysFatal` @@ -217,7 +229,18 @@ const MAX_USER_ERROR_DESCRIPTION_LEN_BYTES: usize = 8 * 1024; /// The error that is sent back to users of IC if something goes /// wrong. It's designed to be copyable and serializable so that we /// can persist it in ingress history. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Deserialize, Serialize)] +#[derive( + Clone, + Eq, + DeterministicHeapBytes, + PartialEq, + Ord, + PartialOrd, + Hash, + Debug, + Deserialize, + Serialize, +)] pub struct UserError { code: ErrorCode, description: String, diff --git a/packages/ic-deterministic-heap-bytes-derive/BUILD.bazel b/packages/ic-heap-bytes-derive/BUILD.bazel similarity index 87% rename from packages/ic-deterministic-heap-bytes-derive/BUILD.bazel rename to packages/ic-heap-bytes-derive/BUILD.bazel index 0d1cc98910a9..92b3aa8be464 100644 --- a/packages/ic-deterministic-heap-bytes-derive/BUILD.bazel +++ b/packages/ic-heap-bytes-derive/BUILD.bazel @@ -3,7 +3,7 @@ load("@rules_rust//rust:defs.bzl", "rust_proc_macro") package(default_visibility = ["//visibility:public"]) rust_proc_macro( - name = "ic-deterministic-heap-bytes-derive", + name = "ic-heap-bytes-derive", srcs = glob(["src/**"]), version = "0.1.0", deps = [ diff --git a/packages/ic-deterministic-heap-bytes-derive/Cargo.toml b/packages/ic-heap-bytes-derive/Cargo.toml similarity index 88% rename from packages/ic-deterministic-heap-bytes-derive/Cargo.toml rename to packages/ic-heap-bytes-derive/Cargo.toml index 8b34d5e801f8..2f504b8d9b1e 100644 --- a/packages/ic-deterministic-heap-bytes-derive/Cargo.toml +++ b/packages/ic-heap-bytes-derive/Cargo.toml @@ -3,7 +3,7 @@ authors.workspace = true description.workspace = true documentation.workspace = true edition.workspace = true -name = "ic-deterministic-heap-bytes-derive" +name = "ic-heap-bytes-derive" version.workspace = true [lib] diff --git a/packages/ic-deterministic-heap-bytes-derive/src/lib.rs b/packages/ic-heap-bytes-derive/src/lib.rs similarity index 57% rename from packages/ic-deterministic-heap-bytes-derive/src/lib.rs rename to packages/ic-heap-bytes-derive/src/lib.rs index 8282b527d7e2..33f6a87fec70 100644 --- a/packages/ic-deterministic-heap-bytes-derive/src/lib.rs +++ b/packages/ic-heap-bytes-derive/src/lib.rs @@ -1,15 +1,15 @@ use darling::{ast::Data, FromDeriveInput, FromField, FromVariant}; use quote::quote; -use syn::{parse_macro_input, DeriveInput, Expr, Ident}; +use syn::{parse_macro_input, spanned::Spanned, DeriveInput, Expr, Ident}; #[derive(FromDeriveInput)] -struct DeterministicHeapBytesReceiver { +struct DeriveInputReceiver { ident: Ident, data: Data, } #[derive(Debug, FromVariant)] -#[darling(attributes(deterministic_heap_bytes))] +#[darling(attributes(heap_bytes, deterministic_heap_bytes))] struct VariantReceiver { ident: Ident, fields: darling::ast::Fields, @@ -18,17 +18,37 @@ struct VariantReceiver { } #[derive(Debug, FromField)] -#[darling(attributes(deterministic_heap_bytes))] +#[darling(attributes(heap_bytes, deterministic_heap_bytes))] struct FieldReceiver { ident: Option, + ty: syn::Type, #[darling(default)] with: Option, } -#[proc_macro_derive(DeterministicHeapBytes, attributes(deterministic_heap_bytes))] +#[proc_macro_derive(HeapBytes, attributes(heap_bytes))] pub fn derive_heap_bytes(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let trait_name = "HeapBytes"; + let method_name = "heap_bytes"; + + sum(input, trait_name, method_name) +} + +#[proc_macro_derive(DeterministicHeapBytes, attributes(deterministic_heap_bytes))] +pub fn derive_deterministic_heap_bytes(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let trait_name = "DeterministicHeapBytes"; + let method_name = "deterministic_heap_bytes"; + + sum(input, trait_name, method_name) +} + +fn sum( + input: proc_macro::TokenStream, + trait_name: &str, + method_name: &str, +) -> proc_macro::TokenStream { let input = parse_macro_input!(input as DeriveInput); - let receiver = match DeterministicHeapBytesReceiver::from_derive_input(&input) { + let receiver = match DeriveInputReceiver::from_derive_input(&input) { Ok(r) => r, Err(e) => return e.write_errors().into(), }; @@ -36,41 +56,47 @@ pub fn derive_heap_bytes(input: proc_macro::TokenStream) -> proc_macro::TokenStr let struct_name = &receiver.ident; let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); - let heap_bytes_body = match receiver.data { - Data::Enum(variants) => enum_heap_bytes(&variants), - Data::Struct(fields) => struct_heap_bytes(&fields), + let body = match receiver.data { + Data::Enum(variants) => enum_sum(&variants, method_name), + Data::Struct(fields) => struct_sum(&fields, method_name), }; + let trait_name = Ident::new(trait_name, receiver.ident.span()); + let method_name = Ident::new(method_name, receiver.ident.span()); quote! { - impl #impl_generics DeterministicHeapBytes for #struct_name #ty_generics #where_clause { - fn deterministic_heap_bytes(&self) -> usize { - #heap_bytes_body + impl #impl_generics #trait_name for #struct_name #ty_generics #where_clause { + fn #method_name(&self) -> usize { + #body } } } .into() } -fn struct_heap_bytes(fields: &darling::ast::Fields) -> proc_macro2::TokenStream { +fn struct_sum( + fields: &darling::ast::Fields, + method_name: &str, +) -> proc_macro2::TokenStream { let fields = fields.fields.iter().enumerate().map(|(index, field)| { let index = syn::Index::from(index); - let accessor = if let Some(ident) = &field.ident { - quote! { self.#ident } + let (accessor, span) = if let Some(ident) = &field.ident { + (quote! { self.#ident }, ident.span()) } else { // Tuple struct fields can only be accessed by index, e.g. `struct S(u8, u16)` - quote! { self.#index } + (quote! { self.#index }, field.ty.span()) }; if let Some(closure) = &field.with { quote! { (#closure)(&#accessor) } } else { - quote! { #accessor.deterministic_heap_bytes() } + let method_name = Ident::new(method_name, span); + quote! { #accessor.#method_name() } } }); quote! { 0 #(+ #fields)* } } -fn enum_heap_bytes(variants: &[VariantReceiver]) -> proc_macro2::TokenStream { +fn enum_sum(variants: &[VariantReceiver], method_name: &str) -> proc_macro2::TokenStream { let match_arms = variants.iter().map(|variant| { let variant_ident = &variant.ident; @@ -81,12 +107,12 @@ fn enum_heap_bytes(variants: &[VariantReceiver]) -> proc_macro2::TokenStream { .iter() .enumerate() .map(|(index, field)| { - let (field_pat, accessor) = if let Some(ident) = &field.ident { - (quote! { #ident }, quote! { #ident }) + let (field_pat, accessor, span) = if let Some(ident) = &field.ident { + (quote! { #ident }, quote! { #ident }, ident.span()) } else { let var_name = format!("v{}", index); - let ident = Ident::new(&var_name, proc_macro2::Span::call_site()); - (quote! { #ident }, quote! { #ident }) + let ident = Ident::new(&var_name, field.ty.span()); + (quote! { #ident }, quote! { #ident }, field.ty.span()) }; let expr = if let Some(closure) = &field.with { @@ -94,7 +120,8 @@ fn enum_heap_bytes(variants: &[VariantReceiver]) -> proc_macro2::TokenStream { } else if let Some(_closure) = &variant.with { quote! { &#accessor } } else { - quote! { #accessor.deterministic_heap_bytes() } + let method_name = Ident::new(method_name, span); + quote! { #accessor.#method_name() } }; (field_pat, expr) diff --git a/packages/ic-deterministic-heap-bytes/BUILD.bazel b/packages/ic-heap-bytes/BUILD.bazel similarity index 52% rename from packages/ic-deterministic-heap-bytes/BUILD.bazel rename to packages/ic-heap-bytes/BUILD.bazel index 52560878c7a9..9fe552670387 100644 --- a/packages/ic-deterministic-heap-bytes/BUILD.bazel +++ b/packages/ic-heap-bytes/BUILD.bazel @@ -3,26 +3,23 @@ load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") package(default_visibility = ["//visibility:public"]) rust_library( - name = "ic-deterministic-heap-bytes", + name = "ic-heap-bytes", srcs = glob(["src/**"]), proc_macro_deps = [ # Keep sorted. - "//packages/ic-deterministic-heap-bytes-derive", + "//packages/ic-heap-bytes-derive", "@crate_index//:paste", ], version = "0.1.0", deps = [ # Keep sorted. "@crate_index//:candid", + "@crate_index//:prometheus", + "@crate_index//:tempfile", ], ) rust_test( - name = "ic-deterministic-heap-bytes-test", - crate = ":ic-deterministic-heap-bytes", - proc_macro_deps = [ - # Keep sorted. - "//packages/ic-deterministic-heap-bytes-derive", - "@crate_index//:paste", - ], + name = "ic-heap-bytes-test", + crate = ":ic-heap-bytes", ) diff --git a/packages/ic-deterministic-heap-bytes/Cargo.toml b/packages/ic-heap-bytes/Cargo.toml similarity index 62% rename from packages/ic-deterministic-heap-bytes/Cargo.toml rename to packages/ic-heap-bytes/Cargo.toml index 942c60da03c5..7bb2fce6c9cd 100644 --- a/packages/ic-deterministic-heap-bytes/Cargo.toml +++ b/packages/ic-heap-bytes/Cargo.toml @@ -3,7 +3,7 @@ authors.workspace = true description.workspace = true documentation.workspace = true edition.workspace = true -name = "ic-deterministic-heap-bytes" +name = "ic-heap-bytes" version.workspace = true [lib] @@ -11,5 +11,7 @@ path = "src/lib.rs" [dependencies] candid = { workspace = true } -ic-deterministic-heap-bytes-derive = { path = "../ic-deterministic-heap-bytes-derive" } +ic-heap-bytes-derive = { path = "../ic-heap-bytes-derive" } paste = { workspace = true } +prometheus = { workspace = true } +tempfile = { workspace = true } diff --git a/packages/ic-deterministic-heap-bytes/README.md b/packages/ic-heap-bytes/README.md similarity index 89% rename from packages/ic-deterministic-heap-bytes/README.md rename to packages/ic-heap-bytes/README.md index 6342cd313dda..988043080a25 100644 --- a/packages/ic-deterministic-heap-bytes/README.md +++ b/packages/ic-heap-bytes/README.md @@ -6,7 +6,7 @@ The trait can be easily derived for structs and enums, enabling deterministic memory usage estimation: ```rust -use ic_deterministic_heap_bytes::DeterministicHeapBytes; +use ic_heap_bytes::{deterministic_total_bytes, DeterministicHeapBytes}; #[derive(DeterministicHeapBytes)] struct MyStruct { @@ -20,7 +20,7 @@ let s = MyStruct { }; assert_eq!(s.deterministic_heap_bytes(), size_of::() * 2 + 1 + 3 + 5); assert_eq!( - s.deterministic_total_bytes(), + deterministic_total_bytes(&s), size_of::() + size_of::() * 2 + 1 + 3 + 5 ); ``` @@ -35,7 +35,7 @@ To mitigate performance impact, field size calculations can be approximated using the `#[deterministic_heap_bytes(with = )]` attribute: ```rust -use ic_deterministic_heap_bytes::DeterministicHeapBytes; +use ic_heap_bytes::{deterministic_total_bytes, DeterministicHeapBytes}; #[derive(DeterministicHeapBytes)] struct CustomHeapBytes { @@ -51,7 +51,7 @@ let s = CustomHeapBytes { }; assert_eq!(s.deterministic_heap_bytes(), 1 + 2 * size_of::>() + 17); assert_eq!( - s.deterministic_total_bytes(), + deterministic_total_bytes(&s), size_of::() + 1 + 2 * size_of::>() + 17 ); ``` @@ -60,7 +60,7 @@ Closure errors are clearly reported: ```rust error[E0282]: type annotations needed - --> packages/ic-deterministic-heap-bytes/src/tests.rs:711:30 + --> packages/ic-heap-bytes/src/tests.rs:711:30 | 711 | #[deterministic_heap_bytes(with = |v| v.len() * size_of::() + 17)] | ^ - type must be known at this point @@ -97,7 +97,7 @@ enum SuperdWith { A function name may also be used instead of a closure: ```rust -use ic_deterministic_heap_bytes::DeterministicHeapBytes; +use ic_heap_bytes::{deterministic_total_bytes, DeterministicHeapBytes}; fn vec_approx(v: &[String]) -> usize { size_of_val(v) + 17 @@ -117,7 +117,7 @@ let s = CustomHeapBytes { }; assert_eq!(s.deterministic_heap_bytes(), 1 + 2 * size_of::>() + 17); assert_eq!( - s.deterministic_total_bytes(), + deterministic_total_bytes(&s), size_of::() + 1 + 2 * size_of::>() + 17 ); ``` diff --git a/packages/ic-deterministic-heap-bytes/src/lib.rs b/packages/ic-heap-bytes/src/lib.rs similarity index 57% rename from packages/ic-deterministic-heap-bytes/src/lib.rs rename to packages/ic-heap-bytes/src/lib.rs index b57297d76546..9f022fe29a8c 100644 --- a/packages/ic-deterministic-heap-bytes/src/lib.rs +++ b/packages/ic-heap-bytes/src/lib.rs @@ -1,6 +1,9 @@ -pub use ic_deterministic_heap_bytes_derive::DeterministicHeapBytes; +pub use ic_heap_bytes_derive::{DeterministicHeapBytes, HeapBytes}; use paste::paste; -use std::collections::BTreeMap; + +//////////////////////////////////////////////////////////////////////// +// DeterministicHeapBytes +//////////////////////////////////////////////////////////////////////// /// A trait to deterministically report heap memory usage. /// @@ -20,16 +23,16 @@ pub trait DeterministicHeapBytes { fn deterministic_heap_bytes(&self) -> usize { 0 } +} - /// Returns the deterministic total size of the object in bytes. - /// - /// The default implementation should suit most types, so typically - /// only the `deterministic_heap_bytes` function needs to be implemented for a new type. - fn deterministic_total_bytes(&self) -> usize { - size_of_val(self) + self.deterministic_heap_bytes() - } +/// Returns the deterministic total size of the object in bytes. +pub fn deterministic_total_bytes(t: &T) -> usize { + size_of_val(t) + t.deterministic_heap_bytes() } +//////////////////////////////////////////////////////////////////////// +// DeterministicHeapBytes scalar types. + impl DeterministicHeapBytes for u8 {} impl DeterministicHeapBytes for u16 {} impl DeterministicHeapBytes for u32 {} @@ -47,6 +50,13 @@ impl DeterministicHeapBytes for f64 {} impl DeterministicHeapBytes for bool {} impl DeterministicHeapBytes for char {} +//////////////////////////////////////////////////////////////////////// +// DeterministicHeapBytes standard library types. + +impl DeterministicHeapBytes for std::sync::atomic::AtomicU64 {} +impl DeterministicHeapBytes for std::time::Duration {} +impl DeterministicHeapBytes for std::fs::File {} + impl DeterministicHeapBytes for String { fn deterministic_heap_bytes(&self) -> usize { self.len() @@ -79,7 +89,7 @@ impl DeterministicHeapBytes for Vec { } impl DeterministicHeapBytes - for BTreeMap + for std::collections::BTreeMap { /// Calculates the precise heap size by summing the heap usage of all elements. /// @@ -98,6 +108,39 @@ impl DeterministicHeapByte } } +impl DeterministicHeapBytes for std::sync::Arc { + fn deterministic_heap_bytes(&self) -> usize { + self.as_ref().deterministic_heap_bytes() + } +} + +impl DeterministicHeapBytes for std::sync::Mutex { + fn deterministic_heap_bytes(&self) -> usize { + self.lock().unwrap().deterministic_heap_bytes() + } +} + +impl DeterministicHeapBytes for Option { + fn deterministic_heap_bytes(&self) -> usize { + match self { + Some(s) => s.deterministic_heap_bytes(), + None => 0, + } + } +} + +impl DeterministicHeapBytes for Result { + fn deterministic_heap_bytes(&self) -> usize { + match self { + Ok(ok) => ok.deterministic_heap_bytes(), + Err(err) => err.deterministic_heap_bytes(), + } + } +} + +//////////////////////////////////////////////////////////////////////// +// DeterministicHeapBytes tuples. + macro_rules! impl_heap_bytes_for_tuple { ( $( $idx:tt ),* ) => { paste! { @@ -120,25 +163,68 @@ impl_heap_bytes_for_tuple!(0, 1, 2, 3, 4, 5); impl_heap_bytes_for_tuple!(0, 1, 2, 3, 4, 5, 6); impl_heap_bytes_for_tuple!(0, 1, 2, 3, 4, 5, 6, 7); -impl DeterministicHeapBytes for Option { - fn deterministic_heap_bytes(&self) -> usize { - match self { - Some(s) => s.deterministic_heap_bytes(), - None => 0, - } +//////////////////////////////////////////////////////////////////////// +// DeterministicHeapBytes external types. + +impl DeterministicHeapBytes for candid::Principal {} +impl DeterministicHeapBytes for candid::types::principal::PrincipalError {} + +//////////////////////////////////////////////////////////////////////// +// HeapBytes +//////////////////////////////////////////////////////////////////////// + +/// A trait to estimate heap memory usage. +/// +/// It can be derived for structs and enums. The `#[heap_bytes(with = ...)]` +/// attribute can be used on variants and fields to specify a custom function +/// to estimate heap bytes for that variant or field. +pub trait HeapBytes { + /// Returns the total estimated size of heap-allocated data. + /// + /// This method performs a recursive heap memory estimation. + /// For large collections, this can be slow. In such cases, consider + /// using the `#[heap_bytes(with = ...)]` attribute and providing a constant + /// time estimation for that variant or field. + /// + /// The default implementation returns 0, which is correct for + /// types that do not have any heap allocations. + fn heap_bytes(&self) -> usize { + 0 } } -impl DeterministicHeapBytes for Result { - fn deterministic_heap_bytes(&self) -> usize { - match self { - Ok(ok) => ok.deterministic_heap_bytes(), - Err(err) => err.deterministic_heap_bytes(), - } +/// Returns the total estimated size of the object in bytes. +pub fn total_bytes(t: &T) -> usize { + size_of_val(t) + t.heap_bytes() +} + +// Use DeterministicHeapBytes as a fallback implementation for HeapBytes. +impl HeapBytes for T { + fn heap_bytes(&self) -> usize { + self.deterministic_heap_bytes() } } -impl DeterministicHeapBytes for candid::Principal {} +//////////////////////////////////////////////////////////////////////// +// HeapBytes external types. + +impl HeapBytes for prometheus::Histogram { + fn heap_bytes(&self) -> usize { + let num_buckets = prometheus::DEFAULT_BUCKETS.len(); + // To get the actual buckets and labels, we need to collect the metric, + // which is slow. Instead, we just assume that histogram allocates + // a default vector of buckets with no labels. + num_buckets * size_of::() + } +} +impl HeapBytes for prometheus::IntCounter {} +impl HeapBytes for prometheus::IntGauge {} +impl HeapBytes for tempfile::TempDir { + fn heap_bytes(&self) -> usize { + // TempDir allocates a string for the path. + self.path().as_os_str().len() + } +} #[cfg(test)] mod tests; diff --git a/packages/ic-deterministic-heap-bytes/src/tests.rs b/packages/ic-heap-bytes/src/tests.rs similarity index 71% rename from packages/ic-deterministic-heap-bytes/src/tests.rs rename to packages/ic-heap-bytes/src/tests.rs index 457fb2e3ac1e..c0a0357c4264 100644 --- a/packages/ic-deterministic-heap-bytes/src/tests.rs +++ b/packages/ic-heap-bytes/src/tests.rs @@ -1,35 +1,62 @@ use std::collections::BTreeMap; -use super::DeterministicHeapBytes; +use super::{deterministic_total_bytes, total_bytes, DeterministicHeapBytes, HeapBytes}; #[test] -fn empty_total_bytes() { +fn empty_deterministic_total_bytes() { #[derive(DeterministicHeapBytes, Default)] struct S {} - assert_eq!(S::default().deterministic_total_bytes(), 0); + assert_eq!(deterministic_total_bytes(&S::default()), 0); #[derive(DeterministicHeapBytes, Default)] struct T(); - assert_eq!(T::default().deterministic_total_bytes(), 0); + assert_eq!(deterministic_total_bytes(&T::default()), 0); #[derive(DeterministicHeapBytes, Default)] enum E { #[default] One, } - assert_eq!(E::default().deterministic_total_bytes(), 0); + assert_eq!(deterministic_total_bytes(&E::default()), 0); assert_eq!( - String::new().deterministic_total_bytes(), + deterministic_total_bytes(&String::new()), size_of::() ); - assert_eq!([()].deterministic_total_bytes(), 0); - assert_eq!(vec![()].deterministic_total_bytes(), size_of::>()); + assert_eq!(deterministic_total_bytes(&[()]), 0); + assert_eq!(deterministic_total_bytes(&vec![()]), size_of::>()); assert_eq!( - BTreeMap::<(), ()>::new().deterministic_total_bytes(), + deterministic_total_bytes(&BTreeMap::<(), ()>::new()), size_of::>() ); - assert_eq!(().deterministic_total_bytes(), 0); - assert_eq!(None::<()>.deterministic_total_bytes(), 1); - assert_eq!(Ok::<(), ()>(()).deterministic_total_bytes(), 1); - assert_eq!(Err::<(), ()>(()).deterministic_total_bytes(), 1); + assert_eq!(deterministic_total_bytes(&()), 0); + assert_eq!(deterministic_total_bytes(&None::<()>), 1); + assert_eq!(deterministic_total_bytes(&Ok::<(), ()>(())), 1); + assert_eq!(deterministic_total_bytes(&Err::<(), ()>(())), 1); +} + +#[test] +fn empty_total_bytes() { + #[derive(HeapBytes, Default)] + struct S {} + assert_eq!(total_bytes(&S::default()), 0); + #[derive(HeapBytes, Default)] + struct T(); + assert_eq!(total_bytes(&T::default()), 0); + #[derive(HeapBytes, Default)] + enum E { + #[default] + One, + } + assert_eq!(total_bytes(&E::default()), 0); + assert_eq!(total_bytes(&String::new()), size_of::()); + assert_eq!(total_bytes(&[()]), 0); + assert_eq!(total_bytes(&vec![()]), size_of::>()); + assert_eq!( + total_bytes(&BTreeMap::<(), ()>::new()), + size_of::>() + ); + assert_eq!(total_bytes(&()), 0); + assert_eq!(total_bytes(&None::<()>), 1); + assert_eq!(total_bytes(&Ok::<(), ()>(())), 1); + assert_eq!(total_bytes(&Err::<(), ()>(())), 1); } macro_rules! assert_struct_basic_field_total_bytes_eq { @@ -38,7 +65,7 @@ macro_rules! assert_struct_basic_field_total_bytes_eq { struct S { v: $field_type, } - assert_eq!(S::default().deterministic_total_bytes(), $expected_size); + assert_eq!(deterministic_total_bytes(&S::default()), $expected_size); }}; } @@ -69,14 +96,14 @@ fn struct_basic_fields_total_bytes() { v1: u8, v2: u128, } - assert_eq!(S::default().deterministic_total_bytes(), size_of::()); + assert_eq!(deterministic_total_bytes(&S::default()), size_of::()); } macro_rules! assert_tuple_struct_basic_field_total_bytes_eq { ($field_type:ty, $expected_size:expr) => {{ #[derive(DeterministicHeapBytes, Default)] struct S($field_type); - assert_eq!(S::default().deterministic_total_bytes(), $expected_size); + assert_eq!(deterministic_total_bytes(&S::default()), $expected_size); }}; } @@ -104,39 +131,39 @@ fn tuple_struct_basic_field_total_bytes() { fn tuple_struct_basic_fields_total_bytes() { #[derive(DeterministicHeapBytes, Default)] struct T1(u8); - assert_eq!(T1::default().deterministic_total_bytes(), size_of::()); + assert_eq!(deterministic_total_bytes(&T1::default()), size_of::()); #[derive(DeterministicHeapBytes, Default)] struct T2(u8, bool); assert!(size_of::() > size_of::()); - assert_eq!(T2::default().deterministic_total_bytes(), size_of::()); + assert_eq!(deterministic_total_bytes(&T2::default()), size_of::()); #[derive(DeterministicHeapBytes, Default)] struct T3(u8, bool, u16); assert!(size_of::() > size_of::()); - assert_eq!(T3::default().deterministic_total_bytes(), size_of::()); + assert_eq!(deterministic_total_bytes(&T3::default()), size_of::()); #[derive(DeterministicHeapBytes, Default)] struct T4(u8, bool, u16, u32); assert!(size_of::() > size_of::()); - assert_eq!(T4::default().deterministic_total_bytes(), size_of::()); + assert_eq!(deterministic_total_bytes(&T4::default()), size_of::()); #[derive(DeterministicHeapBytes, Default)] struct T5(u8, bool, u16, u32, f32); assert!(size_of::() > size_of::()); - assert_eq!(T5::default().deterministic_total_bytes(), size_of::()); + assert_eq!(deterministic_total_bytes(&T5::default()), size_of::()); #[derive(DeterministicHeapBytes, Default)] struct T6(u8, bool, u16, u32, f32, char); assert!(size_of::() > size_of::()); - assert_eq!(T6::default().deterministic_total_bytes(), size_of::()); + assert_eq!(deterministic_total_bytes(&T6::default()), size_of::()); #[derive(DeterministicHeapBytes, Default)] struct T7(u8, bool, u16, u32, f32, char, u64); assert!(size_of::() > size_of::()); - assert_eq!(T7::default().deterministic_total_bytes(), size_of::()); + assert_eq!(deterministic_total_bytes(&T7::default()), size_of::()); #[derive(DeterministicHeapBytes, Default)] struct T8(u8, bool, u16, u32, f32, char, u64, f64); assert!(size_of::() > size_of::()); - assert_eq!(T8::default().deterministic_total_bytes(), size_of::()); + assert_eq!(deterministic_total_bytes(&T8::default()), size_of::()); #[derive(DeterministicHeapBytes, Default)] struct T9(u8, bool, u16, u32, f32, char, u64, f64, u128); assert!(size_of::() > size_of::()); - assert_eq!(T9::default().deterministic_total_bytes(), size_of::()); + assert_eq!(deterministic_total_bytes(&T9::default()), size_of::()); } macro_rules! assert_enum_basic_field_total_bytes_eq { @@ -146,7 +173,7 @@ macro_rules! assert_enum_basic_field_total_bytes_eq { One($field_type), } assert_eq!( - E::One(<$field_type>::default()).deterministic_total_bytes(), + deterministic_total_bytes(&E::One(<$field_type>::default())), $expected_size ); }}; @@ -173,13 +200,13 @@ fn enum_basic_field_total_bytes() { } #[test] -fn enum_basic_fields_total_bytes() { +fn enum_tuple_total_bytes() { #[derive(DeterministicHeapBytes)] enum E { One(u8, u128), } assert_eq!( - E::One(::default(), ::default()).deterministic_total_bytes(), + deterministic_total_bytes(&E::One(::default(), ::default())), size_of::() ); } @@ -192,7 +219,7 @@ fn enum_repr_total_bytes() { #[default] One, } - assert_eq!(U8::default().deterministic_total_bytes(), size_of::()); + assert_eq!(deterministic_total_bytes(&U8::default()), size_of::()); #[derive(DeterministicHeapBytes, Default)] #[repr(u64)] @@ -200,80 +227,92 @@ fn enum_repr_total_bytes() { #[default] One, } - assert_eq!(U64::default().deterministic_total_bytes(), size_of::()); + assert_eq!(deterministic_total_bytes(&U64::default()), size_of::()); +} + +#[test] +fn enum_discriminant_total_bytes() { + #[derive(DeterministicHeapBytes, Default)] + #[allow(dead_code)] + enum E { + #[default] + One = 1, + Two = 2, + } + assert_eq!(deterministic_total_bytes(&E::default()), size_of::()); } #[test] fn string_total_bytes() { let b = size_of::(); - assert_eq!("".to_string().deterministic_total_bytes(), b); - assert_eq!("123".to_string().deterministic_total_bytes(), b + 3); + assert_eq!(deterministic_total_bytes(&"".to_string()), b); + assert_eq!(deterministic_total_bytes(&"123".to_string()), b + 3); } #[test] fn array_basic_total_bytes() { assert_eq!( - [42_u8; 42].deterministic_total_bytes(), + deterministic_total_bytes(&[42_u8; 42]), size_of::() * 42 ); assert_eq!( - [42_u16; 42].deterministic_total_bytes(), + deterministic_total_bytes(&[42_u16; 42]), size_of::() * 42 ); assert_eq!( - [42_u32; 42].deterministic_total_bytes(), + deterministic_total_bytes(&[42_u32; 42]), size_of::() * 42 ); assert_eq!( - [42_u64; 42].deterministic_total_bytes(), + deterministic_total_bytes(&[42_u64; 42]), size_of::() * 42 ); assert_eq!( - [42_u128; 42].deterministic_total_bytes(), + deterministic_total_bytes(&[42_u128; 42]), size_of::() * 42 ); assert_eq!( - [42_usize; 42].deterministic_total_bytes(), + deterministic_total_bytes(&[42_usize; 42]), size_of::() * 42 ); assert_eq!( - [42_i8; 42].deterministic_total_bytes(), + deterministic_total_bytes(&[42_i8; 42]), size_of::() * 42 ); assert_eq!( - [42_i16; 42].deterministic_total_bytes(), + deterministic_total_bytes(&[42_i16; 42]), size_of::() * 42 ); assert_eq!( - [42_i32; 42].deterministic_total_bytes(), + deterministic_total_bytes(&[42_i32; 42]), size_of::() * 42 ); assert_eq!( - [42_i64; 42].deterministic_total_bytes(), + deterministic_total_bytes(&[42_i64; 42]), size_of::() * 42 ); assert_eq!( - [42_i128; 42].deterministic_total_bytes(), + deterministic_total_bytes(&[42_i128; 42]), size_of::() * 42 ); assert_eq!( - [42_isize; 42].deterministic_total_bytes(), + deterministic_total_bytes(&[42_isize; 42]), size_of::() * 42 ); assert_eq!( - [42_f32; 42].deterministic_total_bytes(), + deterministic_total_bytes(&[42_f32; 42]), size_of::() * 42 ); assert_eq!( - [42_f64; 42].deterministic_total_bytes(), + deterministic_total_bytes(&[42_f64; 42]), size_of::() * 42 ); assert_eq!( - [true; 42].deterministic_total_bytes(), + deterministic_total_bytes(&[true; 42]), size_of::() * 42 ); assert_eq!( - ['c'; 42].deterministic_total_bytes(), + deterministic_total_bytes(&['c'; 42]), size_of::() * 42 ); } @@ -282,67 +321,67 @@ fn array_basic_total_bytes() { fn vec_basic_total_bytes() { let b = size_of::>(); assert_eq!( - vec![42_u8; 42].deterministic_total_bytes(), + deterministic_total_bytes(&vec![42_u8; 42]), b + size_of::() * 42 ); assert_eq!( - vec![42_u16; 42].deterministic_total_bytes(), + deterministic_total_bytes(&vec![42_u16; 42]), b + size_of::() * 42 ); assert_eq!( - vec![42_u32; 42].deterministic_total_bytes(), + deterministic_total_bytes(&vec![42_u32; 42]), b + size_of::() * 42 ); assert_eq!( - vec![42_u64; 42].deterministic_total_bytes(), + deterministic_total_bytes(&vec![42_u64; 42]), b + size_of::() * 42 ); assert_eq!( - vec![42_u128; 42].deterministic_total_bytes(), + deterministic_total_bytes(&vec![42_u128; 42]), b + size_of::() * 42 ); assert_eq!( - vec![42_usize; 42].deterministic_total_bytes(), + deterministic_total_bytes(&vec![42_usize; 42]), b + size_of::() * 42 ); assert_eq!( - vec![42_i8; 42].deterministic_total_bytes(), + deterministic_total_bytes(&vec![42_i8; 42]), b + size_of::() * 42 ); assert_eq!( - vec![42_i16; 42].deterministic_total_bytes(), + deterministic_total_bytes(&vec![42_i16; 42]), b + size_of::() * 42 ); assert_eq!( - vec![42_i32; 42].deterministic_total_bytes(), + deterministic_total_bytes(&vec![42_i32; 42]), b + size_of::() * 42 ); assert_eq!( - vec![42_i64; 42].deterministic_total_bytes(), + deterministic_total_bytes(&vec![42_i64; 42]), b + size_of::() * 42 ); assert_eq!( - vec![42_i128; 42].deterministic_total_bytes(), + deterministic_total_bytes(&vec![42_i128; 42]), b + size_of::() * 42 ); assert_eq!( - vec![42_isize; 42].deterministic_total_bytes(), + deterministic_total_bytes(&vec![42_isize; 42]), b + size_of::() * 42 ); assert_eq!( - vec![42_f32; 42].deterministic_total_bytes(), + deterministic_total_bytes(&vec![42_f32; 42]), b + size_of::() * 42 ); assert_eq!( - vec![42_f64; 42].deterministic_total_bytes(), + deterministic_total_bytes(&vec![42_f64; 42]), b + size_of::() * 42 ); assert_eq!( - vec![true; 42].deterministic_total_bytes(), + deterministic_total_bytes(&vec![true; 42]), b + size_of::() * 42 ); assert_eq!( - vec!['c'; 42].deterministic_total_bytes(), + deterministic_total_bytes(&vec!['c'; 42]), b + size_of::() * 42 ); } @@ -351,7 +390,7 @@ macro_rules! assert_btree_map_basic_total_bytes_eq { ($field_type:ty, $expected_size:expr) => {{ let t = <$field_type>::default(); assert_eq!( - BTreeMap::from([(t, t)]).deterministic_total_bytes(), + deterministic_total_bytes(&BTreeMap::from([(t, t)])), $expected_size ); }}; @@ -379,151 +418,151 @@ fn btree_map_basic_total_bytes() { #[test] fn tuple_basic_total_bytes() { - assert_eq!((42_u8,).deterministic_total_bytes(), size_of::()); + assert_eq!(deterministic_total_bytes(&(42_u8,)), size_of::()); assert_eq!( - (42_u8, 42_u8,).deterministic_total_bytes(), + deterministic_total_bytes(&(42_u8, 42_u8,)), size_of::() * 2 ); assert_eq!( - (42_u8, 42_u8, 42_u8,).deterministic_total_bytes(), + deterministic_total_bytes(&(42_u8, 42_u8, 42_u8,)), size_of::() * 3 ); assert_eq!( - (42_u8, 42_u8, 42_u8, 42_u8,).deterministic_total_bytes(), + deterministic_total_bytes(&(42_u8, 42_u8, 42_u8, 42_u8,)), size_of::() * 4 ); assert_eq!( - (42_u8, 42_u8, 42_u8, 42_u8, 42_u8,).deterministic_total_bytes(), + deterministic_total_bytes(&(42_u8, 42_u8, 42_u8, 42_u8, 42_u8,)), size_of::() * 5 ); assert_eq!( - (42_u8, 42_u8, 42_u8, 42_u8, 42_u8, 42_u8,).deterministic_total_bytes(), + deterministic_total_bytes(&(42_u8, 42_u8, 42_u8, 42_u8, 42_u8, 42_u8,)), size_of::() * 6 ); assert_eq!( - (42_u8, 42_u8, 42_u8, 42_u8, 42_u8, 42_u8, 42_u8,).deterministic_total_bytes(), + deterministic_total_bytes(&(42_u8, 42_u8, 42_u8, 42_u8, 42_u8, 42_u8, 42_u8,)), size_of::() * 7 ); assert_eq!( - (42_u8, 42_u8, 42_u8, 42_u8, 42_u8, 42_u8, 42_u8, 42_u8,).deterministic_total_bytes(), + deterministic_total_bytes(&(42_u8, 42_u8, 42_u8, 42_u8, 42_u8, 42_u8, 42_u8, 42_u8,)), size_of::<(u8, u8, u8, u8, u8, u8, u8, u8,)>() ); } #[test] fn option_basic_total_bytes() { - assert_eq!(Some(42_u8).deterministic_total_bytes(), size_of::() * 2); - assert_eq!(None::.deterministic_total_bytes(), size_of::() * 2); + assert_eq!(deterministic_total_bytes(&Some(42_u8)), size_of::() * 2); + assert_eq!(deterministic_total_bytes(&None::), size_of::() * 2); assert_eq!( - Some(42_u16).deterministic_total_bytes(), + deterministic_total_bytes(&Some(42_u16)), size_of::() * 2 ); assert_eq!( - None::.deterministic_total_bytes(), + deterministic_total_bytes(&None::), size_of::() * 2 ); assert_eq!( - Some(42_u32).deterministic_total_bytes(), + deterministic_total_bytes(&Some(42_u32)), size_of::() * 2 ); assert_eq!( - None::.deterministic_total_bytes(), + deterministic_total_bytes(&None::), size_of::() * 2 ); assert_eq!( - Some(42_u64).deterministic_total_bytes(), + deterministic_total_bytes(&Some(42_u64)), size_of::() * 2 ); assert_eq!( - None::.deterministic_total_bytes(), + deterministic_total_bytes(&None::), size_of::() * 2 ); assert_eq!( - Some(42_u128).deterministic_total_bytes(), + deterministic_total_bytes(&Some(42_u128)), size_of::() * 2 ); assert_eq!( - None::.deterministic_total_bytes(), + deterministic_total_bytes(&None::), size_of::() * 2 ); assert_eq!( - Some(42_usize).deterministic_total_bytes(), + deterministic_total_bytes(&Some(42_usize)), size_of::() * 2 ); assert_eq!( - None::.deterministic_total_bytes(), + deterministic_total_bytes(&None::), size_of::() * 2 ); - assert_eq!(Some(true).deterministic_total_bytes(), size_of::()); - assert_eq!(None::.deterministic_total_bytes(), size_of::()); - assert_eq!(Some('c').deterministic_total_bytes(), size_of::()); - assert_eq!(None::.deterministic_total_bytes(), size_of::()); + assert_eq!(deterministic_total_bytes(&Some(true)), size_of::()); + assert_eq!(deterministic_total_bytes(&None::), size_of::()); + assert_eq!(deterministic_total_bytes(&Some('c')), size_of::()); + assert_eq!(deterministic_total_bytes(&None::), size_of::()); } #[test] fn result_basic_total_bytes() { assert_eq!( - Ok::(42).deterministic_total_bytes(), + deterministic_total_bytes(&Ok::(42)), size_of::() * 2 ); assert_eq!( - Err::(42).deterministic_total_bytes(), + deterministic_total_bytes(&Err::(42)), size_of::() * 2 ); assert_eq!( - Ok::(42).deterministic_total_bytes(), + deterministic_total_bytes(&Ok::(42)), size_of::() * 2 ); assert_eq!( - Err::(42).deterministic_total_bytes(), + deterministic_total_bytes(&Err::(42)), size_of::() * 2 ); assert_eq!( - Ok::(42).deterministic_total_bytes(), + deterministic_total_bytes(&Ok::(42)), size_of::() * 2 ); assert_eq!( - Err::(42).deterministic_total_bytes(), + deterministic_total_bytes(&Err::(42)), size_of::() * 2 ); assert_eq!( - Ok::(42).deterministic_total_bytes(), + deterministic_total_bytes(&Ok::(42)), size_of::() * 2 ); assert_eq!( - Err::(42).deterministic_total_bytes(), + deterministic_total_bytes(&Err::(42)), size_of::() * 2 ); assert_eq!( - Ok::(42).deterministic_total_bytes(), + deterministic_total_bytes(&Ok::(42)), size_of::() * 2 ); assert_eq!( - Err::(42).deterministic_total_bytes(), + deterministic_total_bytes(&Err::(42)), size_of::() * 2 ); assert_eq!( - Ok::(42).deterministic_total_bytes(), + deterministic_total_bytes(&Ok::(42)), size_of::() * 2 ); assert_eq!( - Err::(42).deterministic_total_bytes(), + deterministic_total_bytes(&Err::(42)), size_of::() * 2 ); assert_eq!( - Ok::(true).deterministic_total_bytes(), + deterministic_total_bytes(&Ok::(true)), size_of::() * 2 ); assert_eq!( - Err::(true).deterministic_total_bytes(), + deterministic_total_bytes(&Err::(true)), size_of::() * 2 ); assert_eq!( - Ok::('c').deterministic_total_bytes(), + deterministic_total_bytes(&Ok::('c')), size_of::() * 2 ); assert_eq!( - Err::('c').deterministic_total_bytes(), + deterministic_total_bytes(&Err::('c')), size_of::() * 2 ); } @@ -541,7 +580,7 @@ fn mixed_struct() { m: BTreeMap, } assert_eq!( - S::default().deterministic_total_bytes(), + deterministic_total_bytes(&S::default()), size_of::() + size_of::() + size_of::>() @@ -569,7 +608,7 @@ fn mixed_enum() { BTreeMap(BTreeMap), } assert_eq!( - E::default().deterministic_total_bytes(), + deterministic_total_bytes(&E::default()), size_of::() + size_of::() .max(size_of::>()) @@ -593,7 +632,7 @@ fn mixed_tuple() { BTreeMap::from([(String::new(), String::new())]), ); assert_eq!( - tuple.deterministic_total_bytes(), + deterministic_total_bytes(&tuple), size_of::() + size_of::() + size_of::>() @@ -609,7 +648,7 @@ fn mixed_tuple() { let tuple = ("1".to_string(), "123".to_string(), "12345".to_string()); assert_eq!( - tuple.deterministic_total_bytes(), + deterministic_total_bytes(&tuple), size_of::() * 3 + 1 + 3 + 5 ); } @@ -621,7 +660,7 @@ fn mixed_array() { ("12345".to_string(), "1234567".to_string()), ]; assert_eq!( - arr.deterministic_total_bytes(), + deterministic_total_bytes(&arr), size_of::<[(String, String); 2]>() + 1 + 3 + 5 + 7 ); } @@ -633,7 +672,7 @@ fn mixed_vec() { ("12345".to_string(), "1234567".to_string()), ]; assert_eq!( - vec.deterministic_total_bytes(), + deterministic_total_bytes(&vec), size_of::>() + size_of::() * 4 + 1 + 3 + 5 + 7 ); } @@ -646,7 +685,7 @@ fn mixed_btree_map() { ]); // Also account for edges, i.e. pointers to the child nodes. assert_eq!( - btree_map.deterministic_total_bytes(), + deterministic_total_bytes(&btree_map), size_of::>() + size_of::() * 4 + size_of::() * 2 @@ -694,7 +733,7 @@ fn nested_vec_enum_struct_string() { ]), ]; assert_eq!( - v.deterministic_total_bytes(), + deterministic_total_bytes(&v), size_of::>() + size_of::() + size_of::() @@ -761,7 +800,7 @@ fn with_vec_enum_struct_string() { ]; // Same as above, but the third option has a custom size calculation. assert_eq!( - v.deterministic_total_bytes(), + deterministic_total_bytes(&v), size_of::>() + size_of::() + size_of::() @@ -805,19 +844,18 @@ fn with_external_types() { e: ExternalEnum, } assert_eq!( - S::default().deterministic_total_bytes(), + deterministic_total_bytes(&S::default()), size_of::() + 7 + 11 ); assert_eq!( - S { + deterministic_total_bytes(&S { v: vec![ ExternalStruct::default(), ExternalStruct::default(), ExternalStruct::default() ], e: ExternalEnum::Two(u32::MAX), - } - .deterministic_total_bytes(), + }), size_of::() + 3 + 7 + 11 ); } @@ -831,11 +869,10 @@ fn custom_heap_bytes() { } assert_eq!( - PreciseCount { + deterministic_total_bytes(&PreciseCount { s: "1".to_string(), v: vec!["123".to_string(), "12345".to_string()] - } - .deterministic_total_bytes(), + }), size_of::() + size_of::() * 2 + 1 + 3 + 5 ); } @@ -851,11 +888,10 @@ fn struct_heap_bytes_with() { } assert_eq!( - FieldWith { + deterministic_total_bytes(&FieldWith { string: "1".to_string(), vec: vec!["123".to_string(), "12345".to_string()] - } - .deterministic_total_bytes(), + }), size_of::() + 1 + 5 + 2 + 7 ); @@ -867,14 +903,13 @@ fn struct_heap_bytes_with() { } assert_eq!( - SuperWith { + deterministic_total_bytes(&SuperWith { string: "1".to_string(), vec: vec![FieldWith { string: "1".to_string(), vec: vec!["123".to_string(), "12345".to_string()] }] - } - .deterministic_total_bytes(), + }), size_of::() + 1 + 11 + size_of::() + 1 + 5 + 2 + 7 ); } @@ -890,11 +925,14 @@ fn enum_heap_bytes_with() { } assert_eq!( - VariantWith::One("1".to_string()).deterministic_total_bytes(), + deterministic_total_bytes(&VariantWith::One("1".to_string())), size_of::() + 1 + 13 ); assert_eq!( - VariantWith::Two(vec!["123".to_string(), "12345".to_string()]).deterministic_total_bytes(), + deterministic_total_bytes(&VariantWith::Two(vec![ + "123".to_string(), + "12345".to_string() + ])), size_of::() + 2 + 17 ); @@ -911,15 +949,14 @@ fn enum_heap_bytes_with() { } assert_eq!( - FieldWith::One("1".to_string(), "123".to_string()).deterministic_total_bytes(), + deterministic_total_bytes(&FieldWith::One("1".to_string(), "123".to_string())), size_of::() + 1 + 3 + 19 ); assert_eq!( - FieldWith::Two( + deterministic_total_bytes(&FieldWith::Two( "1".to_string(), vec!["123".to_string(), "12345".to_string()] - ) - .deterministic_total_bytes(), + )), size_of::() + 1 + 2 + 23 ); @@ -934,18 +971,17 @@ fn enum_heap_bytes_with() { } assert_eq!( - SuperdWith::One("1".to_string(), "123".to_string()).deterministic_total_bytes(), + deterministic_total_bytes(&SuperdWith::One("1".to_string(), "123".to_string())), size_of::() + 1 + 3 + 27 ); assert_eq!( - SuperdWith::Two( + deterministic_total_bytes(&SuperdWith::Two( "12345".to_string(), vec![FieldWith::Two( "1".to_string(), vec!["123".to_string(), "12345".to_string()] )] - ) - .deterministic_total_bytes(), + )), size_of::() + 5 + 29 + size_of::() + 1 + 2 + 23 ); } @@ -967,7 +1003,7 @@ fn example_struct() { size_of::() * 2 + 1 + 3 + 5 ); assert_eq!( - s.deterministic_total_bytes(), + deterministic_total_bytes(&s), size_of::() + size_of::() * 2 + 1 + 3 + 5 ); @@ -988,7 +1024,7 @@ fn example_struct() { 1 + 2 * size_of::>() + 17 ); assert_eq!( - s.deterministic_total_bytes(), + deterministic_total_bytes(&s), size_of::() + 1 + 2 * size_of::>() + 17 ); } @@ -1016,7 +1052,44 @@ fn example_struct_with_function() { 1 + 2 * size_of::>() + 17 ); assert_eq!( - s.deterministic_total_bytes(), + deterministic_total_bytes(&s), size_of::() + 1 + 2 * size_of::>() + 17 ); } + +#[test] +fn mix_deterministic_and_normal_heap_bytes() { + #[derive(DeterministicHeapBytes)] + struct DeterministicStruct { + #[deterministic_heap_bytes(with = |s: &String| s.len() + 5)] + string: String, + #[deterministic_heap_bytes(with = |_v| self.vec.len() + 7)] + vec: Vec, + } + + assert_eq!( + deterministic_total_bytes(&DeterministicStruct { + string: "1".to_string(), + vec: vec!["123".to_string(), "12345".to_string()] + }), + size_of::() + 1 + 5 + 2 + 7 + ); + + #[derive(HeapBytes)] + struct NormalStruct { + #[heap_bytes(with = |s: &String| s.len() + 11)] + string: String, + vec: Vec, + } + + assert_eq!( + total_bytes(&NormalStruct { + string: "1".to_string(), + vec: vec![DeterministicStruct { + string: "1".to_string(), + vec: vec!["123".to_string(), "12345".to_string()] + }] + }), + size_of::() + 1 + 11 + size_of::() + 1 + 5 + 2 + 7 + ); +} diff --git a/rs/embedders/BUILD.bazel b/rs/embedders/BUILD.bazel index 96f6a3cdcf37..a714860454b2 100644 --- a/rs/embedders/BUILD.bazel +++ b/rs/embedders/BUILD.bazel @@ -8,6 +8,7 @@ package(default_visibility = ["//visibility:public"]) DEPENDENCIES = [ # Keep sorted. "//packages/ic-error-types", + "//packages/ic-heap-bytes", "//rs/config", "//rs/cycles_account_manager", "//rs/interfaces", diff --git a/rs/embedders/Cargo.toml b/rs/embedders/Cargo.toml index 81f51e52e933..719c79d973e3 100644 --- a/rs/embedders/Cargo.toml +++ b/rs/embedders/Cargo.toml @@ -13,6 +13,7 @@ clap = { workspace = true } ic-btc-interface = { workspace = true } ic-config = { path = "../config" } ic-cycles-account-manager = { path = "../cycles_account_manager" } +ic-heap-bytes = { path = "../../packages/ic-heap-bytes" } ic-error-types = { path = "../../packages/ic-error-types" } ic-interfaces = { path = "../interfaces" } ic-logger = { path = "../monitoring/logger" } diff --git a/rs/embedders/src/compilation_cache.rs b/rs/embedders/src/compilation_cache.rs index e4ffc8ac13fb..ac989927f40d 100644 --- a/rs/embedders/src/compilation_cache.rs +++ b/rs/embedders/src/compilation_cache.rs @@ -9,8 +9,9 @@ use std::{ use tempfile::TempDir; use crate::{OnDiskSerializedModule, SerializedModule}; +use ic_heap_bytes::HeapBytes; use ic_interfaces::execution_environment::{HypervisorError, HypervisorResult}; -use ic_types::{MemoryDiskBytes, NumBytes}; +use ic_types::{DiskBytes, NumBytes}; use ic_utils_lru_cache::LruCache; use ic_wasm_types::{CanisterModule, WasmHash}; @@ -29,6 +30,7 @@ const DEFAULT_MEMORY_CAPACITY: NumBytes = NumBytes::new(10 * GB); /// Stores the serialized modules of wasm code that has already been compiled so /// that it can be used again without recompiling. +#[derive(HeapBytes)] pub struct CompilationCache { /// Directory holding all the temporary files. It will be deleted on /// drop. @@ -41,11 +43,7 @@ pub struct CompilationCache { max_entries: usize, } -impl MemoryDiskBytes for CompilationCache { - fn memory_bytes(&self) -> usize { - self.cache.lock().unwrap().memory_bytes() - } - +impl DiskBytes for CompilationCache { fn disk_bytes(&self) -> usize { self.cache.lock().unwrap().disk_bytes() } diff --git a/rs/embedders/src/serialized_module.rs b/rs/embedders/src/serialized_module.rs index 4a8f68c56e15..786f70a6e04f 100644 --- a/rs/embedders/src/serialized_module.rs +++ b/rs/embedders/src/serialized_module.rs @@ -8,9 +8,10 @@ use std::{ sync::Arc, }; +use ic_heap_bytes::DeterministicHeapBytes; use ic_interfaces::execution_environment::{HypervisorError, HypervisorResult}; use ic_replicated_state::canister_state::execution_state::WasmMetadata; -use ic_types::{methods::WasmMethod, MemoryDiskBytes, NumInstructions}; +use ic_types::{methods::WasmMethod, DiskBytes, NumInstructions}; use ic_wasm_types::WasmEngineError; use nix::sys::mman::{mmap, MapFlags, ProtFlags}; use serde::{Deserialize, Serialize}; @@ -82,16 +83,6 @@ pub struct SerializedModule { pub is_wasm64: bool, } -impl MemoryDiskBytes for SerializedModule { - fn memory_bytes(&self) -> usize { - self.bytes.0.len() - } - - fn disk_bytes(&self) -> usize { - 0 - } -} - impl SerializedModule { pub(crate) fn new( module: &Module, @@ -149,7 +140,7 @@ pub struct InitialStateData { /// descriptors are duplicated when passed to the sandbox for execution (this /// happens implicitly when sending over the socket). The files should only be /// accessed through mmap - otherwise seeks could interfere with each other. -#[derive(Debug)] +#[derive(Debug, DeterministicHeapBytes)] pub struct OnDiskSerializedModule { /// Bytes of the compilation artifact. pub bytes: File, @@ -163,11 +154,7 @@ pub struct OnDiskSerializedModule { pub is_wasm64: bool, } -impl MemoryDiskBytes for OnDiskSerializedModule { - fn memory_bytes(&self) -> usize { - std::mem::size_of::() - } - +impl DiskBytes for OnDiskSerializedModule { fn disk_bytes(&self) -> usize { (self.bytes.metadata().unwrap().len() + self.initial_state_data.metadata().unwrap().len()) as usize diff --git a/rs/embedders/src/wasm_utils.rs b/rs/embedders/src/wasm_utils.rs index 69c6ed58060a..aaf3755a753b 100644 --- a/rs/embedders/src/wasm_utils.rs +++ b/rs/embedders/src/wasm_utils.rs @@ -4,6 +4,7 @@ use std::{ }; use ic_config::embedders::Config as EmbeddersConfig; +use ic_heap_bytes::DeterministicHeapBytes; use ic_interfaces::execution_environment::HypervisorResult; use ic_replicated_state::{ canister_state::{execution_state::WasmMetadata, WASM_PAGE_SIZE_IN_BYTES}, @@ -26,7 +27,9 @@ pub mod instrumentation; mod system_api_replacements; pub mod validation; -#[derive(Copy, Clone, Eq, PartialEq, Debug, Default, Deserialize, Serialize)] +#[derive( + Copy, Clone, DeterministicHeapBytes, Eq, PartialEq, Debug, Default, Deserialize, Serialize, +)] pub struct WasmImportsDetails { // True if the module imports these IC0 methods. pub imports_call_cycles_add: bool, diff --git a/rs/execution_environment/BUILD.bazel b/rs/execution_environment/BUILD.bazel index 8b628c373ec2..fbd4e17220cc 100644 --- a/rs/execution_environment/BUILD.bazel +++ b/rs/execution_environment/BUILD.bazel @@ -6,6 +6,7 @@ package(default_visibility = ["//visibility:public"]) DEPENDENCIES = [ # Keep sorted. "//packages/ic-error-types", + "//packages/ic-heap-bytes", "//rs/canister_sandbox:backend_lib", "//rs/config", "//rs/crypto/prng", diff --git a/rs/execution_environment/Cargo.toml b/rs/execution_environment/Cargo.toml index ad4195bbf718..768f7251cd0d 100644 --- a/rs/execution_environment/Cargo.toml +++ b/rs/execution_environment/Cargo.toml @@ -18,6 +18,7 @@ ic-crypto-sha2 = { path = "../crypto/sha2" } ic-crypto-tree-hash = { path = "../crypto/tree_hash" } ic-crypto-utils-canister-threshold-sig = { path = "../crypto/utils/canister_threshold_sig" } ic-cycles-account-manager = { path = "../cycles_account_manager" } +ic-heap-bytes = { path = "../../packages/ic-heap-bytes" } ic-embedders = { path = "../embedders" } ic-error-types = { path = "../../packages/ic-error-types" } ic-interfaces = { path = "../interfaces" } diff --git a/rs/execution_environment/src/hypervisor.rs b/rs/execution_environment/src/hypervisor.rs index 83b36d36f586..ea93a4587937 100644 --- a/rs/execution_environment/src/hypervisor.rs +++ b/rs/execution_environment/src/hypervisor.rs @@ -11,6 +11,7 @@ use ic_embedders::{ CompilationCache, CompilationCacheBuilder, CompilationResult, WasmExecutionInput, WasmtimeEmbedder, }; +use ic_heap_bytes::HeapBytes; use ic_interfaces::execution_environment::{ HypervisorError, HypervisorResult, WasmExecutionOutput, }; @@ -24,8 +25,8 @@ use ic_replicated_state::{ }; use ic_types::batch::CanisterCyclesCostSchedule; use ic_types::{ - messages::RequestMetadata, methods::FuncRef, CanisterId, MemoryDiskBytes, NumBytes, - NumInstructions, SubnetId, Time, + messages::RequestMetadata, methods::FuncRef, CanisterId, DiskBytes, NumBytes, NumInstructions, + SubnetId, Time, }; use ic_wasm_types::CanisterModule; use prometheus::{Histogram, IntCounter, IntGaugeVec}; @@ -166,7 +167,7 @@ impl Hypervisor { if let Some(compilation_result) = compilation_result { self.metrics.observe_compilation_metrics( &compilation_result, - self.compilation_cache.memory_bytes(), + self.compilation_cache.heap_bytes(), self.compilation_cache.disk_bytes(), ); } @@ -409,7 +410,7 @@ impl Hypervisor { if let Some(compilation_result) = compilation_result { self.metrics.observe_compilation_metrics( &compilation_result, - self.compilation_cache.memory_bytes(), + self.compilation_cache.heap_bytes(), self.compilation_cache.disk_bytes(), ); } diff --git a/rs/execution_environment/src/query_handler/query_cache.rs b/rs/execution_environment/src/query_handler/query_cache.rs index f8527b7340cf..01161d1d1845 100644 --- a/rs/execution_environment/src/query_handler/query_cache.rs +++ b/rs/execution_environment/src/query_handler/query_cache.rs @@ -1,5 +1,6 @@ use ic_base_types::{CanisterId, NumBytes}; use ic_error_types::UserError; +use ic_heap_bytes::{total_bytes, DeterministicHeapBytes, HeapBytes}; use ic_interfaces::execution_environment::SystemApiCallCounters; use ic_metrics::MetricsRegistry; use ic_query_stats::QueryStatsCollector; @@ -8,11 +9,11 @@ use ic_types::{ batch::QueryStats, ingress::WasmResult, messages::{CertificateDelegationFormat, CertificateDelegationMetadata, Query}, - Cycles, MemoryDiskBytes, Time, UserId, + Cycles, DiskBytes, Time, UserId, }; use ic_utils_lru_cache::LruCache; use prometheus::{Histogram, IntCounter, IntGauge}; -use std::{collections::BTreeMap, mem::size_of_val, sync::Mutex, time::Duration}; +use std::{collections::BTreeMap, sync::Mutex, time::Duration}; use crate::metrics::duration_histogram; @@ -21,6 +22,7 @@ mod tests; //////////////////////////////////////////////////////////////////////// /// Query Cache metrics. +#[derive(HeapBytes)] pub(crate) struct QueryCacheMetrics { pub hits: IntCounter, pub hits_with_ignored_time: IntCounter, @@ -128,7 +130,7 @@ impl QueryCacheMetrics { /// /// The key is to distinguish query cache entries, i.e. entries with different /// keys are (almost) completely independent from each other. -#[derive(Clone, Eq, PartialEq, Hash)] +#[derive(Clone, DeterministicHeapBytes, Eq, PartialEq, Hash)] pub(crate) struct EntryKey { /// Query source. pub source: UserId, @@ -158,22 +160,14 @@ impl EntryKey { } } -impl MemoryDiskBytes for EntryKey { - fn memory_bytes(&self) -> usize { - size_of_val(self) + self.method_name.len() + self.method_payload.len() - } - - fn disk_bytes(&self) -> usize { - 0 - } -} +impl DiskBytes for EntryKey {} //////////////////////////////////////////////////////////////////////// /// Query Cache entry environment metadata captured before the query execution. /// /// The cache entry is valid as long as the metadata is unchanged, /// or it can be proven that the query does not depend on the change. -#[derive(PartialEq)] +#[derive(DeterministicHeapBytes, PartialEq)] pub(crate) struct EntryEnv { /// The consensus-determined time when the query is executed. pub batch_time: Time, @@ -206,6 +200,7 @@ impl EntryEnv { //////////////////////////////////////////////////////////////////////// /// Query Cache entry value. +#[derive(DeterministicHeapBytes)] pub(crate) struct EntryValue { /// Query Cache entry environment metadata captured before the query execution. env: EntryEnv, @@ -219,15 +214,7 @@ pub(crate) struct EntryValue { ignore_canister_balances: bool, } -impl MemoryDiskBytes for EntryValue { - fn memory_bytes(&self) -> usize { - size_of_val(self) + self.result.memory_bytes() - } - - fn disk_bytes(&self) -> usize { - 0 - } -} +impl DiskBytes for EntryValue {} impl EntryValue { pub(crate) fn new( @@ -377,6 +364,7 @@ impl EntryValue { //////////////////////////////////////////////////////////////////////// /// Replica Side Query Cache. +#[derive(HeapBytes)] pub(crate) struct QueryCache { // We can't use `RwLock`, as the `LruCache::get()` requires mutable reference // to update the LRU. @@ -385,20 +373,10 @@ pub(crate) struct QueryCache { max_expiry_time: Duration, /// The upper limit on how long the data certificate stays valid in the query cache. data_certificate_expiry_time: Duration, - /// Query cache metrics (public for tests) + /// Query cache metrics (public for tests). pub(crate) metrics: QueryCacheMetrics, } -impl MemoryDiskBytes for QueryCache { - fn memory_bytes(&self) -> usize { - size_of_val(self) + self.cache.lock().unwrap().memory_bytes() - } - - fn disk_bytes(&self) -> usize { - 0 - } -} - impl QueryCache { /// Create a new `QueryCache` instance. pub(crate) fn new( @@ -438,7 +416,7 @@ impl QueryCache { // The cache entry is no longer valid, remove it. cache.pop(key); // Update the `count_bytes` metric. - self.metrics.count_bytes.set(cache.memory_bytes() as i64); + self.metrics.count_bytes.set(total_bytes(&*cache) as i64); } } None @@ -486,7 +464,7 @@ impl QueryCache { let d = evicted_value.elapsed_seconds(now); self.metrics.evicted_entries_duration.observe(d); } - let memory_bytes = cache.memory_bytes() as i64; + let memory_bytes = total_bytes(&*cache) as i64; self.metrics.count_bytes.set(memory_bytes); self.metrics.len.set(cache.len() as i64); } diff --git a/rs/execution_environment/src/query_handler/query_cache/tests.rs b/rs/execution_environment/src/query_handler/query_cache/tests.rs index 484397f2a49a..4b4479fcc8d1 100644 --- a/rs/execution_environment/src/query_handler/query_cache/tests.rs +++ b/rs/execution_environment/src/query_handler/query_cache/tests.rs @@ -6,6 +6,7 @@ use crate::{ }; use ic_base_types::CanisterId; use ic_error_types::ErrorCode; +use ic_heap_bytes::{total_bytes, DeterministicHeapBytes, HeapBytes}; use ic_interfaces::execution_environment::{SystemApiCallCounters, SystemApiCallId}; use ic_registry_subnet_type::SubnetType; use ic_replicated_state::canister_state::system_state::CyclesUseCase; @@ -19,7 +20,7 @@ use ic_types::{ CanisterTask, CertificateDelegationFormat, CertificateDelegationMetadata, Query, QuerySource, }, - time, MemoryDiskBytes, + time, }; use ic_types_test_utils::ids::subnet_test_id; use ic_universal_canister::call_args; @@ -809,18 +810,18 @@ fn query_cache_frees_memory_after_invalidated_entries() { .build(); let id = test.canister_from_wat(QUERY_CACHE_WAT).unwrap(); - let memory_bytes = query_cache(&test).memory_bytes(); + let heap_bytes = query_cache(&test).heap_bytes(); // Initially the cache should be empty, i.e. less than 1MB. - assert!(memory_bytes < BIG_RESPONSE_SIZE); + assert!(heap_bytes < BIG_RESPONSE_SIZE); // The 1MB result will be cached internally. let res = test .non_replicated_query(id, "canister_balance_sized_reply", vec![]) .unwrap(); - assert_eq!(BIG_RESPONSE_SIZE, res.memory_bytes()); - let memory_bytes = query_cache(&test).memory_bytes(); + assert_eq!(BIG_RESPONSE_SIZE, res.deterministic_heap_bytes()); + let heap_bytes = query_cache(&test).heap_bytes(); // After the first reply, the cache should have more than 1MB of data. - assert!(memory_bytes > BIG_RESPONSE_SIZE); + assert!(heap_bytes > BIG_RESPONSE_SIZE); // Set the canister balance to 42, so the second reply will have just 42 bytes. test.canister_state_mut(id).system_state.remove_cycles( @@ -832,11 +833,11 @@ fn query_cache_frees_memory_after_invalidated_entries() { let res = test .non_replicated_query(id, "canister_balance_sized_reply", vec![]) .unwrap(); - assert_eq!(SMALL_RESPONSE_SIZE, res.memory_bytes()); - let memory_bytes = query_cache(&test).memory_bytes(); + assert_eq!(SMALL_RESPONSE_SIZE, res.deterministic_heap_bytes()); + let heap_bytes = query_cache(&test).heap_bytes(); // The second 42 reply should invalidate and replace the first 1MB reply in the cache. - assert!(memory_bytes > SMALL_RESPONSE_SIZE); - assert!(memory_bytes < BIG_RESPONSE_SIZE); + assert!(heap_bytes > SMALL_RESPONSE_SIZE); + assert!(heap_bytes < BIG_RESPONSE_SIZE); } #[test] @@ -847,8 +848,8 @@ fn query_cache_respects_cache_capacity() { let id = test.universal_canister().unwrap(); // Initially the cache should be empty, i.e. less than REPLY_SIZE. - let memory_bytes = query_cache(&test).memory_bytes(); - assert!(memory_bytes < REPLY_SIZE); + let heap_bytes = query_cache(&test).heap_bytes(); + assert!(heap_bytes < REPLY_SIZE); // All replies should hit the same cache entry. for _ in 0..ITERATIONS { @@ -856,9 +857,9 @@ fn query_cache_respects_cache_capacity() { let _res = test.non_replicated_query(id, "query", wasm().reply_data(&[1; REPLY_SIZE / 2]).build()); // Now there should be only one reply in the cache. - let memory_bytes = query_cache(&test).memory_bytes(); - assert!(memory_bytes > REPLY_SIZE); - assert!(memory_bytes < QUERY_CACHE_CAPACITY); + let heap_bytes = query_cache(&test).heap_bytes(); + assert!(heap_bytes > REPLY_SIZE); + assert!(heap_bytes < QUERY_CACHE_CAPACITY); } // Now the replies should hit another entry. @@ -866,9 +867,9 @@ fn query_cache_respects_cache_capacity() { let _res = test.non_replicated_query(id, "query", wasm().reply_data(&[2; REPLY_SIZE / 2]).build()); // Now there should be two replies in the cache. - let memory_bytes = query_cache(&test).memory_bytes(); - assert!(memory_bytes > REPLY_SIZE * 2); - assert!(memory_bytes < QUERY_CACHE_CAPACITY); + let heap_bytes = query_cache(&test).heap_bytes(); + assert!(heap_bytes > REPLY_SIZE * 2); + assert!(heap_bytes < QUERY_CACHE_CAPACITY); } // Now the replies should evict the first entry. @@ -876,9 +877,9 @@ fn query_cache_respects_cache_capacity() { let _res = test.non_replicated_query(id, "query", wasm().reply_data(&[3; REPLY_SIZE / 2]).build()); // There should be still just two replies in the cache. - let memory_bytes = query_cache(&test).memory_bytes(); - assert!(memory_bytes > REPLY_SIZE * 2); - assert!(memory_bytes < QUERY_CACHE_CAPACITY); + let heap_bytes = query_cache(&test).heap_bytes(); + assert!(heap_bytes > REPLY_SIZE * 2); + assert!(heap_bytes < QUERY_CACHE_CAPACITY); } } @@ -888,13 +889,13 @@ fn query_cache_works_with_zero_cache_capacity() { let id = test.universal_canister().unwrap(); // Even with zero capacity the cache data structure uses some bytes for the pointers etc. - let initial_memory_bytes = query_cache(&test).memory_bytes(); + let initial_heap_bytes = query_cache(&test).heap_bytes(); // Replies should not change the initial (zero) capacity. for _ in 0..ITERATIONS { let _res = test.non_replicated_query(id, "query", wasm().reply_data(&[1]).build()); - let memory_bytes = query_cache(&test).memory_bytes(); - assert_eq!(initial_memory_bytes, memory_bytes); + let heap_bytes = query_cache(&test).heap_bytes(); + assert_eq!(initial_heap_bytes, heap_bytes); } } @@ -1609,3 +1610,68 @@ fn query_cache_future_proof_test() { } } } + +#[test] +fn total_bytes_future_proof_guard() { + const HEAP_BYTES: usize = 5; + + // Key with no heap data. + let key = EntryKey { + source: user_test_id(1), + receiver: CanisterId::from_u64(1), + method_name: String::new(), + method_payload: vec![], + certificate_delegation_format: None, + }; + assert_eq!(size_of_val(&key), 112); + assert_eq!(total_bytes(&key), size_of_val(&key)); + + // Key with some heap data. + let key = EntryKey { + source: user_test_id(1), + receiver: CanisterId::from_u64(1), + method_name: " ".repeat(HEAP_BYTES), + method_payload: vec![42; HEAP_BYTES], + certificate_delegation_format: None, + }; + assert_eq!(size_of_val(&key), 112); + assert_eq!(total_bytes(&key), size_of_val(&key) + HEAP_BYTES * 2); + + // Value with no heap data. + let env = EntryEnv { + batch_time: time::GENESIS, + canisters_versions_balances_stats: vec![], + }; + let value = EntryValue::new( + env, + Result::Ok(WasmResult::Reply(vec![])), + &SystemApiCallCounters::default(), + ); + assert_eq!(size_of_val(&value), 80); + assert_eq!(total_bytes(&value), size_of_val(&value)); + + // Value with some heap data. + let env = EntryEnv { + batch_time: time::GENESIS, + canisters_versions_balances_stats: vec![ + ( + CanisterId::from_u64(1), + 0, + 0_u64.into(), + QueryStats::default(), + ); + HEAP_BYTES + ], + }; + let env_vec_size = size_of_val(&*env.canisters_versions_balances_stats); + let value = EntryValue::new( + env, + Result::Ok(WasmResult::Reply(vec![42; HEAP_BYTES])), + &SystemApiCallCounters::default(), + ); + assert_eq!(size_of_val(&value), 80); + assert_eq!( + total_bytes(&value), + size_of_val(&value) + env_vec_size + HEAP_BYTES + ); +} diff --git a/rs/interfaces/BUILD.bazel b/rs/interfaces/BUILD.bazel index 9fe653863faf..def4cbfe5f43 100644 --- a/rs/interfaces/BUILD.bazel +++ b/rs/interfaces/BUILD.bazel @@ -2,49 +2,44 @@ load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") package(default_visibility = ["//visibility:public"]) -DEPENDENCIES = [ - # Keep sorted. - "//packages/ic-error-types", - "//rs/crypto/interfaces/sig_verification", - "//rs/interfaces/state_manager", - "//rs/phantom_newtype", - "//rs/protobuf", - "//rs/registry/provisional_whitelist", - "//rs/registry/subnet_type", - "//rs/sys", - "//rs/types/base_types", - "//rs/types/management_canister_types", - "//rs/types/types", - "//rs/types/wasm_types", - "@crate_index//:prost", - "@crate_index//:serde", - "@crate_index//:strum", - "@crate_index//:thiserror", - "@crate_index//:tower", -] - -DEV_DEPENDENCIES = [ - # Keep sorted. - "//rs/crypto/internal/crypto_service_provider/csp_proptest_utils", - "@crate_index//:proptest", -] - -PROC_MACRO_DEPENDENCIES = [ - # Keep sorted. - "@crate_index//:strum_macros", -] - rust_library( name = "interfaces", srcs = glob(["src/**/*.rs"]), crate_name = "ic_interfaces", - proc_macro_deps = PROC_MACRO_DEPENDENCIES, + proc_macro_deps = [ + # Keep sorted. + "@crate_index//:strum_macros", + ], version = "0.9.0", - deps = DEPENDENCIES, + deps = [ + # Keep sorted. + "//packages/ic-error-types", + "//packages/ic-heap-bytes", + "//rs/crypto/interfaces/sig_verification", + "//rs/interfaces/state_manager", + "//rs/phantom_newtype", + "//rs/protobuf", + "//rs/registry/provisional_whitelist", + "//rs/registry/subnet_type", + "//rs/sys", + "//rs/types/base_types", + "//rs/types/management_canister_types", + "//rs/types/types", + "//rs/types/wasm_types", + "@crate_index//:prost", + "@crate_index//:serde", + "@crate_index//:strum", + "@crate_index//:thiserror", + "@crate_index//:tower", + ], ) rust_test( name = "interfaces_test", crate = ":interfaces", - deps = DEPENDENCIES + DEV_DEPENDENCIES, + deps = [ + # Keep sorted. + "//rs/crypto/internal/crypto_service_provider/csp_proptest_utils", + "@crate_index//:proptest", + ], ) diff --git a/rs/interfaces/Cargo.toml b/rs/interfaces/Cargo.toml index 84e155778221..94956e07be19 100644 --- a/rs/interfaces/Cargo.toml +++ b/rs/interfaces/Cargo.toml @@ -9,6 +9,7 @@ documentation.workspace = true [dependencies] ic-base-types = { path = "../types/base_types" } ic-crypto-interfaces-sig-verification = { path = "../crypto/interfaces/sig_verification" } +ic-heap-bytes = { path = "../../packages/ic-heap-bytes" } ic-error-types = { path = "../../packages/ic-error-types" } ic-interfaces-state-manager = { path = "./state_manager" } ic-management-canister-types-private = { path = "../types/management_canister_types" } diff --git a/rs/interfaces/src/execution_environment/errors.rs b/rs/interfaces/src/execution_environment/errors.rs index 5c4e4efbd2ca..b0327b8a1cb0 100644 --- a/rs/interfaces/src/execution_environment/errors.rs +++ b/rs/interfaces/src/execution_environment/errors.rs @@ -1,13 +1,14 @@ use ic_base_types::{NumBytes, PrincipalIdBlobParseError}; use ic_error_types::UserError; -use ic_types::{methods::WasmMethod, CanisterId, Cycles, MemoryDiskBytes, NumInstructions}; +use ic_heap_bytes::DeterministicHeapBytes; +use ic_types::{methods::WasmMethod, CanisterId, Cycles, DiskBytes, NumInstructions}; use ic_wasm_types::{ doc_ref, AsErrorHelp, ErrorHelp, WasmEngineError, WasmInstrumentationError, WasmValidationError, }; use serde::{Deserialize, Serialize}; /// Various traps that a canister can create. -#[derive(Clone, Eq, PartialEq, Debug, Deserialize, Serialize)] +#[derive(Clone, DeterministicHeapBytes, Eq, PartialEq, Debug, Deserialize, Serialize)] pub enum TrapCode { StackOverflow, HeapOutOfBounds, @@ -46,7 +47,7 @@ impl std::fmt::Display for TrapCode { /// /// Should be used as the wrapped error by various components that need to /// handle such cases. -#[derive(Clone, Eq, PartialEq, Hash, Debug, Deserialize, Serialize)] +#[derive(Clone, DeterministicHeapBytes, Eq, PartialEq, Hash, Debug, Deserialize, Serialize)] pub struct CanisterOutOfCyclesError { pub canister_id: CanisterId, pub available: Cycles, @@ -71,7 +72,7 @@ impl std::fmt::Display for CanisterOutOfCyclesError { /// Backtrace coming from canister code. Suitable for displaying to users for /// assistance in debugging canisters. -#[derive(Clone, Eq, PartialEq, Debug, Deserialize, Serialize)] +#[derive(Clone, DeterministicHeapBytes, Eq, PartialEq, Debug, Deserialize, Serialize)] pub struct CanisterBacktrace(pub Vec<(u32, Option)>); impl std::fmt::Display for CanisterBacktrace { @@ -88,7 +89,7 @@ impl std::fmt::Display for CanisterBacktrace { } /// Errors returned by the Hypervisor. -#[derive(Clone, Eq, PartialEq, Debug, Deserialize, Serialize)] +#[derive(Clone, DeterministicHeapBytes, Eq, PartialEq, Debug, Deserialize, Serialize)] pub enum HypervisorError { /// The message sent to the canister refers a function not found in the /// table. The payload contains the index of the table and the index of the @@ -404,15 +405,7 @@ impl std::fmt::Display for HypervisorError { } } -impl MemoryDiskBytes for HypervisorError { - fn memory_bytes(&self) -> usize { - std::mem::size_of::() - } - - fn disk_bytes(&self) -> usize { - 0 - } -} +impl DiskBytes for HypervisorError {} impl AsErrorHelp for HypervisorError { fn error_help(&self) -> ErrorHelp { diff --git a/rs/phantom_newtype/BUILD.bazel b/rs/phantom_newtype/BUILD.bazel index 55e0bc3afe90..20042e99b223 100644 --- a/rs/phantom_newtype/BUILD.bazel +++ b/rs/phantom_newtype/BUILD.bazel @@ -7,6 +7,7 @@ rust_library( srcs = glob(["src/**"]), deps = [ # Keep sorted. + "//packages/ic-heap-bytes", "@crate_index//:candid", "@crate_index//:num-traits", "@crate_index//:serde", diff --git a/rs/phantom_newtype/Cargo.toml b/rs/phantom_newtype/Cargo.toml index 915209d6ae13..0f7cffdd8e20 100644 --- a/rs/phantom_newtype/Cargo.toml +++ b/rs/phantom_newtype/Cargo.toml @@ -8,6 +8,7 @@ version.workspace = true [dependencies] candid = { workspace = true } +ic-heap-bytes = { path = "../../packages/ic-heap-bytes" } num-traits = { workspace = true } serde = { workspace = true } slog = { workspace = true } diff --git a/rs/phantom_newtype/src/amountof.rs b/rs/phantom_newtype/src/amountof.rs index d542a83136ce..22a053f22c8a 100644 --- a/rs/phantom_newtype/src/amountof.rs +++ b/rs/phantom_newtype/src/amountof.rs @@ -1,4 +1,5 @@ use crate::displayer::{DisplayProxy, DisplayerOf}; +use ic_heap_bytes::DeterministicHeapBytes; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::cmp::Ordering; use std::fmt; @@ -556,3 +557,9 @@ impl slog::Value for AmountOf { self.0.serialize(record, key, serializer) } } + +impl DeterministicHeapBytes for AmountOf { + fn deterministic_heap_bytes(&self) -> usize { + self.0.deterministic_heap_bytes() + } +} diff --git a/rs/phantom_newtype/src/id.rs b/rs/phantom_newtype/src/id.rs index c314fdcfd39d..bcb3eb075ca3 100644 --- a/rs/phantom_newtype/src/id.rs +++ b/rs/phantom_newtype/src/id.rs @@ -1,4 +1,5 @@ use crate::displayer::{DisplayProxy, DisplayerOf}; +use ic_heap_bytes::DeterministicHeapBytes; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::cmp::Ordering; use std::fmt; @@ -303,3 +304,9 @@ impl slog::Value for Id { self.0.serialize(record, key, serializer) } } + +impl DeterministicHeapBytes for Id { + fn deterministic_heap_bytes(&self) -> usize { + self.0.deterministic_heap_bytes() + } +} diff --git a/rs/types/base_types/BUILD.bazel b/rs/types/base_types/BUILD.bazel index e9a94fd8ef10..1f0794f22643 100644 --- a/rs/types/base_types/BUILD.bazel +++ b/rs/types/base_types/BUILD.bazel @@ -30,6 +30,7 @@ rust_library( version = "0.9.0", deps = [ # Keep sorted. + "//packages/ic-heap-bytes", "//rs/crypto/sha2", "//rs/phantom_newtype", "//rs/protobuf", diff --git a/rs/types/base_types/Cargo.toml b/rs/types/base_types/Cargo.toml index bae22a5f0e6d..f25ca8cd6aa1 100644 --- a/rs/types/base_types/Cargo.toml +++ b/rs/types/base_types/Cargo.toml @@ -14,6 +14,7 @@ candid = { workspace = true } comparable = { version = "0.5.1", features = ["derive"] } hex = { workspace = true } ic-crypto-sha2 = { path = "../../crypto/sha2" } +ic-heap-bytes = { path = "../../../packages/ic-heap-bytes" } ic-protobuf = { path = "../../protobuf" } phantom_newtype = { path = "../../phantom_newtype" } prost = { workspace = true } diff --git a/rs/types/base_types/src/canister_id.rs b/rs/types/base_types/src/canister_id.rs index 04fc5b6bffdf..e3c8657e7036 100644 --- a/rs/types/base_types/src/canister_id.rs +++ b/rs/types/base_types/src/canister_id.rs @@ -1,12 +1,25 @@ use super::{PrincipalId, PrincipalIdClass, PrincipalIdError, SubnetId}; use candid::types::principal::PrincipalError; use candid::{CandidType, Principal}; +use ic_heap_bytes::DeterministicHeapBytes; use ic_protobuf::{proxy::ProxyDecodeError, types::v1 as pb}; use serde::{de::Error, Deserialize, Serialize}; use std::{convert::TryFrom, fmt}; /// A type representing a canister's [`PrincipalId`]. -#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, CandidType, Serialize)] +#[derive( + Copy, + Clone, + Eq, + DeterministicHeapBytes, + PartialEq, + Ord, + PartialOrd, + Hash, + Debug, + CandidType, + Serialize, +)] pub struct CanisterId(PrincipalId); /// Represents an error that can occur when constructing a [`CanisterId`] from a diff --git a/rs/types/base_types/src/principal_id.rs b/rs/types/base_types/src/principal_id.rs index 8a577dbd2f7c..84c119cf2faa 100644 --- a/rs/types/base_types/src/principal_id.rs +++ b/rs/types/base_types/src/principal_id.rs @@ -4,6 +4,7 @@ use arbitrary::{Arbitrary, Result as ArbitraryResult, Unstructured}; use candid::types::principal::{Principal, PrincipalError}; use candid::types::{Type, TypeId, TypeInner}; use ic_crypto_sha2::Sha224; +use ic_heap_bytes::DeterministicHeapBytes; use ic_protobuf::types::v1 as pb; use serde::{Deserialize, Serialize}; use std::{ @@ -24,7 +25,16 @@ use strum_macros::EnumIter; /// want [`PrincipalId`] to implement the Copy trait, we encode them as /// a fixed-size array and a length. #[derive( - Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Deserialize, Serialize, comparable::Comparable, + Copy, + Clone, + Eq, + DeterministicHeapBytes, + PartialEq, + Ord, + PartialOrd, + Deserialize, + Serialize, + comparable::Comparable, )] #[describe_type(String)] #[describe_body(self.to_string())] @@ -42,7 +52,7 @@ impl Hash for PrincipalId { } } -#[derive(Clone, Eq, PartialEq, Debug, Deserialize, Serialize)] +#[derive(Clone, DeterministicHeapBytes, Eq, PartialEq, Debug, Deserialize, Serialize)] #[repr(transparent)] #[serde(transparent)] pub struct PrincipalIdError(pub PrincipalError); diff --git a/rs/types/types/BUILD.bazel b/rs/types/types/BUILD.bazel index a646bd8f822e..76d7f7b6d5f6 100644 --- a/rs/types/types/BUILD.bazel +++ b/rs/types/types/BUILD.bazel @@ -20,6 +20,7 @@ rust_library( deps = [ # Keep sorted. "//packages/ic-error-types", + "//packages/ic-heap-bytes", "//rs/bitcoin/replica_types", "//rs/crypto/internal/crypto_lib/types", "//rs/crypto/sha2", diff --git a/rs/types/types/Cargo.toml b/rs/types/types/Cargo.toml index 12d71300fcd9..145f24213277 100644 --- a/rs/types/types/Cargo.toml +++ b/rs/types/types/Cargo.toml @@ -18,6 +18,7 @@ ic-crypto-internal-types = { path = "../../crypto/internal/crypto_lib/types" } ic-crypto-sha2 = { path = "../../crypto/sha2" } ic-crypto-tree-hash = { path = "../../crypto/tree_hash" } ic-error-types = { path = "../../../packages/ic-error-types" } +ic-heap-bytes = { path = "../../../packages/ic-heap-bytes" } ic-management-canister-types-private = { path = "../management_canister_types" } ic-protobuf = { path = "../../protobuf" } ic-validate-eq = { path = "../../utils/validate_eq" } diff --git a/rs/types/types/src/batch/execution_environment.rs b/rs/types/types/src/batch/execution_environment.rs index a261e966759c..2d5f57682e93 100644 --- a/rs/types/types/src/batch/execution_environment.rs +++ b/rs/types/types/src/batch/execution_environment.rs @@ -23,6 +23,7 @@ use crate::{node_id_into_protobuf, node_id_try_from_option, QueryStatsEpoch}; use ic_base_types::{CanisterId, NodeId, NumBytes}; +use ic_heap_bytes::DeterministicHeapBytes; use ic_protobuf::registry::subnet::v1 as proto; use ic_protobuf::{ proxy::{try_from_option_field, ProxyDecodeError}, @@ -36,7 +37,7 @@ use prost::{bytes::BufMut, Message}; use serde::{Deserialize, Serialize}; use std::{collections::BTreeMap, hash::Hash}; -#[derive(Clone, Eq, PartialEq, Hash, Debug, Default)] +#[derive(Clone, DeterministicHeapBytes, Eq, PartialEq, Hash, Debug, Default)] pub struct QueryStats { pub num_calls: u32, pub num_instructions: u64, // Want u128, but not supported in protobuf diff --git a/rs/types/types/src/funds/cycles.rs b/rs/types/types/src/funds/cycles.rs index 77c2a0110da4..bd2664f1154f 100644 --- a/rs/types/types/src/funds/cycles.rs +++ b/rs/types/types/src/funds/cycles.rs @@ -1,6 +1,7 @@ use candid::{CandidType, Nat}; #[cfg(test)] use ic_exhaustive_derive::ExhaustiveSet; +use ic_heap_bytes::DeterministicHeapBytes; use ic_protobuf::state::canister_state_bits::v1::CyclesAccount as pbCyclesAccount; use ic_protobuf::state::queues::v1::Cycles as PbCycles; use serde::{Deserialize, Serialize}; @@ -19,6 +20,7 @@ use thousands::Separable; Copy, Clone, Eq, + DeterministicHeapBytes, PartialEq, Ord, PartialOrd, diff --git a/rs/types/types/src/ingress.rs b/rs/types/types/src/ingress.rs index 02b88bd81cfc..e40440e1b708 100644 --- a/rs/types/types/src/ingress.rs +++ b/rs/types/types/src/ingress.rs @@ -1,10 +1,11 @@ //! Ingress types. use crate::artifact::IngressMessageId; -use crate::{CanisterId, MemoryDiskBytes, PrincipalId, Time, UserId}; +use crate::{CanisterId, PrincipalId, Time, UserId}; use ic_error_types::{ErrorCode, UserError}; #[cfg(test)] use ic_exhaustive_derive::ExhaustiveSet; +use ic_heap_bytes::DeterministicHeapBytes; use ic_protobuf::{ proxy::{try_from_option_field, ProxyDecodeError}, state::ingress::v1 as pb_ingress, @@ -107,7 +108,7 @@ impl IngressStatus { pub fn payload_bytes(&self) -> usize { match self { IngressStatus::Known { state, .. } => match state { - IngressState::Completed(result) => result.memory_bytes(), + IngressState::Completed(result) => result.deterministic_heap_bytes(), IngressState::Failed(error) => error.description().len(), _ => 0, }, @@ -166,7 +167,18 @@ impl IngressSets { /// This struct describes the different types that executing a Wasm function in /// a canister can produce -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Deserialize, Serialize)] +#[derive( + Clone, + Eq, + DeterministicHeapBytes, + PartialEq, + Ord, + PartialOrd, + Hash, + Debug, + Deserialize, + Serialize, +)] #[cfg_attr(test, derive(ExhaustiveSet))] pub enum WasmResult { /// Raw response, returned in a "happy" case @@ -176,19 +188,6 @@ pub enum WasmResult { Reject(String), } -impl MemoryDiskBytes for WasmResult { - fn memory_bytes(&self) -> usize { - match self { - WasmResult::Reply(bytes) => bytes.len(), - WasmResult::Reject(string) => string.len(), - } - } - - fn disk_bytes(&self) -> usize { - 0 - } -} - impl WasmResult { /// Returns the bytes in the result. pub fn bytes(self) -> Vec { diff --git a/rs/types/types/src/lib.rs b/rs/types/types/src/lib.rs index b64a11c0e8b6..47e34c637c35 100644 --- a/rs/types/types/src/lib.rs +++ b/rs/types/types/src/lib.rs @@ -581,31 +581,14 @@ pub trait CountBytes { fn count_bytes(&self) -> usize; } -/// Allow an object to report its own byte size on disk and in memory. Not -/// necessarily exact. -pub trait MemoryDiskBytes { - fn memory_bytes(&self) -> usize; - fn disk_bytes(&self) -> usize; -} - -impl MemoryDiskBytes for Time { - fn memory_bytes(&self) -> usize { - 8 - } - +/// Allow an object to report its own byte size on disk. Not necessarily exact. +pub trait DiskBytes { fn disk_bytes(&self) -> usize { 0 } } -impl MemoryDiskBytes for Result { - fn memory_bytes(&self) -> usize { - match self { - Ok(result) => result.memory_bytes(), - Err(err) => err.memory_bytes(), - } - } - +impl DiskBytes for Result { fn disk_bytes(&self) -> usize { match self { Ok(result) => result.disk_bytes(), @@ -614,23 +597,8 @@ impl MemoryDiskBytes for Result { } } -impl MemoryDiskBytes for Arc { - fn memory_bytes(&self) -> usize { - self.as_ref().memory_bytes() - } - +impl DiskBytes for Arc { fn disk_bytes(&self) -> usize { self.as_ref().disk_bytes() } } - -// Implementing `MemoryDiskBytes` in `ic_error_types` introduces a circular dependency. -impl MemoryDiskBytes for ic_error_types::UserError { - fn memory_bytes(&self) -> usize { - self.count_bytes() - } - - fn disk_bytes(&self) -> usize { - 0 - } -} diff --git a/rs/types/types/src/messages/http.rs b/rs/types/types/src/messages/http.rs index 7a7cdeda3cac..da6cd7d9253f 100644 --- a/rs/types/types/src/messages/http.rs +++ b/rs/types/types/src/messages/http.rs @@ -12,6 +12,7 @@ use crate::{ }; use ic_base_types::{hash_of_map, CanisterId, CanisterIdError, NodeId, PrincipalId}; use ic_crypto_tree_hash::{MixedHashTree, Path}; +use ic_heap_bytes::DeterministicHeapBytes; use maplit::btreemap; #[cfg(test)] use proptest_derive::Arbitrary; @@ -712,7 +713,7 @@ pub struct Certificate { pub delegation: Option, } -#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)] +#[derive(Copy, Clone, DeterministicHeapBytes, Eq, PartialEq, Debug, Hash)] pub enum CertificateDelegationFormat { /// Delegation with the canister ranges in the `/subnet/{subnet_id}/canister_ranges` path. Flat, diff --git a/rs/types/types/src/methods.rs b/rs/types/types/src/methods.rs index 2983e356a832..019f7847b0a9 100644 --- a/rs/types/types/src/methods.rs +++ b/rs/types/types/src/methods.rs @@ -5,6 +5,7 @@ use crate::{messages::CallContextId, time::CoarseTime, Cycles}; use ic_base_types::{CanisterId, PrincipalId}; #[cfg(test)] use ic_exhaustive_derive::ExhaustiveSet; +use ic_heap_bytes::DeterministicHeapBytes; use ic_protobuf::proxy::{try_from_option_field, ProxyDecodeError}; use ic_protobuf::state::{canister_state_bits::v1 as pb, queues::v1::Cycles as PbCycles}; use ic_protobuf::types::v1 as pb_types; @@ -16,7 +17,9 @@ use std::{ use strum_macros::EnumIter; /// Represents the types of methods that a Wasm module can export. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Deserialize, Serialize)] +#[derive( + Clone, DeterministicHeapBytes, Eq, PartialEq, Ord, PartialOrd, Debug, Deserialize, Serialize, +)] #[cfg_attr(test, derive(ExhaustiveSet))] pub enum WasmMethod { /// An exported update method along with its name. @@ -129,7 +132,18 @@ impl TryFrom for WasmMethod { } /// The various system methods available to canisters. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Deserialize, EnumIter, Serialize)] +#[derive( + Clone, + DeterministicHeapBytes, + Eq, + PartialEq, + Ord, + PartialOrd, + Debug, + Deserialize, + EnumIter, + Serialize, +)] #[cfg_attr(test, derive(ExhaustiveSet))] pub enum SystemMethod { /// A system method for initializing a Wasm module. diff --git a/rs/types/types/src/time.rs b/rs/types/types/src/time.rs index b78448526ff0..cf28e873efe9 100644 --- a/rs/types/types/src/time.rs +++ b/rs/types/types/src/time.rs @@ -7,6 +7,7 @@ mod tests; #[cfg(test)] use ic_exhaustive_derive::ExhaustiveSet; +use ic_heap_bytes::DeterministicHeapBytes; use ic_limits::{MAX_INGRESS_TTL, PERMITTED_DRIFT}; #[cfg(test)] use proptest_derive::Arbitrary; @@ -18,7 +19,19 @@ use thiserror::Error; /// Time since UNIX_EPOCH (in nanoseconds). Just like 'std::time::Instant' or /// 'std::time::SystemTime', [Time] does not implement the [Default] trait. -#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Deserialize, Serialize)] +#[derive( + Copy, + Clone, + Eq, + DeterministicHeapBytes, + PartialEq, + Ord, + PartialOrd, + Hash, + Debug, + Deserialize, + Serialize, +)] #[cfg_attr(test, derive(Arbitrary, ExhaustiveSet))] pub struct Time(u64); diff --git a/rs/types/wasm_types/BUILD.bazel b/rs/types/wasm_types/BUILD.bazel index c637eb094adb..1e3a09fe482f 100644 --- a/rs/types/wasm_types/BUILD.bazel +++ b/rs/types/wasm_types/BUILD.bazel @@ -13,6 +13,7 @@ rust_library( version = "0.9.0", deps = [ # Keep sorted. + "//packages/ic-heap-bytes", "//rs/crypto/sha2", "//rs/sys", "//rs/types/types", diff --git a/rs/types/wasm_types/Cargo.toml b/rs/types/wasm_types/Cargo.toml index 865bfc9b63b9..daec6ac1916c 100644 --- a/rs/types/wasm_types/Cargo.toml +++ b/rs/types/wasm_types/Cargo.toml @@ -8,6 +8,7 @@ documentation.workspace = true [dependencies] ic-crypto-sha2 = { path = "../../crypto/sha2" } +ic-heap-bytes = { path = "../../../packages/ic-heap-bytes" } ic-sys = { path = "../../sys" } ic-types = { path = "../types" } ic-utils = { path = "../../utils" } diff --git a/rs/types/wasm_types/src/errors.rs b/rs/types/wasm_types/src/errors.rs index bdf423055726..380b48c4a337 100644 --- a/rs/types/wasm_types/src/errors.rs +++ b/rs/types/wasm_types/src/errors.rs @@ -1,3 +1,4 @@ +use ic_heap_bytes::DeterministicHeapBytes; use serde::{Deserialize, Serialize}; /// Create a link to this section of the Execution Errors documentation. @@ -43,7 +44,7 @@ pub trait AsErrorHelp { } /// Represents an error that can happen when parsing or encoding a Wasm module -#[derive(Clone, Eq, PartialEq, Debug, Deserialize, Serialize)] +#[derive(Clone, DeterministicHeapBytes, Eq, PartialEq, Debug, Deserialize, Serialize)] pub struct WasmError(String); impl WasmError { @@ -60,7 +61,7 @@ impl std::fmt::Display for WasmError { } /// Different errors that be returned by `validate_wasm_binary` -#[derive(Clone, Eq, PartialEq, Debug, Deserialize, Serialize)] +#[derive(Clone, DeterministicHeapBytes, Eq, PartialEq, Debug, Deserialize, Serialize)] pub enum WasmValidationError { /// wasmtime::Module::validate() failed WasmtimeValidation(String), @@ -301,7 +302,7 @@ impl AsErrorHelp for WasmValidationError { } /// Different errors that can be returned by `instrument` -#[derive(Clone, Eq, PartialEq, Debug, Deserialize, Serialize)] +#[derive(Clone, DeterministicHeapBytes, Eq, PartialEq, Debug, Deserialize, Serialize)] pub enum WasmInstrumentationError { /// Failure in deserialization the wasm module WasmDeserializeError(WasmError), @@ -356,7 +357,7 @@ impl AsErrorHelp for WasmInstrumentationError { } /// Different errors that be returned by the Wasm engine -#[derive(Clone, Eq, PartialEq, Debug, Deserialize, Serialize)] +#[derive(Clone, DeterministicHeapBytes, Eq, PartialEq, Debug, Deserialize, Serialize)] pub enum WasmEngineError { FailedToInitializeEngine, FailedToInstantiateModule(String), diff --git a/rs/types/wasm_types/src/lib.rs b/rs/types/wasm_types/src/lib.rs index 85bed649897a..c625751653d5 100644 --- a/rs/types/wasm_types/src/lib.rs +++ b/rs/types/wasm_types/src/lib.rs @@ -6,7 +6,8 @@ pub use errors::{ doc_ref, AsErrorHelp, ErrorHelp, WasmEngineError, WasmError, WasmInstrumentationError, WasmValidationError, }; -use ic_types::MemoryDiskBytes; +use ic_heap_bytes::DeterministicHeapBytes; +use ic_types::DiskBytes; use ic_utils::byte_slice_fmt::truncate_and_format; use ic_validate_eq::ValidateEq; use ic_validate_eq_derive::ValidateEq; @@ -185,7 +186,7 @@ impl std::hash::Hash for CanisterModule { } /// The hash of an __uninstrumented__ canister wasm. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +#[derive(Clone, DeterministicHeapBytes, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] pub struct WasmHash([u8; WASM_HASH_LENGTH]); impl WasmHash { @@ -219,15 +220,7 @@ impl TryFrom> for WasmHash { } } -impl MemoryDiskBytes for WasmHash { - fn memory_bytes(&self) -> usize { - self.0.len() - } - - fn disk_bytes(&self) -> usize { - 0 - } -} +impl DiskBytes for WasmHash {} impl std::fmt::Display for WasmHash { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { diff --git a/rs/utils/lru_cache/BUILD.bazel b/rs/utils/lru_cache/BUILD.bazel index 7b07074ae147..79ec4390d2d8 100644 --- a/rs/utils/lru_cache/BUILD.bazel +++ b/rs/utils/lru_cache/BUILD.bazel @@ -9,6 +9,7 @@ rust_library( version = "0.1.0", deps = [ # Keep sorted. + "//packages/ic-heap-bytes", "//rs/types/types", "@crate_index//:lru", ], diff --git a/rs/utils/lru_cache/Cargo.toml b/rs/utils/lru_cache/Cargo.toml index cdf5ebe48b52..b26f84831ead 100644 --- a/rs/utils/lru_cache/Cargo.toml +++ b/rs/utils/lru_cache/Cargo.toml @@ -9,6 +9,7 @@ documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +ic-heap-bytes = { path = "../../../packages/ic-heap-bytes" } ic-types = { path = "../../types/types" } lru = { version = "0.7.8", default-features = false } diff --git a/rs/utils/lru_cache/src/lib.rs b/rs/utils/lru_cache/src/lib.rs index 5056164d3b9e..c86646478477 100644 --- a/rs/utils/lru_cache/src/lib.rs +++ b/rs/utils/lru_cache/src/lib.rs @@ -1,4 +1,5 @@ -use ic_types::{MemoryDiskBytes, NumBytes}; +use ic_heap_bytes::DeterministicHeapBytes; +use ic_types::{DiskBytes, NumBytes}; use std::hash::Hash; /// The upper bound on cache item size and cache capacity. @@ -9,11 +10,13 @@ const MAX_SIZE: usize = usize::MAX / 2; /// A cache with bounded memory capacity that evicts items using the /// least-recently used eviction policy. It guarantees that the sum of /// sizes of the cached items does not exceed the pre-configured capacity. +#[derive(DeterministicHeapBytes)] pub struct LruCache where - K: MemoryDiskBytes + Eq + Hash, - V: MemoryDiskBytes, + K: DeterministicHeapBytes + DiskBytes + Eq + Hash, + V: DeterministicHeapBytes + DiskBytes, { + #[deterministic_heap_bytes(with = |_| self.memory_size)] cache: lru::LruCache, memory_capacity: usize, disk_capacity: usize, @@ -21,15 +24,11 @@ where disk_size: usize, } -impl MemoryDiskBytes for LruCache +impl DiskBytes for LruCache where - K: MemoryDiskBytes + Eq + Hash, - V: MemoryDiskBytes, + K: DeterministicHeapBytes + DiskBytes + Eq + Hash, + V: DeterministicHeapBytes + DiskBytes, { - fn memory_bytes(&self) -> usize { - self.memory_size - } - fn disk_bytes(&self) -> usize { self.disk_size } @@ -37,8 +36,8 @@ where impl LruCache where - K: MemoryDiskBytes + Eq + Hash, - V: MemoryDiskBytes, + K: DeterministicHeapBytes + DiskBytes + Eq + Hash, + V: DeterministicHeapBytes + DiskBytes, { /// Constructs a new LRU cache with the given memory and disk capacity. The /// capacities must not exceed `MAX_SIZE = (2^63 - 1)`. @@ -76,7 +75,7 @@ where /// the cache or other cache entries are evicted (due to the cache capacity), /// then it returns the old entry's key-value pairs. Otherwise, returns an empty vector. pub fn push(&mut self, key: K, value: V) -> Vec<(K, V)> { - let memory_size = key.memory_bytes() + value.memory_bytes(); + let memory_size = key.deterministic_heap_bytes() + value.deterministic_heap_bytes(); assert!(memory_size <= MAX_SIZE); let disk_size = key.disk_bytes() + value.disk_bytes(); assert!(disk_size <= MAX_SIZE); @@ -157,7 +156,7 @@ where } fn pop_inner(&mut self, key: &K, value: &V) { - let memory_size = key.memory_bytes() + value.memory_bytes(); + let memory_size = key.deterministic_heap_bytes() + value.deterministic_heap_bytes(); debug_assert!(self.memory_size >= memory_size); // This cannot underflow because we know that `self.memory_size` is // the sum of memory sizes of all items in the cache. @@ -179,7 +178,8 @@ where self.memory_size, self.cache .iter() - .map(|(key, value)| key.memory_bytes() + value.memory_bytes()) + .map(|(key, value)| key.deterministic_heap_bytes() + + value.deterministic_heap_bytes()) .sum::() ); debug_assert_eq!( @@ -202,24 +202,24 @@ mod tests { #[derive(Eq, PartialEq, Hash, Debug)] struct ValueSize(u32, usize); - impl MemoryDiskBytes for ValueSize { - fn memory_bytes(&self) -> usize { + impl DeterministicHeapBytes for ValueSize { + fn deterministic_heap_bytes(&self) -> usize { self.1 } - - fn disk_bytes(&self) -> usize { - 0 - } } + impl DiskBytes for ValueSize {} + #[derive(Clone, Eq, PartialEq, Hash, Debug)] struct MemoryDiskValue(u32, usize, usize); - impl MemoryDiskBytes for MemoryDiskValue { - fn memory_bytes(&self) -> usize { + impl DeterministicHeapBytes for MemoryDiskValue { + fn deterministic_heap_bytes(&self) -> usize { self.1 } + } + impl DiskBytes for MemoryDiskValue { fn disk_bytes(&self) -> usize { self.2 } @@ -228,16 +228,14 @@ mod tests { #[derive(Eq, PartialEq, Hash, Debug)] struct Key(u32); - impl MemoryDiskBytes for Key { - fn memory_bytes(&self) -> usize { - 0 - } - - fn disk_bytes(&self) -> usize { + impl DeterministicHeapBytes for Key { + fn deterministic_heap_bytes(&self) -> usize { 0 } } + impl DiskBytes for Key {} + #[test] fn lru_cache_single_entry() { let mut lru = LruCache::::new(NumBytes::new(10), NumBytes::new(0)); @@ -442,27 +440,27 @@ mod tests { #[test] fn lru_cache_count_bytes_and_len() { let mut lru = LruCache::::new(NumBytes::new(10), NumBytes::new(20)); - assert_eq!(0, lru.memory_bytes()); + assert_eq!(0, lru.deterministic_heap_bytes()); assert_eq!(0, lru.disk_bytes()); assert_eq!(0, lru.len()); assert!(lru.is_empty()); lru.push(Key(0), MemoryDiskValue(0, 4, 10)); - assert_eq!(4, lru.memory_bytes()); + assert_eq!(4, lru.deterministic_heap_bytes()); assert_eq!(10, lru.disk_bytes()); assert_eq!(1, lru.len()); assert!(!lru.is_empty()); lru.push(Key(1), MemoryDiskValue(1, 6, 2)); - assert_eq!(10, lru.memory_bytes()); + assert_eq!(10, lru.deterministic_heap_bytes()); assert_eq!(12, lru.disk_bytes()); assert_eq!(2, lru.len()); assert!(!lru.is_empty()); lru.pop(&Key(0)); - assert_eq!(6, lru.memory_bytes()); + assert_eq!(6, lru.deterministic_heap_bytes()); assert_eq!(2, lru.disk_bytes()); assert_eq!(1, lru.len()); assert!(!lru.is_empty()); lru.pop(&Key(1)); - assert_eq!(0, lru.memory_bytes()); + assert_eq!(0, lru.deterministic_heap_bytes()); assert_eq!(0, lru.disk_bytes()); assert_eq!(0, lru.len()); assert!(lru.is_empty()); @@ -591,8 +589,8 @@ mod tests { let mut evicted_disk = 0; fn update(memory: &mut usize, disk: &mut usize, k: &MemoryDiskValue, v: &MemoryDiskValue) { - *memory += k.memory_bytes(); - *memory += v.memory_bytes(); + *memory += k.deterministic_heap_bytes(); + *memory += v.deterministic_heap_bytes(); *disk += k.disk_bytes(); *disk += v.disk_bytes(); } @@ -611,7 +609,7 @@ mod tests { update(&mut evicted_memory, &mut evicted_disk, &k, &v); } - assert_eq!(total_memory, evicted_memory + lru.memory_bytes()); + assert_eq!(total_memory, evicted_memory + lru.deterministic_heap_bytes()); assert_eq!(total_disk, evicted_disk + lru.disk_bytes()); } }