kinto 19.2.0__py3-none-any.whl → 19.3.0__py3-none-any.whl

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.

Potentially problematic release.


This version of kinto might be problematic. Click here for more details.

Files changed (29) hide show
  1. kinto/__init__.py +1 -0
  2. kinto/core/__init__.py +1 -0
  3. kinto/core/initialization.py +57 -7
  4. kinto/core/metrics.py +37 -1
  5. kinto/core/utils.py +8 -0
  6. kinto/plugins/admin/VERSION +1 -1
  7. kinto/plugins/admin/build/VERSION +1 -1
  8. kinto/plugins/admin/build/assets/index-BKIg2XW8.js +165 -0
  9. kinto/plugins/admin/build/assets/{index-vylaZGUr.css → index-D8oiN37x.css} +1 -1
  10. kinto/plugins/admin/build/assets/{javascript-upQ8KtFH.js → javascript-iSgyE4tI.js} +1 -1
  11. kinto/plugins/admin/build/index.html +2 -2
  12. kinto/plugins/prometheus.py +56 -20
  13. kinto/plugins/statsd.py +13 -1
  14. {kinto-19.2.0.dist-info → kinto-19.3.0.dist-info}/METADATA +3 -3
  15. {kinto-19.2.0.dist-info → kinto-19.3.0.dist-info}/RECORD +28 -28
  16. {kinto-19.2.0.dist-info → kinto-19.3.0.dist-info}/WHEEL +1 -1
  17. kinto/plugins/admin/build/assets/index-iVTdxamX.js +0 -175
  18. /kinto/plugins/admin/build/assets/{asn1-8gHclKtu.js → asn1-CGOzndHr.js} +0 -0
  19. /kinto/plugins/admin/build/assets/{clojure-plf_rynZ.js → clojure-BMjYHr_A.js} +0 -0
  20. /kinto/plugins/admin/build/assets/{css-tpsEXL3H.js → css-BnMrqG3P.js} +0 -0
  21. /kinto/plugins/admin/build/assets/{logo-FQUYikj1.png → logo-VBRiKSPX.png} +0 -0
  22. /kinto/plugins/admin/build/assets/{mllike-ilm95jrV.js → mllike-C_8OmSiT.js} +0 -0
  23. /kinto/plugins/admin/build/assets/{python-xljIYvii.js → python-BuPzkPfP.js} +0 -0
  24. /kinto/plugins/admin/build/assets/{rpm-cddeyEgF.js → rpm-CTu-6PCP.js} +0 -0
  25. /kinto/plugins/admin/build/assets/{sql-3IaSLchm.js → sql-C4g8LzGK.js} +0 -0
  26. /kinto/plugins/admin/build/assets/{ttcn-cfg-9oMIyPXS.js → ttcn-cfg-BIkV9KBc.js} +0 -0
  27. {kinto-19.2.0.dist-info → kinto-19.3.0.dist-info}/LICENSE +0 -0
  28. {kinto-19.2.0.dist-info → kinto-19.3.0.dist-info}/entry_points.txt +0 -0
  29. {kinto-19.2.0.dist-info → kinto-19.3.0.dist-info}/top_level.txt +0 -0
kinto/__init__.py CHANGED
@@ -38,6 +38,7 @@ DEFAULT_SETTINGS = {
38
38
  "record_id_generator": "kinto.views.RelaxedUUID",
39
39
  "project_name": "kinto",
40
40
  "admin_assets_path": None,
41
+ "metrics_matchdict_fields": ["bucket_id", "collection_id", "group_id", "record_id"],
41
42
  }
42
43
 
43
44
 
kinto/core/__init__.py CHANGED
@@ -96,6 +96,7 @@ DEFAULT_SETTINGS = {
96
96
  "statsd_backend": "kinto.core.statsd",
97
97
  "statsd_prefix": "kinto.core",
98
98
  "statsd_url": None,
99
+ "metrics_matchdict_fields": [],
99
100
  "storage_backend": "",
100
101
  "storage_url": "",
101
102
  "storage_max_fetch_size": 10000,
@@ -431,6 +431,9 @@ def setup_logging(config):
431
431
  def setup_metrics(config):
432
432
  settings = config.get_settings()
433
433
 
434
+ # Register a no-op metrics service by default.
435
+ config.registry.registerUtility(metrics.NoOpMetricsService(), metrics.IMetricsService)
436
+
434
437
  # This does not fully respect the Pyramid/ZCA patterns, but the rest of Kinto uses
435
438
  # `registry.storage`, `registry.cache`, etc. Consistency seems more important.
436
439
  config.registry.__class__.metrics = property(
@@ -449,9 +452,6 @@ def setup_metrics(config):
449
452
  def on_app_created(event):
450
453
  config = event.app
451
454
  metrics_service = config.registry.metrics
452
- if not metrics_service:
453
- logger.warning("No metrics service registered.")
454
- return
455
455
 
456
456
  metrics.watch_execution_time(metrics_service, config.registry.cache, prefix="backend")
457
457
  metrics.watch_execution_time(metrics_service, config.registry.storage, prefix="backend")
@@ -471,15 +471,65 @@ def setup_metrics(config):
471
471
  def on_new_response(event):
472
472
  request = event.request
473
473
  metrics_service = config.registry.metrics
474
- if not metrics_service:
475
- return
474
+
475
+ try:
476
+ endpoint = utils.strip_uri_prefix(request.path)
477
+ except UnicodeDecodeError as e:
478
+ # This `on_new_response` callback is also called when a HTTP 400
479
+ # is returned because of an invalid UTF-8 path. We still want metrics.
480
+ endpoint = str(e)
476
481
 
477
482
  # Count unique users.
478
483
  user_id = request.prefixed_userid
479
484
  if user_id:
480
485
  # Get rid of colons in metric packet (see #1282).
481
- user_id = user_id.replace(":", ".")
482
- metrics_service.count("users", unique=user_id)
486
+ auth, user_id = user_id.split(":")
487
+ metrics_service.count("users", unique=[("auth", auth), ("userid", user_id)])
488
+
489
+ # Add extra labels to metrics, based on fields extracted from the request matchdict.
490
+ metrics_matchdict_fields = aslist(settings["metrics_matchdict_fields"])
491
+ # Turn the `id` field of object endpoints into `{resource}_id` (eg. `mushroom_id`, `bucket_id`)
492
+ enhanced_matchdict = dict(request.matchdict or {})
493
+ try:
494
+ enhanced_matchdict[request.current_resource_name + "_id"] = enhanced_matchdict.get(
495
+ "id", ""
496
+ )
497
+ except AttributeError:
498
+ # Not on a resource.
499
+ pass
500
+ metrics_matchdict_labels = [
501
+ (field, enhanced_matchdict.get(field, "")) for field in metrics_matchdict_fields
502
+ ]
503
+
504
+ # Count served requests.
505
+ metrics_service.count(
506
+ "request_summary",
507
+ unique=[
508
+ ("method", request.method.lower()),
509
+ ("endpoint", endpoint),
510
+ ("status", str(request.response.status_code)),
511
+ ]
512
+ + metrics_matchdict_labels,
513
+ )
514
+
515
+ try:
516
+ current = utils.msec_time()
517
+ duration = current - request._received_at
518
+ metrics_service.observe(
519
+ "request_duration",
520
+ duration,
521
+ labels=[("endpoint", endpoint)] + metrics_matchdict_labels,
522
+ )
523
+ except AttributeError: # pragma: no cover
524
+ # Logging was not setup in this Kinto app (unlikely but possible)
525
+ pass
526
+
527
+ # Observe response size.
528
+ metrics_service.observe(
529
+ "request_size",
530
+ len(request.response.body or b""),
531
+ labels=[("endpoint", endpoint)] + metrics_matchdict_labels,
532
+ )
483
533
 
484
534
  # Count authentication verifications.
485
535
  if hasattr(request, "authn_type"):
kinto/core/metrics.py CHANGED
@@ -1,6 +1,6 @@
1
1
  import types
2
2
 
3
- from zope.interface import Interface
3
+ from zope.interface import Interface, implementer
4
4
 
5
5
  from kinto.core import utils
6
6
 
@@ -16,13 +16,47 @@ class IMetricsService(Interface):
16
16
  Watch execution time.
17
17
  """
18
18
 
19
+ def observe(self, key, value, labels=[]):
20
+ """
21
+ Observe a give `value` for the specified `key`.
22
+ """
23
+
19
24
  def count(key, count=1, unique=None):
20
25
  """
21
26
  Count occurrences. If `unique` is set, overwrites the counter value
22
27
  on each call.
28
+
29
+ `unique` should be of type ``list[tuple[str,str]]``.
23
30
  """
24
31
 
25
32
 
33
+ class NoOpTimer:
34
+ def __call__(self, f):
35
+ @utils.safe_wraps(f)
36
+ def _wrapped(*args, **kwargs):
37
+ return f(*args, **kwargs)
38
+
39
+ return _wrapped
40
+
41
+ def __enter__(self):
42
+ pass
43
+
44
+ def __exit__(self, *args, **kwargs):
45
+ pass
46
+
47
+
48
+ @implementer(IMetricsService)
49
+ class NoOpMetricsService:
50
+ def timer(self, key):
51
+ return NoOpTimer()
52
+
53
+ def observe(self, key, value, labels=[]):
54
+ pass
55
+
56
+ def count(self, key, count=1, unique=None):
57
+ pass
58
+
59
+
26
60
  def watch_execution_time(metrics_service, obj, prefix="", classname=None):
27
61
  """
28
62
  Decorate all methods of an object in order to watch their execution time.
@@ -49,6 +83,8 @@ def listener_with_timer(config, key, func):
49
83
  def wrapped(*args, **kwargs):
50
84
  metrics_service = config.registry.metrics
51
85
  if not metrics_service:
86
+ # This only happens if `kinto.core.initialization.setup_metrics` is
87
+ # not listed in the `initialization_sequence` setting.
52
88
  return func(*args, **kwargs)
53
89
  # If metrics are enabled, monitor execution time of listeners.
54
90
  with metrics_service.timer(key):
kinto/core/utils.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import collections.abc as collections_abc
2
+ import functools
2
3
  import hashlib
3
4
  import hmac
4
5
  import os
@@ -541,3 +542,10 @@ def apply_json_patch(obj, ops):
541
542
  raise ValueError(e)
542
543
 
543
544
  return result
545
+
546
+
547
+ def safe_wraps(wrapper, *args, **kwargs):
548
+ """Safely wraps partial functions."""
549
+ while isinstance(wrapper, functools.partial):
550
+ wrapper = wrapper.func
551
+ return functools.wraps(wrapper, *args, **kwargs)
@@ -1 +1 @@
1
- 3.0.3
1
+ 3.4.1
@@ -1 +1 @@
1
- 3.0.3
1
+ 3.4.1