Compare commits
21 Commits
Author | SHA1 | Date | |
---|---|---|---|
11e12a2f5e | |||
4806f7244a | |||
387ee48881 | |||
![]() |
23520995bf | ||
2b15cbede4 | |||
![]() |
a2c3d34fb2 | ||
![]() |
36a90a297d | ||
54a7b7dc73 | |||
![]() |
75d6d24d2f | ||
![]() |
67c3f96ae6 | ||
bbef2d1607 | |||
d887536199 | |||
![]() |
787bd99c06 | ||
![]() |
bb786d1419 | ||
be0c2e28b2 | |||
af4ac1c167 | |||
ea524fd764 | |||
d829af05f1 | |||
c6434f1a4e | |||
![]() |
c3a26bb44e | ||
![]() |
c58b9fbe90 |
2
.github/release-nest-v3
vendored
2
.github/release-nest-v3
vendored
@ -1 +1 @@
|
|||||||
1
|
2
|
145
README.md
Normal file
145
README.md
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
# falcond - Advanced Linux Gaming Performance Daemon
|
||||||
|
|
||||||
|
falcond is a powerful system daemon designed to automatically optimize your Linux gaming experience. It intelligently manages system resources and performance settings on a per-game basis, eliminating the need to manually configure settings for each game.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Automatic Game Detection**: Automatically detects running games and applies optimized settings
|
||||||
|
- **Per-Game Profiles**: Customizable profiles for different games
|
||||||
|
- **Performance Mode**: Enables system-wide performance optimizations when gaming
|
||||||
|
- **3D vcache Management**: Smart management of AMD 3D vcache settings
|
||||||
|
- **SCX Scheduler Integration**: Dynamically pick a scheduler that is best for the specific game
|
||||||
|
- **Proton Compatibility**: Full support for Steam Proton games, with a global profile for excellent game coverage
|
||||||
|
- **Low Overhead**: Minimal system resource usage
|
||||||
|
- **Different Device Modes**: Profiles for desktops, handhelds, HTPC etc
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
falcond is available on PikaOS via the falcond package or via the Pika Gameutils Metapackage. For other distributions, please check your package manager or build from source.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The configuration file is located at `/etc/falcond/config.conf`. Here's an example configuration, it will be generated automatically on first run:
|
||||||
|
|
||||||
|
```
|
||||||
|
enable_performance_mode = true
|
||||||
|
scx_sched = none
|
||||||
|
scx_sched_props = default
|
||||||
|
vcache_mode = none
|
||||||
|
profile_mode = none
|
||||||
|
```
|
||||||
|
|
||||||
|
This is global configuration and all options other than profile_mode override individual game profiles.
|
||||||
|
|
||||||
|
There is also a list of proton/wine system processes in `/usr/share/falcond/system.conf`. This list can be updated if for example a crash handler is intercepting your profile and needs to be ignored.
|
||||||
|
|
||||||
|
### Configuration Options
|
||||||
|
|
||||||
|
- `enable_performance_mode`: Enable/disable performance mode (default: true)
|
||||||
|
- `scx_sched`: SCX scheduler (options: none, bpfland, lavd, rusty, flash)
|
||||||
|
- `scx_sched_props`: SCX scheduler mode (options: default, gaming, power, latency, server)
|
||||||
|
- `vcache_mode`: VCache management mode (options: none, cache, freq)
|
||||||
|
- `profile_mode`: What type of device is in use, none means desktop (options: none, handheld, htpc)
|
||||||
|
|
||||||
|
## Profile Modes
|
||||||
|
Falcond now supports different profile modes for different device types:
|
||||||
|
- Default (none): Uses profiles from `/usr/share/falcond/profiles`
|
||||||
|
- Handheld: Uses profiles from `/usr/share/falcond/profiles/handheld`
|
||||||
|
- HTPC: Uses profiles from `/usr/share/falcond/profiles/htpc`
|
||||||
|
|
||||||
|
To set a profile mode, add this to your config file:
|
||||||
|
```
|
||||||
|
profile_mode = "handheld" # or "htpc" or "none"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Game Profiles
|
||||||
|
|
||||||
|
Game profiles are stored in `/usr/share/falcond/profiles/` and define specific optimizations for individual games. You can contribute new profiles via PR at:
|
||||||
|
[Falcond Profiles Repository](https://github.com/PikaOS-Linux/falcond-profiles)
|
||||||
|
|
||||||
|
Example profile:
|
||||||
|
```
|
||||||
|
name = "game.exe"
|
||||||
|
performance_mode = true
|
||||||
|
scx_sched = bpfland
|
||||||
|
scx_sched_props = gaming
|
||||||
|
vcache_mode = cache
|
||||||
|
```
|
||||||
|
|
||||||
|
### Available Options
|
||||||
|
|
||||||
|
- `name`: The exe name (examples: cs2, PathOfExileSteam.exe)
|
||||||
|
- `performance_mode`: Enable/disable performance mode (default: true)
|
||||||
|
- `scx_sched`: SCX scheduler (options: none, bpfland, lavd, rusty, flash)
|
||||||
|
- `scx_sched_props`: SCX scheduler mode (options: none, gaming, power, latency, server)
|
||||||
|
- `vcache_mode`: VCache management mode (options: none, cache, freq)
|
||||||
|
|
||||||
|
## Service Management
|
||||||
|
|
||||||
|
To restart the daemon:
|
||||||
|
```bash
|
||||||
|
sudo systemctl restart falcond
|
||||||
|
```
|
||||||
|
|
||||||
|
To check the status:
|
||||||
|
```
|
||||||
|
sudo systemctl status falcond
|
||||||
|
```
|
||||||
|
|
||||||
|
## Source Code
|
||||||
|
|
||||||
|
The source code for falcond can be found at:
|
||||||
|
[Falcond Source Repository](https://git.pika-os.com/general-packages/falcond)
|
||||||
|
|
||||||
|
## Important Notes
|
||||||
|
|
||||||
|
⚠️ **Compatibility Warning** ⚠️ falcond should not be used alongside Feral GameMode or Falcon GameMode as they may conflict with each other. falcond provides similar functionality with additional features and optimizations.
|
||||||
|
|
||||||
|
## Why falcond?
|
||||||
|
|
||||||
|
Traditional gaming on Linux often requires manual optimization for each game - tweaking CPU governors, scheduling priorities, and cache settings. falcond automates this entire process by:
|
||||||
|
|
||||||
|
1. Automatically detecting when games are running
|
||||||
|
2. Applying optimized settings based on pre-configured profiles
|
||||||
|
3. Reverting settings when games are closed
|
||||||
|
4. Providing a centralized way to manage gaming performance
|
||||||
|
5. Easy switching of options for different device types
|
||||||
|
|
||||||
|
This means you can focus on gaming while falcond handles all the technical optimizations in the background!
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
falcond is released under the [MIT License](http://git.pika-os.com/general-packages/falcond/raw/branch/main/LICENSE).
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Please fork the [PikaOS-Linux/falcond](https://github.com/PikaOS-Linux/falcond) repository and submit a pull request.
|
||||||
|
|
||||||
|
## Build Dependencies
|
||||||
|
|
||||||
|
zig 0.14.0+
|
||||||
|
|
||||||
|
## Building from Source
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone https://git.pika-os.com/general-packages/falcond.git
|
||||||
|
cd falcond
|
||||||
|
cd falcond
|
||||||
|
zig build -Doptimize=ReleaseFast
|
||||||
|
```
|
||||||
|
|
||||||
|
## Runtime Dependencies
|
||||||
|
|
||||||
|
These should be feature detected by falcond so if not present that specific feature will not be used.
|
||||||
|
|
||||||
|
power-profiles-daemon or tuned + tuned-ppd
|
||||||
|
scx-sched
|
||||||
|
Linux kernel patched with AMD 3D vcache support
|
||||||
|
|
||||||
|
## Packaging
|
||||||
|
|
||||||
|
falcond should be placed in /usr/bin/falcond and run via a service file. There is an example systemd service file in ./falcond/debian/falcond.service
|
||||||
|
|
||||||
|
falcond needs profiles to be useful, these should be placed in /usr/share/falcond/profiles alongside the system.conf in /usr/share/falcond/system.conf. Upto date profiles can be found in the [PikaOS-Linux/falcond-profiles](https://github.com/PikaOS-Linux/falcond-profiles) repository. We currently pull the latest profiles from there on building of this package but you could also package seperately and depend on that package.
|
||||||
|
|
||||||
|
There is a config file in /etc/falcond/config.conf which is generated automatically on first run. You could also package that if you need different default settings.
|
@ -4,13 +4,17 @@ pub fn build(b: *std.Build) void {
|
|||||||
const target = b.standardTargetOptions(.{});
|
const target = b.standardTargetOptions(.{});
|
||||||
const optimize = b.standardOptimizeOption(.{});
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
|
|
||||||
const exe = b.addExecutable(.{
|
const main_module = b.createModule(.{
|
||||||
.name = "falcond",
|
|
||||||
.root_source_file = .{ .cwd_relative = "src/main.zig" },
|
.root_source_file = .{ .cwd_relative = "src/main.zig" },
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const exe = b.addExecutable(.{
|
||||||
|
.name = "falcond",
|
||||||
|
.root_module = main_module,
|
||||||
|
});
|
||||||
|
|
||||||
exe.bundle_compiler_rt = true;
|
exe.bundle_compiler_rt = true;
|
||||||
|
|
||||||
b.installArtifact(exe);
|
b.installArtifact(exe);
|
||||||
|
@ -6,12 +6,12 @@
|
|||||||
//
|
//
|
||||||
// It is redundant to include "zig" in this name because it is already
|
// It is redundant to include "zig" in this name because it is already
|
||||||
// within the Zig package namespace.
|
// within the Zig package namespace.
|
||||||
.name = "falcond",
|
.name = .falcond,
|
||||||
|
|
||||||
// This is a [Semantic Version](https://semver.org/).
|
// This is a [Semantic Version](https://semver.org/).
|
||||||
// In a future version of Zig it will be used for package deduplication.
|
// In a future version of Zig it will be used for package deduplication.
|
||||||
.version = "0.1.0",
|
.version = "0.1.0",
|
||||||
|
.fingerprint = 0x31d9d78a54cab47b,
|
||||||
// This field is optional.
|
// This field is optional.
|
||||||
// This is currently advisory only; Zig does not yet do anything
|
// This is currently advisory only; Zig does not yet do anything
|
||||||
// with this value.
|
// with this value.
|
||||||
|
@ -1,4 +1,41 @@
|
|||||||
falcond (1.0.7-101pika1) pika; urgency=low
|
falcond (1.1.2-101pika1) pika; urgency=low
|
||||||
|
|
||||||
|
* Add new scx sched options
|
||||||
|
|
||||||
|
-- ferreo <ferreo@pika-os.com> Sun, 12 Jan 2025 13:48:00 +0300
|
||||||
|
|
||||||
|
|
||||||
|
falcond (1.1.1-101pika1) pika; urgency=low
|
||||||
|
|
||||||
|
* Update to support latest zig
|
||||||
|
|
||||||
|
-- ferreo <ferreo@pika-os.com> Sun, 12 Jan 2025 13:48:00 +0300
|
||||||
|
|
||||||
|
falcond (1.1.0-101pika1) pika; urgency=low
|
||||||
|
|
||||||
|
* Fix memory leak on error, fix power ppd being required for falcond to launch.
|
||||||
|
|
||||||
|
-- ferreo <ferreo@pika-os.com> Sun, 12 Jan 2025 13:48:00 +0300
|
||||||
|
|
||||||
|
falcond (1.0.9-101pika2) pika; urgency=low
|
||||||
|
|
||||||
|
* Config parsing fixes
|
||||||
|
|
||||||
|
-- ferreo <ferreo@pika-os.com> Sun, 12 Jan 2025 13:48:00 +0300
|
||||||
|
|
||||||
|
falcond (1.0.8-101pika3) pika; urgency=low
|
||||||
|
|
||||||
|
* Update profiles
|
||||||
|
|
||||||
|
-- ferreo <ferreo@pika-os.com> Sun, 12 Jan 2025 13:48:00 +0300
|
||||||
|
|
||||||
|
falcond (1.0.8-101pika2) pika; urgency=low
|
||||||
|
|
||||||
|
* Fix scx scheduler mode selection order
|
||||||
|
|
||||||
|
-- ferreo <ferreo@pika-os.com> Sun, 12 Jan 2025 13:48:00 +0300
|
||||||
|
|
||||||
|
falcond (1.0.7-101pika2) pika; urgency=low
|
||||||
|
|
||||||
* Don't load profiles in subfolders
|
* Don't load profiles in subfolders
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ Priority: optional
|
|||||||
Maintainer: ferreo <ferreo@pika-os.com>
|
Maintainer: ferreo <ferreo@pika-os.com>
|
||||||
Rules-Requires-Root: no
|
Rules-Requires-Root: no
|
||||||
Build-Depends:
|
Build-Depends:
|
||||||
debhelper-compat (= 13), zig-nightly, git
|
debhelper-compat (= 13), zig, git
|
||||||
Standards-Version: 4.6.1
|
Standards-Version: 4.6.1
|
||||||
Homepage: https://pika-os.com
|
Homepage: https://pika-os.com
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ pub const PowerProfiles = struct {
|
|||||||
original_profile: ?[]const u8,
|
original_profile: ?[]const u8,
|
||||||
has_performance: bool,
|
has_performance: bool,
|
||||||
|
|
||||||
pub fn init(allocator: std.mem.Allocator, config: Config) !*PowerProfiles {
|
pub fn init(allocator: std.mem.Allocator, config: Config) !?*PowerProfiles {
|
||||||
var self = try allocator.create(PowerProfiles);
|
var self = try allocator.create(PowerProfiles);
|
||||||
errdefer allocator.destroy(self);
|
errdefer allocator.destroy(self);
|
||||||
|
|
||||||
@ -37,7 +37,11 @@ pub const PowerProfiles = struct {
|
|||||||
.has_performance = false,
|
.has_performance = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const profiles = try self.getAvailableProfiles(allocator);
|
const profiles = self.getAvailableProfiles(allocator) catch |err| {
|
||||||
|
std.log.err("Failed to get power profiles: {}, power profiles will be disabled", .{err});
|
||||||
|
allocator.destroy(self);
|
||||||
|
return null;
|
||||||
|
};
|
||||||
defer {
|
defer {
|
||||||
for (profiles) |profile| {
|
for (profiles) |profile| {
|
||||||
allocator.free(profile);
|
allocator.free(profile);
|
||||||
|
@ -20,6 +20,8 @@ pub const ScxScheduler = enum {
|
|||||||
simple,
|
simple,
|
||||||
userland,
|
userland,
|
||||||
vder,
|
vder,
|
||||||
|
p2dq,
|
||||||
|
tickless,
|
||||||
none,
|
none,
|
||||||
|
|
||||||
pub fn toScxName(self: ScxScheduler) []const u8 {
|
pub fn toScxName(self: ScxScheduler) []const u8 {
|
||||||
@ -49,6 +51,8 @@ pub const ScxScheduler = enum {
|
|||||||
if (std.mem.eql(u8, str, "scx_simple")) return .simple;
|
if (std.mem.eql(u8, str, "scx_simple")) return .simple;
|
||||||
if (std.mem.eql(u8, str, "scx_userland")) return .userland;
|
if (std.mem.eql(u8, str, "scx_userland")) return .userland;
|
||||||
if (std.mem.eql(u8, str, "scx_vder")) return .vder;
|
if (std.mem.eql(u8, str, "scx_vder")) return .vder;
|
||||||
|
if (std.mem.eql(u8, str, "scx_p2dq")) return .p2dq;
|
||||||
|
if (std.mem.eql(u8, str, "scx_tickless")) return .tickless;
|
||||||
|
|
||||||
return error.InvalidValue;
|
return error.InvalidValue;
|
||||||
}
|
}
|
||||||
@ -75,7 +79,11 @@ pub fn init(alloc: std.mem.Allocator) !void {
|
|||||||
allocator = alloc;
|
allocator = alloc;
|
||||||
std.log.info("Initializing scheduler state", .{});
|
std.log.info("Initializing scheduler state", .{});
|
||||||
|
|
||||||
const sched_list = try getSupportedSchedulers(alloc);
|
const sched_list = getSupportedSchedulers(alloc) catch |err| {
|
||||||
|
std.log.warn("Failed to get supported schedulers: {}, scx_loader may not be available", .{err});
|
||||||
|
supported_schedulers = &[_]ScxScheduler{};
|
||||||
|
return;
|
||||||
|
};
|
||||||
defer alloc.free(sched_list);
|
defer alloc.free(sched_list);
|
||||||
|
|
||||||
std.log.info("Supported schedulers:", .{});
|
std.log.info("Supported schedulers:", .{});
|
||||||
@ -102,8 +110,8 @@ const SCX_IFACE = "org.scx.Loader";
|
|||||||
fn modeToInt(mode: ScxSchedModes) u32 {
|
fn modeToInt(mode: ScxSchedModes) u32 {
|
||||||
return switch (mode) {
|
return switch (mode) {
|
||||||
.default => 0,
|
.default => 0,
|
||||||
.power => 1,
|
.gaming => 1,
|
||||||
.gaming => 2,
|
.power => 2,
|
||||||
.latency => 3,
|
.latency => 3,
|
||||||
.server => 4,
|
.server => 4,
|
||||||
};
|
};
|
||||||
@ -112,8 +120,8 @@ fn modeToInt(mode: ScxSchedModes) u32 {
|
|||||||
fn intToMode(value: u32) ScxError!ScxSchedModes {
|
fn intToMode(value: u32) ScxError!ScxSchedModes {
|
||||||
return switch (value) {
|
return switch (value) {
|
||||||
0 => .default,
|
0 => .default,
|
||||||
1 => .power,
|
1 => .gaming,
|
||||||
2 => .gaming,
|
2 => .power,
|
||||||
3 => .latency,
|
3 => .latency,
|
||||||
4 => .server,
|
4 => .server,
|
||||||
else => error.InvalidValue,
|
else => error.InvalidValue,
|
||||||
@ -123,23 +131,38 @@ fn intToMode(value: u32) ScxError!ScxSchedModes {
|
|||||||
pub fn getCurrentScheduler(alloc: std.mem.Allocator) !?ScxScheduler {
|
pub fn getCurrentScheduler(alloc: std.mem.Allocator) !?ScxScheduler {
|
||||||
var dbus_conn = dbus.DBus.init(alloc, SCX_NAME, SCX_PATH, SCX_IFACE);
|
var dbus_conn = dbus.DBus.init(alloc, SCX_NAME, SCX_PATH, SCX_IFACE);
|
||||||
|
|
||||||
const current = try dbus_conn.getProperty("CurrentScheduler");
|
const current = dbus_conn.getProperty("CurrentScheduler") catch |err| {
|
||||||
|
std.log.warn("Failed to get current scheduler: {}", .{err});
|
||||||
|
return .none;
|
||||||
|
};
|
||||||
defer alloc.free(current);
|
defer alloc.free(current);
|
||||||
|
|
||||||
if (current.len == 0) return null;
|
if (current.len == 0) return .none;
|
||||||
return try ScxScheduler.fromString(current);
|
return ScxScheduler.fromString(current) catch |err| {
|
||||||
|
std.log.warn("Invalid scheduler value: {}", .{err});
|
||||||
|
return .none;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getCurrentMode(alloc: std.mem.Allocator) !ScxSchedModes {
|
pub fn getCurrentMode(alloc: std.mem.Allocator) !ScxSchedModes {
|
||||||
var dbus_conn = dbus.DBus.init(alloc, SCX_NAME, SCX_PATH, SCX_IFACE);
|
var dbus_conn = dbus.DBus.init(alloc, SCX_NAME, SCX_PATH, SCX_IFACE);
|
||||||
|
|
||||||
const mode_str = try dbus_conn.getProperty("SchedulerMode");
|
const mode_str = dbus_conn.getProperty("SchedulerMode") catch |err| {
|
||||||
|
std.log.warn("Failed to get scheduler mode: {}", .{err});
|
||||||
|
return .default;
|
||||||
|
};
|
||||||
defer alloc.free(mode_str);
|
defer alloc.free(mode_str);
|
||||||
|
|
||||||
if (mode_str.len == 0) return .default;
|
if (mode_str.len == 0) return .default;
|
||||||
|
|
||||||
const mode = try std.fmt.parseInt(u32, mode_str, 10);
|
const mode = std.fmt.parseInt(u32, mode_str, 10) catch |err| {
|
||||||
return intToMode(mode);
|
std.log.warn("Invalid scheduler mode value: {}", .{err});
|
||||||
|
return .default;
|
||||||
|
};
|
||||||
|
return intToMode(mode) catch |err| {
|
||||||
|
std.log.warn("Invalid mode value: {}", .{err});
|
||||||
|
return .default;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getSupportedSchedulers(alloc: std.mem.Allocator) ![]ScxScheduler {
|
pub fn getSupportedSchedulers(alloc: std.mem.Allocator) ![]ScxScheduler {
|
||||||
@ -247,23 +270,21 @@ fn runSystemCtl(alloc: std.mem.Allocator, command: []const u8, service: []const
|
|||||||
.allocator = alloc,
|
.allocator = alloc,
|
||||||
.argv = &argv,
|
.argv = &argv,
|
||||||
.max_output_bytes = max_output_size,
|
.max_output_bytes = max_output_size,
|
||||||
}) catch |err| {
|
}) catch {
|
||||||
std.log.warn("Failed to run systemctl {s} {s}: {}", .{ command, service, err });
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
defer alloc.free(result.stderr);
|
defer alloc.free(result.stderr);
|
||||||
defer alloc.free(result.stdout);
|
defer alloc.free(result.stdout);
|
||||||
|
|
||||||
if (result.term.Exited != 0) {
|
if (result.term.Exited != 0) {
|
||||||
std.log.warn("systemctl failed: {s}", .{result.stderr});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deactivateScheduler(alloc: std.mem.Allocator) ScxError!void {
|
pub fn deactivateScheduler(alloc: std.mem.Allocator) ScxError!void {
|
||||||
|
runSystemCtl(alloc, "start", "ananicy-cpp");
|
||||||
var dbus_conn = dbus.DBus.init(alloc, SCX_NAME, SCX_PATH, SCX_IFACE);
|
var dbus_conn = dbus.DBus.init(alloc, SCX_NAME, SCX_PATH, SCX_IFACE);
|
||||||
try dbus_conn.callMethod("StopScheduler", &[_][]const u8{});
|
try dbus_conn.callMethod("StopScheduler", &[_][]const u8{});
|
||||||
runSystemCtl(alloc, "start", "ananicy-cpp");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn restorePreviousState(alloc: std.mem.Allocator) void {
|
pub fn restorePreviousState(alloc: std.mem.Allocator) void {
|
||||||
|
@ -20,7 +20,10 @@ pub fn loadConf(comptime T: type, allocator: std.mem.Allocator, path: []const u8
|
|||||||
if (bytes_read != size) return error.UnexpectedEOF;
|
if (bytes_read != size) return error.UnexpectedEOF;
|
||||||
|
|
||||||
var parser = Parser(T).init(allocator, buffer);
|
var parser = Parser(T).init(allocator, buffer);
|
||||||
return try parser.parse();
|
return parser.parse() catch |err| {
|
||||||
|
std.log.err("Failed to parse config file '{s}': {s}", .{ path, @errorName(err) });
|
||||||
|
return err;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn loadConfDir(comptime T: type, allocator: std.mem.Allocator, dir_path: []const u8) !std.ArrayList(T) {
|
pub fn loadConfDir(comptime T: type, allocator: std.mem.Allocator, dir_path: []const u8) !std.ArrayList(T) {
|
||||||
@ -43,7 +46,10 @@ pub fn loadConfDir(comptime T: type, allocator: std.mem.Allocator, dir_path: []c
|
|||||||
defer allocator.free(content);
|
defer allocator.free(content);
|
||||||
|
|
||||||
var parser = Parser(T).init(allocator, content);
|
var parser = Parser(T).init(allocator, content);
|
||||||
const parsed = try parser.parse();
|
const parsed = parser.parse() catch |err| {
|
||||||
|
std.log.err("Failed to parse config file '{s}': {s}", .{ path, @errorName(err) });
|
||||||
|
return err;
|
||||||
|
};
|
||||||
try result.append(parsed);
|
try result.append(parsed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,17 +81,33 @@ pub fn Parser(comptime T: type) type {
|
|||||||
return error.InvalidSyntax;
|
return error.InvalidSyntax;
|
||||||
|
|
||||||
self.pos += 1;
|
self.pos += 1;
|
||||||
const start = self.pos;
|
var escaped = false;
|
||||||
|
var result = std.ArrayList(u8).init(self.allocator);
|
||||||
|
errdefer result.deinit();
|
||||||
|
|
||||||
while (self.pos < self.content.len) : (self.pos += 1) {
|
while (self.pos < self.content.len) : (self.pos += 1) {
|
||||||
switch (self.content[self.pos]) {
|
const c = self.content[self.pos];
|
||||||
|
if (escaped) {
|
||||||
|
switch (c) {
|
||||||
|
'"' => try result.append('"'),
|
||||||
|
'\\' => try result.append('\\'),
|
||||||
|
'n' => try result.append('\n'),
|
||||||
|
'r' => try result.append('\r'),
|
||||||
|
't' => try result.append('\t'),
|
||||||
|
else => return error.InvalidSyntax,
|
||||||
|
}
|
||||||
|
escaped = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
switch (c) {
|
||||||
'"' => {
|
'"' => {
|
||||||
const str = try self.allocator.dupe(u8, self.content[start..self.pos]);
|
|
||||||
self.pos += 1;
|
self.pos += 1;
|
||||||
return str;
|
return result.toOwnedSlice();
|
||||||
},
|
},
|
||||||
'\\' => return error.InvalidSyntax,
|
'\\' => {
|
||||||
else => {},
|
escaped = true;
|
||||||
|
},
|
||||||
|
else => try result.append(c),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return error.UnterminatedString;
|
return error.UnterminatedString;
|
||||||
@ -162,7 +178,10 @@ pub fn Parser(comptime T: type) type {
|
|||||||
return values.toOwnedSlice();
|
return values.toOwnedSlice();
|
||||||
}
|
}
|
||||||
|
|
||||||
const str = try self.parseString();
|
const str = self.parseString() catch |err| {
|
||||||
|
std.log.err("Failed to parse string at position {}: {s}", .{ self.pos, @errorName(err) });
|
||||||
|
return err;
|
||||||
|
};
|
||||||
try values.append(str);
|
try values.append(str);
|
||||||
|
|
||||||
self.skipWhitespace();
|
self.skipWhitespace();
|
||||||
@ -174,8 +193,10 @@ pub fn Parser(comptime T: type) type {
|
|||||||
self.pos += 1;
|
self.pos += 1;
|
||||||
return values.toOwnedSlice();
|
return values.toOwnedSlice();
|
||||||
}
|
}
|
||||||
|
std.log.err("Expected ',' or ']' but found '{c}' at position {}", .{ self.content[self.pos], self.pos });
|
||||||
return error.InvalidSyntax;
|
return error.InvalidSyntax;
|
||||||
}
|
}
|
||||||
|
std.log.err("Unterminated array at position {}", .{self.pos});
|
||||||
return error.InvalidSyntax;
|
return error.InvalidSyntax;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -258,11 +279,16 @@ pub fn Parser(comptime T: type) type {
|
|||||||
@field(result, field.name) = @intCast(try self.parseNumber());
|
@field(result, field.name) = @intCast(try self.parseNumber());
|
||||||
},
|
},
|
||||||
.array => |array_info| {
|
.array => |array_info| {
|
||||||
const array = try self.parseArray();
|
switch (@typeInfo(array_info.child)) {
|
||||||
if (array.len > array_info.len) return error.InvalidSyntax;
|
i64 => {
|
||||||
@field(result, field.name) = undefined;
|
const array = try self.parseArray();
|
||||||
var dest = &@field(result, field.name);
|
if (array.len > array_info.len) return error.InvalidSyntax;
|
||||||
@memcpy(dest[0..array.len], array);
|
@field(result, field.name) = undefined;
|
||||||
|
var dest = &@field(result, field.name);
|
||||||
|
@memcpy(dest[0..array.len], array);
|
||||||
|
},
|
||||||
|
else => return error.InvalidSyntax,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
.@"enum" => {
|
.@"enum" => {
|
||||||
const ident = try self.parseIdentifier();
|
const ident = try self.parseIdentifier();
|
||||||
@ -288,18 +314,22 @@ pub fn Parser(comptime T: type) type {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
.pointer => |ptr_info| {
|
.pointer => |ptr_info| {
|
||||||
if (ptr_info.size != .Slice) return error.InvalidSyntax;
|
if (ptr_info.size != .slice) {
|
||||||
switch (ptr_info.child) {
|
return error.InvalidSyntax;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ptr_info.child == []const u8) {
|
||||||
|
@field(result, field.name) = try self.parseStringArray();
|
||||||
|
} else switch (ptr_info.child) {
|
||||||
u8 => {
|
u8 => {
|
||||||
@field(result, field.name) = try self.parseString();
|
@field(result, field.name) = try self.parseString();
|
||||||
},
|
},
|
||||||
i64 => {
|
i64 => {
|
||||||
@field(result, field.name) = try self.parseArray();
|
@field(result, field.name) = try self.parseArray();
|
||||||
},
|
},
|
||||||
[]const u8 => {
|
else => {
|
||||||
@field(result, field.name) = try self.parseStringArray();
|
return error.InvalidSyntax;
|
||||||
},
|
},
|
||||||
else => return error.InvalidSyntax,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
else => return error.InvalidSyntax,
|
else => return error.InvalidSyntax,
|
||||||
|
@ -15,7 +15,7 @@ pub const Daemon = struct {
|
|||||||
profile_manager: ProfileManager,
|
profile_manager: ProfileManager,
|
||||||
oneshot: bool,
|
oneshot: bool,
|
||||||
known_pids: ?std.AutoHashMap(u32, *const Profile),
|
known_pids: ?std.AutoHashMap(u32, *const Profile),
|
||||||
power_profiles: *PowerProfiles,
|
power_profiles: ?*PowerProfiles,
|
||||||
performance_mode: bool,
|
performance_mode: bool,
|
||||||
last_profiles_check: i128,
|
last_profiles_check: i128,
|
||||||
last_config_check: i128,
|
last_config_check: i128,
|
||||||
@ -30,13 +30,17 @@ pub const Daemon = struct {
|
|||||||
const system_conf_path_owned = try allocator.dupe(u8, system_conf_path);
|
const system_conf_path_owned = try allocator.dupe(u8, system_conf_path);
|
||||||
errdefer allocator.free(system_conf_path_owned);
|
errdefer allocator.free(system_conf_path_owned);
|
||||||
|
|
||||||
const config = try Config.load(allocator, config_path, system_conf_path);
|
var config = try Config.load(allocator, config_path, system_conf_path);
|
||||||
const power_profiles = try PowerProfiles.init(allocator, config);
|
errdefer config.deinit();
|
||||||
errdefer power_profiles.deinit();
|
|
||||||
|
|
||||||
const performance_mode = power_profiles.isPerformanceAvailable();
|
const power_profiles = try PowerProfiles.init(allocator, config);
|
||||||
if (performance_mode) {
|
var performance_mode = false;
|
||||||
std.log.info("Performance profile available - power profile management enabled", .{});
|
|
||||||
|
if (power_profiles) |pp| {
|
||||||
|
performance_mode = pp.isPerformanceAvailable();
|
||||||
|
if (performance_mode) {
|
||||||
|
std.log.info("Performance profile available - power profile management enabled", .{});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var profile_manager = ProfileManager.init(allocator, power_profiles, config);
|
var profile_manager = ProfileManager.init(allocator, power_profiles, config);
|
||||||
@ -66,7 +70,9 @@ pub const Daemon = struct {
|
|||||||
pub fn deinit(self: *Self) void {
|
pub fn deinit(self: *Self) void {
|
||||||
scx_scheds.deinit();
|
scx_scheds.deinit();
|
||||||
self.profile_manager.deinit();
|
self.profile_manager.deinit();
|
||||||
self.power_profiles.deinit();
|
if (self.power_profiles) |pp| {
|
||||||
|
pp.*.deinit();
|
||||||
|
}
|
||||||
if (self.known_pids) |*map| {
|
if (self.known_pids) |*map| {
|
||||||
map.deinit();
|
map.deinit();
|
||||||
}
|
}
|
||||||
@ -86,7 +92,9 @@ pub const Daemon = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.profile_manager.deinit();
|
self.profile_manager.deinit();
|
||||||
self.power_profiles.deinit();
|
if (self.power_profiles) |pp| {
|
||||||
|
pp.*.deinit();
|
||||||
|
}
|
||||||
self.config.deinit();
|
self.config.deinit();
|
||||||
|
|
||||||
const config = try Config.load(self.allocator, self.config_path, self.system_conf_path);
|
const config = try Config.load(self.allocator, self.config_path, self.system_conf_path);
|
||||||
@ -96,7 +104,7 @@ pub const Daemon = struct {
|
|||||||
|
|
||||||
self.config = config;
|
self.config = config;
|
||||||
self.power_profiles = power_profiles;
|
self.power_profiles = power_profiles;
|
||||||
self.performance_mode = self.power_profiles.isPerformanceAvailable();
|
self.performance_mode = if (power_profiles) |pp| pp.isPerformanceAvailable() else false;
|
||||||
self.profile_manager = profile_manager;
|
self.profile_manager = profile_manager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Daemon = @import("daemon.zig").Daemon;
|
const Daemon = @import("daemon.zig").Daemon;
|
||||||
|
const builtin = @import("builtin");
|
||||||
pub const std_options = std.Options{
|
pub const std_options = std.Options{
|
||||||
.log_level = .debug,
|
.log_level = .debug,
|
||||||
.log_scope_levels = &[_]std.log.ScopeLevel{
|
.log_scope_levels = &[_]std.log.ScopeLevel{
|
||||||
@ -29,19 +29,25 @@ const AllocTracker = struct {
|
|||||||
const config_path: []const u8 = "/etc/falcond/config.conf";
|
const config_path: []const u8 = "/etc/falcond/config.conf";
|
||||||
const system_conf_path: []const u8 = "/usr/share/falcond/system.conf";
|
const system_conf_path: []const u8 = "/usr/share/falcond/system.conf";
|
||||||
|
|
||||||
fn alloc(ctx: *anyopaque, len: usize, ptr_align: u8, ret_addr: usize) ?[*]u8 {
|
fn alloc(ctx: *anyopaque, len: usize, ptr_align: std.mem.Alignment, ret_addr: usize) ?[*]u8 {
|
||||||
var t: *AllocTracker = @ptrCast(@alignCast(ctx));
|
var t: *AllocTracker = @ptrCast(@alignCast(ctx));
|
||||||
t.trackAlloc();
|
t.trackAlloc();
|
||||||
return gpa_vtable.alloc(gpa_ptr, len, ptr_align, ret_addr);
|
return gpa_vtable.alloc(gpa_ptr, len, ptr_align, ret_addr);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resize(ctx: *anyopaque, buf: []u8, log2_buf_align: u8, new_len: usize, ret_addr: usize) bool {
|
fn resize(ctx: *anyopaque, buf: []u8, log2_buf_align: std.mem.Alignment, new_len: usize, ret_addr: usize) bool {
|
||||||
var t: *AllocTracker = @ptrCast(@alignCast(ctx));
|
var t: *AllocTracker = @ptrCast(@alignCast(ctx));
|
||||||
t.trackResize();
|
t.trackResize();
|
||||||
return gpa_vtable.resize(gpa_ptr, buf, log2_buf_align, new_len, ret_addr);
|
return gpa_vtable.resize(gpa_ptr, buf, log2_buf_align, new_len, ret_addr);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn free(ctx: *anyopaque, buf: []u8, log2_buf_align: u8, ret_addr: usize) void {
|
fn remap(ctx: *anyopaque, buf: []u8, log2_buf_align: std.mem.Alignment, new_len: usize, ret_addr: usize) ?[*]u8 {
|
||||||
|
var t: *AllocTracker = @ptrCast(@alignCast(ctx));
|
||||||
|
t.trackResize();
|
||||||
|
return gpa_vtable.remap(gpa_ptr, buf, log2_buf_align, new_len, ret_addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn free(ctx: *anyopaque, buf: []u8, log2_buf_align: std.mem.Alignment, ret_addr: usize) void {
|
||||||
var t: *AllocTracker = @ptrCast(@alignCast(ctx));
|
var t: *AllocTracker = @ptrCast(@alignCast(ctx));
|
||||||
t.trackDealloc();
|
t.trackDealloc();
|
||||||
gpa_vtable.free(gpa_ptr, buf, log2_buf_align, ret_addr);
|
gpa_vtable.free(gpa_ptr, buf, log2_buf_align, ret_addr);
|
||||||
@ -49,16 +55,20 @@ fn free(ctx: *anyopaque, buf: []u8, log2_buf_align: u8, ret_addr: usize) void {
|
|||||||
|
|
||||||
var gpa_vtable: *const std.mem.Allocator.VTable = undefined;
|
var gpa_vtable: *const std.mem.Allocator.VTable = undefined;
|
||||||
var gpa_ptr: *anyopaque = undefined;
|
var gpa_ptr: *anyopaque = undefined;
|
||||||
|
var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
|
||||||
pub fn main() !void {
|
pub fn main() !void {
|
||||||
std.log.info("Starting falcond...", .{});
|
std.log.info("Starting falcond...", .{});
|
||||||
|
var allocator: std.mem.Allocator = undefined;
|
||||||
var tracker = AllocTracker{};
|
var tracker = AllocTracker{};
|
||||||
var gpa = std.heap.GeneralPurposeAllocator(.{
|
const gpa, const is_debug = gpa: {
|
||||||
.verbose_log = false,
|
break :gpa switch (builtin.mode) {
|
||||||
.enable_memory_limit = true,
|
.Debug, .ReleaseSafe => .{ debug_allocator.allocator(), true },
|
||||||
}){};
|
.ReleaseFast, .ReleaseSmall => .{ std.heap.smp_allocator, false },
|
||||||
defer {
|
};
|
||||||
const leaked = gpa.deinit();
|
};
|
||||||
|
allocator = gpa;
|
||||||
|
defer if (is_debug) {
|
||||||
|
const leaked = debug_allocator.deinit();
|
||||||
if (leaked == .leak) {
|
if (leaked == .leak) {
|
||||||
std.log.err("Memory leaks detected!", .{});
|
std.log.err("Memory leaks detected!", .{});
|
||||||
}
|
}
|
||||||
@ -67,18 +77,20 @@ pub fn main() !void {
|
|||||||
tracker.deallocs,
|
tracker.deallocs,
|
||||||
tracker.resizes,
|
tracker.resizes,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
gpa_vtable = gpa.allocator().vtable;
|
|
||||||
gpa_ptr = gpa.allocator().ptr;
|
|
||||||
const allocator = std.mem.Allocator{
|
|
||||||
.ptr = &tracker,
|
|
||||||
.vtable = &std.mem.Allocator.VTable{
|
|
||||||
.alloc = alloc,
|
|
||||||
.resize = resize,
|
|
||||||
.free = free,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
if (is_debug) {
|
||||||
|
gpa_vtable = gpa.vtable;
|
||||||
|
gpa_ptr = gpa.ptr;
|
||||||
|
allocator = std.mem.Allocator{
|
||||||
|
.ptr = &tracker,
|
||||||
|
.vtable = &std.mem.Allocator.VTable{
|
||||||
|
.alloc = alloc,
|
||||||
|
.resize = resize,
|
||||||
|
.free = free,
|
||||||
|
.remap = remap,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const args = try std.process.argsAlloc(allocator);
|
const args = try std.process.argsAlloc(allocator);
|
||||||
defer std.process.argsFree(allocator, args);
|
defer std.process.argsFree(allocator, args);
|
||||||
|
@ -14,10 +14,10 @@ pub const ProfileManager = struct {
|
|||||||
proton_profile: ?*const Profile,
|
proton_profile: ?*const Profile,
|
||||||
active_profile: ?*const Profile = null,
|
active_profile: ?*const Profile = null,
|
||||||
queued_profiles: std.ArrayList(*const Profile),
|
queued_profiles: std.ArrayList(*const Profile),
|
||||||
power_profiles: *PowerProfiles,
|
power_profiles: ?*PowerProfiles,
|
||||||
config: Config,
|
config: Config,
|
||||||
|
|
||||||
pub fn init(allocator: std.mem.Allocator, power_profiles: *PowerProfiles, config: Config) ProfileManager {
|
pub fn init(allocator: std.mem.Allocator, power_profiles: ?*PowerProfiles, config: Config) ProfileManager {
|
||||||
return .{
|
return .{
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.profiles = std.ArrayList(Profile).init(allocator),
|
.profiles = std.ArrayList(Profile).init(allocator),
|
||||||
@ -47,9 +47,9 @@ pub const ProfileManager = struct {
|
|||||||
std.log.info("Activating profile: {s}", .{profile.name});
|
std.log.info("Activating profile: {s}", .{profile.name});
|
||||||
self.active_profile = profile;
|
self.active_profile = profile;
|
||||||
|
|
||||||
if (profile.performance_mode and self.power_profiles.isPerformanceAvailable()) {
|
if (profile.performance_mode and self.power_profiles != null and self.power_profiles.?.isPerformanceAvailable()) {
|
||||||
std.log.info("Enabling performance mode for profile: {s}", .{profile.name});
|
std.log.info("Enabling performance mode for profile: {s}", .{profile.name});
|
||||||
self.power_profiles.enablePerformanceMode();
|
self.power_profiles.?.enablePerformanceMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
const effective_mode = if (self.config.vcache_mode != .none)
|
const effective_mode = if (self.config.vcache_mode != .none)
|
||||||
@ -80,9 +80,9 @@ pub const ProfileManager = struct {
|
|||||||
if (active == profile) {
|
if (active == profile) {
|
||||||
std.log.info("Deactivating profile: {s}", .{profile.name});
|
std.log.info("Deactivating profile: {s}", .{profile.name});
|
||||||
|
|
||||||
if (profile.performance_mode and self.power_profiles.isPerformanceAvailable()) {
|
if (profile.performance_mode and self.power_profiles != null and self.power_profiles.?.isPerformanceAvailable()) {
|
||||||
std.log.info("Disabling performance mode for profile: {s}", .{profile.name});
|
std.log.info("Disabling performance mode for profile: {s}", .{profile.name});
|
||||||
self.power_profiles.disablePerformanceMode();
|
self.power_profiles.?.disablePerformanceMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
vcache_setting.applyVCacheMode(.none);
|
vcache_setting.applyVCacheMode(.none);
|
||||||
@ -102,9 +102,9 @@ pub const ProfileManager = struct {
|
|||||||
if (active == profile) {
|
if (active == profile) {
|
||||||
std.log.info("Unloading profile: {s}", .{profile.name});
|
std.log.info("Unloading profile: {s}", .{profile.name});
|
||||||
|
|
||||||
if (profile.performance_mode and self.power_profiles.isPerformanceAvailable()) {
|
if (profile.performance_mode and self.power_profiles != null and self.power_profiles.?.isPerformanceAvailable()) {
|
||||||
std.log.info("Disabling performance mode for profile: {s}", .{profile.name});
|
std.log.info("Disabling performance mode for profile: {s}", .{profile.name});
|
||||||
self.power_profiles.disablePerformanceMode();
|
self.power_profiles.?.disablePerformanceMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
vcache_setting.applyVCacheMode(.none);
|
vcache_setting.applyVCacheMode(.none);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user