@@ -20,6 +20,8 @@ pub enum Error {
20
20
ParseFloat ( #[ from] std:: num:: ParseFloatError ) ,
21
21
#[ error( "Cgroup v2 not found" ) ]
22
22
CgroupV2NotFound ,
23
+ #[ error( "Parsing PSI error: {0}" ) ]
24
+ ParsingPsi ( String ) ,
23
25
}
24
26
25
27
/// Determines the cgroup v2 path for a given PID.
@@ -70,8 +72,21 @@ pub(crate) async fn poll(file_path: &Path, labels: &[(String, String)]) -> Resul
70
72
71
73
match fs:: read_to_string ( & file_path) . await {
72
74
Ok ( content) => {
73
- let content = content. trim ( ) ;
75
+ if file_name == "memory.pressure"
76
+ || file_name == "io.pressure"
77
+ || file_name == "cpu.pressure"
78
+ {
79
+ if let Err ( err) =
80
+ parse_pressure ( & content, & metric_prefix, labels)
81
+ {
82
+ warn ! ( "[{path}] Failed to parse PSI contents: {err:?}" ,
83
+ path = file_path. to_string_lossy( )
84
+ ) ;
85
+ }
86
+ continue ;
87
+ }
74
88
89
+ let content = content. trim ( ) ;
75
90
// The format of cgroupv2 interface
76
91
// files is defined here:
77
92
// https://docs.kernel.org/admin-guide/cgroup-v2.html#interface-files
@@ -171,3 +186,132 @@ fn kv_pairs(
171
186
}
172
187
Ok ( ( ) )
173
188
}
189
+
190
+ fn parse_pressure ( content : & str , prefix : & str , labels : & [ ( String , String ) ] ) -> Result < ( ) , Error > {
191
+ for line in content. lines ( ) {
192
+ parse_pressure_line ( line, prefix, |metric : String , value : f64 | {
193
+ gauge ! ( metric, labels) . set ( value) ;
194
+ } ) ?;
195
+ }
196
+ Ok ( ( ) )
197
+ }
198
+
199
+ fn parse_pressure_line < F > ( line : & str , prefix : & str , mut f : F ) -> Result < ( ) , Error >
200
+ where
201
+ F : FnMut ( String , f64 ) ,
202
+ {
203
+ // [some|full] avg10=FLOAT avg60=FLOAT avg300=FLOAT total=FLOAT
204
+ let mut parts = line. split_whitespace ( ) ;
205
+ if let Some ( category) = parts. next ( ) {
206
+ for field in parts {
207
+ let Some ( ( key, val) ) = field. split_once ( '=' ) else {
208
+ return Err ( Error :: ParsingPsi ( format ! ( "Invalid psi field: {field}" ) ) ) ;
209
+ } ;
210
+ // It might be that total is an integer but for the sake of
211
+ // simplicity we'll parse as f64. It has to become a float anyway
212
+ // when we write it out as a metric.
213
+ let value = val
214
+ . parse :: < f64 > ( )
215
+ . map_err ( |err| Error :: ParsingPsi ( format ! ( "{val} -> {err}" ) ) ) ?;
216
+
217
+ let metric_name = format ! ( "{prefix}.{category}.{key}" ) ;
218
+ f ( metric_name, value) ;
219
+ }
220
+ } else {
221
+ warn ! ( "Unexpected blank category in psi file, skipping line: {line}" ) ;
222
+ }
223
+ Ok ( ( ) )
224
+ }
225
+
226
+ #[ cfg( test) ]
227
+ mod tests {
228
+ use super :: parse_pressure_line;
229
+
230
+ #[ test]
231
+ fn parse_pressure_line_multiple_fields ( ) {
232
+ let line = "some avg10=0.42 avg60=1.0 total=42" ;
233
+ let prefix = "cgroup.v2.memory.pressure" ;
234
+
235
+ let mut results = Vec :: new ( ) ;
236
+ let res = parse_pressure_line ( line, prefix, |metric, value| {
237
+ results. push ( ( metric, value) ) ;
238
+ } ) ;
239
+
240
+ assert ! ( res. is_ok( ) ) ;
241
+ assert_eq ! ( results. len( ) , 3 ) ;
242
+
243
+ assert_eq ! (
244
+ results[ 0 ] ,
245
+ ( String :: from( "cgroup.v2.memory.pressure.some.avg10" ) , 0.42 )
246
+ ) ;
247
+ assert_eq ! (
248
+ results[ 1 ] ,
249
+ ( String :: from( "cgroup.v2.memory.pressure.some.avg60" ) , 1.0 )
250
+ ) ;
251
+ assert_eq ! (
252
+ results[ 2 ] ,
253
+ ( String :: from( "cgroup.v2.memory.pressure.some.total" ) , 42.0 )
254
+ ) ;
255
+ }
256
+
257
+ #[ test]
258
+ fn parse_pressure_line_blank_line ( ) {
259
+ let line = "" ;
260
+ let prefix = "cgroup.v2.memory.pressure" ;
261
+
262
+ let mut results = Vec :: new ( ) ;
263
+ let res = parse_pressure_line ( line, prefix, |metric, value| {
264
+ results. push ( ( metric, value) ) ;
265
+ } ) ;
266
+
267
+ assert ! ( res. is_ok( ) ) ;
268
+ assert ! ( results. is_empty( ) ) ;
269
+ }
270
+
271
+ #[ test]
272
+ fn parse_pressure_line_incomplete ( ) {
273
+ let line = "some" ;
274
+ let prefix = "cgroup.v2.memory.pressure" ;
275
+
276
+ let mut results = Vec :: new ( ) ;
277
+ let res = parse_pressure_line ( line, prefix, |metric, value| {
278
+ results. push ( ( metric, value) ) ;
279
+ } ) ;
280
+
281
+ assert ! ( res. is_ok( ) ) ;
282
+ assert ! ( results. is_empty( ) ) ;
283
+ }
284
+
285
+ #[ test]
286
+ fn parse_pressure_line_malformed_field ( ) {
287
+ let line = "some avg10=0.0 avg60?" ;
288
+ let prefix = "cgroup.v2.memory.pressure" ;
289
+
290
+ let mut results = Vec :: new ( ) ;
291
+ let res = parse_pressure_line ( line, prefix, |metric, value| {
292
+ results. push ( ( metric, value) ) ;
293
+ } ) ;
294
+
295
+ // Intentionally grab as many fields as possible
296
+ assert ! ( res. is_err( ) ) ;
297
+ assert_eq ! ( results. len( ) , 1 ) ;
298
+ assert_eq ! (
299
+ results[ 0 ] ,
300
+ ( String :: from( "cgroup.v2.memory.pressure.some.avg10" ) , 0.0 )
301
+ ) ;
302
+ }
303
+
304
+ #[ test]
305
+ fn parse_pressure_line_invalid_value ( ) {
306
+ let line = "some avg10=hello" ;
307
+ let prefix = "cgroup.v2.memory.pressure" ;
308
+
309
+ let mut results = Vec :: new ( ) ;
310
+ let res = parse_pressure_line ( line, prefix, |metric, value| {
311
+ results. push ( ( metric, value) ) ;
312
+ } ) ;
313
+
314
+ assert ! ( res. is_err( ) ) ;
315
+ assert ! ( results. is_empty( ) ) ;
316
+ }
317
+ }
0 commit comments