Architecture
perf-sentinel est un détecteur polyglotte d'anti-patterns de performance, construit sous forme de workspace Rust avec deux crates :
- sentinel-core : bibliothèque contenant toute la logique du pipeline
- sentinel-cli : binaire fournissant le point d'entrée CLI (
perf-sentinel)
Glossaire
Quelques termes récurrents dans la doc et le code, dont les distinctions comptent :
- Event — un SpanEvent normalisé. L'unité pré-détection (entrée).
- Finding — un anti-pattern détecté (sortie de
detect). Le concept produit, exposé dans la sortie CLI, JSON, SARIF, HTML, l'API daemon et les acks. - Pattern — le template SQL/HTTP normalisé qui regroupe les events d'un finding (ex.
SELECT * FROM users WHERE id = ?). - Detection — l'action, ou la famille de détecteurs (
n_plus_one,chatty, ...). N'est pas synonyme definding.
Modes opérationnels :
- Mode batch (
perf-sentinel analyze) — traite un jeu complet de traces et produit un seul Report. Utilisé pour les tests d'intégration CI, l'analyse ponctuelle, etperf-sentinel report(HTML). - Mode CI (
perf-sentinel analyze --ci) — identique au batch, mais une violation du quality gate fait sortir le process en code 1. Le champConfidencede chaque finding est taguéci_batch(confiance la plus faible : variété de trafic limitée). - Mode daemon / streaming / watch (
perf-sentinel watch) — process long-running qui ingère les events OTLP/JSON en temps réel, avec éviction TTL et détection per-trace. Les findings sont taguésdaemon_stagingoudaemon_productionselon[daemon] daemon_environment(booste la sévérité chez les consommateurs downstream comme perf-lint).
L'axe Confidence (ci_batch < daemon_staging < daemon_production) est tagué automatiquement par le runtime et exposé dans JSON, SARIF, HTML et l'API de requête du daemon.
Vue d'ensemble du pipeline
Modes de fonctionnement
Mode batch (perf-sentinel analyze)
Traite un ensemble complet d'événements et produit un rapport unique avec évaluation du quality gate.
Vec<SpanEvent>
-> normalize::normalize_all() -> Vec<NormalizedEvent>
-> correlate::correlate() -> Vec<Trace>
-> detect::detect() -> Vec<Finding>
-> score::score_green() -> (Vec<Finding>, GreenSummary)
-> quality_gate::evaluate() -> QualityGate
-> Report { analysis, findings, green_summary, quality_gate }En mode CI (--ci), le processus se termine avec le code 1 si le quality gate échoue.
Mode streaming (perf-sentinel watch)
Fonctionne comme un daemon, recevant les événements en temps réel et émettant les findings au fur et à mesure de leur détection.
OTLP gRPC (port 4317) \
OTLP HTTP (port 4318) +---> canal mpsc ---> TraceWindow (LRU + TTL)
Socket unix JSON / |
+--------+--------+
| |
Éviction LRU Éviction TTL
| |
+--------+--------+
|
normalize -> detect -> score
|
NDJSON findings (stdout)
Prometheus /metrics- Les événements sont normalisés en dehors du verrou TraceWindow pour minimiser le temps de détention du verrou.
- Les traces sont évincées lorsque le cache LRU est plein (
max_active_traces) ou lorsque le TTL expire (trace_ttl_ms). - À l'éviction, la trace est analysée via les étapes detect et score.
- Les findings sont émis en JSON délimité par des sauts de ligne sur stdout.
- Les métriques Prometheus sont exposées sur le même port HTTP (4318) à
/metrics.
API de requête du daemon
En mode watch, le daemon expose son état interne via des endpoints HTTP sur le port 4318 aux côtés de /v1/traces et /metrics :
GET /api/findings(filtrable, plafonné à 1000)GET /api/findings/{trace_id}GET /api/explain/{trace_id}(arbre de trace avec findings en ligne, depuis la mémoire du daemon)GET /api/correlations(corrélations cross-trace, plafonné à 1000)GET /api/status(uptime, traces actives, nombre de findings stockés)
Un FindingsStore (ring buffer) retient les findings récents pour l'API (dimensionné par [daemon] max_retained_findings, défaut 10k). La sous-commande compagnon perf-sentinel query rend ces endpoints en sortie coloré terminal. Gated par [daemon] api_enabled (défaut true). Voir Limites pour le threat model sans authentification.
Responsabilités des modules
| Module | Chemin | Responsabilité |
|---|---|---|
| event | event.rs | Type central SpanEvent (variantes SQL et HTTP) avec timestamp, IDs trace/span, service, opération, cible, durée |
| ingest | ingest/ | Sources d'entrée : parseur JSON avec auto-détection du format (json.rs), import Jaeger JSON (jaeger.rs), import Zipkin JSON v2 (zipkin.rs), récepteur OTLP gRPC+HTTP (otlp.rs). Implémente le trait IngestSource. Parseur PostgreSQL pg_stat_statements (pg_stat.rs) pour l'analyse de hotspots. Récupérateurs de traces distants pour Grafana Tempo (tempo.rs) et l'API de requête Jaeger (jaeger_query.rs), avec helpers partagés pour l'en-tête d'authentification (auth_header.rs), les fenêtres de recherche en arrière (lookback.rs) et l'encodage d'URL (url_enc.rs) |
| normalize | normalize/ | Produit des NormalizedEvent avec template + paramètres extraits. Tokenizer SQL (sql.rs) : remplace les littéraux, UUIDs, listes IN. Normaliseur HTTP (http.rs) : remplace les segments numériques/UUID, supprime les paramètres de requête |
| correlate | correlate/ | Regroupe les événements par trace_id. Mode batch (mod.rs) : agrégation par HashMap. Mode streaming (window.rs) : cache LRU avec buffer circulaire par trace et éviction TTL |
| detect | detect/ | Détection de patterns sur les traces corrélées. N+1 (n_plus_one.rs) : même template, paramètres différents, dans une fenêtre. Redondant (redundant.rs) : même template et paramètres. Lent (slow.rs) : durée au-dessus du seuil avec template récurrent. Fanout (fanout.rs) : span parent avec trop de spans enfants. Service bavard (chatty.rs) : trop d'appels HTTP sortants par trace. Saturation du pool (pool_saturation.rs) : spans SQL concurrents dépassant le seuil via algorithme de balayage. Appels sérialisés (serialized.rs) : appels siblings séquentiels indépendants potentiellement parallélisables. Corrélateur inter-traces pour le mode daemon (correlate_cross.rs). Pistes de remédiation attachées aux findings (suggestions.rs). Classification sensible aux sanitizers (sanitizer_aware.rs) : reconnaît le SQL déjà paramétré pour que la détection N+1 écarte les cas sûrs (SQL uniquement aujourd'hui) |
| score | score/ | Scoring GreenOps (mod.rs) : IIS par endpoint, ratio de gaspillage, top offenders, green_impact par finding. Pipeline carbone réparti entre carbon.rs (table d'intensité grid embarquée, constantes SCI), carbon_compute.rs (boucle d'accumulation par span), carbon_profiles.rs (profils horaires de grid) et region_breakdown.rs (fold par région, sélection du model-tag, finalisation du CarbonReport). Cinq backends de puissance et d'énergie opt-in, chacun un sous-module structuré : scaphandre/ et kepler/ (RAPL), redfish/ (télémétrie BMC), cloud_energy/ (CPU% + interpolation SPECpower embarquée), electricity_maps/ (intensité de grid en direct, uniquement si configuré). Plomberie partagée des scrapers : energy_state.rs (cache de coefficients par service) et ops_snapshot_diff.rs (deltas d'opérations par service et par fenêtre de scrape). Calcul de l'évitable à seuil canonique fixe contre la manipulation des divulgations (canonical.rs). Multi-région SCI v1.0 via attribut OTel cloud.region, intervalles de confiance, profils horaires pour FR/DE/GB/US-East |
| report | report/ | Formatage de sortie. Rapport JSON (json.rs), export SARIF v2.1.0 (sarif.rs), sortie CLI colorée (mod.rs), métriques Prometheus avec exemplars OpenMetrics (metrics.rs), dashboard HTML autonome en un fichier (html.rs + html_template.html embarqué) avec onglets Findings, Explain, pg_stat, Diff, Correlations et Green. Interprétations en langage clair des findings (interpret.rs), avertissements de rapport (warnings.rs), et la pile de divulgation publique périodique (periodic/ : schéma, agrégateur, validateur, configuration d'organisation, hacheur de contenu, attestation) |
| quality_gate | quality_gate.rs | Évalue des règles de seuils configurables par rapport aux findings et au résumé green |
| pipeline | pipeline.rs | Connecte toutes les étapes pour le mode batch : normalize -> correlate -> detect -> score -> quality_gate -> Report |
| daemon | daemon/ | Mode streaming : mod.rs héberge DaemonError et l'orchestrateur run(). Responsabilités réparties entre event_loop.rs (boucle tokio::select, éviction TraceWindow, worker d'analyse unique exécutant detect/score hors de la boucle), listeners.rs (spawn OTLP gRPC/HTTP, scrapers énergie optionnels), tls.rs (chargement certs, MaybeTlsStream, boucle HTTPS avec cap de concurrence sur les handshakes), json_socket.rs (ingestion NDJSON Unix, cfg(unix)), sampling.rs (échantillonnage par hash de trace-id), findings_store.rs + query_api.rs (findings retenus et endpoints HTTP de requête), ack.rs (store d'acquittements et endpoints HTTP d'ack), health.rs (liveness et readiness), archive.rs (archive NDJSON Report par fenêtre consommée par disclose) |
| config | config.rs | Parse .perf-sentinel.toml avec le format sectionné ([thresholds], [detection], [green], [daemon]). Les 8 clés top-level legacy acceptées en 0.5.x ont été retirées en 0.6.0 (voir Configuration pour la table de migration) |
| time | time.rs | Helpers de conversion timestamp partagés (nanos_to_iso8601, micros_to_iso8601). Utilisé par l'ingestion OTLP, Jaeger et Zipkin |
| explain | explain.rs | Visualiseur d'arbre de trace : construit l'arbre des spans à partir de parent_span_id, annote les findings inline (suggestion, fix par framework, code location). Sortie texte et JSON |
| diff | diff.rs | Delta de régression entre deux jeux de traces. Sous-tend la sous-commande diff utilisée en CI de PR |
| calibrate | calibrate.rs | Ajuste les coefficients I/O vers énergie à partir de traces de référence et d'un CSV de puissance mesurée. Sous-tend la sous-commande calibrate |
| acknowledgments | acknowledgments.rs | Filtrage .perf-sentinel-acknowledgments.toml pour la CI, avec une signature canonique sha2 par finding |
| http_client | http_client.rs | Le client HTTP sortant unique compatible TLS (limites de taille de corps, caviardage des endpoints) partagé par les scrapers d'énergie, l'ingestion Tempo et Jaeger, et query |
| shutdown | shutdown.rs | Future de signal d'arrêt (SIGINT partout, SIGTERM sous Unix) partagée par la boucle d'événements du daemon et la boucle de récupération Tempo |
| text_safety | text_safety.rs | Helpers purs partagés par tous les renderers texte qui impriment des chaînes contrôlées par l'attaquant vers un terminal : sanitize_for_terminal retire les caractères de contrôle ASCII pour neutraliser les injections ANSI / OSC 8 / curseur ; safe_url filtre suggested_fix.reference_url aux seules URL HTTPS sans caractères de contrôle |
Types principaux
| Type | Module | Description |
|---|---|---|
SpanEvent | event | Événement I/O brut (requête SQL ou appel HTTP) avec métadonnées et parent_span_id optionnel |
NormalizedEvent | normalize | SpanEvent enrichi d'un template normalisé et de paramètres extraits |
Trace | correlate | Collection de NormalizedEvents partageant le même trace_id |
Finding | detect | Anti-pattern détecté avec type, sévérité, détails du pattern, timestamps, green_impact et confidence (CI batch / daemon staging / daemon production) |
GreenSummary | score | Statistiques I/O agrégées : total ops, ops évitables, ratio de gaspillage, top offenders, CO2 optionnel |
QualityGate | quality_gate | Résultat pass/fail avec évaluations individuelles des règles |
Report | report | Sortie d'analyse complète : métadonnées d'analyse, findings, résumé green, quality gate |
Config | config | Configuration parsée avec toutes les sections et champs validés |
TraceWindow | correlate/window | Cache LRU de traces actives pour le mode streaming avec éviction TTL |
Frontières des crates
sentinel-cli (binaire)
|
+-- CLI clap : sous-commandes analyze / explain / diff / report / watch / query / ack / inspect / tempo / jaeger-query / pg-stat / calibrate / disclose / verify-hash / hash-bake / demo / bench / man / completions
|
+-- dépend de sentinel-core (bibliothèque)
|
+-- Toute la logique du pipeline
+-- Traits uniquement aux frontières : IngestSource, ReportSink
+-- Fonctions pures entre les étapesLe crate CLI est intentionnellement léger : il parse les arguments, charge la configuration et délègue aux fonctions de sentinel-core. Toute la logique métier réside dans sentinel-core.
Features Cargo
Des sous-commandes entières sont conditionnées à la compilation par des features Cargo :
| Feature | Définie dans | Défaut | Conditionne |
|---|---|---|---|
daemon | core et cli | activée dans cli, désactivée dans core | watch, query, ack, la boucle d'événements du daemon et les scrapers d'énergie |
tui | cli uniquement | activée | inspect et les prévisualisations interactives --tui |
tempo | core et cli | activée dans cli, désactivée dans core | la sous-commande tempo (ingestion via l'API Grafana Tempo) |
jaeger-query | core et cli | activée dans cli, désactivée dans core | la sous-commande jaeger-query (ingestion via l'API de requête Jaeger) |
Le binaire publié active les quatre. Les consommateurs de la bibliothèque sentinel-core partent de tout désactivé et activent au besoin. À noter que tonic, axum, tower, prometheus et opentelemetry-proto restent des dépendances inconditionnelles de sentinel-core car report/metrics.rs et ingest/otlp.rs utilisent leurs types même quand aucun écouteur ne tourne.