================================================================================
[OK] GRAFANA & PROMETHEUS EN ÉQUIPE DE 3 DÉVELOPPEURS - APPLICATION PYTHON (GUIDE ULTRA-DÉTAILLÉ)
================================================================================

Ce guide couvre un cas réel et concret: une équipe de 3 développeurs Python
qui travaillent ensemble sur une application Flask et utilisent Prometheus
pour collecter des métriques et Grafana pour les visualiser en temps réel.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PRÉSENTATION DE L'ÉQUIPE, DU PROJET ET DES OUTILS
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

L'équipe:
  - Alice  -> Lead développeuse (aussi admin Grafana/Prometheus)
  - Bob    -> Développeur backend (responsable métriques applicatives)
  - Claire -> Développeuse backend + alertes

Le projet:
  - Nom: "TaskManager API"
  - Stack: Flask + SQLAlchemy + PostgreSQL
  - Monitoring: Prometheus (collecte) + Grafana (visualisation)
  - Versionning: Git + GitHub

Ce que fait chaque outil:
  ┌──────────────────────────────────────────────────────────────────┐
  │  PROMETHEUS                                                      │
  │  ─────────────────────────────────────────────────────────────  │
  │  -> Collecte les métriques (chiffres) depuis votre app          │
  │  -> Les stocke dans une base de données temporelles             │
  │  -> Interrogeable avec son propre langage: PromQL               │
  │  -> Exemple: "combien de requêtes HTTP par seconde?"            │
  │                                                                  │
  │  GRAFANA                                                         │
  │  ─────────────────────────────────────────────────────────────  │
  │  -> Se connecte à Prometheus comme source de données            │
  │  -> Affiche des graphiques, tableaux de bord, alertes           │
  │  -> Interface web belle et interactive                           │
  │  -> Exemple: graphique du temps de réponse API sur 7 jours      │
  └──────────────────────────────────────────────────────────────────┘

Schéma du flux de données:
  ┌─────────────────┐     scrape      ┌─────────────┐    query    ┌─────────┐
  │  Flask App      │  toutes les 15s │  Prometheus │ ──────────-> │ Grafana │
  │  /metrics ──────│────────────────->│  (stockage) │             │ (UI)    │
  │  (exposition    │                 │             │             │         │
  │   des métriques)│                 │  PromQL     │             │Dashboard│
  └─────────────────┘                 └─────────────┘             └─────────┘

Métriques que l'équipe va suivre:
  - Nombre de requêtes HTTP (par route, par méthode, par code de statut)
  - Latence des requêtes (temps de réponse)
  - Nombre d'erreurs 4xx et 5xx
  - Nombre de tâches créées/supprimées/modifiées
  - Utilisation mémoire et CPU de l'application
  - Connexions actives à la base de données

Structure du projet:
  taskmanager/
  ├── app/
  │   ├── __init__.py
  │   ├── models.py
  │   ├── routes.py
  │   ├── metrics.py          <- NOUVEAU: définition des métriques Prometheus
  │   └── middleware.py       <- NOUVEAU: collecte automatique des métriques HTTP
  ├── tests/
  │   ├── conftest.py
  │   └── test_routes.py
  ├── monitoring/
  │   ├── prometheus.yml      <- Config Prometheus
  │   ├── alert_rules.yml     <- Règles d'alertes
  │   └── grafana/
  │       ├── datasources/
  │       │   └── prometheus.yml
  │       └── dashboards/
  │           └── taskmanager.json
  ├── docker-compose.yml      <- Lance tout l'environnement
  ├── requirements.txt
  └── .env


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PARTIE 1 - COMPRENDRE PROMETHEUS: CONCEPTS DE BASE
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Avant de coder, Alice explique les 4 types de métriques Prometheus à l'équipe.

────────────────────────────────────────────────────────────────────────────────
TYPE 1 - COUNTER (Compteur)
────────────────────────────────────────────────────────────────────────────────

  Quoi: Un nombre qui ne peut QU'augmenter (jamais diminuer, sauf reset)
  Analogie: Le compteur kilométrique d'une voiture
  Usage: Compter des événements qui s'accumulent

  Exemples concrets:
  - Nombre total de requêtes HTTP reçues depuis le démarrage
  - Nombre total d'erreurs survenues
  - Nombre total de tâches créées depuis le démarrage

  En Python:
  ──────────
  from prometheus_client import Counter

  # Création du counter (nom, description, labels)
  http_requests_total = Counter(
      'http_requests_total',
      'Nombre total de requêtes HTTP reçues',
      ['method', 'endpoint', 'status_code']
  )

  # Utilisation (incrémenter de 1)
  http_requests_total.labels(
      method='GET',
      endpoint='/tasks',
      status_code='200'
  ).inc()

  Ce que Prometheus voit (endpoint /metrics):
  ─────────────────────────────────────────────
  # HELP http_requests_total Nombre total de requêtes HTTP reçues
  # TYPE http_requests_total counter
  http_requests_total{method="GET",endpoint="/tasks",status_code="200"} 1547.0
  http_requests_total{method="POST",endpoint="/tasks",status_code="201"} 89.0
  http_requests_total{method="GET",endpoint="/tasks",status_code="404"} 12.0

────────────────────────────────────────────────────────────────────────────────
TYPE 2 - GAUGE (Jauge)
────────────────────────────────────────────────────────────────────────────────

  Quoi: Un nombre qui peut monter ET descendre
  Analogie: La jauge d'essence d'une voiture
  Usage: Mesurer un état à un instant T

  Exemples concrets:
  - Nombre de requêtes en cours de traitement RIGHT NOW
  - Nombre de connexions actives à la DB
  - Utilisation mémoire actuelle
  - Nombre de tâches "en cours" (pas terminées)

  En Python:
  ──────────
  from prometheus_client import Gauge

  active_requests = Gauge(
      'active_requests',
      'Nombre de requêtes HTTP en cours de traitement'
  )

  tasks_in_progress = Gauge(
      'tasks_in_progress_total',
      'Nombre de tâches non terminées dans la base de données'
  )

  # Utilisation:
  active_requests.inc()    # +1 quand une requête arrive
  active_requests.dec()    # -1 quand la requête se termine
  active_requests.set(42)  # Fixer à une valeur précise

  tasks_in_progress.set(
      Task.query.filter_by(done=False).count()
  )

────────────────────────────────────────────────────────────────────────────────
TYPE 3 - HISTOGRAM (Histogramme)
────────────────────────────────────────────────────────────────────────────────

  Quoi: Mesure la distribution de valeurs dans des "buckets" (seaux)
  Analogie: Un distributeur qui trie les pièces par taille
  Usage: Mesurer des durées, des tailles de requêtes

  Exemples concrets:
  - Distribution du temps de réponse des requêtes
  - Distribution de la taille des payloads JSON
  - Permet de calculer des percentiles (p50, p95, p99)

  En Python:
  ──────────
  from prometheus_client import Histogram

  request_duration_seconds = Histogram(
      'http_request_duration_seconds',
      'Durée des requêtes HTTP en secondes',
      ['method', 'endpoint'],
      # Buckets: intervalles en secondes
      # < 0.01s | < 0.05s | < 0.1s | < 0.5s | < 1s | < 5s | tout le reste
      buckets=[0.01, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0]
  )

  # Utilisation (mesure automatique du temps):
  import time

  start = time.time()
  # ... traitement de la requête ...
  duration = time.time() - start

  request_duration_seconds.labels(
      method='GET',
      endpoint='/tasks'
  ).observe(duration)

  Ce que Prometheus stocke:
  ──────────────────────────
  # Nombre de requêtes par bucket de durée:
  http_request_duration_seconds_bucket{endpoint="/tasks",le="0.01"}  234
  http_request_duration_seconds_bucket{endpoint="/tasks",le="0.05"}  1023
  http_request_duration_seconds_bucket{endpoint="/tasks",le="0.1"}   1456
  http_request_duration_seconds_bucket{endpoint="/tasks",le="+Inf"}  1547
  # Somme totale des durées:
  http_request_duration_seconds_sum{endpoint="/tasks"}    287.3
  # Nombre total de mesures:
  http_request_duration_seconds_count{endpoint="/tasks"}  1547

────────────────────────────────────────────────────────────────────────────────
TYPE 4 - SUMMARY (Résumé)
────────────────────────────────────────────────────────────────────────────────

  Quoi: Similaire à Histogram mais calcule les percentiles côté application
  Analogie: Un assistant qui calcule directement "50% des commandes < 2min"
  Usage: Percentiles pré-calculés (moins flexible que Histogram)

  Note pour l'équipe:
  Préférer HISTOGRAM à SUMMARY dans la plupart des cas.
  HISTOGRAM permet de calculer les percentiles côté Prometheus (plus flexible).
  SUMMARY calcule les percentiles côté app (moins adaptable après coup).

  En Python (usage rare):
  ───────────────────────
  from prometheus_client import Summary

  request_processing_time = Summary(
      'request_processing_seconds',
      'Temps de traitement des requêtes'
  )

  # Utilisation avec décorateur:
  @request_processing_time.time()
  def process_request():
      # ... code ...
      pass


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PARTIE 2 - MISE EN PLACE PAR ALICE (ADMIN)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 2.1 - Alice crée le docker-compose.yml pour tout l'environnement
────────────────────────────────────────────────────────────────────────────────

Ce fichier lance Flask + Prometheus + Grafana en une seule commande.

  COMMANDE:
  ─────────
  cd taskmanager/
  nano docker-compose.yml

  CONTENU COMPLET:
  ────────────────

  version: '3.8'

  services:

    # ─── APPLICATION FLASK ─────────────────────────────────────────
    flask-app:
      build: .
      container_name: taskmanager-flask
      ports:
        - "5000:5000"    # API Flask
        - "8000:8000"    # Endpoint /metrics (Prometheus scraping)
      environment:
        - FLASK_ENV=development
        - DATABASE_URL=sqlite:///tasks.db
      volumes:
        - ./app:/app/app
      networks:
        - monitoring
      restart: unless-stopped

    # ─── PROMETHEUS ────────────────────────────────────────────────
    prometheus:
      image: prom/prometheus:latest
      container_name: taskmanager-prometheus
      ports:
        - "9090:9090"    # Interface web Prometheus
      volumes:
        # Fichier de configuration Prometheus
        - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml
        # Règles d'alertes
        - ./monitoring/alert_rules.yml:/etc/prometheus/alert_rules.yml
        # Persistance des données Prometheus (ne pas perdre l'historique)
        - prometheus_data:/prometheus
      command:
        - '--config.file=/etc/prometheus/prometheus.yml'
        - '--storage.tsdb.path=/prometheus'
        # Garder 30 jours de données
        - '--storage.tsdb.retention.time=30d'
        # Activer les webhooks d'alertes
        - '--web.enable-lifecycle'
      networks:
        - monitoring
      restart: unless-stopped

    # ─── GRAFANA ───────────────────────────────────────────────────
    grafana:
      image: grafana/grafana:latest
      container_name: taskmanager-grafana
      ports:
        - "3000:3000"    # Interface web Grafana
      environment:
        # Login admin Grafana
        - GF_SECURITY_ADMIN_USER=admin
        - GF_SECURITY_ADMIN_PASSWORD=GrafanaAdmin2024!
        # Désactiver le login anonyme
        - GF_AUTH_ANONYMOUS_ENABLED=false
        # Provisioning automatique (datasources + dashboards)
        - GF_PATHS_PROVISIONING=/etc/grafana/provisioning
      volumes:
        # Datasources auto-provisionnées
        - ./monitoring/grafana/datasources:/etc/grafana/provisioning/datasources
        # Dashboards auto-provisionnés
        - ./monitoring/grafana/dashboards:/etc/grafana/provisioning/dashboards
        # Persistance des données Grafana
        - grafana_data:/var/lib/grafana
      depends_on:
        - prometheus
      networks:
        - monitoring
      restart: unless-stopped

  # ─── VOLUMES PERSISTANTS ─────────────────────────────────────────
  volumes:
    prometheus_data:    # Historique des métriques
    grafana_data:       # Config Grafana, dashboards custom, utilisateurs

  # ─── RÉSEAU INTERNE ──────────────────────────────────────────────
  networks:
    monitoring:
      driver: bridge
      # Tous les services se voient par leur nom de service:
      # flask-app:5000, prometheus:9090, grafana:3000

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 2.2 - Alice crée le Dockerfile pour l'application Flask
────────────────────────────────────────────────────────────────────────────────

  COMMANDE:
  ─────────
  nano Dockerfile

  CONTENU:
  ────────

  FROM python:3.11-slim

  WORKDIR /app

  # Copier les dépendances en premier (cache Docker optimisé)
  COPY requirements.txt .
  RUN pip install --no-cache-dir -r requirements.txt

  # Copier le reste du code
  COPY . .

  # Exposer les deux ports:
  # 5000 = API Flask
  # 8000 = Métriques Prometheus
  EXPOSE 5000 8000

  CMD ["python", "run.py"]

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 2.3 - Alice configure Prometheus (prometheus.yml)
────────────────────────────────────────────────────────────────────────────────

  COMMANDES:
  ──────────
  mkdir -p monitoring
  nano monitoring/prometheus.yml

  CONTENU COMPLET:
  ────────────────

  # Fichier de configuration Prometheus
  global:
    # Intervalle de collecte des métriques (toutes les 15 secondes)
    scrape_interval: 15s

    # Timeout pour chaque collecte
    scrape_timeout: 10s

    # Intervalle d'évaluation des règles d'alertes
    evaluation_interval: 15s

    # Labels ajoutés à toutes les métriques (utile si plusieurs environnements)
    external_labels:
      environment: 'development'
      project: 'taskmanager'
      team: 'backend'

  # Fichiers de règles d'alertes
  rule_files:
    - /etc/prometheus/alert_rules.yml

  # ─── TARGETS À SCRAPER ────────────────────────────────────────────
  # Prometheus va "scraper" (interroger) ces endpoints toutes les 15s
  scrape_configs:

    # 1. Prometheus lui-même (auto-monitoring)
    - job_name: 'prometheus'
      static_configs:
        - targets: ['localhost:9090']
          labels:
            service: 'prometheus'

    # 2. Notre application Flask
    - job_name: 'taskmanager-flask'
      static_configs:
        # flask-app:8000 = nom du service Docker + port métriques
        - targets: ['flask-app:8000']
          labels:
            service: 'flask-api'
            language: 'python'
            framework: 'flask'

      # Chemin de l'endpoint métriques (défaut = /metrics)
      metrics_path: '/metrics'

      # Si votre app nécessite auth basique:
      # basic_auth:
      #   username: 'prometheus'
      #   password: 'secret'

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 2.4 - Alice crée les règles d'alertes Prometheus
────────────────────────────────────────────────────────────────────────────────

  COMMANDE:
  ─────────
  nano monitoring/alert_rules.yml

  CONTENU:
  ────────

  groups:
    # ─── GROUPE 1: ALERTES SUR L'APPLICATION ──────────────────────
    - name: taskmanager_application
      rules:

        # Alerte: Taux d'erreurs élevé (>5% des requêtes sont des erreurs 5xx)
        - alert: HighErrorRate
          expr: |
            rate(http_requests_total{status_code=~"5.."}[5m])
            /
            rate(http_requests_total[5m]) > 0.05
          for: 2m        # L'alerte se déclenche seulement si vraie pendant 2 min
          labels:
            severity: critical
            team: backend
          annotations:
            summary: "Taux d'erreurs HTTP élevé"
            description: |
              Plus de 5% des requêtes retournent une erreur 5xx
              depuis 2 minutes. Taux actuel: {{ $value | humanizePercentage }}

        # Alerte: Temps de réponse lent (95e percentile > 1 seconde)
        - alert: SlowResponseTime
          expr: |
            histogram_quantile(0.95,
              rate(http_request_duration_seconds_bucket[5m])
            ) > 1.0
          for: 5m
          labels:
            severity: warning
            team: backend
          annotations:
            summary: "Temps de réponse API lent"
            description: |
              95% des requêtes prennent plus de 1 seconde
              depuis 5 minutes. P95 actuel: {{ $value }}s

        # Alerte: Application Flask injoignable
        - alert: FlaskAppDown
          expr: up{job="taskmanager-flask"} == 0
          for: 1m
          labels:
            severity: critical
            team: backend
          annotations:
            summary: "Application Flask injoignable"
            description: "Prometheus ne peut plus scraper l'app Flask depuis 1 minute."

        # Alerte: Trop de requêtes (possible attaque ou problème)
        - alert: HighRequestRate
          expr: rate(http_requests_total[1m]) > 100
          for: 3m
          labels:
            severity: warning
            team: backend
          annotations:
            summary: "Trafic HTTP très élevé"
            description: "Plus de 100 requêtes/seconde depuis 3 minutes."

    # ─── GROUPE 2: ALERTES SYSTÈME ────────────────────────────────
    - name: taskmanager_system
      rules:

        # Alerte: Prometheus ne peut plus scraper la cible
        - alert: TargetDown
          expr: up == 0
          for: 1m
          labels:
            severity: critical
          annotations:
            summary: "Cible Prometheus hors ligne: {{ $labels.job }}"
            description: "{{ $labels.job }} est injoignable depuis 1 minute."

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 2.5 - Alice configure Grafana (provisioning automatique)
────────────────────────────────────────────────────────────────────────────────

Le provisioning permet à Grafana de configurer automatiquement la datasource
Prometheus au démarrage, sans intervention manuelle.

  COMMANDES:
  ──────────
  mkdir -p monitoring/grafana/datasources
  mkdir -p monitoring/grafana/dashboards
  nano monitoring/grafana/datasources/prometheus.yml

  CONTENU (datasource):
  ─────────────────────

  apiVersion: 1

  datasources:
    - name: Prometheus
      type: prometheus
      access: proxy
      # prometheus:9090 = nom du service Docker + port
      url: http://prometheus:9090
      isDefault: true
      editable: true
      jsonData:
        timeInterval: '15s'      # Aligné avec scrape_interval
        httpMethod: POST
        exemplarTraceIdDestinations: []

  COMMANDE:
  ─────────
  nano monitoring/grafana/dashboards/dashboards.yml

  CONTENU (config des dashboards):
  ─────────────────────────────────

  apiVersion: 1

  providers:
    - name: 'TaskManager Dashboards'
      orgId: 1
      folder: 'TaskManager'
      type: file
      disableDeletion: false
      editable: true
      updateIntervalSeconds: 30
      options:
        path: /etc/grafana/provisioning/dashboards

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 2.6 - Alice installe la librairie Prometheus côté Python
────────────────────────────────────────────────────────────────────────────────

  COMMANDE:
  ─────────
  nano requirements.txt

  CONTENU:
  ────────
  flask==3.0.0
  flask-sqlalchemy==3.1.1
  # Client Prometheus pour Python:
  prometheus-client==0.19.0
  # Pour mesurer l'utilisation système:
  psutil==5.9.6
  # Pour les tests:
  pytest==7.4.0
  pytest-cov==4.1.0


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PARTIE 3 - INSTRUMENTATION DE L'APPLICATION FLASK PAR BOB
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Bob est responsable d'ajouter les métriques dans le code Flask.

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 3.1 - Bob crée le fichier de définition des métriques
────────────────────────────────────────────────────────────────────────────────

  COMMANDE:
  ─────────
  nano app/metrics.py

  CONTENU COMPLET:
  ────────────────

  """
  app/metrics.py
  ──────────────
  Définition centralisée de toutes les métriques Prometheus
  de l'application TaskManager.

  Toutes les métriques sont définies ici et importées
  là où elles sont nécessaires.
  """

  from prometheus_client import Counter, Gauge, Histogram, Info
  import psutil
  import os


  # ═══════════════════════════════════════════════════════════════════
  # MÉTRIQUES HTTP (collectées automatiquement via middleware)
  # ═══════════════════════════════════════════════════════════════════

  # Compteur total de requêtes HTTP
  # Labels: method (GET/POST/...), endpoint (/tasks/...), status_code (200/404/...)
  http_requests_total = Counter(
      'http_requests_total',
      'Nombre total de requêtes HTTP reçues',
      ['method', 'endpoint', 'status_code']
  )

  # Histogramme de la durée des requêtes HTTP (en secondes)
  # Permet de calculer p50, p95, p99
  http_request_duration_seconds = Histogram(
      'http_request_duration_seconds',
      'Durée des requêtes HTTP en secondes',
      ['method', 'endpoint'],
      buckets=[
          0.005,  # 5ms
          0.01,   # 10ms
          0.025,  # 25ms
          0.05,   # 50ms
          0.1,    # 100ms
          0.25,   # 250ms
          0.5,    # 500ms
          1.0,    # 1 seconde
          2.5,    # 2.5 secondes
          5.0,    # 5 secondes
          10.0    # 10 secondes
      ]
  )

  # Jauge: requêtes en cours de traitement RIGHT NOW
  http_requests_in_progress = Gauge(
      'http_requests_in_progress',
      'Nombre de requêtes HTTP en cours de traitement'
  )

  # Compteur d'erreurs HTTP (4xx et 5xx séparément)
  http_errors_total = Counter(
      'http_errors_total',
      'Nombre total d\'erreurs HTTP',
      ['method', 'endpoint', 'status_code', 'error_type']
      # error_type: 'client_error' (4xx) ou 'server_error' (5xx)
  )


  # ═══════════════════════════════════════════════════════════════════
  # MÉTRIQUES MÉTIER (spécifiques à TaskManager)
  # ═══════════════════════════════════════════════════════════════════

  # Compteurs d'opérations sur les tâches
  tasks_created_total = Counter(
      'tasks_created_total',
      'Nombre total de tâches créées depuis le démarrage'
  )

  tasks_deleted_total = Counter(
      'tasks_deleted_total',
      'Nombre total de tâches supprimées depuis le démarrage'
  )

  tasks_completed_total = Counter(
      'tasks_completed_total',
      'Nombre total de tâches marquées comme terminées'
  )

  # Jauge: état actuel de la base de données
  tasks_total_gauge = Gauge(
      'tasks_total',
      'Nombre total de tâches dans la base de données'
  )

  tasks_done_gauge = Gauge(
      'tasks_done_total',
      'Nombre de tâches terminées dans la base de données'
  )

  tasks_pending_gauge = Gauge(
      'tasks_pending_total',
      'Nombre de tâches en attente dans la base de données'
  )


  # ═══════════════════════════════════════════════════════════════════
  # MÉTRIQUES SYSTÈME (CPU, mémoire, etc.)
  # ═══════════════════════════════════════════════════════════════════

  # Jauge: utilisation mémoire du processus Python (en bytes)
  process_memory_bytes = Gauge(
      'process_memory_bytes',
      'Utilisation mémoire du processus Flask en bytes'
  )

  # Jauge: utilisation CPU du processus (en %)
  process_cpu_percent = Gauge(
      'process_cpu_percent',
      'Utilisation CPU du processus Flask en pourcentage'
  )

  # Info: informations statiques sur l'application
  app_info = Info(
      'taskmanager_app',
      'Informations sur l\'application TaskManager'
  )

  # Initialiser les infos statiques au démarrage
  app_info.info({
      'version': '1.0.0',
      'language': 'python',
      'framework': 'flask',
      'team': 'backend'
  })


  # ═══════════════════════════════════════════════════════════════════
  # FONCTIONS UTILITAIRES
  # ═══════════════════════════════════════════════════════════════════

  def update_system_metrics():
      """
      Met à jour les métriques système (CPU, mémoire).
      À appeler périodiquement depuis l'application.
      """
      process = psutil.Process(os.getpid())
      process_memory_bytes.set(process.memory_info().rss)
      process_cpu_percent.set(process.cpu_percent(interval=None))


  def update_task_gauges(app):
      """
      Met à jour les jauges de tâches depuis la base de données.
      À appeler après chaque opération CRUD sur les tâches.

      Args:
          app: L'application Flask (pour le contexte applicatif)
      """
      from app.models import Task

      with app.app_context():
          total = Task.query.count()
          done = Task.query.filter_by(done=True).count()
          pending = total - done

          tasks_total_gauge.set(total)
          tasks_done_gauge.set(done)
          tasks_pending_gauge.set(pending)

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 3.2 - Bob crée le middleware de collecte automatique des métriques HTTP
────────────────────────────────────────────────────────────────────────────────

Ce middleware intercepte TOUTES les requêtes Flask automatiquement.
Bob n'a pas à ajouter de code dans chaque route.

  COMMANDE:
  ─────────
  nano app/middleware.py

  CONTENU:
  ────────

  """
  app/middleware.py
  ─────────────────
  Middleware Flask qui collecte automatiquement les métriques
  HTTP pour toutes les routes de l'application.
  """

  import time
  from flask import request, g
  from app.metrics import (
      http_requests_total,
      http_request_duration_seconds,
      http_requests_in_progress,
      http_errors_total,
      update_system_metrics
  )


  def normalize_endpoint(path):
      """
      Normalise les URLs avec des IDs pour éviter trop de labels distincts.

      Exemple:
        /tasks/1  -> /tasks/<id>
        /tasks/42 -> /tasks/<id>
        /tasks    -> /tasks

      Sans normalisation, on aurait un label différent pour chaque ID,
      ce qui provoquerait une 'cardinality explosion' dans Prometheus.
      """
      import re
      # Remplace les nombres dans les URLs par <id>
      path = re.sub(r'/\d+', '/<id>', path)
      return path


  def register_metrics_middleware(app):
      """
      Enregistre les hooks before/after request pour collecter
      les métriques automatiquement sur toutes les routes.

      Usage dans app/__init__.py:
          from app.middleware import register_metrics_middleware
          register_metrics_middleware(app)
      """

      @app.before_request
      def before_request_metrics():
          """
          Exécuté AVANT chaque requête:
          - Enregistre l'heure de début
          - Incrémente le compteur de requêtes en cours
          """
          g.start_time = time.time()
          http_requests_in_progress.inc()

      @app.after_request
      def after_request_metrics(response):
          """
          Exécuté APRÈS chaque requête:
          - Calcule la durée de la requête
          - Enregistre toutes les métriques
          - Décrémente le compteur de requêtes en cours
          """
          # Calculer la durée
          duration = time.time() - g.get('start_time', time.time())

          # Normaliser l'endpoint (éviter explosion de cardinalité)
          endpoint = normalize_endpoint(request.path)
          method = request.method
          status_code = str(response.status_code)

          # Incrémenter le compteur de requêtes
          http_requests_total.labels(
              method=method,
              endpoint=endpoint,
              status_code=status_code
          ).inc()

          # Observer la durée dans l'histogramme
          http_request_duration_seconds.labels(
              method=method,
              endpoint=endpoint
          ).observe(duration)

          # Compter les erreurs séparément
          if response.status_code >= 500:
              http_errors_total.labels(
                  method=method,
                  endpoint=endpoint,
                  status_code=status_code,
                  error_type='server_error'
              ).inc()
          elif response.status_code >= 400:
              http_errors_total.labels(
                  method=method,
                  endpoint=endpoint,
                  status_code=status_code,
                  error_type='client_error'
              ).inc()

          # Décrémenter le compteur de requêtes en cours
          http_requests_in_progress.dec()

          # Mettre à jour les métriques système (CPU, mémoire)
          update_system_metrics()

          return response

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 3.3 - Bob modifie app/__init__.py pour exposer /metrics
────────────────────────────────────────────────────────────────────────────────

  COMMANDE:
  ─────────
  nano app/__init__.py

  CONTENU:
  ────────

  from flask import Flask
  from flask_sqlalchemy import SQLAlchemy
  from prometheus_client import make_wsgi_app, REGISTRY
  from werkzeug.middleware.dispatcher import DispatcherMiddleware
  from werkzeug.serving import run_simple

  db = SQLAlchemy()


  def create_app(config=None):
      app = Flask(__name__)

      # Configuration de base
      app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///tasks.db'
      app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

      if config:
          app.config.update(config)

      # Initialiser la base de données
      db.init_app(app)

      # [OK] Enregistrer le middleware de métriques
      from app.middleware import register_metrics_middleware
      register_metrics_middleware(app)

      # Enregistrer les routes
      from app.routes import tasks_bp
      app.register_blueprint(tasks_bp)

      with app.app_context():
          db.create_all()

      return app


  def create_app_with_metrics(config=None):
      """
      Crée l'application Flask avec l'exposition des métriques Prometheus
      sur un port séparé (8000) grâce au DispatcherMiddleware.

      Architecture:
        Port 5000 -> Application Flask normale (API)
        Port 8000 -> Endpoint /metrics (Prometheus scraping)

      Séparer les ports est une bonne pratique:
        - Les métriques ne sont pas accessibles par les utilisateurs de l'API
        - Permet de sécuriser /metrics indépendamment
      """
      app = create_app(config)

      # Créer l'application WSGI qui expose les métriques
      metrics_app = make_wsgi_app()

      # Dispatcher: redirige les requêtes vers la bonne app selon le port
      # (géré par run.py via deux serveurs WSGI distincts)
      return app, metrics_app

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 3.4 - Bob modifie run.py pour exposer les métriques sur le port 8000
────────────────────────────────────────────────────────────────────────────────

  COMMANDE:
  ─────────
  nano run.py

  CONTENU:
  ────────

  """
  run.py
  ──────
  Point d'entrée de l'application.
  Lance deux serveurs WSGI:
    - Port 5000: API Flask (pour les clients)
    - Port 8000: Métriques Prometheus (pour Prometheus scraper)
  """

  import threading
  from wsgiref.simple_server import make_server
  from prometheus_client import make_wsgi_app
  from app import create_app


  def start_metrics_server():
      """Lance le serveur de métriques Prometheus sur le port 8000."""
      metrics_app = make_wsgi_app()
      server = make_server('0.0.0.0', 8000, metrics_app)
      print("[GRAPHIQUE] Prometheus metrics server démarré sur http://localhost:8000/metrics")
      server.serve_forever()


  if __name__ == '__main__':
      # Créer l'application Flask
      app = create_app()

      # Lancer le serveur de métriques dans un thread séparé
      # (daemon=True: s'arrête quand le programme principal s'arrête)
      metrics_thread = threading.Thread(
          target=start_metrics_server,
          daemon=True
      )
      metrics_thread.start()

      print("[RAPIDE] Flask API démarrée sur http://localhost:5000")
      print("[GRAPHIQUE] Prometheus metrics sur http://localhost:8000/metrics")

      # Lancer Flask
      app.run(host='0.0.0.0', port=5000, debug=True, use_reloader=False)
      # Note: use_reloader=False car le thread metrics ne survivrait pas au reload

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 3.5 - Bob ajoute les métriques métier dans les routes Flask
────────────────────────────────────────────────────────────────────────────────

  COMMANDE:
  ─────────
  nano app/routes.py

  CONTENU:
  ────────

  from flask import Blueprint, request, jsonify, abort, current_app
  from app import db
  from app.models import Task
  from app.metrics import (
      tasks_created_total,
      tasks_deleted_total,
      tasks_completed_total,
      update_task_gauges
  )

  tasks_bp = Blueprint('tasks', __name__)


  @tasks_bp.route('/tasks', methods=['GET'])
  def get_tasks():
      tasks = Task.query.all()
      return jsonify([t.to_dict() for t in tasks])


  @tasks_bp.route('/tasks', methods=['POST'])
  def create_task():
      data = request.get_json()
      if not data or 'title' not in data:
          abort(400, description="Title is required")

      task = Task(title=data['title'], description=data.get('description'))
      db.session.add(task)
      db.session.commit()

      # [OK] Incrémenter le counter métier
      tasks_created_total.inc()

      # [OK] Mettre à jour les jauges (nombre total, done, pending)
      update_task_gauges(current_app._get_current_object())

      return jsonify(task.to_dict()), 201


  @tasks_bp.route('/tasks/<int:task_id>', methods=['GET'])
  def get_task(task_id):
      task = Task.query.get(task_id)
      if task is None:
          abort(404, description="Task not found")
      return jsonify(task.to_dict())


  @tasks_bp.route('/tasks/<int:task_id>', methods=['PUT'])
  def update_task(task_id):
      task = Task.query.get(task_id)
      if task is None:
          abort(404, description="Task not found")

      data = request.get_json()
      if not data:
          abort(400, description="No data provided")

      # [OK] Détecter si la tâche vient d'être complétée
      was_done_before = task.done
      task.title = data.get('title', task.title)
      task.description = data.get('description', task.description)
      task.done = data.get('done', task.done)
      db.session.commit()

      # [OK] Incrémenter le counter de tâches complétées
      if not was_done_before and task.done:
          tasks_completed_total.inc()

      # [OK] Mettre à jour les jauges
      update_task_gauges(current_app._get_current_object())

      return jsonify(task.to_dict())


  @tasks_bp.route('/tasks/<int:task_id>', methods=['DELETE'])
  def delete_task(task_id):
      task = Task.query.get(task_id)
      if task is None:
          abort(404, description="Task not found")

      db.session.delete(task)
      db.session.commit()

      # [OK] Incrémenter le counter de suppressions
      tasks_deleted_total.inc()

      # [OK] Mettre à jour les jauges
      update_task_gauges(current_app._get_current_object())

      return jsonify({'message': 'Task deleted'}), 200


  @tasks_bp.route('/health', methods=['GET'])
  def health_check():
      """
      Endpoint de santé de l'application.
      Utilisé par Docker/Kubernetes pour vérifier que l'app tourne.
      """
      return jsonify({
          'status': 'healthy',
          'service': 'taskmanager-api',
          'version': '1.0.0'
      })

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 3.6 - Bob vérifie que l'endpoint /metrics fonctionne
────────────────────────────────────────────────────────────────────────────────

Bob lance l'application et teste l'endpoint métriques:

  COMMANDES:
  ──────────
  # Activer l'environnement virtuel
  source venv/bin/activate

  # Lancer l'application
  python run.py

  # Dans un autre terminal, tester l'endpoint métriques:
  curl http://localhost:8000/metrics

  SORTIE ATTENDUE (extrait):
  ──────────────────────────
  # HELP http_requests_total Nombre total de requêtes HTTP reçues
  # TYPE http_requests_total counter
  http_requests_total{endpoint="/tasks",method="GET",status_code="200"} 3.0
  http_requests_total{endpoint="/tasks",method="POST",status_code="201"} 1.0

  # HELP http_request_duration_seconds Durée des requêtes HTTP en secondes
  # TYPE http_request_duration_seconds histogram
  http_request_duration_seconds_bucket{endpoint="/tasks",method="GET",le="0.005"} 2.0
  http_request_duration_seconds_bucket{endpoint="/tasks",method="GET",le="0.01"} 3.0
  http_request_duration_seconds_bucket{endpoint="/tasks",method="GET",le="+Inf"} 3.0
  http_request_duration_seconds_sum{endpoint="/tasks",method="GET"} 0.00847

  # HELP tasks_created_total Nombre total de tâches créées
  # TYPE tasks_created_total counter
  tasks_created_total_total 1.0

  # HELP process_memory_bytes Utilisation mémoire du processus Flask
  # TYPE process_memory_bytes gauge
  process_memory_bytes 45678592.0

  # HELP taskmanager_app_info Informations sur l'application TaskManager
  # TYPE taskmanager_app_info gauge
  taskmanager_app_info{framework="flask",language="python",team="backend",version="1.0.0"} 1.0

  -> Si vous voyez ces lignes, l'instrumentation fonctionne! [OK]


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PARTIE 4 - LANCEMENT ET EXPLORATION DE PROMETHEUS (ALICE ET L'ÉQUIPE)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 4.1 - Alice lance tout l'environnement avec Docker Compose
────────────────────────────────────────────────────────────────────────────────

  COMMANDE:
  ─────────
  docker-compose up -d

  SORTIE ATTENDUE:
  ────────────────
  [+] Running 3/3
   [OK] Container taskmanager-prometheus  Started   (3.2s)
   [OK] Container taskmanager-grafana     Started   (3.5s)
   [OK] Container taskmanager-flask       Started   (4.1s)

  Vérifier que tout est lancé:
  COMMANDE: docker-compose ps

  NAME                     STATUS      PORTS
  taskmanager-flask        Up          0.0.0.0:5000->5000/tcp, 0.0.0.0:8000->8000/tcp
  taskmanager-grafana      Up          0.0.0.0:3000->3000/tcp
  taskmanager-prometheus   Up          0.0.0.0:9090->9090/tcp

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 4.2 - Exploration de l'interface Prometheus
────────────────────────────────────────────────────────────────────────────────

INTERFACE WEB PROMETHEUS:

  1. Alice ouvre: http://localhost:9090

  2. Elle voit la page principale Prometheus:
     ┌──────────────────────────────────────────────────────────────┐
     │  Prometheus  [Alerts] [Graph] [Status [BLACK_DOWN-POINTING_TRIANGLE]] [Help]              │
     │                                                              │
     │  Expression (PromQL):                                        │
     │  [                                            ] [Execute]    │
     │                                                              │
     │  [WHITE_CIRCLE] Table  [BLACK_CIRCLE] Graph                                            │
     └──────────────────────────────────────────────────────────────┘

  3. Vérifier que la cible Flask est bien scrapée:
     Chemin: Status > Targets

     ┌──────────────────────────────────────────────────────────────┐
     │  Targets                                                     │
     │                                                              │
     │  taskmanager-flask (1/1 up)                                  │
     ├─────────────┬────────┬────────────────┬──────────────────────┤
     │  Endpoint   │ State  │ Last Scrape    │ Scrape Duration      │
     ├─────────────┼────────┼────────────────┼──────────────────────┤
     │  flask-app  │   UP   │  13.2s ago     │  48.3ms              │
     │  :8000/     │ [OK]     │                │                      │
     ├─────────────┼────────┼────────────────┼──────────────────────┤
     │  prometheus │   UP   │  4.1s ago      │  12.1ms              │
     │  :9090/     │ [OK]     │                │                      │
     └─────────────┴────────┴────────────────┴──────────────────────┘

  -> Les deux cibles sont UP [OK]

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 4.3 - Exemples de requêtes PromQL (langage Prometheus)
────────────────────────────────────────────────────────────────────────────────

Alice montre à l'équipe comment interroger Prometheus.
Dans le champ Expression de http://localhost:9090, saisir chaque requête:

  REQUÊTE 1 - Voir toutes les métriques de requêtes HTTP:
  ─────────────────────────────────────────────────────────
  http_requests_total

  RÉSULTAT (Table):
  ┌──────────────────────────────────────────────────────┬───────┐
  │  Metric                                              │ Value │
  ├──────────────────────────────────────────────────────┼───────┤
  │  http_requests_total{endpoint="/tasks",method="GET"} │  47   │
  │  http_requests_total{endpoint="/tasks",method="POST"}│   8   │
  │  http_requests_total{endpoint="/health",method="GET"}│  12   │
  └──────────────────────────────────────────────────────┴───────┘

  REQUÊTE 2 - Taux de requêtes par seconde (sur les 5 dernières minutes):
  ──────────────────────────────────────────────────────────────────────
  rate(http_requests_total[5m])

  -> rate() calcule la vitesse d'évolution d'un counter
  -> [5m] = fenêtre glissante de 5 minutes
  -> Résultat: environ 0.15 requêtes/seconde

  REQUÊTE 3 - Taux de requêtes agrégé (toutes endpoints confondues):
  ──────────────────────────────────────────────────────────────────
  sum(rate(http_requests_total[5m]))

  -> sum() additionne les valeurs de tous les labels

  REQUÊTE 4 - Taux de requêtes par endpoint:
  ───────────────────────────────────────────
  sum by (endpoint) (rate(http_requests_total[5m]))

  RÉSULTAT:
  ┌──────────────────────────────┬──────────────┐
  │  endpoint                    │  Value       │
  ├──────────────────────────────┼──────────────┤
  │  /tasks                      │  0.125 req/s │
  │  /tasks/<id>                 │  0.048 req/s │
  │  /health                     │  0.033 req/s │
  └──────────────────────────────┴──────────────┘

  REQUÊTE 5 - Percentile 95 du temps de réponse (p95):
  ──────────────────────────────────────────────────────
  histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))

  -> histogram_quantile(0.95, ...) = 95% des requêtes sont sous ce temps
  -> Résultat: 0.087 = 87ms -> 95% des requêtes répondent en < 87ms

  REQUÊTE 6 - P95 par endpoint:
  ──────────────────────────────
  histogram_quantile(0.95,
    sum by (endpoint, le) (
      rate(http_request_duration_seconds_bucket[5m])
    )
  )

  REQUÊTE 7 - Taux d'erreurs 5xx:
  ─────────────────────────────────
  rate(http_requests_total{status_code=~"5.."}[5m])

  -> status_code=~"5.." : regex qui matche 500, 501, 502, 503...

  REQUÊTE 8 - Pourcentage d'erreurs:
  ────────────────────────────────────
  rate(http_requests_total{status_code=~"5.."}[5m])
  /
  rate(http_requests_total[5m]) * 100

  REQUÊTE 9 - Nombre de tâches dans la base (Gauge):
  ────────────────────────────────────────────────────
  tasks_total

  REQUÊTE 10 - Mémoire utilisée en MB:
  ─────────────────────────────────────
  process_memory_bytes / 1024 / 1024

  REQUÊTE 11 - Requêtes en cours right now:
  ────────────────────────────────────────
  http_requests_in_progress


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PARTIE 5 - CRÉATION DES DASHBOARDS GRAFANA PAR CLAIRE
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Claire est responsable de créer les dashboards Grafana pour l'équipe.

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 5.1 - Claire se connecte à Grafana
────────────────────────────────────────────────────────────────────────────────

INTERFACE WEB GRAFANA:

  1. Ouvrir: http://localhost:3000

  2. Page de connexion:
     ┌─────────────────────────────────────┐
     │  [ORANGE] Grafana                         │
     │                                     │
     │  Username: [admin               ]   │
     │  Password: [GrafanaAdmin2024!   ]   │
     │                                     │
     │  [      Log In      ]               │
     └─────────────────────────────────────┘

  3. Connexion: admin / GrafanaAdmin2024!

  4. Grafana affiche la page d'accueil:
     ┌──────────────────────────────────────────────────────────────┐
     │  [ACCUEIL] Home                                                     │
     │                                                              │
     │  Welcome to Grafana!                                         │
     │                                                              │
     │  [+ New Dashboard]  [Browse Dashboards]  [Add datasource]   │
     └──────────────────────────────────────────────────────────────┘

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 5.2 - Claire crée des comptes pour Bob et Alice dans Grafana
────────────────────────────────────────────────────────────────────────────────

INTERFACE WEB GRAFANA:

  1. Chemin: Administration (icône engrenage) > Users > [+ Invite]

  2. Créer compte Bob:
     ┌─────────────────────────────────────────────────────────────┐
     │  Invite User                                                │
     │                                                             │
     │  Email or username: [bob@equipe.com        ]               │
     │  Name:              [Bob Dupont            ]               │
     │  Role:              [BLACK_CIRCLE] Editor                               │
     │                     (peut créer des dashboards)            │
     │                                                             │
     │  [   Submit   ]                                             │
     └─────────────────────────────────────────────────────────────┘

  3. Créer compte Alice:
     Email: alice@equipe.com | Role: Admin

  4. Créer compte Claire (son propre compte):
     Email: claire@equipe.com | Role: Editor

  Rôles Grafana:
  - Admin  : Tout faire (gestion users, datasources, alertes)
  - Editor : Créer/modifier dashboards et alertes
  - Viewer : Voir seulement (lecture seule)

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 5.3 - Claire vérifie que la datasource Prometheus est bien configurée
────────────────────────────────────────────────────────────────────────────────

INTERFACE WEB GRAFANA:

  1. Chemin: Connections > Data sources

  2. Elle voit "Prometheus" (auto-provisionné par le fichier datasources.yml):
     ┌──────────────────────────────────────────────────────────────┐
     │  Data sources                                                │
     │                                                              │
     │  [OK] Prometheus   http://prometheus:9090   Default           │
     └──────────────────────────────────────────────────────────────┘

  3. Elle clique sur "Prometheus" puis "Save & test":
     ┌──────────────────────────────────────────────────────────────┐
     │  [OK] Successfully queried the Prometheus API.                 │
     └──────────────────────────────────────────────────────────────┘

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 5.4 - Claire crée le dashboard principal de l'équipe
────────────────────────────────────────────────────────────────────────────────

INTERFACE WEB GRAFANA:

  1. Chemin: Dashboards > [+ New] > New dashboard

  2. Claire voit une page vide avec un bouton "+ Add visualization"

  3. Elle clique sur "+ Add visualization"

  ─── PANEL 1: Taux de requêtes par seconde ──────────────────────────────────

  4. Dans "Select data source": choisir "Prometheus"

  5. Dans l'éditeur de requête (Query):
     ┌──────────────────────────────────────────────────────────────┐
     │  Query A:                                                    │
     │  [sum(rate(http_requests_total[5m]))               ]        │
     │  Legend: Requêtes/sec                                        │
     └──────────────────────────────────────────────────────────────┘

  6. À droite, configurer le panel:
     Panel title: "Taux de Requêtes HTTP"
     Visualization: Time series (graphique en ligne)
     Unit: reqps (requests per second)

  7. Cliquer "Apply" (en haut à droite)

  ─── PANEL 2: Temps de réponse P50/P95/P99 ──────────────────────────────────

  8. Ajouter un nouveau panel: "+ Add" > "Visualization"

  9. Requêtes multiples pour P50, P95, P99:

     Query A:
     [histogram_quantile(0.50, sum by(le)(rate(http_request_duration_seconds_bucket[5m])))]
     Legend: P50

     Query B:
     [histogram_quantile(0.95, sum by(le)(rate(http_request_duration_seconds_bucket[5m])))]
     Legend: P95

     Query C:
     [histogram_quantile(0.99, sum by(le)(rate(http_request_duration_seconds_bucket[5m])))]
     Legend: P99

  10. Configuration du panel:
      Title: "Temps de Réponse (percentiles)"
      Visualization: Time series
      Unit: seconds (s)
      
      Thresholds (seuils de couleur):
      Vert  : < 0.1s   (moins de 100ms -> excellent)
      Jaune : < 0.5s   (moins de 500ms -> acceptable)
      Rouge : >= 0.5s  (plus de 500ms -> problème!)

  11. Cliquer "Apply"

  ─── PANEL 3: Taux d'erreurs ─────────────────────────────────────────────────

  12. Ajouter un nouveau panel

  13. Requête:
      [sum(rate(http_requests_total{status_code=~"5.."}[5m]))]
      Legend: Erreurs 5xx/sec

  14. Configuration:
      Title: "Erreurs HTTP 5xx"
      Visualization: Stat (grand nombre)
      Unit: reqps
      Thresholds:
        Vert : 0
        Rouge: > 0.1

  ─── PANEL 4: Statut de l'application (UP/DOWN) ──────────────────────────────

  15. Requête:
      [up{job="taskmanager-flask"}]
      Legend: Flask App

  16. Configuration:
      Title: "Statut Application"
      Visualization: Stat
      Value mappings:
        1 -> "UP [VERT]"
        0 -> "DOWN [ROUGE]"

  ─── PANEL 5: Métriques des tâches ──────────────────────────────────────────

  17. Requête A: [tasks_total]            Legend: Total
  18. Requête B: [tasks_done_total]       Legend: Terminées
  19. Requête C: [tasks_pending_total]    Legend: En attente

  20. Configuration:
      Title: "État des Tâches"
      Visualization: Stat (multi-value)

  ─── PANEL 6: Utilisation mémoire ───────────────────────────────────────────

  21. Requête:
      [process_memory_bytes / 1024 / 1024]
      Legend: Mémoire Flask

  22. Configuration:
      Title: "Mémoire Flask (MB)"
      Visualization: Gauge (jauge circulaire)
      Unit: megabytes (MB)
      Min: 0 | Max: 512

  ─── PANEL 7: Heatmap des temps de réponse ──────────────────────────────────

  23. Requête:
      [sum by(le)(rate(http_request_duration_seconds_bucket[5m]))]
      Format: Heatmap

  24. Configuration:
      Title: "Distribution des temps de réponse"
      Visualization: Heatmap
      -> Montre la distribution des durées sous forme de grille colorée

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 5.5 - Claire sauvegarde le dashboard et ajoute des variables
────────────────────────────────────────────────────────────────────────────────

INTERFACE WEB GRAFANA:

  1. Cliquer sur l'icône "Save" (disquette) en haut
     ┌──────────────────────────────────────────────────────────────┐
     │  Save dashboard                                              │
     │                                                              │
     │  Dashboard name: [TaskManager - Vue d'ensemble  ]           │
     │  Folder:         [TaskManager                   ]           │
     │  Tags:           [flask] [python] [backend]                  │
     │                                                              │
     │  [   Save   ]                                                │
     └──────────────────────────────────────────────────────────────┘

  2. Ajouter une variable "endpoint" pour filtrer par route:
     Chemin: Dashboard settings ([CONFIG]) > Variables > [+ Add variable]

     ┌──────────────────────────────────────────────────────────────┐
     │  Variable                                                    │
     │                                                              │
     │  Name:  endpoint                                             │
     │  Type:  Query                                                │
     │  Label: Endpoint                                             │
     │                                                              │
     │  Query:  label_values(http_requests_total, endpoint)         │
     │  (récupère automatiquement tous les endpoints disponibles)   │
     │                                                              │
     │  Multi-value: [OK] (peut sélectionner plusieurs endpoints)     │
     │  Include All: [OK] (option "Tout" disponible)                  │
     │                                                              │
     │  [   Apply   ]                                               │
     └──────────────────────────────────────────────────────────────┘

  3. Utiliser la variable dans les requêtes des panels:
     Avant: sum(rate(http_requests_total[5m]))
     Après: sum(rate(http_requests_total{endpoint=~"$endpoint"}[5m]))
     -> $endpoint est remplacé par la valeur du filtre

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 5.6 - Claire exporte le dashboard en JSON pour le versionner dans Git
────────────────────────────────────────────────────────────────────────────────

INTERFACE WEB GRAFANA:

  1. Dashboard settings ([CONFIG]) > JSON model

  2. Elle voit le JSON complet du dashboard

  3. Elle copie et sauvegarde dans le projet:
     COMMANDE:
     ─────────
     nano monitoring/grafana/dashboards/taskmanager.json
     # Coller le JSON copié depuis Grafana

  4. Commit dans Git:
     git add monitoring/grafana/dashboards/taskmanager.json
     git commit -m "feat: add TaskManager Grafana dashboard"
     git push

  -> Tout le dashboard est maintenant versionné! [OK]
  -> Au prochain docker-compose up, le dashboard sera rechargé automatiquement.


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PARTIE 6 - CONFIGURATION DES ALERTES GRAFANA PAR CLAIRE
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 6.1 - Claire crée un Contact Point (où envoyer les alertes)
────────────────────────────────────────────────────────────────────────────────

INTERFACE WEB GRAFANA:

  1. Chemin: Alerting > Contact points > [+ Add contact point]

  2. Pour les alertes par email:
     ┌──────────────────────────────────────────────────────────────┐
     │  New contact point                                           │
     │                                                              │
     │  Name: equipe-backend                                        │
     │  Type: Email                                                 │
     │                                                              │
     │  Addresses:                                                  │
     │  alice@equipe.com;bob@equipe.com;claire@equipe.com          │
     │                                                              │
     │  [   Save contact point   ]                                  │
     └──────────────────────────────────────────────────────────────┘

  3. Pour les alertes Slack (si l'équipe utilise Slack):
     Type: Slack
     URL: https://hooks.slack.com/services/XXXX/YYYY/ZZZZ
           (Webhook URL du channel #alertes-prod)
     Channel: #alertes-prod

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 6.2 - Claire crée les règles d'alertes Grafana
────────────────────────────────────────────────────────────────────────────────

INTERFACE WEB GRAFANA:

  1. Chemin: Alerting > Alert rules > [+ New alert rule]

  ─── ALERTE 1: Temps de réponse trop lent ────────────────────────────────────

  2. Configurer l'alerte:
     ┌──────────────────────────────────────────────────────────────┐
     │  New alert rule                                              │
     │                                                              │
     │  Rule name: TempsReponse-P95-Haut                           │
     │  Folder: TaskManager                                         │
     │                                                              │
     │  ── Define query and alert condition ──                     │
     │                                                              │
     │  Query A:                                                    │
     │  histogram_quantile(0.95,                                    │
     │    sum by(le)(                                               │
     │      rate(http_request_duration_seconds_bucket[5m])          │
     │    )                                                         │
     │  )                                                           │
     │                                                              │
     │  Condition: WHEN query A IS ABOVE 1.0                       │
     │  (déclencher si P95 > 1 seconde)                            │
     │                                                              │
     │  ── Alert evaluation behavior ──                            │
     │  Evaluate every: 1m                                          │
     │  For: 5m   (confirmer pendant 5 min avant d'alerter)        │
     │                                                              │
     │  ── Configure labels and notifications ──                   │
     │  severity: warning                                           │
     │  team: backend                                               │
     │                                                              │
     │  Contact point: equipe-backend                               │
     │                                                              │
     │  [   Save rule and exit   ]                                  │
     └──────────────────────────────────────────────────────────────┘

  ─── ALERTE 2: Taux d'erreurs critique ───────────────────────────────────────

  3. Nouvelle règle:
     Name: TauxErreurs-5xx-Critique
     Query A: rate(http_errors_total{error_type="server_error"}[5m])
     Condition: IS ABOVE 0.05   (plus de 5% d'erreurs)
     For: 2m
     Severity: critical
     Contact point: equipe-backend

  ─── ALERTE 3: Application hors ligne ────────────────────────────────────────

  4. Nouvelle règle:
     Name: App-Flask-Hors-Ligne
     Query A: up{job="taskmanager-flask"}
     Condition: IS BELOW 1
     For: 1m
     Severity: critical
     Contact point: equipe-backend

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 6.3 - Claire configure la politique de notification (silences)
────────────────────────────────────────────────────────────────────────────────

INTERFACE WEB GRAFANA:

  1. Chemin: Alerting > Silences > [+ Add silence]

  Créer un silence pour la maintenance du vendredi soir:
  ┌──────────────────────────────────────────────────────────────┐
  │  New silence                                                 │
  │                                                              │
  │  Start:   2024-01-12 22:00:00                               │
  │  End:     2024-01-13 06:00:00                               │
  │                                                              │
  │  Matchers (labels des alertes à silencer):                  │
  │  severity = critical                                         │
  │                                                              │
  │  Comment: Maintenance vendredi soir - mise à jour serveur   │
  │  Created by: claire                                          │
  │                                                              │
  │  [   Submit   ]                                              │
  └──────────────────────────────────────────────────────────────┘


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PARTIE 7 - WORKFLOW QUOTIDIEN DE L'ÉQUIPE (CAS CONCRETS)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

════════════════════════════════════════════════════════════════════════════════
SCÉNARIO A - Bob détecte une lenteur sur une route spécifique
════════════════════════════════════════════════════════════════════════════════

Lundi matin, Bob ouvre Grafana et voit que le P95 est à 1.2 secondes.

──────────────────────────────────────────────────────────────────────────────
A.1 - Bob identifie la route lente dans Grafana
──────────────────────────────────────────────────────────────────────────────

INTERFACE WEB GRAFANA:

  1. Bob ouvre http://localhost:3000
  2. Il ouvre le dashboard "TaskManager - Vue d'ensemble"
  3. Il voit le panel "Temps de Réponse":
     P50 = 45ms  [OK]
     P95 = 1.2s  [ROUGE] <- anormal!
     P99 = 2.8s  [ROUGE]

  4. Il utilise la variable "endpoint" pour filtrer:
     Il sélectionne successivement chaque endpoint dans le filtre:
     /tasks      -> P95 = 50ms   [OK]
     /tasks/<id> -> P95 = 45ms   [OK]
     /tasks/filter -> P95 = 1.3s [ROUGE] <- problème ici!

  5. Bob sait maintenant que c'est la route /tasks/filter qui est lente.

──────────────────────────────────────────────────────────────────────────────
A.2 - Bob utilise Prometheus pour investiguer
──────────────────────────────────────────────────────────────────────────────

INTERFACE WEB PROMETHEUS (http://localhost:9090):

  Bob saisit dans l'expression:

  REQUÊTE:
  ─────────
  histogram_quantile(0.95,
    rate(http_request_duration_seconds_bucket{endpoint="/tasks/filter"}[5m])
  )

  RÉSULTAT: 1.289 secondes

  Il regarde combien de requêtes touchent cet endpoint:

  REQUÊTE:
  ─────────
  rate(http_requests_total{endpoint="/tasks/filter"}[5m])

  RÉSULTAT: 0.08 req/s (environ 5 par minute)

  Il compare avec la charge normale (/tasks):

  REQUÊTE:
  ─────────
  rate(http_requests_total{endpoint="/tasks"}[5m])

  RÉSULTAT: 0.5 req/s (environ 30 par minute)

──────────────────────────────────────────────────────────────────────────────
A.3 - Bob trouve et corrige le problème
──────────────────────────────────────────────────────────────────────────────

Bob regarde le code de la route /tasks/filter:

  [X] CODE LENT (original):
  def filter_tasks():
      status = request.args.get('done', 'false')
      tasks = Task.query.all()  # <- Charge TOUTES les tâches en mémoire!
      if status == 'true':
          return jsonify([t.to_dict() for t in tasks if t.done])
      return jsonify([t.to_dict() for t in tasks if not t.done])

  [OK] CODE OPTIMISÉ:
  def filter_tasks():
      done = request.args.get('done', 'false').lower() == 'true'
      tasks = Task.query.filter_by(done=done).all()  # Filtre côté DB
      return jsonify([t.to_dict() for t in tasks])

Bob redémarre l'app et observe Grafana:

  Avant: P95 /tasks/filter = 1.3s
  Après: P95 /tasks/filter = 48ms [OK]

  Il laisse un commentaire dans Grafana sur le dashboard:
  Chemin: Panel "Temps de Réponse" > Add annotation
  ┌──────────────────────────────────────────────────────────────┐
  │  Add annotation                                              │
  │  Text: Optimisation requête /tasks/filter - P95: 1.3s->48ms  │
  │  Tags: fix, performance, bob                                 │
  │  [  Save  ]                                                  │
  └──────────────────────────────────────────────────────────────┘

  -> Une annotation verticale apparaît sur le graphique à l'heure de la correction.
  -> L'équipe peut voir exactement quand l'amélioration a eu lieu.

════════════════════════════════════════════════════════════════════════════════
SCÉNARIO B - Claire reçoit une alerte et réagit
════════════════════════════════════════════════════════════════════════════════

Mercredi à 23h47, Claire reçoit un email d'alerte Grafana:

  ─── EMAIL REÇU ──────────────────────────────────────────────────────────────

  From: Grafana Alerts <alerts@grafana.equipe.local>
  To: equipe-backend
  Subject: [CRITICAL] App-Flask-Hors-Ligne

  [ROUGE] FIRING: App-Flask-Hors-Ligne

  Valeur: 0 (attendu: 1)
  Labels: job=taskmanager-flask, severity=critical
  Heure: 2024-01-10 23:47:12

  Description: L'application Flask est injoignable depuis 1 minute.

  Voir dans Grafana: http://localhost:3000/alerting/list

  ─────────────────────────────────────────────────────────────────────────────

──────────────────────────────────────────────────────────────────────────────
B.1 - Claire investigate depuis son domicile
──────────────────────────────────────────────────────────────────────────────

  COMMANDES depuis son PC personnel:
  ─────────────────────────────────────

  # Vérifier l'état des containers
  docker-compose ps

  SORTIE:
  NAME                     STATUS
  taskmanager-flask        Exited (137)   <- OOM Kill! (Out of Memory)
  taskmanager-prometheus   Up
  taskmanager-grafana      Up

  # Redémarrer l'application
  docker-compose start flask-app

  # Vérifier les logs pour comprendre pourquoi
  docker-compose logs --tail=100 flask-app

  SORTIE LOGS:
  ────────────
  [2024-01-10 23:46:02] INFO: 200 GET /tasks (45ms)
  [2024-01-10 23:46:47] INFO: 201 POST /tasks (32ms)
  [2024-01-10 23:46:48] WARNING: Memory usage: 485MB
  [2024-01-10 23:46:50] WARNING: Memory usage: 498MB
  [2024-01-10 23:46:55] ERROR: Killed (OOM - Out of Memory)

──────────────────────────────────────────────────────────────────────────────
B.2 - Claire analyse l'historique dans Grafana
──────────────────────────────────────────────────────────────────────────────

INTERFACE WEB GRAFANA:

  1. Claire ouvre le dashboard "TaskManager - Vue d'ensemble"
  2. Elle sélectionne la période "Last 1 hour"
  3. Elle voit sur le panel "Mémoire Flask (MB)":
     23:30: 85MB   <- normal
     23:40: 200MB  <- commence à monter
     23:45: 480MB  <- pic avant crash!
     23:47: 0      <- crash

  4. Elle corrèle avec le trafic (panel "Taux de Requêtes"):
     23:40-23:47: 8 requêtes/sec <- trafic très élevé (spike inhabituel)

  5. Elle voit la cause: un spike de trafic a causé une fuite mémoire
     (les tâches étaient chargées toutes en mémoire dans la mauvaise version)

──────────────────────────────────────────────────────────────────────────────
B.3 - Claire ajoute une alerte préventive mémoire
──────────────────────────────────────────────────────────────────────────────

INTERFACE WEB GRAFANA:

  Chemin: Alerting > Alert rules > [+ New alert rule]

  Nouvelle alerte préventive:
  Name: MemorieFlask-Haute
  Query A: process_memory_bytes / 1024 / 1024  (en MB)
  Condition: IS ABOVE 400   (alerter à 400MB, avant le crash à 500MB)
  For: 2m
  Severity: warning
  Contact point: equipe-backend
  Message: "Mémoire Flask élevée ({{ $value | humanize }}MB). Investiguer avant OOM."

════════════════════════════════════════════════════════════════════════════════
SCÉNARIO C - Alice fait la revue de monitoring hebdomadaire
════════════════════════════════════════════════════════════════════════════════

Chaque lundi matin, Alice analyse les métriques de la semaine passée.

──────────────────────────────────────────────────────────────────────────────
C.1 - Alice regarde les tendances sur 7 jours
──────────────────────────────────────────────────────────────────────────────

INTERFACE WEB GRAFANA:

  1. Dashboard "TaskManager - Vue d'ensemble"
  2. Sélectionner "Last 7 days" dans le sélecteur de période (en haut à droite)

  3. Alice note les métriques de la semaine dans un tableau:

     ┌────────────────────────────────────────────────────────────────┐
     │  Métriques semaine du 08-14 jan 2024                          │
     ├──────────────────────────┬───────────┬───────────┬────────────┤
     │  Métrique                │  Lun-Mer  │  Jeu-Ven  │  Tendance  │
     ├──────────────────────────┼───────────┼───────────┼────────────┤
     │  Taux requêtes (req/s)   │  0.12     │  0.18     │  ^ +50%   │
     │  P95 temps réponse       │  85ms     │  48ms     │  v Mieux! │
     │  Taux erreurs 5xx (%)    │  0.2%     │  0.1%     │  v Mieux! │
     │  Tâches créées (total)   │  234      │  312      │  ^ +33%   │
     │  Uptime                  │  99.3%    │  100%     │  [OK]       │
     └──────────────────────────┴───────────┴───────────┴────────────┘

  -> Le P95 a baissé (grâce au fix de Bob le lundi)
  -> Le trafic augmente (bon signe, plus d'utilisateurs)
  -> Les erreurs ont diminué

──────────────────────────────────────────────────────────────────────────────
C.2 - Alice crée un dashboard de rapport hebdomadaire
──────────────────────────────────────────────────────────────────────────────

INTERFACE WEB GRAFANA:

  1. Créer nouveau dashboard: "TaskManager - Rapport Hebdomadaire"

  2. Panel "Uptime de la semaine":
     Requête: avg_over_time(up{job="taskmanager-flask"}[7d]) * 100
     Visualization: Stat
     Unit: Percent (0-100)

  3. Panel "Total requêtes cette semaine":
     Requête: increase(http_requests_total[7d])
     Visualization: Stat

  4. Panel "Tâches créées cette semaine":
     Requête: increase(tasks_created_total_total[7d])
     Visualization: Stat

  5. Panel "Top 5 endpoints les plus utilisés":
     Requête: topk(5, sum by(endpoint)(increase(http_requests_total[7d])))
     Visualization: Bar chart

  6. Panel "Taux d'erreurs par jour":
     Requête:
     sum by(day)(
       rate(http_requests_total{status_code=~"5.."}[1h])
     ) / sum by(day)(rate(http_requests_total[1h])) * 100
     Visualization: Table

  7. Alice programme l'export automatique:
     Chemin: Dashboard settings > Time options > Refresh: Every 1h

  8. Pour envoyer le rapport par email:
     Chemin: Share (icône partage) > Export as PNG ou PDF
     -> Grafana génère une image du dashboard
     -> Alice peut l'attacher dans un email hebdomadaire à l'équipe


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PARTIE 8 - REQUÊTES PROMQL AVANCÉES DE RÉFÉRENCE
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

────────────────────────────────────────────────────────────────────────────────
REQUÊTES SUR LE TRAFIC
────────────────────────────────────────────────────────────────────────────────

  # Requêtes/seconde total:
  sum(rate(http_requests_total[5m]))

  # Requêtes/seconde par endpoint:
  sum by (endpoint) (rate(http_requests_total[5m]))

  # Requêtes/seconde par méthode HTTP:
  sum by (method) (rate(http_requests_total[5m]))

  # Top 3 endpoints les plus chargés:
  topk(3, sum by(endpoint)(rate(http_requests_total[5m])))

  # Requêtes/seconde uniquement les POST (créations):
  rate(http_requests_total{method="POST"}[5m])

────────────────────────────────────────────────────────────────────────────────
REQUÊTES SUR LES TEMPS DE RÉPONSE
────────────────────────────────────────────────────────────────────────────────

  # P50 (médiane) global:
  histogram_quantile(0.5, sum by(le)(rate(http_request_duration_seconds_bucket[5m])))

  # P95 global:
  histogram_quantile(0.95, sum by(le)(rate(http_request_duration_seconds_bucket[5m])))

  # P99 global:
  histogram_quantile(0.99, sum by(le)(rate(http_request_duration_seconds_bucket[5m])))

  # P95 par endpoint:
  histogram_quantile(0.95,
    sum by(endpoint, le)(rate(http_request_duration_seconds_bucket[5m]))
  )

  # Temps moyen de réponse:
  rate(http_request_duration_seconds_sum[5m])
  /
  rate(http_request_duration_seconds_count[5m])

────────────────────────────────────────────────────────────────────────────────
REQUÊTES SUR LES ERREURS
────────────────────────────────────────────────────────────────────────────────

  # Taux d'erreurs 5xx (valeur absolue en req/s):
  rate(http_errors_total{error_type="server_error"}[5m])

  # Taux d'erreurs 4xx:
  rate(http_errors_total{error_type="client_error"}[5m])

  # Pourcentage d'erreurs 5xx (en %):
  rate(http_requests_total{status_code=~"5.."}[5m])
  /
  rate(http_requests_total[5m]) * 100

  # Erreurs 404 spécifiquement:
  rate(http_requests_total{status_code="404"}[5m])

  # Erreurs par endpoint:
  sum by(endpoint)(rate(http_errors_total[5m]))

────────────────────────────────────────────────────────────────────────────────
REQUÊTES SUR LES MÉTRIQUES MÉTIER
────────────────────────────────────────────────────────────────────────────────

  # Tâches créées par heure:
  increase(tasks_created_total_total[1h])

  # Tâches créées par jour:
  increase(tasks_created_total_total[24h])

  # Ratio tâches terminées / total:
  tasks_done_total / tasks_total * 100

  # Taux de complétion (tâches terminées par heure):
  rate(tasks_completed_total_total[1h]) * 3600

────────────────────────────────────────────────────────────────────────────────
REQUÊTES SUR LES RESSOURCES SYSTÈME
────────────────────────────────────────────────────────────────────────────────

  # Mémoire en MB:
  process_memory_bytes / 1024 / 1024

  # Mémoire en GB:
  process_memory_bytes / 1024 / 1024 / 1024

  # CPU en %:
  process_cpu_percent

  # Évolution de la mémoire (dernière heure):
  increase(process_memory_bytes[1h])


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PARTIE 9 - RÉSUMÉ VISUEL ET COMMANDES DE RÉFÉRENCE
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

────────────────────────────────────────────────────────────────────────────────
QUI FAIT QUOI
────────────────────────────────────────────────────────────────────────────────

  ALICE (Lead / Admin):
  [OK] Configure docker-compose.yml (une fois)
  [OK] Configure prometheus.yml et alert_rules.yml (une fois)
  [OK] Configure Grafana datasource et provisioning (une fois)
  [OK] Crée les comptes Grafana de l'équipe
  [OK] Revue hebdomadaire des métriques
  [OK] Valide et ajuste les seuils d'alertes selon l'évolution du trafic

  BOB (Métriques applicatives):
  [OK] Ajoute les métriques dans le code Flask (metrics.py, middleware.py)
  [OK] Ajoute les counters/gauges dans les routes lors de nouvelles features
  [OK] Consulte Grafana pour diagnostiquer les lenteurs
  [OK] Ajoute des annotations sur les corrections importantes

  CLAIRE (Dashboards et alertes):
  [OK] Crée et maintient les dashboards Grafana
  [OK] Configure les alertes et contact points
  [OK] Réagit aux alertes critiques (on-call)
  [OK] Exporte et versionne les dashboards dans Git

────────────────────────────────────────────────────────────────────────────────
WORKFLOW COMPLET
────────────────────────────────────────────────────────────────────────────────

  Bob écrit code       Bob ajoute métrique         Prometheus      Grafana
  ──────────────       ─────────────────────        ──────────      ───────
  nouvelle route  ->  tasks_created_total.inc()  ->  scrape /metrics  ->  dashboard
  /tasks          ->  update_task_gauges()        ->  stocke valeurs  ->  graphique
                                                 ->  évalue alertes  ->  alerte email

────────────────────────────────────────────────────────────────────────────────
COMMANDES DE RÉFÉRENCE
────────────────────────────────────────────────────────────────────────────────

  DOCKER COMPOSE:
  ───────────────
  docker-compose up -d                   # Lancer tout l'environnement
  docker-compose down                    # Arrêter tout
  docker-compose restart flask-app       # Redémarrer seulement Flask
  docker-compose logs -f flask-app       # Voir les logs Flask en temps réel
  docker-compose logs -f prometheus      # Logs Prometheus
  docker-compose ps                      # État de tous les services

  DÉVELOPPEMENT LOCAL (sans Docker):
  ─────────────────────────────────────
  python run.py                          # Lancer Flask + serveur métriques
  curl http://localhost:8000/metrics     # Voir les métriques brutes
  curl http://localhost:5000/health      # Vérifier la santé de l'API

  PROMETHEUS - URLS:
  ──────────────────
  http://localhost:9090                  # Interface web Prometheus
  http://localhost:9090/targets          # État des cibles scrapées
  http://localhost:9090/alerts           # Alertes actives
  http://localhost:9090/rules            # Règles d'alertes définies
  http://localhost:9090/metrics          # Métriques de Prometheus lui-même

  GRAFANA - URLS:
  ───────────────
  http://localhost:3000                  # Interface web Grafana
  http://localhost:3000/dashboards       # Liste des dashboards
  http://localhost:3000/alerting/list    # Liste des alertes

  TESTS RAPIDES (simuler du trafic pour voir les métriques):
  ──────────────────────────────────────────────────────────

  # Simuler des requêtes GET
  for i in $(seq 1 20); do
    curl -s http://localhost:5000/tasks > /dev/null
    sleep 0.5
  done

  # Simuler des créations de tâches
  for i in $(seq 1 5); do
    curl -s -X POST http://localhost:5000/tasks \
      -H "Content-Type: application/json" \
      -d "{\"title\": \"Tâche de test $i\"}" > /dev/null
  done

  # Simuler des erreurs 404
  for i in $(seq 1 10); do
    curl -s http://localhost:5000/tasks/99999 > /dev/null
  done

  # Puis observer dans Grafana ou Prometheus les nouvelles valeurs!

  ALIAS PRATIQUES (~/.bashrc ou ~/.zshrc):
  ─────────────────────────────────────────
  alias mon-up='docker-compose up -d'
  alias mon-down='docker-compose down'
  alias mon-logs='docker-compose logs -f flask-app'
  alias mon-metrics='curl -s http://localhost:8000/metrics | grep -v "^#"'
  alias mon-grafana='open http://localhost:3000'
  alias mon-prometheus='open http://localhost:9090'

  # Utilisation:
  mon-up          # Démarre l'environnement de monitoring
  mon-metrics     # Voir les métriques sans les commentaires
  mon-grafana     # Ouvrir Grafana dans le navigateur

────────────────────────────────────────────────────────────────────────────────
RÉCAPITULATIF DES PORTS
────────────────────────────────────────────────────────────────────────────────

  Port  │ Service             │ Usage
  ──────┼─────────────────────┼────────────────────────────────────────────
  5000  │ Flask API           │ Requêtes des clients (GET/POST/PUT/DELETE)
  8000  │ Flask /metrics      │ Endpoint scraping Prometheus (ne pas exposer!)
  9090  │ Prometheus UI       │ Interface web + requêtes PromQL
  3000  │ Grafana UI          │ Dashboards et alertes

────────────────────────────────────────────────────────────────────────────────
COMPARAISON SONARQUBE vs GRAFANA/PROMETHEUS
────────────────────────────────────────────────────────────────────────────────

  Aspect           │ SonarQube               │ Grafana + Prometheus
  ─────────────────┼─────────────────────────┼──────────────────────────────
  Rôle             │ Qualité du code         │ Observabilité en production
  Quand            │ Développement / CI      │ Runtime / Production
  Ce qu'il mesure  │ Bugs, coverage, smells  │ Trafic, latence, erreurs
  Données          │ Code statique           │ Métriques dynamiques (temps réel)
  Fréquence        │ A chaque analyse/push   │ Toutes les 15 secondes
  Alertes sur      │ Nouvelle vulnerability  │ Latence > 1s, erreurs > 5%
  Équipe cible     │ Développeurs            │ Dev + Ops + Management
  Complémentaires? │ OUI - les deux se complètent parfaitement:             │
                   │ SonarQube: le code est-il propre AVANT production?    │
                   │ Grafana:   l'app se comporte-t-elle bien EN production?│

================================================================================
FIN DE LA SECTION - GRAFANA & PROMETHEUS EN ÉQUIPE DE 3 DÉVELOPPEURS (PYTHON)
================================================================================