Browse Source

perf: use orjson in sentry/lang (#70587)

Yagiz Nizipli 10 months ago
parent
commit
04e3addd18

+ 3 - 3
src/sentry/lang/dart/utils.py

@@ -4,12 +4,12 @@ import os
 import re
 from typing import Any
 
+import orjson
 import sentry_sdk
 
 from sentry.lang.java.utils import deobfuscation_template
 from sentry.models.debugfile import ProjectDebugFile
 from sentry.models.project import Project
-from sentry.utils import json
 from sentry.utils.safe import get_path
 
 # Obfuscated type values are either in the form of "xyz" or "xyz<abc>" where
@@ -60,8 +60,8 @@ def generate_dart_symbols_map(uuid: str, project: Project):
             dart_symbols_file_size_in_mb = os.path.getsize(debug_file_path) / (1024 * 1024.0)
             span.set_tag("dart_symbols_file_size_in_mb", dart_symbols_file_size_in_mb)
 
-            with open(debug_file_path) as f:
-                debug_array = json.loads(f.read())
+            with open(debug_file_path, "rb") as f:
+                debug_array = orjson.loads(f.read())
 
             if len(debug_array) % 2 != 0:
                 raise Exception("Debug array contains an odd number of elements")

+ 3 - 3
src/sentry/lang/java/utils.py

@@ -3,6 +3,7 @@ from __future__ import annotations
 import os
 from typing import Any
 
+import orjson
 import sentry_sdk
 
 from sentry import options
@@ -13,7 +14,6 @@ from sentry.lang.java.proguard import open_proguard_mapper
 from sentry.models.debugfile import ProjectDebugFile
 from sentry.models.project import Project
 from sentry.stacktraces.processing import StacktraceInfo
-from sentry.utils import json
 from sentry.utils.cache import cache_key_for_event
 from sentry.utils.safe import get_path
 
@@ -115,7 +115,7 @@ def deobfuscation_template(data, map_type, deobfuscation_fn):
     new_attachments = []
     for attachment in attachments:
         if attachment.type == "event.view_hierarchy":
-            view_hierarchy = json.loads(attachment_cache.get_data(attachment))
+            view_hierarchy = orjson.loads(attachment_cache.get_data(attachment))
             deobfuscation_fn(data, project, view_hierarchy)
 
             # Reupload to cache as a unchunked data
@@ -125,7 +125,7 @@ def deobfuscation_template(data, map_type, deobfuscation_fn):
                     id=attachment.id,
                     name=attachment.name,
                     content_type=attachment.content_type,
-                    data=json.dumps_htmlsafe(view_hierarchy).encode(),
+                    data=orjson.dumps(view_hierarchy),
                     chunks=None,
                 )
             )

+ 3 - 3
src/sentry/lang/javascript/errormapping.py

@@ -6,11 +6,11 @@ import re
 import time
 from urllib.parse import parse_qsl
 
+import orjson
 from django.conf import settings
 from django.core.cache import cache
 
 from sentry import http
-from sentry.utils import json
 from sentry.utils.meta import Meta
 from sentry.utils.safe import get_path
 from sentry.utils.strings import count_sprintf_parameters
@@ -44,7 +44,7 @@ class Processor:
         mapping = cache.get(key)
         cached_rv = None
         if mapping is not None:
-            ts, cached_rv = json.loads(mapping)
+            ts, cached_rv = orjson.loads(mapping)
             if not is_expired(ts):
                 return cached_rv
 
@@ -58,7 +58,7 @@ class Processor:
                 # Make sure we only get a 2xx to prevent caching bad data
                 response.raise_for_status()
             data = response.json()
-            cache.set(key, json.dumps([time.time(), data]), HARD_TIMEOUT)
+            cache.set(key, orjson.dumps([time.time(), data]).decode(), HARD_TIMEOUT)
         except Exception:
             if cached_rv is None:
                 raise

+ 6 - 6
src/sentry/lang/native/appconnect.py

@@ -10,13 +10,13 @@ import pathlib
 from typing import Any
 
 import jsonschema
+import orjson
 import requests
 import sentry_sdk
 from django.db import router, transaction
 
 from sentry.lang.native.sources import APP_STORE_CONNECT_SCHEMA, secret_fields
 from sentry.models.project import Project
-from sentry.utils import json
 from sentry.utils.appleconnect import appstore_connect
 
 logger = logging.getLogger(__name__)
@@ -123,7 +123,7 @@ class AppStoreConnectConfig:
         if not raw:
             raw = "[]"
 
-        all_sources = json.loads(raw)
+        all_sources = orjson.loads(raw)
         for source in all_sources:
             if source.get("type") == SYMBOL_SOURCE_TYPE_NAME and (source.get("id") == config_id):
                 return cls.from_json(source)
@@ -136,7 +136,7 @@ class AppStoreConnectConfig:
         raw = project.get_option(SYMBOL_SOURCES_PROP_NAME)
         if not raw:
             raw = "[]"
-        all_sources = json.loads(raw)
+        all_sources = orjson.loads(raw)
         return [
             s.get("id")
             for s in all_sources
@@ -177,7 +177,7 @@ class AppStoreConnectConfig:
                 data[to_redact] = {"hidden-secret": True}
         return data
 
-    def update_project_symbol_source(self, project: Project, allow_multiple: bool) -> json.JSONData:
+    def update_project_symbol_source(self, project: Project, allow_multiple: bool) -> Any:
         """Updates this configuration in the Project's symbol sources.
 
         If a symbol source of type ``appStoreConnect`` already exists the ID must match and it
@@ -194,7 +194,7 @@ class AppStoreConnectConfig:
         """
         with transaction.atomic(router.db_for_write(Project)):
             all_sources_raw = project.get_option(SYMBOL_SOURCES_PROP_NAME)
-            all_sources = json.loads(all_sources_raw) if all_sources_raw else []
+            all_sources = orjson.loads(all_sources_raw) if all_sources_raw else []
             for i, source in enumerate(all_sources):
                 if source.get("type") == SYMBOL_SOURCE_TYPE_NAME:
                     if source.get("id") == self.id:
@@ -207,7 +207,7 @@ class AppStoreConnectConfig:
             else:
                 # No matching existing appStoreConnect symbol source, append it.
                 all_sources.append(self.to_json())
-            project.update_option(SYMBOL_SOURCES_PROP_NAME, json.dumps(all_sources))
+            project.update_option(SYMBOL_SOURCES_PROP_NAME, orjson.dumps(all_sources).decode())
         return all_sources
 
 

+ 5 - 4
src/sentry/lang/native/sources.py

@@ -8,6 +8,7 @@ from datetime import datetime
 from typing import Any
 
 import jsonschema
+import orjson
 import sentry_sdk
 from django.conf import settings
 from django.urls import reverse
@@ -16,7 +17,7 @@ from rediscluster import RedisCluster
 from sentry import features, options
 from sentry.auth.system import get_system_token
 from sentry.models.project import Project
-from sentry.utils import json, metrics, redis, safe
+from sentry.utils import metrics, redis, safe
 from sentry.utils.http import get_origins
 
 logger = logging.getLogger(__name__)
@@ -374,7 +375,7 @@ def parse_sources(config, filter_appconnect=True):
         return []
 
     try:
-        sources = json.loads(config)
+        sources = orjson.loads(config)
     except Exception as e:
         raise InvalidSourcesError(f"{e}")
 
@@ -397,7 +398,7 @@ def parse_backfill_sources(sources_json, original_sources):
         return []
 
     try:
-        sources = json.loads(sources_json)
+        sources = orjson.loads(sources_json)
     except Exception as e:
         raise InvalidSourcesError("Sources are not valid serialised JSON") from e
 
@@ -431,7 +432,7 @@ def backfill_source(source, original_sources_by_id):
                 source[secret] = secret_value
 
 
-def redact_source_secrets(config_sources: json.JSONData) -> json.JSONData:
+def redact_source_secrets(config_sources: Any) -> Any:
     """
     Returns a JSONData with all of the secrets redacted from every source.
 

+ 6 - 5
src/sentry/lang/native/symbolicator.py

@@ -9,6 +9,7 @@ from dataclasses import dataclass
 from enum import Enum
 from urllib.parse import urljoin
 
+import orjson
 import sentry_sdk
 from django.conf import settings
 from requests.exceptions import RequestException
@@ -22,7 +23,7 @@ from sentry.lang.native.sources import (
 )
 from sentry.models.project import Project
 from sentry.net.http import Session
-from sentry.utils import json, metrics
+from sentry.utils import metrics
 
 MAX_ATTEMPTS = 3
 
@@ -165,8 +166,8 @@ class Symbolicator:
         (sources, process_response) = sources_for_symbolication(self.project)
         scraping_config = get_scraping_config(self.project)
         data = {
-            "sources": json.dumps(sources),
-            "scraping": json.dumps(scraping_config),
+            "sources": orjson.dumps(sources).decode(),
+            "scraping": orjson.dumps(scraping_config).decode(),
             "options": '{"dif_candidates": true}',
         }
 
@@ -182,8 +183,8 @@ class Symbolicator:
         (sources, process_response) = sources_for_symbolication(self.project)
         scraping_config = get_scraping_config(self.project)
         data = {
-            "sources": json.dumps(sources),
-            "scraping": json.dumps(scraping_config),
+            "sources": orjson.dumps(sources).decode(),
+            "scraping": orjson.dumps(scraping_config).decode(),
             "options": '{"dif_candidates": true}',
         }
 

+ 3 - 4
tests/sentry/lang/javascript/test_sourcemaps.py

@@ -1,9 +1,8 @@
 from unittest import TestCase
 
+import orjson
 from symbolic.sourcemap import SourceMapTokenMatch, SourceMapView
 
-from sentry.utils import json
-
 sourcemap = b"""{
     "version":3,
     "file":"file.min.js",
@@ -13,7 +12,7 @@ sourcemap = b"""{
     "sourceRoot": "foo"
 }"""
 
-indexed_sourcemap_example = json.dumps(
+indexed_sourcemap_example = orjson.dumps(
     {
         "version": 3,
         "file": "min.js",
@@ -48,7 +47,7 @@ indexed_sourcemap_example = json.dumps(
             },
         ],
     }
-).encode("utf-8")
+)
 
 
 class FindSourceTest(TestCase):

+ 12 - 14
tests/sentry/lang/native/test_appconnect.py

@@ -1,15 +1,15 @@
 import pathlib
 import uuid
 from datetime import datetime
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, Any
 from unittest import mock
 
+import orjson
 import pytest
 from django.utils import timezone
 
 from sentry.lang.native import appconnect
 from sentry.testutils.pytest.fixtures import django_db_all
-from sentry.utils import json
 from sentry.utils.appleconnect import appstore_connect
 
 if TYPE_CHECKING:
@@ -23,7 +23,7 @@ class TestAppStoreConnectConfig:
         return timezone.now()
 
     @pytest.fixture
-    def data(self, now: datetime) -> json.JSONData:
+    def data(self, now: datetime) -> Any:
         return {
             "type": "appStoreConnect",
             "id": "abc123",
@@ -36,7 +36,7 @@ class TestAppStoreConnectConfig:
             "bundleId": "com.example.app",
         }
 
-    def test_from_json_basic(self, data: json.JSONData, now: datetime) -> None:
+    def test_from_json_basic(self, data: Any, now: datetime) -> None:
         config = appconnect.AppStoreConnectConfig.from_json(data)
         assert config.type == "appStoreConnect"
         assert config.id == data["id"]
@@ -46,13 +46,13 @@ class TestAppStoreConnectConfig:
         assert config.appName == data["appName"]
         assert config.bundleId == data["bundleId"]
 
-    def test_to_json(self, data: json.JSONData, now: datetime) -> None:
+    def test_to_json(self, data: Any, now: datetime) -> None:
         config = appconnect.AppStoreConnectConfig.from_json(data)
         new_data = config.to_json()
 
         assert new_data == data
 
-    def test_to_redacted_json(self, data: json.JSONData, now: datetime) -> None:
+    def test_to_redacted_json(self, data: Any, now: datetime) -> None:
         config = appconnect.AppStoreConnectConfig.from_json(data)
         new_data = config.to_redacted_json()
 
@@ -62,9 +62,7 @@ class TestAppStoreConnectConfig:
         assert new_data == data
 
     @django_db_all
-    def test_from_project_config_empty_sources(
-        self, default_project: "Project", data: json.JSONData
-    ) -> None:
+    def test_from_project_config_empty_sources(self, default_project: "Project", data: Any) -> None:
         with pytest.raises(KeyError):
             appconnect.AppStoreConnectConfig.from_project_config(default_project, "not-an-id")
 
@@ -94,16 +92,16 @@ class TestAppStoreConnectConfigUpdateProjectSymbolSource:
         assert cfg == config
 
         raw = default_project.get_option(appconnect.SYMBOL_SOURCES_PROP_NAME, default="[]")
-        stored_sources = json.loads(raw)
+        stored_sources = orjson.loads(raw)
         assert stored_sources == sources
 
     @django_db_all
     def test_new_sources_with_existing(
         self, default_project: "Project", config: appconnect.AppStoreConnectConfig
     ) -> None:
-        old_sources = json.dumps(
+        old_sources = orjson.dumps(
             [{"type": "not-this-one", "id": "a"}, {"type": "not-this-one", "id": "b"}]
-        )
+        ).decode()
         default_project.update_option(appconnect.SYMBOL_SOURCES_PROP_NAME, old_sources)
 
         sources = config.update_project_symbol_source(default_project, allow_multiple=False)
@@ -112,10 +110,10 @@ class TestAppStoreConnectConfigUpdateProjectSymbolSource:
         assert cfg == config
 
         raw = default_project.get_option(appconnect.SYMBOL_SOURCES_PROP_NAME, default="[]")
-        stored_sources = json.loads(raw)
+        stored_sources = orjson.loads(raw)
         assert stored_sources == sources
 
-        new_sources = json.loads(old_sources)
+        new_sources = orjson.loads(old_sources)
         new_sources.append(cfg.to_json())
         assert stored_sources == new_sources