# Copyright 2013 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

from telemetry.internal.util import atexit_with_log
import inspect
import logging
import os

from collections import defaultdict


def GetChildPids(processes, pid):
  """Returns all child processes of |pid| from the given |processes| list.

  Args:
    processes: A tuple of (pid, ppid, state) as generated by ps.
    pid: The pid for which to get children.

  Returns:
    A list of child pids.
  """
  child_dict = defaultdict(list)
  for curr_pid, curr_ppid, state in processes:
    if 'Z' in state:
      continue  # Ignore zombie processes
    child_dict[int(curr_ppid)].append(int(curr_pid))
  queue = [pid]
  child_ids = []
  while queue:
    parent = queue.pop()
    if parent in child_dict:
      children = child_dict[parent]
      queue.extend(children)
      child_ids.extend(children)
  return child_ids


def GetPsOutputWithPlatformBackend(platform_backend, columns, pid):
  """Returns output of the 'ps' command as a list of lines.

  Args:
    platform_backend: The platform backend (LinuxBasedPlatformBackend or
        PosixPlatformBackend).
    columns: A list of require columns, e.g., ['pid', 'pss'].
    pid: If not None, returns only the information of the process with the pid.
  """
  args = ['ps']
  args.extend(['-p', str(pid)] if pid != None else ['-e'])
  for c in columns:
    args.extend(['-o', c + '='])
  return platform_backend.RunCommand(args).splitlines()


def EnableListingStrayProcessesUponExitHook():
  def _ListAllSubprocesses():
    try:
      import psutil
    except ImportError:
      logging.warning(
          'psutil is not installed on the system. Not listing possible '
          'leaked processes. To install psutil, see: '
          'https://pypi.python.org/pypi/psutil')
      return
    telemetry_pid = os.getpid()
    parent = psutil.Process(telemetry_pid)
    if hasattr(parent, 'children'):
      children = parent.children(recursive=True)
    else:  # Some old version of psutil use get_children instead children.
      children = parent.get_children()
    if children:
      leak_processes_info = []
      for p in children:
        if inspect.ismethod(p.name):
          name = p.name()
        else:  # Process.name is a property in old versions of psutil.
          name = p.name
        process_info = '%s (%s)' % (name, p.pid)
        try:
          if inspect.ismethod(p.cmdline):
            cmdline = p.cmdline()
          else:
            cmdline = p.cmdline
          process_info += ' - %s' % cmdline
        except Exception as e:
          logging.warning(str(e))
        leak_processes_info.append(process_info)
      logging.warning('Telemetry leaks these processes: %s',
                      ', '.join(leak_processes_info))

  atexit_with_log.Register(_ListAllSubprocesses)
