Convert falcon to zig, add a config file
Some checks failed
PikaOS Package Build & Release (amd64-v3) / build (push) Failing after 1m44s
Some checks failed
PikaOS Package Build & Release (amd64-v3) / build (push) Failing after 1m44s
This commit is contained in:
parent
e716d70592
commit
0f8101e8c7
2
.github/release-nest-v3
vendored
2
.github/release-nest-v3
vendored
@ -1 +1 @@
|
|||||||
1
|
3
|
||||||
|
@ -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
15
build.zig
Normal 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
72
build.zig.zon
Normal 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
6
debian/changelog
vendored
@ -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
|
falcon (1.0.3-101pika1) pika; urgency=low
|
||||||
|
|
||||||
* Fix international
|
* Fix international
|
||||||
|
2
debian/control
vendored
2
debian/control
vendored
@ -4,7 +4,7 @@ Priority: optional
|
|||||||
Maintainer: ferreo <harderthanfire@gmail.com>
|
Maintainer: ferreo <harderthanfire@gmail.com>
|
||||||
Rules-Requires-Root: no
|
Rules-Requires-Root: no
|
||||||
Build-Depends:
|
Build-Depends:
|
||||||
debhelper-compat (= 13), golang-go
|
debhelper-compat (= 13), zig-nightly
|
||||||
Standards-Version: 4.6.1
|
Standards-Version: 4.6.1
|
||||||
Homepage: https://pika-os.com
|
Homepage: https://pika-os.com
|
||||||
|
|
||||||
|
10
debian/rules
vendored
10
debian/rules
vendored
@ -8,3 +8,13 @@ override_dh_dwz:
|
|||||||
|
|
||||||
%:
|
%:
|
||||||
dh $@
|
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
|
2
go.sum
2
go.sum
@ -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
238
main.go
@ -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
72
src/config.zig
Normal 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());
|
||||||
|
}
|
||||||
|
};
|
45
src/core_detection/amd.zig
Normal file
45
src/core_detection/amd.zig
Normal 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;
|
||||||
|
}
|
||||||
|
};
|
63
src/core_detection/lscpu.zig
Normal file
63
src/core_detection/lscpu.zig
Normal 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();
|
||||||
|
}
|
||||||
|
};
|
84
src/core_detection/mod.zig
Normal file
84
src/core_detection/mod.zig
Normal 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
69
src/main.zig
Normal 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
27
src/types.zig
Normal 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,
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user