use std::sync::Arc;
use query_planning::error::{QueryError, QueryErrorCode, Result};
#[cfg(feature = "cugraph")]
use super::resident_graph::GpuResidentGraphCache;
use crate::{
native_error::cudf_query_error,
runtime::{
cache::{DeviceCacheReport, local_parquet::GpuLocalParquetSourceCache},
gpu::DeviceLedger,
},
};
#[derive(Debug, Clone)]
pub(crate) struct DeviceCacheAllocationFactory {
ledger: DeviceLedger,
domain: Arc<cudf::CacheAllocationDomain>,
}
impl DeviceCacheAllocationFactory {
pub(crate) fn new(ledger: DeviceLedger) -> Result<Option<Self>> {
let capacity = ledger.profile().cache_cap_bytes;
if capacity == 0 {
return Ok(None);
}
let device = cudf::DeviceId::new(ledger.device_ordinal()).map_err(|error| {
cudf_query_error(
"query_engine.runtime.cache.factory",
"failed to resolve the ledger device for the cache allocation domain",
error,
)
.with_fact("device_ordinal", ledger.device_ordinal())
})?;
let domain = cudf::CacheAllocationDomain::new(device, capacity).map_err(|error| {
cudf_query_error(
"query_engine.runtime.cache.factory",
"failed to create the device cache allocation domain",
error,
)
.with_fact("device_ordinal", ledger.device_ordinal())
.with_fact("cache_cap_bytes", capacity)
})?;
if domain.device().ordinal() != ledger.device_ordinal() || domain.capacity_bytes() != capacity {
return Err(QueryError::new(
QueryErrorCode::CudfInvariantViolation,
"query_engine.runtime.cache.factory",
"cache allocation domain does not match its ledger",
)
.with_fact("reason", "cache_domain_ledger_mismatch")
.with_fact("device_ordinal", ledger.device_ordinal())
.with_fact("domain_device_ordinal", domain.device().ordinal())
.with_fact("cache_cap_bytes", capacity)
.with_fact("domain_capacity_bytes", domain.capacity_bytes()));
}
Ok(Some(Self { ledger, domain: Arc::new(domain) }))
}
pub(crate) fn source_cache(&self) -> Result<GpuLocalParquetSourceCache> {
let lease = self.domain.lease_runtime().map_err(|error| {
cudf_query_error(
"query_engine.runtime.cache.factory.source",
"failed to lease the shared cache allocation domain for source cache",
error,
)
})?;
GpuLocalParquetSourceCache::new(lease, self.ledger.clone())
}
#[cfg(feature = "cugraph")]
pub(crate) fn graph_cache(&self) -> Result<GpuResidentGraphCache> {
GpuResidentGraphCache::new(self.ledger.clone(), (*self.domain).clone())
}
pub(crate) fn report(&self) -> Result<DeviceCacheReport> {
const MAX_COHERENCE_ATTEMPTS: usize = 4;
for _ in 0..MAX_COHERENCE_ATTEMPTS {
let before = self.ledger.report()?;
let usage = self.domain.usage().map_err(|error| {
cudf_query_error(
"query_engine.runtime.cache.factory.report",
"failed to sample the shared cache allocation domain",
error,
)
.with_fact("device_ordinal", self.ledger.device_ordinal())
.with_fact("cache_allocation_domain_id", self.domain.id())
})?;
let after = self.ledger.report()?;
let cache_residency_unchanged = before.cache_evictable_resident_bytes
== after.cache_evictable_resident_bytes
&& before.cache_pinned_in_use_bytes == after.cache_pinned_in_use_bytes;
if before.sequence != after.sequence && !cache_residency_unchanged {
continue;
}
return Ok(DeviceCacheReport {
device_ordinal: self.ledger.device_ordinal(),
allocation_domain_id: self.domain.id().get(),
cache_cap_bytes: self.domain.capacity_bytes(),
ledger_sequence: after.sequence,
ledger_evictable_resident_bytes: after.cache_evictable_resident_bytes,
ledger_pinned_in_use_bytes: after.cache_pinned_in_use_bytes,
allocation_limit_bytes: usage.allocation_limit_bytes,
physical_current_bytes: usage.current_bytes,
physical_current_aligned_bytes: usage.current_aligned_bytes,
physical_peak_bytes: usage.peak_bytes,
physical_total_bytes: usage.total_bytes,
physical_total_allocations: usage.total_allocations,
source: Default::default(),
graph: Default::default(),
});
}
Err(QueryError::new(
QueryErrorCode::ServiceOverloaded,
"query_engine.runtime.cache.factory.report",
"cache residency changed throughout the paired physical usage sample",
)
.with_fact("reason", "cache_report_residency_unstable")
.with_fact("device_ordinal", self.ledger.device_ordinal())
.with_fact("cache_allocation_domain_id", self.domain.id())
.with_fact("max_coherence_attempts", MAX_COHERENCE_ATTEMPTS))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn zero_cap_does_not_create_a_domain() {
let ledger = DeviceLedger::for_test(0, 1024, 1);
assert!(DeviceCacheAllocationFactory::new(ledger).unwrap().is_none());
}
}