-
Notifications
You must be signed in to change notification settings - Fork 638
test: create framework for testing memory #4921
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
fcaafe1
test: create framework for testing memory and io
wjones127 07cccbc
fix
wjones127 89c266f
figure out how to test index cache size bugs
wjones127 90449b3
fix test
wjones127 26af669
fix up test
wjones127 8b0c4ee
wip
wjones127 9211071
add btree and fts
wjones127 e24ce9c
docs
wjones127 a016952
start to clean up tests
wjones127 04ae6db
focus PR just on new framework
wjones127 fe8a7e2
license header
wjones127 9fafe92
clippy and cleanup
wjones127 c27a2ed
exclude macos
wjones127 f8681b1
simplify
wjones127 bdd5b9d
just run on linux for now
wjones127 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| Tests for memory and IO usage. | ||
|
|
||
| ## Debugging memory usage | ||
|
|
||
| Once you've identified a test that is using too much memory, you can use | ||
| bytehound to find the source of the memory usage. (Note: we need to run | ||
| bytehound on the binary, not on cargo, so we have to extract the test binary path.) | ||
|
|
||
| The `RUST_ALLOC_TIMINGS` environment variable tells the tracking allocator | ||
| to logs the start and end of each allocation tracking session, which makes it | ||
| easier to correlate the bytehound output with the code. | ||
|
|
||
| ```shell | ||
| TEST_BINARY=$(cargo test --test resource_tests --no-run 2>&1 | tail -n1 | sed -n 's/.*(\([^)]*\)).*/\1/p') | ||
| LD_PRELOAD=/usr/local/lib/libbytehound.so \ | ||
| RUST_ALLOC_TIMINGS=true \ | ||
| $TEST_BINARY resource_test::write::test_memory_usage_write \ | ||
| bytehound server memory-profiling_*.dat | ||
| ``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| // SPDX-FileCopyrightText: Copyright The Lance Authors | ||
| mod utils; | ||
| mod write; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,192 @@ | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| // SPDX-FileCopyrightText: Copyright The Lance Authors | ||
| use all_asserts::assert_ge; | ||
| use std::alloc::System; | ||
| use std::collections::HashMap; | ||
| use std::sync::{Arc, LazyLock, Mutex, Once}; | ||
| use tracing::Instrument; | ||
| use tracing_subscriber::layer::SubscriberExt; | ||
| use tracing_subscriber::Registry; | ||
| use tracking_allocator::{ | ||
| AllocationGroupId, AllocationGroupToken, AllocationLayer, AllocationRegistry, | ||
| AllocationTracker, Allocator, | ||
| }; | ||
|
|
||
| #[global_allocator] | ||
| static GLOBAL: Allocator<System> = Allocator::system(); | ||
|
|
||
| #[derive(Default, Clone, Debug)] | ||
| pub struct AllocStats { | ||
| pub max_bytes_allocated: isize, | ||
| pub total_bytes_allocated: isize, | ||
| pub total_bytes_deallocated: isize, | ||
| pub total_allocations: usize, | ||
| pub total_deallocations: usize, | ||
| } | ||
|
|
||
| impl AllocStats { | ||
| pub fn net_bytes_allocated(&self) -> isize { | ||
| self.total_bytes_allocated - self.total_bytes_deallocated | ||
| } | ||
| } | ||
|
|
||
| static GLOBAL_STATS: LazyLock<Arc<Mutex<HashMap<AllocationGroupId, AllocStats>>>> = | ||
| std::sync::LazyLock::new(|| Arc::new(Mutex::new(HashMap::new()))); | ||
|
|
||
| struct MemoryTracker; | ||
|
|
||
| impl AllocationTracker for MemoryTracker { | ||
| fn allocated( | ||
| &self, | ||
| _addr: usize, | ||
| object_size: usize, | ||
| _wrapped_size: usize, | ||
| group_id: AllocationGroupId, | ||
| ) { | ||
| if group_id == AllocationGroupId::ROOT { | ||
| // We don't track root allocations | ||
| return; | ||
| } | ||
| let mut guard = GLOBAL_STATS.lock().unwrap(); | ||
| let stats = guard.entry(group_id).or_default(); | ||
| stats.total_bytes_allocated += object_size as isize; | ||
| stats.total_allocations += 1; | ||
| stats.max_bytes_allocated = stats.max_bytes_allocated.max(stats.net_bytes_allocated()); | ||
| } | ||
|
|
||
| fn deallocated( | ||
| &self, | ||
| _addr: usize, | ||
| object_size: usize, | ||
| _wrapped_size: usize, | ||
| source_group_id: AllocationGroupId, | ||
| current_group_id: AllocationGroupId, | ||
| ) { | ||
| let group_id = if source_group_id != AllocationGroupId::ROOT { | ||
| source_group_id | ||
| } else { | ||
| current_group_id | ||
| }; | ||
| if group_id == AllocationGroupId::ROOT { | ||
| // We don't track root allocations | ||
| return; | ||
| } | ||
| let mut guard = GLOBAL_STATS.lock().unwrap(); | ||
| let stats = guard.entry(group_id).or_default(); | ||
| stats.total_bytes_deallocated += object_size as isize; | ||
| stats.total_deallocations += 1; | ||
| } | ||
| } | ||
|
|
||
| static INIT: Once = Once::new(); | ||
|
|
||
| // The alloc tracker holds a span and an associated allocation group id. | ||
| pub struct AllocTracker { | ||
| group_id: AllocationGroupId, | ||
| span: tracing::Span, | ||
| } | ||
|
|
||
| impl AllocTracker { | ||
| pub fn init() { | ||
| INIT.call_once(init_memory_tracking); | ||
| } | ||
|
|
||
| pub fn new() -> Self { | ||
| Self::init(); | ||
|
|
||
| let token = AllocationGroupToken::register().expect("failed to register token"); | ||
| let group_id = token.id(); | ||
|
|
||
| let span = tracing::span!(tracing::Level::INFO, "AllocTracker"); | ||
| token.attach_to_span(&span); | ||
|
|
||
| Self { group_id, span } | ||
| } | ||
|
|
||
| pub fn enter(&self) -> AllocGuard<'_> { | ||
| AllocGuard::new(self) | ||
| } | ||
|
|
||
| pub fn stats(self) -> AllocStats { | ||
| let mut stats = GLOBAL_STATS.lock().unwrap(); | ||
| stats.remove(&self.group_id).unwrap_or_default() | ||
| } | ||
| } | ||
|
|
||
| pub struct AllocGuard<'a> { | ||
| _guard: tracing::span::Entered<'a>, | ||
| } | ||
|
|
||
| impl<'a> AllocGuard<'a> { | ||
| #[allow(clippy::print_stderr)] | ||
| pub fn new(tracker: &'a AllocTracker) -> Self { | ||
| if std::env::var("RUST_ALLOC_TIMINGS").is_ok() { | ||
| eprintln!("alloc:enter:{}", chrono::Utc::now().to_rfc3339()); | ||
| } | ||
| AllocGuard { | ||
| _guard: tracker.span.enter(), | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl Drop for AllocGuard<'_> { | ||
| #[allow(clippy::print_stderr)] | ||
| fn drop(&mut self) { | ||
| if std::env::var("RUST_ALLOC_TIMINGS").is_ok() { | ||
| eprintln!("alloc:exit:{}", chrono::Utc::now().to_rfc3339()); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| pub fn init_memory_tracking() { | ||
| let registry = Registry::default().with(AllocationLayer::new()); | ||
| tracing::subscriber::set_global_default(registry) | ||
| .expect("failed to install tracing subscriber"); | ||
|
|
||
| let tracker = MemoryTracker; | ||
| AllocationRegistry::set_global_tracker(tracker).expect("failed to set global tracker"); | ||
| AllocationRegistry::enable_tracking(); | ||
| } | ||
|
|
||
| #[test] | ||
| fn check_memory_leak() { | ||
| // Make sure AllocTracker can detect leaks | ||
| let mut leaked = Vec::new(); | ||
| let tracker = AllocTracker::new(); | ||
| { | ||
| let _guard = tracker.enter(); | ||
| let v = vec![0u8; 1024 * 1024]; | ||
| leaked.resize(1024, 0u8); | ||
| drop(v); | ||
| } | ||
| let stats = tracker.stats(); | ||
| assert_eq!(stats.max_bytes_allocated, (1024 * 1024) + 1024); | ||
| assert_eq!(stats.total_bytes_allocated, (1024 * 1024) + 1024); | ||
| assert_eq!(stats.total_bytes_deallocated, (1024 * 1024)); | ||
| assert_eq!(stats.total_allocations, 2); | ||
| assert_eq!(stats.net_bytes_allocated(), 1024); | ||
| } | ||
|
|
||
| #[tokio::test] | ||
| async fn check_test_spawn_alloc() { | ||
| let tracker = AllocTracker::new(); | ||
| { | ||
| let _guard = tracker.enter(); | ||
| let future1 = async { | ||
| let v = vec![0u8; 256 * 1024]; | ||
| drop(v); | ||
| }; | ||
| let handle = tokio::spawn(future1.in_current_span()); | ||
| let future2 = async { | ||
| let v = vec![0u8; 512 * 1024]; | ||
| drop(v); | ||
| }; | ||
| let handle2 = tokio::spawn(future2.in_current_span()); | ||
| handle.await.unwrap(); | ||
| handle2.await.unwrap(); | ||
| } | ||
| let stats = tracker.stats(); | ||
| assert_eq!(stats.total_allocations, 4); | ||
| assert_ge!(stats.total_bytes_allocated, 256 * 1024 + 512 * 1024); | ||
| assert_ge!(stats.total_bytes_deallocated, 256 * 1024 + 512 * 1024); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| // SPDX-FileCopyrightText: Copyright The Lance Authors | ||
| use super::utils::AllocTracker; | ||
| use all_asserts::assert_le; | ||
| use arrow_schema::DataType; | ||
| use lance::dataset::InsertBuilder; | ||
| use lance_datafusion::datagen::DatafusionDatagenExt; | ||
| use lance_datagen::{array, gen_batch, BatchCount, ByteCount, RoundingBehavior}; | ||
|
|
||
| #[tokio::test] | ||
| async fn test_insert_memory() { | ||
| // Create a stream of 100MB of data, in batches | ||
| let batch_size = 10 * 1024 * 1024; // 10MB | ||
| let num_batches = BatchCount::from(10); | ||
| let data = gen_batch() | ||
| .col("a", array::rand_type(&DataType::Int32)) | ||
| .into_df_stream_bytes( | ||
| ByteCount::from(batch_size), | ||
| num_batches, | ||
| RoundingBehavior::RoundDown, | ||
| ) | ||
| .unwrap(); | ||
|
|
||
| let alloc_tracker = AllocTracker::new(); | ||
| { | ||
| let _guard = alloc_tracker.enter(); | ||
|
|
||
| // write out to temporary directory | ||
| let tmp_dir = tempfile::tempdir().unwrap(); | ||
| let tmp_path = tmp_dir.path().to_str().unwrap(); | ||
| let _dataset = InsertBuilder::new(tmp_path) | ||
| .execute_stream(data) | ||
| .await | ||
| .unwrap(); | ||
| } | ||
|
|
||
| let stats = alloc_tracker.stats(); | ||
| // Allow for 2x the batch size to account for overheads. | ||
| // The key test is that we don't load all 100MB into memory at once | ||
| assert_le!( | ||
| stats.max_bytes_allocated, | ||
| (batch_size * 2) as isize, | ||
| "Max memory usage exceeded" | ||
| ); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| // SPDX-FileCopyrightText: Copyright The Lance Authors | ||
|
|
||
| // The memory tests don't work currently on MacOS because they rely on thread | ||
| // local storage in the allocator, which seems to have some issues on MacOS. | ||
| #[cfg(target_os = "linux")] | ||
| mod resource_test; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if it would make sense to wrap this up in a macro. Would that promote more widespread usage?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can add that later if we find it simplifies things. Will be easier to see once we have more tests in here and see what patterns are repeating.