Skip to main content

query_runtime/runtime/gpu/
profile.rs

1//! Immutable per-device resource-controller profile.
2
3use std::fmt;
4
5use query_planning::{
6    admission::ExecutionCapability,
7    error::{QueryError, QueryErrorCode, Result, bytes_with_gib},
8};
9
10const OPERATION: &str = "query_engine.gpu.resource_profile";
11
12/// Stable identity of one device-execution capability required by a native
13/// attempt.
14///
15/// Values are frozen into a device profile at installation; placement never
16/// probes or mutates them at submission time.
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18pub struct DeviceCapability {
19    capability: ExecutionCapability,
20}
21
22impl DeviceCapability {
23    /// Creates one immutable device capability identity.
24    pub const fn new(name: &'static str) -> Self {
25        Self { capability: ExecutionCapability::new(name) }
26    }
27
28    const fn from_requirement(requirement: ExecutionCapability) -> Self {
29        Self { capability: requirement }
30    }
31}
32
33/// Immutable capability metadata for one installed CUDA device.
34///
35/// Existing supported hardware declares every execution capability available on
36/// that device.
37/// Installers may instead freeze an exact list for heterogeneous deployments.
38/// A non-modeled requirement is never accepted merely because it appears in
39/// an exact list; the planner must establish the contract first.
40#[derive(Debug, Clone, PartialEq, Eq)]
41pub struct DeviceCapabilities {
42    support: DeviceCapabilitySupport,
43}
44
45#[derive(Debug, Clone, PartialEq, Eq)]
46enum DeviceCapabilitySupport {
47    All,
48    Exact(Vec<DeviceCapability>),
49}
50
51impl Default for DeviceCapabilities {
52    fn default() -> Self {
53        Self::all()
54    }
55}
56
57impl DeviceCapabilities {
58    /// Supports every declared execution capability.
59    pub(crate) const fn all() -> Self {
60        Self { support: DeviceCapabilitySupport::All }
61    }
62
63    /// Supports only the supplied capability identities.
64    ///
65    /// This is the per-device construction input for heterogeneous
66    /// deployments.  Duplicate entries are harmless and do not change
67    /// placement semantics.
68    pub fn only(capabilities: impl IntoIterator<Item = DeviceCapability>) -> Self {
69        let mut exact = Vec::new();
70        for capability in capabilities {
71            if !exact.contains(&capability) {
72                exact.push(capability);
73            }
74        }
75        Self { support: DeviceCapabilitySupport::Exact(exact) }
76    }
77
78    pub(crate) fn supports(&self, requirement: &ExecutionCapability) -> bool {
79        match &self.support {
80            DeviceCapabilitySupport::All => true,
81            DeviceCapabilitySupport::Exact(capabilities) => {
82                capabilities.contains(&DeviceCapability::from_requirement(*requirement))
83            },
84        }
85    }
86
87    pub(crate) fn supports_all(&self, requirements: &[ExecutionCapability]) -> bool {
88        requirements.iter().all(|requirement| self.supports(requirement))
89    }
90
91    /// Whether admission must receive an explicit planner-produced requirement.
92    ///
93    /// An absent requirement is safe only when every execution capability is
94    /// accepted. Exact profiles must not interpret an empty implicit set as
95    /// evidence that the attempt can execute on the device.
96    pub(crate) const fn requires_explicit_attempt_requirement(&self) -> bool {
97        matches!(self.support, DeviceCapabilitySupport::Exact(_))
98    }
99}
100
101/// Frozen capacity and execution-slot bounds for one CUDA device.
102///
103/// `cache_cap_bytes` (`K`) reserves a bounded per-device cache share taken
104/// from the managed capacity (`M`). Query grants and frozen residents share
105/// only the remaining query share (`Q = M - K`); query admission never
106/// promises the same bytes that back the cache share.
107#[derive(Debug, Clone, PartialEq, Eq)]
108pub struct DeviceResourceProfile {
109    /// CUDA device whose ledger owns this resource profile.
110    pub device_ordinal: usize,
111    /// Capability metadata frozen with this device's resource envelope.
112    pub device_capabilities: DeviceCapabilities,
113    pub(crate) configured_limit_bytes: u64,
114    pub(crate) headroom_bytes: u64,
115    pub(crate) backend_reserve_bytes: u64,
116    /// `configured_limit_bytes - headroom_bytes`, before the backend reserve.
117    pub(crate) usable_capacity_bytes: u64,
118    /// `M`: the bytes the ledger may promise to grants, residents, and cache.
119    pub managed_capacity_bytes: u64,
120    /// Default immutable grant in bytes, before any resident-capacity adjustment at admission.
121    pub default_query_limit_bytes: u64,
122    /// `A_d`. Also the number of lifecycle job permits, root streams, and
123    /// attempt lanes.
124    pub max_active_attempts: usize,
125    /// Equals `max_active_attempts`; one lane per admitted attempt.
126    pub root_lane_count: usize,
127    /// Equals `max_active_attempts`; one root stream per admitted attempt.
128    pub root_stream_count: usize,
129    /// `S_d`, normalized to at least 1.
130    pub source_work_per_attempt: usize,
131    /// `A_d * S_d`: auxiliary stream slots and execution job permits for the
132    /// whole device.
133    pub(crate) aux_source_work_capacity: usize,
134    /// At least `A_d + A_d * S_d`, checked by `resolve`.
135    pub stream_pool_size: usize,
136    /// At least `A_d + A_d * S_d`, checked by `resolve`.
137    pub worker_job_capacity: usize,
138    /// Cache share reserved from managed capacity in bytes.
139    pub cache_cap_bytes: u64,
140}
141
142/// Immutable input contract for one device's resolved resource envelope.
143///
144/// These fields jointly define the admission envelope one device freezes at
145/// installation: capacity splits, the default query limit, sloting, and cache
146/// share. `resolve` consumes the envelope; the derived capacities, lane and
147/// stream counts, and aux-source-work arithmetic stay computed outputs on
148/// [`DeviceResourceProfile`].
149#[derive(Debug, Clone, PartialEq, Eq)]
150pub(crate) struct DeviceResourceEnvelope {
151    /// Device ordinal this envelope resolves for.
152    pub(crate) device_ordinal: usize,
153    pub(crate) device_capabilities: DeviceCapabilities,
154    /// Total configured allocation limit before headroom.
155    pub(crate) configured_limit_bytes: u64,
156    /// Headroom subtracted from the configured limit before backend reserve.
157    pub(crate) headroom_bytes: u64,
158    /// Backend-reserved bytes unavailable to managed attempts.
159    pub(crate) backend_reserve_bytes: u64,
160    /// Default per-query limit guaranteed inside managed capacity.
161    pub(crate) default_query_limit_bytes: u64,
162    /// Maximum simultaneously admitted attempts.
163    pub(crate) max_active_attempts: usize,
164    /// Auxiliary slots granted per attempt.
165    ///
166    /// Each slot backs one auxiliary permit holder of the attempt: an
167    /// execution lane, a source worker, or an auxiliary fragment context.
168    /// The native route serves one disjoint fork subset per live holder, so
169    /// this count sizes the attempt's fork pool, and it is one input to the execution width below.
170    /// Execution width is the minimum of this envelope, the free permits,
171    /// the device execution-job capacity, and the ready work.
172    pub(crate) source_work_per_attempt: usize,
173    /// Installed stream-pool size; must cover root and auxiliary work.
174    pub(crate) stream_pool_size: usize,
175    /// Installed worker-job capacity; must cover root and auxiliary work.
176    pub(crate) worker_job_capacity: usize,
177    /// Bounded per-device cache share reserved alongside the default query limit.
178    pub(crate) cache_cap_bytes: u64,
179}
180
181impl DeviceResourceProfile {
182    /// Query share (`Q = M - K`) promisable to grants plus frozen residents.
183    ///
184    /// Cache charges (`C <= K`) never participate in this share, so query
185    /// admission and cache eviction cannot promise the same bytes.
186    pub fn query_share_bytes(&self) -> u64 {
187        self.managed_capacity_bytes.saturating_sub(self.cache_cap_bytes)
188    }
189
190    /// Derives the default per-query grant from the service parallelism
191    /// target: `floor((M - K) / N)` with checked arithmetic.
192    ///
193    /// This is a capacity-allocation policy that does not estimate the memory a
194    /// query needs. With no frozen residents the profile admits `N` default
195    /// grants. An explicitly configured default keeps its value after validation.
196    pub(crate) fn derive_default_query_limit_bytes(
197        device_ordinal: usize,
198        managed_capacity_bytes: u64,
199        cache_cap_bytes: u64,
200        max_active_attempts: usize,
201    ) -> Result<u64> {
202        if max_active_attempts == 0 {
203            return Err(
204                invalid(device_ordinal, "max_active_attempts", "must be greater than zero").with_fact("reason", "zero")
205            );
206        }
207        let query_share_bytes =
208            managed_capacity_bytes.checked_sub(cache_cap_bytes).filter(|share| *share > 0).ok_or_else(|| {
209                invalid(
210                    device_ordinal,
211                    "cache_cap_bytes",
212                    format!(
213                        "is {}, at or above the managed capacity of {}; the largest accepted value is {}",
214                        bytes_with_gib(cache_cap_bytes),
215                        bytes_with_gib(managed_capacity_bytes),
216                        bytes_with_gib(managed_capacity_bytes.saturating_sub(1)),
217                    ),
218                )
219                .with_fact("reason", "cache_cap_exceeds_managed_capacity")
220                .with_fact("cache_cap_bytes", cache_cap_bytes)
221                .with_fact("managed_capacity_bytes", managed_capacity_bytes)
222            })?;
223        let divisor = u64::try_from(max_active_attempts).map_err(|_| {
224            invalid(device_ordinal, "max_active_attempts", "parallelism target exceeds the capacity integer range")
225                .with_fact("reason", "max_active_attempts_overflow")
226        })?;
227        let derived = query_share_bytes.checked_div(divisor).filter(|cap| *cap > 0).ok_or_else(|| {
228            invalid(device_ordinal, "default_query_limit_bytes", "derived default query limit is zero")
229                .with_fact("reason", "derived_default_query_limit_is_zero")
230                .with_fact("query_share_bytes", query_share_bytes)
231                .with_fact("max_active_attempts", max_active_attempts)
232        })?;
233        Ok(derived)
234    }
235
236    /// Resolves all resource-controller arithmetic for one device from its
237    /// immutable resource envelope.
238    pub(crate) fn resolve(envelope: DeviceResourceEnvelope) -> Result<Self> {
239        let DeviceResourceEnvelope {
240            device_ordinal,
241            device_capabilities,
242            configured_limit_bytes,
243            headroom_bytes,
244            backend_reserve_bytes,
245            default_query_limit_bytes,
246            max_active_attempts,
247            source_work_per_attempt,
248            stream_pool_size,
249            worker_job_capacity,
250            cache_cap_bytes,
251        } = envelope;
252        let usable_capacity_bytes = configured_limit_bytes.checked_sub(headroom_bytes).ok_or_else(|| {
253            invalid(
254                device_ordinal,
255                "headroom_bytes",
256                format!(
257                    "is {}, above the configured limit of {}",
258                    bytes_with_gib(headroom_bytes),
259                    bytes_with_gib(configured_limit_bytes),
260                ),
261            )
262            .with_fact("reason", "configured_limit_below_headroom")
263            .with_fact("configured_limit_bytes", configured_limit_bytes)
264            .with_fact("headroom_bytes", headroom_bytes)
265        })?;
266        let managed_capacity_bytes = usable_capacity_bytes.checked_sub(backend_reserve_bytes).ok_or_else(|| {
267            invalid(
268                device_ordinal,
269                "backend_reserve_bytes",
270                format!(
271                    "is {}, above the usable capacity of {}; the largest accepted value is {}",
272                    bytes_with_gib(backend_reserve_bytes),
273                    bytes_with_gib(usable_capacity_bytes),
274                    bytes_with_gib(usable_capacity_bytes.saturating_sub(1)),
275                ),
276            )
277            .with_fact("reason", "backend_reserve_exceeds_usable_capacity")
278            .with_fact("usable_capacity_bytes", usable_capacity_bytes)
279            .with_fact("backend_reserve_bytes", backend_reserve_bytes)
280        })?;
281        if managed_capacity_bytes == 0 {
282            return Err(invalid(
283                device_ordinal,
284                "managed_capacity_bytes",
285                format!(
286                    "is zero; backend_reserve_bytes of {} takes the whole usable capacity of {}",
287                    bytes_with_gib(backend_reserve_bytes),
288                    bytes_with_gib(usable_capacity_bytes),
289                ),
290            )
291            .with_fact("reason", "zero_managed_capacity"));
292        }
293        if cache_cap_bytes >= managed_capacity_bytes {
294            return Err(invalid(
295                device_ordinal,
296                "cache_cap_bytes",
297                format!(
298                    "is {}, at or above the managed capacity of {}; the largest accepted value is {}",
299                    bytes_with_gib(cache_cap_bytes),
300                    bytes_with_gib(managed_capacity_bytes),
301                    bytes_with_gib(managed_capacity_bytes - 1),
302                ),
303            )
304            .with_fact("reason", "cache_cap_exceeds_managed_capacity")
305            .with_fact("cache_cap_bytes", cache_cap_bytes)
306            .with_fact("managed_capacity_bytes", managed_capacity_bytes));
307        }
308        if max_active_attempts == 0 {
309            return Err(
310                invalid(device_ordinal, "max_active_attempts", "must be greater than zero").with_fact("reason", "zero")
311            );
312        }
313        if default_query_limit_bytes == 0 {
314            return Err(invalid(device_ordinal, "default_query_limit_bytes", "must be greater than zero")
315                .with_fact("reason", "zero"));
316        }
317        let query_share_bytes = managed_capacity_bytes.checked_sub(cache_cap_bytes).ok_or_else(|| {
318            invalid(device_ordinal, "cache_cap_bytes", "cache cap and query share overflow capacity arithmetic")
319                .with_fact("reason", "cache_cap_overflow")
320        })?;
321        if default_query_limit_bytes > query_share_bytes {
322            return Err(invalid(
323                device_ordinal,
324                "default_query_limit_bytes",
325                format!(
326                    "is {}, above the query share of {} (managed capacity {} minus cache_cap_bytes {}); the largest accepted value is {}",
327                    bytes_with_gib(default_query_limit_bytes),
328                    bytes_with_gib(query_share_bytes),
329                    bytes_with_gib(managed_capacity_bytes),
330                    bytes_with_gib(cache_cap_bytes),
331                    bytes_with_gib(query_share_bytes),
332                ),
333            )
334            .with_fact("reason", "default_query_limit_exceeds_managed_capacity")
335            .with_fact("default_query_limit_bytes", default_query_limit_bytes)
336            .with_fact("query_share_bytes", query_share_bytes)
337            .with_fact("managed_capacity_bytes", managed_capacity_bytes));
338        }
339        let source_work_per_attempt = source_work_per_attempt.max(1);
340        let aux_source_work_capacity = max_active_attempts.checked_mul(source_work_per_attempt).ok_or_else(|| {
341            invalid(device_ordinal, "source_work_per_attempt", "auxiliary source-work capacity overflowed")
342                .with_fact("reason", "aux_source_work_overflow")
343        })?;
344        let required_slots = max_active_attempts.checked_add(aux_source_work_capacity).ok_or_else(|| {
345            invalid(device_ordinal, "stream_pool_size", "required stream capacity overflowed")
346                .with_fact("reason", "required_slot_overflow")
347        })?;
348        for (field, actual) in [("stream_pool_size", stream_pool_size), ("worker_job_capacity", worker_job_capacity)] {
349            if actual < required_slots {
350                return Err(invalid(device_ordinal, field, "must cover root and auxiliary source work")
351                    .with_fact("reason", "below_required_slot_capacity")
352                    .with_fact("required_slot_capacity", required_slots)
353                    .with_fact("actual_capacity", actual));
354            }
355        }
356        Ok(Self {
357            device_ordinal,
358            device_capabilities,
359            configured_limit_bytes,
360            headroom_bytes,
361            backend_reserve_bytes,
362            usable_capacity_bytes,
363            managed_capacity_bytes,
364            default_query_limit_bytes,
365            max_active_attempts,
366            root_lane_count: max_active_attempts,
367            root_stream_count: max_active_attempts,
368            source_work_per_attempt,
369            aux_source_work_capacity,
370            stream_pool_size,
371            worker_job_capacity,
372            cache_cap_bytes,
373        })
374    }
375}
376
377fn invalid(device_ordinal: usize, field: &'static str, message: impl fmt::Display) -> QueryError {
378    QueryError::new(
379        QueryErrorCode::InvalidConfiguration,
380        OPERATION,
381        format!("device {device_ordinal} {field} {message}"),
382    )
383    .with_fact("device_ordinal", device_ordinal)
384    .with_fact("field", field)
385}
386
387#[cfg(test)]
388mod tests {
389    use query_planning::admission::ExecutionCapability;
390
391    use super::*;
392
393    #[test]
394    fn resolves_heterogeneous_profiles_and_slot_math() {
395        let small = /*OK*/DeviceResourceProfile::resolve(DeviceResourceEnvelope {
396            device_ordinal: 0,
397            configured_limit_bytes: 1_000,
398            headroom_bytes: 100,
399            backend_reserve_bytes: 100,
400            default_query_limit_bytes: 100,
401            max_active_attempts: 2,
402            source_work_per_attempt: 3,
403            stream_pool_size: 8,
404            worker_job_capacity: 8,
405            device_capabilities: DeviceCapabilities::default(),
406            cache_cap_bytes: 0,
407        }).unwrap();
408        let large = DeviceResourceProfile::resolve(DeviceResourceEnvelope {
409            device_ordinal: 1,
410            configured_limit_bytes: 2_000,
411            headroom_bytes: 200,
412            backend_reserve_bytes: 100,
413            default_query_limit_bytes: 100,
414            max_active_attempts: 3,
415            source_work_per_attempt: 1,
416            stream_pool_size: 6,
417            worker_job_capacity: 6,
418            device_capabilities: DeviceCapabilities::default(),
419            cache_cap_bytes: 0,
420        })
421        .unwrap();
422        assert_eq!((small.usable_capacity_bytes, small.managed_capacity_bytes), (900, 800));
423        assert_eq!((small.root_lane_count, small.root_stream_count, small.aux_source_work_capacity), (2, 2, 6));
424        assert_eq!((large.usable_capacity_bytes, large.managed_capacity_bytes), (1_800, 1_700));
425    }
426
427    #[test]
428    fn rejects_invalid_capacity_and_query_limit_arithmetic() {
429        for (limit, headroom, reserve, reason) in [
430            (99, 100, 0, "configured_limit_below_headroom"),
431            (100, 0, 101, "backend_reserve_exceeds_usable_capacity"),
432            (100, 0, 100, "zero_managed_capacity"),
433        ] {
434            let error = DeviceResourceProfile::resolve(DeviceResourceEnvelope {
435                device_ordinal: 7,
436                configured_limit_bytes: limit,
437                headroom_bytes: headroom,
438                backend_reserve_bytes: reserve,
439                default_query_limit_bytes: 0,
440                max_active_attempts: 1,
441                source_work_per_attempt: 1,
442                stream_pool_size: 2,
443                worker_job_capacity: 2,
444                device_capabilities: DeviceCapabilities::default(),
445                cache_cap_bytes: 0,
446            })
447            .unwrap_err();
448            assert_eq!(error.fact_value("device_ordinal"), Some("7"));
449            assert_eq!(error.fact_value("reason"), Some(reason));
450        }
451        let error = DeviceResourceProfile::resolve(DeviceResourceEnvelope {
452            device_ordinal: 7,
453            configured_limit_bytes: u64::MAX - 1,
454            headroom_bytes: 0,
455            backend_reserve_bytes: 0,
456            default_query_limit_bytes: u64::MAX,
457            max_active_attempts: 2,
458            source_work_per_attempt: 1,
459            stream_pool_size: 4,
460            worker_job_capacity: 4,
461            device_capabilities: DeviceCapabilities::default(),
462            cache_cap_bytes: 0,
463        })
464        .unwrap_err();
465        assert_eq!(error.fact_value("reason"), Some("default_query_limit_exceeds_managed_capacity"));
466    }
467
468    #[test]
469    fn validates_cache_cap_against_managed_capacity() {
470        let profile = DeviceResourceProfile::resolve(DeviceResourceEnvelope {
471            device_ordinal: 0,
472            configured_limit_bytes: 1_000,
473            headroom_bytes: 0,
474            backend_reserve_bytes: 0,
475            default_query_limit_bytes: 100,
476            max_active_attempts: 2,
477            source_work_per_attempt: 1,
478            stream_pool_size: 4,
479            worker_job_capacity: 4,
480            device_capabilities: DeviceCapabilities::default(),
481            cache_cap_bytes: 800,
482        })
483        .unwrap();
484        assert_eq!(profile.cache_cap_bytes, 800);
485        let error = DeviceResourceProfile::resolve(DeviceResourceEnvelope {
486            device_ordinal: 0,
487            configured_limit_bytes: 1_000,
488            headroom_bytes: 0,
489            backend_reserve_bytes: 0,
490            default_query_limit_bytes: 100,
491            max_active_attempts: 2,
492            source_work_per_attempt: 1,
493            stream_pool_size: 4,
494            worker_job_capacity: 4,
495            device_capabilities: DeviceCapabilities::default(),
496            cache_cap_bytes: 1_001,
497        })
498        .unwrap_err();
499        assert_eq!(error.fact_value("reason"), Some("cache_cap_exceeds_managed_capacity"));
500    }
501
502    #[test]
503    fn rejects_zero_default_query_limit_for_an_otherwise_valid_profile() {
504        let error = DeviceResourceProfile::resolve(DeviceResourceEnvelope {
505            device_ordinal: 7,
506            configured_limit_bytes: 100,
507            headroom_bytes: 0,
508            backend_reserve_bytes: 0,
509            default_query_limit_bytes: 0,
510            max_active_attempts: 1,
511            source_work_per_attempt: 1,
512            stream_pool_size: 2,
513            worker_job_capacity: 2,
514            device_capabilities: DeviceCapabilities::default(),
515            cache_cap_bytes: 0,
516        })
517        .unwrap_err();
518        assert_eq!(error.fact_value("field"), Some("default_query_limit_bytes"));
519        assert_eq!(error.fact_value("reason"), Some("zero"));
520    }
521
522    #[test]
523    fn rejects_query_limit_and_slot_capacity_below_requirements() {
524        let query_limit = DeviceResourceProfile::resolve(DeviceResourceEnvelope {
525            device_ordinal: 3,
526            configured_limit_bytes: 100,
527            headroom_bytes: 0,
528            backend_reserve_bytes: 0,
529            default_query_limit_bytes: 101,
530            max_active_attempts: 2,
531            source_work_per_attempt: 1,
532            stream_pool_size: 4,
533            worker_job_capacity: 4,
534            device_capabilities: DeviceCapabilities::default(),
535            cache_cap_bytes: 0,
536        })
537        .unwrap_err();
538        assert_eq!(query_limit.fact_value("reason"), Some("default_query_limit_exceeds_managed_capacity"));
539        let slots = DeviceResourceProfile::resolve(DeviceResourceEnvelope {
540            device_ordinal: 3,
541            configured_limit_bytes: 100,
542            headroom_bytes: 0,
543            backend_reserve_bytes: 0,
544            default_query_limit_bytes: 10,
545            max_active_attempts: 2,
546            source_work_per_attempt: 3,
547            stream_pool_size: 7,
548            worker_job_capacity: 7,
549            device_capabilities: DeviceCapabilities::default(),
550            cache_cap_bytes: 0,
551        })
552        .unwrap_err();
553        assert_eq!(slots.fact_value("reason"), Some("below_required_slot_capacity"));
554    }
555
556    #[test]
557    fn frozen_capability_metadata_requires_exact_matches() {
558        let required = ExecutionCapability::new("test_operator");
559        let other = ExecutionCapability::new("other_operator");
560        let restricted = DeviceCapabilities::only([DeviceCapability::new("test_operator")]);
561
562        assert!(restricted.supports(&required));
563        assert!(!restricted.supports(&other));
564        assert!(!DeviceCapabilities::only(std::iter::empty::<DeviceCapability>()).supports(&required));
565    }
566
567    #[test]
568    fn derives_default_query_limit_from_parallelism_target_and_cache_share() {
569        // `floor((M - K) / N)`: the plan's `M = 24, K = 8, N = 2` fixture
570        // yields 8, so two defaults sum to the query share.
571        assert_eq!(DeviceResourceProfile::derive_default_query_limit_bytes(0, 24, 8, 2).unwrap(), 8);
572        assert_eq!(DeviceResourceProfile::derive_default_query_limit_bytes(0, 10, 0, 3).unwrap(), 3);
573        assert_eq!(DeviceResourceProfile::derive_default_query_limit_bytes(0, 1_000, 0, 1).unwrap(), 1_000);
574
575        let zero_share = DeviceResourceProfile::derive_default_query_limit_bytes(0, 10, 9, 3).unwrap_err();
576        assert_eq!(zero_share.fact_value("reason"), Some("derived_default_query_limit_is_zero"));
577
578        let no_share = DeviceResourceProfile::derive_default_query_limit_bytes(0, 8, 8, 2).unwrap_err();
579        assert_eq!(no_share.fact_value("reason"), Some("cache_cap_exceeds_managed_capacity"));
580
581        let no_parallelism = DeviceResourceProfile::derive_default_query_limit_bytes(0, 24, 8, 0).unwrap_err();
582        assert_eq!(no_parallelism.fact_value("reason"), Some("zero"));
583    }
584
585    #[test]
586    fn query_share_subtracts_the_cache_share() {
587        let envelope = |cache_cap_bytes, default_query_limit_bytes| DeviceResourceEnvelope {
588            device_ordinal: 0,
589            configured_limit_bytes: 1_000,
590            headroom_bytes: 0,
591            backend_reserve_bytes: 0,
592            default_query_limit_bytes,
593            max_active_attempts: 2,
594            source_work_per_attempt: 1,
595            stream_pool_size: 4,
596            worker_job_capacity: 4,
597            device_capabilities: DeviceCapabilities::default(),
598            cache_cap_bytes,
599        };
600        let profile = DeviceResourceProfile::resolve(envelope(300, 700)).unwrap();
601        assert_eq!(profile.query_share_bytes(), 700);
602
603        let beyond_share = DeviceResourceProfile::resolve(envelope(300, 701)).unwrap_err();
604        assert_eq!(beyond_share.fact_value("reason"), Some("default_query_limit_exceeds_managed_capacity"));
605
606        let no_share = DeviceResourceProfile::resolve(envelope(1_000, 1)).unwrap_err();
607        assert_eq!(no_share.fact_value("reason"), Some("cache_cap_exceeds_managed_capacity"));
608    }
609}