Files
autoPriority/main.go
T

139 lines
3.9 KiB
Go

//go:build windows
package main
import (
"context"
"errors"
"flag"
"fmt"
"io"
"os"
"os/signal"
"path/filepath"
"syscall"
"time"
)
type options struct {
mem, gameMem uint64
interval time.Duration
dryRun bool
}
func readOptions(args []string, output io.Writer) (options, error) {
var o options
fs := flag.NewFlagSet("autoPriority", flag.ContinueOnError)
fs.SetOutput(output)
mem := fs.String("mem", "512M", "memory threshold (e.g. 512M, 1G)")
gameMem := fs.String("game-mem", "2G", "heavy-process threshold; greater than -mem, or 0 to disable")
fs.DurationVar(&o.interval, "interval", time.Minute, "scan interval (minimum 10s)")
fs.BoolVar(&o.dryRun, "dry-run", false, "log the plan without changing priorities")
if err := fs.Parse(args); err != nil {
return o, err
}
if fs.NArg() != 0 {
return o, fmt.Errorf("unexpected positional arguments")
}
var err error
o.mem, err = parseMemSize(*mem)
if err != nil || o.mem == 0 {
return o, fmt.Errorf("invalid -mem: use a positive integer with optional K/M/G suffix")
}
o.gameMem, err = parseMemSize(*gameMem)
if err != nil {
return o, fmt.Errorf("invalid -game-mem: %w", err)
}
if o.gameMem != 0 && o.gameMem <= o.mem {
return o, fmt.Errorf("-game-mem must be greater than -mem, or 0")
}
if o.interval < 10*time.Second {
o.interval = 10 * time.Second
}
return o, nil
}
func runController(ctx context.Context, o options, api processBackend, emit func(scanReport) error) (result error) {
c := newController(api, o.mem, o.gameMem, o.dryRun, o.interval)
defer func() {
report := c.Shutdown()
result = errors.Join(result, emit(report))
if report.Unrestored > 0 {
result = errors.Join(result, fmt.Errorf("не восстановлены приоритеты %d процессов; см. журнал", report.Unrestored))
}
}()
ticker := time.NewTicker(o.interval)
defer ticker.Stop()
for {
if ctx.Err() != nil {
return nil
}
if err := emit(c.Step()); err != nil {
return err
}
select {
case <-ctx.Done():
return nil
case <-ticker.C:
}
}
}
func runApplication(o options) (result error) {
mutex, exists, err := singleInstance()
if err != nil {
return err
}
defer closeH(mutex)
if exists {
fmt.Fprintln(os.Stderr, "autoPriority: already running")
return nil
}
logFile, err := openRotatingLog(filepath.Join(os.TempDir(), "autopriority.log"), 4<<20)
if err != nil {
return err
}
defer func() { result = errors.Join(result, logFile.Close()) }()
emit := func(r scanReport) error { return logFile.Write(formatReport(r, time.Now())) }
if err = emit(scanReport{Title: fmt.Sprintf("autoPriority: mem=%s, game-mem=%s, interval=%s", formatMemSize(o.mem), formatMemSize(o.gameMem), o.interval), DryRun: o.dryRun}); err != nil {
return err
}
if !o.dryRun {
pid := uint32(os.Getpid())
h, e := openProc(pid, ProcessSetInformation)
if e == nil {
e = setPrio(h, pid, PriorityClassIdle)
closeH(h)
}
if e != nil {
if err = emit(scanReport{Errors: []processError{{Name: "autoPriority.exe", PID: pid, Description: fmt.Sprintf("Собственный приоритет IDLE не установлен: %v", e)}}}); err != nil {
return err
}
}
}
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer cancel()
restored := make(chan struct{})
tray, err := newTray(cancel, restored)
if err != nil {
close(restored)
return err
}
defer func() { result = errors.Join(result, tray.Close()) }()
// runController performs restoration before this channel closes and before the tray disappears.
defer close(restored)
return runController(ctx, o, newWindowsBackend(), emit)
}
func main() {
o, err := readOptions(os.Args[1:], os.Stderr)
if errors.Is(err, flag.ErrHelp) {
return
}
if err == nil {
err = runApplication(o)
}
if err != nil {
fmt.Fprintf(os.Stderr, "autoPriority: %v\n", err)
showError(err)
os.Exit(1)
}
}