493 lines
21 KiB
Markdown
493 lines
21 KiB
Markdown
# Decision: Monitoring especifico de PBS (punto 1.3)
|
|
|
|
## El problema
|
|
|
|
PBS no tiene exporter Prometheus nativo. Las metricas hay que extraerlas de alguna forma.
|
|
Evaluamos 3 vias para alimentar VictoriaMetrics/Grafana con datos de PBS.
|
|
|
|
---
|
|
|
|
## Via A: PBS metricas nativas a InfluxDB
|
|
|
|
### Que es
|
|
PBS tiene soporte nativo para enviar metricas a InfluxDB. Se configura desde la GUI o CLI sin instalar nada en el PBS.
|
|
|
|
### Como funciona
|
|
```
|
|
PBS ----(push nativo)----> InfluxDB ----(datasource)----> Grafana
|
|
^
|
|
VictoriaMetrics ---------(datasource)---------------------+
|
|
```
|
|
|
|
Grafana lee de dos backends: VictoriaMetrics (infra) e InfluxDB (PBS).
|
|
|
|
### Configuracion
|
|
```bash
|
|
# En cada PBS, añadir metric server
|
|
proxmox-backup-manager metric-server add influxdb mi-influxdb \
|
|
--server <ip-influxdb> \
|
|
--port 8089 \
|
|
--protocol udp
|
|
```
|
|
|
|
### Metricas que envia PBS nativamente
|
|
- Uso de CPU, RAM, IO del host PBS
|
|
- Espacio total/usado/disponible por datastore
|
|
- Estado de GC (running, last-run)
|
|
- Trafico de red
|
|
- Estadisticas de chunks (deduplication ratio)
|
|
|
|
### Lo que NO envia nativamente
|
|
- Estado de sync jobs (exito/fallo, ultimo run, bytes transferidos)
|
|
- Estado de verify jobs (errores encontrados, ultimo run)
|
|
- Ultimo backup por datastore/cliente
|
|
- Quota vs uso real por cliente
|
|
- Lista de snapshots y su antiguedad
|
|
- Estado de maintenance mode
|
|
|
|
### Veredicto
|
|
**Insuficiente como solucion unica.** Util como complemento rapido para metricas basicas de espacio, pero no cubre lo operacionalmente critico.
|
|
|
|
---
|
|
|
|
## Via B: natrontech/pbs-exporter + script complementario (RECOMENDADA)
|
|
|
|
### Que es
|
|
Combinacion de un exporter comunitario maduro en Go (`natrontech/pbs-exporter`) que cubre datastores, snapshots y host, complementado con un script ligero via textfile collector para las metricas de jobs (GC, verify, sync) que el exporter no cubre.
|
|
|
|
### pbs-exporter (natrontech)
|
|
|
|
**Repositorio**: https://github.com/natrontech/pbs-exporter
|
|
**Lenguaje**: Go | **Licencia**: GPL-3.0 | **Version**: v0.8.0 (dic 2025) | **Stars**: 45
|
|
**Testado con**: PBS 3.x
|
|
|
|
#### Metricas que expone
|
|
|
|
```prometheus
|
|
# === SISTEMA PBS ===
|
|
pbs_up # 1 si PBS responde, 0 si no
|
|
pbs_version{version="3.3-1"} # Version de PBS
|
|
|
|
# === ALMACENAMIENTO POR DATASTORE ===
|
|
pbs_available{datastore="cliente1"} # Bytes disponibles
|
|
pbs_size{datastore="cliente1"} # Bytes totales
|
|
pbs_used{datastore="cliente1"} # Bytes usados
|
|
|
|
# === SNAPSHOTS ===
|
|
pbs_snapshot_count{datastore="cliente1"} # Total snapshots
|
|
pbs_snapshot_vm_count{id="101",datastore=".."} # Snapshots por VM
|
|
pbs_snapshot_vm_last_timestamp{id="101",...} # Timestamp ultimo backup por VM
|
|
pbs_snapshot_vm_last_verify{id="101",...} # Estado verificacion ultimo backup
|
|
|
|
# === HOST ===
|
|
pbs_host_cpu_usage # Uso CPU
|
|
pbs_host_memory_free / _total / _used # RAM
|
|
pbs_host_swap_free / _total / _used # Swap
|
|
pbs_host_disk_available / _total / _used # Disco
|
|
pbs_host_uptime # Uptime en segundos
|
|
pbs_host_io_wait # IO wait
|
|
pbs_host_load1 / _load5 / _load15 # Load averages
|
|
|
|
# === SUSCRIPCION ===
|
|
pbs_host_subscription_status # Estado licencia
|
|
pbs_host_subscription_due_timestamp_seconds # Expiracion
|
|
```
|
|
|
|
#### Modo multi-target (clave para escalar)
|
|
|
|
Un solo pbs-exporter puede monitorizar multiples PBS:
|
|
|
|
```yaml
|
|
# vmagent scrape config
|
|
- job_name: 'pbs'
|
|
metrics_path: /metrics
|
|
params:
|
|
module: [default]
|
|
static_configs:
|
|
- targets:
|
|
- https://pbs-server1:8007
|
|
- https://pbs-server2:8007
|
|
- https://pbs-server3:8007
|
|
relabel_configs:
|
|
- source_labels: [__address__]
|
|
target_label: __param_target
|
|
- source_labels: [__param_target]
|
|
target_label: instance
|
|
- target_label: __address__
|
|
replacement: pbs-exporter:10019 # un solo exporter central
|
|
```
|
|
|
|
Esto significa: **1 instancia de pbs-exporter para N servidores PBS**. No hay que instalar nada en cada PBS.
|
|
|
|
#### Despliegue
|
|
|
|
**Opcion 1: Docker (recomendado para centralizacion)**
|
|
```yaml
|
|
services:
|
|
pbs-exporter:
|
|
image: ghcr.io/natrontech/pbs-exporter:latest
|
|
container_name: pbs-exporter
|
|
restart: unless-stopped
|
|
environment:
|
|
- PBS_API_TOKEN_NAME=pbs-exporter
|
|
- PBS_API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
|
- PBS_USERNAME=monitoring@pbs
|
|
- PBS_INSECURE=true
|
|
ports:
|
|
- "10019:10019"
|
|
```
|
|
|
|
**Opcion 2: Binario + systemd (en cada PBS si no se usa multi-target)**
|
|
```bash
|
|
# Crear usuario dedicado en PBS (minimos privilegios)
|
|
proxmox-backup-manager user create monitoring@pbs
|
|
proxmox-backup-manager user generate-token monitoring@pbs pbs-exporter
|
|
# Dar solo permisos Audit
|
|
proxmox-backup-manager acl update / Audit --auth-id monitoring@pbs
|
|
|
|
# Descargar binario
|
|
wget https://github.com/natrontech/pbs-exporter/releases/download/v0.8.0/pbs-exporter_linux_amd64
|
|
chmod +x pbs-exporter_linux_amd64
|
|
mv pbs-exporter_linux_amd64 /usr/local/bin/pbs-exporter
|
|
|
|
# Systemd unit
|
|
cat > /etc/systemd/system/pbs-exporter.service << 'EOF'
|
|
[Unit]
|
|
Description=PBS Prometheus Exporter
|
|
After=network.target proxmox-backup.service
|
|
|
|
[Service]
|
|
Type=simple
|
|
EnvironmentFile=/etc/default/pbs-exporter
|
|
ExecStart=/usr/local/bin/pbs-exporter
|
|
Restart=always
|
|
RestartSec=10
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
|
|
# Configuracion
|
|
cat > /etc/default/pbs-exporter << 'EOF'
|
|
PBS_API_TOKEN_NAME=pbs-exporter
|
|
PBS_API_TOKEN=<token-generado>
|
|
PBS_USERNAME=monitoring@pbs
|
|
PBS_ENDPOINT=https://localhost:8007
|
|
PBS_INSECURE=true
|
|
PBS_LISTEN_ADDRESS=:10019
|
|
EOF
|
|
|
|
systemctl enable --now pbs-exporter
|
|
```
|
|
|
|
#### Lo que NO cubre pbs-exporter
|
|
- Estado de GC jobs (ultimo run, duracion, espacio liberado)
|
|
- Estado de sync jobs (exito/fallo, ultimo run, bytes, remote)
|
|
- Estado de verify jobs (como job programado, no como verificacion de snapshot)
|
|
- Maintenance mode
|
|
- Quota/reservation ZFS por datastore
|
|
|
|
### Script complementario para jobs (textfile collector)
|
|
|
|
Solo cubre lo que pbs-exporter no tiene. ~40 lineas.
|
|
|
|
```bash
|
|
#!/bin/bash
|
|
# /usr/local/sbin/pbs-jobs-metrics.sh
|
|
# Complementa pbs-exporter con metricas de GC, verify y sync jobs
|
|
# Cron: */10 * * * * /usr/local/sbin/pbs-jobs-metrics.sh
|
|
|
|
OUTPUT="/var/lib/node_exporter/textfile/pbs_jobs.prom"
|
|
TMPFILE="${OUTPUT}.tmp"
|
|
PBS_API="https://localhost:8007/api2/json"
|
|
|
|
# Autenticacion con API token (sin password)
|
|
TOKEN_NAME="monitoring@pbs!pbs-exporter"
|
|
TOKEN_VALUE="<mismo-token-que-pbs-exporter>"
|
|
AUTH_HEADER="Authorization: PBSAPIToken=${TOKEN_NAME}:${TOKEN_VALUE}"
|
|
|
|
api_get() {
|
|
curl -sk -H "$AUTH_HEADER" "${PBS_API}${1}"
|
|
}
|
|
|
|
> "$TMPFILE"
|
|
|
|
# GC jobs
|
|
api_get "/admin/gc" | jq -r '.data[] | @base64' | while read row; do
|
|
_jq() { echo "$row" | base64 -d | jq -r "$1 // 0"; }
|
|
store=$(_jq '.store')
|
|
last=$(_jq '."last-run-endtime"')
|
|
state=$(_jq '."last-run-state"')
|
|
ok=0; [ "$state" = "ok" ] && ok=1
|
|
echo "pbs_gc_last_run_timestamp{datastore=\"${store}\"} ${last}" >> "$TMPFILE"
|
|
echo "pbs_gc_last_status{datastore=\"${store}\"} ${ok}" >> "$TMPFILE"
|
|
done
|
|
|
|
# Verify jobs
|
|
api_get "/admin/verify" | jq -r '.data[] | @base64' | while read row; do
|
|
_jq() { echo "$row" | base64 -d | jq -r "$1 // 0"; }
|
|
store=$(_jq '.store')
|
|
last=$(_jq '."last-run-endtime"')
|
|
state=$(_jq '."last-run-state"')
|
|
ok=0; [ "$state" = "ok" ] && ok=1
|
|
echo "pbs_verify_last_run_timestamp{datastore=\"${store}\"} ${last}" >> "$TMPFILE"
|
|
echo "pbs_verify_last_status{datastore=\"${store}\"} ${ok}" >> "$TMPFILE"
|
|
done
|
|
|
|
# Sync jobs
|
|
api_get "/admin/sync" | jq -r '.data[] | @base64' | while read row; do
|
|
_jq() { echo "$row" | base64 -d | jq -r "$1 // 0"; }
|
|
store=$(_jq '.store')
|
|
remote=$(_jq '.remote')
|
|
last=$(_jq '."last-run-endtime"')
|
|
state=$(_jq '."last-run-state"')
|
|
ok=0; [ "$state" = "ok" ] && ok=1
|
|
echo "pbs_sync_last_run_timestamp{datastore=\"${store}\",remote=\"${remote}\"} ${last}" >> "$TMPFILE"
|
|
echo "pbs_sync_last_status{datastore=\"${store}\",remote=\"${remote}\"} ${ok}" >> "$TMPFILE"
|
|
done
|
|
|
|
mv "$TMPFILE" "$OUTPUT"
|
|
```
|
|
|
|
### Arquitectura resultante
|
|
|
|
```
|
|
vmagent scrape
|
|
│
|
|
├── :9100 node_exporter (sistema + ZFS quotas)
|
|
│ └── textfile/pbs_jobs.prom (GC/verify/sync jobs via script)
|
|
│
|
|
└── :10019 pbs-exporter (datastores, snapshots, ultimo backup por VM, host)
|
|
└── multi-target: puede cubrir N servidores PBS
|
|
```
|
|
|
|
**Un unico backend**: VictoriaMetrics. Sin InfluxDB. Todo en PromQL/MetricsQL.
|
|
|
|
### Ventajas de esta combinacion
|
|
- pbs-exporter hace el 80% del trabajo pesado (datastores, snapshots por VM, host)
|
|
- Script de 40 lineas cubre el 20% restante (jobs)
|
|
- Mismo token de API para ambos (un solo usuario `monitoring@pbs`)
|
|
- Multi-target: un pbs-exporter central para todos los PBS
|
|
- Si el exporter crece y añade metricas de jobs en el futuro, se elimina el script
|
|
|
|
### Desventajas
|
|
- Dos fuentes de metricas PBS (exporter + textfile), pero ambas en el mismo backend
|
|
- El script tiene resolucion de cron (10 min), el exporter es real-time
|
|
- Depende de un proyecto comunitario (45 stars, pero activo y en Go)
|
|
|
|
---
|
|
|
|
## Via C: Script completo custom (textfile collector) - DESCARTADA como principal
|
|
|
|
La Via B original del documento anterior. Sigue siendo valida como fallback si pbs-exporter deja de mantenerse o tiene algun problema, pero no tiene sentido reescribir en bash lo que ya existe en Go con mejor rendimiento y multi-target.
|
|
|
|
Se mantiene el script completo en la seccion de referencia por si fuera necesario en el futuro.
|
|
|
|
<details>
|
|
<summary>Script completo de referencia (click para expandir)</summary>
|
|
|
|
```bash
|
|
#!/bin/bash
|
|
# /usr/local/sbin/pbs-metrics-exporter.sh
|
|
# Version completa: genera TODAS las metricas PBS
|
|
# Solo usar si pbs-exporter de natrontech no esta disponible
|
|
|
|
OUTPUT="/var/lib/node_exporter/textfile/pbs_metrics.prom"
|
|
TMPFILE="${OUTPUT}.tmp"
|
|
PBS_API="https://localhost:8007/api2/json"
|
|
PBS_USER="root@pam"
|
|
PBS_PASS="$(cat /etc/pbs-monitoring-password)"
|
|
|
|
TICKET_DATA=$(curl -sk -d "username=${PBS_USER}&password=${PBS_PASS}" \
|
|
"${PBS_API}/access/ticket")
|
|
TICKET=$(echo "$TICKET_DATA" | jq -r '.data.ticket')
|
|
CSRF=$(echo "$TICKET_DATA" | jq -r '.data.CSRFPreventionToken')
|
|
|
|
api_get() {
|
|
curl -sk -b "PBSAuthCookie=${TICKET}" -H "CSRFPreventionToken: ${CSRF}" \
|
|
"${PBS_API}${1}"
|
|
}
|
|
|
|
> "$TMPFILE"
|
|
|
|
# Datastore usage
|
|
api_get "/status/datastore-usage" | jq -r '.data[] | @base64' | while read row; do
|
|
_jq() { echo "$row" | base64 -d | jq -r "$1 // 0"; }
|
|
name=$(_jq '.store')
|
|
echo "pbs_datastore_size_bytes{datastore=\"${name}\"} $(_jq '.total')" >> "$TMPFILE"
|
|
echo "pbs_datastore_used_bytes{datastore=\"${name}\"} $(_jq '.used')" >> "$TMPFILE"
|
|
echo "pbs_datastore_available_bytes{datastore=\"${name}\"} $(_jq '.avail')" >> "$TMPFILE"
|
|
done
|
|
|
|
# GC jobs
|
|
api_get "/admin/gc" | jq -r '.data[] | @base64' | while read row; do
|
|
_jq() { echo "$row" | base64 -d | jq -r "$1 // 0"; }
|
|
store=$(_jq '.store'); last=$(_jq '."last-run-endtime"')
|
|
state=$(_jq '."last-run-state"'); ok=0; [ "$state" = "ok" ] && ok=1
|
|
echo "pbs_gc_last_run_timestamp{datastore=\"${store}\"} ${last}" >> "$TMPFILE"
|
|
echo "pbs_gc_last_status{datastore=\"${store}\"} ${ok}" >> "$TMPFILE"
|
|
done
|
|
|
|
# Verify jobs
|
|
api_get "/admin/verify" | jq -r '.data[] | @base64' | while read row; do
|
|
_jq() { echo "$row" | base64 -d | jq -r "$1 // 0"; }
|
|
store=$(_jq '.store'); last=$(_jq '."last-run-endtime"')
|
|
state=$(_jq '."last-run-state"'); ok=0; [ "$state" = "ok" ] && ok=1
|
|
echo "pbs_verify_last_run_timestamp{datastore=\"${store}\"} ${last}" >> "$TMPFILE"
|
|
echo "pbs_verify_last_status{datastore=\"${store}\"} ${ok}" >> "$TMPFILE"
|
|
done
|
|
|
|
# Sync jobs
|
|
api_get "/admin/sync" | jq -r '.data[] | @base64' | while read row; do
|
|
_jq() { echo "$row" | base64 -d | jq -r "$1 // 0"; }
|
|
store=$(_jq '.store'); remote=$(_jq '.remote')
|
|
last=$(_jq '."last-run-endtime"')
|
|
state=$(_jq '."last-run-state"'); ok=0; [ "$state" = "ok" ] && ok=1
|
|
echo "pbs_sync_last_run_timestamp{datastore=\"${store}\",remote=\"${remote}\"} ${last}" >> "$TMPFILE"
|
|
echo "pbs_sync_last_status{datastore=\"${store}\",remote=\"${remote}\"} ${ok}" >> "$TMPFILE"
|
|
done
|
|
|
|
mv "$TMPFILE" "$OUTPUT"
|
|
```
|
|
|
|
</details>
|
|
|
|
---
|
|
|
|
## Comparativa final actualizada
|
|
|
|
| Aspecto | A: InfluxDB nativo | B: pbs-exporter + script (RECOMENDADA) | C: Script completo custom |
|
|
|---------|-------------------|---------------------------------------|--------------------------|
|
|
| Esfuerzo de setup | 5 minutos | 30 min exporter + 1h script | 2 horas |
|
|
| Datastores (espacio) | Si | Si (exporter) | Si |
|
|
| Snapshots por VM | No | Si (exporter) | Posible (mas script) |
|
|
| Ultimo backup por VM | No | Si (exporter) | Posible (mas script) |
|
|
| GC jobs | Basico | Si (script) | Si |
|
|
| Verify jobs | No | Si (script) | Si |
|
|
| Sync jobs | No | Si (script) | Si |
|
|
| Host metrics | Si | Si (exporter) | Manual |
|
|
| Multi-target (1 instancia para N PBS) | N/A | Si (exporter) | No |
|
|
| Backend | InfluxDB (extra) | VictoriaMetrics (unificado) | VictoriaMetrics (unificado) |
|
|
| Dependencias | InfluxDB | Binario Go + bash/curl/jq | bash/curl/jq |
|
|
| Resolucion | ~30s | Real-time (exporter) + 10min (script) | 5-15 min |
|
|
| Mantenimiento | Nulo | Bajo | Medio |
|
|
| Alertas PromQL | No | Si | Si |
|
|
|
|
---
|
|
|
|
## Recomendacion final
|
|
|
|
### Despliegue en fases
|
|
|
|
**Fase 1 (dia 1): pbs-exporter centralizado**
|
|
1. Crear usuario `monitoring@pbs` + API token en cada PBS (Ansible playbook)
|
|
2. Desplegar un pbs-exporter en Docker (VM de monitoring) en modo multi-target
|
|
3. Configurar vmagent para scrapear todos los PBS via multi-target
|
|
4. Dashboard Grafana basico: datastores, espacio, ultimo backup por VM
|
|
|
|
**Fase 2 (dia 2-3): script complementario de jobs**
|
|
1. Desplegar `pbs-jobs-metrics.sh` en cada PBS via Ansible
|
|
2. Configurar cron cada 10 minutos
|
|
3. node_exporter ya esta instalado (del punto 1.2), solo añadir textfile path
|
|
4. Dashboard Grafana: añadir paneles de GC, verify, sync jobs
|
|
|
|
**Fase 3 (semana 2): alertas**
|
|
1. Configurar alertas en Grafana o vmalert:
|
|
- `pbs_snapshot_vm_last_timestamp` > 48h sin backup
|
|
- `pbs_gc_last_status` != 1
|
|
- `pbs_verify_last_status` != 1
|
|
- `pbs_sync_last_status` != 1
|
|
- `pbs_used / pbs_size` > 0.90
|
|
2. Notificaciones: Telegram para criticas, email para warnings
|
|
|
|
**Fase 4 (mes 2): capacity planning**
|
|
1. Dashboard de tendencias de crecimiento por datastore
|
|
2. Prediccion de llenado con `predict_linear()` de PromQL
|
|
3. Deteccion de quotas sobredimensionadas
|
|
|
|
---
|
|
|
|
## Dashboards PBS para Grafana
|
|
|
|
### Dashboard 1: Vista global PBS (para el NOC / pantalla grande)
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────┐
|
|
│ PBS GLOBAL STATUS │
|
|
├──────────────┬──────────────┬──────────────┬────────────┤
|
|
│ Datastores │ Espacio │ Jobs OK │ Alertas │
|
|
│ 42 │ 78% usado │ 39/42 │ 3 │
|
|
├──────────────┴──────────────┴──────────────┴────────────┤
|
|
│ │
|
|
│ [Tabla] Datastores con problemas │
|
|
│ - cliente7: verify fallido hace 5 dias │
|
|
│ - cliente12: sync fallido, ultimo ok hace 45 dias │
|
|
│ - cliente23: espacio > 90% quota │
|
|
│ │
|
|
├──────────────────────────────────────────────────────────┤
|
|
│ [Barras] Top 10 datastores por ocupacion │
|
|
│ ████████████████████░░░░ cliente3 82% │
|
|
│ ███████████████████░░░░░ cliente1 78% │
|
|
│ ... │
|
|
├──────────────────────────────────────────────────────────┤
|
|
│ [Timeline] Jobs ejecutados hoy │
|
|
│ 09:00 GC cliente1 OK (12min) │
|
|
│ 09:30 GC cliente2 OK (8min) │
|
|
│ 10:00 Verify cliente5 OK (45min) │
|
|
│ 10:30 Verify cliente8 FAIL (errors: 2) │
|
|
└──────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### Dashboard 2: Detalle por datastore (para operaciones)
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────┐
|
|
│ DATASTORE: cliente1 Status: ACTIVE │
|
|
├──────────────┬──────────────┬──────────────┬────────────┤
|
|
│ Espacio │ Quota │ Dedup ratio │ Snapshots │
|
|
│ 322 GB │ 600 GB │ 1.42x │ 45 │
|
|
├──────────────┴──────────────┴──────────────┴────────────┤
|
|
│ [Grafico linea] Espacio usado ultimos 90 dias │
|
|
│ (tendencia de crecimiento + prediccion de llenado) │
|
|
├──────────────────────────────────────────────────────────┤
|
|
│ JOBS │
|
|
│ GC: ultimo 2h ago, duro 12min, libero 5.2GB [OK]│
|
|
│ Verify: ultimo 1d ago, 45 snaps ok, 0 errores [OK]│
|
|
│ Sync: ultimo 25d ago, desde pbs3343, 10.7GB [OK]│
|
|
│ Proximo sync: dia 15 a las 09:23 │
|
|
├──────────────────────────────────────────────────────────┤
|
|
│ [Tabla] Snapshots recientes │
|
|
│ vm/101 2026-03-12 12.3GB verified │
|
|
│ vm/101 2026-03-11 12.1GB verified │
|
|
│ vm/102 2026-03-12 8.7GB outdated │
|
|
└──────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### Dashboard 3: Capacidad y planificacion (para management)
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────┐
|
|
│ CAPACITY PLANNING │
|
|
├──────────────────────────────────────────────────────────┤
|
|
│ Pool9: 85TB usados / 120TB total (71%) │
|
|
│ [Grafico] Tendencia 12 meses + prediccion │
|
|
│ "Al ritmo actual, pool9 se llena en 8 meses" │
|
|
├──────────────────────────────────────────────────────────┤
|
|
│ [Tabla] Clientes por crecimiento mensual │
|
|
│ cliente3: +15GB/mes (el que mas crece) │
|
|
│ cliente1: +12GB/mes │
|
|
│ cliente7: +8GB/mes │
|
|
├──────────────────────────────────────────────────────────┤
|
|
│ [Tabla] Clientes con quota sobredimensionada │
|
|
│ cliente15: usa 20GB de 600GB quota (3%) │
|
|
│ cliente22: usa 45GB de 600GB quota (7.5%) │
|
|
│ "Oportunidad de reclamar 1.1TB de quota" │
|
|
├──────────────────────────────────────────────────────────┤
|
|
│ Resumen comercial │
|
|
│ Clientes activos: 40 │
|
|
│ GB comerciales vendidos: 8,200 GB │
|
|
│ GB reales usados: 28,500 GB (ratio 3.5x real) │
|
|
│ GB quota asignada: 49,200 GB (ratio 6x) │
|
|
└──────────────────────────────────────────────────────────┘
|
|
```
|