<?phpnamespace App\Xapi;/** * The performance alarms XAPI's perfmon daemon watches: the XML of the key "perfmon" of a VM's, host's or SR's other_config, as * XenCenter writes it (VM.add_to_other_config $vm 'perfmon' $xml): * * <config><variable><name value="cpu_usage"/><alarm_trigger_level value="0.9"/><alarm_trigger_period value="60"/> * <alarm_auto_inhibit_period value="3600"/></variable>...</config> * * Levels: a ratio 0..1 for cpu_usage and physical_utilisation, bytes per second for network_usage / disk_usage, KiB for * memory_free_kib (an alarm when it goes BELOW: alarm_trigger_sense low), MiB per second for sr_io_throughput_total_per_host. * When the value stays over the level for the trigger period, perfmon writes a message ALARM (body "value: …\nconfig: <variable>…"), * then keeps quiet for the inhibit period. */final class Perfmon{ /** what XenCenter offers for each kind of object */ public const VARIABLES = [ 'vm' => ['cpu_usage', 'network_usage', 'disk_usage'], 'host' => ['cpu_usage', 'memory_free_kib', 'network_usage'], 'sr' => ['physical_utilisation', 'sr_io_throughput_total_per_host'], ]; /** @return array<string, array{level: float, period: int, inhibit: int}> variable name => its alarm */ public static function parse(string $xml): array { $out = []; preg_match_all('#<variable>(.*?)</variable>#s', $xml, $variables); foreach ($variables[1] as $variable) { $value = fn (string $tag): ?string => preg_match('#<' . $tag . '\s+value="([^"]*)"#', $variable, $m) ? html_entity_decode($m[1], ENT_XML1) : null; $name = $value('name'); $level = $value('alarm_trigger_level'); if ($name === null || $name === '' || $level === null) { continue; } $out[$name] = ['level' => (float)$level, 'period' => (int)($value('alarm_trigger_period') ?? 60), 'inhibit' => (int)($value('alarm_auto_inhibit_period') ?? 3600)]; } return $out; } /** @param array<string, array{level: float, period: int, inhibit: int}> $alarms */ public static function build(array $alarms): string { $xml = '<config>'; foreach ($alarms as $name => $a) { $xml .= self::variable($name, $a, ''); } return $xml . '</config>'; } /** One <variable>, $indent between the lines (perfmon's message bodies are indented with tabs, one tag per line). @param array{level: float, period: int, inhibit: int} $a */ public static function variable(string $name, array $a, string $indent = "\n\t"): string { $end = $indent === '' ? '' : "\n"; return '<variable>' . $indent . '<name value="' . htmlspecialchars($name, ENT_XML1) . '"/>' . $indent . '<alarm_trigger_level value="' . self::number($a['level']) . '"/>' . $indent . '<alarm_trigger_period value="' . $a['period'] . '"/>' . $indent . '<alarm_auto_inhibit_period value="' . $a['inhibit'] . '"/>' . ($name === 'memory_free_kib' ? $indent . '<alarm_trigger_sense value="low"/>' : '') . $end . '</variable>'; } /** The body of the ALARM message perfmon writes. @param array{level: float, period: int, inhibit: int} $a */ public static function alarmBody(string $name, float $value, array $a): string { return 'value: ' . sprintf('%.6F', $value) . "\nconfig:\n" . self::variable($name, $a) . "\n"; } /** Below the level is the alarm for free memory, above it for the rest. */ public static function fires(string $name, float $value, float $level): bool { return $name === 'memory_free_kib' ? $value < $level : $value > $level; } private static function number(float $x): string { $s = rtrim(rtrim(sprintf('%.6F', $x), '0'), '.'); return $s === '' || $s === '-0' ? '0' : $s; }}