| 1 | package boot |
| 2 | |
| 3 | import ( |
| 4 | "fmt" |
| 5 | "strings" |
| 6 | |
| 7 | "reasonix/internal/extension" |
| 8 | ) |
| 9 | |
| 10 | // FormatRuntimeStatus renders a doctor-facing explanation of component |
| 11 | // activation state: why a component is Inactive/Failed and what the plan did. |
| 12 | func FormatRuntimeStatus(status *extension.RuntimeStatus) string { |
| 13 | if status == nil { |
| 14 | return "runtime status: unavailable\n" |
| 15 | } |
| 16 | var b strings.Builder |
| 17 | fmt.Fprintf(&b, "published generation: %d\n", status.PublishedGeneration) |
| 18 | if status.Plan != nil { |
| 19 | p := status.Plan |
| 20 | fmt.Fprintf(&b, "plan: from=%d to=%d prefixChanged=%v providerChanged=%v\n", p.FromGeneration, p.ToGeneration, p.PrefixChanged, p.ProviderChanged) |
| 21 | if len(p.Added) > 0 { |
| 22 | fmt.Fprintf(&b, " added: %s\n", joinIDs(p.Added)) |
| 23 | } |
| 24 | if len(p.Removed) > 0 { |
| 25 | fmt.Fprintf(&b, " removed: %s\n", joinIDs(p.Removed)) |
| 26 | } |
| 27 | if len(p.Reloaded) > 0 { |
| 28 | fmt.Fprintf(&b, " reloaded: %s\n", joinIDs(p.Reloaded)) |
| 29 | } |
| 30 | if len(p.Unchanged) > 0 { |
| 31 | fmt.Fprintf(&b, " unchanged: %s\n", joinIDs(p.Unchanged)) |
| 32 | } |
| 33 | } |
| 34 | if len(status.Components) == 0 { |
| 35 | b.WriteString("components: (none)\n") |
| 36 | } else { |
| 37 | b.WriteString("components:\n") |
| 38 | for _, c := range status.Components { |
| 39 | fmt.Fprintf(&b, " %s state=%s gen=%d", c.ID, c.State, c.Generation) |
| 40 | if c.Error != "" { |
| 41 | fmt.Fprintf(&b, " error=%s", c.Error) |
| 42 | } |
| 43 | b.WriteByte('\n') |
| 44 | for _, d := range c.Diagnostics { |
| 45 | fmt.Fprintf(&b, " - %s\n", d) |
| 46 | } |
| 47 | if c.State == extension.ComponentInactive && len(c.Diagnostics) == 0 && c.Error == "" { |
| 48 | b.WriteString(" - not activated (missing required dependency or optional runtime)\n") |
| 49 | } |
| 50 | } |
| 51 | } |
| 52 | if len(status.Receipts) > 0 { |
| 53 | b.WriteString("effect receipts:\n") |
| 54 | for _, r := range status.Receipts { |
| 55 | fmt.Fprintf(&b, " %s class=%d owner=%s compensation=%s\n", r.ID, r.Class, r.Owner, r.CompensationStatus) |
| 56 | } |
| 57 | } |
| 58 | m := extension.DefaultLifecycleMetrics.Snapshot() |
| 59 | fmt.Fprintf(&b, "metrics: publishes=%d drains=%d staleDrops=%d admitReject=%d noOpRebuilds=%d fullRebuilds=%d subgraphRebuilds=%d\n", |
| 60 | m.Publishes, m.Drains, m.StaleDrops, m.AdmissionRejected, m.NoOpRebuilds, m.FullRebuilds, m.SubgraphRebuilds) |
| 61 | return b.String() |
| 62 | } |
| 63 | |
| 64 | func joinIDs(ids []extension.ComponentID) string { |
| 65 | parts := make([]string, len(ids)) |
| 66 | for i, id := range ids { |
| 67 | parts[i] = string(id) |
| 68 | } |
| 69 | return strings.Join(parts, ", ") |
| 70 | } |
| 71 |