Skip to content

Instantly share code, notes, and snippets.

@RealEthanPlayzDev
Last active April 26, 2023 05:41
Show Gist options
  • Save RealEthanPlayzDev/9bb9786a47d4551ba7ab25dff2e83233 to your computer and use it in GitHub Desktop.
Save RealEthanPlayzDev/9bb9786a47d4551ba7ab25dff2e83233 to your computer and use it in GitHub Desktop.
Client statistics stuff (Roblox)
--!strict
--[[
File name: ClientStatisticsProvider.luau
Author: RadiatedExodus (RealEthanPlayzDev)
Created at: April 26, 2023
Module for providing various client statistics related information
--]]
--// Types
export type InfoSection = { [string]: string | number }
export type Info = { [string]: InfoSection }
export type InfoOrder = { string }
--// Services
local serv = {
RunService = game:GetService("RunService");
Stats = game:GetService("Stats");
Players = game:GetService("Players");
}
--// Variables
--// The player
local Player = serv.Players.LocalPlayer
--// Default ordered InfoOrder array
local DefaultOrderedInfoOrder: InfoOrder = {
"== Framerate ==";
"RenderFPS";
"PhysicsFPS";
"";
"== Memory ==";
"Memory";
"LuaMemory";
"GraphicsMemory";
"PhysicsMemory";
"";
"== Scheduler ==";
"HeartbeatTimeMs";
"PhysicsStepTimeMs";
"";
"== Instances ==";
"InstanceCount";
"PrimitivesCount";
"";
"== Network ==";
"Ping";
"TotalSendKbps";
"TotalReceiveKbps";
"DataSendKbps";
"DataReceiveKbps";
"PhysicsSendKbps";
"PhysicsReceiveKbps";
}
table.freeze(DefaultOrderedInfoOrder)
--// Memory tag categorization
local LuaMemoryTags = {
Enum.DeveloperMemoryTag.LuaHeap;
Enum.DeveloperMemoryTag.Script;
Enum.DeveloperMemoryTag.Signals;
}
local GraphicsMemoryTags = {
Enum.DeveloperMemoryTag.GraphicsParts;
Enum.DeveloperMemoryTag.GraphicsTerrain;
Enum.DeveloperMemoryTag.GraphicsTexture;
Enum.DeveloperMemoryTag.GraphicsMeshParts;
Enum.DeveloperMemoryTag.GraphicsParticles;
Enum.DeveloperMemoryTag.GraphicsSolidModels;
Enum.DeveloperMemoryTag.GraphicsSpatialHash;
Enum.DeveloperMemoryTag.GraphicsTextureCharacter;
Enum.DeveloperMemoryTag.Gui;
}
local PhysicsMemoryTags = {
Enum.DeveloperMemoryTag.PhysicsCollision;
Enum.DeveloperMemoryTag.PhysicsParts;
Enum.DeveloperMemoryTag.GeometryCSG;
}
--// Render framerate variables
local RenderFPS = 0
local RenderStart = os.clock()
local RenderLastIteration = 0
local RenderFrameUpdates = {} :: any
local RenderUpdateConnection: RBXScriptConnection?
--// Number conversion functions
local function ConvertKbpsString(float: number) return `{string.format("%.2f", float)} kb/s` end
local function ConvertMsString(float: number) return `{string.format("%.3f", float)} ms` end
local function ConvertMbString(float: number) return `{string.format("%.2f", float)} mb` end
--// Stdout functions
local function CreateDivider(name: string)
local Left = string.rep("=", 10 - #name / 2 - #name % 2)
local Right = string.rep("=", 10 - #name /2)
return `### {Left} {name} {Right} ###`
end
--// Static class ClientStatisticsProvider
local ClientStatisticsProvider = {}
ClientStatisticsProvider.DefaultOrderedInfoOrder = DefaultOrderedInfoOrder
function ClientStatisticsProvider.ToggleRenderFPSUpdate(enabled: boolean?): boolean
local Enabled = if (typeof(enabled) == "boolean") then enabled else (not (typeof(RenderUpdateConnection) == "RBXScriptConnection"))
if Enabled then
RenderUpdateConnection = serv.RunService.Heartbeat:Connect(function(deltaTime: number)
RenderLastIteration = os.clock()
for Index = #RenderFrameUpdates, 1, -1 do
RenderFrameUpdates[Index + 1] = if RenderFrameUpdates[Index] >= RenderLastIteration - 1 then RenderFrameUpdates[Index] else nil
end
RenderFrameUpdates[1] = RenderLastIteration
RenderFPS = math.floor(if os.clock() - RenderStart >= 1 then #RenderFrameUpdates else #RenderFrameUpdates / (os.clock() - RenderStart))
end)
else
if RenderUpdateConnection then
RenderUpdateConnection:Disconnect()
RenderUpdateConnection = nil
end
end
return Enabled
end
function ClientStatisticsProvider.CompileInfo(nosections: boolean?): Info | InfoSection
local LuaMemory do
LuaMemory = 0
for _, tag in ipairs(LuaMemoryTags) do
LuaMemory += serv.Stats:GetMemoryUsageMbForTag(tag)
end
end
local GraphicsMemory do
GraphicsMemory = 0
for _, tag in ipairs(GraphicsMemoryTags) do
GraphicsMemory += serv.Stats:GetMemoryUsageMbForTag(tag)
end
end
local PhysicsMemory do
PhysicsMemory = 0
for _, tag in ipairs(PhysicsMemoryTags) do
PhysicsMemory += serv.Stats:GetMemoryUsageMbForTag(tag)
end
end
local Info: Info = {
FPS = {
RenderFPS = `{RenderFPS} fps`;
PhysicsFPS = `{workspace:GetRealPhysicsFPS()} fps`;
},
Scheduler = {
HeartbeatTimeMs = ConvertMsString(serv.Stats.HeartbeatTimeMs);
PhysicsStepTimeMs = ConvertMsString(serv.Stats.PhysicsStepTimeMs);
},
Network = {
Ping = `{Player:GetNetworkPing()} ms`;
DataSendKbps = ConvertKbpsString(serv.Stats.DataSendKbps);
DataReceiveKbps = ConvertKbpsString(serv.Stats.DataReceiveKbps);
PhysicsSendKbps = ConvertKbpsString(serv.Stats.PhysicsSendKbps);
PhysicsReceiveKbps = ConvertKbpsString(serv.Stats.PhysicsReceiveKbps);
TotalSendKbps = ConvertKbpsString(serv.Stats.DataSendKbps + serv.Stats.PhysicsSendKbps);
TotalReceiveKbps = ConvertKbpsString(serv.Stats.DataReceiveKbps + serv.Stats.PhysicsReceiveKbps);
},
Memory = {
Memory = ConvertMbString(serv.Stats:GetTotalMemoryUsageMb());
LuaMemory = ConvertMbString(LuaMemory);
GraphicsMemory = ConvertMbString(GraphicsMemory);
PhysicsMemory = ConvertMbString(PhysicsMemory);
},
Misc = {
InstanceCount = serv.Stats.InstanceCount;
PrimitivesCount = serv.Stats.PrimitivesCount;
}
}
local FinalInfo: Info | InfoSection = Info :: Info
if nosections then
FinalInfo = {} :: InfoSection
for _, section in Info :: Info do
for name, value in section do
(FinalInfo :: any)[name] = value
end
end
end
return FinalInfo
end
function ClientStatisticsProvider.CompileInfoToString(info: Info?, nosections: boolean?): string
local info = info or ClientStatisticsProvider.CompileInfo(false)
local FinalStr = ""
for section, data in pairs(info) do
if (not nosections) then
FinalStr ..= CreateDivider(section).."\n"
end
for name, value in data do
FinalStr ..= if nosections then `{name}: {value}\n` else ` {name}: {value}\n`
end
if not (nosections) then
FinalStr ..= "\n"
end
end
return FinalStr
end
function ClientStatisticsProvider.CompileInfoToStringOrdered(order: InfoOrder?, infonodivider: InfoSection?): string
local order = order or DefaultOrderedInfoOrder
local infonodivider = infonodivider or ClientStatisticsProvider.CompileInfo(true) :: InfoSection
local FinalStr = ""
for _, order in ipairs(order) do
if infonodivider[order] then
FinalStr ..= `{order}: {infonodivider[order]}\n`
else
FinalStr ..= `{order}\n`
end
end
return FinalStr
end
return table.freeze(ClientStatisticsProvider)
--[[
File name: Statistics.client.luau
Author: RadiatedExodus (RealEthanPlayzDev)
Created at: March 18, 2023
--]]
local Stats = game:GetService("Stats")
local RunService = game:GetService("RunService")
local Player = game:GetService("Players").LocalPlayer
local OrderedInfoOrder = {
"== Framerate ==";
"RenderFPS";
"PhysicsFPS";
"";
"== Memory ==";
"Memory";
"LuaMemory";
"GraphicsMemory";
"PhysicsMemory";
"";
"== Scheduler ==";
"HeartbeatTimeMs";
"PhysicsStepTimeMs";
"";
"== Instances ==";
"InstanceCount";
"PrimitivesCount";
"";
"== Network ==";
"Ping";
"TotalSendKbps";
"TotalReceiveKbps";
"DataSendKbps";
"DataReceiveKbps";
"PhysicsSendKbps";
"PhysicsReceiveKbps";
}
local LuaMemoryTags = {
Enum.DeveloperMemoryTag.LuaHeap;
Enum.DeveloperMemoryTag.Script;
Enum.DeveloperMemoryTag.Signals;
}
local GraphicsMemoryTags = {
Enum.DeveloperMemoryTag.GraphicsParts;
Enum.DeveloperMemoryTag.GraphicsTerrain;
Enum.DeveloperMemoryTag.GraphicsTexture;
Enum.DeveloperMemoryTag.GraphicsMeshParts;
Enum.DeveloperMemoryTag.GraphicsParticles;
Enum.DeveloperMemoryTag.GraphicsSolidModels;
Enum.DeveloperMemoryTag.GraphicsSpatialHash;
Enum.DeveloperMemoryTag.GraphicsTextureCharacter;
Enum.DeveloperMemoryTag.Gui;
}
local PhysicsMemoryTags = {
Enum.DeveloperMemoryTag.PhysicsCollision;
Enum.DeveloperMemoryTag.PhysicsParts;
Enum.DeveloperMemoryTag.GeometryCSG;
}
local RenderFPS = 0
local Start = os.clock()
local LastIteration = 0
local FrameUpdates = {}
RunService.Heartbeat:Connect(function(deltaTime: number)
LastIteration = os.clock()
for Index = #FrameUpdates, 1, -1 do
FrameUpdates[Index + 1] = FrameUpdates[Index] >= LastIteration - 1 and FrameUpdates[Index] or nil
end
FrameUpdates[1] = LastIteration
RenderFPS = math.floor(if os.clock() - Start >= 1 then #FrameUpdates else #FrameUpdates / (os.clock() - Start))
end)
local function ConvertKbpsString(float: number)
return `{string.format("%.2f", float)} kb/s`
end
local function ConvertMsString(float: number)
return `{string.format("%.3f", float)} ms`
end
local function ConvertMbString(float: number)
return `{string.format("%.2f", float)} mb`
end
local function CompileInfo(nodividers: boolean?)
local LuaMemory do
LuaMemory = 0
for _, tag in ipairs(LuaMemoryTags) do
LuaMemory += Stats:GetMemoryUsageMbForTag(tag)
end
end
local GraphicsMemory do
GraphicsMemory = 0
for _, tag in ipairs(GraphicsMemoryTags) do
GraphicsMemory += Stats:GetMemoryUsageMbForTag(tag)
end
end
local PhysicsMemory do
PhysicsMemory = 0
for _, tag in ipairs(PhysicsMemoryTags) do
PhysicsMemory += Stats:GetMemoryUsageMbForTag(tag)
end
end
local Info = {
FPS = {
RenderFPS = `{RenderFPS} fps`;
PhysicsFPS = `{workspace:GetRealPhysicsFPS()} fps`;
},
Scheduler = {
HeartbeatTimeMs = ConvertMsString(Stats.HeartbeatTimeMs);
PhysicsStepTimeMs = ConvertMsString(Stats.PhysicsStepTimeMs);
},
Network = {
Ping = `{Player:GetNetworkPing()} ms`;
DataSendKbps = ConvertKbpsString(Stats.DataSendKbps);
DataReceiveKbps = ConvertKbpsString(Stats.DataReceiveKbps);
PhysicsSendKbps = ConvertKbpsString(Stats.PhysicsSendKbps);
PhysicsReceiveKbps = ConvertKbpsString(Stats.PhysicsReceiveKbps);
TotalSendKbps = ConvertKbpsString(Stats.DataSendKbps + Stats.PhysicsSendKbps);
TotalReceiveKbps = ConvertKbpsString(Stats.DataReceiveKbps + Stats.PhysicsReceiveKbps);
},
Memory = {
Memory = ConvertMbString(Stats:GetTotalMemoryUsageMb());
LuaMemory = ConvertMbString(LuaMemory);
GraphicsMemory = ConvertMbString(GraphicsMemory);
PhysicsMemory = ConvertMbString(PhysicsMemory);
},
Misc = {
InstanceCount = Stats.InstanceCount;
PrimitivesCount = Stats.PrimitivesCount;
}
}
local FinalInfo = Info
if nodividers then
FinalInfo = {}
for _, section in Info do
for name, value in section do
FinalInfo[name] = value
end
end
end
return FinalInfo
end
local function CreateDivider(name: string)
local Left = string.rep("=", 10 - #name / 2 - #name % 2)
local Right = string.rep("=", 10 - #name /2)
return `### {Left} {name} {Right} ###`
end
local function CompileInfoToString(info: { [string]: { [string]: any } }, nodivider: boolean?)
local FinalStr = ""
for section, data in info do
if (not nodivider) then
FinalStr ..= CreateDivider(section).."\n"
end
for name, value in data do
FinalStr ..= if nodivider then `{name}: {value}\n` else ` {name}: {value}\n`
end
if not (nodivider) then
FinalStr ..= "\n"
end
end
return FinalStr
end
local function CompileInfoToStringOrdered(infonodivider: { [string]: { [string]: any } }, order: {string})
local FinalStr = ""
for _, order in ipairs(order) do
if infonodivider[order] then
FinalStr ..= `{order}: {infonodivider[order]}\n`
else
FinalStr ..= `{order}\n`
end
end
return FinalStr
end
while true do
--// print("\n"..CompileInfoToString(CompileInfo(), true))
print("\n"..CompileInfoToStringOrdered(CompileInfo(true), OrderedInfoOrder))
task.wait(1)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment