1import os
2from threading import Lock
3import warnings
4
5from .mmap_dict import mmap_key, MmapedDict
6
7
8class MutexValue:
9 """A float protected by a mutex."""
10
11 _multiprocess = False
12
13 def __init__(self, typ, metric_name, name, labelnames, labelvalues, help_text, **kwargs):
14 self._value = 0.0
15 self._exemplar = None
16 self._lock = Lock()
17
18 def inc(self, amount):
19 with self._lock:
20 self._value += amount
21
22 def set(self, value, timestamp=None):
23 with self._lock:
24 self._value = value
25
26 def set_exemplar(self, exemplar):
27 with self._lock:
28 self._exemplar = exemplar
29
30 def get(self):
31 with self._lock:
32 return self._value
33
34 def get_exemplar(self):
35 with self._lock:
36 return self._exemplar
37
38
39def MultiProcessValue(process_identifier=os.getpid):
40 """Returns a MmapedValue class based on a process_identifier function.
41
42 The 'process_identifier' function MUST comply with this simple rule:
43 when called in simultaneously running processes it MUST return distinct values.
44
45 Using a different function than the default 'os.getpid' is at your own risk.
46 """
47 files = {}
48 values = []
49 pid = {'value': process_identifier()}
50 # Use a single global lock when in multi-processing mode
51 # as we presume this means there is no threading going on.
52 # This avoids the need to also have mutexes in __MmapDict.
53 lock = Lock()
54
55 class MmapedValue:
56 """A float protected by a mutex backed by a per-process mmaped file."""
57
58 _multiprocess = True
59
60 def __init__(self, typ, metric_name, name, labelnames, labelvalues, help_text, multiprocess_mode='', **kwargs):
61 self._params = typ, metric_name, name, labelnames, labelvalues, help_text, multiprocess_mode
62 # This deprecation warning can go away in a few releases when removing the compatibility
63 if 'prometheus_multiproc_dir' in os.environ and 'PROMETHEUS_MULTIPROC_DIR' not in os.environ:
64 os.environ['PROMETHEUS_MULTIPROC_DIR'] = os.environ['prometheus_multiproc_dir']
65 warnings.warn("prometheus_multiproc_dir variable has been deprecated in favor of the upper case naming PROMETHEUS_MULTIPROC_DIR", DeprecationWarning)
66 with lock:
67 self.__check_for_pid_change()
68 self.__reset()
69 values.append(self)
70
71 def __reset(self):
72 typ, metric_name, name, labelnames, labelvalues, help_text, multiprocess_mode = self._params
73 if typ == 'gauge':
74 file_prefix = typ + '_' + multiprocess_mode
75 else:
76 file_prefix = typ
77 if file_prefix not in files:
78 filename = os.path.join(
79 os.environ.get('PROMETHEUS_MULTIPROC_DIR'),
80 '{}_{}.db'.format(file_prefix, pid['value']))
81
82 files[file_prefix] = MmapedDict(filename)
83 self._file = files[file_prefix]
84 self._key = mmap_key(metric_name, name, labelnames, labelvalues, help_text)
85 self._value, self._timestamp = self._file.read_value(self._key)
86
87 def __check_for_pid_change(self):
88 actual_pid = process_identifier()
89 if pid['value'] != actual_pid:
90 pid['value'] = actual_pid
91 # There has been a fork(), reset all the values.
92 for f in files.values():
93 f.close()
94 files.clear()
95 for value in values:
96 value.__reset()
97
98 def inc(self, amount):
99 with lock:
100 self.__check_for_pid_change()
101 self._value += amount
102 self._timestamp = 0.0
103 self._file.write_value(self._key, self._value, self._timestamp)
104
105 def set(self, value, timestamp=None):
106 with lock:
107 self.__check_for_pid_change()
108 self._value = value
109 self._timestamp = timestamp or 0.0
110 self._file.write_value(self._key, self._value, self._timestamp)
111
112 def set_exemplar(self, exemplar):
113 # TODO: Implement exemplars for multiprocess mode.
114 return
115
116 def get(self):
117 with lock:
118 self.__check_for_pid_change()
119 return self._value
120
121 def get_exemplar(self):
122 # TODO: Implement exemplars for multiprocess mode.
123 return None
124
125 return MmapedValue
126
127
128def get_value_class():
129 # Should we enable multi-process mode?
130 # This needs to be chosen before the first metric is constructed,
131 # and as that may be in some arbitrary library the user/admin has
132 # no control over we use an environment variable.
133 if 'prometheus_multiproc_dir' in os.environ or 'PROMETHEUS_MULTIPROC_DIR' in os.environ:
134 return MultiProcessValue()
135 else:
136 return MutexValue
137
138
139ValueClass = get_value_class()