Compare commits
1 commit
| Author | SHA1 | Date | |
|---|---|---|---|
| f9954978ba |
5 changed files with 509 additions and 3618 deletions
|
|
@ -2,12 +2,12 @@ name: Generate Power Graph
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ master, dev ]
|
branches: [ master ]
|
||||||
paths: [ power_log.csv ]
|
paths: [ power_log.csv ]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
plot:
|
plot:
|
||||||
runs-on: [docker]
|
runs-on: [self-hosted]
|
||||||
env:
|
env:
|
||||||
BRANCH: ${{ github.ref_name }}
|
BRANCH: ${{ github.ref_name }}
|
||||||
COMMIT: ${{ github.sha }}
|
COMMIT: ${{ github.sha }}
|
||||||
|
|
@ -36,3 +36,4 @@ jobs:
|
||||||
with:
|
with:
|
||||||
name: power-graph
|
name: power-graph
|
||||||
path: power_graph.png
|
path: power_graph.png
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ A Forgejo workflow (`.forgejo/workflows/power-graph.yml`) automates the chart ge
|
||||||
|
|
||||||
What it does:
|
What it does:
|
||||||
|
|
||||||
1. Runs on a self-hosted runner because it needs local hwmon logs and Python packages.
|
1. Runs on a self-hosted runner for separation of processing (You will need to set one up if using forgejo).
|
||||||
2. Manually clones the repo with a read token stored in `secrets.PAT_READ_REPO`.
|
2. Manually clones the repo with a read token stored in `secrets.PAT_READ_REPO`.
|
||||||
3. Installs Python (via `apt`), prepares a virtualenv, and pulls `pandas` + `matplotlib`.
|
3. Installs Python (via `apt`), prepares a virtualenv, and pulls `pandas` + `matplotlib`.
|
||||||
4. Executes `python plot_power.py`, producing `power_graph.png`.
|
4. Executes `python plot_power.py`, producing `power_graph.png`.
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ APU_HWMON="/sys/class/hwmon/hwmon5" # APU sensor
|
||||||
GPU_HWMON="/sys/class/drm/card0/device/hwmon/hwmon4" # dGPU sensor
|
GPU_HWMON="/sys/class/drm/card0/device/hwmon/hwmon4" # dGPU sensor
|
||||||
INTERVAL=1 # seconds between refreshes
|
INTERVAL=1 # seconds between refreshes
|
||||||
LOGFILE="power_log.csv" # CSV output file
|
LOGFILE="power_log.csv" # CSV output file
|
||||||
RETENTION_SECONDS=3600 # keep roughly 1 hour of samples
|
MAX_LINES=500 # limit to last 500 lines
|
||||||
|
|
||||||
# --- Helpers ---
|
# --- Helpers ---
|
||||||
read_watts() {
|
read_watts() {
|
||||||
|
|
@ -57,16 +57,12 @@ while true; do
|
||||||
# --- Append to CSV ---
|
# --- Append to CSV ---
|
||||||
echo "$TIME,$APU_PWR,$GPU_PWR,$TOTAL,$APU_TEMP,$GPU_TEMP" >> "$LOGFILE"
|
echo "$TIME,$APU_PWR,$GPU_PWR,$TOTAL,$APU_TEMP,$GPU_TEMP" >> "$LOGFILE"
|
||||||
|
|
||||||
# --- Keep roughly 1 hour of samples (plus header) ---
|
# --- Keep only last $MAX_LINES lines ---
|
||||||
MAX_SAMPLES=$(( (RETENTION_SECONDS + INTERVAL - 1) / INTERVAL ))
|
|
||||||
MAX_LINES_WITH_HEADER=$(( MAX_SAMPLES + 1 ))
|
|
||||||
LINES=$(wc -l < "$LOGFILE")
|
LINES=$(wc -l < "$LOGFILE")
|
||||||
if (( LINES > MAX_LINES_WITH_HEADER )); then
|
if (( LINES > MAX_LINES )); then
|
||||||
{
|
tail -n $MAX_LINES "$LOGFILE" > "${LOGFILE}.tmp" && mv "${LOGFILE}.tmp" "$LOGFILE"
|
||||||
head -n 1 "$LOGFILE"
|
|
||||||
tail -n "$MAX_SAMPLES" "$LOGFILE"
|
|
||||||
} > "${LOGFILE}.tmp" && mv "${LOGFILE}.tmp" "$LOGFILE"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
sleep $INTERVAL
|
sleep $INTERVAL
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ import matplotlib.pyplot as plt
|
||||||
|
|
||||||
CSV_PATH = "power_log.csv"
|
CSV_PATH = "power_log.csv"
|
||||||
OUTPUT_FILE = "power_graph.png"
|
OUTPUT_FILE = "power_graph.png"
|
||||||
RETENTION_SECONDS = 3600
|
|
||||||
|
|
||||||
print(f"📊 Reading {CSV_PATH}...")
|
print(f"📊 Reading {CSV_PATH}...")
|
||||||
|
|
||||||
|
|
@ -24,11 +23,6 @@ df = pd.read_csv(
|
||||||
|
|
||||||
# --- Convert timestamps ---
|
# --- Convert timestamps ---
|
||||||
df["timestamp"] = pd.to_datetime(df["timestamp"], errors="coerce")
|
df["timestamp"] = pd.to_datetime(df["timestamp"], errors="coerce")
|
||||||
latest_ts = df["timestamp"].dropna().max()
|
|
||||||
if pd.notna(latest_ts):
|
|
||||||
cutoff = latest_ts - pd.Timedelta(seconds=RETENTION_SECONDS)
|
|
||||||
df = df[df["timestamp"] >= cutoff]
|
|
||||||
|
|
||||||
df["time_fmt"] = df["timestamp"].dt.strftime("%Y-%m-%d %H:%M:%S")
|
df["time_fmt"] = df["timestamp"].dt.strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
|
||||||
# --- Downsample if too many entries ---
|
# --- Downsample if too many entries ---
|
||||||
|
|
@ -67,3 +61,4 @@ plt.grid(axis="y", alpha=0.3)
|
||||||
plt.tight_layout()
|
plt.tight_layout()
|
||||||
plt.savefig(OUTPUT_FILE, dpi=150)
|
plt.savefig(OUTPUT_FILE, dpi=150)
|
||||||
print(f"✅ Saved graph: {OUTPUT_FILE}")
|
print(f"✅ Saved graph: {OUTPUT_FILE}")
|
||||||
|
|
||||||
|
|
|
||||||
4099
power_log.csv
4099
power_log.csv
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue