Last active
April 26, 2023 05:41
-
-
Save RealEthanPlayzDev/9bb9786a47d4551ba7ab25dff2e83233 to your computer and use it in GitHub Desktop.
Client statistics stuff (Roblox)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--!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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ | |
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