* ...
*
* 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: …"),
* 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 variable name => its alarm */
public static function parse(string $xml): array
{
$out = [];
preg_match_all('#(.*?)#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 $alarms */
public static function build(array $alarms): string
{
$xml = '';
foreach ($alarms as $name => $a) {
$xml .= self::variable($name, $a, '');
}
return $xml . '';
}
/** One , $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 '' . $indent . '' . $indent
. '' . $indent . '' . $indent
. '' . ($name === 'memory_free_kib' ? $indent . '' : '') . $end . '';
}
/** 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;
}
}