func()

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
}