query-exporter 5.2.2__tar.gz → 5.2.3__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.
- {query_exporter-5.2.2/query_exporter.egg-info → query_exporter-5.2.3}/PKG-INFO +1 -4
- {query_exporter-5.2.2 → query_exporter-5.2.3}/README.rst +0 -3
- {query_exporter-5.2.2 → query_exporter-5.2.3}/pyproject.toml +1 -3
- {query_exporter-5.2.2 → query_exporter-5.2.3}/query_exporter/__init__.py +1 -1
- {query_exporter-5.2.2 → query_exporter-5.2.3}/query_exporter/db.py +75 -49
- {query_exporter-5.2.2 → query_exporter-5.2.3/query_exporter.egg-info}/PKG-INFO +1 -4
- {query_exporter-5.2.2 → query_exporter-5.2.3}/LICENSE.txt +0 -0
- {query_exporter-5.2.2 → query_exporter-5.2.3}/query_exporter/builtin_metrics.py +0 -0
- {query_exporter-5.2.2 → query_exporter-5.2.3}/query_exporter/config.py +0 -0
- {query_exporter-5.2.2 → query_exporter-5.2.3}/query_exporter/executor.py +0 -0
- {query_exporter-5.2.2 → query_exporter-5.2.3}/query_exporter/main.py +0 -0
- {query_exporter-5.2.2 → query_exporter-5.2.3}/query_exporter/py.typed +0 -0
- {query_exporter-5.2.2 → query_exporter-5.2.3}/query_exporter/schema.py +0 -0
- {query_exporter-5.2.2 → query_exporter-5.2.3}/query_exporter/yaml.py +0 -0
- {query_exporter-5.2.2 → query_exporter-5.2.3}/query_exporter.egg-info/SOURCES.txt +0 -0
- {query_exporter-5.2.2 → query_exporter-5.2.3}/query_exporter.egg-info/dependency_links.txt +0 -0
- {query_exporter-5.2.2 → query_exporter-5.2.3}/query_exporter.egg-info/entry_points.txt +0 -0
- {query_exporter-5.2.2 → query_exporter-5.2.3}/query_exporter.egg-info/requires.txt +0 -0
- {query_exporter-5.2.2 → query_exporter-5.2.3}/query_exporter.egg-info/top_level.txt +0 -0
- {query_exporter-5.2.2 → query_exporter-5.2.3}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: query-exporter
|
|
3
|
-
Version: 5.2.
|
|
3
|
+
Version: 5.2.3
|
|
4
4
|
Summary: Export Prometheus metrics generated from SQL queries
|
|
5
5
|
Author-email: Alberto Donato <oss@albertodonato.net>
|
|
6
6
|
Maintainer-email: Alberto Donato <oss@albertodonato.net>
|
|
@@ -251,8 +251,6 @@ The image has support for connecting the following databases:
|
|
|
251
251
|
- ClickHouse (``clickhouse+native://``)
|
|
252
252
|
- Teradata (``teradatasql://``)
|
|
253
253
|
|
|
254
|
-
A `Helm chart`_ to run the container in Kubernetes is also available.
|
|
255
|
-
|
|
256
254
|
Automated builds from the ``main`` branch are available on the `GitHub container registry`_ via::
|
|
257
255
|
|
|
258
256
|
docker pull ghcr.io/albertodonato/query-exporter:main
|
|
@@ -295,7 +293,6 @@ For general purpose questions, you can use `Discussions`_ on GitHub.
|
|
|
295
293
|
.. _`Docker Hub`: https://hub.docker.com/r/adonato/query-exporter
|
|
296
294
|
.. _`configuration file format`: docs/configuration.rst
|
|
297
295
|
.. _`contribution guide`: docs/contributing.rst
|
|
298
|
-
.. _`Helm chart`: https://github.com/makezbs/helm-charts/tree/main/charts/query-exporter
|
|
299
296
|
.. _`GitHub container registry`: https://github.com/albertodonato/query-exporter/pkgs/container/query-exporter
|
|
300
297
|
.. _`Discussions`: https://github.com/albertodonato/query-exporter/discussions
|
|
301
298
|
|
|
@@ -212,8 +212,6 @@ The image has support for connecting the following databases:
|
|
|
212
212
|
- ClickHouse (``clickhouse+native://``)
|
|
213
213
|
- Teradata (``teradatasql://``)
|
|
214
214
|
|
|
215
|
-
A `Helm chart`_ to run the container in Kubernetes is also available.
|
|
216
|
-
|
|
217
215
|
Automated builds from the ``main`` branch are available on the `GitHub container registry`_ via::
|
|
218
216
|
|
|
219
217
|
docker pull ghcr.io/albertodonato/query-exporter:main
|
|
@@ -256,7 +254,6 @@ For general purpose questions, you can use `Discussions`_ on GitHub.
|
|
|
256
254
|
.. _`Docker Hub`: https://hub.docker.com/r/adonato/query-exporter
|
|
257
255
|
.. _`configuration file format`: docs/configuration.rst
|
|
258
256
|
.. _`contribution guide`: docs/contributing.rst
|
|
259
|
-
.. _`Helm chart`: https://github.com/makezbs/helm-charts/tree/main/charts/query-exporter
|
|
260
257
|
.. _`GitHub container registry`: https://github.com/albertodonato/query-exporter/pkgs/container/query-exporter
|
|
261
258
|
.. _`Discussions`: https://github.com/albertodonato/query-exporter/discussions
|
|
262
259
|
|
|
@@ -96,6 +96,7 @@ tests = [
|
|
|
96
96
|
"time-machine",
|
|
97
97
|
]
|
|
98
98
|
types = [
|
|
99
|
+
"pytest-docker",
|
|
99
100
|
"types-python-dateutil",
|
|
100
101
|
"types-pyyaml",
|
|
101
102
|
"types-requests",
|
|
@@ -118,9 +119,6 @@ ignore-words-list = "timeschedule"
|
|
|
118
119
|
[tool.pyproject-fmt]
|
|
119
120
|
expand_tables = [ "tool.tox" ]
|
|
120
121
|
|
|
121
|
-
[tool.ty]
|
|
122
|
-
rules.unresolved-import = "ignore"
|
|
123
|
-
|
|
124
122
|
[tool.pytest]
|
|
125
123
|
ini_options.testpaths = [
|
|
126
124
|
"tests",
|
|
@@ -268,38 +268,54 @@ class QueryExecution:
|
|
|
268
268
|
)
|
|
269
269
|
|
|
270
270
|
|
|
271
|
-
class
|
|
272
|
-
"""
|
|
271
|
+
class StatementCanceller:
|
|
272
|
+
"""Cancel the statement running on a connection from another thread.
|
|
273
|
+
|
|
274
|
+
The connection itself is never closed or invalidated from another thread,
|
|
275
|
+
as neither SQLAlchemy nor DBAPI connections are thread-safe. Instead, the
|
|
276
|
+
driver's `cancel()` (e.g. oracledb, psycopg) or `interrupt()` (sqlite3)
|
|
277
|
+
method is used if available, since drivers providing them support calling
|
|
278
|
+
them while another thread is executing a statement. The statement then
|
|
279
|
+
fails on the thread running it.
|
|
280
|
+
"""
|
|
281
|
+
|
|
282
|
+
CANCEL_METHODS = ("cancel", "interrupt")
|
|
273
283
|
|
|
274
284
|
def __init__(self) -> None:
|
|
275
285
|
self._lock = Lock()
|
|
276
|
-
self.
|
|
286
|
+
self._dbapi_conn: DBAPIConnection | None = None
|
|
287
|
+
self.cancelled = False
|
|
277
288
|
|
|
278
289
|
@contextmanager
|
|
279
290
|
def track(self, conn: Connection) -> Generator[None]:
|
|
280
291
|
"""Track `conn` for the duration of the statement.
|
|
281
292
|
|
|
282
293
|
The connection is untracked before it's returned to the pool, so that a
|
|
283
|
-
late
|
|
284
|
-
checked out.
|
|
294
|
+
late cancel can't hit a statement from another query that has since
|
|
295
|
+
checked out the connection.
|
|
285
296
|
"""
|
|
286
297
|
with self._lock:
|
|
287
|
-
self.
|
|
298
|
+
self._dbapi_conn = conn.connection.dbapi_connection
|
|
288
299
|
try:
|
|
289
300
|
yield
|
|
290
301
|
finally:
|
|
291
302
|
with self._lock:
|
|
292
|
-
self.
|
|
303
|
+
self._dbapi_conn = None
|
|
293
304
|
|
|
294
|
-
def
|
|
295
|
-
"""
|
|
305
|
+
def cancel(self) -> bool:
|
|
306
|
+
"""Cancel the running statement, if any.
|
|
296
307
|
|
|
297
|
-
|
|
298
|
-
statement completes on its own, so it must never run on the event loop.
|
|
308
|
+
Return whether the driver supports cancelling statements.
|
|
299
309
|
"""
|
|
300
310
|
with self._lock:
|
|
301
|
-
if self.
|
|
302
|
-
|
|
311
|
+
if self._dbapi_conn is None:
|
|
312
|
+
return True
|
|
313
|
+
for name in self.CANCEL_METHODS:
|
|
314
|
+
if cancel := getattr(self._dbapi_conn, name, None):
|
|
315
|
+
self.cancelled = True
|
|
316
|
+
cancel()
|
|
317
|
+
return True
|
|
318
|
+
return False
|
|
303
319
|
|
|
304
320
|
|
|
305
321
|
class Database:
|
|
@@ -326,17 +342,17 @@ class Database:
|
|
|
326
342
|
max_workers=max_workers,
|
|
327
343
|
thread_name_prefix=f"[Database-{self.name}]",
|
|
328
344
|
)
|
|
329
|
-
#
|
|
330
|
-
#
|
|
331
|
-
#
|
|
332
|
-
self.
|
|
345
|
+
# Cancelling a statement can perform I/O, so it's done off the event
|
|
346
|
+
# loop, in a separate pool since the query one is by definition
|
|
347
|
+
# saturated by the statements being cancelled.
|
|
348
|
+
self._cancel_executor = ThreadPoolExecutor(
|
|
333
349
|
max_workers=max_workers,
|
|
334
|
-
thread_name_prefix=f"[Database-{self.name}-
|
|
350
|
+
thread_name_prefix=f"[Database-{self.name}-cancel]",
|
|
335
351
|
)
|
|
336
352
|
|
|
337
353
|
def close(self) -> None:
|
|
338
354
|
self._executor.shutdown(wait=True, cancel_futures=True)
|
|
339
|
-
self.
|
|
355
|
+
self._cancel_executor.shutdown(wait=False, cancel_futures=True)
|
|
340
356
|
self._engine.dispose()
|
|
341
357
|
|
|
342
358
|
async def execute(self, query_execution: QueryExecution) -> MetricResults:
|
|
@@ -373,47 +389,38 @@ class Database:
|
|
|
373
389
|
parameters = {}
|
|
374
390
|
|
|
375
391
|
loop = asyncio.get_event_loop()
|
|
376
|
-
|
|
392
|
+
canceller = StatementCanceller()
|
|
377
393
|
future = loop.run_in_executor(
|
|
378
|
-
self._executor,
|
|
394
|
+
self._executor,
|
|
395
|
+
self._execute_sync,
|
|
396
|
+
statement,
|
|
397
|
+
parameters,
|
|
398
|
+
canceller,
|
|
379
399
|
)
|
|
380
400
|
try:
|
|
381
401
|
return await asyncio.wait_for(
|
|
382
402
|
asyncio.shield(future), timeout=timeout
|
|
383
403
|
)
|
|
384
404
|
except TimeoutError:
|
|
385
|
-
|
|
405
|
+
# a queued statement won't run at all, the outcome of a running one
|
|
406
|
+
# is discarded
|
|
407
|
+
future.cancel()
|
|
408
|
+
self._cancel_executor.submit(self._cancel_sync, canceller)
|
|
386
409
|
raise
|
|
387
410
|
|
|
388
|
-
def
|
|
389
|
-
self,
|
|
390
|
-
future: asyncio.Future[QueryResults],
|
|
391
|
-
tracker: ConnectionTracker,
|
|
392
|
-
) -> None:
|
|
393
|
-
"""Drop the connection of a query that timed out.
|
|
394
|
-
|
|
395
|
-
The result is deliberately not awaited: dropping the connection can
|
|
396
|
-
block for as long as the query itself, and the timeout must be reported
|
|
397
|
-
right away.
|
|
398
|
-
"""
|
|
399
|
-
self._timeout_executor.submit(self._invalidate_sync, tracker)
|
|
400
|
-
# the shielded future outlives the timeout, consume its outcome so that
|
|
401
|
-
# asyncio doesn't report it as never retrieved
|
|
402
|
-
future.add_done_callback(self._discard_outcome)
|
|
403
|
-
|
|
404
|
-
def _invalidate_sync(self, tracker: ConnectionTracker) -> None:
|
|
411
|
+
def _cancel_sync(self, canceller: StatementCanceller) -> None:
|
|
405
412
|
try:
|
|
406
|
-
|
|
413
|
+
if not canceller.cancel():
|
|
414
|
+
self.logger.debug(
|
|
415
|
+
"driver doesn't support cancelling queries, "
|
|
416
|
+
"waiting for timed out query to complete"
|
|
417
|
+
)
|
|
407
418
|
except Exception as error:
|
|
408
419
|
self.logger.warning(
|
|
409
|
-
"failed
|
|
420
|
+
"failed cancelling timed out query",
|
|
410
421
|
error=self._error_message(error),
|
|
411
422
|
)
|
|
412
423
|
|
|
413
|
-
def _discard_outcome(self, future: asyncio.Future[QueryResults]) -> None:
|
|
414
|
-
if not future.cancelled():
|
|
415
|
-
future.exception()
|
|
416
|
-
|
|
417
424
|
def _setup_engine(self) -> Engine:
|
|
418
425
|
engine = create_db_engine(self.config)
|
|
419
426
|
|
|
@@ -446,21 +453,40 @@ class Database:
|
|
|
446
453
|
self,
|
|
447
454
|
statement: TextClause,
|
|
448
455
|
parameters: dict[str, Any],
|
|
449
|
-
|
|
456
|
+
canceller: StatementCanceller,
|
|
450
457
|
) -> QueryResults:
|
|
451
458
|
try:
|
|
452
|
-
with self._engine.begin() as conn,
|
|
459
|
+
with self._engine.begin() as conn, canceller.track(conn):
|
|
453
460
|
try:
|
|
454
461
|
start_time = perf_counter()
|
|
455
462
|
result = conn.execute(statement, parameters)
|
|
456
463
|
latency = perf_counter() - start_time
|
|
457
464
|
return QueryResults.from_result(result, latency=latency)
|
|
458
465
|
except Exception as error:
|
|
459
|
-
raise self.
|
|
466
|
+
raise self._execute_error(
|
|
467
|
+
error, DatabaseQueryError, canceller
|
|
468
|
+
)
|
|
460
469
|
except DatabaseQueryError:
|
|
461
470
|
raise
|
|
462
471
|
except Exception as error:
|
|
463
|
-
raise self.
|
|
472
|
+
raise self._execute_error(error, DatabaseConnectError, canceller)
|
|
473
|
+
|
|
474
|
+
def _execute_error(
|
|
475
|
+
self,
|
|
476
|
+
error: Exception,
|
|
477
|
+
exc_class: type[DatabaseError],
|
|
478
|
+
canceller: StatementCanceller,
|
|
479
|
+
) -> DatabaseError:
|
|
480
|
+
"""Create a DatabaseError for a failed statement.
|
|
481
|
+
|
|
482
|
+
Errors from cancelled statements are expected, and the timeout has
|
|
483
|
+
already been reported, so they're not logged as errors.
|
|
484
|
+
"""
|
|
485
|
+
if not canceller.cancelled:
|
|
486
|
+
return self._db_error(error, exc_class=exc_class)
|
|
487
|
+
message = self._error_message(error)
|
|
488
|
+
self.logger.debug("timed out query cancelled", error=message)
|
|
489
|
+
return exc_class(message)
|
|
464
490
|
|
|
465
491
|
def _query_db_error(
|
|
466
492
|
self,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: query-exporter
|
|
3
|
-
Version: 5.2.
|
|
3
|
+
Version: 5.2.3
|
|
4
4
|
Summary: Export Prometheus metrics generated from SQL queries
|
|
5
5
|
Author-email: Alberto Donato <oss@albertodonato.net>
|
|
6
6
|
Maintainer-email: Alberto Donato <oss@albertodonato.net>
|
|
@@ -251,8 +251,6 @@ The image has support for connecting the following databases:
|
|
|
251
251
|
- ClickHouse (``clickhouse+native://``)
|
|
252
252
|
- Teradata (``teradatasql://``)
|
|
253
253
|
|
|
254
|
-
A `Helm chart`_ to run the container in Kubernetes is also available.
|
|
255
|
-
|
|
256
254
|
Automated builds from the ``main`` branch are available on the `GitHub container registry`_ via::
|
|
257
255
|
|
|
258
256
|
docker pull ghcr.io/albertodonato/query-exporter:main
|
|
@@ -295,7 +293,6 @@ For general purpose questions, you can use `Discussions`_ on GitHub.
|
|
|
295
293
|
.. _`Docker Hub`: https://hub.docker.com/r/adonato/query-exporter
|
|
296
294
|
.. _`configuration file format`: docs/configuration.rst
|
|
297
295
|
.. _`contribution guide`: docs/contributing.rst
|
|
298
|
-
.. _`Helm chart`: https://github.com/makezbs/helm-charts/tree/main/charts/query-exporter
|
|
299
296
|
.. _`GitHub container registry`: https://github.com/albertodonato/query-exporter/pkgs/container/query-exporter
|
|
300
297
|
.. _`Discussions`: https://github.com/albertodonato/query-exporter/discussions
|
|
301
298
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|