Skip to content
Blog

Find the resource that makes your FiveM server lag

Server stalls or low FPS? How to tell them apart, read FXServer’s profiler, count each resource’s commands and find the script behind the lag.

By Termi

7 min read
  • Performance
  • Debugging
  • Lua
On this page

“The server lags” is two different problems. Sometimes the server thread stalls and everyone stutters at once; sometimes some players lose FPS while the server is fine. This guide shows how to tell them apart, how to read FXServer’s own profiler, and how to find the cause that hides from both: a resource that registers thousands of commands. At the end, the same hunt in one command.

Two kinds of lag

  • The server thread stalls. Everyone rubber-bands at the same moment, cars warp, and the server console prints server thread hitch warning: timer interval of 212 milliseconds. Something in a server script takes too long in one frame.
  • A player’s FPS drops. The server is fine and its console is quiet, but some players stutter, often in the same places. The cost is in client scripts, streamed assets or the entities around them.

They need different tools, so first check which one you have: are there hitch warnings, and does everyone feel it or only some players?

Read the server profiler

FXServer ships with a profiler that times every resource, thread and event handler. In the server console:

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

Start it while the server lags, stop it after ten seconds or so, and save the recording. lag.json is a Chrome trace: open DevTools in Chrome, go to Performance and load the file. Each server frame is a Resource Manager Tick, and inside it you find every resource’s tick (name), its threads as thread @resource/file.lua[from..to], its event handlers and its exports.

Look for frames that take far longer than the rest and see which resource fills them. The line range tells you which CreateThread or handler it was.

A resource cannot start the profiler by itself. ExecuteCommand('profiler record start') from a script is refused unless server.cfg allows it: add_ace resource.<name> command.profiler allow, plus the same line for command.record and command.saveJSON.

Measure a player’s FPS

On the player’s side, F8 has two tools. resmon 1 shows every resource’s CPU time and memory live, which is enough when one resource clearly stands out. For a closer look, the same profiler runs in the game:

F8
resmon 1
profiler record 500
profiler saveJSON lag.json

That records 500 frames. Load the file in DevTools the same way and look for client threads that run every frame. A Wait(0) loop that does real work in each frame, like measuring the distance to hundreds of markers, is the usual first suspect.

The cause neither tool shows

One kind of lag hides from both. A server adds an emote pack and soon after it starts to lag. The profiler shows no heavy thread, and searching the pack for RegisterCommand finds a handful of calls.

The calls sit in a loop, behind a local alias, one for every 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

With two thousand emotes, that is two thousand commands and two thousand chat suggestions in every player’s game. A text search cannot count them, but the game can: GetRegisteredCommands() returns every command with the resource that registered it.

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

Run it as a client script and as a server script. A resource with hundreds of commands is worth a look. One with thousands almost always registers a command for every emote or item, where one command with an argument would do.

The usual suspects

  • Threads that wait too little. Wait(0) in a loop runs every frame. That is fine for drawing a marker and expensive when every round walks over all players or all points.
  • Work on the server tick. Rebuilding a big table or sorting thousands of rows inside one thread blocks every other resource for as long as it takes.
  • Events to everyone. TriggerClientEvent(name, -1, data) in a loop sends the whole payload to every player, every time.
  • Entities nobody deletes. Props and peds that pile up cost every player near them. GetGamePool('CObject') counts them.
  • Commands by the thousand. See above.

Common questions

What does “server thread hitch warning” mean in FiveM?

FXServer’s main thread is meant to tick 20 times a second, once every 50 ms. When more than 150 ms pass between two ticks, the console prints server thread hitch warning: timer interval of 212 milliseconds, and the number is how long that gap lasted. Nothing else on the server thread runs during the gap, so every player feels it at the same moment. Something on the server held the thread too long, usually one resource doing a lot of work in a single frame.

Why does my FiveM server hitch every few seconds?

A hitch on a regular beat usually comes from a server thread that sleeps a few seconds with Wait and then does heavy work in one go, such as walking every player, vehicle or job point. The server profiler shows which thread it is: the long frames name the resource and the lines of the thread, for example thread @jobs_garbage/server/main.lua[88..131].

How do I find which resource is lagging my FiveM server?

Use FXServer’s built-in profiler. Run profiler record start in the server console, profiler record stop about ten seconds later, then profiler saveJSON lag.json, and load the file in the Performance tab of Chrome DevTools. Every resource’s tick, thread and event handler shows up with its time, so the one behind a long frame stands out. If only some players lag, have them type resmon 1 in F8 instead to see each client resource’s CPU time live.

What does resmon show in FiveM?

resmon 1, typed in the F8 console, opens the resource monitor: a live list of every client resource with the CPU time it takes each frame and the memory it holds. It measures only the game of the player who opened it, so it explains that player’s FPS and says nothing about a server hitch.

What does oxmysql’s “took … ms to execute a query” warning mean?

oxmysql prints a line like [10.11.6-MariaDB] jobs_garbage took 312.4512ms to execute a query!, followed by the query, whenever a query runs longer than the mysql_slow_query_warning convar, 200 ms by default. The resource name tells you which script sent it. The query runs asynchronously, so it does not stall the server thread by itself, but the script waiting for the result is late, and players see a garage or an inventory that opens slowly. A missing index on the column in the WHERE clause is a common cause; put EXPLAIN in front of the query to see whether MySQL uses one.

The same hunt in one command

FiveDock’s agent does all of this for you. With the three add_ace lines in server.cfg, fivedock perf records the profiler for ten seconds, counts every resource’s commands on the server and in three players’ games, and asks every player for their FPS:

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.

The garbage job’s thread at server/main.lua:88-131 holds the server up for about 200 ms every few seconds, and the emote pack has 2,034 commands in the players’ game. When the server thread stalls while nobody is watching, the agent records the next ten seconds by itself, so the next report already says what happened.

Find what lags covers the setup and every part of the report.

All articles