Files
xi-saturationreport/saturationreport.api.php
2026-02-10 18:51:31 +00:00

546 lines
22 KiB
PHP

<?php
//
// Storage Saturation Report Component - API Endpoint
//
require_once(dirname(__FILE__) . '/../componenthelper.inc.php');
// Only initialize and route if not being called for data only (from index.php export mode)
if (!defined('SATURATIONREPORT_DATA_ONLY')) {
// Normal API mode - initialize and route
pre_init();
init_session();
grab_request_vars();
check_prereqs();
check_authentication();
route_request();
}
/**
* Check if service has trackvolume custom variable set to 1 or "true"
* Based on customvariabletab component pattern: get_xml_custom_service_variable_status()
*
* @param object $customvars XML object with customvar array (from get_xml_custom_service_variable_status)
* @return bool True if trackvolume is set to 1 or "true"
*/
function has_trackvolume_enabled($customvars)
{
if (!$customvars || !isset($customvars->customvar)) {
return false;
}
// Iterate through custom variables to find trackvolume
foreach ($customvars->customvar as $customvar) {
$name = (string)$customvar->name;
$value = (string)$customvar->value;
if ($name === 'trackvolume' || $name === 'TRACKVOLUME') {
// Check if value is 1, "1", "true", or "TRUE"
if ($value === '1' || $value === 1 || strtolower($value) === 'true') {
return true;
}
}
}
return false;
}
/**
* Parse perfdata to extract disk usage information
* Supports multiple formats:
* 1. check_local_disk format: /=4985MiB;9592;10791;0;11991
* 2. NCPA format: 'used'=0.69GiB;;; 'free'=4.81GiB;;; 'total'=5.82GiB;;;
*
* @param string $perfdata Performance data string from service status
* @param string $check_command Optional check command name to help identify format
* @return array|false Array with 'used', 'total', 'percent', 'available' or false if not found
*/
function parse_disk_perfdata($perfdata, $check_command = '')
{
if (empty($perfdata)) {
return false;
}
$used_value = null;
$used_unit = '';
$total_value = null;
$total_unit = '';
// Pattern to match 'used'=value[unit];;; (with quotes)
if (preg_match("/'used'\s*=\s*([0-9.]+)([KMGT]?(?:IB|B)?);/i", $perfdata, $used_matches)) {
$used_value = floatval($used_matches[1]);
$used_unit = isset($used_matches[2]) ? strtoupper(trim($used_matches[2])) : '';
}
// Pattern to match 'total'=value[unit];;; (with quotes)
if (preg_match("/'total'\s*=\s*([0-9.]+)([KMGT]?(?:IB|B)?);/i", $perfdata, $total_matches)) {
$total_value = floatval($total_matches[1]);
$total_unit = isset($total_matches[2]) ? strtoupper(trim($total_matches[2])) : '';
}
// If we found both used and total, calculate percentage
if ($used_value !== null && $total_value !== null && $total_value > 0) {
// If total has no unit but used has a unit, assume total uses same unit
if (empty($total_unit) && !empty($used_unit) && $total_value > 0) {
$total_unit = $used_unit;
}
// Convert to bytes for calculation
$used_bytes = convert_to_bytes($used_value, $used_unit);
$total_bytes = convert_to_bytes($total_value, $total_unit);
// Calculate percentage
if ($total_bytes > 0 && $used_bytes > 0) {
$percent = ($used_bytes / $total_bytes) * 100;
$available_bytes = $total_bytes - $used_bytes;
return array(
'used' => $used_value,
'total' => $total_value,
'available' => $total_value - $used_value,
'percent' => round($percent, 2),
'used_bytes' => $used_bytes,
'total_bytes' => $total_bytes,
'available_bytes' => $available_bytes,
'used_unit' => $used_unit,
'total_unit' => $total_unit,
'label' => ''
);
}
}
// Try alternative NCPA format without quotes: used=0.69GiB;;; free=4.81GiB;;; total=5.82GiB;;;
if ($used_value === null || $total_value === null) {
$used_value = null;
$used_unit = '';
$total_value = null;
$total_unit = '';
if (preg_match("/\bused\s*=\s*([0-9.]+)([KMGT]?(?:IB|B)?);/i", $perfdata, $used_matches)) {
$used_value = floatval($used_matches[1]);
$used_unit = isset($used_matches[2]) ? strtoupper(trim($used_matches[2])) : '';
}
if (preg_match("/\btotal\s*=\s*([0-9.]+)([KMGT]?(?:IB|B)?);/i", $perfdata, $total_matches)) {
$total_value = floatval($total_matches[1]);
$total_unit = isset($total_matches[2]) ? strtoupper(trim($total_matches[2])) : '';
}
if ($used_value !== null && $total_value !== null && $total_value > 0) {
if (empty($total_unit) && !empty($used_unit) && $total_value > 0) {
$total_unit = $used_unit;
}
$used_bytes = convert_to_bytes($used_value, $used_unit);
$total_bytes = convert_to_bytes($total_value, $total_unit);
if ($total_bytes > 0 && $used_bytes > 0) {
$percent = ($used_bytes / $total_bytes) * 100;
$available_bytes = $total_bytes - $used_bytes;
return array(
'used' => $used_value,
'total' => $total_value,
'available' => $total_value - $used_value,
'percent' => round($percent, 2),
'used_bytes' => $used_bytes,
'total_bytes' => $total_bytes,
'available_bytes' => $available_bytes,
'used_unit' => $used_unit,
'total_unit' => $total_unit,
'label' => ''
);
}
}
}
// Try check_local_disk / single-metric format: 'label'=value[unit];warn;crit;min;max
// Supports quoted labels with special chars (e.g. 'C:\_Label:__Serial_Number_2c3f47f4'=91445.6016MB;111560;132477;0;139450)
// If multiple metrics present, pick the one with highest percent used (most saturated)
$label_pattern = "(?:'([^']*)'|[^=\s]+)";
$value_pattern = "=([0-9.]+)([KMGT]?(?:IB|B)?);[^;]*;[^;]*;[^;]*;([0-9.]+)([KMGT]?(?:IB|B)?)?";
$full_pattern = '/' . $label_pattern . $value_pattern . '/i';
if (preg_match_all($full_pattern, $perfdata, $all_matches, PREG_SET_ORDER)) {
$best = null;
$best_percent = -1.0;
foreach ($all_matches as $m) {
$label = isset($m[1]) ? trim($m[1]) : '';
$used = floatval($m[2]);
$used_unit = isset($m[3]) ? strtoupper(trim($m[3])) : '';
$total = isset($m[4]) ? floatval($m[4]) : 0;
$total_unit = isset($m[5]) ? strtoupper(trim($m[5])) : '';
if (empty($total_unit) && !empty($used_unit) && $total > 0) {
$total_unit = $used_unit;
}
$used_bytes = convert_to_bytes($used, $used_unit);
$total_bytes = $total > 0 ? convert_to_bytes($total, $total_unit) : 0;
if ($total_bytes <= 0 || $used_bytes <= 0) {
continue;
}
$percent = ($used_bytes / $total_bytes) * 100;
if ($percent > $best_percent) {
$best_percent = $percent;
$available_bytes = $total_bytes - $used_bytes;
$best = array(
'used' => $used,
'total' => $total,
'available' => $total - $used,
'percent' => round($percent, 2),
'used_bytes' => $used_bytes,
'total_bytes' => $total_bytes,
'available_bytes' => $available_bytes,
'used_unit' => $used_unit,
'total_unit' => $total_unit,
'label' => $label
);
}
}
if ($best !== null) {
return $best;
}
}
// Fallback: single match without label (original pattern) /=4985MiB;9592;10791;0;11991
// Pattern: Handle binary units (KiB, MiB, GiB, TiB) and decimal units (KB, MB, GB, TB)
// Matches: label=value[unit];warn;crit;min;max[unit]
$pattern = '/[^=]*=([0-9.]+)([KMGT]?(?:IB|B)?);[^;]*;[^;]*;[^;]*;([0-9.]+)([KMGT]?(?:IB|B)?)?/i';
if (preg_match($pattern, $perfdata, $matches)) {
$used = floatval($matches[1]);
$used_unit = isset($matches[2]) ? strtoupper(trim($matches[2])) : '';
$total = isset($matches[3]) ? floatval($matches[3]) : 0;
$total_unit = isset($matches[4]) ? strtoupper(trim($matches[4])) : '';
// If total has no unit but used has a unit, assume total uses same unit
if (empty($total_unit) && !empty($used_unit) && $total > 0) {
$total_unit = $used_unit;
}
// Convert to bytes for calculation if units are present
$used_bytes = convert_to_bytes($used, $used_unit);
$total_bytes = $total > 0 ? convert_to_bytes($total, $total_unit) : 0;
// Calculate percentage if we have both used and total
if ($total_bytes > 0 && $used_bytes > 0) {
$percent = ($used_bytes / $total_bytes) * 100;
$available_bytes = $total_bytes - $used_bytes;
return array(
'used' => $used,
'total' => $total,
'available' => $total - $used,
'percent' => round($percent, 2),
'used_bytes' => $used_bytes,
'total_bytes' => $total_bytes,
'available_bytes' => $available_bytes,
'used_unit' => $used_unit,
'total_unit' => $total_unit,
'label' => ''
);
}
// If we have total but couldn't convert (no units), try direct calculation
if ($total > 0 && $used > 0 && empty($used_unit) && empty($total_unit)) {
$percent = ($used / $total) * 100;
$available = $total - $used;
return array(
'used' => $used,
'total' => $total,
'available' => $available,
'percent' => round($percent, 2),
'used_bytes' => 0,
'total_bytes' => 0,
'available_bytes' => 0,
'used_unit' => '',
'total_unit' => '',
'label' => ''
);
}
}
return false;
}
/**
* Convert value with unit to bytes
* Supports both binary (KiB, MiB, GiB, TiB) and decimal (KB, MB, GB, TB) units
*
* @param float $value Numeric value
* @param string $unit Unit (B, KB, MB, GB, TB, KiB, MiB, GiB, TiB, K, M, G, T, %)
* @return float Value in bytes (or original if percentage or no unit)
*/
function convert_to_bytes($value, $unit)
{
$unit = strtoupper(trim($unit));
// Handle binary units (KiB, MiB, GiB, TiB) - base 1024
if (strpos($unit, 'IB') !== false || strpos($unit, 'I') !== false) {
switch ($unit) {
case 'TIB':
case 'TI':
return $value * 1024 * 1024 * 1024 * 1024;
case 'GIB':
case 'GI':
return $value * 1024 * 1024 * 1024;
case 'MIB':
case 'MI':
return $value * 1024 * 1024;
case 'KIB':
case 'KI':
return $value * 1024;
default:
return $value;
}
}
// Handle decimal units (KB, MB, GB, TB) or single letter (K, M, G, T)
switch ($unit) {
case 'TB':
case 'T':
return $value * 1024 * 1024 * 1024 * 1024;
case 'GB':
case 'G':
return $value * 1024 * 1024 * 1024;
case 'MB':
case 'M':
return $value * 1024 * 1024;
case 'KB':
case 'K':
return $value * 1024;
case 'B':
case '%':
case '':
default:
return $value;
}
}
/**
* Format bytes to human-readable format
*
* @param float $bytes Number of bytes
* @param string $unit Preferred unit (GB, MB, etc.) or empty for auto
* @return array Array with 'value' and 'unit'
*/
function format_bytes($bytes, $unit = '')
{
if ($bytes == 0) {
return array('value' => 0, 'unit' => 'GB');
}
// If unit is specified, use it
if (!empty($unit)) {
$unit = strtoupper($unit);
switch ($unit) {
case 'TB':
return array('value' => round($bytes / (1024 * 1024 * 1024 * 1024), 1), 'unit' => 'TB');
case 'GB':
return array('value' => round($bytes / (1024 * 1024 * 1024), 1), 'unit' => 'GB');
case 'MB':
return array('value' => round($bytes / (1024 * 1024), 1), 'unit' => 'MB');
case 'KB':
return array('value' => round($bytes / 1024, 1), 'unit' => 'KB');
default:
break;
}
}
// Auto-format based on size
if ($bytes >= 1024 * 1024 * 1024 * 1024) {
return array('value' => round($bytes / (1024 * 1024 * 1024 * 1024), 1), 'unit' => 'TB');
} elseif ($bytes >= 1024 * 1024 * 1024) {
return array('value' => round($bytes / (1024 * 1024 * 1024), 1), 'unit' => 'GB');
} elseif ($bytes >= 1024 * 1024) {
return array('value' => round($bytes / (1024 * 1024), 1), 'unit' => 'MB');
} elseif ($bytes >= 1024) {
return array('value' => round($bytes / 1024, 1), 'unit' => 'KB');
} else {
return array('value' => round($bytes, 1), 'unit' => 'B');
}
}
/**
* Route the request call from script main.js
*/
function route_request()
{
// Set JSON header early, before any output
if (!headers_sent()) {
header('Content-Type: application/json');
}
// grab request values
$mode = grab_request_var('mode');
switch ( $mode ) {
case 'services':
generate_saturationreport_services();
break;
default:
echo json_encode(array('error' => 'Invalid mode'));
break;
}
}
/**
* Get storage saturation report service data
* Filters services with trackvolume custom variable = 1 or "true"
* Returns array of service data
*/
function get_saturationreport_services_data()
{
$services = array();
// Check if required functions are available
if (!function_exists('get_data_service_status')) {
return array('error' => 'get_data_service_status() function not available');
}
if (!function_exists('get_xml_custom_service_variable_status')) {
return array('error' => 'get_xml_custom_service_variable_status() function not available');
}
try {
// Get all services
$request_args = array();
$service_objects = get_data_service_status($request_args);
if (!is_array($service_objects)) {
return array('error' => 'Service objects is not an array');
}
// Filter services by trackvolume custom variable
foreach ($service_objects as $obj) {
if (!is_array($obj)) {
continue;
}
$host_name = $obj['host_name'];
$service_description = $obj['service_description'];
// Get custom variables
$args = array(
"host_name" => $host_name,
"service_description" => $service_description
);
$xml_result = get_xml_custom_service_variable_status($args);
// Check if trackvolume is enabled
$has_trackvolume = false;
if ($xml_result && isset($xml_result->customservicevarstatus->customvars)) {
$has_trackvolume = has_trackvolume_enabled($xml_result->customservicevarstatus->customvars);
}
if (!$has_trackvolume) {
continue;
}
// Get service ID and name
$id = intval($obj['service_object_id']);
$name = isset($obj['display_name']) && !empty($obj['display_name'])
? $obj['display_name']
: $service_description;
// Get perfdata - try direct from get_data_service_status() first (like diskpressure)
$perfdata = isset($obj['perfdata']) ? $obj['perfdata'] : '';
// If not available, get it separately
if (empty($perfdata) && function_exists('get_xml_service_status')) {
$backendargs = array(
"cmd" => "getservicestatus",
"host_name" => "=" . $host_name,
"service_description" => "=" . $service_description
);
$xml_status = get_xml_service_status($backendargs);
if ($xml_status && isset($xml_status->servicestatus)) {
$perfdata = strval($xml_status->servicestatus->performance_data);
}
}
// Parse disk usage from perfdata
$disk_info = parse_disk_perfdata($perfdata);
// Build service data
$service_data = array(
'service_id' => $id,
'name' => $name,
'host_name' => $host_name,
'service_description' => $service_description,
'perfdata' => $perfdata
);
// Add disk usage information if available
if ($disk_info !== false) {
$service_data['disk_usage_percent'] = $disk_info['percent'];
$service_data['disk_used'] = $disk_info['used'];
$service_data['disk_total'] = $disk_info['total'];
$service_data['disk_available'] = $disk_info['available'];
$service_data['disk_used_bytes'] = $disk_info['used_bytes'];
$service_data['disk_total_bytes'] = $disk_info['total_bytes'];
$service_data['disk_available_bytes'] = $disk_info['available_bytes'];
$service_data['disk_used_unit'] = $disk_info['used_unit'];
$service_data['disk_total_unit'] = $disk_info['total_unit'];
$service_data['disk_label'] = isset($disk_info['label']) ? $disk_info['label'] : '';
// Caption: use perfdata volume label when present (e.g. C:\_Label:__Serial_Number_...), else service description
if (!empty($service_data['disk_label'])) {
$service_data['caption_display'] = $service_data['disk_label'];
} else {
$service_data['caption_display'] = $service_data['name'];
}
// Format values for display
$used_formatted = format_bytes($service_data['disk_used_bytes'], $service_data['disk_used_unit']);
$available_formatted = format_bytes($service_data['disk_available_bytes'], $service_data['disk_total_unit']);
$service_data['disk_used_display'] = $used_formatted['value'] . ' ' . $used_formatted['unit'];
$service_data['disk_available_display'] = $available_formatted['value'] . ' ' . $available_formatted['unit'];
$service_data['disk_percent_display'] = number_format($disk_info['percent'], 0) . '%';
} else {
// Set defaults if perfdata can't be parsed
$service_data['disk_usage_percent'] = 0;
$service_data['disk_used'] = 0;
$service_data['disk_total'] = 0;
$service_data['disk_available'] = 0;
$service_data['disk_used_bytes'] = 0;
$service_data['disk_total_bytes'] = 0;
$service_data['disk_available_bytes'] = 0;
$service_data['disk_label'] = '';
$service_data['caption_display'] = $service_data['name'];
$service_data['disk_used_display'] = 'N/A';
$service_data['disk_available_display'] = 'N/A';
$service_data['disk_percent_display'] = 'N/A';
}
$services[$id] = $service_data;
}
// Sort by available space (ascending - most critical first)
if (count($services) > 0) {
usort($services, function($a, $b) {
$available_a = isset($a['disk_available_bytes']) ? $a['disk_available_bytes'] : PHP_INT_MAX;
$available_b = isset($b['disk_available_bytes']) ? $b['disk_available_bytes'] : PHP_INT_MAX;
return $available_a <=> $available_b;
});
// Re-index array
$services = array_values($services);
}
} catch (Exception $e) {
return array('error' => 'Exception: ' . $e->getMessage());
} catch (Error $e) {
return array('error' => 'Fatal error: ' . $e->getMessage());
}
// Return empty array if no services found (not an error)
return $services;
}
/**
* Generate JSON output for services
*/
function generate_saturationreport_services()
{
$services = get_saturationreport_services_data();
echo json_encode($services);
}