carlos / platform Public

merge: only a hibernating instance may render as asleep (#30)

console: only a hibernating instance may render as asleep
Paul Campbell 36ff02064c17efcb7fd83fea72d1f7204194f360 parent d6e7d08parent 283fb15
4 files changed, +29 −3
  • internal/console/metricsview.go +13 −2
  • internal/console/server.go +5 −0
  • internal/console/server_test.go +7 −1
  • internal/console/templates/app.html +4 −0
diff --git a/internal/console/metricsview.go b/internal/console/metricsview.go
index a6defb365..be27dcb31 100644
--- a/internal/console/metricsview.go
+++ b/internal/console/metricsview.go
@@ -403,11 +403,22 @@ type InstanceMetrics struct {
MemBytes *int64
SparkCPU, SparkMem string
+
+ // Hibernate is the route's hibernation setting, copied in by the caller
+ // (handleApp) — the sweep cannot see it. It only matters when Reporting
+ // is false, where it decides what the absence of readings may claim: a
+ // hibernating instance is presumed asleep, anything else is NOT — it may
+ // be serving right now from outside per-instance accounting (a bespoke
+ // unit like keymail-relay.service, or an exec child of the agent), and
+ // the template must not assert a sleep state nobody observed.
+ Hibernate bool
}
// Reporting is the template's one question: did any box report this
-// instance's unit in the last sweep? False is the hibernating/absent case —
-// rendered as "asleep — no recent readings", never as zeros. (Reporting with
+// instance's unit in the last sweep? False is the no-readings case, whose
+// rendering splits on Hibernate: hibernating instances read "asleep — no
+// recent readings", everything else the state-neutral "no per-instance
+// readings" — never zeros either way. (Reporting with
// nil readings is a different, honest state: the unit is up but that metric
// was unreadable, which renders as the usual em-dash.)
func (m InstanceMetrics) Reporting() bool { return m.Box != "" }
diff --git a/internal/console/server.go b/internal/console/server.go
index a24650380..2dd00ad47 100644
--- a/internal/console/server.go
+++ b/internal/console/server.go
@@ -1237,15 +1237,20 @@ func (s *Server) handleApp(w http.ResponseWriter, r *http.Request) {
// with (the half-migrated state AccountMismatch describes) yields no
// metrics rather than someone else's.
hosts := make([]string, 0, len(views[0].Routes))
+ hib := make(map[string]bool, len(views[0].Routes))
for _, rt := range views[0].Routes {
if rt.Account != acct.Sqid {
continue
}
hosts = append(hosts, rt.Host)
+ hib[rt.Host] = rt.Hibernate
}
if am, err := s.OwnerAppMetrics(r.Context(), hosts); err != nil {
log.Printf("console: app metrics %s: %v", app, err)
} else {
+ for i := range am {
+ am[i].Hibernate = hib[am[i].Host]
+ }
p.AppMetrics = am
}
if events, err := s.store.Events(acct.ID, app, eventsCap); err != nil {
diff --git a/internal/console/server_test.go b/internal/console/server_test.go
index 9c5d054a9..29955b01a 100644
--- a/internal/console/server_test.go
+++ b/internal/console/server_test.go
@@ -1370,6 +1370,11 @@ func TestAppPageOwnerMetricsBoundary(t *testing.T) {
Status: "ready", App: "kass", Backing: registry.BackingUnit})
fx.addRoute(t, registry.Route{Host: "asleep.example.com", Kind: "instance",
Status: "ready", App: "kass", Backing: registry.BackingUnit, Hibernate: true})
+ // The keymail.dev shape: awake and serving, but from a process no
+ // carlos-app@ cgroup covers (bespoke unit / exec child), so the sweep
+ // has nothing for it. Non-hibernating, so "asleep" would be a lie.
+ fx.addRoute(t, registry.Route{Host: "unmetered.example.com", Kind: "instance",
+ Status: "ready", App: "kass"})
ctx := context.Background()
at := time.Now().UTC().Add(-time.Minute) // inside metricsFreshWindow
@@ -1398,7 +1403,8 @@ func TestAppPageOwnerMetricsBoundary(t *testing.T) {
}
for _, want := range []string{
"Resources", "owned.example.com", "11%", "1.0 KB",
- "asleep.example.com", "asleep — no recent readings",
+ "asleep.example.com</code> — asleep — no recent readings",
+ "unmetered.example.com</code> — no per-instance readings",
} {
if !strings.Contains(body, want) {
t.Fatalf("app page missing %q in:\n%s", want, body)
diff --git a/internal/console/templates/app.html b/internal/console/templates/app.html
index 8684587a9..44a0cf899 100644
--- a/internal/console/templates/app.html
+++ b/internal/console/templates/app.html
@@ -77,7 +77,11 @@
</div>
</section>
{{else}}
+{{if .Hibernate}}
<p class="small"><code>{{.Host}}</code> — asleep — no recent readings</p>
+{{else}}
+<p class="small"><code>{{.Host}}</code> — no per-instance readings</p>
+{{end}}
{{end}}
{{end}}
{{end}}