Coverage Report

Created: 2026-09-18 20:40

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
124
    pub const fn new(map: HashMap<String, PlatformPropertyValue>) -> Self {
42
124
        Self { properties: map }
43
124
    }
44
45
    /// Determines if the worker's `PlatformProperties` is satisfied by this struct.
46
    #[must_use]
47
62
    pub fn is_satisfied_by(&self, worker_properties: &Self, full_worker_logging: bool) -> bool {
48
62
        for (
property26
,
check_value26
) in &self.properties {
49
26
            if let PlatformPropertyValue::Ignore(_) = check_value {
50
1
                continue; // always matches
51
25
            }
52
25
            if let Some(
worker_value24
) = worker_properties.properties.get(property) {
53
24
                if !check_value.is_satisfied_by(worker_value) {
54
11
                    if full_worker_logging {
55
3
                        match check_value {
56
                            PlatformPropertyValue::Minimum(_) => {
57
2
                                info!(
58
                                    "Property mismatch on worker property {property}. {worker_value:?} < {check_value:?}"
59
                                );
60
                            }
61
                            _ => {
62
1
                                info!(
63
                                    "Property mismatch on worker property {property}. {worker_value:?} != {check_value:?}"
64
                                );
65
                            }
66
                        }
67
8
                    }
68
11
                    return false;
69
13
                }
70
            } else {
71
                // Unknown properties only restrict workers that declare the key.
72
1
                if let PlatformPropertyValue::Unknown(_) = check_value {
73
1
                    continue;
74
0
                }
75
0
                if full_worker_logging {
76
0
                    info!("Property missing on worker property {property}");
77
0
                }
78
0
                return false;
79
            }
80
        }
81
51
        true
82
62
    }
83
}
84
85
impl From<ProtoPlatform> for PlatformProperties {
86
0
    fn from(platform: ProtoPlatform) -> Self {
87
0
        let mut properties = HashMap::with_capacity(platform.properties.len());
88
0
        for property in platform.properties {
89
0
            properties.insert(
90
0
                property.name,
91
0
                PlatformPropertyValue::Unknown(property.value),
92
0
            );
93
0
        }
94
0
        Self { properties }
95
0
    }
96
}
97
98
impl From<&PlatformProperties> for ProtoPlatform {
99
46
    fn from(val: &PlatformProperties) -> Self {
100
46
        let mut properties = val
101
46
            .properties
102
46
            .iter()
103
46
            .map(|(name, value)| ProtoProperty {
104
15
                name: name.clone(),
105
15
                value: value.as_str().to_string(),
106
15
            })
107
46
            .collect::<Vec<_>>();
108
46
        properties.sort_unstable_by(|a, b| 
a.name2
.
cmp2
(
&b.name2
));
109
46
        Self { properties }
110
46
    }
111
}
112
113
/// Holds the associated value of the key and type.
114
///
115
/// Exact    - Means the worker must have this exact value.
116
/// Minimum  - Means that workers must have at least this number available. When
117
///            a worker executes a task that has this value, the worker will have
118
///            this value subtracted from the available resources of the worker.
119
/// Priority - Means the worker is given this information, but does not restrict
120
///            what workers can take this value. However, the worker must have the
121
///            associated key present to be matched.
122
///            TODO(palfrey) In the future this will be used by the scheduler and
123
///            worker to cause the scheduler to prefer certain workers over others,
124
///            but not restrict them based on these values.
125
/// Ignore   - Jobs can request this key, but workers do not have to have it. This allows
126
///            for example the `InputRootAbsolutePath` case for chromium builds, where we can safely
127
///            ignore it without having to change the worker configs.
128
/// Unknown  - The key was not declared in the scheduler's configuration. Workers
129
///            that declare the key must match the value exactly, workers that do
130
///            not declare the key are not restricted by it.
131
#[derive(Eq, PartialEq, Hash, Clone, Ord, PartialOrd, Debug, Serialize, Deserialize)]
132
pub enum PlatformPropertyValue {
133
    Exact(String),
134
    Minimum(u64),
135
    Priority(String),
136
    Ignore(String),
137
    Unknown(String),
138
}
139
140
impl PlatformPropertyValue {
141
    /// Same as `PlatformProperties::is_satisfied_by`, but on an individual value.
142
    #[must_use]
143
30
    pub fn is_satisfied_by(&self, worker_value: &Self) -> bool {
144
30
        if self == worker_value {
145
13
            return true;
146
17
        }
147
17
        match self {
148
12
            Self::Minimum(v) => {
149
12
                if let Self::Minimum(worker_v) = worker_value {
150
12
                    return worker_v >= v;
151
0
                }
152
0
                false
153
            }
154
            // Priority is used to pass info to the worker and not restrict which
155
            // workers can be selected, but might be used to prefer certain workers
156
            // over others.
157
1
            Self::Priority(_) | Self::Ignore(_) => true,
158
            // Unknown properties are not typed by the scheduler config, so
159
            // compare by value regardless of the worker's variant.
160
4
            Self::Unknown(value) => worker_value.as_str() == value.as_str(),
161
            // Success exact case is handled above.
162
0
            Self::Exact(_) => false,
163
        }
164
30
    }
165
166
21
    pub fn as_str(&self) -> Cow<'_, str> {
167
21
        match self {
168
4
            Self::Exact(value)
169
0
            | Self::Priority(value)
170
2
            | Self::Unknown(value)
171
6
            | Self::Ignore(
value0
) => Cow::Borrowed(value),
172
15
            Self::Minimum(value) => Cow::Owned(value.to_string()),
173
        }
174
21
    }
175
}
176
177
impl MetricsComponent for PlatformPropertyValue {
178
0
    fn publish(
179
0
        &self,
180
0
        kind: MetricKind,
181
0
        field_metadata: MetricFieldData,
182
0
    ) -> Result<MetricPublishKnownKindData, nativelink_metric::Error> {
183
0
        let name = field_metadata.name.into_owned();
184
0
        let help = field_metadata.help.as_ref();
185
0
        match self {
186
0
            Self::Exact(v) => publish!(name, v, kind, help, "exact"),
187
0
            Self::Minimum(v) => publish!(name, v, kind, help, "minimum"),
188
0
            Self::Priority(v) => publish!(name, v, kind, help, "priority"),
189
0
            Self::Ignore(v) => publish!(name, v, kind, help, "ignore"),
190
0
            Self::Unknown(v) => publish!(name, v, kind, help, "unknown"),
191
        }
192
193
0
        Ok(MetricPublishKnownKindData::Component)
194
0
    }
195
}