blob: 7c7875e19e301d993ac49850110009c88bbc6833 [file] [log] [blame]
Allen Li51bb6122017-06-21 12:04:13 -07001# Copyright 2017 The Chromium OS 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
5"""Process metrics."""
6
7from __future__ import absolute_import
Allen Li51bb6122017-06-21 12:04:13 -07008
Allen Li3992c662018-01-05 15:26:36 -08009from functools import partial
10
Mike Frysingercb56b642019-08-25 15:33:08 -040011import psutil # pylint: disable=import-error
Allen Li51bb6122017-06-21 12:04:13 -070012
13from chromite.lib import cros_logging as logging
Allen Lia9c6e802017-07-11 15:42:47 -070014from chromite.lib import metrics
Allen Li51bb6122017-06-21 12:04:13 -070015
16logger = logging.getLogger(__name__)
17
Allen Lia9c6e802017-07-11 15:42:47 -070018_count_metric = metrics.GaugeMetric(
Allen Li6bb74d52017-06-22 14:44:53 -070019 'proc/count',
Allen Li51bb6122017-06-21 12:04:13 -070020 description='Number of processes currently running.')
Allen Lia9c6e802017-07-11 15:42:47 -070021_cpu_percent_metric = metrics.GaugeMetric(
Allen Li6bb74d52017-06-22 14:44:53 -070022 'proc/cpu_percent',
23 description='CPU usage percent of processes.')
Allen Li51bb6122017-06-21 12:04:13 -070024
25
26def collect_proc_info():
Allen Li6bb74d52017-06-22 14:44:53 -070027 collector = _ProcessMetricsCollector()
28 collector.collect()
29
30
31class _ProcessMetricsCollector(object):
32 """Class for collecting process metrics."""
33
34 def __init__(self):
35 self._metrics = [
Allen Li3511a832018-06-27 14:41:01 -070036 _ProcessMetric('apache',
37 test_func=partial(_is_process_name, 'apache2')),
Allen Li22989bd2017-07-12 10:34:37 -070038 _ProcessMetric('autoserv',
39 test_func=_is_parent_autoserv),
Aviv Keshet98f33792019-10-29 11:05:53 -070040 _ProcessMetric('getty',
41 test_func=partial(_is_process_name, 'getty')),
Allen Li3511a832018-06-27 14:41:01 -070042 _ProcessMetric('gs_offloader',
43 test_func=_is_gs_offloader),
Allen Li3992c662018-01-05 15:26:36 -080044 _ProcessMetric('job_aborter',
45 test_func=partial(_is_python_module,
46 'lucifer.cmd.job_aborter')),
47 _ProcessMetric('job_reporter',
48 test_func=partial(_is_python_module,
49 'lucifer.cmd.job_reporter')),
Allen Liee3f5c42018-08-27 18:07:06 -070050 _ProcessMetric('lucifer',
51 test_func=partial(_is_process_name, 'lucifer')),
Prathmesh Prabhu5ed6f902018-05-07 14:13:02 -070052 _ProcessMetric('lxc-start',
53 test_func=partial(_is_process_name, 'lxc-start')),
54 _ProcessMetric('lxc-attach',
55 test_func=partial(_is_process_name, 'lxc-attach')),
Allen Li3511a832018-06-27 14:41:01 -070056 _ProcessMetric('sysmon',
57 test_func=partial(_is_python_module,
58 'chromite.scripts.sysmon')),
Allen Li6bb74d52017-06-22 14:44:53 -070059 ]
60 self._other_metric = _ProcessMetric('other')
61
62 def collect(self):
63 for proc in psutil.process_iter():
64 self._collect_proc(proc)
65 self._flush()
66
67 def _collect_proc(self, proc):
Allen Li6bb74d52017-06-22 14:44:53 -070068 for metric in self._metrics:
Allen Lif8397a82017-07-13 13:19:44 -070069 if metric.add(proc):
70 break
71 else:
Allen Li6bb74d52017-06-22 14:44:53 -070072 self._other_metric.add(proc)
73
74 def _flush(self):
75 for metric in self._metrics:
76 metric.flush()
77 self._other_metric.flush()
78
79
80class _ProcessMetric(object):
81 """Class for gathering process metrics."""
82
83 def __init__(self, process_name, test_func=lambda proc: True):
84 """Initialize instance.
85
86 process_name is used to identify the metric stream.
87
88 test_func is a function called
89 for each process. If it returns True, the process is counted. The
90 default test is to count every process.
91 """
92 self._fields = {
Allen Li22989bd2017-07-12 10:34:37 -070093 'process_name': process_name,
Allen Li6bb74d52017-06-22 14:44:53 -070094 }
95 self._test_func = test_func
96 self._count = 0
97 self._cpu_percent = 0
98
99 def add(self, proc):
100 """Do metric collection for the given process.
101
102 Returns True if the process was collected.
103 """
104 if not self._test_func(proc):
105 return False
106 self._count += 1
107 self._cpu_percent += proc.cpu_percent()
108 return True
109
110 def flush(self):
111 """Finish collection and send metrics."""
112 _count_metric.set(self._count, fields=self._fields)
113 self._count = 0
Aviv Keshet0b634e92017-07-14 14:29:48 -0700114 _cpu_percent_metric.set(int(round(self._cpu_percent)), fields=self._fields)
Allen Li6bb74d52017-06-22 14:44:53 -0700115 self._cpu_percent = 0
Allen Li51bb6122017-06-21 12:04:13 -0700116
117
118def _is_parent_autoserv(proc):
119 """Return whether proc is a parent (not forked) autoserv process."""
120 return _is_autoserv(proc) and not _is_autoserv(proc.parent())
121
122
123def _is_autoserv(proc):
124 """Return whether proc is an autoserv process."""
125 # This relies on the autoserv script being run directly. The script should
126 # be named autoserv exactly and start with a shebang that is /usr/bin/python,
127 # NOT /bin/env
Prathmesh Prabhu0b795f02018-05-07 13:12:37 -0700128 return _is_process_name('autoserv', proc)
Allen Li51bb6122017-06-21 12:04:13 -0700129
130
Allen Li3511a832018-06-27 14:41:01 -0700131def _is_gs_offloader(proc):
132 """Return whether proc is a gs_offloader process."""
133 cmdline = proc.cmdline()
134 return (len(cmdline) >= 2
135 and cmdline[0].endswith('python')
136 and cmdline[1].endswith('gs_offloader.py'))
137
138
Allen Li3992c662018-01-05 15:26:36 -0800139def _is_python_module(module, proc):
140 """Return whether proc is a process running a Python module."""
Aviv Keshet70a91c52017-07-17 16:09:09 -0700141 cmdline = proc.cmdline()
142 return (cmdline and
143 cmdline[0].endswith('python') and
Allen Li3992c662018-01-05 15:26:36 -0800144 cmdline[1:3] == ['-m', module])
145
146
Prathmesh Prabhu0b795f02018-05-07 13:12:37 -0700147def _is_process_name(name, proc):
148 """Return whether process proc is named name."""
149 return proc.name() == name