Skip to content
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

[823] add detailed disk metrics #827

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 15 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ Bob versions changelog
- Added mimalloc allocator for musl target (#688)
- Added jemalloc-profile for memory profiling (#797)
- Proper support for GetSource::ALL requests (#723)
- Added detailed information about total, used and free space on every disk (#823)

#### Changed
- BobClient clone overhead reduced (#774)
Expand Down
62 changes: 42 additions & 20 deletions bob/src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use bob_backend::pearl::{Group as PearlGroup, Holder, NoopHooks};
use bob_common::{
configs::node::TLSConfig,
data::{BobData, BobKey, BobMeta, BOB_KEY_SIZE},
core_types::{VDisk as DataVDisk, NodeDisk},
core_types::{VDisk as DataVDisk, NodeDisk, DiskName},
operation_options::{BobPutOptions, BobGetOptions, BobDeleteOptions},
error::Error as BobError,
};
Expand Down Expand Up @@ -145,12 +145,25 @@ pub(crate) struct NodeConfiguration {
}

#[derive(Debug, Serialize)]
pub(crate) struct SpaceInfo {
struct Space {
total_disk_space_bytes: u64,
free_disk_space_bytes: u64,
used_disk_space_bytes: u64,
}

#[derive(Debug, Serialize)]
pub(crate) struct SpaceInfo {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please update openapi in config-examples

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be good to integrate utoipa for openapi auto-gen at some point

#[serde(flatten)]
total_space: Space,

/// Total occupied disk space by Bob
occupied_disk_space_bytes: u64,
occupied_disk_space_by_disk: HashMap<String, u64>,

/// Key - Bob disk name
occupied_disk_space_by_disk: HashMap<DiskName, u64>,

/// Key - Mount point or Bob disk name if no mount point was found
disk_space_by_disk: HashMap<String, Space>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just thought that it would be better to use disk name as the key (on Linux it is sda1, sdb1, on Windows it is C:, D:), because this is the common way of identification of disks in OS

Copy link
Member

@ikopylov ikopylov Jan 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think, it would be better to do it later in another request. Right now, mount point would be enough here

}

async fn tls_server(tls_config: &TLSConfig, addr: SocketAddr) -> AxumServer<RustlsAcceptor> {
Expand Down Expand Up @@ -313,31 +326,40 @@ async fn status<A: Authenticator>(bob: Extension<BobServer<A>>) -> Json<Node> {
async fn get_space_info<A: Authenticator>(
bob: Extension<BobServer<A>>,
) -> Result<Json<SpaceInfo>, StatusExt> {
let DiskSpaceMetrics {
total_space,
used_space,
free_space,
} = bob.grinder().hw_counter().update_space_metrics();

let disk_metrics = bob.grinder().hw_counter().update_space_metrics();
let total_disks_metrics: DiskSpaceMetrics = disk_metrics.values().sum();
let backend = bob.grinder().backend().inner();
let (dcs, adc) = backend
.disk_controllers()
.ok_or_else(not_acceptable_backend)?;
let mut map = HashMap::new();
for dc in dcs.iter() {
map.insert(dc.disk().name().to_string(), dc.disk_used().await);
}

let mut occupied_disk_space_by_disk: HashMap<DiskName, u64> = futures::future::join_all(
dcs.iter().map(|dc| async { ( dc.disk().name().clone(), dc.disk_used().await ) })
).await.into_iter().collect();
let adc_space = adc.disk_used().await;
map.entry(adc.disk().name().to_string())
.and_modify(|s| *s = *s + adc_space)

let disk_space_by_disk = disk_metrics.into_iter().map(|(mount_point, disk)| (
mount_point.to_string_lossy().to_string(),
Space {
total_disk_space_bytes: disk.total_space,
free_disk_space_bytes: disk.free_space,
used_disk_space_bytes: disk.used_space
}
)).collect();

occupied_disk_space_by_disk.entry(adc.disk().name().clone())
.and_modify(|s| *s += adc_space)
.or_insert(adc_space);

Ok(Json(SpaceInfo {
total_disk_space_bytes: total_space,
used_disk_space_bytes: used_space,
free_disk_space_bytes: free_space,
occupied_disk_space_bytes: map.values().sum(),
occupied_disk_space_by_disk: map,
total_space: Space {
total_disk_space_bytes: total_disks_metrics.total_space,
used_disk_space_bytes: total_disks_metrics.used_space,
free_disk_space_bytes: total_disks_metrics.free_space,
},
occupied_disk_space_bytes: occupied_disk_space_by_disk.values().sum(),
occupied_disk_space_by_disk,
disk_space_by_disk,
}))
}

Expand Down
97 changes: 74 additions & 23 deletions bob/src/hw_metrics_collector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ use bob_common::metrics::{
TOTAL_SPACE, FREE_SPACE, USED_SPACE, HW_DISKS_FOLDER
};
use libc::statvfs;
use std::ops::{AddAssign, Add};
use std::iter::Sum;
use std::os::unix::fs::MetadataExt;
use std::path::{Path, PathBuf};
use tokio::process::Command;
Expand All @@ -16,6 +18,7 @@ const DESCRS_DIR: &str = "/proc/self/fd/";
const CPU_STAT_FILE: &str = "/proc/stat";
const DISK_STAT_FILE: &str = "/proc/diskstats";

#[derive(Debug, Clone)]
pub(crate) struct DiskSpaceMetrics {
pub(crate) total_space: u64,
pub(crate) used_space: u64,
Expand Down Expand Up @@ -63,7 +66,11 @@ impl HWMetricsCollector {
tokio::spawn(Self::task(self.interval_time, self.disks.clone()));
}

pub(crate) fn update_space_metrics(&self) -> DiskSpaceMetrics {
/// Returns the updated space metrics of this [`HWMetricsCollector`].
idruzhitskiy marked this conversation as resolved.
Show resolved Hide resolved
///
/// Key -- mount points of used disks
/// Value -- Updated [`DiskSpaceMetrics`]
pub(crate) fn update_space_metrics(&self) -> HashMap<PathBuf, DiskSpaceMetrics> {
Self::update_space_metrics_from_disks(&self.disks)
}

Expand Down Expand Up @@ -125,11 +132,14 @@ impl HWMetricsCollector {
}
}

fn update_space_metrics_from_disks(disks: &HashMap<PathBuf, DiskName>) -> DiskSpaceMetrics {
fn update_space_metrics_from_disks(
disks: &HashMap<PathBuf, DiskName>,
) -> HashMap<PathBuf, DiskSpaceMetrics> {
let disks_metrics = Self::space(disks);
gauge!(TOTAL_SPACE, bytes_to_mb(disks_metrics.total_space) as f64);
gauge!(USED_SPACE, bytes_to_mb(disks_metrics.used_space) as f64);
gauge!(FREE_SPACE, bytes_to_mb(disks_metrics.free_space) as f64);
let summed_space: DiskSpaceMetrics = disks_metrics.values().sum();
ikopylov marked this conversation as resolved.
Show resolved Hide resolved
gauge!(TOTAL_SPACE, bytes_to_mb(summed_space.total_space) as f64);
gauge!(USED_SPACE, bytes_to_mb(summed_space.used_space) as f64);
gauge!(FREE_SPACE, bytes_to_mb(summed_space.free_space) as f64);
disks_metrics
}

Expand Down Expand Up @@ -158,30 +168,32 @@ impl HWMetricsCollector {
// NOTE: HashMap contains only needed mount points of used disks, so it won't be really big,
// but maybe it's more efficient to store disks (instead of mount_points) and update them one by one

fn space(disks: &HashMap<PathBuf, DiskName>) -> DiskSpaceMetrics {
let mut total = 0;
let mut used = 0;
let mut free = 0;

for mount_point in disks.keys() {
/// Maps mount point to the corresponding [`DiskSpaceMetrics`].
fn space(disks: &HashMap<PathBuf, DiskName>) -> HashMap<PathBuf, DiskSpaceMetrics> {
let mut res = HashMap::new();
let mut fs_ids = HashSet::new();
for (mount_point, _) in disks {
let cm_p = Self::to_cpath(mount_point.as_path());
let stat = Self::statvfs_wrap(&cm_p);
if let Some(stat) = stat {
let bsize = stat.f_bsize as u64;
let blocks = stat.f_blocks as u64;
let bavail = stat.f_bavail as u64;
let bfree = stat.f_bfree as u64;
total += bsize * blocks;
free += bsize * bavail;
used += (blocks - bfree) * bsize;
let bsize: u64 = stat.f_bsize;
let blocks: u64 = stat.f_blocks;
let bavail: u64 = stat.f_bavail;
let bfree: u64 = stat.f_bfree;
if fs_ids.insert(stat.f_fsid) {
res.insert(
mount_point.clone(),
DiskSpaceMetrics {
total_space: bsize * blocks,
used_space: bsize * bavail,
free_space: (blocks - bfree) * bsize,
},
);
}
}
}

DiskSpaceMetrics {
total_space: total,
used_space: used,
free_space: free,
}
res
}
}

Expand Down Expand Up @@ -603,3 +615,42 @@ async fn parse_command_output(command: &mut Command) -> Result<String, String> {
}
}
}

// Std Traits Impls

impl<'a> AddAssign<&'a Self> for DiskSpaceMetrics {
fn add_assign(&mut self, rhs: &Self) {
self.used_space += rhs.used_space;
self.free_space += rhs.free_space;
self.total_space += rhs.total_space;
}
}
impl<'a> Add<&'a Self> for DiskSpaceMetrics {
type Output = Self;

fn add(mut self, rhs: &Self) -> Self::Output {
self += rhs;
self
}
}

impl<'a> Sum<&'a Self> for DiskSpaceMetrics {
/// Summarize [`DiskSpaceMetrics`] over an iterator.
/// NOTE: the `disk_name` field will be chosen from the first appeared disk in iterator if there
/// is any. Otherwise 'None' will be passed
fn sum<I: Iterator<Item = &'a Self>>(mut iter: I) -> Self {
let init = if let Some(metrics) = iter.next() {
metrics.clone()
} else {
return Self {
total_space: 0,
used_space: 0,
free_space: 0,
}
};
iter.fold(
init,
|a, b| a + b,
)
}
}
Loading