Skip to content

Commit

Permalink
bug-1906113: document metrics
Browse files Browse the repository at this point in the history
This documents all metrics in a format similar to how Glean defines
metrics. This adds a filter when running tests that raises an error if a
metric is emitted that's not documented.

This adds a Sphinx extension that automatically documents all metrics
defined in `tecken/tecken_metrics.yaml`.
  • Loading branch information
willkg committed Aug 6, 2024
1 parent 6df25dd commit 725b8e3
Show file tree
Hide file tree
Showing 11 changed files with 343 additions and 41 deletions.
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ indent_style = tab
[*.py]
max_line_length = 88

[*.{css,js,json,html,yml}]
[*.{css,js,json,html,yml,yaml}]
indent_size = 2
1 change: 1 addition & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"sphinxcontrib.httpdomain",
"everett.sphinxext",
"exts.adr_log",
"exts.document_metrics",
]

# Add any paths that contain templates here, relative to this directory.
Expand Down
16 changes: 8 additions & 8 deletions docs/exts/eliot_metrics.py → docs/exts/document_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ def build_table(table):
yield " ".join("=" * width for width in col_size)
yield " ".join(
header + (" " * (width - len(header)))
for header, width in zip(table[0], col_size)
for header, width in zip(table[0], col_size, strict=True)
)
yield " ".join("=" * width for width in col_size)
for row in table[1:]:
yield " ".join(
col + (" " * (width - len(col)))
for col, width in zip(row, col_size)
for col, width in zip(row, col_size, strict=True)
)
yield " ".join("=" * width for width in col_size)

Expand All @@ -53,7 +53,7 @@ def generate_docs(self, clspath):
module = sys.modules[modpath]
metrics = getattr(module, name)

sourcename = "metrics of %s" % clspath
sourcename = f"metrics of {clspath}"

# First build a table of metric items
self.add_line("Table of metrics:", sourcename)
Expand All @@ -62,7 +62,7 @@ def generate_docs(self, clspath):
table = []
table.append(("Key", "Type"))
for key, metric in metrics.items():
table.append((":py:data:`%s`" % key, metric.stat_type))
table.append((f":py:data:`{key}`", metric["type"]))

for line in build_table(table):
self.add_line(line, sourcename)
Expand All @@ -72,14 +72,14 @@ def generate_docs(self, clspath):
self.add_line("", sourcename)

for key, metric in metrics.items():
self.add_line(".. py:data:: %s" % key, sourcename)
self.add_line(f".. py:data:: {key}", sourcename)
self.add_line("", sourcename)
self.add_line("", sourcename)
self.add_line(" Type: %s" % metric.stat_type, sourcename)
self.add_line(f" **Type**: ``{metric['type']}``", sourcename)
self.add_line("", sourcename)
self.add_line("", sourcename)
for line in textwrap.dedent(metric.description).splitlines():
self.add_line(" " + line, sourcename)
for line in textwrap.dedent(metric["description"]).splitlines():
self.add_line(f" {line}", sourcename)
self.add_line("", sourcename)
self.add_line("", sourcename)

Expand Down
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
dev
contributing
configuration
metrics
frontend
redis
adr_log
Expand Down
10 changes: 10 additions & 0 deletions docs/metrics.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.. _metrics-chapter:

=======
Metrics
=======

Metrics in Tecken
=================

.. autometrics:: tecken.libmarkus.TECKEN_METRICS
1 change: 1 addition & 0 deletions requirements.in
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ psycopg2==2.9.9
pytest==8.3.2
pytest-django==4.8.0
python-dateutil==2.9.0.post0
PyYAML==6.0.1
redis==5.0.8
requests-mock==1.12.1
requests==2.32.3
Expand Down
53 changes: 53 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,59 @@ python-dateutil==2.9.0.post0 \
# via
# -r requirements.in
# botocore
pyyaml==6.0.1 \
--hash=sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5 \
--hash=sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc \
--hash=sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df \
--hash=sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741 \
--hash=sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206 \
--hash=sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27 \
--hash=sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595 \
--hash=sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62 \
--hash=sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98 \
--hash=sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696 \
--hash=sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290 \
--hash=sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9 \
--hash=sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d \
--hash=sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6 \
--hash=sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867 \
--hash=sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47 \
--hash=sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486 \
--hash=sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6 \
--hash=sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3 \
--hash=sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007 \
--hash=sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938 \
--hash=sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0 \
--hash=sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c \
--hash=sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735 \
--hash=sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d \
--hash=sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28 \
--hash=sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4 \
--hash=sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba \
--hash=sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8 \
--hash=sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef \
--hash=sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5 \
--hash=sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd \
--hash=sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3 \
--hash=sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0 \
--hash=sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515 \
--hash=sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c \
--hash=sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c \
--hash=sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924 \
--hash=sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34 \
--hash=sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43 \
--hash=sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859 \
--hash=sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673 \
--hash=sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54 \
--hash=sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a \
--hash=sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b \
--hash=sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab \
--hash=sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa \
--hash=sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c \
--hash=sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585 \
--hash=sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d \
--hash=sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f
# via -r requirements.in
redis==5.0.8 \
--hash=sha256:0c5b10d387568dfe0698c6fad6615750c24170e548ca2deac10c649d463e9870 \
--hash=sha256:56134ee08ea909106090934adc36f65c9bcbbaecea5b21ba704ba6fb561f8eb4
Expand Down
8 changes: 6 additions & 2 deletions tecken/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from django.apps import AppConfig

from tecken.libdockerflow import get_release_name
from tecken.libmarkus import METRICS, setup_markus
from tecken.libmarkus import METRICS, set_up_markus


logger = logging.getLogger("django")
Expand Down Expand Up @@ -104,7 +104,11 @@ def ready(self):

@staticmethod
def _configure_markus():
setup_markus(settings.MARKUS_BACKENDS, settings.HOSTNAME)
set_up_markus(
backends=settings.MARKUS_BACKENDS,
hostname=settings.HOSTNAME,
debug=settings.LOCAL_DEV_ENV or settings.TEST_ENV,
)

@staticmethod
def _fix_default_redis_connection():
Expand Down
29 changes: 27 additions & 2 deletions tecken/libmarkus.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,50 @@
# file, You can obtain one at https://mozilla.org/MPL/2.0/.

import json
from pathlib import Path
import time
from functools import partialmethod

import markus
from markus import INCR, GAUGE, HISTOGRAM, TIMING
from markus.backends import BackendBase
from markus.filters import AddTagFilter
from markus.filters import AddTagFilter, RegisteredMetricsFilter
import yaml


_IS_MARKUS_SETUP = False

METRICS = markus.get_metrics("tecken")


def setup_markus(backends, hostname):
# Complete index of all Eliot metrics. This is used in documentation and to filter
# outgoing metrics.
def _load_registered_metrics():
# Load the eliot_metrics.yaml file in this directory
path = Path(__file__).parent / "tecken_metrics.yaml"
with open(path) as fp:
data = yaml.safe_load(fp)
return data


TECKEN_METRICS = _load_registered_metrics()


def set_up_markus(backends, hostname, debug=False):
global _IS_MARKUS_SETUP, METRICS
if _IS_MARKUS_SETUP:
return

markus.configure(backends)

if debug:
# In local dev and test environments, we want the RegisteredMetricsFilter to
# raise exceptions when metrics are used incorrectly.
metrics_filter = RegisteredMetricsFilter(
registered_metrics=TECKEN_METRICS, raise_error=True
)
METRICS.filters.append(metrics_filter)

if hostname:
# Define host tag here instead of in the backend so it shows up in tests
METRICS.filters.append(AddTagFilter(f"host:{hostname}"))
Expand Down
159 changes: 159 additions & 0 deletions tecken/tecken_metrics.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
# statsd metrics emitted using Markus.
#
# When adding a new metric, make sure to add it here first.
---

tecken.api:
type: "timing"
description: |
Timer for how long it takes to handle an API request.
Tags:
* ``endpoint``: the API endpoint name
tecken.api_stats:
type: "timing"
description: |
Timer for how long it takes to compute upload API stats.
tecken.download_symbol:
type: "timing"
description: |
Timer for how long it takes to handle a download API request.
tecken.download_symbol_code_id_lookup:
type: "incr"
description: |
Counter for successful symbol code id lookups in download API.
tecken.gunicorn_worker_abort:
type: "incr"
description: |
Counter for number of gunicorn workers were terminated.
tecken.remove_orphaned_files.delete_file:
type: "incr"
description: |
Counter for successfully deleted orphaned files.
tecken.remove_orphaned_files.delete_file_error:
type: "incr"
description: |
Counter for errors encountered when deleting orphaned files.
tecken.remove_orphaned_files.timing:
type: "timing"
description: |
Timer for how long it took to run the ``remove_orphaned_files`` Django
command.
tecken.symboldownloader_exists:
type: "timing"
description: |
Timer for retrieving object metadata indicating the symbols file exists
in storage.
tecken.symboldownloader.file_age_days:
type: "histogram"
description: |
Histogram for how long the file has been in storage. Value is in days.
tecken.syminfo.lookup.cached:
type: "incr"
description: |
Counter for symbol information lookup.
Tags:
* ``result``: true or false as to whether symbol information came from the
cache
tecken.syminfo.lookup.timing:
type: "timing"
description: |
Timer for how long it takes to look up symbol information.
tecken.upload_archive:
type: "timing"
description: |
Timer for how long it takes to handle an upload API request.
tecken.upload_download_by_url:
type: "timing"
description: |
Timer for how long it takes to download the symbols zip archive from the
download url indicated in the upload API payload.
tecken.upload_dump_and_extract:
type: "timing"
description: |
Timer for how long it takes to unzip the symbols zip archive and extract
files to a temporary directory on disk.
tecken.upload_file_exists:
type: "timing"
description: |
Timer for retrieving object metadata for a file.
tecken.upload_file_upload:
type: "timing"
description: |
Timer for how long it takes to handle uploading a file to storage. This
includes determining whether the file already exists,
compressing/decompressing it, saving a record to the database, and any
other processing required.
tecken.upload_file_upload_skip:
type: "incr"
description: |
Counter for files to be uploaded to storage but were skipped because
they're already there.
tecken.upload_file_upload_upload:
type: "incr"
description: |
Counter for each file successfully uploaded to storage.
tecken.upload_gzip_payload:
type: "timing"
description: |
Timer for how long it takes to gzip a file before uploading to storage.
tecken.upload_put_object:
type: "timing"
description: |
Timer for uploading a file to storage.
tecken.upload_skip_early_compressed:
type: "incr"
description: |
Counter for each time we skip uploading a *compressed* symbol file because
it exists in storage already.
tecken.upload_skip_early_uncompressed:
type: "incr"
description: |
Counter for each time we skip uploading an *uncompressed* symbol file
because it exists in storage already.
tecken.upload_uploads:
type: "incr"
description: |
Counter for upload API requests that were successfully completed.
Tags:
* ``try``: whether or not it was a try upload
* ``bucket``: the storage bucket name files were uploaded to
tecken.useradmin_is_blocked_in_auth0:
type: "timing"
description: |
Timer for how long it takes to determine if the user is blocked in Auth0.
tecken.sentry_scrub_error:
type: "incr"
description: |
Emitted when there are errors scrubbing Sentry events. Monitor these
because it means we're missing Sentry event data.
Loading

0 comments on commit 725b8e3

Please sign in to comment.