Saltar al contenido
Blog

Servidor FiveM con lag: encuentra el resource que lo causa

¿El servidor se congela o bajan los FPS? Distínguelo, lee el profiler de FXServer, cuenta los comandos de cada resource y encuentra el script que causa el lag.

Por Termi

7 min de lectura
  • Performance
  • Debugging
  • Lua
En esta página

“El servidor va con lag” son dos problemas distintos. A veces se atasca el thread del servidor y todos van a tirones a la vez; otras veces solo algunos jugadores pierden FPS mientras el servidor va bien. Esta guía muestra cómo distinguirlos, cómo leer el profiler del propio FXServer y cómo encontrar la causa que se esconde de ambos: un resource que registra miles de comandos. Al final, la misma búsqueda con un solo comando.

Dos tipos de lag

  • El thread del servidor se atasca. Todos hacen rubber-band en el mismo momento, los coches se teletransportan y la consola del servidor imprime server thread hitch warning: timer interval of 212 milliseconds. Algo en un script del servidor tarda demasiado en un frame.
  • Bajan los FPS de un jugador. El servidor está bien y su consola calla, pero algunos jugadores van a tirones, a menudo en los mismos sitios. El coste está en scripts del cliente, assets en streaming o las entidades de alrededor.

Necesitan herramientas distintas, así que primero comprueba cuál tienes: ¿hay hitch warnings, y lo notan todos o solo algunos jugadores?

Leer el profiler del servidor

FXServer trae un profiler que mide cada resource, cada thread y cada manejador de eventos. En la consola del servidor:

server console
profiler record start
profiler record stop
profiler saveJSON lag.json

Lánzalo mientras el servidor va con lag, páralo pasados unos diez segundos y guarda la grabación. lag.json es un trace de Chrome: abre las DevTools en Chrome, ve a Performance y carga el archivo. Cada frame del servidor es un Resource Manager Tick, y dentro encuentras el tick (name) de cada resource, sus threads como thread @resource/file.lua[from..to], sus manejadores de eventos y sus exports.

Busca los frames que tardan mucho más que el resto y mira qué resource los llena. El rango de líneas te dice qué CreateThread o manejador era.

Un resource no puede iniciar el profiler por sí solo. ExecuteCommand('profiler record start') desde un script se rechaza a menos que server.cfg lo permita: add_ace resource.<name> command.profiler allow, más la misma línea para command.record y command.saveJSON.

Medir los FPS de un jugador

En el lado del jugador, F8 ofrece dos herramientas. resmon 1 muestra en vivo el tiempo de CPU y la memoria de cada resource, lo que basta cuando uno destaca claramente. Para mirar más de cerca, el mismo profiler funciona dentro del juego:

F8
resmon 1
profiler record 500
profiler saveJSON lag.json

Eso graba 500 frames. Carga el archivo en las DevTools igual que antes y busca threads del cliente que corran en cada frame. Un bucle Wait(0) que hace trabajo real en cada frame, como medir la distancia a cientos de markers, suele ser el primer sospechoso.

La causa que ninguna de las dos herramientas muestra

Hay un tipo de lag que se esconde de ambas. Un servidor añade un pack de emotes y poco después empieza a ir con lag. El profiler no muestra ningún thread pesado, y buscar RegisterCommand en el pack encuentra un puñado de llamadas.

Las llamadas están en un bucle, tras un alias local, una por cada emote:

client.lua
local register = RegisterCommand

for _, emote in ipairs(Emotes) do
  register(emote.command, function() PlayEmote(emote) end, false)
  TriggerEvent('chat:addSuggestion', '/' .. emote.command, emote.label)
end

Con dos mil emotes, son dos mil comandos y dos mil sugerencias de chat en el juego de cada jugador. Una búsqueda de texto no puede contarlos, pero el juego sí: GetRegisteredCommands() devuelve cada comando con el resource que lo registró.

count.lua
local counts = {}
for _, command in ipairs(GetRegisteredCommands()) do
  counts[command.resource] = (counts[command.resource] or 0) + 1
end

for resource, count in pairs(counts) do
  if count > 100 then print(resource, count) end
end

Ejecútalo como script de cliente y como script de servidor. Un resource con cientos de comandos merece una mirada. Uno con miles casi siempre registra un comando por cada emote o item, cuando bastaría un comando con un argumento.

Los sospechosos habituales

  • Threads que esperan muy poco. Wait(0) en un bucle corre en cada frame. Está bien para dibujar un marker y sale caro cuando cada vuelta recorre a todos los jugadores o todos los puntos.
  • Trabajo en el tick del servidor. Reconstruir una tabla grande u ordenar miles de filas dentro de un thread bloquea a todos los demás resources el tiempo que dure.
  • Eventos para todos. TriggerClientEvent(name, -1, data) en un bucle envía todo el payload a cada jugador, cada vez.
  • Entidades que nadie borra. Los props y peds que se acumulan le cuestan a cada jugador cercano. GetGamePool('CObject') los cuenta.
  • Comandos a miles. Mira más arriba.

Preguntas frecuentes

¿Qué significa “server thread hitch warning” en FiveM?

El thread principal de FXServer debe hacer 20 ticks por segundo, uno cada 50 ms. Cuando pasan más de 150 ms entre dos ticks, la consola imprime server thread hitch warning: timer interval of 212 milliseconds, y el número es lo que duró ese hueco. Durante el hueco no corre nada más en el thread del servidor, así que todos los jugadores lo notan en el mismo momento. Algo en el servidor retuvo el thread demasiado tiempo, normalmente un resource que hace mucho trabajo en un solo frame.

¿Por qué mi servidor FiveM da hitch cada pocos segundos?

Un hitch con un ritmo regular suele venir de un thread del servidor que duerme unos segundos con Wait y luego hace mucho trabajo de golpe, como recorrer a todos los jugadores, vehículos o puntos de un job. El profiler del servidor muestra qué thread es: los frames largos nombran el resource y las líneas del thread, por ejemplo thread @jobs_garbage/server/main.lua[88..131].

¿Cómo encuentro qué resource da lag a mi servidor FiveM?

Usa el profiler integrado de FXServer. Ejecuta profiler record start en la consola del servidor, profiler record stop unos diez segundos después y luego profiler saveJSON lag.json, y carga el archivo en la pestaña Performance de las DevTools de Chrome. El tick, los threads y los manejadores de eventos de cada resource aparecen con su tiempo, así que el que está detrás de un frame largo destaca. Si solo algunos jugadores tienen lag, pídeles que escriban resmon 1 en F8 para ver en vivo el tiempo de CPU de cada resource del cliente.

¿Qué muestra resmon en FiveM?

resmon 1, escrito en la consola F8, abre el monitor de resources: una lista en vivo de cada resource del cliente con el tiempo de CPU que consume por frame y la memoria que ocupa. Mide solo el juego del jugador que lo abrió, así que explica los FPS de ese jugador y no dice nada de un hitch del servidor.

¿Qué significa el aviso “took … ms to execute a query” de oxmysql?

oxmysql imprime una línea como [10.11.6-MariaDB] jobs_garbage took 312.4512ms to execute a query!, seguida de la consulta, cada vez que una consulta tarda más que el convar mysql_slow_query_warning, 200 ms por defecto. El nombre del resource te dice qué script la envió. La consulta corre de forma asíncrona, así que por sí sola no atasca el thread del servidor, pero el script que espera el resultado llega tarde y los jugadores ven un garaje o un inventario que abre despacio. Una causa frecuente es que falte un índice en la columna de la cláusula WHERE; pon EXPLAIN delante de la consulta para ver si MySQL usa alguno.

La misma búsqueda con un comando

El agente de FiveDock hace todo esto por ti. Con las tres líneas add_ace en server.cfg, fivedock perf graba el profiler durante diez segundos, cuenta los comandos de cada resource en el servidor y en el juego de tres jugadores, y pide los FPS a cada jugador:

fivedock perf my-city
my-city  profiled 10 s · 214 players online
  server thread  late by 3.8 ms on average, 212 ms at worst in the last minute

Server  198 ticks, the slowest took 212 ms
  resource          total   per tick  worst tick
  jobs_garbage   1,236 ms     6.2 ms      205 ms
  hud              412 ms     2.1 ms      6.3 ms
  oxmysql           96 ms     0.5 ms       14 ms
  emotes            12 ms     0.1 ms      0.4 ms

Where exactly  the threads, events and exports that took longest
  jobs_garbage     611 ms  thread · server/main.lua:88-131 · 3× · worst 203 ms
  jobs_garbage     598 ms  thread · server/main.lua:12-40 · 198× · worst 6.1 ms
  hud              380 ms  event hud:sync · server.lua:52-77 · 1,420× · worst 4.2 ms
  oxmysql           88 ms  export · dist/build.js:310 · 402× · worst 14 ms

Commands  on the server, and in the game of 3 sampled players
  resource     server  players
  emotes            –    2,034
  admin_menu       61       12
  hud               9        4
  total           248    2,310
  A command per emote or item adds up fast. Check whether that resource needs them all.

El thread del trabajo de basureros en server/main.lua:88-131 retiene el servidor unos 200 ms cada pocos segundos, y el pack de emotes tiene 2.034 comandos en el juego de los jugadores. Cuando el thread del servidor se atasca sin que nadie mire, el agente graba por su cuenta los diez segundos siguientes, así que el siguiente informe ya dice qué pasó.

Encontrar qué da lag cubre la configuración y cada parte del informe.

Todos los artículos