Files
portal-api/app/Services/VmMetricsService.php
baodev0andClaude Sonnet 5.5 6315a392c0 feat(service): custom product, lịch sử giám sát VM và mã hoá response theo phiên
- Dịch vụ custom: bảng custom_products + services.custom_product_id; createService nhận
  custom{} thay product_id, giá = đơn giá tháng x số tháng chu kỳ (1/3/6/12/24/36),
  gia hạn tính theo cùng đơn giá, ServiceClientDetailResource trả custom_product và specs.
- Giám sát VM: GET v1/system/vm-metrics/history gom lịch sử về tối đa 240 điểm;
  lỗi lấy dung lượng đĩa không còn làm hỏng cả thẻ metrics.
- SealResponse: AES-256-GCM theo khoá phiên do proxy Next cấp qua header x-seal-key
  (HMAC của session id). Client opt-in bằng x-seal; chi tiết dịch vụ, mật khẩu VM và
  console ticket luôn được mã hoá. Body gửi lên dạng {enc} được giải mã trước validate;
  khoá lệch trả 428 để client lấy khoá mới. Đây là che mắt DevTools, không phải bảo mật
  chống chính chủ tài khoản.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 13:58:50 +07:00

78 lines
3.2 KiB
PHP

<?php
namespace App\Services;
class VmMetricsService
{
public function __construct(private readonly PortalMonitorClient $portalMonitor)
{
}
public function get(string $vmwareId, ?int $memoryTotalMb = null, ?int $cpuCores = null): array
{
$stats = $this->portalMonitor->vmMetrics($vmwareId);
try {
$storage = $this->portalMonitor->vmStorage($vmwareId);
} catch (\RuntimeException) {
$storage = [];
}
$provisionedDiskBytes = collect($storage['disks'] ?? [])->sum(
fn ($disk) => is_array($disk) ? (int) ($disk['capacity_bytes'] ?? 0) : 0
);
$diskSummary = is_array($storage['summary'] ?? null) ? $storage['summary'] : [];
return [
'cpu_usage_percent' => $stats['cpu_usage_percent'] ?? null,
'cpu_cores' => $cpuCores,
'cpu_mhz' => $stats['cpu_mhz'] ?? null,
'cpu_ready_ms' => $stats['cpu_ready_ms'] ?? null,
'mem_usage_percent' => $stats['guest_memory_usage_percent'] ?? null,
'mem_usage_mb' => $stats['memory_active_mb'] ?? null,
'mem_consumed_mb' => $stats['memory_consumed_mb'] ?? null,
'mem_total_mb' => $memoryTotalMb,
'disk_read_bps' => $stats['disk_read_bps'] ?? null,
'disk_write_bps' => $stats['disk_write_bps'] ?? null,
'disk_latency_ms' => $stats['disk_latency_ms'] ?? null,
'disk_total_bytes' => $diskSummary['total_bytes'] ?? ($provisionedDiskBytes ?: null),
'disk_used_bytes' => $diskSummary['used_bytes'] ?? null,
'disk_usage_percent' => $diskSummary['usage_percent'] ?? null,
'network_rx_bps' => $stats['network_rx_bps'] ?? null,
'network_tx_bps' => $stats['network_tx_bps'] ?? null,
'timestamp' => $stats['timestamp'] ?? null,
'stale' => (bool) ($stats['stale'] ?? false),
];
}
private const HISTORY_FIELDS = [
'cpu' => 'cpu_usage_percent',
'mem' => 'guest_memory_usage_percent',
'disk_read_bps' => 'disk_read_bps',
'disk_write_bps' => 'disk_write_bps',
'net_rx_bps' => 'network_rx_bps',
'net_tx_bps' => 'network_tx_bps',
];
public function history(string $vmwareId, \DateTimeInterface $from, \DateTimeInterface $to, int $maxPoints = 240): array
{
$rows = $this->portalMonitor->vmHistory($vmwareId, $from, $to);
$span = max(1, $to->getTimestamp() - $from->getTimestamp());
$bucket = max(1, (int) ceil($span / $maxPoints));
return collect($rows)
->filter(fn ($row) => is_array($row) && isset($row['timestamp']))
->groupBy(fn ($row) => intdiv(strtotime($row['timestamp']), $bucket))
->map(function ($group, $key) use ($bucket) {
$point = ['t' => gmdate('c', $key * $bucket + intdiv($bucket, 2))];
foreach (self::HISTORY_FIELDS as $name => $source) {
$values = $group->pluck($source)->filter(fn ($v) => $v !== null);
$point[$name] = $values->isEmpty() ? null : round($values->avg(), 2);
}
return $point;
})
->sortKeys()
->values()
->all();
}
}