perf: optimize statistics exports - cache task history and slim down Livewire payload

- Cache getTaskHistoryProperty() with 30s TTL to avoid Proxmox API calls on every render
- Move networkAnalysis heavy arrays (connections, topConnections, topPorts) out of Livewire public property into server-side cache only
- Store only counts and metadata in the Livewire payload instead of thousands of connection objects
- Pagination and export methods now read from cache instead of public property
- Reduce wire:poll intervals from 10s to 30s on task history and lifecycle events cards
- Dramatically reduces JSON payload size on every Livewire request
This commit is contained in:
2026-03-01 05:38:16 -05:00
parent 20f8a71f75
commit 3fa4c9d65e
2 changed files with 83 additions and 47 deletions

View File

@@ -71,9 +71,9 @@ class Statistics extends Component
public int $lifecycleEventsCurrentPage = 1;
public array $networkAnalysis = [
'connections' => [],
'topConnections' => [],
'topPorts' => [],
'connectionCount' => 0,
'topConnectionCount' => 0,
'topPortCount' => 0,
'security' => [],
'lastUpdated' => null,
'error' => null,
@@ -157,9 +157,9 @@ class Statistics extends Component
// Initialize error state if not configured to prevent infinite loading
if (!$this->coreRouterEnabled()) {
$this->networkAnalysis['error'] = 'Core Router API is not configured.';
$this->networkAnalysis['connections'] = [];
$this->networkAnalysis['topConnections'] = [];
$this->networkAnalysis['topPorts'] = [];
$this->networkAnalysis['connectionCount'] = 0;
$this->networkAnalysis['topConnectionCount'] = 0;
$this->networkAnalysis['topPortCount'] = 0;
$this->networkAnalysis['security'] = [];
}
@@ -244,7 +244,15 @@ class Statistics extends Component
'last_updated' => $cachedAnalysis['lastUpdated'] ?? null,
]);
$this->networkAnalysis = $cachedAnalysis;
// Only store lightweight metadata in the public property (not the heavy arrays)
$this->networkAnalysis = [
'connectionCount' => count($cachedAnalysis['connections'] ?? []),
'topConnectionCount' => count($cachedAnalysis['topConnections'] ?? []),
'topPortCount' => count($cachedAnalysis['topPorts'] ?? []),
'security' => $cachedAnalysis['security'] ?? [],
'lastUpdated' => $cachedAnalysis['lastUpdated'],
'error' => $cachedAnalysis['error'] ?? null,
];
$this->networkAnalysisLoading = false;
// Reset retry count when we successfully hydrate from cache
@@ -262,9 +270,9 @@ class Statistics extends Component
if (! $this->coreRouterEnabled()) {
$this->networkAnalysis['error'] = 'Core Router API is not configured.';
$this->networkAnalysis['connections'] = [];
$this->networkAnalysis['topConnections'] = [];
$this->networkAnalysis['topPorts'] = [];
$this->networkAnalysis['connectionCount'] = 0;
$this->networkAnalysis['topConnectionCount'] = 0;
$this->networkAnalysis['topPortCount'] = 0;
$this->networkAnalysis['security'] = [];
$this->networkAnalysisLoading = false;
@@ -274,9 +282,9 @@ class Statistics extends Component
$serverIps = $this->getServerIps();
if (empty($serverIps)) {
$this->networkAnalysis['error'] = 'No IP addresses are associated with this server.';
$this->networkAnalysis['connections'] = [];
$this->networkAnalysis['topConnections'] = [];
$this->networkAnalysis['topPorts'] = [];
$this->networkAnalysis['connectionCount'] = 0;
$this->networkAnalysis['topConnectionCount'] = 0;
$this->networkAnalysis['topPortCount'] = 0;
$this->networkAnalysis['security'] = [];
$this->networkAnalysisLoading = false;
@@ -400,7 +408,8 @@ class Statistics extends Component
'sample_connections' => array_slice($connections, 0, 3),
]);
// Store all connections without limit - pagination handles display
// Store all connections in cache only — NOT in the public property
// This prevents thousands of connections from being serialized in every Livewire request
$analysis = [
'connections' => $connections,
'topConnections' => $topConnections,
@@ -410,16 +419,24 @@ class Statistics extends Component
'error' => null,
];
$this->networkAnalysis = $analysis;
// Persist the full analysis in cache for pagination and export
Cache::put($cacheKey, $analysis, now()->addSeconds(30));
// Only store lightweight metadata in the Livewire public property
$this->networkAnalysis = [
'connectionCount' => count($connections),
'topConnectionCount' => count($topConnections),
'topPortCount' => count($topPorts),
'security' => $securityWarnings,
'lastUpdated' => now()->toDateTimeString(),
'error' => null,
];
// Reset pagination to page 1 when data is refreshed
$this->connectionsCurrentPage = 1;
$this->topConnectionsCurrentPage = 1;
$this->topPortsCurrentPage = 1;
// Persist the computed analysis for a short period to speed up subsequent loads
Cache::put($cacheKey, $analysis, now()->addSeconds(30));
// Reset retry count on successful load
$this->networkAnalysisRetryCount = 0;
} catch (\Throwable $e) {
@@ -428,10 +445,10 @@ class Statistics extends Component
$errorMessage = $e->getMessage();
$is502Error = str_contains($errorMessage, 'HTTP 502') || str_contains($errorMessage, '(HTTP 502)');
// Always initialize error arrays first
$this->networkAnalysis['connections'] = [];
$this->networkAnalysis['topConnections'] = [];
$this->networkAnalysis['topPorts'] = [];
// Reset counts and security on error
$this->networkAnalysis['connectionCount'] = 0;
$this->networkAnalysis['topConnectionCount'] = 0;
$this->networkAnalysis['topPortCount'] = 0;
$this->networkAnalysis['security'] = [];
// On failure, do not leave any stale cache behind for this server
@@ -485,8 +502,9 @@ class Statistics extends Component
*/
public function getPaginatedConnectionsProperty(): array
{
$connections = $this->networkAnalysis['connections'] ?? [];
$total = count($connections);
$cacheKey = 'proxmox_network_analysis_' . $this->server->id;
$cachedAnalysis = Cache::get($cacheKey, []);
$connections = $cachedAnalysis['connections'] ?? [];
$offset = ($this->connectionsCurrentPage - 1) * $this->connectionsPerPage;
return array_slice($connections, $offset, $this->connectionsPerPage);
@@ -497,7 +515,7 @@ class Statistics extends Component
*/
public function getConnectionsTotalPagesProperty(): int
{
$total = count($this->networkAnalysis['connections'] ?? []);
$total = $this->networkAnalysis['connectionCount'] ?? 0;
return max(1, (int) ceil($total / $this->connectionsPerPage));
}
@@ -506,7 +524,9 @@ class Statistics extends Component
*/
public function getPaginatedTopConnectionsProperty(): array
{
$connections = $this->networkAnalysis['topConnections'] ?? [];
$cacheKey = 'proxmox_network_analysis_' . $this->server->id;
$cachedAnalysis = Cache::get($cacheKey, []);
$connections = $cachedAnalysis['topConnections'] ?? [];
$offset = ($this->topConnectionsCurrentPage - 1) * $this->topConnectionsPerPage;
return array_slice($connections, $offset, $this->topConnectionsPerPage);
@@ -517,7 +537,7 @@ class Statistics extends Component
*/
public function getTopConnectionsTotalPagesProperty(): int
{
$total = count($this->networkAnalysis['topConnections'] ?? []);
$total = $this->networkAnalysis['topConnectionCount'] ?? 0;
return max(1, (int) ceil($total / $this->topConnectionsPerPage));
}
@@ -526,7 +546,9 @@ class Statistics extends Component
*/
public function getPaginatedTopPortsProperty(): array
{
$ports = $this->networkAnalysis['topPorts'] ?? [];
$cacheKey = 'proxmox_network_analysis_' . $this->server->id;
$cachedAnalysis = Cache::get($cacheKey, []);
$ports = $cachedAnalysis['topPorts'] ?? [];
$offset = ($this->topPortsCurrentPage - 1) * $this->topPortsPerPage;
return array_slice($ports, $offset, $this->topPortsPerPage);
@@ -537,7 +559,7 @@ class Statistics extends Component
*/
public function getTopPortsTotalPagesProperty(): int
{
$total = count($this->networkAnalysis['topPorts'] ?? []);
$total = $this->networkAnalysis['topPortCount'] ?? 0;
return max(1, (int) ceil($total / $this->topPortsPerPage));
}
@@ -1363,8 +1385,25 @@ class Statistics extends Component
public function getTaskHistoryProperty()
{
$cacheKey = 'proxmox_task_history_' . $this->server->id;
try {
$proxmox = new Proxmox;
return Cache::remember($cacheKey, 30, function () {
return $this->fetchTaskHistoryFromApi();
});
} catch (\Throwable $e) {
\Log::error('Error fetching task history for server ' . $this->server->id . ': ' . $e->getMessage());
return [];
}
}
/**
* Fetch task history directly from the Proxmox API.
* Separated from the computed property so Cache::remember can wrap it.
*/
protected function fetchTaskHistoryFromApi(): array
{
$proxmox = new Proxmox;
$vmId = (string)$this->server->vm_id;
$allTasks = [];
@@ -1528,11 +1567,6 @@ class Statistics extends Component
// Limit to last 100 tasks
return array_slice($tasks, 0, 100);
} catch (\Throwable $e) {
// Log error for debugging but return empty array
\Log::error('Error fetching task history for server ' . $this->server->id . ': ' . $e->getMessage());
return [];
}
}
public function getTaskDetails($taskId)
@@ -2071,7 +2105,9 @@ class Statistics extends Component
public function exportNetworkAnalysis()
{
$connections = $this->networkAnalysis['connections'] ?? [];
$cacheKey = 'proxmox_network_analysis_' . $this->server->id;
$cachedAnalysis = Cache::get($cacheKey, []);
$connections = $cachedAnalysis['connections'] ?? [];
if (empty($connections)) {
$this->showExportInfo('No network analysis data available to export. Please refresh the network analysis first.');