Architecture
Ce sujet existe en version animée : voir le moteur dans le synoptique →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 reviennent dans la doc et le code, chacun avec un sens distinct :
- 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 ou messaging 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 processus avec le code 1. - Mode daemon / streaming / watch (
perf-sentinel watch) : processus de longue durée qui ingère les events OTLP/JSON en temps réel, avec éviction TTL et détection par trace. Les findings sont étiquetésdaemon_stagingoudaemon_productionselon[daemon] environment(augmente la sévérité chez les consommateurs en aval comme perf-lint).
L'axe Confidence (local_batch < ci_batch < daemon_staging < daemon_production) est étiqueté automatiquement par le runtime et exposé dans JSON, SARIF, HTML et l'API de requête du daemon. Un run batch est étiqueté ci_batch quand il détecte un environnement de CI (CI défini à autre chose que false ou 0, ou JENKINS_URL présent) et local_batch sinon, que --ci soit passé ou non.
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, Vec<PerEndpointIoOps>)
-> 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 routes, liens parent et destinations de consumer OTLP valides et échantillonnés de services nommés sont conservés avec les événements, afin que la fenêtre puisse résoudre une route d'entrée externe du même service, ou un span consumer, arrivé dans un export ultérieur. Les contextes d'endpoint, destinations de consumer et entrées d'ascendance sont plafonnés séparément par
max_events_per_trace, partagent le LRU et le TTL de la trace et ne comptent jamais comme événements I/O. - 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, sauf avec
[daemon] findings_stdout = false. - 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 inter-traces, plafonné à 1000)GET /api/status(uptime, traces actives, nombre de findings stockés)GET /api/config(configuration[daemon]effective, secrets résumés)GET /api/energy(santé live des backends énergie et intensité)GET /api/export/report(état live en JSON Report, pipe-compatible avecreport --input -)POSTetDELETE /api/findings/{signature}/ack,GET /api/acks(acquittements au runtime)POSTetGET /api/incidents(incidents Alertmanager avec leurs findings figés)
Un FindingsStore (buffer circulaire) retient les findings récents pour l'API (dimensionné par [daemon] max_retained_findings, défaut 10k). La sous-commande compagnon perf-sentinel query restitue ces endpoints en sortie colorée dans le terminal. Conditionné par [daemon] api_enabled (défaut true). Voir Limites pour le modèle de menace sans authentification.
Responsabilités des modules
| Module | Chemin | Responsabilité |
|---|---|---|
| event | event.rs | Type central SpanEvent (variantes SQL, HTTP et messaging) avec horodatage, 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/), dont la conversion recoud les requêtes SQL qu'une instrumentation en couches a scindées entre les spans qui portent le statement et ceux qui portent la durée (PHP Doctrine + PDO). Implémente le trait IngestSource. Parseur PostgreSQL pg_stat_statements (pg_stat.rs) et parseur des digests MySQL Performance Schema (mysql_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 frères séquentiels indépendants potentiellement parallélisables. Passages sous le seuil (below_threshold.rs) : quand le daemon tient une liste de surveillance, les détecteurs à seuil comptent dans leur propre boucle les exécutions des signatures à seuil surveillées restées au niveau de leur seuil ou en dessous, pour le registre de détections du daemon. Corrélateur inter-traces pour le mode daemon (correlate_cross.rs). Pistes de remédiation attachées aux findings (suggestions/). 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é du réseau embarquée, constantes SCI), carbon_compute.rs (boucle d'accumulation par span), carbon_profiles.rs (profils horaires du réseau) et region_breakdown.rs (regroupement par région, sélection du model-tag, finalisation du CarbonReport). Six backends de puissance et d'énergie opt-in, chacun un sous-module structuré : alumet/, scaphandre/ et kepler/ (RAPL), redfish/ (télémétrie BMC), cloud_energy/ (CPU% + interpolation SPECpower embarquée), electricity_maps/ (intensité du réseau en direct, uniquement si configuré), grid_feed/ (huit flux gratuits de gestionnaires de réseau pour l'intensité en direct, chacun en opt-in, Electricity Maps l'emportant là où les deux s'appliquent). Plomberie partagée des scrapers : energy_state.rs (cache de coefficients par service, et le partage d'une source : les services qui lisent une même source se partagent son énergie au prorata de leurs opérations), ops_snapshot_diff.rs (deltas d'opérations par service et par fenêtre de scrape) et prom_parser.rs (parseur d'exposition Prometheus partagé par les scrapers Kepler et Alumet). Hôtes déclarés : host_static.rs (les sections [green.database_static] et [green.broker_static] typées, leur placement et les noms base de données et broker) et broker_static.rs (le plafond provisionné qu'elles facturent). 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 du réseau pour 22 régions (mensuel x horaire complet pour FR/DE/GB/US-East, journalier représentatif pour 18 de plus). Équivalences Base Carbone des cartes à partager : equivalence.rs (recherche, choix, nombre calculé sur le CO2 imprimé) sur le fichier généré equivalence_data.rs. |
| 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/ + html_template.html embarqué) avec onglets Findings, Explain, pg_stat, mysql_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) periodic/pdf/ rend une divulgation en PDF/A-3b et en relit les pièces jointes (feature pdf). Cartes à partager (card/, feature card) : l'entrée card.json et les charges utiles signées complète et compacte (payload.rs), clés Ed25519, empreintes et enveloppes (keys.rs), la signature du contenu des fichiers (content.rs), les textes (copy.rs) et la mise en page (layout.rs), le rendu PNG sur tiny-skia (raster.rs), le rendu SVG (svg.rs), le rendu JPEG XL (jxl.rs) et le carrousel PDF (pdf.rs), la vérification des charges utiles signées et du contenu des fichiers (verify.rs), le lien de la clé au domaine (binding.rs) et ses lectures --online (online.rs). Voir docs/FR/SHARE-CARD-FR.md. |
| 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 plafond 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/ (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), detection_ledger.rs (comptes de détections par jour UTC et signature de finding, servis sur /api/detections), hub_detection_export.rs (pousse vers PerfSentinelHub les jours du registre qui ont changé) |
| config | config/ | 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 partagés de conversion d'horodatage (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 en ligne (suggestion, correctif par framework, emplacement dans le code). 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 |
| capture | capture.rs | Récepteur OTLP côté CI : monte les mêmes écouteurs OTLP gRPC/HTTP que le daemon sur un sink brut et écrit chaque requête reçue sous forme d'une ligne NDJSON pour analyze --ci. Aucune corrélation, aucune détection ni aucun scoring, et aucune conversion non plus, si bien que le fichier décrit ce que l'application a envoyé. Sous-tend la sous-commande capture, qui enveloppe aussi une commande de test et propage son code de sortie |
| 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, horodatages, 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/TTL de traces actives avec contexte OTLP d'endpoint et de parent borné pour l'attribution entre exports |
Frontières des crates
sentinel-cli (binaire)
|
+-- CLI clap : sous-commandes analyze / explain / diff / report / watch / query / ack / inspect / tempo / jaeger-query / pg-stat / mysql-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 reste 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) |
pdf | core et cli | activée dans cli, désactivée dans core | disclose-pdf et verify-hash --report report.pdf (rendu PDF/A-3b et lecture des pièces jointes) |
card | core et cli | activée dans cli, désactivée dans core | card keygen, card render et verify-card (cartes à partager, implique pdf) |
Le binaire publié active les six. Les consommateurs de la bibliothèque sentinel-core partent de tout désactivé et activent au besoin. tonic, axum, tower, prometheus et opentelemetry-proto restent des dépendances inconditionnelles de sentinel-core car report/metrics.rs et ingest/otlp/ utilisent leurs types même quand aucun écouteur ne tourne.