Coletores
O que cada arquivo de collectors faz e em qual intervalo
Coletores
O script é dividido em 5 coletores independentes em server/collectors/. Cada um tem sua própria responsabilidade e cadência de push.
Resumo dos intervalos
| Coletor | Cadência | Tipo | Endpoint |
|---|---|---|---|
push_metrics.lua | Config.IntervalSec (60s padrão) | Snapshot agregado | /v1/ingest/push-metrics |
orgs.lua | A cada ~5 min | Snapshot por permissão | /v1/ingest/org-members |
money.lua | A cada ~10 min | Snapshot por jogador | /v1/ingest/player-money |
players.lua | Event-driven | 1 evento por sessão | /v1/ingest/player-session-start /v1/ingest/player-session-end /v1/ingest/player-job-change |
heatmap.lua | Snapshot 30 min + eventos imediatos | Posições + drops | /v1/ingest/positions /v1/ingest/disconnect-batch |
push_metrics.lua
O coração do script. Roda a cada Config.IntervalSec (padrão 60s) e envia um snapshot consolidado:
{
"cfxId": "abc123",
"players": 200,
"maxPlayers": 1000,
"entries": 12, // novos players desde último push
"exits": 8,
"newPlayers": 2,
"bans": 0,
"legalOnline": 35,
"illegalOnline": 41,
"groups": {
"legal": { "Polícia": 12, "Paramédico": 8, "Mecânica": 15 },
"ilegal": { "Drogas": 20, "Contrabando": 21 }
},
"staffMembers": [
{ "externalId": "1234", "name": "João", "role": "Admin" }
]
}Alimenta: Visão Geral, Métricas, Legal & Ilegal (counts), Banimentos, Staff online.
Inicia automaticamente 2 segundos após o resource subir (via server/main.lua).
players.lua
Event-driven. Não faz push em intervalo — reage a eventos:
| Evento | Ação |
|---|---|
vRP:playerSpawn / Connect | Envia player-session-start com license, nome, cfxId, cohortTag |
vRP:playerLeave / Disconnect / playerDropped | Envia player-session-end com durationMs, lastJob |
vRP:playerJoinGroup | Envia player-job-change com fromJob, toJob, jobType |
Alimenta: página Retenção — cohorts, churn risk, time-to-first-job legal vs ilegal.
Mantém um mapa local sessionStart[license] = os.time() para calcular duração corretamente.
orgs.lua
Push periódico (loop com Wait(INTERVAL_MS), inicia 15s após o resource subir).
Para cada permissão em Config.LegalOrganizations + Config.IllegalOrganizations, chama Adapter.getOrgMembers(permission) e envia:
{
"cfxId": "abc123",
"groupName": "Polícia",
"groupType": "legal",
"fullSnapshot": true,
"members": [
{
"passport": "1234",
"name": "João",
"lastName": "Silva",
"license": "abc",
"lastLoginAt": 1700000000,
"level": 5
}
]
}Alimenta: páginas Facções e Grupos do painel — lista de membros, hierarquia, último login.
Mesmo se o player estiver offline, o Adapter.getOrgMembers retorna ele — porque os dados vêm do banco (characters table), não da memória.
money.lua
Push periódico (loop, inicia 30s após o resource subir).
Chama Adapter.getPlayersMoney() que retorna todos os personagens que logaram nos últimos 7 dias com cash + bank:
{
"cfxId": "abc123",
"players": [
{ "passport": "1234", "name": "João", "cash": 5000, "bank": 250000 }
]
}Alimenta: página Economia — M2 em circulação, inflação semanal, top milionários, distribuição (Gini), detector de duping via z-score.
Este coletor faz uma query MySQL pesada (JOIN com playerdata para extrair cash via JSON_VALUE). Por isso a cadência é longa (10+ minutos). Se o seu DB for pequeno (< 50k characters) você pode reduzir, mas evite cadências menores que 5 min.
heatmap.lua
Dois threads paralelos:
Thread 1 — Snapshot de posições (30min)
A cada 30 minutos:
- Itera todos os players online via
GetPlayers() - Para cada um, lê
GetEntityCoords(GetPlayerPed(src))(server-side, OneSync) - Captura passport + jobType via adapter
- Adiciona ao batch
pendingPositions
Wait(0) -- yield entre cada player para não travarThread 2 — Eventos de disconnect (imediato)
No handler playerDropped(reason):
- Lê a posição final do ped (ainda válido por alguns ms após o drop)
- Classifica
reasonem:crash/drop_s2c/drop_c2s/quit - Adiciona ao batch
pendingDisconnects
Thread 3 — Flush (30s)
A cada 30 segundos:
- Se há disconnects pendentes → 1 POST batch para
/v1/ingest/disconnect-batch - Se há snapshots pendentes → 1 POST para
/v1/ingest/positions
Alimenta: página Mapa de calor — concentração de jogadores, locais de crash, drops S→C e C→S.
Requer OneSync. Sem OneSync, GetEntityCoords server-side retorna (0, 0, 0) para todos, e o mapa fica vazio.
Classificação de drop reason
Regras aplicadas em classifyReason(reason):
| Padrão (lowercase) | kind |
|---|---|
disconnected. exiting kicked | quit |
timed out time-out connection lost | drop_c2s |
reliable network event overflow onesync server overloaded | drop_s2c |
crash failed to set up game crashed couldn't load | crash |
| (default) | crash |