Skip to content
Merged
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
286 changes: 0 additions & 286 deletions src/sentry/profiles/device.py → src/sentry/api/helpers/ios_models.py
Original file line number Diff line number Diff line change
@@ -1,82 +1,3 @@
from enum import Enum

GIB = 1024 * 1024 * 1024
UNKNOWN_DEVICE = "Unknown Device"


class DeviceClass(Enum):
UNCLASSIFIED = 0
LOW_END = 1
MID_END = 2
HIGH_END = 3

def __str__(self) -> str:
return {0: "unclassified", 1: "low", 2: "mid", 3: "high"}[self.value]


class Platform(Enum):
UNKNOWN = 0
IOS_DEVICE = 1
IOS_SIMULATOR = 2
ANDROID_DEVICE = 3
ANDROID_EMULATOR = 4


# classify_device classifies a device as being low, mid, or high end
def classify_device(
model: str,
os_name: str,
is_emulator: bool,
cpu_frequencies: tuple[int] | None = None,
physical_memory_bytes: int | None = None,
) -> DeviceClass:
platform = get_platform(os_name, is_emulator)
if platform in (Platform.IOS_SIMULATOR, Platform.ANDROID_EMULATOR):
"""
We exclude simulators/emulators from performance statistics for
low/mid/high end because these run on arbitrary PC hardware and
will make our data noisy.
"""
return DeviceClass.UNCLASSIFIED

if platform == Platform.IOS_DEVICE:
frequencies = ios_cpu_core_max_frequencies_mhz(model)
if core_frequency(frequencies) < 2000:
return DeviceClass.LOW_END # less than 2GHz clock speed
if core_frequency(frequencies) < 3000:
return DeviceClass.MID_END # less than 3Ghz clock speed
return DeviceClass.HIGH_END

if platform == Platform.ANDROID_DEVICE and cpu_frequencies and physical_memory_bytes:
if number_of_cores(cpu_frequencies) < 8 or physical_memory_bytes < (4 * GIB):
return DeviceClass.LOW_END # less than 8 cores or less than 4GiB of RAM
if core_frequency(cpu_frequencies) < 2500:
return DeviceClass.MID_END # less than 2.5GHz clock speed
return DeviceClass.HIGH_END

return DeviceClass.UNCLASSIFIED


def number_of_cores(frequencies: tuple[int, ...] | None) -> int:
return len(frequencies) if frequencies is not None else 0


def core_frequency(frequencies: tuple[int, ...] | None) -> int:
return max(frequencies) if frequencies is not None else 0


def get_platform(device_os_name: str, is_emulator: bool) -> Platform:
if device_os_name == "android":
if is_emulator:
return Platform.ANDROID_EMULATOR
return Platform.ANDROID_DEVICE
if device_os_name in ("iPhone OS", "iOS", "iPadOS", "watchOS", "tvOS"):
if is_emulator:
return Platform.IOS_SIMULATOR
return Platform.IOS_DEVICE
return Platform.UNKNOWN


IPHONE4 = "iPhone 4"
IPHONE5 = "iPhone 5"
IPHONE5C = "iPhone 5c"
Expand Down Expand Up @@ -367,210 +288,3 @@ def get_platform(device_os_name: str, is_emulator: bool) -> Platform:
"i386": "iOS Simulator (i386)",
"x86_64": "iOS Simulator (x86_64)",
}

CPU1 = (520, 520)
CPU2 = (1000, 1000)
CPU3 = (1300, 1300)
CPU4 = (1400, 1400)
CPU5 = (1500, 1500)
CPU6 = (1800, 1800)
CPU7 = (1850, 1850)
CPU8 = (2160, 2160)
CPU9 = (2260, 2260)
CPU10 = (2320, 2320)
CPU11 = (2340, 2340)
CPU12 = (1500, 1500, 1500)
CPU13 = (2380, 2380, 2380, 1300, 1300, 1300)
CPU14 = (2390, 2390, 1420, 1420, 1420, 1420)
CPU15 = (2490, 2490, 1587, 1587, 1587, 1587)
CPU16 = (2650, 2650, 1600, 1600, 1600, 1600)
CPU17 = (2490, 2490, 2490, 2490, 1587, 1587, 1587, 1587)
CPU18 = (3100, 3100, 1800, 1800, 1800, 1800)
CPU19 = (3230, 3230, 1800, 1800, 1800, 1800)
CPU20 = (2900, 2900, 1800, 1800, 1800, 1800)
CPU21 = (3200, 3200, 3200, 3200, 2060, 2060, 2060, 2060)
CPU22 = (3230, 3230, 2020, 2020, 2020, 2020)
CPU23 = (3460, 3460, 2020, 2020, 2020, 2020)


IOS_CPU_FREQUENCIES: dict[str, tuple[int, ...]] = {
"iPhone1,1": (412,),
"iPhone1,2": (412,),
"iPod1,1": (412,),
"Watch1,1": (520,),
"Watch1,2": (520,),
"iPod1,2": (533,),
"iPhone2,1": (600,),
"iPod3,1": (600,),
"iPhone3,1": (800,),
"iPhone3,2": (800,),
"iPhone3,3": (800,),
"iPod4,1": (800,),
"iPhone4,1": (800,),
"iPad1,1": (1000,),
"AppleTV1,1": (1000,),
"AppleTV2,1": (1000,),
"AppleTV3,1": (1000,),
"AppleTV3,2": (1000,),
"Watch2,6": CPU1,
"Watch2,7": CPU1,
"Watch2,3": CPU1,
"Watch2,4": CPU1,
# The clock speeds for the Watch3,4,5 have not been published, we only
# know that they are dual core 64-bit chips. Here we will assume that
# they use the confirmed clock frequency from the Watch2, but in reality
# they are likely higher.
"Watch3,1": CPU1,
"Watch3,2": CPU1,
"Watch3,3": CPU1,
"Watch3,4": CPU1,
"Watch4,1": CPU1,
"Watch4,2": CPU1,
"Watch4,3": CPU1,
"Watch4,4": CPU1,
"Watch5,1": CPU1,
"Watch5,2": CPU1,
"Watch5,3": CPU1,
"Watch5,4": CPU1,
"Watch5,9": CPU2,
"Watch5,10": CPU2,
"Watch5,11": CPU2,
"Watch5,12": CPU2,
"Watch6,3": CPU2,
"Watch6,4": CPU2,
"iPod5,1": (800, 800),
"iPad2,1": CPU2,
"iPad2,2": CPU2,
"iPad2,3": CPU2,
"iPad2,4": CPU2,
"iPad2,5": CPU2,
"iPad2,6": CPU2,
"iPad2,7": CPU2,
"iPad3,1": CPU2,
"iPad3,2": CPU2,
"iPad3,3": CPU2,
"iPod7,1": (1100, 1100),
"iPhone5,1": CPU3,
"iPhone5,2": CPU3,
"iPhone5,3": CPU3,
"iPhone5,4": CPU3,
"iPhone6,1": CPU3,
"iPhone6,2": CPU3,
"iPad4,4": CPU3,
"iPad4,5": CPU3,
"iPad4,6": CPU3,
"iPad4,7": CPU3,
"iPad4,8": CPU3,
"iPad4,9": CPU3,
"iPhone7,1": CPU4,
"iPhone7,2": CPU4,
"iPad3,4": CPU4,
"iPad3,5": CPU4,
"iPad3,6": CPU4,
"iPad4,1": CPU4,
"iPad4,2": CPU4,
"iPad4,3": CPU4,
"iPad5,1": CPU5,
"iPad5,2": CPU5,
"AppleTV5,3": CPU5,
"iPod9,1": (1630, 1630),
"iPad6,11": CPU6,
"iPad6,12": CPU6,
"iPhone8,1": CPU7,
"iPhone8,2": CPU7,
"iPhone8,4": CPU7,
"iPad6,3": CPU8,
"iPad6,4": CPU8,
"iPad6,7": CPU9,
"iPad6,8": CPU9,
"iPad7,11": CPU10,
"iPad7,12": CPU10,
"iPad7,5": CPU11,
"iPad7,6": CPU11,
"iPhone9,1": CPU11,
"iPhone9,2": CPU11,
"iPhone9,3": CPU11,
"iPhone9,4": CPU11,
"iPad5,3": CPU12,
"iPad5,4": CPU12,
"AppleTV6,2": (2380, 2380, 2380),
"iPad7,1": CPU13,
"iPad7,2": CPU13,
"iPad7,3": CPU13,
"iPad7,4": CPU13,
"iPhone10,1": CPU14,
"iPhone10,2": CPU14,
"iPhone10,3": CPU14,
"iPhone10,4": CPU14,
"iPhone10,5": CPU14,
"iPhone10,6": CPU14,
"iPad11,1": CPU15,
"iPad11,2": CPU15,
"iPad11,3": CPU15,
"iPad11,4": CPU15,
"iPad11,6": CPU15,
"iPad11,7": CPU15,
"iPhone11,2": CPU15,
"iPhone11,4": CPU15,
"iPhone11,6": CPU15,
"iPhone11,8": CPU15,
"AppleTV11,1": CPU15,
"iPhone12,1": CPU16,
"iPhone12,3": CPU16,
"iPhone12,5": CPU16,
"iPhone12,8": CPU16,
"iPad12,1": CPU16,
"iPad12,2": CPU16,
"iPad8,1": CPU17,
"iPad8,2": CPU17,
"iPad8,3": CPU17,
"iPad8,4": CPU17,
"iPad8,5": CPU17,
"iPad8,6": CPU17,
"iPad8,7": CPU17,
"iPad8,8": CPU17,
"iPad8,9": CPU17,
"iPad8,10": CPU17,
"iPad8,11": CPU17,
"iPad8,12": CPU17,
"iPhone13,1": CPU18,
"iPhone13,2": CPU18,
"iPhone13,3": CPU18,
"iPhone13,4": CPU18,
"iPad13,1": CPU18,
"iPad13,2": CPU18,
"iPhone14,2": CPU19,
"iPhone14,3": CPU19,
"iPhone14,4": CPU19,
"iPhone14,5": CPU19,
"iPad14,1": CPU20,
"iPad14,2": CPU20,
"iPad13,4": CPU21,
"iPad13,5": CPU21,
"iPad13,6": CPU21,
"iPad13,7": CPU21,
"iPad13,8": CPU21,
"iPad13,9": CPU21,
"iPad13,10": CPU21,
"iPad13,11": CPU21,
"iPhone14,6": CPU19,
"iPhone14,7": CPU22,
"iPhone14,8": CPU22,
"iPhone15,2": CPU23,
"iPhone15,3": CPU23,
}


def ios_cpu_core_max_frequencies_mhz(model: str) -> tuple[int, ...] | None:
if model in IOS_CPU_FREQUENCIES:
return IOS_CPU_FREQUENCIES[model]
# New unreleased device, assume device is best of class */
if model.startswith("iPhone"):
return CPU19
if model.startswith("iPad"):
return CPU21
if model.startswith("AppleTV"):
return CPU15
if model.startswith("Watch"):
return CPU6
return None # unknown device
2 changes: 1 addition & 1 deletion src/sentry/api/helpers/mobile.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations

from sentry.api.helpers.android_models import ANDROID_MODELS
from sentry.profiles.device import IOS_MODELS
from sentry.api.helpers.ios_models import IOS_MODELS


def get_readable_device_name(device: str) -> str | None:
Expand Down
34 changes: 7 additions & 27 deletions src/sentry/profiles/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
from sentry.models.eventerror import EventError
from sentry.models.organization import Organization
from sentry.models.project import Project
from sentry.profiles.device import classify_device
from sentry.profiles.java import (
convert_android_methods_to_jvm_frames,
deobfuscate_signature,
Expand All @@ -31,13 +30,16 @@
apply_stack_trace_rules_to_profile,
get_from_profiling_service,
)
from sentry.search.utils import DEVICE_CLASS
from sentry.signals import first_profile_received
from sentry.silo.base import SiloMode
from sentry.tasks.base import instrumented_task
from sentry.utils import json, metrics
from sentry.utils.outcomes import Outcome, track_outcome
from sentry.utils.sdk import set_measurement

REVERSE_DEVICE_CLASS = {next(iter(tags)): label for label, tags in DEVICE_CLASS.items()}


@instrumented_task(
name="sentry.profiles.task.process_profile",
Expand Down Expand Up @@ -344,34 +346,12 @@ def _normalize(profile: Profile, organization: Organization) -> None:
if platform not in {"cocoa", "android"} or version == "2":
return

classification_options = dict()
classification = profile.get("transaction_tags", {}).get("device.class", None)

if platform == "android":
classification_options.update(
{
"cpu_frequencies": profile["device_cpu_frequencies"],
"physical_memory_bytes": profile["device_physical_memory_bytes"],
}
)

if version == "1":
classification_options.update(
{
"model": profile["device"]["model"],
"os_name": profile["os"]["name"],
"is_emulator": profile["device"]["is_emulator"],
}
)
elif version is None:
classification_options.update(
{
"model": profile["device_model"],
"os_name": profile["device_os_name"],
"is_emulator": profile["device_is_emulator"],
}
)
if not classification:
return

classification = str(classify_device(**classification_options))
classification = REVERSE_DEVICE_CLASS.get(classification, "unknown")

if version == "1":
profile["device"]["classification"] = classification
Expand Down
31 changes: 0 additions & 31 deletions tests/sentry/profiles/test_device.py

This file was deleted.

Loading
Loading