Skip to content

Commit a635896

Browse files
authored
Merge pull request #4 from eytans/master
Adding simple statistics collection
2 parents 1ab159c + df8cf2c commit a635896

File tree

4 files changed

+113
-14
lines changed

4 files changed

+113
-14
lines changed

.cargo/config.toml

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[env]
2+
RUST_TEST_THREADS = "1"

Cargo.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "cap"
3-
version = "0.1.1"
3+
version = "0.1.2"
44
license = "MIT OR Apache-2.0"
55
authors = ["Alec Mocatta <[email protected]>"]
66
categories = []
@@ -23,5 +23,6 @@ maintenance = { status = "passively-maintained" }
2323

2424
[features]
2525
nightly = []
26+
stats = []
2627

2728
[dependencies]

azure-pipelines.yml

+3-3
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ jobs:
1414
endpoint: alecmocatta
1515
default:
1616
rust_toolchain: nightly
17-
rust_lint_toolchain: nightly-2019-10-20
17+
rust_lint_toolchain: nightly-2023-03-09
1818
rust_flags: ''
19-
rust_features: ';nightly'
19+
rust_features: ';stats'
2020
rust_target_check: ''
2121
rust_target_build: ''
2222
rust_target_run: ''
@@ -26,7 +26,7 @@ jobs:
2626
rust_target_run: 'x86_64-pc-windows-msvc i686-pc-windows-msvc' # currently broken building crate-type=lib: x86_64-pc-windows-gnu i686-pc-windows-gnu
2727
mac:
2828
imageName: 'macOS-10.15'
29-
rust_target_run: 'x86_64-apple-darwin i686-apple-darwin'
29+
rust_target_run: 'x86_64-apple-darwin'
3030
linux:
3131
imageName: 'ubuntu-18.04'
3232
rust_target_run: 'x86_64-unknown-linux-gnu i686-unknown-linux-gnu x86_64-unknown-linux-musl i686-unknown-linux-musl'

src/lib.rs

+106-10
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,11 @@
4040
unused_results,
4141
clippy::pedantic
4242
)] // from https://github.com/rust-unofficial/patterns/blob/master/anti_patterns/deny-warnings.md
43-
#![allow()]
43+
#![allow(
44+
clippy::result_unit_err,
45+
clippy::let_underscore_untyped,
46+
clippy::missing_errors_doc
47+
)]
4448

4549
#[cfg(feature = "nightly")]
4650
use std::alloc::{Alloc, AllocErr, CannotReallocInPlace};
@@ -54,6 +58,10 @@ pub struct Cap<H> {
5458
allocator: H,
5559
remaining: AtomicUsize,
5660
limit: AtomicUsize,
61+
#[cfg(feature = "stats")]
62+
total_allocated: AtomicUsize,
63+
#[cfg(feature = "stats")]
64+
max_allocated: AtomicUsize,
5765
}
5866

5967
impl<H> Cap<H> {
@@ -65,6 +73,10 @@ impl<H> Cap<H> {
6573
allocator,
6674
remaining: AtomicUsize::new(limit),
6775
limit: AtomicUsize::new(limit),
76+
#[cfg(feature = "stats")]
77+
total_allocated: AtomicUsize::new(0),
78+
#[cfg(feature = "stats")]
79+
max_allocated: AtomicUsize::new(0),
6880
}
6981
}
7082

@@ -101,16 +113,16 @@ impl<H> Cap<H> {
101113
}
102114
if self
103115
.limit
104-
.compare_and_swap(limit_old, limit, Ordering::Relaxed)
105-
!= limit_old
116+
.compare_exchange(limit_old, limit, Ordering::Relaxed, Ordering::Relaxed)
117+
.is_err()
106118
{
107119
continue;
108120
}
109121
} else {
110122
if self
111123
.limit
112-
.compare_and_swap(limit_old, limit, Ordering::Relaxed)
113-
!= limit_old
124+
.compare_exchange(limit_old, limit, Ordering::Relaxed, Ordering::Relaxed)
125+
.is_err()
114126
{
115127
continue;
116128
}
@@ -134,6 +146,34 @@ impl<H> Cap<H> {
134146
}
135147
}
136148
}
149+
150+
/// Get total amount of allocated memory. This includes already deallocated memory.
151+
#[cfg(feature = "stats")]
152+
pub fn total_allocated(&self) -> usize {
153+
self.total_allocated.load(Ordering::Relaxed)
154+
}
155+
156+
/// Get maximum amount of memory that was allocated at any point in time.
157+
#[cfg(feature = "stats")]
158+
pub fn max_allocated(&self) -> usize {
159+
self.max_allocated.load(Ordering::Relaxed)
160+
}
161+
162+
fn update_stats(&self, size: usize) {
163+
#[cfg(feature = "stats")]
164+
{
165+
let _ = self.total_allocated.fetch_add(size, Ordering::Relaxed);
166+
// If max_allocated is less than currently allocated, then it will be updated to limit - remaining.
167+
// Otherwise, it will remain unchanged.
168+
let _ = self
169+
.max_allocated
170+
.fetch_max(self.allocated(), Ordering::Relaxed);
171+
}
172+
#[cfg(not(feature = "stats"))]
173+
{
174+
let _ = (self, size);
175+
}
176+
}
137177
}
138178

139179
unsafe impl<H> GlobalAlloc for Cap<H>
@@ -149,6 +189,8 @@ where
149189
};
150190
if res.is_null() {
151191
let _ = self.remaining.fetch_add(size, Ordering::Release);
192+
} else {
193+
self.update_stats(size);
152194
}
153195
res
154196
}
@@ -166,13 +208,15 @@ where
166208
};
167209
if res.is_null() {
168210
let _ = self.remaining.fetch_add(size, Ordering::Release);
211+
} else {
212+
self.update_stats(size);
169213
}
170214
res
171215
}
172216
unsafe fn realloc(&self, ptr: *mut u8, old_l: Layout, new_s: usize) -> *mut u8 {
173217
let new_l = Layout::from_size_align_unchecked(new_s, old_l.align());
174218
let (old_size, new_size) = (old_l.size(), new_l.size());
175-
if new_size > old_size {
219+
let res = if new_size > old_size {
176220
let res = if self
177221
.remaining
178222
.fetch_sub(new_size - old_size, Ordering::Acquire)
@@ -195,8 +239,13 @@ where
195239
.remaining
196240
.fetch_add(old_size - new_size, Ordering::Release);
197241
}
242+
// Although this might just deaalocate, I will still update the stats as if it allocates to be on "the safe side"
198243
res
244+
};
245+
if !res.is_null() {
246+
self.update_stats(new_size);
199247
}
248+
res
200249
}
201250
}
202251

@@ -214,6 +263,8 @@ where
214263
};
215264
if res.is_err() {
216265
let _ = self.remaining.fetch_add(size, Ordering::Release);
266+
} else {
267+
self.update_stats(size);
217268
}
218269
res
219270
}
@@ -233,7 +284,7 @@ where
233284
self.allocator.usable_size(&old_l).1,
234285
self.allocator.usable_size(&new_l).1,
235286
);
236-
if new_size > old_size {
287+
let res = if new_size > old_size {
237288
let res = if self
238289
.remaining
239290
.fetch_sub(new_size - old_size, Ordering::Acquire)
@@ -257,7 +308,11 @@ where
257308
.fetch_add(old_size - new_size, Ordering::Release);
258309
}
259310
res
311+
};
312+
if res.is_ok() {
313+
self.update_stats(new_size);
260314
}
315+
res
261316
}
262317
unsafe fn alloc_zeroed(&mut self, l: Layout) -> Result<ptr::NonNull<u8>, AllocErr> {
263318
let size = self.allocator.usable_size(&l).1;
@@ -268,6 +323,8 @@ where
268323
};
269324
if res.is_err() {
270325
let _ = self.remaining.fetch_add(size, Ordering::Release);
326+
} else {
327+
self.update_stats(size);
271328
}
272329
res
273330
}
@@ -292,6 +349,8 @@ where
292349
let _ = self
293350
.remaining
294351
.fetch_add(new_size - old_size, Ordering::Release);
352+
} else {
353+
self.update_stats(new_size - old_size);
295354
}
296355
res
297356
}
@@ -357,26 +416,63 @@ mod tests {
357416
.into_iter()
358417
.for_each(|thread| thread.join().unwrap());
359418
let allocated2 = A.allocated();
419+
#[cfg(feature = "stats")]
420+
let total_allocated = A.total_allocated();
360421
if cfg!(all(test, feature = "nightly")) {
361422
assert_eq!(allocated, allocated2);
423+
#[cfg(feature = "stats")]
424+
assert!(total_allocated >= allocated);
362425
}
363426
}
427+
#[cfg(feature = "stats")]
428+
assert!(A.max_allocated() < A.total_allocated());
429+
}
430+
431+
#[cfg(all(test, not(feature = "nightly")))]
432+
#[test]
433+
fn limit() {
434+
#[cfg(feature = "stats")]
435+
let initial = A.allocated();
436+
let allocate_amount = 30 * 1024 * 1024;
437+
A.set_limit(A.allocated() + allocate_amount).unwrap();
438+
for _ in 0..10 {
439+
let mut vec = Vec::<u8>::with_capacity(0);
440+
if let Err(_e) = vec.try_reserve_exact(allocate_amount + 1) {
441+
} else {
442+
A.set_limit(usize::max_value()).unwrap();
443+
panic!("{}", A.remaining());
444+
};
445+
assert_eq!(vec.try_reserve_exact(allocate_amount), Ok(()));
446+
let mut vec2 = Vec::<u8>::with_capacity(0);
447+
assert!(vec2.try_reserve_exact(1).is_err());
448+
}
449+
// Might have additional allocations of errors and what not along the way.
450+
#[cfg(feature = "stats")]
451+
{
452+
assert!(A.total_allocated() >= initial + 10 * allocate_amount);
453+
assert_eq!(A.max_allocated(), initial + allocate_amount);
454+
}
364455
}
365456

366457
#[cfg(all(test, feature = "nightly"))]
367458
#[test]
368459
fn limit() {
369-
A.set_limit(A.allocated() + 30 * 1024 * 1024).unwrap();
460+
let allocate_amount = 30 * 1024 * 1024;
461+
A.set_limit(A.allocated() + allocate_amount).unwrap();
370462
for _ in 0..10 {
371463
let mut vec = Vec::<u8>::with_capacity(0);
372464
if let Err(TryReserveError::AllocError { .. }) =
373-
vec.try_reserve_exact(30 * 1024 * 1024 + 1)
465+
vec.try_reserve_exact(allocate_amount + 1)
374466
{
375467
} else {
376468
A.set_limit(usize::max_value()).unwrap();
377469
panic!("{}", A.remaining())
378470
};
379-
assert_eq!(vec.try_reserve_exact(30 * 1024 * 1024), Ok(()));
471+
assert_eq!(vec.try_reserve_exact(allocate_amount), Ok(()));
472+
let mut vec2 = Vec::<u8>::with_capacity(0);
473+
assert!(vec2.try_reserve_exact(1).is_err());
380474
}
475+
assert_eq!(A.total_allocated(), 10 * allocate_amount);
476+
assert_eq!(A.max_allocated(), allocate_amount)
381477
}
382478
}

0 commit comments

Comments
 (0)