@@ -149,15 +149,24 @@ def __init__(self, influx_client: InfluxClient, config_file: Dict[str, Any], ver
149149
150150 # SERVER
151151 # add server later due multiple processes
152- self .__ps_grep_list = ["mongod" , "beam.smp" , "java" ] # be aware this is double declared below
153- for grep_name in self .__ps_grep_list :
152+ self .__process_grep_list = ["mongod" , "beam.smp" , "java" ] # be aware this is double declared below
153+ for grep_name in self .__process_grep_list :
154154 self .__client_commands [SshTypes .SERVER ].append (
155155 SshCommand (
156156 command = f"ps -o \" %cpu,%mem,comm,rss,vsz,user,pid,etimes\" -p $(pgrep -d',' -f { grep_name } ) S -ww" ,
157157 parse_function = self ._parse_ps_cmd ,
158158 table_name = "processStats"
159159 )
160160 )
161+ # Top commands for CPU Only
162+ for grep_name in self .__process_grep_list :
163+ self .__client_commands [SshTypes .SERVER ].append (
164+ SshCommand (
165+ command = f"top -bs -w 512 -n1 -p $(pgrep -d',' -f { grep_name } )" ,
166+ parse_function = self ._parse_top_cmd ,
167+ table_name = "processStats"
168+ )
169+ )
161170
162171 # ################ END OF SSH COMMAND LIST GROUPS ############################
163172
@@ -221,6 +230,74 @@ def ssh(self) -> None:
221230 ExceptionUtils .exception_info (
222231 error = error , extra_message = f"Top-level-error when excecuting { ssh_type .value } ssh commands, skipping them all" )
223232
233+ def _parse_top_cmd (self , ssh_command : SshCommand , ssh_type : SshTypes ) -> Tuple [str , List [Dict [str , Any ]]]:
234+ """Parses the result of the `top` command, splitting it into its parts.
235+
236+ Arguments:
237+ ssh_command {SshCommand} -- command with saved result
238+ ssh_type {SshTypes} -- type of the client
239+ Raises:
240+ ValueError: no command given or no result saved
241+ ValueError: no ssh type given
242+ Returns:
243+ Tuple[str, List[Dict[str, Any]]] -- Tuple of the tablename and a insert list
244+ """
245+
246+ if (not ssh_command or not ssh_command .result ):
247+ raise ValueError ("no command given or empty result" )
248+ if (not ssh_type ):
249+ raise ValueError ("no sshtype given" )
250+ if (not ssh_command .table_name ):
251+ raise ValueError ("need table name to insert parsed value" )
252+
253+ result_lines = ssh_command .result .splitlines ()
254+ header = result_lines [6 ].split ()
255+
256+ values : List [Dict [str , Any ]] = list (
257+ map (lambda row : dict (zip (header , row .split ())), result_lines [7 :])) # type: ignore
258+
259+ # All lines above (header) 5 are pruned, not used anymore. This data is tracked via ps (see Issue #71)
260+
261+ time_pattern = re .compile (r"(\d+):(\d{2})(?:\.(\d{2}))?" )
262+
263+ # remove `top` from commands, it is also tracked
264+ values = list (filter (lambda row : row ["COMMAND" ] in self .__process_grep_list , values ))
265+
266+ for row in values :
267+ # Delete Memory, this is tracked by ps command (See Issue #71)
268+ row .pop ("VIRT" , None )
269+ row .pop ("RES" , None )
270+ row .pop ("SHR" , None )
271+ row .pop ("%MEM" , None )
272+ # Add information
273+ row ["collectionType" ] = "TOP"
274+
275+ # unused information
276+ row .pop ("PR" , None )
277+ row .pop ("NI" , None )
278+ row .pop ("S" , None )
279+
280+
281+ # set default needed fields
282+ row ['hostName' ] = ssh_command .host_name
283+ row ['ssh_type' ] = ssh_type .name
284+ (time_key , time_value ) = SppUtils .get_capture_timestamp_sec ()
285+ row [time_key ] = time_value
286+
287+ # split time into seconds
288+ match = re .match (time_pattern , row ['TIME+' ])
289+ if (match ):
290+ time_list = match .groups ()
291+ (hours , minutes , seconds ) = time_list
292+ if (seconds is None ):
293+ seconds = 0
294+ time = int (hours )* pow (60 , 2 ) + int (minutes )* pow (60 , 1 ) + int (seconds )* pow (60 , 0 )
295+ else :
296+ time = None
297+ row ['TIME+' ] = time
298+
299+ return (ssh_command .table_name , values )
300+
224301 def _parse_ps_cmd (self , ssh_command : SshCommand , ssh_type : SshTypes ) -> Tuple [str , List [Dict [str , Any ]]]:
225302 """Parses the result of the `df` command, splitting it into its parts.
226303
@@ -246,10 +323,15 @@ def _parse_ps_cmd(self, ssh_command: SshCommand, ssh_type: SshTypes) -> Tuple[st
246323 values : List [Dict [str , Any ]] = list (
247324 map (lambda row : dict (zip (header , row .split ())), result_lines [1 :])) # type: ignore
248325
249- # remove top statistic itself to avoid spam with useless information
250- values = list (filter (lambda row : row ["COMMAND" ] in self .__ps_grep_list , values ))
326+ # remove `ps` from commands, it is also tracked
327+ values = list (filter (lambda row : row ["COMMAND" ] in self .__process_grep_list , values ))
251328
252329 for row in values :
330+ # Remove CPU, it is tracked by TOP-Command (see Issue #71)
331+ row .pop ("%CPU" , None )
332+ # Add information
333+ row ["collectionType" ] = "PS"
334+
253335 # set default needed fields
254336 row ['hostName' ] = ssh_command .host_name
255337 row ['ssh_type' ] = ssh_type .name
0 commit comments