Skip to content

Commit ef30c26

Browse files
committed
Add detailed index and database statistics reporting in FDB bridge
1 parent 3662849 commit ef30c26

6 files changed

Lines changed: 143 additions & 46 deletions

File tree

rust/crates/fdb-sys/cpp/fdb_bridge.cpp

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "metkit/mars/MarsRequest.h"
2121

2222
#include <mutex>
23+
#include <sstream>
2324
#include <stdexcept>
2425

2526
// Include the cxx-generated header for our bridge types
@@ -490,13 +491,25 @@ StatsElementData StatsIteratorHandle::next() {
490491

491492
has_current_ = false;
492493

494+
// Mirror `fdb5::StatsElement { IndexStats; DbStats; }` directly.
495+
// For `IndexStats` we can read every numeric accessor; for
496+
// `DbStats` upstream only exposes `report(ostream&)`, so the
497+
// captured text is the only thing we can surface.
493498
StatsElementData data;
494-
// StatsElement is a DbStats - access via indexStatistics methods
495-
data.location = rust::String("");
496-
data.field_count = current_.indexStatistics.fieldsCount();
497-
data.total_size = current_.indexStatistics.fieldsSize();
498-
data.duplicate_count = current_.indexStatistics.duplicatesCount();
499-
data.duplicate_size = current_.indexStatistics.duplicatesSize();
499+
data.index_statistics.fields_count = current_.indexStatistics.fieldsCount();
500+
data.index_statistics.fields_size = current_.indexStatistics.fieldsSize();
501+
data.index_statistics.duplicates_count = current_.indexStatistics.duplicatesCount();
502+
data.index_statistics.duplicates_size = current_.indexStatistics.duplicatesSize();
503+
{
504+
std::ostringstream os;
505+
current_.indexStatistics.report(os);
506+
data.index_statistics.report = os.str();
507+
}
508+
{
509+
std::ostringstream os;
510+
current_.dbStatistics.report(os);
511+
data.db_statistics.report = os.str();
512+
}
500513
return data;
501514
}
502515

rust/crates/fdb-sys/cpp/fdb_bridge.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ struct DumpElementData;
8484
struct StatusElementData;
8585
struct WipeElementData;
8686
struct PurgeElementData;
87+
struct IndexStatsData;
88+
struct DbStatsData;
8789
struct StatsElementData;
8890
struct ControlElementData;
8991
struct MoveElementData;

rust/crates/fdb-sys/src/lib.rs

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -159,19 +159,34 @@ mod ffi {
159159
pub content: String,
160160
}
161161

162-
/// Result from stats iteration.
162+
/// Index-level stats — mirrors `fdb5::IndexStats`. Bundles the four
163+
/// numeric accessors (`fieldsCount` / `fieldsSize` /
164+
/// `duplicatesCount` / `duplicatesSize`) plus the `report()` text.
165+
#[derive(Debug, Clone, Default)]
166+
pub struct IndexStatsData {
167+
pub fields_count: u64,
168+
pub fields_size: u64,
169+
pub duplicates_count: u64,
170+
pub duplicates_size: u64,
171+
/// Captured `fdb5::IndexStats::report()` output.
172+
pub report: String,
173+
}
174+
175+
/// Database-level stats — mirrors `fdb5::DbStats`. Upstream exposes
176+
/// `DbStats` as fully opaque content; the only public read accessor
177+
/// is `report(std::ostream&)`, so the captured report text is the
178+
/// only thing we can surface.
179+
#[derive(Debug, Clone, Default)]
180+
pub struct DbStatsData {
181+
/// Captured `fdb5::DbStats::report()` output.
182+
pub report: String,
183+
}
184+
185+
/// Result from stats iteration — mirrors `fdb5::StatsElement`.
163186
#[derive(Debug, Clone, Default)]
164187
pub struct StatsElementData {
165-
/// Location
166-
pub location: String,
167-
/// Number of fields
168-
pub field_count: u64,
169-
/// Total size in bytes
170-
pub total_size: u64,
171-
/// Duplicate count
172-
pub duplicate_count: u64,
173-
/// Duplicate size
174-
pub duplicate_size: u64,
188+
pub index_statistics: IndexStatsData,
189+
pub db_statistics: DbStatsData,
175190
}
176191

177192
/// Result from control iteration.

rust/crates/fdb/src/iterator.rs

Lines changed: 48 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -429,11 +429,16 @@ impl Iterator for StatsIterator {
429429

430430
match self.handle.pin_mut().next() {
431431
Ok(data) => Some(Ok(StatsElement {
432-
location: data.location,
433-
field_count: data.field_count,
434-
total_size: data.total_size,
435-
duplicate_count: data.duplicate_count,
436-
duplicate_size: data.duplicate_size,
432+
index_statistics: IndexStats {
433+
fields_count: data.index_statistics.fields_count,
434+
fields_size: data.index_statistics.fields_size,
435+
duplicates_count: data.index_statistics.duplicates_count,
436+
duplicates_size: data.index_statistics.duplicates_size,
437+
report: data.index_statistics.report,
438+
},
439+
db_statistics: DbStats {
440+
report: data.db_statistics.report,
441+
},
437442
})),
438443
Err(e) => {
439444
self.exhausted = true;
@@ -451,19 +456,46 @@ impl Iterator for StatsIterator {
451456
#[allow(clippy::non_send_fields_in_send_ty)]
452457
unsafe impl Send for StatsIterator {}
453458

454-
/// A stats element containing database statistics.
459+
/// Index-level statistics — mirrors `fdb5::IndexStats`.
460+
///
461+
/// Bundles the four numeric accessors upstream exposes
462+
/// (`fieldsCount` / `fieldsSize` / `duplicatesCount` / `duplicatesSize`)
463+
/// plus the captured `report()` text.
464+
#[derive(Debug, Clone)]
465+
pub struct IndexStats {
466+
/// Number of fields covered by this index.
467+
pub fields_count: u64,
468+
/// Total size in bytes of those fields.
469+
pub fields_size: u64,
470+
/// Number of duplicate (masked) entries.
471+
pub duplicates_count: u64,
472+
/// Total size in bytes of the duplicate entries.
473+
pub duplicates_size: u64,
474+
/// Captured `fdb5::IndexStats::report()` output — the same text
475+
/// `fdb-stats --details` prints for the index portion.
476+
pub report: String,
477+
}
478+
479+
/// Database-level statistics — mirrors `fdb5::DbStats`.
480+
///
481+
/// Upstream's `DbStats` is fully content-opaque; the only public
482+
/// readable accessor is `report(std::ostream&)`. The captured report
483+
/// text is therefore the only thing this binding can surface — same
484+
/// rule the C++ tools play by.
485+
#[derive(Debug, Clone)]
486+
pub struct DbStats {
487+
/// Captured `fdb5::DbStats::report()` output — the same text
488+
/// `fdb-stats --details` prints for the database portion.
489+
pub report: String,
490+
}
491+
492+
/// A stats element — mirrors `fdb5::StatsElement`.
455493
#[derive(Debug, Clone)]
456494
pub struct StatsElement {
457-
/// Location of the database.
458-
pub location: String,
459-
/// Number of fields.
460-
pub field_count: u64,
461-
/// Total size in bytes.
462-
pub total_size: u64,
463-
/// Number of duplicate entries.
464-
pub duplicate_count: u64,
465-
/// Size of duplicate data in bytes.
466-
pub duplicate_size: u64,
495+
/// Index-level statistics for this database.
496+
pub index_statistics: IndexStats,
497+
/// Database-level statistics for this database.
498+
pub db_statistics: DbStats,
467499
}
468500

469501
// =============================================================================

rust/crates/fdb/src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ pub use datareader::DataReader;
4545
pub use error::{Error, Result};
4646
pub use handle::{ArchiveCallbackData, Fdb, FdbConfig, FdbStats};
4747
pub use iterator::{
48-
ControlElement, ControlIterator, DumpElement, DumpIterator, ListElement, ListIterator,
49-
MoveElement, MoveIterator, PurgeElement, PurgeIterator, StatsElement, StatsIterator,
50-
StatusElement, StatusIterator, WipeElement, WipeIterator,
48+
ControlElement, ControlIterator, DbStats, DumpElement, DumpIterator, IndexStats, ListElement,
49+
ListIterator, MoveElement, MoveIterator, PurgeElement, PurgeIterator, StatsElement,
50+
StatsIterator, StatusElement, StatusIterator, WipeElement, WipeIterator,
5151
};
5252
pub use key::Key;
5353
pub use options::{DumpOptions, ListOptions, PurgeOptions, WipeOptions};

rust/crates/fdb/tests/fdb_integration.rs

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -571,17 +571,52 @@ fn test_fdb_stats_iterator() {
571571
let stats_items: Vec<_> = fdb
572572
.stats_iter(&request)
573573
.expect("failed to get stats")
574-
.collect();
574+
.collect::<Result<Vec<_>, _>>()
575+
.expect("stats iterator returned an error");
575576

576-
println!("Stats returned {} items", stats_items.len());
577-
for item in &stats_items {
578-
match item {
579-
Ok(elem) => println!(
580-
" fields={}, size={}, duplicates={}",
581-
elem.field_count, elem.total_size, elem.duplicate_count
582-
),
583-
Err(e) => println!(" error: {e}"),
584-
}
577+
assert!(
578+
!stats_items.is_empty(),
579+
"expected at least one stats element after archiving one field"
580+
);
581+
582+
// Sum the index-level numeric fields across all returned databases.
583+
// We just archived one field, so the totals across the iterator must
584+
// include it. (Some FDB layouts may report it as multiple index
585+
// entries; what matters is that the totals are non-zero and
586+
// consistent with what we wrote.)
587+
let total_fields: u64 = stats_items
588+
.iter()
589+
.map(|s| s.index_statistics.fields_count)
590+
.sum();
591+
let total_bytes: u64 = stats_items
592+
.iter()
593+
.map(|s| s.index_statistics.fields_size)
594+
.sum();
595+
596+
assert!(
597+
total_fields >= 1,
598+
"expected total fields_count >= 1, got {total_fields}"
599+
);
600+
assert!(
601+
total_bytes >= grib_data.len() as u64,
602+
"expected total fields_size >= {} bytes (the GRIB we archived), got {total_bytes}",
603+
grib_data.len()
604+
);
605+
606+
// The report text fields are captured straight from
607+
// `IndexStats::report()` / `DbStats::report()` on the C++ side.
608+
// They should be non-empty for a populated database — that proves
609+
// the captured-report path is actually wired up, not just an empty
610+
// sentinel like the bogus `location` field used to be.
611+
for stats in &stats_items {
612+
assert!(
613+
!stats.index_statistics.report.is_empty(),
614+
"index_statistics.report should not be empty after archiving data"
615+
);
616+
assert!(
617+
!stats.db_statistics.report.is_empty(),
618+
"db_statistics.report should not be empty after archiving data"
619+
);
585620
}
586621
}
587622

0 commit comments

Comments
 (0)