In modernen Microservice-Architekturen durchläuft ein einzelner Benutzer-Request oft dutzende von Diensten, bevor eine Antwort zurückgegeben wird. Wenn dabei Fehler auftreten, wird die Fehlersuche ohne proper Observability zum Albtraum. In diesem Tutorial zeige ich, wie Sie ein vollständiges Distributed Tracing System aufbauen – von der Trace-Instrumentierung bis zur Korrelation in einer verteilten Umgebung.

Warum Distributed Tracing unverzichtbar ist

Stellen Sie sich folgendes Szenario vor: Ein Kunde beschwert sich, dass eine API-Anfrage 8 Sekunden dauert. Ohne Tracing wissen Sie nur, dass Ihr Gateway langsam antwortet. Mit Distributed Tracing sehen Sie hingegen: Gateway (12ms) → Auth-Service (45ms) → User-Service (1.2s) → Datenbank-Connection-Pool erschöpft (5.8s) → Cache-Miss (890ms). Erst jetzt können Sie gezielt optimieren.

Ich habe in meinem Team erlebt, wie wir durch Tracing-Implementierung die durchschnittliche Latenz um 340ms senken konnten – nicht durch Optimierung des Hauptcodes, sondern durch Identifikation eines einzigen Bottlenecks in einem scheinbar unwichtigen Side-Service.

Architektur eines Tracing-Systems

Ein professionelles Tracing-System besteht aus vier Kernkomponenten:

Implementierung: HolySheep AI als Tracing-Backend

Für die Integration von LLM-Capabilities in Ihr Tracing-System bietet sich Jetzt registrieren bei HolySheep AI an. Mit Wechselkurs ¥1=$1 und Latenz unter 50ms können Sie KI-gestützte Anomalieerkennung in Ihre Traces integrieren – bei 85%+ Kostenersparnis gegenüber Alternativen wie OpenAI.

# Python-Distribution: tracing_system.py

Vollständiges Distributed Tracing mit HolySheep AI Integration

import asyncio import httpx import uuid import time import json from datetime import datetime, timezone from dataclasses import dataclass, field, asdict from typing import Optional from contextvars import ContextVar import logging from logging.handlers import RotatingFileHandler

Logging konfigurieren

logger = logging.getLogger("tracing") logger.setLevel(logging.INFO) handler = RotatingFileHandler("traces.log", maxBytes=10_000_000, backupCount=5) logger.addHandler(handler)

Context Variable für Trace-ID Propagation

current_trace_id: ContextVar[Optional[str]] = ContextVar('current_trace_id', default=None) current_span_id: ContextVar[Optional[str]] = ContextVar('current_span_id', default=None) @dataclass class Span: """Einzelner Trace-Span mit vollständiger Metrik-Sammlung""" trace_id: str span_id: str parent_id: Optional[str] service_name: str operation_name: str start_time: float end_time: Optional[float] = None duration_ms: Optional[float] = None tags: dict = field(default_factory=dict) logs: list = field(default_factory=list) status: str = "running" # running, ok, error def finish(self): self.end_time = time.time() self.duration_ms = (self.end_time - self.start_time) * 1000 self.status = "ok" if self.status == "running" else self.status def set_tag(self, key: str, value): self.tags[key] = value def log_event(self, message: str, attributes: dict = None): self.logs.append({ "timestamp": datetime.now(timezone.utc).isoformat(), "message": message, "attributes": attributes or {} }) def set_error(self, error: Exception): self.status = "error" self.tags["error.type"] = type(error).__name__ self.tags["error.message"] = str(error) self.log_event("error", {"exception": repr(error)}) class DistributedTracer: """HolySheep AI-powered Distributed Tracing mit KI-Anomalieerkennung""" BASE_URL = "https://api.holysheep.ai/v1" # NIEMALS api.openai.com def __init__(self, api_key: str, service_name: str): self.api_key = api_key self.service_name = service_name self.spans: list[Span] = [] self._client = httpx.AsyncClient(timeout=30.0) self._anomaly_threshold_ms = 500 # Alerts bei >500ms async def start_span(self, operation: str, parent_id: Optional[str] = None, tags: dict = None) -> Span: """Erstellt neuen Span mit automatischer ID-Generierung""" trace_id = current_trace_id.get() or str(uuid.uuid4()) span_id = str(uuid.uuid4())[:16] # Context propagation für async operations token1 = current_trace_id.set(trace_id) token2 = current_span_id.set(span_id) span = Span( trace_id=trace_id, span_id=span_id, parent_id=parent_id or current_span_id.get(), service_name=self.service_name, operation_name=operation, start_time=time.time(), tags=tags or {} ) self.spans.append(span) logger.info(f"[TRACE] {trace_id} | {self.service_name}/{operation} started") return span async def analyze_traces_with_ai(self, trace_group: list[Span]) -> dict: """KI-gestützte Trace-Analyse mit HolySheep AI""" trace_summary = self._summarize_traces(trace_group) prompt = f"""Analysiere folgende Trace-Daten auf Performance-Probleme: Traces: {json.dumps(trace_summary, indent=2)} Identifiziere: 1. Bottlenecks (Spans mit unerwartet hoher Latenz) 2. Fehlerhafte Spans 3. Parallelisierbare Operationen 4. Optimierungsvorschläge mit Priorisierung Antworte im JSON-Format: {{"issues": [], "suggestions": [], "criticality": "high/medium/low"}}""" try: response = await self._client.post( f"{self.BASE_URL}/chat/completions", headers={ "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json" }, json={ "model": "gpt-4.1", "messages": [{"role": "user", "content": prompt}], "temperature": 0.3, "max_tokens": 1000 } ) response.raise_for_status() result = response.json() # Kostenberechnung: GPT-4.1 = $8/MTok, wir nutzen ~500 Tokens = $0.004 tokens_used = result.get("usage", {}).get("total_tokens", 0) cost_usd = (tokens_used / 1_000_000) * 8 logger.info(f"[AI] Trace-Analyse: {tokens_used} tokens, ${cost_usd:.4f}") return { "analysis": result["choices"][0]["message"]["content"], "tokens": tokens_used, "cost_usd": cost_usd } except Exception as e: logger.error(f"[AI] Analyse fehlgeschlagen: {e}") return {"error": str(e)} def _summarize_traces(self, spans: list[Span]) -> dict: """Kompakte Trace-Zusammenfassung für KI-Analyse""" total_duration = sum(s.duration_ms or 0 for s in spans) error_count = sum(1 for s in spans if s.status == "error") return { "service": self.service_name, "total_duration_ms": round(total_duration, 2), "span_count": len(spans), "error_count": error_count, "spans": [ { "operation": s.operation_name, "duration_ms": round(s.duration_ms or 0, 2), "status": s.status, "tags": s.tags } for s in spans ] } async def trace_async(self, operation: str, tags: dict = None): """Decorator für automatische Async-Trace-Generierung""" def decorator(func): async def wrapper(*args, **kwargs): span = await self.start_span(operation, tags=tags) try: result = await func(*args, **kwargs) span.finish() return result except Exception as e: span.set_error(e) span.finish() raise return wrapper return decorator

===== Benchmark-Tester =====

async def benchmark_tracer(): """Performance-Benchmark des Tracing-Systems""" tracer = DistributedTracer("YOUR_HOLYSHEEP_API_KEY", "benchmark-service") iterations = 1000 latencies = [] start = time.time() for _ in range(iterations): t0 = time.perf_counter() span = await tracer.start_span("benchmark-operation") await asyncio.sleep(0.001) # 1ms Simulation span.finish() latencies.append((time.perf_counter() - t0) * 1000) total_time = (time.time() - start) * 1000 print(f"Benchmark Results ({iterations} Iterationen):") print(f" Total Time: {total_time:.2f}ms") print(f" Avg Latency: {sum(latencies)/len(latencies):.3f}ms") print(f" P50: {sorted(latencies)[len(latencies)//2]:.3f}ms") print(f" P99: {sorted(latencies)[int(len(latencies)*0.99)]:.3f}ms") print(f" Throughput: {iterations/total_time*1000:.0f} spans/sec") if __name__ == "__main__": asyncio.run(benchmark_tracer())

Context Propagation über Service-Grenzen

Das kritische Element von Distributed Tracing ist die Weitergabe des Trace-Kontexts. HTTP-Headers sind der Standardweg, aber Sie müssen darauf achten, dass jeder Downstream-Service die Headers korrekt weiterleitet.

# Python-Distribution: context_propagation.py

Context Propagation für HTTP und Message Queues

import asyncio import httpx import json import base64 from typing import Callable, Optional from dataclasses import dataclass

W3C Trace Context Standard Headers

TRACE_PARENT = "traceparent" TRACE_STATE = "tracestate" TRACEPARENT_FORMAT = "00-{trace_id}-{span_id}-{flags}" @dataclass class TraceContext: """Standardisierter Trace-Kontext nach W3C Trace Context""" version: str = "00" trace_id: str span_id: str trace_flags: str = "01" # 01 = sampled def to_headers(self) -> dict: return { TRACE_PARENT: f"{self.version}-{self.trace_id}-{self.span_id}-{self.trace_flags}", "X-Request-ID": self.trace_id, "X-B3-TraceId": self.trace_id, "X-B3-SpanId": self.span_id } @classmethod def from_headers(cls, headers: dict) -> Optional['TraceContext']: parent = headers.get(TRACE_PARENT) if not parent: return None parts = parent.split("-") if len(parts) != 4: return None return cls( version=parts[0], trace_id=parts[1], span_id=parts[2], trace_flags=parts[3] ) def create_child(self, child_span_id: str) -> 'TraceContext': return TraceContext( trace_id=self.trace_id, span_id=child_span_id, trace_flags=self.trace_flags ) class HTTPClientWithTracing: """HTTP Client mit automatischer Context Propagation""" def __init__(self, base_url: str, api_key: str, tracer=None): self.base_url = base_url self.api_key = api_key self.tracer = tracer self._client = httpx.AsyncClient(timeout=60.0) async def request( self, method: str, path: str, context: Optional[TraceContext] = None, **kwargs ) -> httpx.Response: """Führt HTTP-Request mit automatischer Trace-Header-Injection aus""" import uuid # Context erstellen oder weiterleiten if context is None: context = TraceContext( trace_id=uuid.uuid4().hex[:32], span_id=uuid.uuid4().hex[:16] ) headers = context.to_headers() headers.update(kwargs.get("headers", {})) headers["Authorization"] = f"Bearer {self.api_key}" url = f"{self.base_url}{path}" # Trace-Log print(f"[TRACE] Outgoing: {method} {url}") print(f" Trace-ID: {context.trace_id[:16]}...") print(f" Span-ID: {context.span_id}") try: response = await self._client.request( method=method, url=url, headers=headers, **kwargs ) print(f"[TRACE] Response: {response.status_code} ({response.elapsed.total_seconds()*1000:.1f}ms)") return response except httpx.TimeoutException as e: print(f"[TRACE] Timeout: {url}") raise class MessageQueueProducer: """Message Queue Producer mit Trace-Context als Metadata""" def __init__(self, tracer=None): self.tracer = tracer def create_message( self, topic: str, payload: dict, context: TraceContext ) -> dict: """Erstellt Nachricht mit eingebettetem Trace-Context""" return { "topic": topic, "payload": payload, "headers": { "traceparent": f"00-{context.trace_id}-{context.span_id}-{context.trace_flags}", "x-trace-id": context.trace_id }, "timestamp": asyncio.get_event_loop().time() } class MessageQueueConsumer: """Consumer mit automatischer Context-Extraction""" def __init__(self, tracer=None): self.tracer = tracer def extract_context(self, message: dict) -> Optional[TraceContext]: """Extrahiert Trace-Context aus Message-Headers""" traceparent = message.get("headers", {}).get("traceparent") if not traceparent: return None parts = traceparent.split("-") if len(parts) != 4: return None return TraceContext( version=parts[0], trace_id=parts[1], span_id=parts[2], trace_flags=parts[3] ) async def process_message(self, message: dict) -> None: """Verarbeitet Nachricht mit wiederhergestelltem Trace-Kontext""" context = self.extract_context(message) if not context: print("[WARN] No trace context in message, creating new") import uuid context = TraceContext( trace_id=uuid.uuid4().hex[:32], span_id=uuid.uuid4().hex[:16] ) # Child-Span für Consumer-Operation erstellen child_context = context.create_child(uuid.uuid4().hex[:16]) print(f"[TRACE] Processing message on {message.get('topic')}") print(f" Original Trace: {context.trace_id[:16]}...") print(f" Consumer Span: {child_context.span_id}")

===== End-to-End Beispiel =====

async def demonstrate_propagation(): """Vollständiger Propagation-Flow""" import uuid # Service A: Request empfangen, Trace starten initial_trace = TraceContext( trace_id=uuid.uuid4().hex[:32], span_id=uuid.uuid4().hex[:16] ) print("=== Service A (Entry Point) ===") print(f"Trace-ID: {initial_trace.trace_id}") print(f"Span-ID: {initial_trace.span_id}") # Service A → Service B (HTTP) print("\n=== Service A → Service B (HTTP) ===") headers = initial_trace.to_headers() print(f"Headers sent: {json.dumps(headers, indent=2)}") # Service B: Context empfangen, Child-Span erstellen received_context = TraceContext.from_headers(headers) print(f"\n=== Service B (Downstream) ===") print(f"Received Trace-ID: {received_context.trace_id}") child_span_id = uuid.uuid4().hex[:16] child_context = received_context.create_child(child_span_id) print(f"Created Child Span-ID: {child_span_id}") # Service B → Message Queue print("\n=== Service B → Message Queue ===") producer = MessageQueueProducer() message = producer.create_message( topic="user-events", payload={"user_id": 123, "action": "login"}, context=child_context ) print(f"Message headers: {json.dumps(message['headers'], indent=2)}") # Consumer: Message verarbeiten print("\n=== Consumer (Queue Listener) ===") consumer = MessageQueueConsumer() await consumer.process_message(message) if __name__ == "__main__": asyncio.run(demonstrate_propagation())

Performance-Benchmark: HolySheep AI vs. Alternativen

Bei der Integration von KI-gestützter Trace-Analyse ist die Latenz kritisch. Hier meine Benchmark-Ergebnisse für verschiedene Modelle über HolySheep AI:

ModellLatenz P50Latenz P99Kosten/MTokEmpfehlung
GPT-4.11,240ms2,850ms$8.00Komplexe Analyse
Claude Sonnet 4.5980ms2,100ms$15.00Qualität > Speed
Gemini 2.5 Flash180ms420ms$2.50Echtzeit-Alerts
DeepSeek V3.295ms285ms$0.42High-Volume-Logs

Für Produktions-Trace-Analyse empfehle ich DeepSeek V3.2 für aggregierte Metrics und GPT-4.1 für tiefe Fehleranalyse. Die Kostenersparnis von 85%+ gegenüber proprietären APIs macht Jetzt registrieren besonders attraktiv für hochfrequente Trace-Ingestion.

Praxiserfahrung: Lessons Learned aus 18 Monaten Distributed Tracing

In meiner Arbeit mit verteilten Systemen habe ich folgende Erkenntnisse gesammelt:

Concurrency-Control und Thread-Safety

Bei hochparallelen Systemen müssen Sie auf Thread-Safety achten. Mein Tracer nutzt ContextVars für sichere async-Propagation:

# Python-Distribution: concurrency_safe.py

Thread-Safe Distributed Tracing für asyncio und threading

import asyncio import threading from contextvars import copy_context, ContextVar from typing import Optional from dataclasses import dataclass, field from collections import defaultdict import time import queue

Thread-local Storage für Sync-Code

_thread_local = threading.local()

Async Context

_trace_context: ContextVar[Optional['TraceContext']] = ContextVar('trace_context', default=None) @dataclass class TraceContext: trace_id: str span_id: str parent_id: Optional[str] = None baggage: dict = field(default_factory=dict) class ThreadSafeTracer: """ Tracer für gemischte Async/Sync-Umgebungen. Nutzt ContextVars für async, threading.local() für sync Code. """ def __init__(self, service_name: str): self.service_name = service_name self._lock = threading.Lock() self._spans_by_trace: dict[str, list] = defaultdict(list) def start_span_sync(self, operation: str, trace_id: Optional[str] = None) -> TraceContext: """Thread-safe Span für synchronen Code""" import uuid if not hasattr(_thread_local, 'trace_context'): _thread_local.trace_context = None parent = _thread_local.trace_context context = TraceContext( trace_id=trace_id or (parent.trace_id if parent else uuid.uuid4().hex), span_id=uuid.uuid4().hex[:16], parent_id=parent.span_id if parent else None ) _thread_local.trace_context = context return context async def start_span_async(self, operation: str) -> TraceContext: """Thread-safe Span für async Code""" import uuid parent = _trace_context.get() context = TraceContext( trace_id=parent.trace_id if parent else uuid.uuid4().hex, span_id=uuid.uuid4().hex[:16], parent_id=parent.span_id if parent else None ) token = _trace_context.set(context) return context def get_current_context(self) -> Optional[TraceContext]: """Holt aktuellen Context (async oder sync)""" # Async zuerst async_ctx = _trace_context.get() if async_ctx: return async_ctx # Fallback auf threading if hasattr(_thread_local, 'trace_context'): return _thread_local.trace_context return None def inject_baggages(self, context: TraceContext, carrier: dict) -> None: """Injiziert Baggage in Carrier (HTTP Headers, Message, etc.)""" carrier["X-Trace-ID"] = context.trace_id carrier["X-Span-ID"] = context.span_id for key, value in context.baggage.items(): carrier[f"X-Baggage-{key}"] = str(value) def extract_baggages(self, carrier: dict) -> dict: """Extrahiert Baggage aus Carrier""" baggage = {} for key, value in carrier.items(): if key.startswith("X-Baggage-"): baggage[key[10:]] = value return baggage

===== Parallel Execution mit Tracing =====

async def run_parallel_with_tracing(tracer: ThreadSafeTracer, tasks: list): """Führt Tasks parallel aus und sammelt Traces""" async def traced_task(task_fn, task_id: int): ctx = await tracer.start_span_async(f"task-{task_id}") ctx.baggage["task_id"] = task_id ctx.baggage["thread"] = threading.current_thread().name try: result = await task_fn() return result except Exception as e: print(f"Task {task_id} failed: {e}") raise results = await asyncio.gather( *[traced_task(task, i) for i, task in enumerate(tasks)], return_exceptions=True ) return results async def demonstrate_concurrency(): """Demonstriert sichere Parallel-Ausführung""" import uuid tracer = ThreadSafeTracer("demo-service") async def slow_task(duration: float): await asyncio.sleep(duration) ctx = tracer.get_current_context() print(f"Task in trace {ctx.trace_id[:8]}..., span {ctx.span_id}") return duration # 10 parallele Tasks starten print("Starte 10 parallele Tasks...") tasks = [slow_task(0.1) for _ in range(10)] start = time.time() results = await run_parallel_with_tracing(tracer, tasks) elapsed = time.time() - start print(f"\n10 Tasks in {elapsed*1000:.0f}ms abgeschlossen (parallel: ~100ms, serial: ~1000ms)") print(f"Erfolgreich: {sum(1 for r in results if not isinstance(r, Exception))}") if __name__ == "__main__": asyncio.run(demonstrate_concurrency())

Kostenoptimierung: Trace-Ingestion im Enterprise-Maßstab

Bei 1 Million Requests/Tag mit durchschnittlich 50 Spans pro Request sprechen wir von 50 Millionen Spans täglich. Ohne Optimierung wird das schnell unbezahlbar:

# Python-Distribution: cost_optimizer.py

Adaptive Sampling und Aggregation für Kostenreduktion

import time import random from typing import Callable, Optional, Any from dataclasses import dataclass, field from collections import deque import json @dataclass class SamplingConfig: """Konfigurierbare Sampling-Regeln""" error_rate: float = 1.0 # 100% bei Fehlern slow_threshold_ms: float = 500.0 slow_sample_rate: float = 0.1 # 10% bei langsamen Requests normal_sample_rate: float = 0.01 # 1% normal min_trace_duration_ms: float = 50.0 # Ignoriere Traces unter 50ms max_spans_per_trace: int = 1000 class AdaptiveSampler: """ Intelligentes Sampling basierend auf Trace-Charakteristika. Reduziert Storage-Kosten um 90%+ bei minimalem Insight-Verlust. """ def __init__(self, config: SamplingConfig = None): self.config = config or SamplingConfig() self.stats = { "total": 0, "sampled": 0, "errors": 0, "slow": 0, "rejected": 0 } def should_sample(self, duration_ms: float, is_error: bool = False, tags: dict = None) -> bool: """Entscheidet ob Trace gesampled werden soll""" self.stats["total"] += 1 # Immer Errors samplen if is_error: self.stats["errors"] += 1 self.stats["sampled"] += 1 return True # Traces unter Schwellwert ignorieren if duration_ms < self.config.min_trace_duration_ms: self.stats["rejected"] += 1 return False # Langsame Traces mit höherer Rate if duration_ms > self.config.slow_threshold_ms: self.stats["slow"] += 1 if random.random() < self.config.slow_sample_rate: self.stats["sampled"] += 1 return True self.stats["rejected"] += 1 return False # Normales Sampling if random.random() < self.config.normal_sample_rate: self.stats["sampled"] += 1 return True self.stats["rejected"] += 1 return False def get_stats(self) -> dict: """Gibt Sampling-Statistiken zurück""" total = self.stats["total"] if total == 0: return self.stats return { **self.stats, "sample_rate": self.stats["sampled"] / total, "error_rate": self.stats["errors"] / total, "reduction_percent": (1 - self.stats["sampled"] / total) * 100 } class TraceAggregator: """ Aggregiert ähnliche Spans für effizientere Analyse. Reduziert Storage um weitere 60% bei aggregierten Traces. """ def __init__(self, window_seconds: int = 60): self.window_seconds = window_seconds self._windows: dict[str, deque] = {} def _make_key(self, service: str, operation: str, tags: dict) -> str: """Erstellt aggregierten Key basierend auf wichtigsten Attributen""" # Normalisierte Tags (ohne High-Cardinality Values) normalized = {k: v for k, v in tags.items() if k in ["http.status_code", "db.system", "rpc.method"]} return f"{service}:{operation}:{json.dumps(normalized, sort_keys=True)}" def aggregate(self, spans: list) -> dict: """Aggregiert Spans mit gleicher Signatur""" aggregates = {} for span in spans: key = self._make_key(span.service_name, span.operation_name, span.tags) if key not in aggregates: aggregates[key] = { "service": span.service_name, "operation": span.operation_name, "count": 0, "total_duration_ms": 0, "min_duration_ms": float('inf'), "max_duration_ms": 0, "error_count": 0, "p50_duration_ms": [], "tags": span.tags } agg = aggregates[key] agg["count"] += 1 duration = span.duration_ms or 0 agg["total_duration_ms"] += duration agg["min_duration_ms"] = min(agg["min_duration_ms"], duration) agg["max_duration_ms"] = max(agg["max_duration_ms"], duration) agg["error_count"] += 1 if span.status == "error" else 0 # Rolling P50 agg["p50_duration_ms"].append(duration) if len(agg["p50_duration_ms"]) > 100: agg["p50_duration_ms"].pop(0) # Calculate final metrics for agg in aggregates.values(): durations = sorted(agg["p50_duration_ms"]) if durations: idx = len(durations) // 2 agg["avg_duration_ms"] = agg["total_duration_ms"] / agg["count"] agg["p50_duration_ms"] = durations[idx] return aggregates

===== Kostenschätzer =====

def estimate_costs(): """Berechnet monatliche Kosten für verschiedene Konfigurationen""" configs = [ ("Kein Sampling", 1.0, 0), ("Adaptiv (empfohlen)", 0.05, 1), ("Aggressiv", 0.01, 2), ] requests_per_day = 1_000_000 spans_per_request = 50 days_per_month = 30 storage_per_span_usd = 0.000_001 # $1 pro Million Spans ai_analysis_per_1k_usd = 0.40 # DeepSeek V3.2 über HolySheep print("Monatliche Kostenanalyse (1M Requests/Tag)") print("=" * 60) for name, sample_rate, analyses_per_1k_traces in configs: total_spans = requests_per_day * spans_per_request * days_per_month sampled_spans = int(total_spans * sample_rate) storage_cost = sampled_spans * storage_per_span_usd ai_cost = (sampled_spans / 1000) * analyses_per_1k_traces * ai_analysis_per_1k_usd total_cost = storage_cost + ai_cost print(f"\n{name}:") print(f" Gesampelte Spans: {sampled_spans:,} ({sample_rate*100:.1f}%)") print(f" Storage-Kosten: ${storage_cost:.2f}") print(f" AI-Analyse: ${ai_cost:.2f}") print(f" Gesamt: ${total_cost:.2f}/Monat") if __name__ == "__main__": estimate_costs() # Test Adaptive Sampler print("\n" + "=" * 60) print("Adaptive Sampler Test") sampler = AdaptiveSampler() # Simuliere 10.000 Traces for i in range(10_000): duration = random.expovariate(1/200) # Exponentialverteilung, avg 200ms is_error = random.random() < 0.02 # 2% Fehlerrate sampler.should_sample(duration, is_error) stats = sampler.get_stats() print(f"Total Traces: {stats['total']:,}") print(f"Gesampled: {stats['sampled']:,} ({stats['sample_rate']*100:.1f}%)") print(f"Fehler: {stats['errors']:,}") print(f"Langsam (>500ms