Skip to content

⚡️ Speed up exceptions_from_error() by 8% in sentry_sdk/utils.py#7

Open
codeflash-ai[bot] wants to merge 1 commit intomasterfrom
codeflash/optimize-exceptions_from_error-2024-06-18T22.25.03
Open

⚡️ Speed up exceptions_from_error() by 8% in sentry_sdk/utils.py#7
codeflash-ai[bot] wants to merge 1 commit intomasterfrom
codeflash/optimize-exceptions_from_error-2024-06-18T22.25.03

Conversation

@codeflash-ai
Copy link

@codeflash-ai codeflash-ai bot commented Jun 18, 2024

📄 exceptions_from_error() in sentry_sdk/utils.py

📈 Performance improved by 8% (0.08x faster)

⏱️ Runtime went down from 3.35 milliseconds to 3.12 milliseconds

Explanation and details

Certainly! We can optimize the given code for better performance by addressing areas that might involve unnecessary computations, redundant dictionary operations, and excessive attribute access. Here's the optimized version.

Key Optimizations.

  1. Optimal Dictionary Manipulation:

    • Grouped dictionary initializations and modifications together to reduce the operations and thus improve speed.
  2. Reduced Redundant Access.

    • Added checks and usage of getattr with default values to minimize repeated attribute access.
  3. Conditional Optimizations.

    • Simplified nested conditions to one-liners where possible to reduce overhead.
  4. Context and Cause Handling.

    • Used getattr to directly fetch default values for context and cause-related attributes reducing redundancy in the checks.

These adjustments aim to make the overall function more performant without changing the existing logic.

Correctness verification

The new optimized code was tested for correctness. The results are listed below.

🔘 (none found) − ⚙️ Existing Unit Tests

✅ 7 Passed − 🌀 Generated Regression Tests

(click to show generated tests)
# imports
from builtins import BaseExceptionGroup

import pytest  # used for our unit tests
from sentry_sdk.consts import DEFAULT_MAX_VALUE_LENGTH


# function to test
def single_exception_from_error_tuple(
    exc_type,  # type: Optional[type]
    exc_value,  # type: Optional[BaseException]
    tb,  # type: Optional[TracebackType]
    client_options=None,  # type: Optional[Dict[str, Any]]
    mechanism=None,  # type: Optional[Dict[str, Any]]
    exception_id=None,  # type: Optional[int]
    parent_id=None,  # type: Optional[int]
    source=None,  # type: Optional[str]
):
    # type: (...) -> Dict[str, Any]
    """
    Creates a dict that goes into the events `exception.values` list and is ingestible by Sentry.

    See the Exception Interface documentation for more details:
    https://develop.sentry.dev/sdk/event-payloads/exception/
    """
    exception_value = {}  # type: Dict[str, Any]
    exception_value["mechanism"] = (
        mechanism.copy() if mechanism else {"type": "generic", "handled": True}
    )
    if exception_id is not None:
        exception_value["mechanism"]["exception_id"] = exception_id

    if exc_value is not None:
        errno = get_errno(exc_value)
    else:
        errno = None

    if errno is not None:
        exception_value["mechanism"].setdefault("meta", {}).setdefault(
            "errno", {}
        ).setdefault("number", errno)

    if source is not None:
        exception_value["mechanism"]["source"] = source

    is_root_exception = exception_id == 0
    if not is_root_exception and parent_id is not None:
        exception_value["mechanism"]["parent_id"] = parent_id
        exception_value["mechanism"]["type"] = "chained"

    if is_root_exception and "type" not in exception_value["mechanism"]:
        exception_value["mechanism"]["type"] = "generic"

    is_exception_group = BaseExceptionGroup is not None and isinstance(
        exc_value, BaseExceptionGroup
    )
    if is_exception_group:
        exception_value["mechanism"]["is_exception_group"] = True

    exception_value["module"] = get_type_module(exc_type)
    exception_value["type"] = get_type_name(exc_type)
    exception_value["value"] = get_error_message(exc_value)

    if client_options is None:
        include_local_variables = True
        include_source_context = True
        max_value_length = DEFAULT_MAX_VALUE_LENGTH  # fallback
    else:
        include_local_variables = client_options["include_local_variables"]
        include_source_context = client_options["include_source_context"]
        max_value_length = client_options["max_value_length"]

    frames = [
        serialize_frame(
            tb.tb_frame,
            tb_lineno=tb.tb_lineno,
            include_local_variables=include_local_variables,
            include_source_context=include_source_context,
            max_value_length=max_value_length,
        )
        for tb in iter_stacks(tb)
    ]

    if frames:
        exception_value["stacktrace"] = {"frames": frames}

    return exception_value
from sentry_sdk.utils import exceptions_from_error


# Mocking helper functions
def get_errno(exc_value):
    return getattr(exc_value, 'errno', None)

def get_type_module(exc_type):
    return exc_type.__module__ if exc_type else None

def get_type_name(exc_type):
    return exc_type.__name__ if exc_type else None

def get_error_message(exc_value):
    return str(exc_value) if exc_value else None

def serialize_frame(frame, tb_lineno, include_local_variables, include_source_context, max_value_length):
    return {
        "filename": frame.f_code.co_filename,
        "lineno": tb_lineno,
        "function": frame.f_code.co_name,
        "vars": frame.f_locals if include_local_variables else None,
        "context": frame.f_globals if include_source_context else None,
    }

def iter_stacks(tb):
    while tb:
        yield tb
        tb = tb.tb_next

# unit tests
def test_single_value_error():
    exc_type = ValueError
    exc_value = ValueError("A simple value error")
    tb = None
    _, exceptions = exceptions_from_error(exc_type, exc_value, tb)
    assert len(exceptions) == 1
    assert exceptions[0]['type'] == 'ValueError'
    assert exceptions[0]['value'] == 'A simple value error'

def test_direct_cause():
    cause = TypeError("A type error")
    exc_value = ValueError("A value error")
    exc_value.__cause__ = cause
    exc_type = type(exc_value)
    tb = None
    _, exceptions = exceptions_from_error(exc_type, exc_value, tb)
    assert len(exceptions) == 2
    assert exceptions[0]['type'] == 'ValueError'
    assert exceptions[1]['type'] == 'TypeError'

def test_single_exception_group():
    exc1 = ValueError("A value error")
    exc2 = TypeError("A type error")
    exc_value = BaseExceptionGroup("An exception group", [exc1, exc2])
    exc_type = type(exc_value)
    tb = None
    _, exceptions = exceptions_from_error(exc_type, exc_value, tb)
    assert len(exceptions) == 3
    assert exceptions[0]['type'] == 'BaseExceptionGroup'
    assert exceptions[1]['type'] == 'ValueError'
    assert exceptions[2]['type'] == 'TypeError'

def test_custom_mechanism():
    mechanism = {"type": "custom", "handled": False}
    exc_type = ValueError
    exc_value = ValueError("A value error")
    tb = None
    _, exceptions = exceptions_from_error(exc_type, exc_value, tb, mechanism=mechanism)
    assert exceptions[0]['mechanism']['type'] == 'custom'
    assert exceptions[0]['mechanism']['handled'] is False

def test_null_inputs():
    exc_type = None
    exc_value = None
    tb = None
    _, exceptions = exceptions_from_error(exc_type, exc_value, tb)
    assert len(exceptions) == 1
    assert exceptions[0]['type'] is None
    assert exceptions[0]['value'] is None

def test_large_exception_group():
    excs = [ValueError(f"Error {i}") for i in range(1000)]
    exc_value = BaseExceptionGroup("A large exception group", excs)
    exc_type = type(exc_value)
    tb = None
    _, exceptions = exceptions_from_error(exc_type, exc_value, tb)
    assert len(exceptions) == 1001

def test_suppressed_context():
    cause = TypeError("A type error")
    exc_value = ValueError("A value error")
    exc_value.__cause__ = cause
    exc_value.__suppress_context__ = True
    exc_type = type(exc_value)
    tb = None
    _, exceptions = exceptions_from_error(exc_type, exc_value, tb)
    assert len(exceptions) == 2
    assert exceptions[0]['type'] == 'ValueError'
    assert exceptions[1]['type'] == 'TypeError'

🔘 (none found) − ⏪ Replay Tests

Certainly! We can optimize the given code for better performance by addressing areas that might involve unnecessary computations, redundant dictionary operations, and excessive attribute access. Here's the optimized version.



### Key Optimizations.
1. **Optimal Dictionary Manipulation**: 
    - Grouped dictionary initializations and modifications together to reduce the operations and thus improve speed.
  
2. **Reduced Redundant Access**.
    - Added checks and usage of `getattr` with default values to minimize repeated attribute access.
  
3. **Conditional Optimizations**.
    - Simplified nested conditions to one-liners where possible to reduce overhead.
  
4. **Context and Cause Handling**.
    - Used `getattr` to directly fetch default values for context and cause-related attributes reducing redundancy in the checks.

These adjustments aim to make the overall function more performant without changing the existing logic.
@codeflash-ai codeflash-ai bot added the ⚡️ codeflash Optimization PR opened by Codeflash AI label Jun 18, 2024
@codeflash-ai codeflash-ai bot requested a review from ihitamandal June 18, 2024 22:25
Copy link
Owner

@ihitamandal ihitamandal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code changes seem fine, although the timing improvement seems fairly small.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⚡️ codeflash Optimization PR opened by Codeflash AI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant