Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 17 additions & 30 deletions docs/spec-frontend-dashboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,16 @@ Supervision et architecture : **Rémi Cérès**
Dépôt : `corese-stack/corese-benchmark`
Date de publication : 29 octobre 2026

---

## 1. Principes Directeurs & Répartition des Rôles

- **Rémi Cérès (Backend)** : 100 % du moteur de benchmark en Java 21, isolation des sous-processus JVM, streaming sampler de mémoire, conformité formelle W3C, hachage canonique SHA-256 (`SparqlResultHasher`), et génération du rapport JSON auto-suffisant (`site/data/benchmark-report.json`).
- **Abdessamad Abdoun (Frontend)** : 100 % de l'expérience utilisateur, du dashboard web (`site/index.html`, `site/app.js`, `site/styles.css`), et des composants de visualisation Chart.js.

### Règle d'or pour le Frontend

**Le Frontend ne doit réaliser aucun calcul complexe, aucune déduction métier et aucune inférence heuristique.**
Tous les statuts (`PASS`, `MISMATCH`, `TIMEOUT`, `ERROR`), toutes les métriques (médianes, TTFR, deltas mémoire), et toutes les séries temporelles sont fournis calculés et typés par le backend dans le JSON.

---
Tous les statuts (`PASS`, `MISMATCH`, `TIMEOUT`, `ERROR`), toutes les métriques (médianes, TTFR, deltas mémoire), et toutes les séries temporelles sont fournis calculés et typés par le backend dans le JSON.

## 2. Pièges Techniques JavaScript & Règles Impératives

Expand All @@ -36,20 +34,20 @@ Ces points sont issus des audits de code et doivent être strictement respectés
- Un moteur ayant produit un résultat faux (`MISMATCH`) ou ayant expiré (`TIMEOUT`) ne doit **JAMAIS** recevoir la couleur verte de victoire ni être désigné vainqueur dans les cartes KPI ou dans les tableaux.
5. **Prévention des fuites mémoire Chart.js** :
- Avant chaque réinstanciation d'un canvas Chart.js, toujours exécuter :

```javascript
if (currentChartInstance) {
currentChartInstance.destroy();
}
```

6. **Protection contre les injections XSS** :
- Les requêtes SPARQL et les messages d'erreur système ne doivent jamais être injectés bruts dans le DOM avec `innerHTML`.
- Utiliser systématiquement `element.textContent = ...` ou une fonction d'échappement `escapeHtml()`.
7. **Autonomie locale complète (Zéro CDN)** :
- Télécharger `chart.umd.min.js` et le placer dans `site/vendor/chart.umd.min.js`.
- Remplacer le tag `<script src="https://cdn.jsdelivr.net/..."></script>` par `<script src="vendor/chart.umd.min.js"></script>`. Le dashboard doit s'ouvrir et fonctionner parfaitement sans connexion Internet.

---

## 3. Architecture de Navigation & Écrans Détaillés

La barre de navigation supérieure est permanente et ne doit jamais masquer d'onglets lors du changement de jeu de données :
Expand All @@ -58,9 +56,8 @@ La barre de navigation supérieure est permanente et ne doit jamais masquer d'on
[ Overview ] [ Loading & Ingestion ] [ Bowlogna ] [ BSBM ] [ LUBM ] [ DBpedia ] [ W3C Conformance ] [ Machine & Transparency ]
```

---

### Écran 1 : Overview (Synthèse & KPIs Globaux)

- **Header Pill** : Pastille permanente sous le titre du site :
`📍 Inria SED Sophia · 16c/32t CPU · 64 GB RAM · Linux 6.8 · OpenJDK 21 (-Xms4g -Xmx8g) · Protocole: Warmup + 3 Reps (Médiane)`.
- **Cartes KPI Globales** :
Expand All @@ -69,9 +66,8 @@ La barre de navigation supérieure est permanente et ne doit jamais masquer d'on
- Taux global de conformité / validité (% de requêtes `PASS` sur l'ensemble des benchmarks).
- **Graphiques** : Barchart comparatif du débit moyen et barchart de la latence médiane SPARQL.

---

### Écran 2 : Loading & Ingestion (Toutes Sources Confondues)

- **Graphique Bi-Axe Temps Réel (Chart.js LineChart)** :
- **Axe X** : Temps écoulé en secondes (`Elapsed Time (s)`).
- **Axe Y1 (Gauche)** : Mémoire Heap utilisée en Mo (`Heap Used (MB)`), courbes pleines par moteur (Bleu Corese `#4472C4`, Orange Jena `#ED7D31`, Vert RDF4J `#70AD47`).
Expand All @@ -82,14 +78,12 @@ La barre de navigation supérieure est permanente et ne doit jamais masquer d'on
- **Tableau détaillé d'ingestion** :
- Colonnes : `Engine` | `Dataset` | `File` | `Format` | `Triples` | `Median Time (ms)` | `Throughput (t/s)` | `Heap Delta (MB)` | `Repetitions [R1, R2, R3] (ms)` | `CV (%)`.

---

### Écran 3 : Bowlogna Benchmark (Académique / Université, 1.23M triplets, 17 requêtes)

#### Matrice des Familles de Requêtes & Classification :
#### Matrice des Familles de Requêtes & Classification

- **Toutes les requêtes (17)** : Bouton `All (17)`
- **Agrégations massives & GROUP BY (5 requêtes)** : Bouton `Aggregations`
*(Cible prioritaire du goulot Q8)* :
- **Agrégations massives & GROUP BY (5 requêtes)** : Bouton `Aggregations` *(Cible prioritaire du goulot Q8)* :
- `query_8` : Échecs par unité d'enseignement (`GROUP BY ?X`)
- `query_9` : Évaluations par unité d'enseignement (`GROUP BY ?X`)
- `query_11` : Top 5 des meilleures moyennes d'étudiants (`GROUP BY ?X ORDER BY DESC(?avgMark) LIMIT 5`)
Expand All @@ -110,16 +104,16 @@ La barre de navigation supérieure est permanente et ne doit jamais masquer d'on
- `query_2` : Thèses avec mention.
- `query_15` : Recherche d'unités enseignées en anglais.

#### Composants de l'écran :
#### Composants de l'écran

1. **Graphique TTFR empilé** : Segment 1 = `TTFR` (plein) + Segment 2 = Temps de complétion restant (hachuré).
2. **Tableau matriciel des résultats** avec badges `PASS`, `MISMATCH`, `TIMEOUT`, `ERROR`.
3. **Modale d'Inspection de Résultat (Diff Inspector)** :
- S'ouvre au clic sur n'importe quel badge `MISMATCH` ou sur une cellule du tableau.
- Contient : Requête SPARQL formatée, rowCount attendu vs obtenu, hash canonique SHA-256 attendu vs obtenu.

---

### Écran 4 : BSBM Benchmark (Berlin SPARQL Benchmark — eCommerce, 12 requêtes)

- **Boutons de filtres par familles** :
- `All Queries (12)`
- `Search & Product Features (Q1, Q2, Q3, Q4)`
Expand All @@ -129,19 +123,17 @@ La barre de navigation supérieure est permanente et ne doit jamais masquer d'on
- **Bascule d'échelle logarithmique** : Bouton `Log Scale` pour analyser les requêtes rapides (< 10 ms) et lourdes (> 1 000 ms) sur un même plan.
- **Matrice des 4 statuts** avec modale d'inspection de diff.

---

### Écran 5 : LUBM Benchmark (Inférence & RDFS, 14 requêtes)

- **Boutons de filtres par familles** :
- `All Queries (14)`
- `Large Result Sets (Q2, Q6, Q9)` (stress-test du streaming de résultats)
- `Selective Graph Patterns (Q1, Q3, Q5, Q7, Q8, Q10, Q14)`
- `Transitive & SubOrganization (Q4, Q11, Q12, Q13)`
- **Graphique de streaming TTFR** mettant en valeur la latence d'émission du premier tuple.

---

### Écran 6 : DBpedia Scale (Scalabilité sur Données Réelles)

- **Sélecteur de fichiers** :
- `persondata_en.nt` (356 914 triplets)
- `homepages_en.nt` (394 897 triplets)
Expand All @@ -151,28 +143,25 @@ La barre de navigation supérieure est permanente et ne doit jamais masquer d'on
- **Courbe de scalabilité bilogarithmique** : Triplets vs Temps de chargement.
- **Graphique d'efficacité mémoire** : Mo consommés par million de triplets par fichier.

---

### Écran 7 : W3C Conformance (Cadrage Précis — Zéro Doublon)

- **Règle** : Ne pas charger le fichier lourd de 2.3 Mo de `corese-w3c` dans `corese-benchmark`.
- **Affichage** :
- Carte de synthèse : Pass Rate officiel de Corese v5 ($99.38\%$, 2 897 / 2 915 tests réussis).
- Note explicative : *"La conformité officielle W3C SPARQL 1.0, 1.1 et SHACL est exécutée et certifiée de manière indépendante sur le banc dédié corese-w3c."*
- Bouton direct : `Consulter le portail officiel de conformité W3C (corese-w3c) ↗` pointant vers `https://corese-stack.github.io/corese-w3c/`.

---

### Écran 8 : Machine & Transparency (Fiche d'Identité SED Complète)

Grille de 6 cartes visuelles professionnelles :

1. **CPU** : Modèle exact (AMD EPYC / Intel Xeon), sockets, cœurs physiques, threads logiques, fréquence, gouverneur CPU (`performance`).
2. **Mémoire & Disque** : RAM physique totale (ex: 64 GB DDR4 ECC), swap actif, swappiness, type de disque.
3. **OS & Noyau** : Distribution Linux (`Ubuntu 24.04.1 LTS`), version exacte du noyau Linux (`Linux 6.8.0-45-generic`).
4. **JVM** : Vendeur, version OpenJDK 21, drapeaux JVM stricts appliqués aux sous-processus isolés (`-Xms4g -Xmx8g -XX:+UseG1GC`).
5. **Traçabilité logicielle** : Commit SHA cliquable de `corese-benchmark`, commit SHA de `corese-core`, versions exactes Jena (6.2.0) et RDF4J (5.3.2).
6. **Protocole de mesure** : Warmup (1 run), répétitions (3 runs), sélection de la médiane, timeouts (60s SPARQL, 120s chargement).

---

## 4. Organisation Recommandée du Code JavaScript (`site/js/`)

Pour éviter de surcharger un fichier monolithique, scinder le code en modules ES6 :
Expand Down Expand Up @@ -207,8 +196,6 @@ site/
└── transparency.js
```

---

## 5. Données de Test Mockées Clés en Main

Un fichier mock complet couvrant tous les cas (`PASS`, `MISMATCH`, `TIMEOUT`, `timeSeries`, métadonnées SED) est disponible directement dans :
Expand Down
51 changes: 27 additions & 24 deletions src/main/java/fr/inria/corese/benchmark/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import fr.inria.corese.benchmark.harness.BenchmarkHarness;
import fr.inria.corese.benchmark.metrics.FileResult;
import fr.inria.corese.benchmark.metrics.QueryResult;
import fr.inria.corese.benchmark.report.BenchmarkProtocol;
import fr.inria.corese.benchmark.report.BenchmarkReport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -73,20 +74,21 @@ public static void main(String[] args) {
List<FileResult> allFileResults = new ArrayList<>();
List<QueryResult> allQueryResults = new ArrayList<>();

BenchmarkProtocol proto = BenchmarkProtocol.fromOptions(opts.quick(), opts.queryTimeoutSecs());
boolean shouldIsolate = opts.isolateProcess() && opts.engines().size() > 1;
for (String engineName : opts.engines()) {
String cleanName = engineName.trim();
if (shouldIsolate) {
runChild(cleanName, opts, allFileResults, allQueryResults);
} else {
runEngine(cleanName, opts, queries, allFileResults, allQueryResults);
runEngine(cleanName, opts, queries, allFileResults, allQueryResults, proto);
}
}

crossValidateQueries(allQueryResults);

try {
BenchmarkReport.write(allFileResults, allQueryResults, queries, opts.outputFile());
BenchmarkReport.write(allFileResults, allQueryResults, queries, opts.outputFile(), proto);
} catch (Exception e) {
log.error("Failed to write report: {}", e.getMessage(), e);
System.exit(1);
Expand All @@ -108,22 +110,8 @@ private static void crossValidateQueries(List<QueryResult> queryResults) {
if (!"PASS".equals(qr.status()) || qr.resultHash().isEmpty()) {
continue;
}
String key = qr.dataset() + "::" + qr.queryId();
List<QueryResult> peers = grouped.get(key);
if (peers == null || peers.size() <= 1) {
continue;
}
boolean mismatch = false;
for (QueryResult peer : peers) {
if (peer == qr || !"PASS".equals(peer.status()) || peer.resultHash().isEmpty()) {
continue;
}
if (!peer.resultHash().equals(qr.resultHash()) || peer.rowCount() != qr.rowCount()) {
mismatch = true;
break;
}
}
if (mismatch) {
List<QueryResult> peers = grouped.get(qr.dataset() + "::" + qr.queryId());
if (hasPeerMismatch(qr, peers)) {
log.warn("Cross-engine mismatch detected for query {} on dataset {}: engine {} produced hash {} (rows: {})",
qr.queryId(), qr.dataset(), qr.engine(), qr.resultHash().substring(0, Math.min(8, qr.resultHash().length())), qr.rowCount());
queryResults.set(i, new QueryResult(
Expand All @@ -135,6 +123,19 @@ private static void crossValidateQueries(List<QueryResult> queryResults) {
}
}

private static boolean hasPeerMismatch(QueryResult qr, List<QueryResult> peers) {
if (peers == null || peers.size() <= 1) {
return false;
}
for (QueryResult peer : peers) {
if (peer != qr && "PASS".equals(peer.status()) && !peer.resultHash().isEmpty()
&& (!peer.resultHash().equals(qr.resultHash()) || peer.rowCount() != qr.rowCount())) {
return true;
}
}
return false;
}

private static BenchmarkOptions parseOptions(String[] args) {
String datasetDir = args[0];
String outputFile = args[1];
Expand Down Expand Up @@ -206,15 +207,13 @@ private static void runEngine(
BenchmarkOptions opts,
List<String> queries,
List<FileResult> allFileResults,
List<QueryResult> allQueryResults
List<QueryResult> allQueryResults,
BenchmarkProtocol protocol
) {
log.info("Starting engine: {}", engineName);
try {
TripleStoreDriver driver = DriverFactory.create(engineName, opts.inference());
int reps = opts.quick() ? 1 : 3;
BenchmarkHarness harness = opts.queryTimeoutSecs() > 0
? new BenchmarkHarness(driver, opts.datasetName(), reps, opts.queryTimeoutSecs())
: new BenchmarkHarness(driver, opts.datasetName(), reps);
BenchmarkHarness harness = new BenchmarkHarness(driver, opts.datasetName(), protocol);

List<FileResult> fileResults = harness.run(opts.datasetDir());
allFileResults.addAll(fileResults);
Expand All @@ -230,6 +229,7 @@ private static void runEngine(
}
}

@SuppressWarnings("java:S5443") // Ephemeral internal IPC JSON file cleaned up in finally block
private static void runChild(
String engineName,
BenchmarkOptions opts,
Expand Down Expand Up @@ -283,7 +283,10 @@ private static void readChildReport(
}
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(tmp.toFile());
if (root.has("runs")) {
if (root.has("loadingRuns")) {
FileResult[] frs = mapper.treeToValue(root.get("loadingRuns"), FileResult[].class);
allFileResults.addAll(Arrays.asList(frs));
} else if (root.has("runs")) {
FileResult[] frs = mapper.treeToValue(root.get("runs"), FileResult[].class);
allFileResults.addAll(Arrays.asList(frs));
}
Expand Down
Loading
Loading