1import os
2from typing import Callable, Iterable, Optional, Union
3
4from .metrics_core import CounterMetricFamily, GaugeMetricFamily, Metric
5from .registry import Collector, CollectorRegistry, REGISTRY
6
7try:
8 import resource
9
10 _PAGESIZE = resource.getpagesize()
11except ImportError:
12 # Not Unix
13 _PAGESIZE = 4096
14
15
16class ProcessCollector(Collector):
17 """Collector for Standard Exports such as cpu and memory."""
18
19 def __init__(self,
20 namespace: str = '',
21 pid: Callable[[], Union[int, str]] = lambda: 'self',
22 proc: str = '/proc',
23 registry: Optional[CollectorRegistry] = REGISTRY):
24 self._namespace = namespace
25 self._pid = pid
26 self._proc = proc
27 if namespace:
28 self._prefix = namespace + '_process_'
29 else:
30 self._prefix = 'process_'
31 self._ticks = 100.0
32 try:
33 self._ticks = os.sysconf('SC_CLK_TCK')
34 except (ValueError, TypeError, AttributeError, OSError):
35 pass
36
37 self._pagesize = _PAGESIZE
38
39 # This is used to test if we can access /proc.
40 self._btime = 0
41 try:
42 self._btime = self._boot_time()
43 except OSError:
44 pass
45 if registry:
46 registry.register(self)
47
48 def _boot_time(self):
49 with open(os.path.join(self._proc, 'stat'), 'rb') as stat:
50 for line in stat:
51 if line.startswith(b'btime '):
52 return float(line.split()[1])
53
54 def collect(self) -> Iterable[Metric]:
55 if not self._btime:
56 return []
57
58 pid = os.path.join(self._proc, str(self._pid()).strip())
59
60 result = []
61 try:
62 with open(os.path.join(pid, 'stat'), 'rb') as stat:
63 parts = (stat.read().split(b')')[-1].split())
64
65 vmem = GaugeMetricFamily(self._prefix + 'virtual_memory_bytes',
66 'Virtual memory size in bytes.', value=float(parts[20]))
67 rss = GaugeMetricFamily(self._prefix + 'resident_memory_bytes', 'Resident memory size in bytes.',
68 value=float(parts[21]) * self._pagesize)
69 start_time_secs = float(parts[19]) / self._ticks
70 start_time = GaugeMetricFamily(self._prefix + 'start_time_seconds',
71 'Start time of the process since unix epoch in seconds.',
72 value=start_time_secs + self._btime)
73 utime = float(parts[11]) / self._ticks
74 stime = float(parts[12]) / self._ticks
75 cpu = CounterMetricFamily(self._prefix + 'cpu_seconds_total',
76 'Total user and system CPU time spent in seconds.',
77 value=utime + stime)
78 result.extend([vmem, rss, start_time, cpu])
79 except OSError:
80 pass
81
82 try:
83 with open(os.path.join(pid, 'limits'), 'rb') as limits:
84 for line in limits:
85 if line.startswith(b'Max open file'):
86 max_fds = GaugeMetricFamily(self._prefix + 'max_fds',
87 'Maximum number of open file descriptors.',
88 value=float(line.split()[3]))
89 break
90 open_fds = GaugeMetricFamily(self._prefix + 'open_fds',
91 'Number of open file descriptors.',
92 len(os.listdir(os.path.join(pid, 'fd'))))
93 result.extend([open_fds, max_fds])
94 except OSError:
95 pass
96
97 return result
98
99
100PROCESS_COLLECTOR = ProcessCollector()
101"""Default ProcessCollector in default Registry REGISTRY."""