tobiko/tobiko/shell/sh/_ps.py

154 lines
4.8 KiB
Python

# Copyright (c) 2020 Red Hat, Inc.
#
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from __future__ import absolute_import
import collections
import re
import time
import tobiko
from tobiko.shell.sh import _execute
from tobiko.shell.sh import _hostname
class PsError(tobiko.TobikoException):
message = "Unable to list processes from host: {error}"
class PsWaitTimeout(PsError):
message = ("Process(es) still running on host {hostname!r} after "
"{timeout} seconds:\n{processes!s}")
IS_KERNEL_RE = re.compile('^\\[.*\\]$')
class PsProcess(collections.namedtuple('PsProcess', ['ssh_client',
'pid',
'command'])):
"""Process listed by ps command
"""
@property
def is_kernel(self):
return IS_KERNEL_RE.match(self.command) is not None
def list_kernel_processes(**list_params):
return list_processes(is_kernel=True, **list_params)
def list_all_processes(**list_params):
return list_processes(is_kernel=None, **list_params)
def list_processes(pid=None, command=None, is_kernel=False, ssh_client=None,
**execute_params):
"""Returns the number of seconds passed since last host reboot
It reads and parses remote special file /proc/uptime and returns a floating
point value that represents the number of seconds passed since last host
reboot
"""
result = _execute.execute('ps -A', expect_exit_status=None,
ssh_client=ssh_client, **execute_params)
output = result.stdout and result.stdout.strip()
if result.exit_status or not output:
raise PsError(error=result.stderr)
# Extract a list of PsProcess instances from table body
processes = tobiko.Selection()
for process_data in parse_table(lines=output.splitlines(),
schema=PS_TABLE_SCHEMA):
processes.append(PsProcess(ssh_client=ssh_client, **process_data))
if processes and pid:
# filter processes by PID
pid = int(pid)
assert pid > 0
processes = processes.with_attributes(pid=pid)
if processes and command is not None:
# filter processes by command
command = re.compile(command)
processes = tobiko.select(process
for process in processes
if command.match(process.command))
if processes and is_kernel is not None:
# filter kernel processes
processes = processes.with_attributes(is_kernel=bool(is_kernel))
return processes
def wait_for_processes(timeout=float('inf'), sleep_interval=5.,
ssh_client=None, **list_params):
start_time = time.time()
time_left = timeout
while True:
processes = list_processes(timeout=time_left,
ssh_client=ssh_client,
**list_params)
if not processes:
break
time_left = timeout - (time.time() - start_time)
if time_left < sleep_interval:
hostname = _hostname.get_hostname(ssh_client=ssh_client)
process_lines = [
' {pid} {command}'.format(pid=process.pid,
command=process.command)
for process in processes]
raise PsWaitTimeout(timeout=timeout, hostname=hostname,
processes='\n'.join(process_lines))
time.sleep(sleep_interval)
def parse_pid(value):
return 'pid', int(value)
def parse_command(value):
return 'command', str(value)
PS_TABLE_SCHEMA = {
'pid': parse_pid,
'cmd': parse_command,
'command': parse_command,
}
def parse_table(lines, schema, header_line=None):
lines = iter(lines)
while not header_line:
header_line = next(lines)
getters = []
column_names = header_line.strip().lower().split()
for position, name in enumerate(column_names):
getter = schema.get(name)
if getter:
getters.append((position, getter))
for line in lines:
row = line.strip().split()
if row:
yield dict(getter(row[position])
for position, getter in getters)