package main import ( "io" "log/slog" "math" "net/http" "os" "regexp" "strconv" "strings" ) type cpuHandler struct { corePaths []string maxFreq int minFreq int logger *slog.Logger } // readIntFromFile reads the given file until the end // and converts the contained string into an integer func readIntFromFile(path string) (int, error) { b, err := os.ReadFile(path) if err != nil { return 0, err } return strconv.Atoi(strings.TrimSpace(string(b))) } func enumerateCPUCores(logger *slog.Logger) ([]string, int, int, error) { files, err := os.ReadDir("/sys/devices/system/cpu/") if err != nil { logger.Error("reading /sys/devices/system/cpu/", "err", err) return nil, -1, -1, err } r, _ := regexp.Compile("cpu([0-9]+)") freqPaths := make([]string, 0) maxF := math.MaxInt minF := math.MinInt // we look for cpuN directories for _, f := range files { if !f.IsDir() || !r.MatchString(f.Name()) { continue } // read files for min & max freqs dir := "/sys/devices/system/cpu/" + f.Name() + "/cpufreq/" minFreq, err := readIntFromFile(dir + "cpuinfo_min_freq") if err != nil { logger.Error("failed to read cpu min freq", "path", dir+"cpuinfo_min_freq", "error", err) continue } maxFreq, err := readIntFromFile(dir + "cpuinfo_max_freq") if err != nil { logger.Error("failed to read cpu max freq", "path", dir+"cpuinfo_min_freq", "error", err) continue } freqPaths = append(freqPaths, dir+"scaling_max_freq") maxF = min(maxF, maxFreq) minF = max(minF, minFreq) } return freqPaths, minF, maxF, nil } func (s *cpuHandler) handleMin(w http.ResponseWriter, r *http.Request) { if r.Method != "GET" { writeResponse("invalid method", http.StatusBadRequest, w, s.logger) return } writeResponse(strconv.Itoa(s.minFreq), http.StatusOK, w, s.logger) } func (s *cpuHandler) handleMax(w http.ResponseWriter, r *http.Request) { if r.Method != "GET" { writeResponse("invalid method", http.StatusBadRequest, w, s.logger) return } writeResponse(strconv.Itoa(s.maxFreq), http.StatusOK, w, s.logger) } func (s *cpuHandler) handleCurrent(w http.ResponseWriter, r *http.Request) { switch r.Method { case "GET": maxFreq := math.MinInt for _, core := range s.corePaths { freq, err := readIntFromFile(core) if err != nil { s.logger.Error("error while reading core frequency", "core", core, "error", err) writeResponse("internal server error", http.StatusInternalServerError, w, s.logger) return } s.logger.Debug("core frequency", "core", core, "frequency", freq) maxFreq = max(maxFreq, freq) } s.logger.Debug("reported frequency", "freq", maxFreq) writeResponse(strconv.Itoa(maxFreq), http.StatusOK, w, s.logger) case "PUT": body, err := io.ReadAll(r.Body) if err != nil { s.logger.Error("failed to read the request body", "err", err) writeResponse("internal server error", http.StatusInternalServerError, w, s.logger) } bodyStr := strings.TrimSpace(string(body)) // Check if it is an integer freqInt, err := strconv.Atoi(bodyStr) if err != nil { s.logger.Error("failed to convert body to int", "error", err) writeResponse("bad request", http.StatusBadRequest, w, s.logger) } if freqInt < s.minFreq || freqInt > s.maxFreq { s.logger.Warn("requested frequency is out-of-bounds", "freq", freqInt, "min", s.minFreq, "max", s.maxFreq) freqInt = max(s.minFreq, min(freqInt, s.maxFreq)) bodyStr = strconv.Itoa(freqInt) } s.logger.Info("requested to set current frequency", "frequency", bodyStr) success := true for _, core := range s.corePaths { err = os.WriteFile(core, []byte(bodyStr), 0) if err != nil { s.logger.Error("failed to set core frequency", "core", core, "error", err) success = false } } if success { w.WriteHeader(http.StatusOK) } else { w.WriteHeader(http.StatusInternalServerError) } default: s.logger.Error("unexpected method for current cpu", "method", r.Method) writeResponse("invalid method", http.StatusBadRequest, w, s.logger) } } func (s *cpuHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { requestedPath := getStrippedRequestPath(r.URL, r.Pattern) switch requestedPath { case "frequency/min": s.handleMin(w, r) return case "frequency/max": s.handleMax(w, r) return case "frequency/current": s.handleCurrent(w, r) return default: s.logger.Error("unknown resource", "path", requestedPath) writeResponse("not found", http.StatusNotFound, w, s.logger) } } func main() { appEnv := os.Getenv("AGENT_ENV") loggerOpts := &slog.HandlerOptions{ Level: slog.LevelDebug, } var handler slog.Handler = slog.NewTextHandler(os.Stdout, loggerOpts) if appEnv == "production" { loggerOpts.Level = slog.LevelInfo handler = slog.NewJSONHandler(os.Stdout, loggerOpts) } logger := slog.New(handler) freqPaths, minF, maxF, err := enumerateCPUCores(logger) if err != nil { os.Exit(1) } cpuHandler := &cpuHandler{ logger: logger, maxFreq: maxF, minFreq: minF, corePaths: freqPaths, } http.Handle("/cpu/", logRequests(logger, cpuHandler)) logger.Info("exit", "result", http.ListenAndServe(":8080", nil)) }