Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions downstairs-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ api_versions!([
// | example for the next person.
// v
// (next_int, IDENT),
(2, MEMORY),
(1, INITIAL),
]);

Expand Down Expand Up @@ -122,4 +123,14 @@ pub trait CrucibleDownstairsRepairApi {
async fn get_work(
rqctx: RequestContext<Self::Context>,
) -> Result<HttpResponseOk<bool>, HttpError>;

/// Memory usage report
#[endpoint {
method = GET,
path = "/memory",
versions = VERSION_MEMORY..,
}]
async fn get_memory(
rqctx: RequestContext<Self::Context>,
) -> Result<HttpResponseOk<latest::repair::MemoryReport>, HttpError>;
}
39 changes: 38 additions & 1 deletion downstairs-types/versions/src/initial/repair.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright 2026 Oxide Computer Company

use schemars::JsonSchema;
use serde::Deserialize;
use serde::{Deserialize, Serialize};

#[derive(Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
Expand Down Expand Up @@ -37,3 +37,40 @@ pub struct ExtentFilePath {
pub struct JobPath {
pub id: String,
}

/// Per-connection memory report
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct ConnectionMemoryReport {
pub pending_jobs: usize,
pub pending_jobs_bytes: usize,
pub pending_jobs_capacity_hwm: usize,
pub completed_ranges: usize,
pub completed_ranges_hwm: usize,
}

/// Summary of downstairs memory usage
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct MemoryReport {
// Region configuration for context
pub extent_count: u32,
pub extent_size: u64,
pub block_size: u64,

// Region memory
pub region_bytes: usize,
pub extent_meta_bytes: usize,
pub bytes_per_extent: usize,
pub dirty_extent_count: usize,

// High-water marks for transient I/O buffers
pub read_bytes_hwm: usize,
pub write_bytes_hwm: usize,

// Thread counts
pub tokio_worker_threads: usize,
pub rayon_threads: usize,

// Connection memory
pub active_connections: usize,
pub connections: Vec<ConnectionMemoryReport>,
}
2 changes: 2 additions & 0 deletions downstairs-types/versions/src/latest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ pub mod admin {
}

pub mod repair {
pub use crate::v1::repair::ConnectionMemoryReport;
pub use crate::v1::repair::ExtentFilePath;
pub use crate::v1::repair::ExtentPath;
pub use crate::v1::repair::FileSpec;
pub use crate::v1::repair::FileType;
pub use crate::v1::repair::JobPath;
pub use crate::v1::repair::MemoryReport;
}
5 changes: 5 additions & 0 deletions downstairs/src/complete_jobs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ impl CompletedJobs {
self.completed.contains(&id)
}

/// Returns the number of contiguous ranges stored
pub fn range_count(&self) -> usize {
self.completed.iter().count()
}

/// Returns the list of completed jobs
pub fn completed(&self) -> impl Iterator<Item = JobId> + use<'_> {
self.completed
Expand Down
17 changes: 17 additions & 0 deletions downstairs/src/extent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ pub(crate) trait ExtentInner: Send + Sync + Debug {
&mut self,
block_context: &DownstairsBlockContext,
) -> Result<(), CrucibleError>;

/// Returns the heap bytes allocated by this extent's in-memory
/// metadata (not the on-disk file data).
fn heap_size(&self) -> usize;
}

/// BlockContext, with the addition of block index and on_disk_hash
Expand Down Expand Up @@ -716,6 +720,19 @@ impl Extent {
) -> Result<Vec<Option<DownstairsBlockContext>>, CrucibleError> {
self.inner.get_block_contexts(block, count)
}

pub fn heap_size(&self) -> usize {
self.inner.heap_size()
}
}

impl ExtentState {
pub fn heap_size(&self) -> usize {
match self {
ExtentState::Opened(extent) => extent.heap_size(),
ExtentState::Closed => 0,
}
}
}

/**
Expand Down
10 changes: 10 additions & 0 deletions downstairs/src/extent_inner_raw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,10 @@ impl BlockBitArray {
self.block_count as usize
}

fn heap_size(&self) -> usize {
self.data.capacity() * size_of::<u32>()
}

fn iter(&self) -> impl Iterator<Item = bool> + '_ {
(0..self.block_count).map(|i| self[i])
}
Expand Down Expand Up @@ -687,6 +691,12 @@ impl ExtentInner for RawInner {
) -> Result<Vec<Option<DownstairsBlockContext>>, CrucibleError> {
RawInner::get_block_contexts(self, block, count)
}

fn heap_size(&self) -> usize {
size_of_val(self)
+ self.active_context.0.heap_size()
+ self.block_dirty.heap_size()
}
}

impl RawInner {
Expand Down
4 changes: 4 additions & 0 deletions downstairs/src/extent_inner_sqlite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ impl ExtentInner for SqliteInner {
.unwrap()
.set_dirty_and_block_context(block_context)
}

fn heap_size(&self) -> usize {
0
}
}

impl SqliteInner {
Expand Down
Loading