@@ -89,9 +89,9 @@ def send_http_get(
8989) -> dict :
9090 """ Prepare HTTP post reqest to be sent to docker socket, evluate HTTP response """
9191
92- cmd : str = (f'GET { endpoint } HTTP/1.1\r \n '
93- f'Host: { host } \r \n '
94- f'User-Agent: { useragent } \r \n '
92+ cmd : str = (f'GET { endpoint } HTTP/1.1\r \n '
93+ f'Host: { host } \r \n '
94+ f'User-Agent: { useragent } \r \n '
9595 f'Accept: application/json\r \n '
9696 f'Connection: close\r \n \r \n ' )
9797
@@ -122,9 +122,9 @@ def send_http_get(
122122 response ["http_response" ]: dict = json .loads (buf_lines [- 1 ])
123123
124124 if response ["http_status" ] != 200 :
125- exit_plugin (2 , (f'Daemon API v{ response ["http_header_api-version" ] } returned HTTP '
126- f'{ response ["http_status" ] } while fetching { endpoint } : '
127- f'{ response ["http_response" ] } ' ), '' )
125+ exit_plugin (2 , (f'Daemon API v{ response ["http_header_api-version" ]} returned HTTP '
126+ f'{ response ["http_status" ]} while fetching { endpoint } : '
127+ f'{ response ["http_response" ]} ' ), '' )
128128
129129 return response
130130
@@ -167,19 +167,25 @@ def send_socket_cmd(cmd: str, socketfile: str) -> str:
167167 buf += data .decode ()
168168
169169 # Shut down sending
170- sock .shutdown (socket .SHUT_WR )
170+ try :
171+ sock .shutdown (socket .SHUT_WR )
172+ except OSError :
173+ # catch "OSError: [Errno 57] Socket is not connected" on MacOS
174+ # Apparently Docker on MacOS shuts down the socket connection at this point already due to our
175+ # "Connection: close" header
176+ pass
171177
172178 # Close socket connection
173179 sock .close ()
174180
175181 except FileNotFoundError :
176- exit_plugin (3 , f'Socket file { socketfile } not found!' , "" )
182+ exit_plugin (3 , f'Socket file { socketfile } not found!' , "" )
177183 except PermissionError :
178- exit_plugin (3 , f'Access to socket file { socketfile } denied!' , "" )
184+ exit_plugin (3 , f'Access to socket file { socketfile } denied!' , "" )
179185 except (TimeoutError , socket .timeout ):
180- exit_plugin (3 , f'Connection to socket { socketfile } timed out!' , "" )
186+ exit_plugin (3 , f'Connection to socket { socketfile } timed out!' , "" )
181187 except ConnectionError as err :
182- exit_plugin (3 , f'Error during socket connection: { err } ' , "" )
188+ exit_plugin (3 , f'Error during socket connection: { err } ' , "" )
183189
184190 return buf
185191
@@ -238,15 +244,15 @@ def set_state(newstate: int, state: int) -> int:
238244def convert_bytes_to_pretty (raw_bytes : int ) -> str :
239245 """ converts raw bytes into human readable output """
240246 if raw_bytes >= 1099511627776 :
241- output = f'{ round (raw_bytes / 1024 ** 4 , 2 ) } TiB'
247+ output = f'{ round (raw_bytes / 1024 ** 4 , 2 )} TiB'
242248 elif raw_bytes >= 1073741824 :
243- output = f'{ round (raw_bytes / 1024 ** 3 , 2 ) } GiB'
249+ output = f'{ round (raw_bytes / 1024 ** 3 , 2 )} GiB'
244250 elif raw_bytes >= 1048576 :
245- output = f'{ round (raw_bytes / 1024 ** 2 , 2 ) } MiB'
251+ output = f'{ round (raw_bytes / 1024 ** 2 , 2 )} MiB'
246252 elif raw_bytes >= 1024 :
247- output = f'{ round (raw_bytes / 1024 , 2 ) } KiB'
253+ output = f'{ round (raw_bytes / 1024 , 2 )} KiB'
248254 elif raw_bytes < 1024 :
249- output = f'{ raw_bytes } B'
255+ output = f'{ raw_bytes } B'
250256 else :
251257 # Theoretically impossible, prevent pylint possibly-used-before-assignment
252258 raise ValueError ('Impossible value in convert_bytes_to_pretty()' )
@@ -258,25 +264,25 @@ def get_container_from_name(args: Arguments) -> dict:
258264
259265 # Query all containers that match the given name from /containers/json
260266 containers : dict = send_http_get (
261- f'/v1.45 /containers/json?all=true&filters={{"name":["{ args .container_name } "]}}' ,
267+ f'/v1.51 /containers/json?all=true&filters={{"name":["{ args .container_name } "]}}' ,
262268 socketfile = args .socket
263269 )
264270
265271 if len (containers ["http_response" ]) == 0 :
266- exit_plugin (2 , f'No container matched name { args .container_name } ' , '' )
272+ exit_plugin (2 , f'No container matched name { args .container_name } ' , '' )
267273 elif args .wildcard is True and len (containers ["http_response" ]) > 1 :
268- exit_plugin (2 , f'Multiple containers matched wildcard name { args .container_name } ' , '' )
274+ exit_plugin (2 , f'Multiple containers matched wildcard name { args .container_name } ' , '' )
269275
270276 if args .wildcard is True :
271277 # We previously checked than wildcard name matched only one container - so this must be it
272278 container_info : dict = containers ["http_response" ][0 ]
273279 else :
274280 # loop over returned containers and check for match
275281 for cnt in containers ["http_response" ]:
276- if f'/{ args .container_name } ' in cnt ["Names" ]:
282+ if f'/{ args .container_name } ' in cnt ["Names" ]:
277283 container_info : dict = cnt
278284 if 'container_info' not in locals ():
279- exit_plugin (2 , f'No container matched name { args .container_name } ' , '' )
285+ exit_plugin (2 , f'No container matched name { args .container_name } ' , '' )
280286
281287 return container_info
282288
@@ -289,13 +295,13 @@ def calc_container_metrics(info: dict, stats: dict) -> dict:
289295
290296 try :
291297 # Extract container name and id from api response
292- container .update ({"name" : f'{ info ["Names" ][0 ][1 :] } ' })
293- container .update ({"id" : f'{ info ["Id" ][:12 ] } ' })
294- container .update ({"id_long" : f'{ info ["Id" ] } ' })
298+ container .update ({"name" : f'{ info ["Names" ][0 ][1 :]} ' })
299+ container .update ({"id" : f'{ info ["Id" ][:12 ]} ' })
300+ container .update ({"id_long" : f'{ info ["Id" ]} ' })
295301
296302 # Get container state
297- container .update ({"state" : f'{ info ["State" ] } ' })
298- container .update ({"status" : f'{ info ["Status" ] } ' })
303+ container .update ({"state" : f'{ info ["State" ]} ' })
304+ container .update ({"status" : f'{ info ["Status" ]} ' })
299305
300306 # Get process statistics
301307 container .update ({"pid_count" : stats ["pids_stats" ].get ("current" , 0 )})
@@ -353,7 +359,7 @@ def calc_container_metrics(info: dict, stats: dict) -> dict:
353359 container .update ({"blk_io" : {"r" : blkio_r , "w" : blkio_w }})
354360
355361 except KeyError as err :
356- exit_plugin (2 , f'Error while extracting values from JSON response: { err } ' , "" )
362+ exit_plugin (2 , f'Error while extracting values from JSON response: { err } ' , "" )
357363
358364 return container
359365
@@ -367,45 +373,45 @@ def main():
367373 # Get daemon API version
368374 server_version : dict = send_http_get ('/version' , socketfile = args .socket )["http_response" ]
369375
370- # Check of daemon is compatible with API v1.45
371- if tuple (server_version ["MinAPIVersion" ].split ('.' )) > ("1" , "45 " ):
372- exit_plugin (2 , (f'This plugin requires a docker daemon supporting API version 1.45 - '
376+ # Check of daemon is compatible with API v1.51
377+ if tuple (server_version ["MinAPIVersion" ].split ('.' )) >= ("1" , "51 " ):
378+ exit_plugin (2 , (f'This plugin requires a docker daemon supporting API version 1.51 - '
373379 f'Minimum supported version of this docker daemon is '
374- f'{ server_version ["MinAPIVersion" ] } ' ), '' )
380+ f'{ server_version ["MinAPIVersion" ]} ' ), '' )
375381
376382 # Get container id for name from /containers/json
377383 # Returns Container JSON object as dict
378384 container_info : dict = get_container_from_name (args )
379385
380386 # Check if container is running, if not we can exit early without perfdata
381387 if container_info ["State" ] != "running" :
382- exit_plugin (2 , f'Container { container_info ["Names" ][0 ][1 :] } is { container_info ["Status" ] } ' , '' )
388+ exit_plugin (2 , f'Container { container_info ["Names" ][0 ][1 :]} is { container_info ["Status" ]} ' , '' )
383389
384390 # Get container stats
385391 container_stats : dict = send_http_get (
386- f'/v1.45 /containers/{ container_info ["Id" ] } /stats?stream=false&one-shot=false' ,
392+ f'/v1.51 /containers/{ container_info ["Id" ]} /stats?stream=false&one-shot=false' ,
387393 socketfile = args .socket
388394 )["http_response" ]
389395
390396 # Hand raw API responses over to calc_container_metrics to extract attributes and derive metrics
391397 container : dict = calc_container_metrics (container_info , container_stats )
392398
393399 # Construct perfdata and output
394- output = (f"{ container ['name' ] } ({ container ['id' ] } ) is { container ['status' ] } - "
395- f"CPU: { container ['cpu_pct' ] } %, "
396- f"Memory: { convert_bytes_to_pretty (container ['memory' ]['used' ]) } , "
400+ output = (f"{ container ['name' ]} ({ container ['id' ]} ) is { container ['status' ]} - "
401+ f"CPU: { container ['cpu_pct' ]} %, "
402+ f"Memory: { convert_bytes_to_pretty (container ['memory' ]['used' ])} , "
397403 f"PIDs: { container ['pid_count' ]} " )
398404
399- perfdata = (f" | cpu={ container ['cpu_pct' ] } %;{ args .cpuwarn or '' } ;"
400- f"{ args .cpucrit or '' } ;; "
401- f"pids={ container ['pid_count' ] } ;{ args .pidwarn or '' } ;"
402- f"{ args .pidcrit or '' } ;0;{ container ['pid_limit' ] } "
405+ perfdata = (f" | cpu={ container ['cpu_pct' ]} %;{ args .cpuwarn or '' } ;"
406+ f"{ args .cpucrit or '' } ;; "
407+ f"pids={ container ['pid_count' ]} ;{ args .pidwarn or '' } ;"
408+ f"{ args .pidcrit or '' } ;0;{ container ['pid_limit' ]} "
403409 f"mem={ container ['memory' ]['used' ]} B;{ args .memwarn or '' } ;"
404- f"{ args .memcrit or '' } ;0;{ container ['memory' ]['available' ] } "
405- f"net_send={ container ['net_io' ]['tx' ] } B;;;; "
406- f"net_recv={ container ['net_io' ]['rx' ] } B;;;; "
407- f"disk_read={ container ['blk_io' ]['r' ] } B;;;; "
408- f"disk_write={ container ['blk_io' ]['w' ] } B;;;; " )
410+ f"{ args .memcrit or '' } ;0;{ container ['memory' ]['available' ]} "
411+ f"net_send={ container ['net_io' ]['tx' ]} B;;;; "
412+ f"net_recv={ container ['net_io' ]['rx' ]} B;;;; "
413+ f"disk_read={ container ['blk_io' ]['r' ]} B;;;; "
414+ f"disk_write={ container ['blk_io' ]['w' ]} B;;;; " )
409415
410416 # Set initial return code
411417 returncode = 0
0 commit comments