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
3 changes: 3 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,6 @@ jobs:

- name: MyPy omniray
run: uv run --directory packages/omniray mypy omniray/

- name: Shamefile check
run: uv run shame me . --dry-run
5 changes: 5 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,8 @@ repos:
additional_dependencies: [wrapt, pydantic, opentelemetry-api, opentelemetry-sdk]
files: ^packages/
exclude: tests/

- repo: https://github.com/BKDDFS/shamefile
rev: v0.1.7
hooks:
- id: shamefile
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
[![Python](https://img.shields.io/pypi/pyversions/omniray)](https://pypi.org/project/omniray/)
[![Docs](https://img.shields.io/badge/docs-blue)](https://omniviser.github.io/omniray/)
[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
[![shamefile](https://img.shields.io/badge/tracked_with-shamefile-fe3434)](https://github.com/BKDDFS/shamefile)

**One call, and you see everything that's happening in your code.**

Expand Down
19 changes: 8 additions & 11 deletions packages/omniray/omniray/tracing/compactor.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ def _emit_summary(self, streak: _Streak) -> None:
logger.info(
"%s%s %s",
profilers.get_indent(streak.depth, is_start=False),
profilers._colored(Fore.RED + Style.BRIGHT, f"x{streak.count}"), # noqa: SLF001
profilers.colored(Fore.RED + Style.BRIGHT, f"x{streak.count}"),
streak.span_name,
)
cont = _continuation_indent(streak.depth)
Expand Down Expand Up @@ -268,26 +268,23 @@ def _format_time_line(streak: _Streak, avg_ms: float, thresholds: Thresholds) ->


def _color_ms(value_ms: float, thresholds: Thresholds) -> str:
color = profilers._bucket_color(value_ms, thresholds.duration_ms) # noqa: SLF001
return profilers._colored(color, f"{value_ms:.2f}ms") # noqa: SLF001
color = profilers.bucket_color(value_ms, thresholds.duration_ms)
return profilers.colored(color, f"{value_ms:.2f}ms")


def _format_memory_line(streak: _Streak, thresholds: Thresholds) -> str:
"""Build the ``memory:`` continuation line, omitting unset fields."""
parts: list[str] = []
if streak.total_input_mb is not None:
parts.append("Σin: " + profilers._format_mb(streak.total_input_mb, thresholds.size_mb)) # noqa: SLF001
parts.append("Σin: " + profilers.format_mb(streak.total_input_mb, thresholds.size_mb))
if streak.total_output_mb is not None:
parts.append("Σout: " + profilers._format_mb(streak.total_output_mb, thresholds.size_mb)) # noqa: SLF001
parts.append("Σout: " + profilers.format_mb(streak.total_output_mb, thresholds.size_mb))
if streak.max_rss_mb is not None:
parts.append("rss: " + profilers._format_mb(streak.max_rss_mb, thresholds.rss_mb)) # noqa: SLF001
parts.append("rss: " + profilers.format_mb(streak.max_rss_mb, thresholds.rss_mb))
if streak.total_rss_delta_mb is not None:
parts.append(
"Σ"
+ profilers._format_mb( # noqa: SLF001
streak.total_rss_delta_mb, thresholds.rss_delta_mb, sign=True
)
"Σ" + profilers.format_mb(streak.total_rss_delta_mb, thresholds.rss_delta_mb, sign=True)
)
if streak.max_rss_peak_mb is not None:
parts.append("peak: " + profilers._format_mb(streak.max_rss_peak_mb, thresholds.rss_mb)) # noqa: SLF001
parts.append("peak: " + profilers.format_mb(streak.max_rss_peak_mb, thresholds.rss_mb))
return ", ".join(parts)
9 changes: 9 additions & 0 deletions packages/omniray/omniray/tracing/otel.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,14 @@ class OtelConfig:
status: type[_StatusType] | None
status_code: type[_StatusCodeType] | None
span_type: type | None
context_api: object | None
trace_api: object | None


def _init_otel(*, module_name: str) -> OtelConfig:
"""Initialize OpenTelemetry integration if available."""
try:
from opentelemetry import context as _context_api # noqa: PLC0415
from opentelemetry import trace as api # noqa: PLC0415
from opentelemetry.trace import INVALID_SPAN # noqa: PLC0415
from opentelemetry.trace import Span as _OtelSpan # noqa: PLC0415
Expand All @@ -42,6 +45,8 @@ def _init_otel(*, module_name: str) -> OtelConfig:
status=_Status,
status_code=_StatusCode,
span_type=_OtelSpan,
context_api=_context_api,
trace_api=api,
)
except ImportError:
return OtelConfig(
Expand All @@ -51,6 +56,8 @@ def _init_otel(*, module_name: str) -> OtelConfig:
status=None,
status_code=None,
span_type=None,
context_api=None,
trace_api=None,
)


Expand All @@ -77,5 +84,7 @@ def _check_otel_env(*, flag: bool | None, has_otel: bool) -> bool | None:
Status = _otel.status
StatusCode = _otel.status_code
OtelSpan = _otel.span_type
context_api = _otel.context_api
trace_api = _otel.trace_api

OTEL_FLAG = _check_otel_env(flag=_env_flag("OMNIRAY_OTEL"), has_otel=HAS_OTEL)
22 changes: 11 additions & 11 deletions packages/omniray/omniray/tracing/profilers.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,16 @@ def log_span_success( # noqa: PLR0913
rss_peak_mb: float | None = None,
) -> None:
"""Log span success with per-segment colored values."""
body = _colored(
_bucket_color(duration_ms, _THRESHOLDS.duration_ms),
body = colored(
bucket_color(duration_ms, _THRESHOLDS.duration_ms),
f"{duration_ms:.2f}ms",
)
if input_size_mb is not None:
body += ", in: " + _format_mb(input_size_mb, _THRESHOLDS.size_mb)
body += ", in: " + format_mb(input_size_mb, _THRESHOLDS.size_mb)
if output_size_mb is not None:
body += ", out: " + _format_mb(output_size_mb, _THRESHOLDS.size_mb)
body += ", out: " + format_mb(output_size_mb, _THRESHOLDS.size_mb)
if rss_current_mb is not None:
body += ", rss: " + _format_mb(rss_current_mb, _THRESHOLDS.rss_mb)
body += ", rss: " + format_mb(rss_current_mb, _THRESHOLDS.rss_mb)
extras = _format_rss_extras(rss_delta_mb, rss_peak_mb)
if extras:
body += f" ({extras})"
Expand Down Expand Up @@ -104,12 +104,12 @@ def get_indent(depth: int, *, is_start: bool = True) -> str:
return prefix + (NEST_START if is_start else NEST_END)


def _colored(color: str, text: str) -> str:
def colored(color: str, text: str) -> str:
"""Wrap *text* in *color* with a trailing style reset."""
return f"{color}{text}{Style.RESET_ALL}"


def _bucket_color(value: float, thresholds: tuple[float, float, float]) -> str:
def bucket_color(value: float, thresholds: tuple[float, float, float]) -> str:
"""Map *value* onto a DIM/GREEN/YELLOW/RED bucket using ``(low, medium, high)``."""
low, medium, high = thresholds
if value < low:
Expand All @@ -121,19 +121,19 @@ def _bucket_color(value: float, thresholds: tuple[float, float, float]) -> str:
return Fore.RED + Style.BRIGHT


def _format_mb(value: float, thresholds: tuple[float, float, float], *, sign: bool = False) -> str:
def format_mb(value: float, thresholds: tuple[float, float, float], *, sign: bool = False) -> str:
"""Return a colored ``X.XXMB`` string; ``sign=True`` forces a leading +/-."""
spec = f"{value:+.2f}" if sign else f"{value:.2f}"
return _colored(_bucket_color(value, thresholds), f"{spec}MB")
return colored(bucket_color(value, thresholds), f"{spec}MB")


def _format_rss_extras(rss_delta_mb: float | None, rss_peak_mb: float | None) -> str:
"""Build the ``Δ..., max: ...`` group (empty string when both inputs are ``None``)."""
extras: list[str] = []
if rss_delta_mb is not None:
extras.append(DELTA + _format_mb(rss_delta_mb, _THRESHOLDS.rss_delta_mb, sign=True))
extras.append(DELTA + format_mb(rss_delta_mb, _THRESHOLDS.rss_delta_mb, sign=True))
if rss_peak_mb is not None:
extras.append("max: " + _format_mb(rss_peak_mb, _THRESHOLDS.rss_mb))
extras.append("max: " + format_mb(rss_peak_mb, _THRESHOLDS.rss_mb))
return ", ".join(extras)


Expand Down
4 changes: 2 additions & 2 deletions packages/omniray/omniray/tracing/thresholds.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def __post_init__(self) -> None:
self._validate_triple("rss_delta", self.rss_delta)
self._validate_triple("duration_ms", self.duration_ms)
self._validate_scalar("duration_slow_tag_ms", self.duration_slow_tag_ms)
self._validate_bool("compact", self.compact)
self._validate_bool("compact", value=self.compact)
self._validate_positive_int("compact_threshold", self.compact_threshold, minimum=2)

def _validate_triple(self, name: str, value: list[float] | None) -> None:
Expand All @@ -74,7 +74,7 @@ def _validate_scalar(self, name: str, value: float | None) -> None:
msg = f"{name} must be numeric, got {type(value).__name__}"
raise self.ConfigError(msg)

def _validate_bool(self, name: str, value: bool | None) -> None: # noqa: FBT001
def _validate_bool(self, name: str, *, value: bool | None) -> None:
if value is None:
return
if not isinstance(value, bool):
Expand Down
32 changes: 11 additions & 21 deletions packages/omniray/omniray/tracing/tracers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from __future__ import annotations

import time
from contextlib import suppress
from contextvars import ContextVar
from typing import TYPE_CHECKING

Expand All @@ -25,7 +26,9 @@
OTEL_MISSING_MSG,
Status,
StatusCode,
context_api,
otel_tracer,
trace_api,
)
from omniray.tracing.rss import read_peak_rss_mb, read_rss_mb
from omniray.tracing.sizing import measure_size_mb
Expand Down Expand Up @@ -110,12 +113,10 @@ def trace( # noqa: PLR0913
result = wrapped(*args, **kwargs)
except Exception as e:
duration_s = time.time() - start_time
try: # noqa: SIM105 - tracing must never mask user exceptions
with suppress(Exception):
cls._finish_tracing_failure(
span, duration_s, span_name, current_depth, e, flags
)
except Exception: # noqa: BLE001, S110
pass
raise
else:
duration_s = time.time() - start_time
Expand All @@ -130,10 +131,8 @@ def trace( # noqa: PLR0913
)
return result
finally:
try: # noqa: SIM105 - tracing must never mask user exceptions
with suppress(Exception):
cls._trace_duration(span, duration_s)
except Exception: # noqa: BLE001, S110
pass
finally:
_call_depth.set(current_depth)

Expand Down Expand Up @@ -331,12 +330,10 @@ async def trace( # noqa: PLR0913
result = await wrapped(*args, **kwargs)
except Exception as e:
duration_s = time.time() - start_time
try: # noqa: SIM105 - tracing must never mask user exceptions
with suppress(Exception):
cls._finish_tracing_failure(
span, duration_s, span_name, current_depth, e, flags
)
except Exception: # noqa: BLE001, S110
pass
raise
else:
duration_s = time.time() - start_time
Expand All @@ -351,10 +348,8 @@ async def trace( # noqa: PLR0913
)
return result
finally:
try: # noqa: SIM105 - tracing must never mask user exceptions
with suppress(Exception):
cls._trace_duration(span, duration_s)
except Exception: # noqa: BLE001, S110
pass
finally:
cls._exit_otel_span(span, token, flags)
finally:
Expand All @@ -365,21 +360,16 @@ def _enter_otel_span(span_name: str, flags: TraceFlags) -> tuple:
"""Start an OTel span with explicit attach — no generator, safe for async GC."""
if not flags.otel:
return None, None
from opentelemetry import context as context_api # noqa: PLC0415
from opentelemetry import trace as trace_api # noqa: PLC0415

# otel_tracer guaranteed non-None: guarded by flags.otel + HAS_OTEL
# otel_tracer/context_api/trace_api guaranteed non-None: guarded by flags.otel + HAS_OTEL
span = otel_tracer.start_span(span_name) # type: ignore[union-attr]
ctx = trace_api.set_span_in_context(span)
token = context_api.attach(ctx)
ctx = trace_api.set_span_in_context(span) # type: ignore[union-attr]
token = context_api.attach(ctx) # type: ignore[union-attr]
return span, token

@staticmethod
def _exit_otel_span(span: OtelSpan | None, token: object | None, flags: TraceFlags) -> None:
"""Detach context and end span. Counterpart to :meth:`_enter_otel_span`."""
if not flags.otel:
return
from opentelemetry import context as context_api # noqa: PLC0415

context_api.detach(token) # type: ignore[arg-type] # Token[Context] stored as object
context_api.detach(token) # type: ignore[union-attr, arg-type]
span.end() # type: ignore[union-attr]
10 changes: 5 additions & 5 deletions packages/omniray/tests/unit/tracing/test_profilers.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,9 +320,9 @@ def test_get_indent_depth_two():
],
)
def test_bucket_color_for_duration(monkeypatch, duration_ms, expected_color):
"""Test _bucket_color returns correct color for different durations."""
"""Test bucket_color returns correct color for different durations."""
monkeypatch.setattr(profilers, "_THRESHOLDS", Thresholds())
result = profilers._bucket_color(duration_ms, profilers._THRESHOLDS.duration_ms)
result = profilers.bucket_color(duration_ms, profilers._THRESHOLDS.duration_ms)

assert result == expected_color

Expand Down Expand Up @@ -440,7 +440,7 @@ def test_resolve_unicode_support_forced_style(monkeypatch, style, expected):
)
def test_bucket_color_for_size(monkeypatch, value, expected):
monkeypatch.setattr(profilers, "_THRESHOLDS", _DEFAULT_THRESHOLDS)
assert profilers._bucket_color(value, _DEFAULT_THRESHOLDS.size_mb) == expected
assert profilers.bucket_color(value, _DEFAULT_THRESHOLDS.size_mb) == expected


@pytest.mark.parametrize(
Expand All @@ -454,7 +454,7 @@ def test_bucket_color_for_size(monkeypatch, value, expected):
)
def test_bucket_color_for_rss(monkeypatch, value, expected):
monkeypatch.setattr(profilers, "_THRESHOLDS", _DEFAULT_THRESHOLDS)
assert profilers._bucket_color(value, _DEFAULT_THRESHOLDS.rss_mb) == expected
assert profilers.bucket_color(value, _DEFAULT_THRESHOLDS.rss_mb) == expected


@pytest.mark.parametrize(
Expand All @@ -472,4 +472,4 @@ def test_bucket_color_for_rss(monkeypatch, value, expected):
def test_bucket_color_for_rss_delta(monkeypatch, value, expected):
"""Unified DIM/GREEN/YELLOW/RED ladder — negative/near-zero fall into ``< low`` → DIM."""
monkeypatch.setattr(profilers, "_THRESHOLDS", _DEFAULT_THRESHOLDS)
assert profilers._bucket_color(value, _DEFAULT_THRESHOLDS.rss_delta_mb) == expected
assert profilers.bucket_color(value, _DEFAULT_THRESHOLDS.rss_delta_mb) == expected
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ dev = [
"mypy>=1.15",
"types-psutil>=7.0",
"pip-licenses>=5.0",
"shamefile>=0.1.7",
]
docs = [
"mkdocs-material>=9.0",
Expand Down
Loading
Loading