<!-- fivemad.com, Markdown source of https://fivemad.com/docs/tools/profiler-analyzer -->
> Source: https://fivemad.com/docs/tools/profiler-analyzer
> Section: Tools
> Tags: profiler, resmon, performance, lag, saveJSON
> Tool: https://fivemad.com/tools/profiler-analyzer
> Last updated: 2026-09-22
> Site map for assistants: https://fivemad.com/llms.txt

# Profiler Analyzer

Record a FiveM profile with profiler record and saveJSON, then read time per resource, worst frames, hitches, a flame graph and findings with fixes.

The Profiler Analyzer reads the JSON file the built-in FiveM profiler saves and turns it into a report you can act on: which resources eat your frame or tick time, which frames were the worst and what ran in them, which events and threads are heavy, when hitches happened, and plain advice on what to fix. It works for both server and client recordings and runs entirely in your browser.

## What it is for

`resmon` tells you a resource averages 0.8 ms, but not why, not when, and not what it looked like in the frame where the game stuttered. The profiler records all of that, but its raw output is hard to read. Use this tool when:

- Players report lag or rubber banding and you need to know which resource is responsible.
- The server console prints hitch warnings and you want to know whether a script caused them.
- You changed a resource and want proof that it got faster (the Compare tab).
- You want to share a short, readable summary with a script's developer.

## How to record a profile

FiveM has a profiler built in on both sides. It records every resource tick and event handler with microsecond timing, including escrowed resources. The **How to record** card next to the drop zone has the same commands with copy buttons for **Server console** and **Client F8**.

### Server (txAdmin Live Console or the FXServer window)

```bash
profiler record 1200
profiler status
profiler saveJSON lag.json
```

1. `profiler record 1200` records 1200 ticks. The server runs 20 ticks per second, so that is one minute. Use `600` for a quick look.
2. `profiler status` shows whether it is still recording. It stops by itself.
3. `profiler saveJSON lag.json` writes the file next to your `server.cfg` with txAdmin, or wherever FXServer was started.

Record while the server is busy. Problems that only show with players online will not show on an empty server.

### Client (F8 console)

```bash
profiler record 1000
profiler status
profiler saveJSON lag.json
```

Stand where the game feels bad and do the laggy thing while it records. 1000 frames is 8-16 seconds depending on your FPS. The file ends up in `%localappdata%\FiveM\FiveM.app\citizen\lag.json`.

Client recordings store a small screenshot per frame, so they get big fast. Files of a few hundred MB are fine here.

### Other profiler commands

| Command | What it does |
| --- | --- |
| `profiler record start` / `profiler record stop` | Record until you stop it |
| `profiler resource myresource 300` | Records Lua function calls of one resource (`*` for all). Slower, but shows which function is heavy |
| `profiler view` | Opens the raw trace in Chrome DevTools for a manual look |
| `profiler save name` | Writes a binary file this tool cannot read. Use `saveJSON` |

## Quick start

1. Record a profile as above.
2. Drop the `.json` on the page, or press **Choose a file**. A gzipped `.json.gz` works too.
3. Read the verdict at the top, then the **Findings** tab.
4. Click the worst bars on the timeline to see exactly what ran in those frames.

No file handy? **Try a sample** loads a made-up server recording so you can look around. Its resource names are invented.

## The report

### File bar

Shows the file name, size, number of events and how long it took to read. Buttons:

| Button | What you get |
| --- | --- |
| Copy for Discord | A short summary that fits in one Discord message (2000 characters) |
| Report .md | `<file>-report.md`, the full report as Markdown |
| Timeline .png | `<file>-timeline.png`, a picture of the current timeline |
| Open another | Load a different file. You can also drop a new file anywhere on the page |

### Verdict

A one-line status (**Healthy**, **Some pressure**, **Struggling** or **Overloaded**), a title and a sentence explaining it. **Read as** shows whether the file was detected as a **Server** or **Client** recording and why; switch it if the guess is wrong.

The card on the right shows:

- **Scripts per tick (or frame), average**: the big number.
- **p95 / p99**: how bad the slow ticks or frames get.
- **Worst tick / frame**: click it to open that frame.
- **FPS, avg / 1% low** on the client, or **Tick interval, p99** on the server.
- **Hitches**, and how many of them came from scripts.
- **Recorded** (ticks or frames) and **Length**.

### Timeline

One bar per tick or frame. Two views:

- **Script time**: what resources spent in each frame or tick.
- **Tick interval** (server) or **Frame time** (client): the time between two ticks or frames for the whole server or game.

Amber and red mark the budgets below, and guide lines show 50 ms (a whole tick) or 60 and 30 FPS. Hover a bar for the numbers and the top three resources in it. Click a bar to open that frame. Drag to zoom, <kbd>Ctrl</kbd> + scroll to zoom, <kbd>Shift</kbd> + scroll to pan. Under the chart, **Worst ticks / frames** lists the eight worst with their time in the recording.

When you select a resource in the Resources tab, its own time is drawn in white on top of the timeline.

### Frame detail

Opens under the timeline when you click a bar or a worst-frame button. It shows:

- **Scripts**, **Resource Manager Tick**, and **Until next tick** (server) or **Frame time** with the FPS pace (client), marked heavy, over budget or hitch when they are.
- A flame chart of exactly what ran in that frame, in order, left to right. Colours mark the type: frame, resource tick, event handler, thread or function. Hover a block for its duration, start and file lines. Click a block to zoom in, **Whole tick / frame** to zoom out.
- On client recordings, the screenshot the game took at the end of that frame.
- **Heaviest resources in this frame**: click one to open it in Resources.

Use the arrows to step to the previous or next frame.

## Tabs

### Findings

Rules that read the numbers and point at problems, grouped as **Fix first**, **Worth fixing** and **Good to know** (filter with the buttons at the top). Each finding lists the evidence and a concrete fix, with **Open resource** and buttons that jump to the frames involved. They cover:

- Scripts taking a big part of every tick or frame.
- A resource running code every frame (the `Wait(0)` loop pattern).
- Heavy event handlers and heavy NUI callbacks.
- Events that fire constantly or very often.
- Spikes that repeat on a timer, and one-off big spikes.
- Resources that run many small threads.
- Hitches caused by scripts, and hitches not caused by scripts (streaming, the host, garbage collection).
- Resources with no detail (JS and C#), Lua memory that keeps growing, short recordings and unclosed scopes.

If nothing stands out you get **Nothing stands out**, with the tip to record during the laggy moment or on the other side (client vs server).

### Resources

A sortable table of every resource:

| Column | Meaning |
| --- | --- |
| Share | Share of all script time in the recording |
| Avg | Average ms per frame or tick over the whole recording, the same thing `resmon` shows |
| Worst | Most time spent in a single frame or tick |
| Events | Time spent in event handlers |
| Calls | Number of calls |
| Active | Share of frames or ticks where it ran |

Times are exclusive: when resource A triggers an event in resource B, B's handler counts for B, not twice. Filter by name; the table shows 60 rows until you press **Show all**.

Click a resource to open its detail: average, p95, worst and total time, **Its worst ticks / frames**, **What triggered it** (every tick, or which events, with calls, average, worst and total), **Threads, handlers and functions** with file and line numbers, and the Lua heap at the start and end when the file has it.

### Flame graph

The whole recording merged by call path. The wider a block, the more total time it took. Hover for total, per tick or frame, share of all, self time and calls. Click a block to zoom in and use the path at the top to go back. **Highlight a name** marks every block that matches a resource or file and shows how much time the matches take in total.

### Compare

Did your fix work? The loaded file is **before**. Drop a second recording taken after your change, ideally the same length and in the same spot, on **Drop the "after" profile**. You get:

- A summary: scripts per tick or frame (average and 99th percentile), worst tick or frame, average FPS on the client and hitches per minute, each marked better, worse or about the same.
- **Per resource, average ms per tick / frame** with before, after and the change. Green means less time after the change.

Numbers are per tick or frame, so two recordings of different length still line up. A warning shows if you compare a client recording with a server one. **Remove after** clears the second file.

## Budgets

| | Client | Server |
| --- | --- | --- |
| Frame or tick | 16.7 ms at 60 FPS | 50 ms (20 ticks per second) |
| Scripts get heavy at | 4 ms per frame | 25 ms per tick |
| Hitch | a frame over 50 ms | ticks over 150 ms apart (the console's hitch warning) |
| One resource worth a look | 0.1 ms per frame, heavy from 0.5 ms | 0.5 ms per tick, heavy from 2 ms |

**Script time** is what resources spent in a frame or tick. **Frame time** and **tick interval** cover everything. When the gap is big but script time is small, the stall came from somewhere else, and the findings say so instead of blaming a script.

## Examples

The most common finding is a loop that runs every frame even when it has nothing to do:

```lua title="client.lua"
-- runs every frame, all the time
CreateThread(function()
    while true do
        Wait(0)
        local coords = GetEntityCoords(PlayerPedId())
        if #(coords - shopCoords) < 2.0 then
            DrawMarker(2, shopCoords.x, shopCoords.y, shopCoords.z, 0, 0, 0, 0, 0, 0, 0.3, 0.3, 0.3, 255, 255, 255, 150, false, true, 2, false, nil, nil, false)
        end
    end
end)
```

```lua title="client.lua"
-- only runs every frame while the player is close
CreateThread(function()
    while true do
        local sleep = 1000
        local coords = GetEntityCoords(PlayerPedId())
        if #(coords - shopCoords) < 10.0 then
            sleep = 0
            DrawMarker(2, shopCoords.x, shopCoords.y, shopCoords.z, 0, 0, 0, 0, 0, 0, 0.3, 0.3, 0.3, 255, 255, 255, 150, false, true, 2, false, nil, nil, false)
        end
        Wait(sleep)
    end
end)
```

Record again after a change like this and drop both files into **Compare**.

## Tips

- Record on the side where the problem is. Client FPS drops need a client recording; server hitch warnings need a server one.
- Record for long enough to catch the problem. A short recording gets a **Short recording** note.
- For a resource that shows as heavy but has no named threads, run `profiler resource <name> 300` for function-level detail.
- The profiler adds a little overhead itself, so absolute numbers run slightly high. Compare recordings made the same way.

## Limitations

- The file never leaves your browser. There is no upload and no shared leaderboard.
- Only Lua threads and handlers get names. JS and C# resources show up as a total per resource.
- Files from `profiler save` (binary) cannot be read; use `profiler saveJSON`.
- The sample file is synthetic, not a real server.

## Related tools

- [Server Status Checker](/docs/tools/server-status-checker) to check the server from outside while you debug.
- [FXServer artifacts](/docs/tools/artifacts) in case the hitches come from a known broken server build.
- [fxmanifest Generator](/docs/tools/fxmanifest-generator) when a finding points at how a resource is set up.
