44import re
55import subprocess
66from datetime import datetime , timezone
7+ from ipaddress import ip_address
78from typing import Any , Dict , List , Set , Tuple , Union
89
910import yaml
@@ -36,20 +37,24 @@ def _get_stat_type(suffix: str, unit: str) -> str:
3637 return 'gauge'
3738
3839
39- def _handle_ip_in_param (parts : List [str ]) -> List [str ]:
40+ def _handle_ip_in_param (parts : List [str ]) -> Tuple [ List [str ], bool ]:
4041 """
4142 Merge parameter parts corresponding to an IP address.
4243
4344 Example:
4445 ['some','172','0','0','12@tcp','param']
4546 => ['some','172.0.0.12@tcp', 'param']
4647 """
47- match_index = 0
48- for i , part in enumerate (parts ):
49- if '@' in part :
50- match_index = i
51- new_part = "." .join (parts [match_index - 3 : match_index + 1 ])
52- return [* parts [: match_index - 3 ], new_part , * parts [match_index + 1 :]]
48+ match_indexes = [i for i in range (len (parts )) if '@' in parts [i ]]
49+ if len (match_indexes ) != 1 or match_indexes [0 ] < 3 :
50+ return [], False
51+ index = match_indexes [0 ]
52+ new_part = "." .join (parts [index - 3 : index + 1 ])
53+ try :
54+ ip_address (new_part .split ('@' )[0 ])
55+ except ValueError :
56+ return [], False
57+ return [* parts [: index - 3 ], new_part , * parts [index + 1 :]], True
5358
5459
5560class LustreCheck (AgentCheck ):
@@ -160,6 +165,7 @@ def _update_filesystems(self) -> None:
160165 filesystem = match .group (0 )
161166 filesystems .append (filesystem )
162167 self .filesystems = list (set (filesystems )) # Remove duplicates
168+ assert self .filesystems , f'Nothing matched regex `{ filesystem_regex } ` in params { lines } '
163169 self .log .debug ('Found filesystem(s): %s' , self .filesystems )
164170 except Exception as e :
165171 self .log .error ('Failed to find filesystems: %s' , e )
@@ -194,6 +200,11 @@ def _run_command(self, bin: str, *args: str, sudo: bool = False) -> str:
194200 try :
195201 self .log .debug ('Running command: %s' , cmd )
196202 output = subprocess .run (cmd , timeout = 5 , shell = True , capture_output = True , text = True )
203+ if not output .returncode == 0 and output .stderr :
204+ self .log .debug (
205+ 'Command %s exited with returncode %s. Captured stderr: %s' , cmd , output .returncode , output .stderr
206+ )
207+ return ''
197208 if output .stdout is None :
198209 self .log .debug (
199210 'Command %s returned no output, check if dd-agent is running\
@@ -218,12 +229,12 @@ def submit_jobstats_metrics(self, filesystems: List[str]) -> None:
218229 device_name = jobstats_param .split ('.' )[1 ] # For example: lustre-MDT0000
219230 if not any (device_name .startswith (fs ) for fs in filesystems ):
220231 continue
221- jobstats_metrics = self ._get_jobstats_metrics (jobstats_param )[ 'job_stats' ]
232+ jobstats_metrics = self ._get_jobstats_metrics (jobstats_param ). get ( 'job_stats' )
222233 if jobstats_metrics is None :
223234 self .log .debug ('No jobstats metrics found for %s' , jobstats_param )
224235 continue
225236 for job in jobstats_metrics :
226- job_id = job [ 'job_id' ]
237+ job_id = job . get ( 'job_id' , "unknown" )
227238 tags = self .tags + [f'device_name:{ device_name } ' , f'job_id:{ job_id } ' ]
228239 for metric_name , metric_values in job .items ():
229240 if not isinstance (metric_values , dict ):
@@ -265,15 +276,19 @@ def _get_jobstats_metrics(self, jobstats_param: str) -> Dict[str, Any]:
265276 jobstats_output = self ._run_command ('lctl' , 'get_param' , '-ny' , jobstats_param , sudo = True )
266277 try :
267278 return yaml .safe_load (jobstats_output ) or {}
268- except KeyError :
269- self .log .debug ('No jobstats metrics found for %s ' , jobstats_param )
279+ except Exception as e :
280+ self .log .debug ('Could not get data for "%s", caught exception: %s ' , jobstats_param , e )
270281 return {}
271282
272283 def submit_lnet_stats_metrics (self ) -> None :
273284 '''
274285 Submit the lnet stats metrics.
275286 '''
276- lnet_metrics = self ._get_lnet_metrics ('stats' )['statistics' ]
287+ lnet_metrics = self ._get_lnet_metrics ('stats' )
288+ if 'statistics' not in lnet_metrics :
289+ self .log .debug ('Could not find `statistics` property in the output of lnet stats. Output: %s' , lnet_metrics )
290+ return
291+ lnet_metrics = lnet_metrics ['statistics' ]
277292 for metric in lnet_metrics :
278293 if metric .endswith ('_count' ) or metric == 'errors' :
279294 metric_type = 'count'
@@ -285,7 +300,11 @@ def submit_lnet_local_ni_metrics(self) -> None:
285300 '''
286301 Submit the lnet local ni metrics.
287302 '''
288- lnet_local_stats = self ._get_lnet_metrics ('net' )['net' ]
303+ lnet_local_stats = self ._get_lnet_metrics ('net' )
304+ if 'net' not in lnet_local_stats :
305+ self .log .debug ('Could not find `net` property in the output of lnet stats. Output: %s' , lnet_local_stats )
306+ return
307+ lnet_local_stats = lnet_local_stats ['net' ]
289308 for net in lnet_local_stats :
290309 net_type = net .get ('net type' )
291310 for ni in net .get ('local NI(s)' , []):
@@ -302,7 +321,11 @@ def submit_lnet_peer_ni_metrics(self) -> None:
302321 '''
303322 Submit the lnet peer ni metrics.
304323 '''
305- lnet_peer_stats = self ._get_lnet_metrics ('peer' )['peer' ]
324+ lnet_peer_stats = self ._get_lnet_metrics ('peer' )
325+ if 'peer' not in lnet_peer_stats :
326+ self .log .debug ('Could not find `peer` property in the output of lnet stats. Output: %s' , lnet_peer_stats )
327+ return
328+ lnet_peer_stats = lnet_peer_stats ['peer' ]
306329 for peer in lnet_peer_stats :
307330 nid = peer .get ('primary nid' )
308331 for ni in peer .get ('peer ni' , []):
@@ -347,8 +370,8 @@ def _get_lnet_metrics(self, stats_type: str = 'stats') -> Dict[str, Any]:
347370 lnet_stats = self ._run_command ('lnetctl' , stats_type , 'show' , '-v' , self .lnetctl_verbosity , sudo = True )
348371 try :
349372 return yaml .safe_load (lnet_stats ) or {}
350- except ( KeyError , ValueError ) :
351- self .log .debug ('No lnet stats found' )
373+ except Exception as e :
374+ self .log .debug ('Could not get lnet %s, caught exception: %s' , stats_type , e )
352375 return {}
353376
354377 def submit_param_data (self , params : Set [LustreParam ], filesystems : List [str ]) -> None :
@@ -400,32 +423,36 @@ def _submit_stat(self, prefix: str, name: str, value: Dict[str, Any], tags: List
400423 else :
401424 self .log .debug ('Unexpected metric value for %s.%s: %s' , name , suffix , metric_value )
402425
403- def _extract_tags_from_param (self , param_regex : str , param_name : str , wildcards : Tuple [str ]) -> List [str ]:
426+ def _extract_tags_from_param (self , param_regex : str , param_name : str , wildcards : Tuple [str , ... ]) -> List [str ]:
404427 '''
405428 Extract tags from the parameter name based on the regex and wildcard meanings.
406429 '''
430+ if not wildcards :
431+ return []
407432 tags = []
408- if wildcards :
409- regex_parts = param_regex .split ('.' )
410- param_parts = param_name .split ('.' )
411- wildcard_number = 0
412- if not len (regex_parts ) == len (param_parts ):
413- # Edge case: mdt.lustre-MDT0000.exports.172.31.16.218@tcp.stats
414- if any ("@" in part for part in param_parts ):
415- # We need to reconstruct the address
416- param_parts = _handle_ip_in_param (param_parts )
417- else :
418- self .log .debug ('Parameter name %s does not match regex %s' , param_name , param_regex )
433+ regex_parts = param_regex .split ('.' )
434+ param_parts = param_name .split ('.' )
435+ wildcard_number = 0
436+ if not len (regex_parts ) == len (param_parts ):
437+ # Edge case: mdt.lustre-MDT0000.exports.172.31.16.218@tcp.stats
438+ if len (regex_parts ) + 3 == len (param_parts ):
439+ # We need to reconstruct the address
440+ param_parts , is_valid_ip = _handle_ip_in_param (param_parts )
441+ if not is_valid_ip :
442+ self .log .debug ("Skipping tags for parameter %s" , param_name )
443+ return []
444+ else :
445+ self .log .debug ('Parameter name %s does not match regex %s' , param_name , param_regex )
446+ return tags
447+ for part_number , part in enumerate (regex_parts ):
448+ if part == '*' :
449+ if wildcard_number >= len (wildcards ):
450+ self .log .debug (
451+ 'Found %s wildcards, which exceeds available wildcard tags %s' , wildcard_number , wildcards
452+ )
419453 return tags
420- for part_number , part in enumerate (regex_parts ):
421- if part == '*' :
422- if wildcard_number >= len (wildcards ):
423- self .log .debug (
424- 'Found %s wildcards, which exceeds available wildcard tags %s' , wildcard_number , wildcards
425- )
426- return tags
427- tags .append (f'{ wildcards [wildcard_number ]} :{ param_parts [part_number ]} ' )
428- wildcard_number += 1
454+ tags .append (f'{ wildcards [wildcard_number ]} :{ param_parts [part_number ]} ' )
455+ wildcard_number += 1
429456 return tags
430457
431458 def _parse_stats (self , raw_stats : str ) -> Dict [str , Dict [str , Union [int , str ]]]:
0 commit comments