Hello Creators,
Recently we have been working on improving ScriptProfiler. We have made many improvements that are available today, as well as several features coming soon.
Available Now
Display total wall-clock duration of the completed profiling session
The total wall-clock duration of the session is now displayed at the top of the window in hours:minutes:seconds.milliseconds
format.
Focus on subtree
Right-clicking on a node in the Callgraph view sets the focus on that node, displaying only itself and its child nodes. Right-clicking the same node again will reset the view, displaying the contents of the <root>
node again.
Higher Sampling Frequency
The Freq: N KHz
button toggles the profiler between recording samples at 1KHz, or 1000 times per second, and 10KHz. This higher sampling mode enables greater time precision at the cost of some performance.
Functions View
A flat view of functions is now available, listing all functions recorded during the session.
Live Update
The Live
checkbox enables polling and refreshing of profiling data continuously, updating the view with new data each second.
Searching
The search bar is now functional, hiding any parts of the callgraph that do not lead to a node containing the name typed in the search field.
Averaging
The Average
button toggles displaying timing values on an average of 1 second, 1 minute, 5 minutes, or 10 minutes based on the wall-clock duration of the entire session. Averaging using an option that is longer than the session duration will extrapolate timing values. For example, for a profiling session of 1 minute in length, averaging using the 5-minutes option will display timing values at 5x their recorded values.
Timed Profiling
The Time
button enables automatically stopping the profiler after a set duration. A countdown timer, with the remaining time in the session, is displayed during profiling. Clicking Stop
at any time will cancel the timer, and stop the profiler as usual.
Coming Soon
Export profiler data
The Export
view enables saving the profiling data displayed in DevConsole to the Roblox Logs directory. Profiling data is encoded as a JSON string, the structure of which is described below.
Profiling Data Format
Example JSON Blob
{
"Version":2,
"SessionStartTime":1704850750514,
"SessionEndTime":1704850751198,
"Categories":
[
{"Name":"Parallel Luau","NodeId":4},
{"Name":"Heartbeat","NodeId":1}
],
"Nodes":
[
{"TotalDuration":2530,"FunctionIds":[1],"NodeIds":[2]},
{"TotalDuration":2530,"FunctionIds":[2,5],"NodeIds":[3,7]},
{"TotalDuration":1267},
{"TotalDuration":7746,"FunctionIds":[3],"NodeIds":[5]},
{"TotalDuration":7746,"FunctionIds":[4],"NodeIds":[6]},
{"TotalDuration":7746},
{"TotalDuration":1263,"FunctionIds":[6],"NodeIds":[8]},
{"TotalDuration":1263,"FunctionIds":[7],"NodeIds":[9]},
{"TotalDuration":1263,"FunctionIds":[8],"NodeIds":[10]},
{"TotalDuration":1263}
],
"Functions":
[
{"Name":"main","TotalDuration":2530},
{"Source":"builtin_ManageCollaborators.rbxm.ManageCollaborators.Packages._Index.roblox_rodux-3.0.0.rodux.Store","Line":81,"TotalDuration":1267},
{"Name":"Script","TotalDuration":7746},
{"Source":"Workspace.Actor.Script","Line":1,"TotalDuration":7746},
{"Source":"builtin_DeveloperInspector.rbxm.DeveloperInspector.Packages._Index.DeveloperFramework.DeveloperFramework.UI.Components.Grid","Line":221,"TotalDuration":1263},
{"Source":"builtin_DeveloperInspector.rbxm.DeveloperInspector.Packages._Index.DeveloperFramework.DeveloperFramework.UI.Components.Grid","Name":"_update","Line":236,"TotalDuration":1263},
{"Source":"builtin_DeveloperInspector.rbxm.DeveloperInspector.Packages._Index.DeveloperFramework.DeveloperFramework.UI.Components.Grid","Name":"_getRange","Line":277,"TotalDuration":1263},
{"Source":"[C]","Name":"ScrollingFrame.CanvasPosition","TotalDuration":1263}
]
}
The Version
field tracks the major version number of the format. This value is currently 2. The format is subject to change with new, optional fields that may be added at any time, but we expect these changes to remain compatible with tooling that adopts this format today.
A NodeId
is a unique identifier for a given node. This identifier is a 1-based index into the Nodes
array. Thus, to lookup the Node with NodeId 123, one can simply retrieve the 123rd element in Nodes
.
Similarly, a FunctionId
identifies a given function descriptor in the Functions
array by indexing Functions
by the FunctionId
.
A Duration
is an integer representing an amount of time in profiler ticks. Currently, each tick is equivalent to 1 microsecond. Thus, to convert a duration to seconds, it is simply Duration / 1e6
.
SessionStartTime
and SessionEndTime
are timestamps in milliseconds since the Unix epoch.
Each object in the Categories
array contains a Name
field and a NodeId
field. Each category is representative of each of the top-level categories displayed in ScriptProfiler. The associated NodeId maps to a top-level node in the callgraph with child nodes that refer to each of the functions and nodes that executed during that category’s part of the frame.
Each entry in Nodes
represents a function executing at a particular point in the callgraph. Nodes will always have a TotalDuration
field, describing the total time spent in that node, including time spent in any child nodes. A node may optionally have a FunctionIds
array and a NodeIds
array. Each entry in these arrays corresponds to a callee of the current node. For any given child of a node, the i-th index of each array corresponds to the function descriptor of the child in Functions
, and the node descriptor of the child in Nodes
. If one of these arrays are present, then the other is also present; additionally, the number of entries in each array will always match.
Each entry in Functions
stores auxiliary information shared amongst nodes that describe source-location, name, line number, and the total aggregate time spent in the function, as well as additional flags. A function entry will always have a TotalDuration
field. Source
, Name
, and Line
are optional; they are only present when the profiler has access to the corresponding information.
A function may also have a Flags
integer. This is a bitfield with the following potential bits:
0 | IsNative; if set, this function was executed via NativeCodeGen |
---|---|
1 | IsPlugin; if set, this function (and its descendants) was executed as part of a plugin |
Flags
uniquely identify a function. There may more than one function entry for the same exact function if one Lua thread had executed that function under NativeCodeGen, and another that had executed the function via the LuauVM interpreter.
Hide GC Overhead and Plugin Scripts
We will additionally have checkboxes that toggle hiding time spent in garbage collection, and time spent in plugin scripts. Timings displayed across the callgraph will adjust accordingly.
Coming Later
We are planning to make available a public API for plugin tools to control the profiler and access profiling data. We will have more details on this API in the near future.
Feedback
We are continuously considering feedback and requests to improve ScriptProfiler. Feel free to suggest improvements and report issues in the thread below.