Coletores
O que cada arquivo de collectors faz e em qual intervalo
Coletores
O script é dividido em 6 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 |
transactions.lua | Flush 30s (batch até 500) | Eventos de entrada/saída de dinheiro | /v1/ingest/player-transactions |
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.
transactions.lua
Buffer em memória + flush a cada 30 segundos em batches de até 500 entradas. Buffer máximo de 5000 entradas (entries acima disso são descartadas pra não estourar memória).
Não dispara sozinho — depende de chamadas dos exports addIncome e addExpense feitas a partir do seu sistema de banco/loja/recompensas. Veja Exports → Transações e Hooks → Transações.
{
"cfxId": "abc123",
"batch": [
{
"passport": "1234",
"kind": "income",
"amount": 5000,
"description": "Salário Polícia",
"category": "salario",
"timestamp": 1700000000000
},
{
"passport": "1234",
"kind": "expense",
"amount": 250,
"description": "Compra mercado 24h",
"category": "loja",
"timestamp": 1700000005000
}
]
}Alimenta: aba Transações da página Economia — log por jogador, filtro por período, soma por categoria, detecção de movimentos suspeitos.
Faz flush automático no onResourceStop pra não perder eventos pendentes durante restart do resource.
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 |