diff --git a/succbone/succd/http.go b/succbone/succd/http.go
index 61df8a8..4365428 100644
--- a/succbone/succd/http.go
+++ b/succbone/succd/http.go
@@ -70,6 +70,9 @@ type apiData struct {
// HighReached is true when the system has reached a high vacuum stage.
HighReached bool
}
+ // LoopLoad is a percentage expressing how busy the processing loop is; 100
+ // is full utilization; >100 is lag.
+ LoopLoad int64
// System junk.
System struct {
// Load of the system.
@@ -114,6 +117,7 @@ func (s *webServer) apiData(skipSystem bool) *apiData {
ad.Pumps.DPOn = state.dpOn
ad.Feedback.RoughReached = rough
ad.Feedback.HighReached = high
+ ad.LoopLoad = s.d.loopLoad()
ad.System.Load = load
ad.System.Hostname = hostname
return &ad
diff --git a/succbone/succd/index.html b/succbone/succd/index.html
index 08acab3..a26db5d 100644
--- a/succbone/succd/index.html
+++ b/succbone/succd/index.html
@@ -100,27 +100,29 @@ td > span {
Control |
RP |
-
+ |
|
DP |
-
+ |
|
-
+ |
|
Status |
- OK |
+ OK |
+ Load |
+ ... |
@@ -275,6 +277,7 @@ window.addEventListener("load", (_) => {
let ping = document.querySelector("#ping");
let trough = document.querySelector("#trough");
let thigh = document.querySelector("#thigh");
+ let load = document.querySelector("#load");
// Buttons
let pd = document.querySelector("#pd");
@@ -354,6 +357,7 @@ window.addEventListener("load", (_) => {
thigh.innerHTML = "NOK";
thigh.style = "background-color: #f06060";
}
+ load.innerHTML = data.LoopLoad.toString() + "%";
historicalPush(data.Pirani.MbarFloat);
ping.innerHTML = Date.now();
});
diff --git a/succbone/succd/process.go b/succbone/succd/process.go
index 026c13d..de99e84 100644
--- a/succbone/succd/process.go
+++ b/succbone/succd/process.go
@@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"sync"
+ "sync/atomic"
"time"
"k8s.io/klog"
@@ -23,6 +24,8 @@ type daemon struct {
gpioBelowRough gpio
gpioBelowHigh gpio
+ load atomic.Int64
+
// mu guards the state below.
mu sync.RWMutex
daemonState
@@ -62,11 +65,22 @@ func (d *daemonState) vacuumStatus() (rough, high bool) {
// process runs the pain acquisition and control loop of succd.
func (d *daemon) process(ctx context.Context) {
- ticker := time.NewTicker(time.Millisecond * 100)
+ var lastRun time.Time
+ hz := 100
+ period := time.Second / time.Duration(hz)
+ // Extra grace period for GC pauses and other non-realtime system jitter.
+ periodGrace := period + time.Millisecond*5
+
+ ticker := time.NewTicker(period)
defer ticker.Stop()
for {
select {
case <-ticker.C:
+ now := time.Now()
+ if elapsed := now.Sub(lastRun); !lastRun.IsZero() && elapsed > periodGrace {
+ klog.Warningf("Processing loop lag: took %s, want %s", elapsed, period)
+ }
+ lastRun = now
if err := d.processOnce(ctx); err != nil {
if errors.Is(err, ctx.Err()) {
return
@@ -75,6 +89,9 @@ func (d *daemon) process(ctx context.Context) {
time.Sleep(time.Second * 10)
}
}
+ runtime := time.Since(lastRun)
+ load := int64(100 * runtime / period)
+ d.load.Store(load)
case <-ctx.Done():
return
}
diff --git a/succbone/succd/process_controller.go b/succbone/succd/process_controller.go
index 0c0abc6..b7f689c 100644
--- a/succbone/succd/process_controller.go
+++ b/succbone/succd/process_controller.go
@@ -7,6 +7,7 @@ import "k8s.io/klog"
//
// This is a subset of daemon functions limited to a safe, explicit subset.
type daemonController interface {
+ loopLoad() int64
// snapshot returns an internally consistent copy of the daemon state.
snapshot() *daemonState
// rpSet enables/disables the roughing pump.
@@ -19,6 +20,10 @@ type daemonController interface {
ventPress()
}
+func (d *daemon) loopLoad() int64 {
+ return d.load.Load()
+}
+
func (d *daemon) snapshot() *daemonState {
d.mu.RLock()
ds := d.daemonState