<?php
/**
 * Demo Oracle — indicateurs de performance (section « config/indicators »).
 *
 * Portage de modules/oracle/_report_indicators.php, sans retrait : ratios
 * V$SYSSTAT et advisors memoire, tous en lecture. Le module complet ne propose
 * aucune action sur cette page ; la demonstration la sert a l'identique.
 *
 * Le referentiel d'aide (definition, cible, leviers de chaque indicateur) est
 * conserve tel quel, en francais et hors i18n comme dans le module complet :
 * c'est de la documentation Oracle, pas de l'interface.
 *
 * Inclus par report.php. Variables : $oraDb, $db, $currentUser, $settings.
 */

require_once __DIR__ . '/lib/DemoOracleOciConnect.php';
require_once __DIR__ . '/lib/DemoOracleIndicatorsRepository.php';

/* ── Connexion ─────────────────────────────────────────────────────────── */
$ociError    = null;
$fetchError  = null;
$conn        = demo_oracle_oci_open($oraDb, $db, $ociError, '', 'config/indicators');
$statsKv     = [];     // V$SYSSTAT + agrégats — flat key/value
$advisors    = [];     // ['name' => [{sz_mb,...}, ...]]

if (!$conn) {
    $fetchError = $ociError
        ? ('ORA-' . $ociError['code'] . ': ' . $ociError['message'])
        : t('demo_oracle.connect_error');
} else {
    $statsKv = DemoOracleIndicatorsRepository::fetchSysStats($conn);
    if (empty($statsKv)) {
        $fetchError = t('demo_oracle.indicators_fetch_error') ?? 'sysstat fetch failed';
    }

    $advisors = [
        'db_cache'    => DemoOracleIndicatorsRepository::fetchDbCacheAdvice($conn),
        'shared_pool' => DemoOracleIndicatorsRepository::fetchSharedPoolAdvice($conn),
        'pga'         => DemoOracleIndicatorsRepository::fetchPgaAdvice($conn),
        'sga'         => DemoOracleIndicatorsRepository::fetchSgaAdvice($conn),
        'memory'      => DemoOracleIndicatorsRepository::fetchMemoryAdvice($conn),
    ];

    oci_close($conn);
}

/* ── Calcul des ratios depuis $statsKv ──────────────────────────────────
 *   Helper : division sécurisée (retourne null si dénominateur = 0).
 *   Chaque ratio est exprimé en pourcentage (0..100).                  */
$div = function (float $num, float $den): ?float {
    return $den > 0 ? ($num / $den) : null;
};
$pct = function (?float $v, bool $invert = false): ?float {
    if ($v === null) return null;
    return $invert ? 100.0 * (1.0 - $v) : 100.0 * $v;
};
$g = function (string $k) use ($statsKv): float {
    return (float)($statsKv[$k] ?? 0);
};

$gets        = $g('consistent gets') + $g('db block gets');
$ratios = [
    'buffer_nowait' => $pct($div($g('event.buffer_busy_waits'), $gets), true),
    'buffer_hit'    => $pct($div($g('physical reads'),          $gets), true),
    'lib_hit'       => $pct($div($g('librarycache.pinhits'),    $g('librarycache.pins'))),
    'exec_to_parse' => $pct($div($g('parse count (total)'),     $g('execute count')), true),
    'parse_cpu_pct' => $pct($div($g('parse time cpu'),          $g('parse time elapsed'))),
    'redo_nowait'   => $pct($div($g('redo log space requests'), $g('redo entries')), true),
    'sort_memory'   => $pct($div($g('sorts (memory)'),          $g('sorts (memory)') + $g('sorts (disk)'))),
    'soft_parse'    => $pct($div($g('parse count (hard)'),      $g('parse count (total)')), true),
    'latch_hit'     => $pct($div($g('latch.misses'),            $g('latch.gets')), true),
    'non_parse_cpu' => $pct($div($g('parse time cpu'),          $g('CPU used by this session')), true),
];

/* ── Catalogue des indicateurs (label + sens + seuils) ──────────────────
 *   thresholds : [seuil_orange, seuil_vert]. Pour 'maximiser' :
 *     valeur >= vert    → green
 *     valeur >= orange  → orange
 *     sinon              → red
 *   Pour 'minimiser' : seuils interprétés en sens inverse.            */
$indicators = [
    'buffer_nowait' => ['label' => 'Buffer NoWait %',           'goal' => 'max', 'thr' => [95, 99]],
    'buffer_hit'    => ['label' => 'Buffer Hit %',              'goal' => 'max', 'thr' => [90, 95]],
    'lib_hit'       => ['label' => 'Library Hit %',             'goal' => 'max', 'thr' => [95, 99]],
    'exec_to_parse' => ['label' => 'Execute to Parse %',        'goal' => 'max', 'thr' => [50, 90]],
    'parse_cpu_pct' => ['label' => 'Parse CPU to Parse Elapsd %','goal' => 'max', 'thr' => [80, 95]],
    'redo_nowait'   => ['label' => 'Redo NoWait %',             'goal' => 'max', 'thr' => [99, 99.9]],
    'sort_memory'   => ['label' => 'In-Memory Sort %',          'goal' => 'max', 'thr' => [90, 99]],
    'soft_parse'    => ['label' => 'Soft Parse %',              'goal' => 'max', 'thr' => [80, 95]],
    'latch_hit'     => ['label' => 'Latch Hit %',               'goal' => 'max', 'thr' => [99, 99.5]],
    'non_parse_cpu' => ['label' => '% Non-Parse CPU',           'goal' => 'max', 'thr' => [70, 90]],
];

/* ── Aide par indicateur (définition, sens, leviers) — non-i18n volontaire,
 *   c'est un référentiel technique Oracle. La fenêtre modale affiche ça. */
$help = [
    'buffer_nowait' => [
        'def' => 'Pourcentage d\'accès au buffer cache obtenus sans aucune attente. Une attente "buffer busy" indique que deux sessions veulent le même bloc en même temps.',
        'goal'=> '≥ 99 % visé. Doit être proche de 100 %.',
        'levers' => [
            'Identifier les hot blocks via DBA_HIST_ACTIVE_SESS_HISTORY (event = "buffer busy waits").',
            'PCTFREE plus élevé sur les tables hot pour éclater l\'INSERT (contention HW).',
            'Partitionner les segments concernés.',
            'Cache plus large : SGA / DB_CACHE_SIZE.',
        ],
    ],
    'buffer_hit' => [
        'def' => 'Pourcentage des lectures logiques satisfaites par le buffer cache (sans I/O physique). Métrique cumulée depuis le startup.',
        'goal'=> '≥ 95 % sur OLTP. Sur DSS/DWH la valeur peut être beaucoup plus basse sans être pathologique.',
        'levers' => [
            'Augmenter DB_CACHE_SIZE (cf. V$DB_CACHE_ADVICE plus bas).',
            'Identifier les Top SQL en physical reads (V$SQL ORDER BY disk_reads).',
            'Index manquants → full scans coûteux.',
            'PARALLEL_DEGREE_LIMIT sur les DSS pour ne pas saturer le cache.',
        ],
    ],
    'lib_hit' => [
        'def' => 'Pourcentage de "pins" sur la library cache servis sans recompiler/reparser. Cible la réutilisation du shared pool.',
        'goal'=> '≥ 99 % en OLTP. < 95 % = problème de soft parse / shared pool sous-dimensionné.',
        'levers' => [
            'Activer le bind variable (CURSOR_SHARING=FORCE en dernier recours).',
            'Augmenter SHARED_POOL_SIZE.',
            'Réduire les invalidations (DDL en heure de charge).',
            'DBMS_SHARED_POOL.KEEP sur les packages critiques.',
        ],
    ],
    'exec_to_parse' => [
        'def' => 'Pourcentage d\'exécutions qui n\'ont PAS provoqué un nouveau parse. 100 % = aucun curseur n\'est reparser, juste exécuté.',
        'goal'=> '≥ 90 % visé. Très faible si les applis ne réutilisent pas leurs curseurs (parse à chaque exécution).',
        'levers' => [
            'Session cursor caching : SESSION_CACHED_CURSORS (≥ 50).',
            'Côté appli : préparer une fois, exécuter N fois (PreparedStatement réutilisé).',
            'Bind variables au lieu de littéraux.',
        ],
    ],
    'parse_cpu_pct' => [
        'def' => 'Part du temps de parse qui est du CPU (le reste = attentes sur des ressources). Met en évidence la contention shared pool / library cache.',
        'goal'=> '≥ 95 %. Sous 80 %, les parses passent leur temps à attendre (library cache mutex/latch).',
        'levers' => [
            'Réduire le hard parse (cf. Soft Parse %).',
            'Augmenter shared pool si shrink/grow fréquents.',
            'Identifier "library cache: mutex X" dans V$SESSION_WAIT.',
        ],
    ],
    'redo_nowait' => [
        'def' => 'Pourcentage d\'écritures redo qui n\'ont pas attendu un nouveau log file. Mesure la santé du sous-système redo.',
        'goal'=> '≥ 99 % visé. Une chute = redo log files trop petits ou ARCH/LGWR à la traîne.',
        'levers' => [
            'Augmenter la taille des redo log files (ALTER DATABASE ADD LOGFILE).',
            'Plus de groupes redo (3 minimum, 5+ en gros débit).',
            'I/O performant sur les redo (SSD / multiplexing limité).',
            'Vérifier les "log file sync" en parallèle.',
        ],
    ],
    'sort_memory' => [
        'def' => 'Pourcentage de tris effectués entièrement en mémoire (sans débordement TEMP).',
        'goal'=> '≥ 99 %. Sous 95 %, les tris débordent sur disque (TEMP I/O).',
        'levers' => [
            'PGA_AGGREGATE_TARGET plus élevé (cf. V$PGA_TARGET_ADVICE).',
            'Optimiser les requêtes : ORDER BY / GROUP BY mal indexés.',
            'WORK_AREA_SIZE_POLICY = AUTO (cas standard).',
        ],
    ],
    'soft_parse' => [
        'def' => 'Pourcentage de parses qui réutilisent un plan déjà en library cache (soft parse). 100 - SoftParse% = hard parse %.',
        'goal'=> '≥ 95 %. Un hard parse coûte cher (compilation, latch shared pool).',
        'levers' => [
            'Bind variables côté appli.',
            'CURSOR_SHARING=FORCE (palliatif).',
            'Shared pool dimensionné.',
            'Éviter DDL pendant la charge (invalide les curseurs).',
        ],
    ],
    'latch_hit' => [
        'def' => 'Pourcentage de prises de latch obtenues sans miss (sans spin/sleep). Mesure la contention interne Oracle.',
        'goal'=> '≥ 99 %. Sous 99 %, identifier le type de latch coupable.',
        'levers' => [
            'V$LATCH ORDER BY misses : isoler le latch chaud.',
            '"cache buffers chains" → hot blocks (cf. Buffer NoWait).',
            '"library cache" / "shared pool" → cf. Soft Parse.',
            '"row cache objects" → DDL trop fréquent.',
        ],
    ],
    'non_parse_cpu' => [
        'def' => 'Part du CPU instance qui n\'est PAS du parse. 100 - NonParseCPU% = % du CPU consommé à parser.',
        'goal'=> '≥ 90 %. Trop de CPU en parse = symptôme de hard parsing ou de curseurs non réutilisés.',
        'levers' => [
            'Cf. leviers Soft Parse % et Execute to Parse %.',
            'Bind variables, session cursor caching, shared pool.',
        ],
    ],
];

/* ── Aide pour les advisors ─────────────────────────────────────────── */
$advisorHelp = [
    'db_cache' => [
        'title' => 'V$DB_CACHE_ADVICE',
        'def'   => 'Simule l\'effet d\'une variation de DB_CACHE_SIZE sur le volume de lectures physiques. Le point factor=1.0 est la taille actuelle.',
        'goal'  => 'Augmenter le cache tant que la courbe descend significativement ; à plat → augmenter ne sert plus à rien.',
        'levers'=> [
            'ALTER SYSTEM SET DB_CACHE_SIZE = …',
            'Sous AMM/ASMM, Oracle ajuste tout seul — l\'advice reste informatif.',
        ],
    ],
    'shared_pool' => [
        'title' => 'V$SHARED_POOL_ADVICE',
        'def'   => 'Simule le temps cumulé de chargement library cache pour différentes tailles de shared pool. Plus c\'est bas, mieux c\'est.',
        'goal'  => 'Atteindre le plateau à gauche (taille minimale qui maintient la library cache stable).',
        'levers'=> [
            'ALTER SYSTEM SET SHARED_POOL_SIZE = …',
            'Compléter par des bind variables (réduit la pression library cache).',
        ],
    ],
    'pga' => [
        'title' => 'V$PGA_TARGET_ADVICE',
        'def'   => 'Taux de cache hit PGA estimé pour différentes valeurs de PGA_AGGREGATE_TARGET. Cible : 100 % cache hit, 0 over-alloc.',
        'goal'  => 'Maximiser le cache hit (tris/hash en mémoire).',
        'levers'=> [
            'ALTER SYSTEM SET PGA_AGGREGATE_TARGET = …',
            'Optimisations SQL : éviter les énormes ORDER BY/HASH JOIN.',
        ],
    ],
    'sga' => [
        'title' => 'V$SGA_TARGET_ADVICE',
        'def'   => 'Estimation du temps DB pour différentes valeurs de SGA_TARGET (mode ASMM). Visible si memory_target n\'est PAS positionné.',
        'goal'  => 'Minimiser estd_db_time — atteindre le minimum local.',
        'levers'=> [
            'ALTER SYSTEM SET SGA_TARGET = …',
            'Réviser les ratios buffer/shared pool si trop de fragmentation.',
        ],
    ],
    'memory' => [
        'title' => 'V$MEMORY_TARGET_ADVICE',
        'def'   => 'Estimation du temps DB pour différentes valeurs de MEMORY_TARGET (mode AMM). Visible si memory_target est positionné.',
        'goal'  => 'Minimiser estd_db_time.',
        'levers'=> [
            'ALTER SYSTEM SET MEMORY_TARGET = …',
            'AMM gère le partage SGA/PGA tout seul mais avec moins de finesse qu\'ASMM.',
        ],
    ],
];

/* ── Statut couleur d'une valeur selon les seuils ─────────────────────── */
if (!function_exists('demo_indicator_status')) {
    function demo_indicator_status(?float $v, array $thr, string $goal): string {
        if ($v === null) return 'unknown';
        [$orange, $green] = $thr;
        if ($goal === 'max') {
            return $v >= $green ? 'good' : ($v >= $orange ? 'warn' : 'bad');
        }
        return $v <= $green ? 'good' : ($v <= $orange ? 'warn' : 'bad');
    }
}

/* ── View ──────────────────────────────────────────────────────────────── */
View::display(__DIR__ . '/views/report/indicators.php', [
    'fetchError'  => $fetchError,
    'ratios'      => $ratios,
    'indicators'  => $indicators,
    'advisors'    => $advisors,
    'help'        => $help,
    'advisorHelp' => $advisorHelp,
    'settings'    => $settings,
]);
