1# Copyright 2013 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import atexit
6import inspect
7import logging
8import os
9
10from collections import defaultdict
11
12
13def GetChildPids(processes, pid):
14  """Returns all child processes of |pid| from the given |processes| list.
15
16  Args:
17    processes: A tuple of (pid, ppid, state) as generated by ps.
18    pid: The pid for which to get children.
19
20  Returns:
21    A list of child pids.
22  """
23  child_dict = defaultdict(list)
24  for curr_pid, curr_ppid, state in processes:
25    if 'Z' in state:
26      continue  # Ignore zombie processes
27    child_dict[int(curr_ppid)].append(int(curr_pid))
28  queue = [pid]
29  child_ids = []
30  while queue:
31    parent = queue.pop()
32    if parent in child_dict:
33      children = child_dict[parent]
34      queue.extend(children)
35      child_ids.extend(children)
36  return child_ids
37
38
39def GetPsOutputWithPlatformBackend(platform_backend, columns, pid):
40  """Returns output of the 'ps' command as a list of lines.
41
42  Args:
43    platform_backend: The platform backend (LinuxBasedPlatformBackend or
44        PosixPlatformBackend).
45    columns: A list of require columns, e.g., ['pid', 'pss'].
46    pid: If not None, returns only the information of the process with the pid.
47  """
48  args = ['ps']
49  args.extend(['-p', str(pid)] if pid != None else ['-e'])
50  for c in columns:
51    args.extend(['-o', c + '='])
52  return platform_backend.RunCommand(args).splitlines()
53
54
55def EnableListingStrayProcessesUponExitHook():
56  def _ListAllSubprocesses():
57    try:
58      import psutil
59    except ImportError:
60      logging.warning(
61          'psutil is not installed on the system. Not listing possible '
62          'leaked processes. To install psutil, see: '
63          'https://pypi.python.org/pypi/psutil')
64      return
65    telemetry_pid = os.getpid()
66    parent = psutil.Process(telemetry_pid)
67    if hasattr(parent, 'children'):
68      children = parent.children(recursive=True)
69    else:  # Some old version of psutil use get_children instead children.
70      children = parent.get_children()
71    if children:
72      leak_processes_info = []
73      for p in children:
74        if inspect.ismethod(p.name):
75          name = p.name()
76        else:  # Process.name is a property in old versions of psutil.
77          name = p.name
78        process_info = '%s (%s)' % (name, p.pid)
79        try:
80          if inspect.ismethod(p.cmdline):
81            cmdline = p.cmdline()
82          else:
83            cmdline = p.cmdline
84          process_info += ' - %s' % cmdline
85        except Exception as e:
86          logging.warning(str(e))
87        leak_processes_info.append(process_info)
88      logging.error('Telemetry leaks these processes: %s',
89                    ', '.join(leak_processes_info))
90
91  atexit.register(_ListAllSubprocesses)
92