blob: 84f8bdfe359e4f79292849136af89b18dd481a38 [file] [log] [blame]
Mike Frysingere58c0e22017-10-04 15:43:30 -04001# -*- coding: utf-8 -*-
Allen Li51bb6122017-06-21 12:04:13 -07002# Copyright 2017 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Process metrics."""
7
8from __future__ import absolute_import
9from __future__ import print_function
10
Allen Li3992c662018-01-05 15:26:36 -080011from functools import partial
12
Allen Li51bb6122017-06-21 12:04:13 -070013import psutil
14
15from chromite.lib import cros_logging as logging
Allen Lia9c6e802017-07-11 15:42:47 -070016from chromite.lib import metrics
Allen Li51bb6122017-06-21 12:04:13 -070017
18logger = logging.getLogger(__name__)
19
Allen Lia9c6e802017-07-11 15:42:47 -070020_count_metric = metrics.GaugeMetric(
Allen Li6bb74d52017-06-22 14:44:53 -070021 'proc/count',
Allen Li51bb6122017-06-21 12:04:13 -070022 description='Number of processes currently running.')
Allen Lia9c6e802017-07-11 15:42:47 -070023_cpu_percent_metric = metrics.GaugeMetric(
Allen Li6bb74d52017-06-22 14:44:53 -070024 'proc/cpu_percent',
25 description='CPU usage percent of processes.')
Allen Li51bb6122017-06-21 12:04:13 -070026
27
28def collect_proc_info():
Allen Li6bb74d52017-06-22 14:44:53 -070029 collector = _ProcessMetricsCollector()
30 collector.collect()
31
32
33class _ProcessMetricsCollector(object):
34 """Class for collecting process metrics."""
35
36 def __init__(self):
37 self._metrics = [
Allen Li22989bd2017-07-12 10:34:37 -070038 _ProcessMetric('autoserv',
39 test_func=_is_parent_autoserv),
40 _ProcessMetric('sysmon',
Allen Li3992c662018-01-05 15:26:36 -080041 test_func=partial(_is_python_module,
42 'chromite.scripts.sysmon')),
43 _ProcessMetric('job_aborter',
44 test_func=partial(_is_python_module,
45 'lucifer.cmd.job_aborter')),
46 _ProcessMetric('job_reporter',
47 test_func=partial(_is_python_module,
48 'lucifer.cmd.job_reporter')),
49 _ProcessMetric('lucifer_run_job',
Prathmesh Prabhu0b795f02018-05-07 13:12:37 -070050 test_func=partial(_is_process_name, 'lucifer_run_job')),
Allen Li22989bd2017-07-12 10:34:37 -070051 _ProcessMetric('apache',
Prathmesh Prabhu0b795f02018-05-07 13:12:37 -070052 test_func=partial(_is_process_name, 'apache2')),
Prathmesh Prabhu5ed6f902018-05-07 14:13:02 -070053 _ProcessMetric('lxc-start',
54 test_func=partial(_is_process_name, 'lxc-start')),
55 _ProcessMetric('lxc-attach',
56 test_func=partial(_is_process_name, 'lxc-attach')),
Allen Li6bb74d52017-06-22 14:44:53 -070057 ]
58 self._other_metric = _ProcessMetric('other')
59
60 def collect(self):
61 for proc in psutil.process_iter():
62 self._collect_proc(proc)
63 self._flush()
64
65 def _collect_proc(self, proc):
Allen Li6bb74d52017-06-22 14:44:53 -070066 for metric in self._metrics:
Allen Lif8397a82017-07-13 13:19:44 -070067 if metric.add(proc):
68 break
69 else:
Allen Li6bb74d52017-06-22 14:44:53 -070070 self._other_metric.add(proc)
71
72 def _flush(self):
73 for metric in self._metrics:
74 metric.flush()
75 self._other_metric.flush()
76
77
78class _ProcessMetric(object):
79 """Class for gathering process metrics."""
80
81 def __init__(self, process_name, test_func=lambda proc: True):
82 """Initialize instance.
83
84 process_name is used to identify the metric stream.
85
86 test_func is a function called
87 for each process. If it returns True, the process is counted. The
88 default test is to count every process.
89 """
90 self._fields = {
Allen Li22989bd2017-07-12 10:34:37 -070091 'process_name': process_name,
Allen Li6bb74d52017-06-22 14:44:53 -070092 }
93 self._test_func = test_func
94 self._count = 0
95 self._cpu_percent = 0
96
97 def add(self, proc):
98 """Do metric collection for the given process.
99
100 Returns True if the process was collected.
101 """
102 if not self._test_func(proc):
103 return False
104 self._count += 1
105 self._cpu_percent += proc.cpu_percent()
106 return True
107
108 def flush(self):
109 """Finish collection and send metrics."""
110 _count_metric.set(self._count, fields=self._fields)
111 self._count = 0
Aviv Keshet0b634e92017-07-14 14:29:48 -0700112 _cpu_percent_metric.set(int(round(self._cpu_percent)), fields=self._fields)
Allen Li6bb74d52017-06-22 14:44:53 -0700113 self._cpu_percent = 0
Allen Li51bb6122017-06-21 12:04:13 -0700114
115
116def _is_parent_autoserv(proc):
117 """Return whether proc is a parent (not forked) autoserv process."""
118 return _is_autoserv(proc) and not _is_autoserv(proc.parent())
119
120
121def _is_autoserv(proc):
122 """Return whether proc is an autoserv process."""
123 # This relies on the autoserv script being run directly. The script should
124 # be named autoserv exactly and start with a shebang that is /usr/bin/python,
125 # NOT /bin/env
Prathmesh Prabhu0b795f02018-05-07 13:12:37 -0700126 return _is_process_name('autoserv', proc)
Allen Li51bb6122017-06-21 12:04:13 -0700127
128
Allen Li3992c662018-01-05 15:26:36 -0800129def _is_python_module(module, proc):
130 """Return whether proc is a process running a Python module."""
Aviv Keshet70a91c52017-07-17 16:09:09 -0700131 cmdline = proc.cmdline()
132 return (cmdline and
133 cmdline[0].endswith('python') and
Allen Li3992c662018-01-05 15:26:36 -0800134 cmdline[1:3] == ['-m', module])
135
136
Prathmesh Prabhu0b795f02018-05-07 13:12:37 -0700137def _is_process_name(name, proc):
138 """Return whether process proc is named name."""
139 return proc.name() == name