Convert falcon to zig, add a config file
Some checks failed
PikaOS Package Build & Release (amd64-v3) / build (push) Failing after 1m44s

This commit is contained in:
ferreo 2024-12-05 22:53:50 +00:00
parent e716d70592
commit 0f8101e8c7
16 changed files with 465 additions and 254 deletions

View File

@ -1 +1 @@
1
3

View File

@ -1,7 +0,0 @@
all:
true
install:
mkdir -p $(DESTDIR)/usr/bin/
GOAMD=v3 go build -ldflags="-s -w" -o $(DESTDIR)/usr/bin/falcon -buildvcs=false
chmod 755 $(DESTDIR)/usr/bin/falcon

15
build.zig Normal file
View File

@ -0,0 +1,15 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "falcon",
.root_source_file = .{ .cwd_relative = "src/main.zig" },
.target = target,
.optimize = optimize,
});
b.installArtifact(exe);
}

72
build.zig.zon Normal file
View File

@ -0,0 +1,72 @@
.{
// This is the default name used by packages depending on this one. For
// example, when a user runs `zig fetch --save <url>`, this field is used
// as the key in the `dependencies` table. Although the user can choose a
// different name, most users will stick with this provided value.
//
// It is redundant to include "zig" in this name because it is already
// within the Zig package namespace.
.name = "falcon",
// This is a [Semantic Version](https://semver.org/).
// In a future version of Zig it will be used for package deduplication.
.version = "0.1.0",
// This field is optional.
// This is currently advisory only; Zig does not yet do anything
// with this value.
//.minimum_zig_version = "0.11.0",
// This field is optional.
// Each dependency must either provide a `url` and `hash`, or a `path`.
// `zig build --fetch` can be used to fetch all dependencies of a package, recursively.
// Once all dependencies are fetched, `zig build` no longer requires
// internet connectivity.
.dependencies = .{
// See `zig fetch --save <url>` for a command-line interface for adding dependencies.
//.example = .{
// // When updating this field to a new URL, be sure to delete the corresponding
// // `hash`, otherwise you are communicating that you expect to find the old hash at
// // the new URL.
// .url = "https://example.com/foo.tar.gz",
//
// // This is computed from the file contents of the directory of files that is
// // obtained after fetching `url` and applying the inclusion rules given by
// // `paths`.
// //
// // This field is the source of truth; packages do not come from a `url`; they
// // come from a `hash`. `url` is just one of many possible mirrors for how to
// // obtain a package matching this `hash`.
// //
// // Uses the [multihash](https://multiformats.io/multihash/) format.
// .hash = "...",
//
// // When this is provided, the package is found in a directory relative to the
// // build root. In this case the package's hash is irrelevant and therefore not
// // computed. This field and `url` are mutually exclusive.
// .path = "foo",
// // When this is set to `true`, a package is declared to be lazily
// // fetched. This makes the dependency only get fetched if it is
// // actually used.
// .lazy = false,
//},
},
// Specifies the set of files and directories that are included in this package.
// Only files and directories listed here are included in the `hash` that
// is computed for this package. Only files listed here will remain on disk
// when using the zig package manager. As a rule of thumb, one should list
// files required for compilation plus any license(s).
// Paths are relative to the build root. Use the empty string (`""`) to refer to
// the build root itself.
// A directory listed here means that all files within, recursively, are included.
.paths = .{
"build.zig",
"build.zig.zon",
"src",
// For example...
//"LICENSE",
//"README.md",
},
}

6
debian/changelog vendored
View File

@ -1,3 +1,9 @@
falcon (1.0.4-101pika1) pika; urgency=low
* Zig version + configs
-- Ward Nakchbandi <hotrod.master@hotmail.com> Sat, 10 Dec 2022 13:48:00 +0300
falcon (1.0.3-101pika1) pika; urgency=low
* Fix international

2
debian/control vendored
View File

@ -4,7 +4,7 @@ Priority: optional
Maintainer: ferreo <harderthanfire@gmail.com>
Rules-Requires-Root: no
Build-Depends:
debhelper-compat (= 13), golang-go
debhelper-compat (= 13), zig-nightly
Standards-Version: 4.6.1
Homepage: https://pika-os.com

10
debian/rules vendored
View File

@ -8,3 +8,13 @@ override_dh_dwz:
%:
dh $@
override_dh_install:
dh_install
mkdir -p debian/falcon/usr/bin/
cp -vf falcon debian/falcon/usr/bin/
chmod 755 debian/falcon/usr/bin/falcon
chmod +x debian/falcon/usr/bin/falcon
override_dh_auto_build:
zig build-exe src/main.zig -O ReleaseFast -mcpu x86_64_v3 --name falcon

5
go.mod
View File

@ -1,5 +0,0 @@
module falcon
go 1.22
require golang.org/x/sync v0.8.0

2
go.sum
View File

@ -1,2 +0,0 @@
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=

238
main.go
View File

@ -1,238 +0,0 @@
package main
import (
"bufio"
"context"
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"sort"
"strconv"
"strings"
"golang.org/x/sync/errgroup"
)
type CPUInfo struct {
CPU int `json:"cpu"`
MaxMHz float64 `json:"maxmhz"`
}
type LSCPUOutput struct {
CPUs []CPUInfo `json:"cpus"`
}
type CoreFreq struct {
Core int
Freq int
}
func main() {
args := os.Args[1:]
if len(args) == 0 {
fmt.Println("Please provide a command to run")
os.Exit(1)
}
eg, ctx := errgroup.WithContext(context.Background())
canPerf, cores, err := getBestCores()
if err != nil {
eg.Go(func() error {
return runCommand(ctx, false, []int{}, args)
})
err = eg.Wait()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
os.Exit(0)
}
eg.Go(func() error {
return runCommand(ctx, canPerf, cores, args)
})
err = eg.Wait()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
os.Exit(0)
}
func runCommand(ctx context.Context, canPerf bool, cores []int, args []string) error {
commandToRun := make([]string, 0)
if canPerf {
commandToRun = append(commandToRun, "powerprofilesctl", "launch", "-p", "performance")
}
if len(cores) > 0 {
coreVals := make([]string, len(cores))
for i, core := range cores {
coreVals[i] = fmt.Sprint(core)
}
commandToRun = append(commandToRun, "taskset", "-c", strings.Join(coreVals, ","))
}
commandToRun = append(commandToRun, args...)
cmd := exec.CommandContext(ctx, commandToRun[0], commandToRun[1:]...)
cmd.Env = append(cmd.Environ(), "POSIXLY_CORRECT=1")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
return cmd.Run()
}
func getBestCores() (bool, []int, error) {
perfMode, amdPrefCores, amdFlag, highestCores, err := GetPPStatusAndHighestCores()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
if !amdFlag {
return perfMode, highestCores, nil
}
numAmdCores := len(amdPrefCores)
switch numAmdCores {
case 32:
return perfMode, getHighestCores(16, amdPrefCores), nil
case 24:
return perfMode, getHighestCores(12, amdPrefCores), nil
case 16:
return perfMode, getHighestCores(8, amdPrefCores), nil
default:
return perfMode, getHighestCores(numAmdCores, amdPrefCores), nil
}
}
func getHighestCores(n int, coreFreqs []CoreFreq) []int {
if n <= 0 {
return []int{}
}
sort.Slice(coreFreqs, func(i, j int) bool {
return coreFreqs[i].Freq > coreFreqs[j].Freq
})
result := make([]int, 0, n)
for i := 0; i < n && i < len(coreFreqs); i++ {
result = append(result, coreFreqs[i].Core)
}
sort.Stable(sort.IntSlice(result))
return result
}
func GetPPStatusAndHighestCores() (bool, []CoreFreq, bool, []int, error) {
var (
performanceMode bool
amdPstateFreqs []CoreFreq
amdFlag bool
highestCores []int
)
g, ctx := errgroup.WithContext(context.Background())
g.Go(func() error {
cmd := exec.CommandContext(ctx, "powerprofilesctl", "list")
output, err := cmd.Output()
if err != nil {
performanceMode = false
return nil
}
performanceMode = strings.Contains(string(output), "performance")
return nil
})
g.Go(func() error {
pattern := "/sys/devices/system/cpu/cpu*/cpufreq/amd_pstate_prefcore_ranking"
matches, err := filepath.Glob(pattern)
if err != nil {
return fmt.Errorf("failed to glob AMD pstate files: %w", err)
}
if len(matches) > 0 {
amdFlag = true
}
for _, match := range matches {
file, err := os.Open(match)
if err != nil {
return fmt.Errorf("failed to open file %s: %w", match, err)
}
defer file.Close()
parts := strings.Split(match, "/")
cpuPart := parts[len(parts)-3]
coreNum, err := strconv.Atoi(strings.TrimPrefix(cpuPart, "cpu"))
if err != nil {
return fmt.Errorf("failed to parse core number from path %s: %w", match, err)
}
scanner := bufio.NewScanner(file)
if scanner.Scan() {
value, err := strconv.Atoi(strings.TrimSpace(scanner.Text()))
if err != nil {
return fmt.Errorf("failed to parse value from file %s: %w", match, err)
}
amdPstateFreqs = append(amdPstateFreqs, CoreFreq{Core: coreNum, Freq: value})
}
if err := scanner.Err(); err != nil {
return fmt.Errorf("error reading file %s: %w", match, err)
}
}
return nil
})
g.Go(func() error {
cmd := exec.CommandContext(ctx, "lscpu", "-e", "-J")
cmd.Env = append(cmd.Env, "LANG=en_GB.utf8")
output, err := cmd.Output()
if err != nil {
return fmt.Errorf("failed to run lscpu command: %w", err)
}
var lscpuOutput LSCPUOutput
if err := json.Unmarshal(output, &lscpuOutput); err != nil {
return fmt.Errorf("failed to unmarshal lscpu output: %w", err)
}
sort.Slice(lscpuOutput.CPUs, func(i, j int) bool {
return lscpuOutput.CPUs[i].MaxMHz > lscpuOutput.CPUs[j].MaxMHz
})
if len(lscpuOutput.CPUs) > 0 {
topMHz := lscpuOutput.CPUs[0].MaxMHz
secondMHz := topMHz
for _, cpu := range lscpuOutput.CPUs {
if cpu.MaxMHz < topMHz {
secondMHz = cpu.MaxMHz
break
}
}
for _, cpu := range lscpuOutput.CPUs {
if cpu.MaxMHz == topMHz || cpu.MaxMHz == secondMHz {
highestCores = append(highestCores, cpu.CPU)
}
}
}
return nil
})
if err := g.Wait(); err != nil {
return false, nil, false, nil, err
}
return performanceMode, amdPstateFreqs, amdFlag, highestCores, nil
}

72
src/config.zig Normal file
View File

@ -0,0 +1,72 @@
const std = @import("std");
const fs = std.fs;
const json = std.json;
pub const Config = struct {
force_lscpu: bool = false,
thread_count: usize = 16,
override_thread_count: bool = false,
enable_performance_mode: bool = true,
disable_taskset: bool = false,
custom_taskset: []const u8 = "",
lscpu_core_strategy: enum {
HighestFreq,
Sequential,
} = .HighestFreq,
pub fn load(allocator: std.mem.Allocator) !Config {
const home = std.posix.getenv("HOME") orelse return Config{};
var path_buf: [fs.max_path_bytes]u8 = undefined;
const config_path = try std.fmt.bufPrint(
&path_buf,
"{s}/.config/falcon/config.json",
.{home},
);
const file = fs.openFileAbsolute(config_path, .{}) catch |err| switch (err) {
error.FileNotFound => {
const default_config = Config{};
try default_config.save();
return default_config;
},
else => return err,
};
defer file.close();
const content = try file.readToEndAlloc(allocator, std.math.maxInt(usize));
defer allocator.free(content);
var parsed = try json.parseFromSlice(Config, allocator, content, .{});
defer parsed.deinit();
return parsed.value;
}
pub fn save(self: Config) !void {
const home = std.posix.getenv("HOME") orelse return error.NoHomeDir;
var path_buf: [fs.max_path_bytes]u8 = undefined;
const config_dir = try std.fmt.bufPrint(
&path_buf,
"{s}/.config/falcon",
.{home},
);
fs.makeDirAbsolute(config_dir) catch |err| switch (err) {
error.PathAlreadyExists => {},
else => return err,
};
const config_path = try std.fmt.bufPrint(
&path_buf,
"{s}/config.json",
.{config_dir},
);
const file = try fs.createFileAbsolute(config_path, .{});
defer file.close();
try json.stringify(self, .{ .whitespace = .indent_2 }, file.writer());
}
};

View File

@ -0,0 +1,45 @@
const std = @import("std");
const types = @import("../types.zig");
pub const AMDDetector = struct {
allocator: std.mem.Allocator,
const Self = @This();
pub fn init(allocator: std.mem.Allocator) Self {
return .{
.allocator = allocator,
};
}
pub fn detect(self: *Self) !?[]const types.CoreFreq {
var cpu_dir = try std.fs.cwd().openDir("/sys/devices/system/cpu", .{ .iterate = true });
defer cpu_dir.close();
var cores = std.ArrayList(types.CoreFreq).init(self.allocator);
var it = cpu_dir.iterate();
while (try it.next()) |entry| {
if (!std.mem.startsWith(u8, entry.name, "cpu")) continue;
if (std.mem.eql(u8, entry.name, "cpuidle") or std.mem.eql(u8, entry.name, "cpufreq")) continue;
const ranking_path = try std.fmt.allocPrint(self.allocator, "{s}/cpufreq/amd_pstate_prefcore_ranking", .{entry.name});
const file = cpu_dir.openFile(ranking_path, .{}) catch continue;
defer file.close();
const core_num = try std.fmt.parseInt(i32, entry.name[3..], 10);
var buf: [64]u8 = undefined;
const bytes_read = try file.readAll(&buf);
const value = try std.fmt.parseInt(i32, std.mem.trim(u8, buf[0..bytes_read], "\n"), 10);
try cores.append(.{ .core = core_num, .freq = value });
}
return if (cores.items.len > 0)
try cores.toOwnedSlice()
else
null;
}
};

View File

@ -0,0 +1,63 @@
const std = @import("std");
const types = @import("../types.zig");
const Child = std.process.Child;
const ArrayList = std.ArrayList;
pub const LSCPUDetector = struct {
allocator: std.mem.Allocator,
const Self = @This();
pub fn init(allocator: std.mem.Allocator) Self {
return .{
.allocator = allocator,
};
}
pub fn detect(self: *Self) ![]const i32 {
var highest_cores = ArrayList(i32).init(self.allocator);
var child = Child.init(&[_][]const u8{ "lscpu", "-e", "-J" }, self.allocator);
child.stdout_behavior = .Pipe;
child.stderr_behavior = .Inherit;
try child.spawn();
if (child.stdout) |stdout| {
var buffer = ArrayList(u8).init(self.allocator);
try stdout.reader().readAllArrayList(&buffer, std.math.maxInt(usize));
if (buffer.items.len > 0) {
var parsed = try std.json.parseFromSlice(
types.LsCpuOutput,
self.allocator,
buffer.items,
.{},
);
defer parsed.deinit();
if (parsed.value.cpus.len > 0) {
const top_mhz = parsed.value.cpus[0].maxmhz;
var second_mhz = top_mhz;
for (parsed.value.cpus) |cpu| {
if (cpu.maxmhz < top_mhz) {
second_mhz = cpu.maxmhz;
break;
}
}
for (parsed.value.cpus) |cpu| {
if (cpu.maxmhz == top_mhz or cpu.maxmhz == second_mhz) {
try highest_cores.append(cpu.cpu);
}
}
}
}
}
_ = try child.wait();
return try highest_cores.toOwnedSlice();
}
};

View File

@ -0,0 +1,84 @@
const std = @import("std");
const types = @import("../types.zig");
const AMDDetector = @import("amd.zig").AMDDetector;
const LSCPUDetector = @import("lscpu.zig").LSCPUDetector;
const Config = @import("../config.zig").Config;
pub const CoreDetector = struct {
allocator: std.mem.Allocator,
amd: AMDDetector,
lscpu: LSCPUDetector,
config: Config,
const Self = @This();
pub fn init(allocator: std.mem.Allocator, config: Config) Self {
return .{
.allocator = allocator,
.amd = AMDDetector.init(allocator),
.lscpu = LSCPUDetector.init(allocator),
.config = config,
};
}
pub fn detect(self: *Self) !types.DetectionResult {
if (!self.config.force_lscpu) {
if (try self.amd.detect()) |amd_cores| {
const num_cores = if (self.config.override_thread_count)
self.config.thread_count
else switch (amd_cores.len) {
32 => 16,
24 => 12,
16 => 8,
else => amd_cores.len,
};
const cores = try self.getHighestCores(num_cores, amd_cores);
return .{
.cores = cores,
.perf_mode = self.config.enable_performance_mode,
};
}
}
const lscpu_cores = try self.lscpu.detect();
var mutable_cores = try self.allocator.dupe(i32, lscpu_cores);
if (self.config.lscpu_core_strategy == .Sequential) {
std.mem.sort(i32, mutable_cores, {}, comptime std.sort.asc(i32));
}
if (self.config.override_thread_count) {
mutable_cores = mutable_cores[0..@min(self.config.thread_count, mutable_cores.len)];
}
return .{
.cores = mutable_cores,
.perf_mode = self.config.enable_performance_mode,
};
}
fn getHighestCores(self: *Self, n: usize, core_freqs: []const types.CoreFreq) ![]const i32 {
if (n <= 0) return &[_]i32{};
var freqs = try self.allocator.dupe(types.CoreFreq, core_freqs);
defer self.allocator.free(freqs);
std.mem.sort(types.CoreFreq, freqs, {}, struct {
fn lessThan(_: void, a: types.CoreFreq, b: types.CoreFreq) bool {
return a.freq > b.freq;
}
}.lessThan);
const result_len = @min(n, freqs.len);
var result = try self.allocator.alloc(i32, result_len);
for (freqs[0..result_len], 0..) |freq, i| {
result[i] = freq.core;
}
std.mem.sort(i32, result, {}, comptime std.sort.asc(i32));
return result;
}
};

69
src/main.zig Normal file
View File

@ -0,0 +1,69 @@
const std = @import("std");
const process = std.process;
const ArrayList = std.ArrayList;
const ArenaAllocator = std.heap.ArenaAllocator;
const Child = std.process.Child;
const CoreDetector = @import("core_detection/mod.zig").CoreDetector;
const Config = @import("config.zig").Config;
pub fn main() !void {
var arena = ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const allocator = arena.allocator();
const args = try process.argsAlloc(allocator);
defer process.argsFree(allocator, args);
if (args.len <= 1) {
std.debug.print("Please provide a command to run\n", .{});
process.exit(1);
}
const config = try Config.load(allocator);
var detector = CoreDetector.init(allocator, config);
const result = try detector.detect();
try runCommand(allocator, result.perf_mode, result.cores, args[1..], config);
}
fn runCommand(allocator: std.mem.Allocator, can_perf: bool, cores: []const i32, args: []const []const u8, config: Config) !void {
var command = ArrayList([]const u8).init(allocator);
if (can_perf) {
try command.appendSlice(&[_][]const u8{ "powerprofilesctl", "launch", "-p", "performance" });
}
if (!config.disable_taskset) {
if (config.custom_taskset.len > 0) {
try command.appendSlice(&[_][]const u8{ "taskset", "-c", config.custom_taskset });
} else if (cores.len > 0) {
try command.appendSlice(&[_][]const u8{ "taskset", "-c" });
var cores_list = ArrayList(u8).init(allocator);
for (cores, 0..) |core, i| {
if (i > 0) try cores_list.append(',');
const core_str = try std.fmt.allocPrint(allocator, "{d}", .{core});
try cores_list.appendSlice(core_str);
}
const cores_str = try cores_list.toOwnedSlice();
try command.append(cores_str);
}
}
try command.appendSlice(args);
var child = Child.init(command.items, allocator);
child.stdout_behavior = .Inherit;
child.stderr_behavior = .Inherit;
child.stdin_behavior = .Inherit;
var env_map = try std.process.getEnvMap(allocator);
child.env_map = &env_map;
try child.spawn();
const term = try child.wait();
if (term.Exited != 0) {
return error.CommandFailed;
}
}

27
src/types.zig Normal file
View File

@ -0,0 +1,27 @@
const std = @import("std");
pub const CoreFreq = struct {
core: i32,
freq: i32,
};
pub const CpuInfo = struct {
cpu: i32,
node: i32,
socket: i32,
core: i32,
@"l1d:l1i:l2:l3": []const u8,
online: bool,
maxmhz: f64,
minmhz: f64,
mhz: f64,
};
pub const LsCpuOutput = struct {
cpus: []CpuInfo,
};
pub const DetectionResult = struct {
cores: []const i32,
perf_mode: bool,
};