Benchmark panel

Frames per second, static memory and node count sampled every frame into three live graphs, while a wave of nodes is added and removed.

The editor above is running this project. Change a line and it reloads.

Godot classes used

PerformanceControl._drawCanvasItem.draw_polyline

The code

scripts/main.gd
extends Node

const CHURN: int = 40

const MONITORS: Array = [
	{monitor = Performance.TIME_FPS, label = 'frames per second', format = '%.0f', scale = 1.0, tint = '#8eef97'},
	{monitor = Performance.MEMORY_STATIC, label = 'static memory', format = '%.2f MB', scale = 1.0 / 1048576.0, tint = '#f3d48d'},
	{monitor = Performance.OBJECT_NODE_COUNT, label = 'nodes in the tree', format = '%.0f', scale = 1.0, tint = '#8da5f3'},
]

var rows: Array = []
var ballast: Node
var elapsed: float = 0.0
var manual: int = 0
var status: Label


func _ready() -> void:
	var column: VBoxContainer = _root()

	var title: Label = Label.new()
	title.text = 'Performance, read while it happens'
	title.add_theme_font_size_override('font_size', 28)
	column.add_child(title)

	var hint: Label = Label.new()
	hint.text = 'Performance.get_monitor returns a single float. One sample per frame is already a graph.'
	hint.add_theme_color_override('font_color', Color('#8a90a0'))
	column.add_child(hint)

	var graph_script: GDScript = Get.script('graph')

	for entry in MONITORS:
		column.add_child(_row(entry, graph_script))

	ballast = Node.new()
	add_child(ballast)

	var button: Button = Button.new()
	button.text = 'add 400 more nodes'
	button.pressed.connect(_pile_up)
	column.add_child(button)

	status = Label.new()
	status.add_theme_color_override('font_color', Color('#8eef97'))
	status.text = 'a wave of nodes is being added and removed on its own'
	column.add_child(status)

	print('the node count breathes by itself, the button piles 400 more on top')


func _process(delta: float) -> void:
	elapsed += delta
	_churn()

	for row in rows:
		var value: float = Performance.get_monitor(row.entry.monitor) * row.entry.scale
		row.value.text = row.entry.format % value
		row.graph.push(value)


func _churn() -> void:
	var target: int = manual + int(500.0 + 460.0 * sin(elapsed * 3.4))
	var have: int = ballast.get_child_count()

	if have < target:
		for i in mini(CHURN, target - have):
			ballast.add_child(Node.new())
	elif have > target:
		for i in mini(CHURN, have - target):
			var spare: Node = ballast.get_child(0)
			ballast.remove_child(spare)
			spare.free()


func _pile_up() -> void:
	manual += 400
	status.text = '%d extra nodes pinned on top of the wave' % manual


func _row(entry: Dictionary, graph_script: GDScript) -> Control:
	var line: HBoxContainer = HBoxContainer.new()
	line.add_theme_constant_override('separation', 20)

	var caption: VBoxContainer = VBoxContainer.new()
	caption.custom_minimum_size = Vector2(220, 0)
	line.add_child(caption)

	var name_label: Label = Label.new()
	name_label.text = entry.label
	name_label.add_theme_color_override('font_color', Color('#8a90a0'))
	caption.add_child(name_label)

	var value: Label = Label.new()
	value.text = '0'
	value.add_theme_font_size_override('font_size', 30)
	value.add_theme_color_override('font_color', Color(entry.tint))
	caption.add_child(value)

	var graph: Control = Control.new()
	graph.set_script(graph_script)
	graph.custom_minimum_size = Vector2(880, 108)
	line.add_child(graph)
	graph.setup(Color(entry.tint))

	rows.append({entry = entry, value = value, graph = graph})

	return line


func _root() -> VBoxContainer:
	var background: ColorRect = ColorRect.new()
	background.color = Color('#12141a')
	add_child(background)
	background.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)

	var margin: MarginContainer = MarginContainer.new()

	for side in ['margin_left', 'margin_right', 'margin_top', 'margin_bottom']:
		margin.add_theme_constant_override(side, 40)

	add_child(margin)
	margin.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)

	var column: VBoxContainer = VBoxContainer.new()
	column.add_theme_constant_override('separation', 12)
	margin.add_child(column)

	return column
scripts/graph.gd
extends Control

const CAP: int = 90

var samples: PackedFloat32Array = PackedFloat32Array()
var tint: Color = Color('#8da5f3')


func setup(color: Color) -> void:
	tint = color


func push(value: float) -> void:
	samples.append(value)

	if samples.size() > CAP:
		samples.remove_at(0)

	queue_redraw()


func _draw() -> void:
	draw_rect(Rect2(Vector2.ZERO, size), Color('#12141a'))
	draw_line(Vector2(0, size.y * 0.5), Vector2(size.x, size.y * 0.5), Color('#2a2f3d'), 1.0)

	if samples.size() < 2:
		return

	var low: float = samples[0]
	var high: float = samples[0]

	for value in samples:
		low = minf(low, value)
		high = maxf(high, value)

	var span: float = high - low

	# a monitor that never moves would divide by zero and pin the line to the floor
	if span < 0.001:
		low -= 0.5
		span = 1.0

	var points: PackedVector2Array = PackedVector2Array()

	for i in samples.size():
		var x: float = size.x * float(i) / float(CAP - 1)
		var y: float = size.y - 6.0 - (samples[i] - low) / span * (size.y - 12.0)
		points.append(Vector2(x, y))

	draw_polyline(points, tint, 2.0, true)

More Data examples