Skip to content
This repository was archived by the owner on Jun 13, 2025. It is now read-only.

Commit 9beb3f2

Browse files
lvthanh03Swatinem
andauthored
Replace Sentry metrics with Prometheus metrics (#918)
Co-authored-by: Arpad Borsos <[email protected]>
1 parent 02e7bcd commit 9beb3f2

20 files changed

+211
-173
lines changed

graphql_api/tests/test_views.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import json
2-
from unittest.mock import Mock, call, patch
2+
from unittest.mock import Mock, patch
33

44
from ariadne import ObjectType, make_executable_schema
55
from ariadne.validation import cost_directive
@@ -201,11 +201,12 @@ async def test_query_metrics_extension_set_type_and_name_timeout(
201201
assert extension.operation_type == "unknown_type"
202202
assert extension.operation_name == "unknown_name"
203203

204-
@patch("sentry_sdk.metrics.incr")
204+
@patch("graphql_api.views.GQL_REQUEST_MADE_COUNTER.labels")
205+
@patch("graphql_api.views.GQL_ERROR_TYPE_COUNTER.labels")
205206
@patch("graphql_api.views.AsyncGraphqlView._check_ratelimit")
206207
@override_settings(DEBUG=False, GRAPHQL_RATE_LIMIT_RPM=1000)
207208
async def test_when_rate_limit_reached(
208-
self, mocked_check_ratelimit, mocked_sentry_incr
209+
self, mocked_check_ratelimit, mocked_error_counter, mocked_request_counter
209210
):
210211
schema = generate_cost_test_schema()
211212
mocked_check_ratelimit.return_value = True
@@ -217,11 +218,10 @@ async def test_when_rate_limit_reached(
217218
== "It looks like you've hit the rate limit of 1000 req/min. Try again later."
218219
)
219220

220-
expected_calls = [
221-
call("graphql.info.request_made", tags={"path": "/graphql/gh"}),
222-
call("graphql.error.rate_limit", tags={"path": "/graphql/gh"}),
223-
]
224-
mocked_sentry_incr.assert_has_calls(expected_calls)
221+
mocked_error_counter.assert_called_with(
222+
error_type="rate_limit", path="/graphql/gh"
223+
)
224+
mocked_request_counter.assert_called_with(path="/graphql/gh")
225225

226226
@override_settings(
227227
DEBUG=False, GRAPHQL_RATE_LIMIT_RPM=0, GRAPHQL_RATE_LIMIT_ENABLED=False

graphql_api/views.py

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@
1919
)
2020
from graphql import DocumentNode
2121
from sentry_sdk import capture_exception
22-
from sentry_sdk import metrics as sentry_metrics
23-
from shared.metrics import Counter, Histogram
22+
from shared.metrics import Counter, Histogram, inc_counter
2423

2524
from codecov.commands.exceptions import BaseException
2625
from codecov.commands.executor import get_executor_from_request
@@ -51,6 +50,17 @@
5150
buckets=[0.05, 0.1, 0.25, 0.5, 0.75, 1, 2, 5, 10, 30],
5251
)
5352

53+
GQL_REQUEST_MADE_COUNTER = Counter(
54+
"api_gql_requests_made",
55+
"Total API GQL requests made",
56+
["path"],
57+
)
58+
59+
GQL_ERROR_TYPE_COUNTER = Counter(
60+
"api_gql_errors",
61+
"Number of times API GQL endpoint failed with an exception by type",
62+
["error_type", "path"],
63+
)
5464

5565
# covers named and 3 unnamed operations (see graphql_api/types/query/query.py)
5666
GQL_TYPE_AND_NAME_PATTERN = r"^(query|mutation|subscription)(?:\(\$input:|) (\w+)(?:\(| \(|{| {|!)|^(?:{) (me|owner|config)(?:\(| |{)"
@@ -109,9 +119,13 @@ def request_started(self, context):
109119
"""
110120
self.set_type_and_name(query=context["clean_query"])
111121
self.start_timestamp = time.perf_counter()
112-
GQL_HIT_COUNTER.labels(
113-
operation_type=self.operation_type, operation_name=self.operation_name
114-
).inc()
122+
inc_counter(
123+
GQL_HIT_COUNTER,
124+
labels=dict(
125+
operation_type=self.operation_type,
126+
operation_name=self.operation_name,
127+
),
128+
)
115129

116130
def request_finished(self, context):
117131
"""
@@ -226,10 +240,12 @@ async def post(self, request, *args, **kwargs):
226240
"user": request.user,
227241
}
228242
log.info("GraphQL Request", extra=log_data)
229-
sentry_metrics.incr("graphql.info.request_made", tags={"path": req_path})
230-
243+
inc_counter(GQL_REQUEST_MADE_COUNTER, labels=dict(path=req_path))
231244
if self._check_ratelimit(request=request):
232-
sentry_metrics.incr("graphql.error.rate_limit", tags={"path": req_path})
245+
inc_counter(
246+
GQL_ERROR_TYPE_COUNTER,
247+
labels=dict(error_type="rate_limit", path=req_path),
248+
)
233249
return JsonResponse(
234250
data={
235251
"status": 429,
@@ -245,7 +261,10 @@ async def post(self, request, *args, **kwargs):
245261
data = json.loads(content)
246262

247263
if "errors" in data:
248-
sentry_metrics.incr("graphql.error.all", tags={"path": req_path})
264+
inc_counter(
265+
GQL_ERROR_TYPE_COUNTER,
266+
labels=dict(error_type="all", path=req_path),
267+
)
249268
try:
250269
if data["errors"][0]["extensions"]["cost"]:
251270
costs = data["errors"][0]["extensions"]["cost"]
@@ -257,9 +276,12 @@ async def post(self, request, *args, **kwargs):
257276
request_body=req_body,
258277
),
259278
)
260-
sentry_metrics.incr(
261-
"graphql.error.query_cost_exceeded",
262-
tags={"path": req_path},
279+
inc_counter(
280+
GQL_ERROR_TYPE_COUNTER,
281+
labels=dict(
282+
error_type="query_cost_exceeded",
283+
path=req_path,
284+
),
263285
)
264286
return HttpResponseBadRequest(
265287
JsonResponse("Your query is too costly.")

upload/helpers.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -790,14 +790,15 @@ def get_version_from_headers(headers):
790790
return "unknown-user-agent"
791791

792792

793-
def generate_upload_sentry_metrics_tags(
793+
def generate_upload_prometheus_metrics_labels(
794794
action,
795795
request,
796796
is_shelter_request,
797797
endpoint: Optional[str] = None,
798798
repository: Optional[Repository] = None,
799799
position: Optional[str] = None,
800800
upload_version: Optional[str] = None,
801+
include_empty_labels: bool = True,
801802
):
802803
metrics_tags = dict(
803804
agent=get_agent_from_headers(request.headers),
@@ -806,13 +807,23 @@ def generate_upload_sentry_metrics_tags(
806807
endpoint=endpoint,
807808
is_using_shelter="yes" if is_shelter_request else "no",
808809
)
810+
811+
repo_visibility = None
809812
if repository:
810-
metrics_tags["repo_visibility"] = (
811-
"private" if repository.private is True else "public"
812-
)
813-
if position:
814-
metrics_tags["position"] = position
815-
if upload_version:
816-
metrics_tags["upload_version"] = upload_version
813+
repo_visibility = "private" if repository.private else "public"
814+
815+
optional_fields = {
816+
"repo_visibility": repo_visibility,
817+
"position": position,
818+
"upload_version": upload_version,
819+
}
820+
821+
metrics_tags.update(
822+
{
823+
field: value
824+
for field, value in optional_fields.items()
825+
if value or include_empty_labels
826+
}
827+
)
817828

818829
return metrics_tags

upload/metrics.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from shared.metrics import Counter
2+
3+
API_UPLOAD_COUNTER = Counter(
4+
"api_upload",
5+
"Total API upload endpoint requests",
6+
[
7+
"agent",
8+
"version",
9+
"action",
10+
"endpoint",
11+
"is_using_shelter",
12+
"repo_visibility",
13+
"position",
14+
"upload_version",
15+
],
16+
)

upload/tests/views/test_commits.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ def test_commit_github_oidc_auth(mock_jwks_client, mock_jwt_decode, db, mocker):
328328
private=False, author__username="codecov", name="the_repo"
329329
)
330330
mocked_call = mocker.patch.object(TaskService, "update_commit")
331-
mock_sentry_metrics = mocker.patch("upload.views.commits.sentry_metrics.incr")
331+
mock_prometheus_metrics = mocker.patch("upload.metrics.API_UPLOAD_COUNTER.labels")
332332
mock_jwt_decode.return_value = {
333333
"repository": f"url/{repository.name}",
334334
"repository_owner": repository.author.username,
@@ -374,15 +374,15 @@ def test_commit_github_oidc_auth(mock_jwks_client, mock_jwt_decode, db, mocker):
374374
}
375375
assert expected_response == response_json
376376
mocked_call.assert_called_with(commitid="commit_sha", repoid=repository.repoid)
377-
mock_sentry_metrics.assert_called_with(
378-
"upload",
379-
tags={
377+
mock_prometheus_metrics.assert_called_with(
378+
**{
380379
"agent": "cli",
381380
"version": "0.4.7",
382381
"action": "coverage",
383382
"endpoint": "create_commit",
384383
"repo_visibility": "public",
385384
"is_using_shelter": "no",
386385
"position": "end",
386+
"upload_version": None,
387387
},
388388
)

upload/tests/views/test_empty_upload.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def test_empty_upload_with_yaml_ignored_files(
5151
mocker.patch.object(
5252
CanDoCoverageUploadsPermission, "has_permission", return_value=True
5353
)
54-
mock_sentry_metrics = mocker.patch("upload.views.empty_upload.sentry_metrics.incr")
54+
mock_prometheus_metrics = mocker.patch("upload.metrics.API_UPLOAD_COUNTER.labels")
5555
mock_final_yaml.return_value = UserYaml(
5656
{
5757
"ignore": [
@@ -93,16 +93,16 @@ def test_empty_upload_with_yaml_ignored_files(
9393
notify_mock.assert_called_once_with(
9494
repoid=repository.repoid, commitid=commit.commitid, empty_upload="pass"
9595
)
96-
mock_sentry_metrics.assert_called_with(
97-
"upload",
98-
tags={
96+
mock_prometheus_metrics.assert_called_with(
97+
**{
9998
"agent": "cli",
10099
"version": "0.4.7",
101100
"action": "coverage",
102101
"endpoint": "empty_upload",
103102
"repo_visibility": "private",
104103
"is_using_shelter": "no",
105104
"position": "end",
105+
"upload_version": None,
106106
},
107107
)
108108

upload/tests/views/test_reports.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def test_reports_get_not_allowed(client, mocker, db):
4141

4242
def test_reports_post(client, db, mocker):
4343
mocked_call = mocker.patch.object(TaskService, "preprocess_upload")
44-
mock_sentry_metrics = mocker.patch("upload.views.reports.sentry_metrics.incr")
44+
mock_prometheus_metrics = mocker.patch("upload.metrics.API_UPLOAD_COUNTER.labels")
4545
repository = RepositoryFactory(
4646
name="the_repo", author__username="codecov", author__service="github"
4747
)
@@ -65,16 +65,16 @@ def test_reports_post(client, db, mocker):
6565
commit_id=commit.id, code="code1", report_type=CommitReport.ReportType.COVERAGE
6666
).exists()
6767
mocked_call.assert_called_with(repository.repoid, commit.commitid, "code1")
68-
mock_sentry_metrics.assert_called_with(
69-
"upload",
70-
tags={
68+
mock_prometheus_metrics.assert_called_with(
69+
**{
7170
"agent": "cli",
7271
"version": "0.4.7",
7372
"action": "coverage",
7473
"endpoint": "create_report",
7574
"repo_visibility": "private",
7675
"is_using_shelter": "no",
7776
"position": "end",
77+
"upload_version": None,
7878
},
7979
)
8080

@@ -343,7 +343,7 @@ def test_reports_results_post_successful_github_oidc_auth(
343343
mock_jwks_client, mock_jwt_decode, client, db, mocker
344344
):
345345
mocked_task = mocker.patch("services.task.TaskService.create_report_results")
346-
mock_sentry_metrics = mocker.patch("upload.views.reports.sentry_metrics.incr")
346+
mock_prometheus_metrics = mocker.patch("upload.metrics.API_UPLOAD_COUNTER.labels")
347347
mocker.patch.object(
348348
CanDoCoverageUploadsPermission, "has_permission", return_value=True
349349
)
@@ -383,16 +383,16 @@ def test_reports_results_post_successful_github_oidc_auth(
383383
report_id=commit_report.id,
384384
).exists()
385385
mocked_task.assert_called_once()
386-
mock_sentry_metrics.assert_called_with(
387-
"upload",
388-
tags={
386+
mock_prometheus_metrics.assert_called_with(
387+
**{
389388
"agent": "cli",
390389
"version": "0.4.7",
391390
"action": "coverage",
392391
"endpoint": "create_report_results",
393392
"repo_visibility": "private",
394393
"is_using_shelter": "no",
395394
"position": "end",
395+
"upload_version": None,
396396
},
397397
)
398398

upload/tests/views/test_test_results.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
def test_upload_test_results(db, client, mocker, mock_redis):
2222
upload = mocker.patch.object(TaskService, "upload")
23-
mock_sentry_metrics = mocker.patch("upload.views.test_results.metrics.incr")
23+
mock_prometheus_metrics = mocker.patch("upload.metrics.API_UPLOAD_COUNTER.labels")
2424
create_presigned_put = mocker.patch(
2525
"services.archive.StorageService.create_presigned_put",
2626
return_value="test-presigned-put",
@@ -100,16 +100,16 @@ def test_upload_test_results(db, client, mocker, mock_redis):
100100
report_code=None,
101101
report_type="test_results",
102102
)
103-
mock_sentry_metrics.assert_called_with(
104-
"upload",
105-
tags={
103+
mock_prometheus_metrics.assert_called_with(
104+
**{
106105
"agent": "cli",
107106
"version": "0.4.7",
108107
"action": "test_results",
109108
"endpoint": "test_results",
110109
"repo_visibility": "private",
111110
"is_using_shelter": "no",
112111
"position": "end",
112+
"upload_version": None,
113113
},
114114
)
115115

upload/tests/views/test_upload_completion.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,7 @@ def test_upload_completion_view_processed_uploads(mocked_manual_trigger, db, moc
5151
mocker.patch.object(
5252
CanDoCoverageUploadsPermission, "has_permission", return_value=True
5353
)
54-
mock_sentry_metrics = mocker.patch(
55-
"upload.views.upload_completion.sentry_metrics.incr"
56-
)
54+
mock_prometheus_metrics = mocker.patch("upload.metrics.API_UPLOAD_COUNTER.labels")
5755
repository = RepositoryFactory(
5856
name="the_repo", author__username="codecov", author__service="github"
5957
)
@@ -88,16 +86,16 @@ def test_upload_completion_view_processed_uploads(mocked_manual_trigger, db, moc
8886
"uploads_error": 0,
8987
}
9088
mocked_manual_trigger.assert_called_once_with(repository.repoid, commit.commitid)
91-
mock_sentry_metrics.assert_called_with(
92-
"upload",
93-
tags={
89+
mock_prometheus_metrics.assert_called_with(
90+
**{
9491
"agent": "cli",
9592
"version": "0.4.7",
9693
"action": "coverage",
9794
"endpoint": "upload_complete",
9895
"repo_visibility": "private",
9996
"is_using_shelter": "no",
10097
"position": "end",
98+
"upload_version": None,
10199
},
102100
)
103101

0 commit comments

Comments
 (0)