You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
<divalign="center">Remote packet analyzer tool for Roblox</div>
7
+
<divalign="center">Extensible replication profiler for Roblox</div>
8
8
9
9
<div> </div>
10
10
11
11
## Introduction
12
12
13
-
Packet Profiler is a plugin which allows you to accurately read remote data sent by different contexts. Unlike the vague and uninformative Stats windows which only show the current KB/s receive and send rates, this plugin allows you to accurately see packet data each frame, along with precise byte size information.
13
+
Packet Profiler shows the volume and contents of replication traffic frame by frame. Remote traffic is included out of the box, while a public layer API lets each game add its own sources, such as instance replication, custom networking libraries, state synchronization, or domain-specific metrics.
14
+
15
+
When addons register more traffic sources, the timeline can show all layers together or isolate any individual layer without discarding the combined capture. The view selector stays hidden when remotes are the only registered layer. Selecting a frame opens a breakdown of the calls recorded for the active view.
16
+
17
+
The **Top spikes** view retains the largest frames from the full profiler session, even after they leave the rolling timeline. It can show the top 5, 10, or 20 frames for the combined view or any registered layer. The details widget becomes a largest-first spike inspector with comparable magnitude bars, contributor summaries, expandable packet data, bulk expand/collapse, and a frozen ranking mode for reading while capture continues.
18
+
19
+
The rolling timeline uses timestamped 60 Hz buckets, so its 256 bars consistently cover about 4.27 seconds. When rendering slows down, the capture clock commits every elapsed bucket instead of collapsing them into one rendered frame. Extensions can read the bucket duration with `GetFrameInterval()`.
20
+
21
+
The interface is rendered with Vide. The timeline is a mirrored cyclic strip: each commit writes one persistent bar slot and shifts the strip, so existing heights remain untouched until their frames genuinely leave the 256-frame window. This avoids full-chart reconciliation and render-frame polling.
@@ -24,6 +32,84 @@ This BindableEvent may also be used to log RemoteEvents that have been created a
24
32
Some games may rename their RemoteEvents for network & encoding purposes (such as those that only use 1 RemoteEvent for everything). You can rename RemoteEvents by adding a `RemoteName.profiler` ModuleScript anywhere in ReplicatedStorage, whose return must be a function. This function will be called with two arguments: the invoked RemoteEvent, and the RemoteEvent's first argument. The function must return a string, which will be used as the RemoteEvent's name in the profiler.
25
33
**NOTE**: adding this will cause the profiler to use the module to rename all remote events, so make sure to return the original name if you don't want to rename a specific RemoteEvent.
26
34
35
+
## Extension API
36
+
37
+
Requiring the in-game package returns the running profiler. In a Studio client with the PacketProfiler plugin installed, it instead returns a bridge to the plugin so game-defined layers appear in the plugin window rather than opening a second profiler UI. The installed plugin connects its Edit and Play client instances through `PluginConnectionService`; no server relay or script-injection permission is required.
38
+
39
+
Layers describe a source of traffic and receive an `Emit` function. The value returned by `Start` is used as the layer's cleanup function.
40
+
41
+
```luau
42
+
local ReplicatedStorage = game:GetService("ReplicatedStorage")
43
+
local PacketProfiler = require(ReplicatedStorage.Packages.PacketProfiler)
44
+
45
+
local registration = PacketProfiler:RegisterLayer({
46
+
Id = "instance-audit",
47
+
Name = "Instance audit",
48
+
Color = Color3.fromHex("#B98CFF"),
49
+
Description = "Replicated instance creation",
50
+
51
+
Start = function(context)
52
+
local connection = workspace.DescendantAdded:Connect(function(instance)
53
+
context.Emit({
54
+
Name = instance.ClassName,
55
+
Size = 24,
56
+
Data = {
57
+
Path = instance:GetFullName(),
58
+
ClassName = instance.ClassName,
59
+
},
60
+
Metadata = {
61
+
Instance = instance,
62
+
},
63
+
})
64
+
end)
65
+
66
+
return function()
67
+
connection:Disconnect()
68
+
end
69
+
end,
70
+
})
71
+
72
+
-- Remove the layer and stop its capture adapter later.
73
+
registration:Disconnect()
74
+
```
75
+
76
+
Every packet needs a display `Name` and non-negative byte `Size`. `Data`, `RawData`, `Metadata`, and `Timestamp` are optional. Metadata is deliberately layer-defined so addons can retain references or structured context without changing the profiler core.
77
+
78
+
Studio-bridged descriptors and packets are serialized across a `PluginConnection`, so keep their fields to primitives, tables, Roblox vectors and colors, enum items, and Instances. Functions remain client-side in `Start` and are never sent to the plugin. Set `IncludeInAll = false` for a count-based or non-byte layer that should have its own view without changing the combined byte graph. `EntryName`, `Unit`, `DefaultMaxFrameValue`, and `ScaleValues` customize that view's labels and scale.
79
+
80
+
### Hooks and addons
81
+
82
+
Hooks can observe or transform traffic without owning a layer:
83
+
84
+
```luau
85
+
local hook = PacketProfiler:RegisterHook("BeforeRecord", function(packet)
86
+
if packet.Metadata and packet.Metadata.IgnoreInProfiler then
87
+
return false
88
+
end
89
+
90
+
return packet
91
+
end)
92
+
```
93
+
94
+
Supported stages are `BeforeRecord`, `AfterRecord`, and `FrameCommitted`. Returning `false` from `BeforeRecord` drops a packet; returning a table replaces it. Addons can group several layers and hooks behind one lifecycle:
95
+
96
+
```luau
97
+
local addon = PacketProfiler:RegisterAddon({
98
+
Id = "my-network-suite",
99
+
Install = function(profiler)
100
+
local layer = profiler:RegisterLayer(MyLayer)
101
+
local hook = profiler:RegisterHook("AfterRecord", MyObserver)
102
+
103
+
return function()
104
+
layer:Disconnect()
105
+
hook:Disconnect()
106
+
end
107
+
end,
108
+
})
109
+
```
110
+
111
+
The Studio bridge exposes `RegisterLayer`, `RegisterHook`, `Record`, `RegisterAddon`, and `Destroy`. Its `BeforeRecord` and `AfterRecord` hooks run in the game client before packets are batched to the plugin. The full in-game profiler additionally exposes `SetView`, `SetPaused`, `SelectFrame`, `GetLayer`, `GetLayers`, `GetFramePackets`, `GetTopFrames`, frame hooks, and UI signals; view and frame state remain owned by the plugin. `GetTopFrames(view, limit)` returns ranked `{ Frame, Size }` entries retained from the current session.
112
+
27
113
## In-game profiling
28
114
Sometimes, simply profiling in studio may not be enough. As such, you can use this plugin **in-game**! Simply grab the latest release `.rbxm` file and place it inside `StarterPlayerScripts`. You can then open the UI through `Ctrl + F5`, and pause/unpause through `Ctrl + P`.
0 commit comments