546 lines
22 KiB
PHP
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);
|
|
}
|
|
|