Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## [Unreleased]

### Added
- Container memory utilization for percentage-based alert rules and dashboard panels

## [0.4.0-beta.7] - 2026-08-23

### Fixed
Expand Down
11 changes: 11 additions & 0 deletions src/components/AlertRulesPanel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,17 @@ describe("AlertRulesPanel", () => {
expect(wrapper.text()).toContain("CPU usage above 80% for 60s, on shop");
});

it("presents container memory utilization as a percentage", async () => {
vi.mocked(observabilityApi.alertRules).mockResolvedValue({
data: [{ ...rule, name: "Memory high", metric: "container.memory.utilization", threshold: 90 }],
} as any);

const wrapper = mountPanel();
await flushPromises();

expect(wrapper.text()).toContain("Container memory utilization (%) above 90% for 60s, on shop");
});

it("marks a rule that is currently firing", async () => {
vi.mocked(observabilityApi.firingAlerts).mockResolvedValue({
data: [{ rule_id: "r1", rule_name: "CPU high", state: "firing" }],
Expand Down
18 changes: 16 additions & 2 deletions src/components/AlertRulesPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,22 @@ const firingSnapshot = (id?: string): string => {
return ev.snapshot.map((c) => `${c.container} (${asBytes ? bytes(c.value) : `${c.value.toFixed(1)}%`})`).join(", ");
};

const allMetricOptions: { value: string; label: string; unit: string; host?: boolean; rate?: boolean }[] = [
const allMetricOptions: {
value: string;
label: string;
unit: string;
hint?: string;
host?: boolean;
rate?: boolean;
}[] = [
{ value: METRIC.cpu, label: "Container CPU usage", unit: "percent" },
{ value: METRIC.memUsage, label: "Container memory usage", unit: "bytes" },
{
value: METRIC.memUtilization,
label: "Container memory utilization (%)",
unit: "percent",
hint: "A percentage of the container's effective memory limit.",
},
{ value: METRIC.memUsage, label: "Container memory usage (bytes)", unit: "bytes" },
{ value: METRIC.netRx, label: "Container network in (per second)", unit: "bytes", rate: true },
{ value: METRIC.netTx, label: "Container network out (per second)", unit: "bytes", rate: true },
{ value: METRIC.hostCpu, label: "Host CPU", unit: "percent", host: true },
Expand Down Expand Up @@ -212,6 +225,7 @@ watch(isHostMetric, (host) => {

const unitHint = computed(() => {
const opt = metricOptions.value.find((m) => m.value === draft.value.metric);
if (opt?.hint) return opt.hint;
if (opt?.unit !== "bytes") return "A percentage.";
return opt.rate ? "In bytes per second." : "In bytes.";
});
Expand Down
1 change: 1 addition & 0 deletions src/components/dashboards/PanelCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ const series = ref<MetricSeries | null>(null);
const unit = computed<"percent" | "bytes" | "ms" | "count">(() => {
if (props.panel.source === "serving") return props.panel.series === "latency" ? "ms" : "count";
if (props.panel.series.startsWith("container.cpu")) return "percent";
if (props.panel.series === "container.memory.utilization") return "percent";
if (props.panel.series.startsWith("container.memory")) return "bytes";
if (props.panel.series.startsWith("container.network")) return "bytes";
return "count";
Expand Down
1 change: 1 addition & 0 deletions src/components/dashboards/PanelEditorModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ const containerSeries = [
{ value: "container.cpu.usage", label: "CPU usage" },
{ value: "container.memory.usage", label: "Memory usage" },
{ value: "container.memory.limit", label: "Memory limit" },
{ value: "container.memory.utilization", label: "Memory utilization" },
{ value: "container.network.io.rx", label: "Network in" },
{ value: "container.network.io.tx", label: "Network out" },
];
Expand Down
1 change: 1 addition & 0 deletions src/services/observability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export const METRIC = {
cpu: "container.cpu.usage",
memUsage: "container.memory.usage",
memLimit: "container.memory.limit",
memUtilization: "container.memory.utilization",
netRx: "container.network.io.rx",
netTx: "container.network.io.tx",
hostCpu: "system.cpu.utilization",
Expand Down
Loading