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
🐙 Code Quality / detect (push) Waiting to run
🐙 Code Quality / lint (${{ matrix.leg.name }}) (push) Blocked by required conditions
🐙 Code Quality / summary (push) Blocked by required conditions
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 - E2E Tests / build-opik (push) Waiting to run
Opik Optimizer - E2E Tests / Opik Optimizer E2E Tests Python ${{matrix.python_version}} (push) Blocked by required conditions
Opik Optimizer - E2E Tests / Opik Optimizer Integration Smoke Tests (push) Blocked by required conditions
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 Compatibility V1.x E2E Tests / Select Python version matrix (push) Waiting to run
Python SDK Compatibility V1.x E2E Tests / build-opik (push) Waiting to run
Python SDK Compatibility V1.x E2E Tests / Python SDK Compatibility V1.x E2E Tests ${{matrix.python_version}} (push) Blocked by required conditions
Python SDK E2E Tests / Select Python version matrix (push) Waiting to run
Python SDK E2E Tests / build-opik (push) Waiting to run
Python SDK E2E Tests / Python SDK E2E Tests ${{matrix.python_version}} (push) Blocked by required conditions
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 Compatibility V1.x E2E Tests / Select Node version matrix (push) Waiting to run
TypeScript SDK Compatibility V1.x E2E Tests / build-opik (push) Waiting to run
TypeScript SDK Compatibility V1.x E2E Tests / TypeScript SDK Compatibility V1.x E2E Tests Node ${{matrix.node_version}} (push) Blocked by required conditions
TypeScript SDK E2E Tests / Select Node version matrix (push) Waiting to run
TypeScript SDK E2E Tests / build-opik (push) Waiting to run
TypeScript SDK E2E Tests / TypeScript SDK E2E Tests Node ${{matrix.node_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
240 lines
8.7 KiB
YAML
240 lines
8.7 KiB
YAML
name: Load Tests (SDK ingestion)
|
||
run-name: "Load Tests ${{ github.ref_name }} by @${{ github.actor }}"
|
||
|
||
permissions:
|
||
contents: read
|
||
# checks: write so EnricoMi/publish-unit-test-result-action can post the
|
||
# "Load Test Results" check; without it the step gets 403 Forbidden and
|
||
# fails the whole workflow even when all scenarios passed.
|
||
checks: write
|
||
|
||
on:
|
||
schedule:
|
||
- cron: '0 4 * * 0' # Weekly, Sunday 04:00 UTC
|
||
workflow_dispatch:
|
||
|
||
env:
|
||
OPIK_ENABLE_LITELLM_MODELS_MONITORING: False
|
||
OPIK_SENTRY_ENABLE: False
|
||
OPIK_URL_OVERRIDE: http://localhost:8080
|
||
OPIK_CONSOLE_LOGGING_LEVEL: WARNING
|
||
|
||
jobs:
|
||
run-load-tests:
|
||
# Larger GitHub-hosted runner (~4 vCPU / 16 GB) instead of the default
|
||
# ubuntu-latest (2 vCPU / 7 GB). The heaviest ingestion scenarios
|
||
# (e.g. test_many_spans_per_trace) intermittently OOM-killed an xdist
|
||
# worker on 7 GB; the extra memory headroom is what stops that.
|
||
runs-on: ubuntu-latest-m
|
||
timeout-minutes: 60
|
||
|
||
steps:
|
||
- name: Checkout
|
||
uses: actions/checkout@v6
|
||
|
||
- name: Setup Python
|
||
uses: actions/setup-python@v5
|
||
with:
|
||
python-version: "3.12"
|
||
|
||
- name: Run latest Opik server
|
||
env:
|
||
OPIK_USAGE_REPORT_ENABLED: false
|
||
COMPOSE_BAKE: false
|
||
TOGGLE_RUNNERS_ENABLED: "true"
|
||
run: |
|
||
cd ${{ github.workspace }}
|
||
./opik.sh --backend --port-mapping --build
|
||
|
||
- name: Check Opik server availability
|
||
shell: bash
|
||
run: |
|
||
chmod +x ${{ github.workspace }}/tests_end_to_end/installer_utils/*.sh
|
||
cd ${{ github.workspace }}/deployment/docker-compose
|
||
${{ github.workspace }}/tests_end_to_end/installer_utils/check_docker_compose_pods.sh
|
||
${{ github.workspace }}/tests_end_to_end/installer_utils/check_backend.sh
|
||
|
||
- name: Install Opik SDK
|
||
run: |
|
||
cd ${{ github.workspace }}/sdks/python
|
||
pip install .
|
||
|
||
- name: Install Python SDK load-test requirements
|
||
run: |
|
||
cd ${{ github.workspace }}/tests_load/suite/python_sdk
|
||
pip install -r requirements.txt
|
||
|
||
- name: Run Python SDK load tests
|
||
# -n 2 + --dist=worksteal runs two scenarios in parallel at a time.
|
||
# History: on the old 7 GB ubuntu-latest, `-n auto` (= 4 workers)
|
||
# reliably OOM-killed the heaviest scenarios
|
||
# (test_many_traces_one_span_each, test_many_spans_per_trace) when
|
||
# co-scheduled against the same docker-compose Opik stack, and even
|
||
# -n 2 still crashed a worker occasionally. The job now runs on the
|
||
# larger ubuntu-latest-m (~16 GB, see runs-on above), so -n 2 has
|
||
# comfortable memory headroom; it's kept conservative for now and can
|
||
# be raised toward -n auto on the larger runner if more parallelism is
|
||
# wanted. Each test uses a unique project so isolation holds. Worksteal
|
||
# balances the very uneven per-test durations (spread is window-locked
|
||
# at 600 s, others run in 1-6 min).
|
||
run: |
|
||
cd ${{ github.workspace }}/tests_load
|
||
pytest suite/python_sdk -n 2 --dist=worksteal --junitxml=${{ github.workspace }}/load_test_results.xml
|
||
|
||
- name: Render metrics into job summary
|
||
if: always()
|
||
run: |
|
||
python - <<'PY' >> "$GITHUB_STEP_SUMMARY"
|
||
import json
|
||
import pathlib
|
||
|
||
metrics_dir = pathlib.Path("tests_load/.last_run")
|
||
files = sorted(metrics_dir.glob("*.json")) if metrics_dir.exists() else []
|
||
|
||
if not files:
|
||
print("## Load test metrics\n\n_No metrics produced (suite did not run)._")
|
||
raise SystemExit
|
||
|
||
|
||
def fmt_seconds(value):
|
||
if not isinstance(value, (int, float)):
|
||
return "—"
|
||
return f"{value / 60:.1f} m" if value >= 60 else f"{value:.1f} s"
|
||
|
||
|
||
def fmt_count(value):
|
||
return f"{value:,}" if isinstance(value, (int, float)) else "—"
|
||
|
||
|
||
def submit_seconds(metrics):
|
||
# The submit phase is timed as "logging" for trace-based tests and
|
||
# as "insert" for the dataset-versions test.
|
||
for key in ("logging_seconds", "insert_seconds"):
|
||
if isinstance(metrics.get(key), (int, float)):
|
||
return metrics[key]
|
||
return None
|
||
|
||
|
||
def submitted_volume(metrics):
|
||
# Returns (count, label). Picks the most representative unit per
|
||
# test type so the table shows what was actually submitted.
|
||
if "expected_total_items" in metrics:
|
||
return metrics["expected_total_items"], (
|
||
f"{metrics['expected_total_items']:,} items"
|
||
)
|
||
if "total_traces" in metrics:
|
||
return metrics["total_traces"], (
|
||
f"{metrics['total_traces']:,} traces"
|
||
)
|
||
trace_count = metrics.get("trace_count")
|
||
spans_per_trace = metrics.get("spans_per_trace")
|
||
if isinstance(trace_count, int) and isinstance(spans_per_trace, int):
|
||
return trace_count, (
|
||
f"{trace_count:,} traces × {spans_per_trace} spans"
|
||
)
|
||
if isinstance(trace_count, int):
|
||
return trace_count, f"{trace_count:,} traces"
|
||
return None, "—"
|
||
|
||
|
||
def submit_rate(count, seconds):
|
||
if count is None or not isinstance(seconds, (int, float)) or seconds <= 0:
|
||
return "—"
|
||
return f"{count / seconds:,.0f}/s"
|
||
|
||
|
||
def total_seconds(metrics):
|
||
parts = [
|
||
metrics.get(key)
|
||
for key in (
|
||
"logging_seconds",
|
||
"insert_seconds",
|
||
"flush_seconds",
|
||
"verify_seconds",
|
||
)
|
||
]
|
||
parts = [p for p in parts if isinstance(p, (int, float))]
|
||
return sum(parts) if parts else None
|
||
|
||
|
||
def delivered_count(metrics):
|
||
for key in ("delivered_trace_count", "delivered_item_count"):
|
||
if key in metrics:
|
||
return metrics[key]
|
||
return None
|
||
|
||
|
||
print("## Load test metrics\n")
|
||
print(
|
||
"_Submit = time spent calling decorated functions / context managers."
|
||
" Submit rate = volume ÷ submit time."
|
||
" Total = submit + flush + verify._\n"
|
||
)
|
||
print(
|
||
"| Test | Volume | Submit | Submit rate"
|
||
" | Flush | Verify | Total | Delivered |"
|
||
)
|
||
print(
|
||
"| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: |"
|
||
)
|
||
payloads = []
|
||
for path in files:
|
||
metrics = json.loads(path.read_text())
|
||
payloads.append(metrics)
|
||
name = metrics.get("test_name", path.stem)
|
||
count, volume_label = submitted_volume(metrics)
|
||
submit_s = submit_seconds(metrics)
|
||
print(
|
||
f"| `{name}`"
|
||
f" | {volume_label}"
|
||
f" | {fmt_seconds(submit_s)}"
|
||
f" | {submit_rate(count, submit_s)}"
|
||
f" | {fmt_seconds(metrics.get('flush_seconds'))}"
|
||
f" | {fmt_seconds(metrics.get('verify_seconds'))}"
|
||
f" | {fmt_seconds(total_seconds(metrics))}"
|
||
f" | {fmt_count(delivered_count(metrics))} |"
|
||
)
|
||
|
||
print("\n<details>\n<summary>Per-test metrics (raw JSON)</summary>\n")
|
||
for metrics in payloads:
|
||
print(f"\n**{metrics.get('test_name', '?')}**")
|
||
print("```json")
|
||
print(json.dumps(metrics, indent=2))
|
||
print("```")
|
||
print("\n</details>")
|
||
PY
|
||
|
||
- name: Upload metrics report
|
||
if: always()
|
||
uses: actions/upload-artifact@v7
|
||
with:
|
||
name: load-test-metrics
|
||
path: ${{ github.workspace }}/tests_load/.last_run/
|
||
|
||
- name: Publish Test Report
|
||
uses: EnricoMi/publish-unit-test-result-action/linux@v2
|
||
if: always()
|
||
with:
|
||
action_fail: true
|
||
comment_mode: failures
|
||
check_name: Load Test Results
|
||
files: ${{ github.workspace }}/load_test_results.xml
|
||
|
||
- name: Keep BE log in case of failure
|
||
if: failure()
|
||
run: |
|
||
docker logs opik-backend-1 > ${{ github.workspace }}/opik-backend.log
|
||
|
||
- name: Attach BE log
|
||
if: failure()
|
||
uses: actions/upload-artifact@v7
|
||
with:
|
||
name: opik-backend-log
|
||
path: ${{ github.workspace }}/opik-backend.log
|
||
|
||
- name: Stop opik server
|
||
if: always()
|
||
run: |
|
||
cd ${{ github.workspace }}
|
||
./opik.sh --stop
|