Skip to content

Commit

Permalink
fix(PerformanceManager): split into multiple classes
Browse files Browse the repository at this point in the history
  • Loading branch information
ShadowApex committed Jan 13, 2024
1 parent d2f36ca commit 56816a6
Show file tree
Hide file tree
Showing 13 changed files with 1,045 additions and 538 deletions.
230 changes: 230 additions & 0 deletions core/systems/hardware/cpu.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
extends Resource
class_name CPU

## Read and manage the system CPU

## Emitted when CPU boost is updated
signal boost_updated(enabled: bool)
## Emitted when SMT is updated
signal smt_updated(enabled: bool)

const POWERTOOLS_PATH := "/usr/share/opengamepadui/scripts/powertools"
const BOOST_PATH := "/sys/devices/system/cpu/cpufreq/boost"
const SMT_PATH := "/sys/devices/system/cpu/smt/control"
const CPUS_PATH := "/sys/bus/cpu/devices"

var mutex := Mutex.new()
var core_count := get_total_core_count()
var boost_capable: bool = false
var boost_enabled := false:
get:
mutex.lock()
var prop := boost_enabled
mutex.unlock()
return prop
set(v):
if boost_enabled == v:
return
mutex.lock()
boost_enabled = v
mutex.unlock()
emit_signal.call_deferred("changed")
emit_signal.call_deferred("boost_updated", v)
var vendor: String
var model: String
var smt_capable: bool = false
var smt_enabled := false:
get:
mutex.lock()
var prop := smt_enabled
mutex.unlock()
return prop
set(v):
if smt_enabled == v:
return
mutex.lock()
smt_enabled = v
mutex.unlock()
emit_signal.call_deferred("changed")
emit_signal.call_deferred("smt_updated", v)
var logger := Log.get_logger("CPU")


func _init() -> void:
var cpu_raw := _get_lscpu_info()
for param in cpu_raw:
var parts := param.split(" ", false) as Array
if parts.is_empty():
continue
if parts[0] == "Flags:":
if "ht" in parts:
smt_capable = true
if "cpb" in parts and FileAccess.file_exists(BOOST_PATH):
boost_capable = true
if parts[0] == "Vendor" and parts[1] == "ID:":
# Delete parts of the string we don't want
parts.remove_at(1)
parts.remove_at(0)
vendor = str(" ".join(parts))
if parts[0] == "Model" and parts[1] == "name:":
# Delete parts of the string we don't want
parts.remove_at(1)
parts.remove_at(0)
model = str(" ".join(parts))
# TODO: We can get min/max CPU freq here.
update()


## Returns the count of number of enabled CPU cores
func get_enabled_core_count() -> int:
return OS.get_processor_count()


## Returns true if boost is currently enabled
func get_boost_enabled() -> bool:
if not boost_capable:
return false
var value := _get_property(BOOST_PATH).to_lower().strip_edges()

return value == "on" or value == "1"


## Set CPU boost to the given value
func set_boost_enabled(enabled: bool) -> int:
if not boost_capable:
return -1
var enabled_str := "1" if enabled else "0"
var args := ["cpuBoost", enabled_str]
var cmd := CommandSync.new(POWERTOOLS_PATH, args)
if cmd.execute() != OK:
logger.warn("Failed to set CPU boost")
return cmd.code
update()

return cmd.code


## Returns true if SMT is currently enabled
func get_smt_enabled() -> bool:
if not smt_capable:
return false
var value := _get_property(SMT_PATH).to_lower().strip_edges()

return value == "on" or value == "1"


## Set CPU smt to the given value
func set_smt_enabled(enabled: bool) -> int:
if not smt_capable:
return -1
var enabled_str := "1" if enabled else "0"
var args := ["smtToggle", enabled_str]
var cmd := CommandSync.new(POWERTOOLS_PATH, args)
if cmd.execute() != OK:
logger.warn("Failed to set CPU smt")
return cmd.code
update()

return cmd.code


## Returns an instance of the given CPU core
func get_core(num: int) -> CPUCore:
# Try to load the core info if it already exists
var res_path := "hardware://cpu/" + str(num)
if ResourceLoader.exists(res_path):
var core := load(res_path) as CPUCore
core.update()
return core

# Create a new core instance and take over the caching path
var core := CPUCore.new(num)
core.take_over_path(res_path)
core.update()

return core


## Returns an array of all CPU cores
func get_cores() -> Array[CPUCore]:
var cores: Array[CPUCore] = []
for core_num in range(0, core_count):
var core := get_core(core_num)
if core:
cores.append(core)

return cores


## Returns the total number of detected CPU cores
func get_total_core_count() -> int:
var core_dirs := DirAccess.get_directories_at(CPUS_PATH)
if core_dirs.size() == 0:
logger.warn("Unable to determine total CPU count")
return 1
return core_dirs.size()


## Returns the total number of CPU cores that are online
func get_online_core_count() -> int:
var count := 0
for core in get_cores():
if not core.online:
continue
count += 1
return count


## Called to set the number of enabled CPU's
func set_cpu_core_count(value: int) -> void:
# Update the state of the CPU
update()
var cores := get_cores()
logger.debug("Enable cpu cores: " + str(value) + "/" + str(cores.size()))

for core in cores:
if core.num == 0:
continue
var online := true
if smt_enabled and core.num >= value:
online = false
elif not smt_enabled:
if core.num % 2 != 0 or core.num >= (value * 2) - 1:
online = false
logger.debug("Set CPU No: " + str(core.num) + " online: " + str(online))
core.set_online(online)


## Fetches the current CPU info
func update() -> void:
boost_enabled = get_boost_enabled()
smt_enabled = get_smt_enabled()


func _get_property(prop_path: String) -> String:
if not FileAccess.file_exists(prop_path):
return ""
var file := FileAccess.open(prop_path, FileAccess.READ)
var length := file.get_length()
var bytes := file.get_buffer(length)

return bytes.get_string_from_utf8()


## Provides info on the GPU vendor, model, and capabilities.
func _get_lscpu_info() -> PackedStringArray:
var cmd := CommandSync.new("lscpu")
if cmd.execute() != OK:
return []
return cmd.stdout.split("\n")


func _to_string() -> String:
return "<CPU:" \
+ " Vendor: (" + str(vendor) \
+ ") Model: (" + str(model) \
+ ") Core count: (" + str(core_count) \
+ ") Boost Capable: (" + str(boost_capable) \
+ ") SMT Capable: (" + str(smt_capable) \
+ ")>"

64 changes: 64 additions & 0 deletions core/systems/hardware/cpu_core.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
extends Resource
class_name CPUCore

## Instance of a single CPU core

var path: String
var num: int
var online: bool:
get:
var prop := online
return prop
set(v):
if online == v:
return
online = v
changed.emit()

func _init(n: int) -> void:
num = n
path = "/".join([CPU.CPUS_PATH, "cpu" + str(n)])


## Update the state of the CPU core
func update() -> void:
online = get_online()


## Returns whether or not the core is online
func get_online() -> bool:
if num == 0:
return true
var online_str := _get_property("online").strip_escapes()
return online_str == "1"


## Sets the online state of the CPU core
func set_online(enabled: bool) -> int:
var logger := Log.get_logger("CPUInfo")
if num == 0:
logger.warn("Unable to disable CPU 0")
return -1
var enabled_str := "1" if enabled else "0"
var cmd := CommandSync.new(CPU.POWERTOOLS_PATH, ["cpuToggle", str(num), enabled_str])
if cmd.execute() != OK:
logger.warn("Failed to update CPU core: " + cmd.stdout)
update()
logger.info("Set core " + str(num) + " enabled: " + str(enabled))

return cmd.code


func _get_property(prop: String) -> String:
var prop_path := "/".join([path, prop])
if not FileAccess.file_exists(prop_path):
return ""
var file := FileAccess.open(prop_path, FileAccess.READ)
var length := file.get_length()
var bytes := file.get_buffer(length)

return bytes.get_string_from_utf8()


func _to_string() -> String:
return "<Core" + str(num) + " Online: " + str(online) + ">"
45 changes: 45 additions & 0 deletions core/systems/hardware/cpu_test.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
extends GutTest

var hardware_manager := load("res://core/systems/hardware/hardware_manager.tres") as HardwareManager
var cpu := hardware_manager.get_cpu()

func before_all() -> void:
gut.p(cpu)


func test_get_cpu_cores() -> void:
for core in cpu.get_cores():
gut.p(core)

pass_test("Skipping")


func test_cpu_boost() -> void:
cpu.set_boost_enabled(false)
gut.p("Boost: " + str(cpu.get_boost_enabled()))
cpu.set_boost_enabled(true)
gut.p("Boost: " + str(cpu.get_boost_enabled()))

pass_test("Skipping")


func test_cpu_core_enable() -> void:
var core := cpu.get_core(10)
core.changed.connect(_on_core_changed)
if core.set_online(false) != OK:
gut.p("Failed to turn off core")
if core.set_online(true) != OK:
gut.p("Failed to turn on core")

pass_test("Skipping")


func test_set_cpu_core_count() -> void:
cpu.set_cpu_core_count(2)
cpu.set_cpu_core_count(cpu.get_total_core_count())

pass_test("Skipping")


func _on_core_changed() -> void:
gut.p("Core state changed!")
27 changes: 27 additions & 0 deletions core/systems/hardware/drm_card_info.gd
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ var subvendor_id: String
var revision_id: String


func _init(card_dir: String) -> void:
name = card_dir


## Returns a [DRMCardPort] object for the given port directory (E.g. card1-HDMI-A-1)
func get_port(port_dir: String) -> DRMCardPort:
var port_name := port_dir.trim_prefix(name + "-")
Expand Down Expand Up @@ -55,6 +59,29 @@ func get_ports() -> Array[DRMCardPort]:
return found_ports


## Returns the maximum and minimum GPU clock values
func get_clock_limits() -> Vector2:
return Vector2.ZERO


## Returns the current GPU minimum and maximum clock values
func get_clock_values() -> Vector2:
return Vector2.ZERO


## Read the data from the given property path relative to /sys/class/drm/cardX
func _get_property(prop: String) -> String:
var card_path := "/".join([drm_path, name])
var prop_path := "/".join([card_path, prop])
if not FileAccess.file_exists(prop_path):
return ""
var file := FileAccess.open(prop_path, FileAccess.READ)
var length := file.get_length()
var bytes := file.get_buffer(length)

return bytes.get_string_from_utf8()


func _to_string() -> String:
return "<DRMCardInfo:" \
+ " Name: (" + str(name) \
Expand Down
Loading

0 comments on commit 56816a6

Please sign in to comment.