in internal/ps/ps_linux.go [232:290]
func (p linuxClient) CPUUsage(ctx context.Context, pid int) (float64, error) {
baseProcDir := filepath.Join(p.procDir, strconv.Itoa(pid))
// Read the stat file. This is where the usage data is kept.
stats, err := os.ReadFile(filepath.Join(baseProcDir, "stat"))
if err != nil {
return 0, fmt.Errorf("error reading stat file: %w", err)
}
statsLines := strings.Fields(string(stats))
// Read utime and stime values. The fields are not labelled, so get them
// as-is.
utime, err := strconv.ParseFloat(statsLines[13], 64)
if err != nil {
return 0, fmt.Errorf("error parsing utime: %w", err)
}
stime, err := strconv.ParseFloat(statsLines[14], 64)
if err != nil {
return 0, fmt.Errorf("error parsing stime: %w", err)
}
// Total time used is the sum of both values.
// Since the values are in clock ticks, divide by clock tick.
totalTimeTicks := utime + stime
if clkTime == 0 {
clkTime, err = readClkTicks(ctx)
if err != nil {
return 0, fmt.Errorf("error reading clk time: %w", err)
}
}
runTime := totalTimeTicks / clkTime
// Get the process start time and system uptime. These make up for the total
// elapsed time since the process started.
startTimeTicks, err := strconv.ParseFloat(statsLines[21], 64)
if err != nil {
return 0, fmt.Errorf("error parsing start time: %w", err)
}
startTime := startTimeTicks / clkTime
// uptime is the total running time of the system.
uptimeContents, err := os.ReadFile(filepath.Join(p.procDir, "uptime"))
if err != nil {
return 0, fmt.Errorf("error reading /proc/uptime file: %w", err)
}
uptime, err := strconv.ParseFloat(strings.Fields(string(uptimeContents))[0], 64)
if err != nil {
return 0, fmt.Errorf("error parsing /proc/uptime: %w", err)
}
// Time the process spent running.
elapsedTime := uptime - startTime
// Divide by elapsed time.
return runTime / elapsedTime, nil
}