Files
wehub-resource-sync 5a558eb09e
Backend Tests / discover-tests (push) Waiting to run
Backend Tests / ${{ matrix.name }} (push) Blocked by required conditions
Build and Publish SDK / build-and-publish (push) Waiting to run
Build Opik Docker Images / set-version (push) Waiting to run
Build Opik Docker Images / build-backend (push) Blocked by required conditions
Build Opik Docker Images / build-sandbox-executor-python (push) Blocked by required conditions
Build Opik Docker Images / build-python-backend (push) Blocked by required conditions
Build Opik Docker Images / build-frontend (push) Blocked by required conditions
Build Opik Docker Images / create-git-tag (push) Blocked by required conditions
ClickHouse Migration Cluster Check / validate-clickhouse-migrations (push) Waiting to run
Docs - Publish / run (push) Waiting to run
E2E Tests - Post Merge (v2) / 🧪 E2E v2 Tests (${{ github.event.inputs.tier || 't1' }}) (push) Waiting to run
E2E Tests - Post Merge (v2) / 📢 Slack Notification (push) Blocked by required conditions
Frontend Unit Tests / Test on Node 20 (push) Waiting to run
Guardrails E2E Tests / Select Python version matrix (push) Waiting to run
Guardrails E2E Tests / Guardrails E2E Tests ${{matrix.python_version}} (push) Blocked by required conditions
Guardrails E2E Tests / 📢 Slack Notification (push) Blocked by required conditions
Guardrails Backend Unit Tests / Guardrails Backend Unit Tests (push) Waiting to run
Guardrails Backend Unit Tests / 📢 Slack Notification (push) Blocked by required conditions
SDK Library Integration Tests Runner / Check Secrets (push) Waiting to run
SDK Library Integration Tests Runner / Missed OpenAI API Key Warning (push) Blocked by required conditions
SDK Library Integration Tests Runner / Build (push) Blocked by required conditions
SDK Library Integration Tests Runner / openai_tests (push) Blocked by required conditions
SDK Library Integration Tests Runner / langchain_tests (push) Blocked by required conditions
SDK Library Integration Tests Runner / langchain_legacy_tests (push) Blocked by required conditions
SDK Library Integration Tests Runner / llama_index_tests (push) Blocked by required conditions
SDK Library Integration Tests Runner / anthropic_tests (push) Blocked by required conditions
SDK Library Integration Tests Runner / mistral_tests (push) Blocked by required conditions
SDK Library Integration Tests Runner / groq_tests (push) Blocked by required conditions
SDK Library Integration Tests Runner / aisuite_tests (push) Blocked by required conditions
SDK Library Integration Tests Runner / haystack_tests (push) Blocked by required conditions
SDK Library Integration Tests Runner / dspy_tests (push) Blocked by required conditions
SDK Library Integration Tests Runner / crewai_v0_tests (push) Blocked by required conditions
SDK Library Integration Tests Runner / crewai_v1_tests (push) Blocked by required conditions
SDK Library Integration Tests Runner / genai_tests (push) Blocked by required conditions
SDK Library Integration Tests Runner / adk_tests (push) Blocked by required conditions
SDK Library Integration Tests Runner / adk_legacy_1_3_0_tests (push) Blocked by required conditions
SDK Library Integration Tests Runner / evaluation_metrics_tests (push) Blocked by required conditions
SDK Library Integration Tests Runner / bedrock_tests (push) Blocked by required conditions
SDK Library Integration Tests Runner / litellm_tests (push) Blocked by required conditions
SDK Library Integration Tests Runner / harbor_tests (push) Blocked by required conditions
SDK Library Integration Tests Runner / Slack Notification (push) Blocked by required conditions
Lint Opik Helm Chart / lint-helm-chart (Helm v3.21.0) (push) Waiting to run
Lint Opik Helm Chart / lint-helm-chart (Helm v4.2.0) (push) Waiting to run
Lint Opik Helm Chart / unittest-helm-chart (push) Waiting to run
Lint Opik Helm Chart / render-equality (push) Waiting to run
Opik Optimizer - Unit Tests / Opik Optimizer Unit Tests Python ${{matrix.python_version}} (push) Waiting to run
Python BE E2E Tests / Python BE E2E (push) Waiting to run
Python Backend Tests / run-python-backend-tests (push) Waiting to run
Python SDK Unit Tests / Python SDK Unit Tests ${{matrix.python_version}} (push) Waiting to run
Release Drafter / update_release_draft (push) Waiting to run
SDK E2E Libraries Integration Tests / Check Secrets (push) Waiting to run
SDK E2E Libraries Integration Tests / Missed OpenAI API Key Warning (push) Blocked by required conditions
SDK E2E Libraries Integration Tests / build-opik (push) Blocked by required conditions
SDK E2E Libraries Integration Tests / E2E Lib Integration Python ${{matrix.python_version}} (push) Blocked by required conditions
TypeScript SDK Integration Build & Publish / build-and-publish (opik-gemini) (push) Waiting to run
TypeScript SDK Integration Build & Publish / build-and-publish (opik-langchain) (push) Waiting to run
TypeScript SDK Integration Build & Publish / build-and-publish (opik-openai) (push) Waiting to run
TypeScript SDK Integration Build & Publish / build-and-publish (opik-otel) (push) Waiting to run
TypeScript SDK Integration Build & Publish / build-and-publish (opik-vercel) (push) Waiting to run
TypeScript SDK Library Integration Tests / Check Secrets (push) Waiting to run
TypeScript SDK Library Integration Tests / opik-vercel (Vercel AI SDK / eve) (push) Blocked by required conditions
TypeScript SDK Build & Publish / build-and-publish (push) Waiting to run
TypeScript SDK Unit Tests / Test on Node ${{ matrix.node-version }} (push) Waiting to run
TypeScript SDK Compatibility V1.x E2E Tests / Select Node version matrix (push) Has been cancelled
TypeScript SDK Compatibility V1.x E2E Tests / TypeScript SDK Compatibility V1.x E2E Tests Node ${{matrix.node_version}} (push) Has been cancelled
TypeScript SDK E2E Tests / TypeScript SDK E2E Tests Node ${{matrix.node_version}} (push) Has been cancelled
Opik Optimizer - E2E Tests / build-opik (push) Has been cancelled
TypeScript SDK Compatibility V1.x E2E Tests / build-opik (push) Has been cancelled
Python SDK E2E Tests / Select Python version matrix (push) Has been cancelled
Python SDK E2E Tests / Python SDK E2E Tests ${{matrix.python_version}} (push) Has been cancelled
Python SDK E2E Tests / build-opik (push) Has been cancelled
Python SDK Compatibility V1.x E2E Tests / Select Python version matrix (push) Has been cancelled
Python SDK Compatibility V1.x E2E Tests / Python SDK Compatibility V1.x E2E Tests ${{matrix.python_version}} (push) Has been cancelled
Python SDK Compatibility V1.x E2E Tests / build-opik (push) Has been cancelled
TypeScript SDK E2E Tests / Select Node version matrix (push) Has been cancelled
TypeScript SDK E2E Tests / build-opik (push) Has been cancelled
Opik Optimizer - E2E Tests / Opik Optimizer E2E Tests Python ${{matrix.python_version}} (push) Has been cancelled
Opik Optimizer - E2E Tests / Opik Optimizer Integration Smoke Tests (push) Has been cancelled
🐙 Code Quality / detect (push) Has been cancelled
🐙 Code Quality / lint (${{ matrix.leg.name }}) (push) Has been cancelled
🐙 Code Quality / summary (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:25:44 +08:00

267 lines
8.8 KiB
Python

"""Helpers shared by the load-test suite.
Functions here are intentionally simple so individual tests can be read
top-to-bottom without jumping around.
"""
import base64
import contextlib
import json
import logging
import os
import random
import string
import time
import uuid
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, Iterable, Iterator, List, Set, Tuple
import opik
from opik import Opik
from opik.rest_api.types.span_public import SpanPublic
from opik.rest_api.types.trace_public import TracePublic
PROJECT_PREFIX: str = "loadtest"
REPORT_DIR: Path = Path(__file__).resolve().parents[2] / ".last_run"
KB: int = 1_000
MB: int = 1_000_000
REQUIRED_SPAN_FIELDS: Tuple[str, ...] = ("name", "end_time", "input", "output")
class Metrics:
"""Tiny key/value recorder with a `timer(label)` context manager.
One instance per test; written to ``REPORT_DIR/<test_name>.json`` at
teardown by the ``metrics`` fixture.
"""
test_name: str
_data: Dict[str, Any]
def __init__(self, test_name: str) -> None:
self.test_name = test_name
self._data = {"test_name": test_name}
def __setitem__(self, key: str, value: Any) -> None:
self._data[key] = value
@contextlib.contextmanager
def timer(self, label: str) -> Iterator[None]:
start: float = time.perf_counter()
yield
self._data[f"{label}_seconds"] = round(time.perf_counter() - start, 3)
def write(self) -> None:
REPORT_DIR.mkdir(parents=True, exist_ok=True)
out_path: Path = REPORT_DIR / f"{self.test_name}.json"
out_path.write_text(json.dumps(self._data, indent=2, default=str))
logging.getLogger(self.test_name).info(
"metrics: %s", json.dumps(self._data, default=str)
)
def unique_project_name(scenario: str) -> str:
timestamp: str = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%S")
return f"{PROJECT_PREFIX}-{scenario}-{timestamp}-{uuid.uuid4().hex[:6]}"
def random_text(size_bytes: int) -> str:
return "".join(random.choices(string.ascii_letters + string.digits, k=size_bytes))
def random_bytes(size_bytes: int) -> bytes:
return os.urandom(size_bytes)
PNG_MAGIC: bytes = b"\x89PNG\r\n\x1a\n"
def random_base64_png(size_bytes: int) -> str:
"""Returns a standard-alphabet base64-encoded byte stream that the SDK's
attachment-extraction pipeline recognises as a PNG.
Two constraints from the SDK that the naïve approach (random bytes,
``urlsafe_b64encode``) trips over:
1. The extraction regex is ``[A-Za-z0-9+/]``, so the encoded blob
must use the standard ``+/`` alphabet (``b64encode``), not the
``-_`` url-safe one.
2. The decoder MIME-sniffs the raw bytes and skips anything that
comes back as ``application/octet-stream`` or ``text/plain``,
so the byte stream must start with a recognised magic header.
The bytes after the 8-byte PNG magic are pure noise — the test
doesn't render the image, it just exercises the extract/upload path.
"""
raw_size: int = (size_bytes // 4) * 3
payload: bytes = PNG_MAGIC + os.urandom(max(0, raw_size - len(PNG_MAGIC)))
return base64.b64encode(payload).decode("ascii")
def think_time() -> None:
"""Sleeps a small randomized interval so submits aren't lockstep.
Real callers always have at least some gap between consecutive SDK calls
(LLM responses, request handling, etc.). The range is small on purpose so
it doesn't dominate runtime for high-count scenarios.
"""
time.sleep(random.uniform(0.0005, 0.002))
def opik_client() -> Opik:
return opik.Opik()
def now_utc() -> datetime:
return datetime.now(timezone.utc)
def _assert_required_fields_set(
items: Iterable[Any], required_fields: Tuple[str, ...], kind: str
) -> None:
"""Fails the test if any item is missing one of `required_fields`.
Catches partial-write bugs where the entity lands but a later update
(e.g. ``end_time``) was dropped or never sent.
"""
broken: List[str] = []
for item in items:
for field in required_fields:
if getattr(item, field) is None:
broken.append(f"{item.id}: {field} is None")
break
if broken:
raise AssertionError(
f"{len(broken)} {kind}(s) missing required fields "
f"({', '.join(required_fields)}); sample: {broken[:5]}"
)
def verify_traces(
client: Opik,
project_name: str,
expected_count: int,
timeout_seconds: int = 900,
) -> List[TracePublic]:
"""Polls until at least `expected_count` traces are visible in the project.
Excludes ``input``/``output``/``metadata`` from the response. We only
need ``id``, ``name``, ``end_time`` for the load-test verification,
and excluding the bulky fields:
1. Streams much less data back for the high-count scenarios.
2. Side-steps OPIK-6651, where attachment-extracted traces fail to
stream because ``AttachmentService.list`` can't read
``workspaceName`` from the reactor context during enrichment.
Excluding the fields skips the enrichment path entirely. Drop
the exclude list once OPIK-6651 is fixed.
``end_time``/``name`` field checks still run; ``input``/``output``
are not included in the response so we can't assert on them here.
"""
traces: List[TracePublic] = client.search_traces(
project_name=project_name,
max_results=expected_count,
wait_for_at_least=expected_count,
wait_for_timeout=timeout_seconds,
exclude=["input", "output", "metadata"],
)
_assert_required_fields_set(traces, ("name", "end_time"), "trace")
return traces
def verify_spans_for_trace(
client: Opik,
project_name: str,
trace_id: str,
expected_count: int,
timeout_seconds: int = 600,
) -> List[SpanPublic]:
"""Polls until at least `expected_count` spans are visible for a trace."""
spans: List[SpanPublic] = client.search_spans(
project_name=project_name,
trace_id=trace_id,
max_results=expected_count,
wait_for_at_least=expected_count,
wait_for_timeout=timeout_seconds,
)
_assert_required_fields_set(spans, REQUIRED_SPAN_FIELDS, "span")
return spans
def verify_exact_trace_ids(
client: Opik,
project_name: str,
expected_ids: Set[str],
timeout_seconds: int = 900,
) -> Set[str]:
"""Waits for all `expected_ids` to land and returns the delivered id set.
Raises ``AssertionError`` listing a sample of missing ids if some traces
are never delivered within the timeout. This catches dropped messages
(e.g. regression cases like OPIK-6444) rather than just under-counts.
Required-field validation runs inside ``verify_traces`` so any trace
landing without ``end_time``, ``name``, ``input``, or ``output`` also
fails the test.
"""
traces: List[TracePublic] = verify_traces(
client,
project_name=project_name,
expected_count=len(expected_ids),
timeout_seconds=timeout_seconds,
)
delivered: Set[str] = {trace.id for trace in traces}
missing: Set[str] = expected_ids - delivered
if missing:
raise AssertionError(
f"{len(missing)}/{len(expected_ids)} traces missing after flush; "
f"sample missing ids: {sorted(missing)[:3]}"
)
return delivered
def verify_attachments(
client: Opik,
project_name: str,
entity_type: str,
entity_id: str,
expected_count: int,
timeout_seconds: int = 600,
) -> int:
"""Polls the attachments REST endpoint until `expected_count` are listed.
The endpoint's ``path`` query parameter is a base64-encoded base URL
the backend uses to build download links — we pass the SDK's
configured base URL so the round-trip succeeds. Uses the standard
``b64encode`` alphabet (not ``urlsafe_b64encode``) to match the
contract used by ``attachment/client.py`` and ``tests/e2e/verifiers.py``.
"""
project = client.rest_client.projects.retrieve_project(name=project_name)
base_url: str = str(client.config.url_override).rstrip("/")
encoded_base_url: str = base64.b64encode(
base_url.encode("utf-8")
).decode("ascii")
deadline: float = time.time() + timeout_seconds
last_seen: int = 0
while time.time() < deadline:
page = client.rest_client.attachments.attachment_list(
project_id=project.id,
entity_type=entity_type,
entity_id=entity_id,
path=encoded_base_url,
size=expected_count + 10,
)
last_seen = len(page.content or [])
if last_seen >= expected_count:
return last_seen
time.sleep(1)
raise TimeoutError(
f"Only {last_seen}/{expected_count} attachments visible after "
f"{timeout_seconds}s for {entity_type} {entity_id}"
)