-
Notifications
You must be signed in to change notification settings - Fork 166
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
psi-collector: Introduce standalone version of PSI Collector.
This commit adds a standalone version of the PSI Collector, allowing it to be built and used independently on older versions of EVE where the collector is not integrated into the root filesystem. The new component includes a Makefile for building the binary, a README with instructions, and a basic main.go implementation that handles the PSI collection process. This ensures compatibility and provides memory pressure monitoring capabilities for legacy systems. Signed-off-by: Nikolay Martyanov <nikolay@zededa.com>
- Loading branch information
1 parent
792e83e
commit 14098e1
Showing
4 changed files
with
292 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
bin | ||
psi.txt |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
BUILD_ARGS = CGO_ENABLED=0 GOOS=linux | ||
BUILD_FLAGS = -a -ldflags '-extldflags "-static"' | ||
|
||
build: | ||
GOARCH=amd64 $(BUILD_ARGS) go build -o bin/psi-collector $(BUILD_FLAGS) main.go | ||
|
||
build-arm: | ||
GOARCH=arm64 $(BUILD_ARGS) go build -o bin/psi-collector $(BUILD_FLAGS) main.go | ||
|
||
local-check-dir: | ||
ssh local_eve "mkdir -p /persist/memory-monitor/psi-collector" | ||
|
||
local-install: local-check-dir | ||
scp -O bin/psi-collector local_eve:/persist/memory-monitor/psi-collector | ||
|
||
local-run: | ||
ssh local_eve /persist/memory-monitor/psi-collector/psi-collector | ||
|
||
local-get-results: | ||
scp -O local_eve:/persist/memory-monitor/output/psi.txt . | ||
|
||
local-view-results: | ||
make -C ../../../../../tools/psi-visualizer prepare-env | ||
source ../../../../../tools/psi-visualizer/venv/bin/activate && python ../../../../../tools/psi-visualizer/visualize.py psi.txt | ||
|
||
help: | ||
@echo "build - build the binary" | ||
@echo "local-install - install the binary on local_eve" | ||
@echo local-run - run the binary on local_eve" | ||
@echo "local-get-results - get the results from local_eve" | ||
@echo "local-view-results - view the results, using psi-visualizer" | ||
@echo "help - show this help message" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
# Pressure Stall Information (PSI) Collector | ||
|
||
The PSI is a kernel feature that provides information about the pressure on the | ||
memory, CPU, and IO subsystems. In our case, we are interested in the memory | ||
pressure. | ||
|
||
The PSI collector is a tool that collects PSI metrics from the kernel and | ||
outputs them in a format that can be consumed by a visualization tool. | ||
|
||
## Requirements | ||
|
||
The PSI collector requires the kernel to have the PSI feature enabled. The PSI | ||
feature is available in the Linux kernel starting from version 4.20, but it is | ||
disabled by default. To enable the PSI feature, the kernel must be compiled with | ||
the `CONFIG_PSI` option enabled. | ||
|
||
## Output | ||
|
||
The output can be found in the `/persist/memory-monitor/output/psi.txt` file. | ||
|
||
The output is a series of lines, where each line represents a single snapshot | ||
of the PSI metrics. They are formatted as follows: | ||
|
||
```text | ||
date time someAvg10 someAvg60 someAvg300 someTotal fullAvg10 fullAvg60 fullAvg300 fullTotal | ||
``` | ||
|
||
### Visualization | ||
|
||
The PSI collector output can be visualized using the PSI visualizer tool. The | ||
tool is available in [psi-visualizer](../../../../../tools/psi-visualizer). | ||
For more information on how to use the PSI visualizer, see the tool's | ||
[README](../../../../../tools/psi-visualizer/README.md). | ||
|
||
## EVE Integration | ||
|
||
The PSI collector is integrated with the Pillar agentlog component. The PSI | ||
collector can be started and stopped by sending corresponding commands to the | ||
Pillar. The command to start the PSI collector are integrated as a part of the | ||
eve script. | ||
|
||
### Start | ||
|
||
To start the PSI collector, run the following command: | ||
|
||
```sh | ||
eve psi-collector start | ||
``` | ||
|
||
### Stop | ||
|
||
To stop the PSI collector, run the following command: | ||
|
||
```sh | ||
eve psi-collector stop | ||
``` | ||
|
||
## Standalone Usage | ||
|
||
For the older versions of EVE, the PSI collector can be run as a standalone | ||
tool. For that one needs to build the PSI collector binary and copy it to the | ||
target device. | ||
|
||
Worth noting that in this case, EVE Kernel should have the PSI feature enabled. | ||
Most probably, the kernel should be recompiled with the `CONFIG_PSI` option. | ||
|
||
### Building | ||
|
||
To build the PSI collector, run the following command: | ||
|
||
```sh | ||
make build | ||
``` | ||
|
||
To build the binary for ARM architecture, run: | ||
|
||
```sh | ||
make build-arm | ||
``` | ||
|
||
The binary will be placed in the `bin` directory. | ||
|
||
### Running | ||
|
||
After building the binary, copy it to the target device, preferably to the | ||
`/persist/memory-monitor` directory. Then run the binary: | ||
|
||
```sh | ||
/persist/memory-monitor/psi-collector | ||
``` | ||
|
||
## Local make targets | ||
|
||
In the case of running EVE on a local machine, in QEMU, with SSH access enabled, | ||
and available as `local_eve`, the following make targets can be used: | ||
|
||
* local-install - install the binary on local_eve | ||
* local-run - run the binary on local_eve | ||
* local-get-results - get the results from local_eve | ||
* local-view-results - view the results, using psi-visualizer |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"log/syslog" | ||
"os" | ||
"os/signal" | ||
"syscall" | ||
|
||
"github.com/sirupsen/logrus" | ||
|
||
"github.com/lf-edge/eve/pkg/pillar/agentlog" | ||
"github.com/lf-edge/eve/pkg/pillar/base" | ||
"github.com/lf-edge/eve/pkg/pillar/types" | ||
) | ||
|
||
const ( | ||
//PIDFile is the file to store the PID | ||
PIDFile = types.MemoryMonitorDir + "/psi-collector/psi-collector.pid" | ||
) | ||
|
||
var log *base.LogObject | ||
|
||
func createPIDFile() error { | ||
f, err := os.Create(PIDFile) | ||
if err != nil { | ||
log.Errorf("Failed to create PID file: %v", err) | ||
return err | ||
} | ||
defer f.Close() | ||
_, err = f.WriteString(fmt.Sprintf("%d", os.Getpid())) | ||
if err != nil { | ||
log.Errorf("Failed to write PID to file: %v", err) | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
func getPIDFromFile() (int, error) { | ||
f, err := os.Open(PIDFile) | ||
if err != nil { | ||
log.Errorf("Failed to open PID file: %v", err) | ||
return 0, err | ||
} | ||
defer f.Close() | ||
var pid int | ||
_, err = fmt.Fscanf(f, "%d", &pid) | ||
if err != nil { | ||
log.Errorf("Failed to read PID from file: %v", err) | ||
return 0, err | ||
} | ||
return pid, nil | ||
} | ||
|
||
func daemonize() error { | ||
|
||
// Check if the process is already daemonized by checking the environment variable | ||
if os.Getenv("DAEMONIZED") == "1" { | ||
return nil | ||
} | ||
|
||
// If it's not daemonized, daemonize it | ||
|
||
log.Noticef("Starting Memory PSI Collector") | ||
|
||
filePath, err := os.Executable() | ||
if err != nil { | ||
log.Errorf("Failed to get executable path: %v", err) | ||
return err | ||
} | ||
args := os.Args | ||
env := os.Environ() | ||
// Add the daemon env variable to differentiate the daemon process | ||
env = append(env, "DAEMONIZED=1") | ||
// Open /dev/null for the child process | ||
devNull, err := os.OpenFile("/dev/null", os.O_RDWR, 0) | ||
if err != nil { | ||
log.Errorf("Failed to open /dev/null: %v", err) | ||
return err | ||
} | ||
forkAttr := &syscall.ProcAttr{ | ||
// Files is the set of file descriptors to be duped into the child's | ||
Files: []uintptr{devNull.Fd(), devNull.Fd(), devNull.Fd()}, | ||
Sys: &syscall.SysProcAttr{ | ||
Setsid: true, // Create a new session to detach from the terminal | ||
}, | ||
Env: env, | ||
} | ||
|
||
// Fork off the parent process | ||
_, err = syscall.ForkExec(filePath, args, forkAttr) | ||
if err != nil { | ||
log.Errorf("Failed to fork: %v", err) | ||
return err | ||
} | ||
os.Exit(0) | ||
return nil | ||
} | ||
|
||
func main() { | ||
|
||
// Create a logger | ||
logger := logrus.New() | ||
|
||
// Create a syslog writer | ||
syslogWriter, err := syslog.New(syslog.LOG_NOTICE|syslog.LOG_DAEMON, "psi-collector") | ||
if err != nil { | ||
fmt.Println("Failed to create syslog writer: ", err) | ||
os.Exit(1) | ||
} | ||
// Set the output of the logger to the syslog writer | ||
logger.SetOutput(syslogWriter) | ||
|
||
// Create a log object | ||
log = base.NewSourceLogObject(logger, "psi-collector", os.Getpid()) | ||
|
||
// Check if the collector is already running | ||
if _, err := os.Stat(PIDFile); err == nil { | ||
savedPid, err := getPIDFromFile() | ||
if err != nil { | ||
log.Errorf("Failed to get PID from file: %v", err) | ||
return | ||
} | ||
if savedPid != os.Getpid() { | ||
log.Errorf("Memory PSI Collector is already running with PID: %d", savedPid) | ||
return | ||
} | ||
} | ||
|
||
err = daemonize() | ||
if err != nil { | ||
log.Errorf("Failed to daemonize: %v", err) | ||
return | ||
} | ||
|
||
// Create a PID file | ||
err = createPIDFile() | ||
if err != nil { | ||
log.Errorf("Failed to create PID file: %v", err) | ||
return | ||
} | ||
defer os.Remove(PIDFile) | ||
|
||
signalChan := make(chan os.Signal, 1) | ||
signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) | ||
go func() { | ||
<-signalChan | ||
os.Remove(PIDFile) | ||
log.Noticef("Memory PSI Collector stopped") | ||
os.Exit(0) | ||
}() | ||
|
||
err = agentlog.MemoryPSICollector(context.Background(), log) | ||
if err != nil { | ||
log.Errorf("MemoryPSICollector failed: %v", err) | ||
} | ||
} |