Coverage Report

Created: 2025-11-11 16:38

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/build/source/nativelink-util/src/platform_properties.rs
Line
Count
Source
1
// Copyright 2024 The NativeLink Authors. All rights reserved.
2
//
3
// Licensed under the Functional Source License, Version 1.1, Apache 2.0 Future License (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//    See LICENSE file for details
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
use std::borrow::Cow;
16
use std::collections::HashMap;
17
18
use nativelink_metric::{
19
    MetricFieldData, MetricKind, MetricPublishKnownKindData, MetricsComponent, publish,
20
};
21
use nativelink_proto::build::bazel::remote::execution::v2::Platform as ProtoPlatform;
22
use nativelink_proto::build::bazel::remote::execution::v2::platform::Property as ProtoProperty;
23
use serde::{Deserialize, Serialize};
24
use tracing::info;
25
26
/// `PlatformProperties` helps manage the configuration of platform properties to
27
/// keys and types. The scheduler uses these properties to decide what jobs
28
/// can be assigned to different workers. For example, if a job states it needs
29
/// a specific key, it will never be run on a worker that does not have at least
30
/// all the platform property keys configured on the worker.
31
///
32
/// Additional rules may be applied based on `PlatformPropertyValue`.
33
#[derive(Eq, PartialEq, Clone, Debug, Default, Serialize, Deserialize, MetricsComponent)]
34
pub struct PlatformProperties {
35
    #[metric]
36
    pub properties: HashMap<String, PlatformPropertyValue>,
37
}
38
39
impl PlatformProperties {
40
    #[must_use]
41
51
    pub const fn new(map: HashMap<String, PlatformPropertyValue>) -> Self {
42
51
        Self { properties: map }
43
51
    }
44
45
    /// Determines if the worker's `PlatformProperties` is satisfied by this struct.
46
    #[must_use]
47
37
    pub fn is_satisfied_by(&self, worker_properties: &Self, full_worker_logging: bool) -> bool {
48
41
        for (
property9
,
check_value9
) in &self.properties {
49
9
            if let Some(worker_value) = worker_properties.properties.get(property) {
  Branch (49:20): [True: 9, False: 0]
  Branch (49:20): [Folded - Ignored]
50
9
                if !check_value.is_satisfied_by(worker_value) {
  Branch (50:20): [True: 5, False: 4]
  Branch (50:20): [Folded - Ignored]
51
5
                    if full_worker_logging {
  Branch (51:24): [True: 5, False: 0]
  Branch (51:24): [Folded - Ignored]
52
5
                        info!(
53
5
                            "Property mismatch on worker property {property}. {worker_value:?} != {check_value:?}"
54
                        );
55
0
                    }
56
5
                    return false;
57
4
                }
58
            } else {
59
0
                if full_worker_logging {
  Branch (59:20): [True: 0, False: 0]
  Branch (59:20): [Folded - Ignored]
60
0
                    info!("Property missing on worker property {property}");
61
0
                }
62
0
                return false;
63
            }
64
        }
65
32
        true
66
37
    }
67
}
68
69
impl From<ProtoPlatform> for PlatformProperties {
70
0
    fn from(platform: ProtoPlatform) -> Self {
71
0
        let mut properties = HashMap::with_capacity(platform.properties.len());
72
0
        for property in platform.properties {
73
0
            properties.insert(
74
0
                property.name,
75
0
                PlatformPropertyValue::Unknown(property.value),
76
0
            );
77
0
        }
78
0
        Self { properties }
79
0
    }
80
}
81
82
impl From<&PlatformProperties> for ProtoPlatform {
83
33
    fn from(val: &PlatformProperties) -> Self {
84
        Self {
85
33
            properties: val
86
33
                .properties
87
33
                .iter()
88
33
                .map(|(name, value)| ProtoProperty {
89
5
                    name: name.clone(),
90
5
                    value: value.as_str().to_string(),
91
5
                })
92
33
                .collect(),
93
        }
94
33
    }
95
}
96
97
/// Holds the associated value of the key and type.
98
///
99
/// Exact    - Means the worker must have this exact value.
100
/// Minimum  - Means that workers must have at least this number available. When
101
///            a worker executes a task that has this value, the worker will have
102
///            this value subtracted from the available resources of the worker.
103
/// Priority - Means the worker is given this information, but does not restrict
104
///            what workers can take this value. However, the worker must have the
105
///            associated key present to be matched.
106
///            TODO(palfrey) In the future this will be used by the scheduler and
107
///            worker to cause the scheduler to prefer certain workers over others,
108
///            but not restrict them based on these values.
109
#[derive(Eq, PartialEq, Hash, Clone, Ord, PartialOrd, Debug, Serialize, Deserialize)]
110
pub enum PlatformPropertyValue {
111
    Exact(String),
112
    Minimum(u64),
113
    Priority(String),
114
    Unknown(String),
115
}
116
117
impl PlatformPropertyValue {
118
    /// Same as `PlatformProperties::is_satisfied_by`, but on an individual value.
119
    #[must_use]
120
9
    pub fn is_satisfied_by(&self, worker_value: &Self) -> bool {
121
9
        if self == worker_value {
  Branch (121:12): [True: 4, False: 5]
  Branch (121:12): [Folded - Ignored]
122
4
            return true;
123
5
        }
124
5
        match self {
125
3
            Self::Minimum(v) => {
126
3
                if let Self::Minimum(worker_v) = worker_value {
  Branch (126:24): [True: 3, False: 0]
  Branch (126:24): [Folded - Ignored]
127
3
                    return worker_v >= v;
128
0
                }
129
0
                false
130
            }
131
            // Priority is used to pass info to the worker and not restrict which
132
            // workers can be selected, but might be used to prefer certain workers
133
            // over others.
134
0
            Self::Priority(_) => true,
135
            // Success exact case is handled above.
136
2
            Self::Exact(_) | Self::Unknown(_) => false,
137
        }
138
9
    }
139
140
7
    pub fn as_str(&self) -> Cow<'_, str> {
141
7
        match self {
142
2
            Self::Exact(value) | Self::Priority(
value0
) | Self::Unknown(
value0
) => {
143
2
                Cow::Borrowed(value)
144
            }
145
5
            Self::Minimum(value) => Cow::Owned(value.to_string()),
146
        }
147
7
    }
148
}
149
150
impl MetricsComponent for PlatformPropertyValue {
151
0
    fn publish(
152
0
        &self,
153
0
        kind: MetricKind,
154
0
        field_metadata: MetricFieldData,
155
0
    ) -> Result<MetricPublishKnownKindData, nativelink_metric::Error> {
156
0
        let name = field_metadata.name.into_owned();
157
0
        let help = field_metadata.help.as_ref();
158
0
        match self {
159
0
            Self::Exact(v) => publish!(name, v, kind, help, "exact"),
160
0
            Self::Minimum(v) => publish!(name, v, kind, help, "minimum"),
161
0
            Self::Priority(v) => publish!(name, v, kind, help, "priority"),
162
0
            Self::Unknown(v) => publish!(name, v, kind, help, "unknown"),
163
        }
164
165
0
        Ok(MetricPublishKnownKindData::Component)
166
0
    }
167
}