Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ permissions:

jobs:
check:
runs-on: ubuntu-latest
# Pinned: setup-python has no 3.14.4 build for ubuntu-26.04 (ubuntu-latest); unpin once .python-version is bumped
runs-on: ubuntu-24.04
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
Expand Down Expand Up @@ -45,7 +46,8 @@ jobs:
run: make check-deps

test:
runs-on: ubuntu-latest
# Pinned: setup-python has no 3.14.4 build for ubuntu-26.04 (ubuntu-latest); unpin once .python-version is bumped
runs-on: ubuntu-24.04
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
Expand Down Expand Up @@ -162,7 +164,8 @@ jobs:
run: docker compose -f docker-compose.e2e.yml down -v

build:
runs-on: ubuntu-latest
# Pinned: setup-python has no 3.14.4 build for ubuntu-26.04 (ubuntu-latest); unpin once .python-version is bumped
runs-on: ubuntu-24.04
needs: [check, test, e2e]

steps:
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ dependencies = [
"rich>=14.1.0",
"sentry-arroyo>=2.47.0",
"sentry-options==1.2.10",
"sentry-sdk>=2.36.0",
"sentry-sdk>=2.70.0",
"sortedcontainers>=2.4.0",
"taskbroker-client>=0.20.26,<1",
"typing-extensions>=4.15.0",
Expand Down
22 changes: 16 additions & 6 deletions src/launchpad/artifact_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from pathlib import Path
from typing import Any, Iterator, cast

import sentry_sdk
import sentry_sdk.traces

from objectstore_client import (
Client as ObjectstoreClient,
Expand Down Expand Up @@ -138,11 +138,21 @@ def process_message(
with contextlib.ExitStack() as stack:
stack.enter_context(request_context(artifact_id=artifact_id))
processing_start = time.monotonic()
scope = stack.enter_context(sentry_sdk.new_scope())
scope.set_tag("launchpad.project_id", project_id)
scope.set_tag("launchpad.organization_id", organization_id)
scope.set_tag("launchpad.preprod_artifact_id", artifact_id)
stack.enter_context(scope.start_transaction(op="subprocess", name="launchpad.process_message"))
scope = stack.enter_context(sentry_sdk.isolation_scope())
sentry_sdk.traces.new_trace()
artifact_attributes = {
"launchpad.project_id": project_id,
"launchpad.organization_id": organization_id,
"launchpad.preprod_artifact_id": artifact_id,
}
# Tags apply to errors, attributes apply to spans and logs
scope.set_tags(artifact_attributes)
scope.set_attributes(artifact_attributes)
stack.enter_context(
sentry_sdk.traces.start_span(
name="launchpad.process_message", attributes={"sentry.op": "subprocess"}, parent_span=None
)
)
statsd.increment("artifact.processing.started")
logger.info(f"Processing artifact {artifact_id} (project: {project_id}, org: {organization_id})")
try:
Expand Down
26 changes: 15 additions & 11 deletions src/launchpad/artifacts/android/aab.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from pathlib import Path
from typing import Callable

import sentry_sdk
import sentry_sdk.traces

from launchpad.parsers.android.dex.dex_mapping import DexMapping
from launchpad.parsers.android.icon.proto_xml_drawable_parser import (
Expand Down Expand Up @@ -41,7 +41,7 @@ def __init__(self, path: Path, cleanup: None | Callable[[], None] = None) -> Non
self._dex_mapping: DexMapping | None = None
self._universal_apk: APK | None = None

@sentry_sdk.trace
@sentry_sdk.traces.trace
def get_manifest(self) -> AndroidManifest:
if self._manifest is not None:
return self._manifest
Expand All @@ -61,7 +61,7 @@ def get_manifest(self) -> AndroidManifest:
self._manifest = ProtoXmlUtils.proto_xml_to_android_manifest(manifest_buffer, proto_res_tables)
return self._manifest

@sentry_sdk.trace
@sentry_sdk.traces.trace
def get_resource_tables(self) -> list[ProtobufResourceTable]: # type: ignore[override]
if self._resource_table is not None:
return [self._resource_table]
Expand All @@ -79,25 +79,27 @@ def get_resource_tables(self) -> list[ProtobufResourceTable]: # type: ignore[ov
self._resource_table = ProtobufResourceTable(arsc_buffer)
return [self._resource_table]

@sentry_sdk.trace
@sentry_sdk.traces.trace
def get_primary_apks(self, device_spec: DeviceSpec = DeviceSpec()) -> list[APK]:
if self._primary_apks is not None:
return self._primary_apks

apks_dir = create_temp_directory("apks-")
try:
bundletool = Bundletool()
with sentry_sdk.start_span(op="aab", description="aab.build_primary_apks"):
with sentry_sdk.traces.start_span(name="aab.build_primary_apks", attributes={"sentry.op": "aab"}):
bundletool.build_apks(bundle_path=self.path, output_dir=apks_dir, device_spec=device_spec)

apks = []
for apk_path in apks_dir.glob("*.apk"):
tmp_dir = Path(tempfile.mkdtemp())
new_apk_path = tmp_dir / apk_path.name
with sentry_sdk.start_span(op="file.copy", description="aab.copy_primary_apk") as span:
span.set_data("apk.size", apk_path.stat().st_size)
with sentry_sdk.traces.start_span(
name="aab.copy_primary_apk", attributes={"sentry.op": "file.copy"}
) as span:
span.set_attribute("apk.size", apk_path.stat().st_size)
shutil.copyfile(apk_path, new_apk_path)
with sentry_sdk.start_span(op="aab", description="aab.initialize_primary_apk"):
with sentry_sdk.traces.start_span(name="aab.initialize_primary_apk", attributes={"sentry.op": "aab"}):
apks.append(
APK(
new_apk_path,
Expand All @@ -109,10 +111,12 @@ def get_primary_apks(self, device_spec: DeviceSpec = DeviceSpec()) -> list[APK]:
self._primary_apks = apks
return apks
finally:
with sentry_sdk.start_span(op="file.cleanup", description="aab.cleanup_primary_apks"):
with sentry_sdk.traces.start_span(
name="aab.cleanup_primary_apks", attributes={"sentry.op": "file.cleanup"}
):
cleanup_directory(apks_dir)

@sentry_sdk.trace
@sentry_sdk.traces.trace
def get_universal_apk(self, apk_dir: Path, device_spec: DeviceSpec = DeviceSpec()) -> APK:
if self._universal_apk is not None:
return self._universal_apk
Expand Down Expand Up @@ -152,7 +156,7 @@ def get_dex_mapping(self) -> DexMapping | None:
self._dex_mapping = DexMapping(dex_mapping_buffer)
return self._dex_mapping

@sentry_sdk.trace
@sentry_sdk.traces.trace
def get_app_icon(self) -> bytes | None:
manifest = self.get_manifest()
if manifest.application is None:
Expand Down
46 changes: 22 additions & 24 deletions src/launchpad/artifacts/android/apk.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from pathlib import Path
from typing import IO, Callable, Iterator

import sentry_sdk
import sentry_sdk.traces

from launchpad.parsers.android.dex.dex_mapping import DexMapping
from launchpad.parsers.android.icon.binary_xml_drawable_parser import (
Expand Down Expand Up @@ -40,7 +40,7 @@ def __init__(
super().__init__(path, cleanup=cleanup)
self._dex_mapping = dex_mapping
self._zip_provider = ZipProvider(path)
with sentry_sdk.start_span(op="file.extract", description="apk.extract"):
with sentry_sdk.traces.start_span(name="apk.extract", attributes={"sentry.op": "file.extract"}):
self._extract_dir = self._zip_provider.extract_to_temp_directory()
self._manifest: AndroidManifest | None = None
self._resource_table: BinaryResourceTable | None = None
Expand All @@ -54,7 +54,7 @@ def raw_file(self) -> Iterator[IO[bytes]]:
finally:
f.close()

@sentry_sdk.trace
@sentry_sdk.traces.trace
def get_manifest(self) -> AndroidManifest:
if self._manifest is not None:
return self._manifest
Expand All @@ -74,7 +74,7 @@ def get_manifest(self) -> AndroidManifest:
self._manifest = AxmlUtils.binary_xml_to_android_manifest(manifest_buffer, binary_res_tables)
return self._manifest

@sentry_sdk.trace
@sentry_sdk.traces.trace
def get_resource_tables(self) -> list[BinaryResourceTable]: # type: ignore[override]
if self._resource_table is not None:
return [self._resource_table]
Expand All @@ -94,7 +94,7 @@ def get_resource_tables(self) -> list[BinaryResourceTable]: # type: ignore[over
self._resource_table = BinaryResourceTable(arsc_buffer)
return [self._resource_table]

@sentry_sdk.trace
@sentry_sdk.traces.trace
def get_class_definitions(self) -> list[ClassDefinition]:
if self._class_definitions is not None:
return self._class_definitions
Expand Down Expand Up @@ -122,25 +122,23 @@ def get_class_definitions(self) -> list[ClassDefinition]:
logger.error(f"Failed to read DEX file {dex_file}: {e}")
continue

if (span := sentry_sdk.get_current_span()) is not None:
span.set_data("dex_stats.file_count", dex_stats.file_count)
span.set_data("dex_stats.total_bytes", dex_stats.total_bytes)
span.set_data("dex_stats.read_duration_ms", dex_stats.read_duration_ms)
span.set_data("dex_stats.parser.duration_ms", dex_stats.parser_duration_ms)
span.set_data("dex_stats.parser.method_duration_ms", dex_stats.parser_method_duration_ms)
span.set_data("dex_stats.parser.field_duration_ms", dex_stats.parser_field_duration_ms)
span.set_data("dex_stats.parser.class_count", dex_stats.parser_class_count)
span.set_data("dex_stats.parser.method_count", dex_stats.parser_method_count)
span.set_data("dex_stats.parser.field_count", dex_stats.parser_field_count)
span.set_data(
"dex_stats.parser.method_annotation_scan_steps",
dex_stats.parser_method_annotation_scan_steps,
if (span := sentry_sdk.traces.get_current_span()) is not None:
span.set_attributes(
{
"dex_stats.file_count": dex_stats.file_count,
"dex_stats.total_bytes": dex_stats.total_bytes,
"dex_stats.read_duration_ms": dex_stats.read_duration_ms,
"dex_stats.parser.duration_ms": dex_stats.parser_duration_ms,
"dex_stats.parser.method_duration_ms": dex_stats.parser_method_duration_ms,
"dex_stats.parser.field_duration_ms": dex_stats.parser_field_duration_ms,
"dex_stats.parser.class_count": dex_stats.parser_class_count,
"dex_stats.parser.method_count": dex_stats.parser_method_count,
"dex_stats.parser.field_count": dex_stats.parser_field_count,
"dex_stats.parser.method_annotation_scan_steps": dex_stats.parser_method_annotation_scan_steps,
"dex_stats.parser.field_annotation_scan_steps": dex_stats.parser_field_annotation_scan_steps,
"dex_stats.parser.mapping_present": dex_stats.parser_mapping_present,
}
)
span.set_data(
"dex_stats.parser.field_annotation_scan_steps",
dex_stats.parser_field_annotation_scan_steps,
)
span.set_data("dex_stats.parser.mapping_present", dex_stats.parser_mapping_present)

return self._class_definitions

Expand All @@ -151,7 +149,7 @@ def get_apksigner_certs(self) -> str:
apksigner = Apksigner()
return apksigner.get_certs(self.path)

@sentry_sdk.trace
@sentry_sdk.traces.trace
def get_app_icon(self) -> bytes | None:
manifest = self.get_manifest()
if manifest.application is None:
Expand Down
4 changes: 2 additions & 2 deletions src/launchpad/artifacts/android/zipped_aab.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from pathlib import Path
from typing import Callable

import sentry_sdk
import sentry_sdk.traces

from launchpad.parsers.android.dex.dex_mapping import DexMapping

Expand All @@ -25,7 +25,7 @@ def __init__(self, path: Path, cleanup: None | Callable[[], None] = None) -> Non
def get_manifest(self) -> AndroidManifest:
return self.get_aab().get_manifest()

@sentry_sdk.trace
@sentry_sdk.traces.trace
def get_aab(self) -> AAB:
if self._aab is not None:
return self._aab
Expand Down
4 changes: 2 additions & 2 deletions src/launchpad/artifacts/android/zipped_apk.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from pathlib import Path
from typing import Callable

import sentry_sdk
import sentry_sdk.traces

from launchpad.parsers.android.dex.dex_mapping import DexMapping

Expand All @@ -24,7 +24,7 @@ def __init__(self, path: Path, cleanup: None | Callable[[], None] = None) -> Non
def get_manifest(self) -> AndroidManifest:
return self.get_primary_apk().get_manifest()

@sentry_sdk.trace
@sentry_sdk.traces.trace
def get_primary_apk(self) -> APK:
if self._primary_apk is not None:
return self._primary_apk
Expand Down
Loading
Loading