prometheus-dcache-exporter 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- prometheus_dcache_exporter-0.1.0/LICENCE +13 -0
- prometheus_dcache_exporter-0.1.0/MANIFEST.in +2 -0
- prometheus_dcache_exporter-0.1.0/PKG-INFO +32 -0
- prometheus_dcache_exporter-0.1.0/pyproject.toml +99 -0
- prometheus_dcache_exporter-0.1.0/setup.cfg +4 -0
- prometheus_dcache_exporter-0.1.0/src/prometheus_dcache_exporter/__init__.py +13 -0
- prometheus_dcache_exporter-0.1.0/src/prometheus_dcache_exporter/__main__.py +437 -0
- prometheus_dcache_exporter-0.1.0/src/prometheus_dcache_exporter/_collector.py +638 -0
- prometheus_dcache_exporter-0.1.0/src/prometheus_dcache_exporter/_config.py +92 -0
- prometheus_dcache_exporter-0.1.0/src/prometheus_dcache_exporter/_dcache/__init__.py +38 -0
- prometheus_dcache_exporter-0.1.0/src/prometheus_dcache_exporter/_dcache/admin.py +199 -0
- prometheus_dcache_exporter-0.1.0/src/prometheus_dcache_exporter/_dcache/frontend.py +187 -0
- prometheus_dcache_exporter-0.1.0/src/prometheus_dcache_exporter/_util.py +56 -0
- prometheus_dcache_exporter-0.1.0/src/prometheus_dcache_exporter.egg-info/PKG-INFO +32 -0
- prometheus_dcache_exporter-0.1.0/src/prometheus_dcache_exporter.egg-info/SOURCES.txt +17 -0
- prometheus_dcache_exporter-0.1.0/src/prometheus_dcache_exporter.egg-info/dependency_links.txt +1 -0
- prometheus_dcache_exporter-0.1.0/src/prometheus_dcache_exporter.egg-info/entry_points.txt +2 -0
- prometheus_dcache_exporter-0.1.0/src/prometheus_dcache_exporter.egg-info/requires.txt +13 -0
- prometheus_dcache_exporter-0.1.0/src/prometheus_dcache_exporter.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Copyright © 2026 Christoph Anton Mitterer <mail@christoph.anton.mitterer.name>
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
This program is free software: you can redistribute it and/or modify it under
|
|
5
|
+
the terms of the GNU General Public License as published by the Free Software
|
|
6
|
+
Foundation, either version 3 of the License, or (at your option) any later
|
|
7
|
+
version.
|
|
8
|
+
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
9
|
+
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
|
10
|
+
PARTICULAR PURPOSE.
|
|
11
|
+
See the GNU General Public License for more details.
|
|
12
|
+
You should have received a copy of the GNU General Public License along with
|
|
13
|
+
this program. If not, see <https://www.gnu.org/licenses/>.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: prometheus-dcache-exporter
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Prometheus exporter for dCache.
|
|
5
|
+
Author-email: Christoph Anton Mitterer <mail@christoph.anton.mitterer.name>
|
|
6
|
+
Maintainer-email: Christoph Anton Mitterer <mail@christoph.anton.mitterer.name>
|
|
7
|
+
Project-URL: website, https://gitlab.com/calestyo/prometheus-dcache-exporter
|
|
8
|
+
Keywords: monitoring,prometheus,prometheus exporter,dcache
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Environment :: No Input/Output (Daemon)
|
|
12
|
+
Classifier: Environment :: Plugins
|
|
13
|
+
Classifier: Intended Audience :: Information Technology
|
|
14
|
+
Classifier: Intended Audience :: System Administrators
|
|
15
|
+
Classifier: Natural Language :: English
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Topic :: System :: Monitoring
|
|
18
|
+
Classifier: Topic :: System :: Networking :: Monitoring
|
|
19
|
+
Requires-Python: >=3.13
|
|
20
|
+
License-File: LICENCE
|
|
21
|
+
Requires-Dist: certifi
|
|
22
|
+
Requires-Dist: httpx
|
|
23
|
+
Requires-Dist: humanfriendly
|
|
24
|
+
Requires-Dist: paramiko
|
|
25
|
+
Requires-Dist: prometheus-client
|
|
26
|
+
Requires-Dist: python-jsonpath
|
|
27
|
+
Requires-Dist: python-module-utils
|
|
28
|
+
Provides-Extra: extended-logging-formatter
|
|
29
|
+
Requires-Dist: python-logging-extras; extra == "extended-logging-formatter"
|
|
30
|
+
Provides-Extra: rich-text
|
|
31
|
+
Requires-Dist: rich; extra == "rich-text"
|
|
32
|
+
Dynamic: license-file
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "prometheus-dcache-exporter"
|
|
3
|
+
|
|
4
|
+
description = "A Prometheus exporter for dCache."
|
|
5
|
+
keywords = [
|
|
6
|
+
"monitoring", "prometheus", "prometheus exporter",
|
|
7
|
+
"dcache"
|
|
8
|
+
]
|
|
9
|
+
classifiers = [
|
|
10
|
+
"Development Status :: 4 - Beta",
|
|
11
|
+
"Environment :: Console",
|
|
12
|
+
"Environment :: No Input/Output (Daemon)",
|
|
13
|
+
"Environment :: Plugins",
|
|
14
|
+
"Intended Audience :: Information Technology",
|
|
15
|
+
"Intended Audience :: System Administrators",
|
|
16
|
+
"Natural Language :: English",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Topic :: System :: Monitoring",
|
|
19
|
+
"Topic :: System :: Networking :: Monitoring"
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
authors = [
|
|
23
|
+
{name = "Christoph Anton Mitterer", email = "mail@christoph.anton.mitterer.name"}
|
|
24
|
+
]
|
|
25
|
+
maintainers = [
|
|
26
|
+
{name = "Christoph Anton Mitterer", email = "mail@christoph.anton.mitterer.name"}
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
license-files = ["LICENCE"]
|
|
30
|
+
|
|
31
|
+
requires-python = ">=3.13"
|
|
32
|
+
dependencies = [
|
|
33
|
+
"certifi",
|
|
34
|
+
"httpx",
|
|
35
|
+
"humanfriendly",
|
|
36
|
+
"paramiko",
|
|
37
|
+
"prometheus-client",
|
|
38
|
+
"python-jsonpath",
|
|
39
|
+
"python-module-utils"
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
dynamic = ["version"]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
[project.urls]
|
|
46
|
+
website = "https://gitlab.com/calestyo/prometheus-dcache-exporter"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
[project.optional-dependencies]
|
|
50
|
+
extended-logging-formatter = ["python-logging-extras"]
|
|
51
|
+
rich-text = ["rich"]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
[project.scripts]
|
|
55
|
+
prometheus-dcache-exporter = "prometheus_dcache_exporter.__main__:main"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
[build-system]
|
|
61
|
+
requires = ["setuptools>=77", "setuptools-scm>=8"]
|
|
62
|
+
build-backend = "setuptools.build_meta"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
[tool.setuptools]
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
[tool.setuptools_scm]
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
#Copyright © 2026 Christoph Anton Mitterer <mail@christoph.anton.mitterer.name>
|
|
88
|
+
#
|
|
89
|
+
#
|
|
90
|
+
#This program is free software: you can redistribute it and/or modify it under
|
|
91
|
+
#the terms of the GNU General Public License as published by the Free Software
|
|
92
|
+
#Foundation, either version 3 of the License, or (at your option) any later
|
|
93
|
+
#version.
|
|
94
|
+
#This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
95
|
+
#WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
|
96
|
+
#PARTICULAR PURPOSE.
|
|
97
|
+
#See the GNU General Public License for more details.
|
|
98
|
+
#You should have received a copy of the GNU General Public License along with
|
|
99
|
+
#this program. If not, see <https://www.gnu.org/licenses/>.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#Copyright © 2026 Christoph Anton Mitterer <mail@christoph.anton.mitterer.name>
|
|
2
|
+
#
|
|
3
|
+
#
|
|
4
|
+
#This program is free software: you can redistribute it and/or modify it under
|
|
5
|
+
#the terms of the GNU General Public License as published by the Free Software
|
|
6
|
+
#Foundation, either version 3 of the License, or (at your option) any later
|
|
7
|
+
#version.
|
|
8
|
+
#This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
9
|
+
#WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
|
10
|
+
#PARTICULAR PURPOSE.
|
|
11
|
+
#See the GNU General Public License for more details.
|
|
12
|
+
#You should have received a copy of the GNU General Public License along with
|
|
13
|
+
#this program. If not, see <https://www.gnu.org/licenses/>.
|
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
from module_utils import optional_import, optional_from_import
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import sys
|
|
5
|
+
import os
|
|
6
|
+
import argparse
|
|
7
|
+
import enum
|
|
8
|
+
import signal
|
|
9
|
+
import locale
|
|
10
|
+
import logging
|
|
11
|
+
import contextlib
|
|
12
|
+
from warnings import warn
|
|
13
|
+
|
|
14
|
+
import prometheus_client
|
|
15
|
+
(rich, HAVE_RICH_CONSOLE) = optional_import("rich.console", get_top_level_modules=True)
|
|
16
|
+
(rich.logging, HAVE_RICH_LOGGING) = optional_import("rich.logging")
|
|
17
|
+
(ExtendedLoggingFormatter, HAVE_EXTENDEDLOGGINGFORMATTER) = optional_from_import("logging_extras.formatters", "ExtendedLoggingFormatter")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
from ._config import *
|
|
21
|
+
from ._util import sequence_to_separated_str
|
|
22
|
+
from . import _collector as collector
|
|
23
|
+
from . import _dcache as dcache
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
#*******************************************************************************
|
|
33
|
+
#* Enums *
|
|
34
|
+
#*******************************************************************************
|
|
35
|
+
class ExportMode(enum.StrEnum):
|
|
36
|
+
http = enum.auto()
|
|
37
|
+
stdout = enum.auto()
|
|
38
|
+
file = enum.auto()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
#*******************************************************************************
|
|
44
|
+
#* Global Variables *
|
|
45
|
+
#*******************************************************************************
|
|
46
|
+
log = logging.getLogger(__name__)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
#*******************************************************************************
|
|
52
|
+
#* Signal Handlers *
|
|
53
|
+
#*******************************************************************************
|
|
54
|
+
def exiting_signal_handler(signalnum, frame):
|
|
55
|
+
#prevent exiting while already exiting
|
|
56
|
+
for s in EXITING_SIGNALS:
|
|
57
|
+
signal.signal(s, signal.SIG_IGN)
|
|
58
|
+
|
|
59
|
+
sys.exit(128 + signalnum)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
#*******************************************************************************
|
|
65
|
+
#* Miscellaneous *
|
|
66
|
+
#*******************************************************************************
|
|
67
|
+
class PositiveInt(int):
|
|
68
|
+
def __new__(cls, number):
|
|
69
|
+
integer = int(number)
|
|
70
|
+
|
|
71
|
+
if isinstance(number, str):
|
|
72
|
+
if str(integer) != number:
|
|
73
|
+
raise ValueError(f"`{number}` contains invalid characters (which includes signs, whitespace, redundant leading zeros and non-Latin digits).")
|
|
74
|
+
else:
|
|
75
|
+
if integer != number:
|
|
76
|
+
raise ValueError(f"`{number}` is not an integer.")
|
|
77
|
+
if integer <= 0:
|
|
78
|
+
raise ValueError(f"The integer `{number}` is not positive.")
|
|
79
|
+
|
|
80
|
+
return integer
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class IntOrStr:
|
|
86
|
+
def __new__(cls, obj):
|
|
87
|
+
string = str(obj)
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
integer = int(obj)
|
|
91
|
+
except ValueError:
|
|
92
|
+
return string
|
|
93
|
+
|
|
94
|
+
if str(integer) == string:
|
|
95
|
+
return integer
|
|
96
|
+
else:
|
|
97
|
+
return string
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class FromResourceOrBytesOrStr:
|
|
103
|
+
def __new__(cls, obj):
|
|
104
|
+
if isinstance(obj, str):
|
|
105
|
+
match obj[:1]:
|
|
106
|
+
case "=":
|
|
107
|
+
return obj[1:]
|
|
108
|
+
case "$":
|
|
109
|
+
return os.environb[ obj[1:].encode() ]
|
|
110
|
+
case "<":
|
|
111
|
+
with open(obj[1:], mode="rb") as f:
|
|
112
|
+
return f.read()
|
|
113
|
+
case _:
|
|
114
|
+
return obj
|
|
115
|
+
elif isinstance(obj, bytes):
|
|
116
|
+
return obj
|
|
117
|
+
else:
|
|
118
|
+
return str(obj)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class AddMetricsExtraLabel(argparse.Action):
|
|
124
|
+
def __call__(self, parser, namespace, values, option_string=None):
|
|
125
|
+
metric_name_regular_expression = values[0]
|
|
126
|
+
extra_label_name = values[1]
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
#if necessary, initialise the attribute specified via `argparse.ArgumentParser.add_argument`’s `dest`-parameter
|
|
130
|
+
if not hasattr(namespace, self.dest) or getattr(namespace, self.dest) is None:
|
|
131
|
+
setattr(namespace, self.dest, {})
|
|
132
|
+
|
|
133
|
+
#get the attribute specified via `argparse.ArgumentParser.add_argument`’s `dest`-parameter
|
|
134
|
+
metrics_extra_labels = getattr(namespace, self.dest)
|
|
135
|
+
|
|
136
|
+
#if necessary, initialise the set for the extra label names for the given metric name regular expression
|
|
137
|
+
if metric_name_regular_expression not in metrics_extra_labels:
|
|
138
|
+
metrics_extra_labels[metric_name_regular_expression] = set()
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
#add the given extra label name for the given metric name regular expression
|
|
142
|
+
metrics_extra_labels[metric_name_regular_expression].add(extra_label_name)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def warn_about_unmatched_metric_name_regular_expressions(*, unmatched_metric_name_regular_expressions):
|
|
148
|
+
if len(unmatched_metric_name_regular_expressions) == 1:
|
|
149
|
+
if len(cfg.metrics_extra_labels[ unmatched_metric_name_regular_expressions[0] ]) == 1:
|
|
150
|
+
log.warning(f"The `--metrics.extra-label`-option with the `REGULAR-EXPRESSION`-argument having the value `{unmatched_metric_name_regular_expressions[0]}` is ignored.")
|
|
151
|
+
else:
|
|
152
|
+
log.warning(f"The `--metrics.extra-label`-options with the `REGULAR-EXPRESSION`-argument having the value `{unmatched_metric_name_regular_expressions[0]}` is ignored.")
|
|
153
|
+
else:
|
|
154
|
+
log.warning(f"The `--metrics.extra-label`-options with the `REGULAR-EXPRESSION`-argument having any of the values {sequence_to_separated_str(unmatched_metric_name_regular_expressions, ", ", " and ", element_delimiters="`")} is ignored.")
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
#*******************************************************************************
|
|
160
|
+
#* Main *
|
|
161
|
+
#*******************************************************************************
|
|
162
|
+
def main():
|
|
163
|
+
global cfg
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
#set locale
|
|
167
|
+
locale.setlocale(locale.LC_ALL, "")
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
#set up logging
|
|
171
|
+
logging_root_logger = logging.getLogger()
|
|
172
|
+
|
|
173
|
+
if os.isatty(sys.stderr.fileno()) and HAVE_RICH_LOGGING and HAVE_RICH_CONSOLE:
|
|
174
|
+
logging_handler = rich.logging.RichHandler(console=rich.console.Console(stderr=True), rich_tracebacks=True, tracebacks_code_width=None, keywords=())
|
|
175
|
+
logging.basicConfig(handlers=(logging_handler, ), format="{name}: {message}", datefmt="%c", style="{")
|
|
176
|
+
else:
|
|
177
|
+
if HAVE_EXTENDEDLOGGINGFORMATTER:
|
|
178
|
+
# Beware that this `logging` handler must not be associated with more than a
|
|
179
|
+
# single `logging` logger (which is `logging_root_logger`).
|
|
180
|
+
# See the `dedicated_handler`-parameter of the
|
|
181
|
+
# `ExtendedLoggingFormatter.callbacks.add_logging_handler_to_hook_state,`-
|
|
182
|
+
# callback-function.
|
|
183
|
+
logging_handler = logging.StreamHandler()
|
|
184
|
+
|
|
185
|
+
# Beware that this `logging` formatter must not be associated with more than a
|
|
186
|
+
# single `logging` handler (which is `logging_handler`).
|
|
187
|
+
# See the `dedicated_handler`-parameter of the
|
|
188
|
+
# `ExtendedLoggingFormatter.callbacks.add_logging_handler_to_hook_state,`-
|
|
189
|
+
# callback-function.
|
|
190
|
+
logging_formatter = ExtendedLoggingFormatter(fmt="{asctime}: {levelname}: {name}: {message_lineno}{message}",
|
|
191
|
+
exc_fmt="{asctime}: DEBUG: {name}: {exc_lineno}{exc}",
|
|
192
|
+
stack_fmt="{asctime}: DEBUG: {name}: {stack_lineno}{stack}",
|
|
193
|
+
datefmt="%c", style="{")
|
|
194
|
+
logging_formatter.registerCallback(ExtendedLoggingFormatter.Hook.format_begin, ExtendedLoggingFormatter.callbacks.add_logging_logger_to_hook_state)
|
|
195
|
+
logging_formatter.registerCallback(ExtendedLoggingFormatter.Hook.format_begin, ExtendedLoggingFormatter.callbacks.add_logging_handler_to_hook_state, dedicated_handler=logging_handler)
|
|
196
|
+
logging_formatter.registerCallback(ExtendedLoggingFormatter.Hook.format_begin, ExtendedLoggingFormatter.callbacks.clear_record_parts_by_extra_level, exc_level=logging.DEBUG, stack_level=logging.DEBUG)
|
|
197
|
+
|
|
198
|
+
logging_handler.setFormatter(logging_formatter)
|
|
199
|
+
logging_root_logger.addHandler(logging_handler)
|
|
200
|
+
else:
|
|
201
|
+
logging.basicConfig(format="{asctime}: {levelname}: {name}: {message}", datefmt="%c", style="{")
|
|
202
|
+
|
|
203
|
+
logging_root_logger.setLevel(logging.NOTSET)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
#parse command arguments
|
|
207
|
+
parser = argparse.ArgumentParser(allow_abbrev=False)
|
|
208
|
+
parser.add_argument("--dcache-frontend-rest.base-url", type=str, default=DEFAULT_DCACHE_FRONTEND_REST_BASE_URL, dest="dcache_frontend_rest_base_url", help=f"The base URL under which the REST interface of dCache’s `frontend` service can be reached. Defaults to `{DEFAULT_DCACHE_FRONTEND_REST_BASE_URL}` if this option isn’t given.", metavar="URL")
|
|
209
|
+
parser.add_argument("--dcache-frontend-rest.http-auth-type", type=dcache.frontend.HTTPAuthType, choices=[e.value for e in dcache.frontend.HTTPAuthType], default=dcache.frontend.HTTPAuthType.none, dest="dcache_frontend_rest_http_auth_type", help=f"The type of HTTP authentication to be performed. `{dcache.frontend.HTTPAuthType.none}` (which is the default if this option isn’t given) causes no HTTP authentication to be performed. `{dcache.frontend.HTTPAuthType.basic_auth}` causes basic authentication to be used and requires the `--dcache-frontend-rest.http-auth-user`- and `--dcache-frontend-rest.http-auth-passphrase`-options to be given.")
|
|
210
|
+
parser.add_argument("--dcache-frontend-rest.http-auth-user", type=str, dest="dcache_frontend_rest_http_auth_user", help="The user used for HTTP authentication when connecting to the REST interface of dCache’s `frontend` service.", metavar="USERNAME")
|
|
211
|
+
parser.add_argument("--dcache-frontend-rest.http-auth-passphrase", type=FromResourceOrBytesOrStr, dest="dcache_frontend_rest_http_auth_passphrase", help="The passphrase used for HTTP authentication when connecting to the REST interface of dCache’s `frontend` service. If STRING starts with `$`, the passphrase is binarily read from an environment variable with the remainder of STRING as the name. If it starts with `<`, the passphrase is binarily read from a file with the remainder of it as the pathname. If it starts with `=`, the passphrase is the remainder of it.", metavar="STRING")
|
|
212
|
+
parser.add_argument("--dcache-frontend-rest.allow-http-auth-without-tls", action="store_true", dest="dcache_frontend_rest_allow_http_auth_without_tls", help="Allow HTTP authentication when connecting to the REST interface of dCache’s `frontend` service without TLS.")
|
|
213
|
+
parser.add_argument("--dcache-frontend-rest.tls-ca-certificates", type=str, dest="dcache_frontend_rest_tls_ca_certificates", help="The CA certificates that are trusted when connecting to the REST interface of dCache’s `frontend` service via TLS. If this option isn’t given, the system’s default CA certificates are used. If a regular file (or symbolic link to such), it must consist of concatenated CA certificates in PEM format; if a directory (or symbolic link to such), it must follow the well-known layout by OpenSSL. CRLs are ignored and OCSP isn’t performed.", metavar="PATHNAME")
|
|
214
|
+
parser.add_argument("--dcache-frontend-rest.tls-client-auth-certificate", type=str, dest="dcache_frontend_rest_tls_client_auth_certificate", help="The certificate and, unless the `--dcache-frontend-rest.tls-client-auth-private-key`-option is given, private key used for client authentication when connecting to the REST interface of dCache’s `frontend` service via TLS. It must be a regular file (or symbolic link to such) that consists of the certificate, depending on the aforementioned, concatenated with the private key in PEM format.", metavar="PATHNAME")
|
|
215
|
+
parser.add_argument("--dcache-frontend-rest.tls-client-auth-private-key", type=str, dest="dcache_frontend_rest_tls_client_auth_private_key", help="The private key used for client authentication when connecting to the REST interface of dCache’s `frontend` service via TLS. It must be a regular file (or symbolic link to such) that consists of the private key in PEM format.", metavar="PATHNAME")
|
|
216
|
+
parser.add_argument("--dcache-frontend-rest.tls-client-auth-private-key-passphrase", type=FromResourceOrBytesOrStr, dest="dcache_frontend_rest_tls_client_auth_private_key_passphrase", help="The passphrase used for decrypting the private key specified via the `--dcache-frontend-rest.tls-client-auth-certificate`- or `--dcache-frontend-rest.tls-client-auth-private-key`-options. If STRING starts with `$`, the passphrase is binarily read from an environment variable with the remainder of STRING as the name. If it starts with `<`, the passphrase is binarily read from a file with the remainder of it as the pathname. If it starts with `=`, the passphrase is the remainder of it.", metavar="STRING")
|
|
217
|
+
parser.add_argument("--dcache-frontend-rest.connect-timeout", type=float, default=DEFAULT_DCACHE_FRONTEND_REST_CONNECT_TIMEOUT, dest="dcache_frontend_rest_connect_timeout", help=f"The timeout in seconds for establishing the connection to the REST interface of dCache’s `frontend` service, with non-positive decimals disabling it. Defaults to `{DEFAULT_DCACHE_FRONTEND_REST_CONNECT_TIMEOUT}` if this option isn’t given.", metavar="DECIMAL")
|
|
218
|
+
parser.add_argument("--dcache-frontend-rest.read-timeout", type=float, default=DEFAULT_DCACHE_FRONTEND_REST_READ_TIMEOUT, dest="dcache_frontend_rest_read_timeout", help=f"The timeout in seconds for reading a chunk of data from the REST interface of dCache’s `frontend` service, with non-positive decimals disabling it. Defaults to `{DEFAULT_DCACHE_FRONTEND_REST_READ_TIMEOUT}` if this option isn’t given.", metavar="DECIMAL")
|
|
219
|
+
parser.add_argument("--dcache-frontend-rest.pool-timeout", type=float, default=DEFAULT_DCACHE_FRONTEND_REST_POOL_TIMEOUT, dest="dcache_frontend_rest_pool_timeout", help=f"The timeout in seconds for getting a connection from the pool of connections to the REST interface of dCache’s `frontend` service, with non-positive decimals disabling it. Defaults to `{DEFAULT_DCACHE_FRONTEND_REST_POOL_TIMEOUT}` if this option isn’t given.", metavar="DECIMAL")
|
|
220
|
+
parser.add_argument("--dcache-frontend-rest.connection-pool-max-size", type=float, dest="dcache_frontend_rest_connection_pool_max_size", help="The maximum number of connections to the REST interface of dCache’s `frontend` service in the connection pool, with non-positive decimals meaning unlimited. Defaults to the value of the `--dcache-frontend-rest.max-concurrent-requests`-option if this option isn’t given. The maximum number of requests that are actually made concurrently is specified via the `--dcache-frontend-rest.max-concurrent-requests`-option.", metavar="INTEGER")
|
|
221
|
+
parser.add_argument("--dcache-frontend-rest.connection-pool-max-idle-size", type=float, dest="dcache_frontend_rest_connection_pool_max_idle_size", help="The maximum number of idle connections to the REST interface of dCache’s `frontend` service in the connection pool, with negative decimals meaning unlimited and zero meaning none. Defaults to the value of the `--dcache-frontend-rest.connection-pool-max-size`-option if this option isn’t given.", metavar="INTEGER")
|
|
222
|
+
parser.add_argument("--dcache-frontend-rest.connection-pool-max-idle-time", type=float, default=DEFAULT_DCACHE_FRONTEND_REST_CONNECTION_POOL_MAX_IDLE_TIME, dest="dcache_frontend_rest_connection_pool_max_idle_time", help=f"The maximum time in seconds for which an idle connection to the REST interface of dCache’s `frontend` service may be kept open in the connection pool, with negative decimals meaning unlimited and zero meaning instant closure. Defaults to `{DEFAULT_DCACHE_FRONTEND_REST_CONNECTION_POOL_MAX_IDLE_TIME}` if this option isn’t given.", metavar="DECIMAL")
|
|
223
|
+
parser.add_argument("--dcache-frontend-rest.max-concurrent-requests", type=PositiveInt, default=DEFAULT_DCACHE_FRONTEND_REST_MAX_CONCURRENT_REQUESTS, dest="dcache_frontend_rest_max_concurrent_requests", help=f"The maximum number of requests that are concurrently made to the REST interface of dCache’s `frontend` service. Defaults to `{DEFAULT_DCACHE_FRONTEND_REST_MAX_CONCURRENT_REQUESTS}` if this option isn’t given.", metavar="POSITIVE-INTEGER")
|
|
224
|
+
parser.add_argument("--dcache-admin-ssh.host", type=str, default=DEFAULT_DCACHE_ADMIN_SSH_HOST, dest="dcache_admin_ssh_host", help=f"The host under which the SSH interface of dCache’s `admin` service can be reached. Defaults to `{DEFAULT_DCACHE_ADMIN_SSH_HOST}` if this option isn’t given.", metavar="HOSTNAME-OR-ADDRESS")
|
|
225
|
+
parser.add_argument("--dcache-admin-ssh.port", type=int, default=DEFAULT_DCACHE_ADMIN_SSH_PORT, dest="dcache_admin_ssh_port", help=f"The port under which the SSH interface of dCache’s `admin` service can be reached. Defaults to `{DEFAULT_DCACHE_ADMIN_SSH_PORT}` if this option isn’t given.", metavar="PORTNUMBER")
|
|
226
|
+
parser.add_argument("--dcache-admin-ssh.user", type=str, dest="dcache_admin_ssh_user", help="The user used for connecting to the SSH interface of dCache’s `admin` service. Defaults to the name of the user that executes the program.", metavar="USERNAME")
|
|
227
|
+
parser.add_argument("--dcache-admin-ssh.known-hosts", type=str, action="append", default=[], dest="dcache_admin_ssh_known_hosts", help=f"Adds the known hosts file (using OpenSSH’s `ssh_known_hosts` format) to the list of those from which trusted SSH host keys are read. The list is processed after that of any existing system known hosts files ({sequence_to_separated_str(OPENSSH_GLOBAL_KNOWN_HOSTS_FILES + OPENSSH_USER_KNOWN_HOSTS_FILES, ", ", " and ", element_delimiters="`")}) and in the order as given, with all but the first entry for a given being ignored.", metavar="PATHNAME")
|
|
228
|
+
parser.add_argument("--dcache-admin-ssh.private-key", type=str, dest="dcache_admin_ssh_private_key", help="The file with the private key used for connecting to the SSH interface of dCache’s `admin` service via the authentication method `publickey`.", metavar="PATHNAME")
|
|
229
|
+
parser.add_argument("--dcache-admin-ssh.private-key-passphrase", type=FromResourceOrBytesOrStr, dest="dcache_admin_ssh_private_key_passphrase", help="The passphrase used for decrypting the private key specified via the `--dcache-admin-ssh.private-key`-option. If STRING starts with `$`, the passphrase is binarily read from an environment variable with the remainder of STRING as the name. If it starts with `<`, the passphrase is binarily read from a file with the remainder of it as the pathname. If it starts with `=`, the passphrase is the remainder of it.", metavar="STRING")
|
|
230
|
+
parser.add_argument("--dcache-admin-ssh.passphrase", type=FromResourceOrBytesOrStr, dest="dcache_admin_ssh_passphrase", help="The passphrase used for connecting to the SSH interface of dCache’s `admin` service via the authentication methods `password` and `keyboard-interactive`. If STRING starts with `$`, the passphrase is binarily read from an environment variable with the remainder of STRING as the name. If it starts with `<`, the passphrase is binarily read from a file with the remainder of it as the pathname. If it starts with `=`, the passphrase is the remainder of it.", metavar="STRING")
|
|
231
|
+
parser.add_argument("--dcache-admin-ssh.no-system-known-hosts", action="store_true", dest="dcache_admin_ssh_no_system_known_hosts", help=f"Don’t add any system known hosts files ({sequence_to_separated_str(OPENSSH_GLOBAL_KNOWN_HOSTS_FILES + OPENSSH_USER_KNOWN_HOSTS_FILES, ", ", " and ", element_delimiters="`")}) to the list of those from which trusted SSH host keys are read.")
|
|
232
|
+
parser.add_argument("--dcache-admin-ssh.no-default-private-key-locations", action="store_true", dest="dcache_admin_ssh_no_default_private_key_locations", help="Don’t use any private keys from default locations (which are the default of OpenSSH’s `IdentityFile`-option).")
|
|
233
|
+
parser.add_argument("--dcache-admin-ssh.no-agent", action="store_true", dest="dcache_admin_ssh_no_agent", help="Don’t use any SSH agent.")
|
|
234
|
+
parser.add_argument("--dcache-admin-ssh.tcp-timeout", type=float, default=DEFAULT_DCACHE_ADMIN_SSH_TCP_TIMEOUT, dest="dcache_admin_ssh_tcp_timeout", help=f"The timeout in seconds for establishing the TCP connection to the SSH interface of dCache’s `admin` service, with non-positive decimals disabling it. Defaults to `{DEFAULT_DCACHE_ADMIN_SSH_TCP_TIMEOUT}` if this option isn’t given.", metavar="DECIMAL")
|
|
235
|
+
parser.add_argument("--dcache-admin-ssh.banner-timeout", type=float, default=DEFAULT_DCACHE_ADMIN_SSH_BANNER_TIMEOUT, dest="dcache_admin_ssh_banner_timeout", help=f"The timeout in seconds for receiving any banner from the SSH interface of dCache’s `admin` service, with non-positive decimals disabling it. Defaults to `{DEFAULT_DCACHE_ADMIN_SSH_BANNER_TIMEOUT}` if this option isn’t given.", metavar="DECIMAL")
|
|
236
|
+
parser.add_argument("--dcache-admin-ssh.authentication-timeout", type=float, default=DEFAULT_DCACHE_ADMIN_SSH_AUTHENTICATION_TIMEOUT, dest="dcache_admin_ssh_authentication_timeout", help=f"The timeout in seconds for authenticating to the SSH interface of dCache’s `admin` service, with non-positive decimals disabling it. Defaults to `{DEFAULT_DCACHE_ADMIN_SSH_AUTHENTICATION_TIMEOUT}` if this option isn’t given.", metavar="DECIMAL")
|
|
237
|
+
parser.add_argument("--dcache-admin-ssh.channel-open-timeout", type=float, default=DEFAULT_DCACHE_ADMIN_SSH_CHANNEL_OPEN_TIMEOUT, dest="dcache_admin_ssh_channel_open_timeout", help=f"The timeout in seconds for opening a (SSH) channel to the SSH interface of dCache’s `admin` service, with non-positive decimals disabling it. Defaults to `{DEFAULT_DCACHE_ADMIN_SSH_CHANNEL_OPEN_TIMEOUT}` if this option isn’t given.", metavar="DECIMAL")
|
|
238
|
+
parser.add_argument("--dcache-admin-ssh.command-channel-timeout", type=float, default=DEFAULT_DCACHE_ADMIN_SSH_COMMAND_CHANNEL_TIMEOUT, dest="dcache_admin_ssh_command_channel_timeout", help=f"The timeout in seconds for read/write operations on the (SSH) channel of an executed command in the SSH interface of dCache’s `admin` service, with non-positive decimals disabling it. Defaults to `{DEFAULT_DCACHE_ADMIN_SSH_COMMAND_CHANNEL_TIMEOUT}` if this option isn’t given.", metavar="DECIMAL")
|
|
239
|
+
parser.add_argument("--dcache-admin-ssh.keepalive-interval", type=int, default=DEFAULT_DCACHE_ADMIN_SSH_KEEPALIVE_INTERVAL, dest="dcache_admin_ssh_keepalive_interval", help=f"The interval in seconds at which (client-initiated) SSH keepalive packets are sent to the SSH interface of dCache’s `admin` service, with non-positive integers disabling it. Defaults to `{DEFAULT_DCACHE_ADMIN_SSH_KEEPALIVE_INTERVAL}` if this option isn’t given.", metavar="INTEGER")
|
|
240
|
+
parser.add_argument("--dcache-admin-ssh.max-concurrent-commands", type=PositiveInt, default=DEFAULT_DCACHE_ADMIN_SSH_MAX_CONCURRENT_COMMANDS, dest="dcache_admin_ssh_max_concurrent_commands", help=f"The maximum number of commands that are concurrently executed via the SSH interface of dCache’s `admin` service. Defaults to `{DEFAULT_DCACHE_ADMIN_SSH_MAX_CONCURRENT_COMMANDS}` if this option isn’t given.", metavar="POSITIVE-INTEGER")
|
|
241
|
+
parser.add_argument("--metrics.name-prefix", type=str, default=DEFAULT_METRICS_NAME_PREFIX, dest="metrics_name_prefix", help="The prefix for all metric names. Defaults to `{DEFAULT_METRICS_NAME_PREFIX}` if this option isn’t given.", metavar="STRING")
|
|
242
|
+
parser.add_argument("--metrics.extra-label", type=str, nargs=2, default={}, action=AddMetricsExtraLabel, dest="metrics_extra_labels", help="Adds the extra label `EXTRA-LABEL-NAME` (of each matching metric respectively) to metrics whose name (without the metrics prefix specified via the `--metrics.name-prefix`-option and without any `_`-separator after that) matches the (implicitly fully anchored) Python `re` regular expression `REGULAR-EXPRESSION`.", metavar=("REGULAR-EXPRESSION", "EXTRA-LABEL-NAME"))
|
|
243
|
+
parser.add_argument("--export-mode", type=ExportMode, choices=[e.value for e in ExportMode], default=ExportMode.http, help=f"The mode in which the metrics are exported. `{ExportMode.http.value}` (which is the default if this option isn’t given) provides them via a HTTP server (that runs until the program is terminated by a signal or an error), with metrics being (freshly) collected on every scrape. `{ExportMode.stdout.value}` writes them to standard output (once, after which the program terminates). `{ExportMode.file.value}` writes them atomically (via moving a newly created temporary file) to the file specified via the `--output-file`-option (once, after which the program terminates), with the output file being overwritten if it exists already.")
|
|
244
|
+
parser.add_argument("--http-port", type=int, help=f"The port on which the HTTP server listens when the export mode is `http` (ignored otherwise). Defaults to `{DEFAULT_HTTP_PORT}` if this option isn’t given.", metavar="PORTNUMBER")
|
|
245
|
+
parser.add_argument("--output-file", type=str, help="The file to which the metrics are written when the export mode is `file` (ignored otherwise).", metavar="PATHNAME")
|
|
246
|
+
parser.add_argument("--log.level", type=IntOrStr, choices=[ level for name, number in logging.getLevelNamesMapping().items() if name != "NOTSET" for level in (name, number) ], default=DEFAULT_LOG_LEVELNAME, dest="log_level", help=f"The minimum level (name or number) of log messages to be logged. Defaults to `{DEFAULT_LOG_LEVELNAME}` (`{logging.getLevelName(DEFAULT_LOG_LEVELNAME)}`) if this option isn’t given.")
|
|
247
|
+
|
|
248
|
+
cfg = parser.parse_args(namespace=cfg)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
#configure logging
|
|
252
|
+
logging_root_logger.setLevel(cfg.log_level)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
#check whether options that are required depending on other options are given
|
|
256
|
+
match cfg.dcache_frontend_rest_http_auth_type:
|
|
257
|
+
case dcache.frontend.HTTPAuthType.none:
|
|
258
|
+
pass
|
|
259
|
+
case dcache.frontend.HTTPAuthType.basic_auth:
|
|
260
|
+
if cfg.dcache_frontend_rest_http_auth_user is None and cfg.dcache_frontend_rest_http_auth_passphrase is None:
|
|
261
|
+
parser.error("the following arguments are required: --dcache-frontend-rest.http-auth-user, --dcache-frontend-rest.http-auth-passphrase")
|
|
262
|
+
elif cfg.dcache_frontend_rest_http_auth_user is None:
|
|
263
|
+
parser.error("the following arguments are required: --dcache-frontend-rest.http-auth-user")
|
|
264
|
+
elif cfg.dcache_frontend_rest_http_auth_passphrase is None:
|
|
265
|
+
parser.error("the following arguments are required: --dcache-frontend-rest.http-auth-passphrase")
|
|
266
|
+
case _:
|
|
267
|
+
raise NotImplementedError(f"Unknown HTTP authentication type `{cfg.dcache_frontend_rest_http_auth_type.name}`.")
|
|
268
|
+
if cfg.dcache_frontend_rest_tls_client_auth_private_key is not None and cfg.dcache_frontend_rest_tls_client_auth_certificate is None:
|
|
269
|
+
parser.error("the following arguments are required: --dcache-frontend-rest.tls-client-auth-certificate")
|
|
270
|
+
if cfg.export_mode == ExportMode.file and cfg.output_file is None:
|
|
271
|
+
parser.error("the following arguments are required: --output-file")
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
#warn about ignored options
|
|
275
|
+
match cfg.dcache_frontend_rest_http_auth_type:
|
|
276
|
+
case dcache.frontend.HTTPAuthType.none:
|
|
277
|
+
if cfg.dcache_frontend_rest_http_auth_user is not None:
|
|
278
|
+
log.warning("The `--dcache-frontend-rest.http-auth-user`-option is ignored.")
|
|
279
|
+
if cfg.dcache_frontend_rest_http_auth_passphrase is not None:
|
|
280
|
+
log.warning("The `--dcache-frontend-rest.http-auth-passphrase`-option is ignored.")
|
|
281
|
+
case dcache.frontend.HTTPAuthType.basic_auth:
|
|
282
|
+
pass
|
|
283
|
+
case _:
|
|
284
|
+
raise NotImplementedError(f"Unknown HTTP authentication type `{cfg.dcache_frontend_rest_http_auth_type.name}`.")
|
|
285
|
+
if cfg.dcache_frontend_rest_tls_client_auth_private_key_passphrase is not None and (cfg.dcache_frontend_rest_tls_client_auth_certificate is None and cfg.dcache_frontend_rest_tls_client_auth_private_key is None):
|
|
286
|
+
log.warning("The `--dcache-frontend-rest.tls-client-auth-private-key-passphrase`-option is ignored.")
|
|
287
|
+
if cfg.http_port is not None and cfg.export_mode in (ExportMode.stdout, ExportMode.file):
|
|
288
|
+
log.warning("The `--http-port`-option is ignored.")
|
|
289
|
+
if cfg.output_file is not None and ( cfg.export_mode == ExportMode.http or (cfg.export_mode == ExportMode.stdout and cfg.output_file != "-") ):
|
|
290
|
+
log.warning("The `--output-file`-option is ignored.")
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
#set option default values (late)
|
|
294
|
+
if cfg.dcache_frontend_rest_connection_pool_max_size is None:
|
|
295
|
+
cfg.dcache_frontend_rest_connection_pool_max_size = cfg.dcache_frontend_rest_max_concurrent_requests
|
|
296
|
+
if cfg.dcache_frontend_rest_connection_pool_max_idle_size is None:
|
|
297
|
+
cfg.dcache_frontend_rest_connection_pool_max_idle_size = cfg.dcache_frontend_rest_connection_pool_max_size
|
|
298
|
+
if cfg.http_port is None:
|
|
299
|
+
cfg.http_port = DEFAULT_HTTP_PORT
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
#if the export mode is `file` and the `--output-file`-option has the special value `-`, change the former to `stdout`
|
|
303
|
+
if cfg.export_mode == ExportMode.file and cfg.output_file == "-":
|
|
304
|
+
cfg.export_mode = ExportMode.stdout
|
|
305
|
+
|
|
306
|
+
#disable intervals/limits that are set to a non-positive value
|
|
307
|
+
for a in ("dcache_frontend_rest_connect_timeout", "dcache_frontend_rest_read_timeout", "dcache_frontend_rest_pool_timeout", "dcache_frontend_rest_connection_pool_max_size", "dcache_admin_ssh_tcp_timeout", "dcache_admin_ssh_banner_timeout", "dcache_admin_ssh_authentication_timeout", "dcache_admin_ssh_channel_open_timeout", "dcache_admin_ssh_command_channel_timeout", "dcache_admin_ssh_keepalive_interval"):
|
|
308
|
+
if getattr(cfg, a) <= 0:
|
|
309
|
+
setattr(cfg, a, None)
|
|
310
|
+
|
|
311
|
+
#disable limits that are set to a negative value
|
|
312
|
+
for a in ("dcache_frontend_rest_connection_pool_max_idle_size", "dcache_frontend_rest_connection_pool_max_idle_time"):
|
|
313
|
+
if getattr(cfg, a) < 0:
|
|
314
|
+
setattr(cfg, a, None)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
#warn about suboptimal option values
|
|
318
|
+
if cfg.dcache_frontend_rest_connection_pool_max_size is not None and cfg.dcache_frontend_rest_connection_pool_max_size < cfg.dcache_frontend_rest_max_concurrent_requests:
|
|
319
|
+
log.warning("The value of the `--dcache-frontend-rest.connection-pool-max-size`-option is less than that of the `--dcache-frontend-rest.max-concurrent-requests`-option, which means that some of the concurrent requests will block until others have finished.")
|
|
320
|
+
if cfg.dcache_frontend_rest_connection_pool_max_idle_size is not None:
|
|
321
|
+
if cfg.dcache_frontend_rest_connection_pool_max_size is None:
|
|
322
|
+
log.warning("The value of the `--dcache-frontend-rest.connection-pool-max-idle-size`-option causes a limit while that of the `--dcache-frontend-rest.connection-pool-max-size`-option does not, which means that some connections might not be re-used.")
|
|
323
|
+
elif cfg.dcache_frontend_rest_connection_pool_max_idle_size < cfg.dcache_frontend_rest_connection_pool_max_size:
|
|
324
|
+
log.warning("The value of the `--dcache-frontend-rest.connection-pool-max-idle-size`-option is less than that of the `--dcache-frontend-rest.connection-pool-max-size`-option, which means that some connections might not be re-used.")
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
#manually apply option values
|
|
328
|
+
collector.MetricName.prefix = cfg.metrics_name_prefix
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
#exit regularly on various exiting signals
|
|
332
|
+
for s in EXITING_SIGNALS:
|
|
333
|
+
signal.signal(s, exiting_signal_handler)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
#determine the system SSH known hosts files
|
|
337
|
+
if not cfg.dcache_admin_ssh_no_system_known_hosts:
|
|
338
|
+
cfg.dcache_admin_ssh_system_known_hosts = list(OPENSSH_GLOBAL_KNOWN_HOSTS_FILES)
|
|
339
|
+
for p in OPENSSH_USER_KNOWN_HOSTS_FILES:
|
|
340
|
+
cfg.dcache_admin_ssh_system_known_hosts.append(os.path.expanduser(p))
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
#unregister `prometheus_client`’s default collectors
|
|
344
|
+
# TODO: Keep this aligned with `prometheus_client`’s default collectors.
|
|
345
|
+
prometheus_client.REGISTRY.unregister(prometheus_client.GC_COLLECTOR)
|
|
346
|
+
prometheus_client.REGISTRY.unregister(prometheus_client.PLATFORM_COLLECTOR)
|
|
347
|
+
prometheus_client.REGISTRY.unregister(prometheus_client.PROCESS_COLLECTOR)
|
|
348
|
+
|
|
349
|
+
#create collector registry
|
|
350
|
+
registry = prometheus_client.CollectorRegistry()
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
with contextlib.ExitStack() as resource_manager:
|
|
354
|
+
#create collector
|
|
355
|
+
try:
|
|
356
|
+
resource_manager.enter_context( collector.DcacheCollector(registry=registry, unmatched_metric_name_regular_expressions_callback=warn_about_unmatched_metric_name_regular_expressions) )
|
|
357
|
+
except collector.DcacheCollector.exceptions.ConfigurationError as e1:
|
|
358
|
+
e2 = e1.__cause__
|
|
359
|
+
match type(e2):
|
|
360
|
+
case collector.DcacheCollector.exceptions.InvalidMetricNameRegularExpressionError:
|
|
361
|
+
parser.error(f"argument --metrics.extra-label: invalid REGULAR-EXPRESSION value: '{e2.__cause__.pattern}'")
|
|
362
|
+
case collector.DcacheCollector.exceptions.ExtraLabelValueCallablesNotFoundError:
|
|
363
|
+
if len(e2.extra_labels) == 1:
|
|
364
|
+
parser.error(f"argument --metrics.extra-label: invalid EXTRA-LABEL-NAME value in effect for the '{e2.metric_name}'-metric: '{e2.extra_labels[0]}'")
|
|
365
|
+
else:
|
|
366
|
+
parser.error(f"argument --metrics.extra-label: invalid EXTRA-LABEL-NAME values in effect for the '{e2.metric_name}'-metric: {sequence_to_separated_str(e2.extra_labels, ", ", " and ", element_delimiters="'")}")
|
|
367
|
+
case _:
|
|
368
|
+
warn(f"Unexpected type `{e2.__class__.__module__}.{e2.__class__.__qualname__}` of the `__cause__`-attribute of a `{e1.__class__.__module__}.{e1.__class__.__qualname__}`-exception.")
|
|
369
|
+
raise
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
#export metrics
|
|
373
|
+
match cfg.export_mode:
|
|
374
|
+
case ExportMode.http:
|
|
375
|
+
tmp = prometheus_client.start_http_server(cfg.http_port, registry=registry)
|
|
376
|
+
|
|
377
|
+
#wait for the program to be terminated by a signal or an error
|
|
378
|
+
# TODO: Remove this handling (including the `tmp`-variable) for
|
|
379
|
+
# `prometheus_client` versions < 0.20.0 once newer versions are
|
|
380
|
+
# sufficiently widespread.
|
|
381
|
+
if tmp is None:
|
|
382
|
+
#`prometheus_client` versions < 0.20.0:
|
|
383
|
+
|
|
384
|
+
import time
|
|
385
|
+
while True:
|
|
386
|
+
time.sleep(2**16)
|
|
387
|
+
else:
|
|
388
|
+
#`prometheus_client` versions ≧ 0.20.0:
|
|
389
|
+
|
|
390
|
+
thread = tmp[1]
|
|
391
|
+
thread.join()
|
|
392
|
+
case ExportMode.stdout:
|
|
393
|
+
# TODO: Keep the encoding aligned to that used by `prometheus_client`’s
|
|
394
|
+
# `generate_latest`-function.
|
|
395
|
+
print( prometheus_client.generate_latest(registry=registry).decode(encoding="utf-8"), end="" )
|
|
396
|
+
case ExportMode.file:
|
|
397
|
+
prometheus_client.write_to_textfile(cfg.output_file, registry=registry)
|
|
398
|
+
case _:
|
|
399
|
+
raise NotImplementedError(f"Unknown export mode `{cfg.export_mode.name}`.")
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
return os.EX_OK
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
if __name__ == "__main__":
|
|
408
|
+
sys.exit( main() )
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
#Copyright © 2026 Christoph Anton Mitterer <mail@christoph.anton.mitterer.name>
|
|
426
|
+
#
|
|
427
|
+
#
|
|
428
|
+
#This program is free software: you can redistribute it and/or modify it under
|
|
429
|
+
#the terms of the GNU General Public License as published by the Free Software
|
|
430
|
+
#Foundation, either version 3 of the License, or (at your option) any later
|
|
431
|
+
#version.
|
|
432
|
+
#This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
433
|
+
#WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
|
434
|
+
#PARTICULAR PURPOSE.
|
|
435
|
+
#See the GNU General Public License for more details.
|
|
436
|
+
#You should have received a copy of the GNU General Public License along with
|
|
437
|
+
#this program. If not, see <https://www.gnu.org/licenses/>.
|