fix(container): apply less accurate solution to calculate container status for swarm environment [BE-12256] (#1225)

pull/10306/merge
Oscar Zhou 2025-09-18 16:29:35 +12:00 committed by GitHub
parent eedf37d18a
commit 666d51482e
4 changed files with 62 additions and 10 deletions

View File

@ -3,6 +3,7 @@ package stats
import (
"context"
"errors"
"strings"
"sync"
"github.com/docker/docker/api/types/container"
@ -20,7 +21,11 @@ type DockerClient interface {
ContainerInspect(ctx context.Context, containerID string) (container.InspectResponse, error)
}
func CalculateContainerStats(ctx context.Context, cli DockerClient, containers []container.Summary) (ContainerStats, error) {
func CalculateContainerStats(ctx context.Context, cli DockerClient, isSwarm bool, containers []container.Summary) (ContainerStats, error) {
if isSwarm {
return CalculateContainerStatsForSwarm(containers), nil
}
var running, stopped, healthy, unhealthy int
var mu sync.Mutex
@ -90,3 +95,31 @@ func getContainerStatus(state *container.State) ContainerStats {
return stat
}
// This is a temporary workaround to calculate container stats for Swarm
// TODO: Remove this once we have a proper way to calculate container stats for Swarm
func CalculateContainerStatsForSwarm(containers []container.Summary) ContainerStats {
var running, stopped, healthy, unhealthy int
for _, container := range containers {
switch container.State {
case "running":
running++
case "exited", "stopped":
stopped++
}
if strings.Contains(container.Status, "(healthy)") {
healthy++
} else if strings.Contains(container.Status, "(unhealthy)") {
unhealthy++
}
}
return ContainerStats{
Running: running,
Stopped: stopped,
Healthy: healthy,
Unhealthy: unhealthy,
Total: len(containers),
}
}

View File

@ -79,7 +79,7 @@ func TestCalculateContainerStats(t *testing.T) {
// Call the function and measure time
startTime := time.Now()
stats, err := CalculateContainerStats(context.Background(), mockClient, containers)
stats, err := CalculateContainerStats(context.Background(), mockClient, false, containers)
require.NoError(t, err, "failed to calculate container stats")
duration := time.Since(startTime)
@ -120,7 +120,7 @@ func TestCalculateContainerStatsAllErrors(t *testing.T) {
mockClient.On("ContainerInspect", mock.Anything, "container2").Return(container.InspectResponse{}, errors.New("permission denied"))
// Call the function
stats, err := CalculateContainerStats(context.Background(), mockClient, containers)
stats, err := CalculateContainerStats(context.Background(), mockClient, false, containers)
// Assert that an error was returned
require.Error(t, err, "should return error when all containers fail to inspect")
@ -232,3 +232,22 @@ func TestGetContainerStatus(t *testing.T) {
})
}
}
func TestCalculateContainerStatsForSwarm(t *testing.T) {
containers := []container.Summary{
{State: "running"},
{State: "running", Status: "Up 5 minutes (healthy)"},
{State: "exited"},
{State: "stopped"},
{State: "running", Status: "Up 10 minutes"},
{State: "running", Status: "Up about an hour (unhealthy)"},
}
stats := CalculateContainerStatsForSwarm(containers)
assert.Equal(t, 4, stats.Running)
assert.Equal(t, 2, stats.Stopped)
assert.Equal(t, 1, stats.Healthy)
assert.Equal(t, 1, stats.Unhealthy)
assert.Equal(t, 6, stats.Total)
}

View File

@ -143,7 +143,7 @@ func (h *Handler) dashboard(w http.ResponseWriter, r *http.Request) *httperror.H
stackCount = len(stacks)
}
containersStats, err := stats.CalculateContainerStats(r.Context(), cli, containers)
containersStats, err := stats.CalculateContainerStats(r.Context(), cli, info.Swarm.ControlAvailable, containers)
if err != nil {
return httperror.InternalServerError("Unable to retrieve Docker containers stats", err)
}

View File

@ -209,16 +209,16 @@ func dockerSnapshotContainers(snapshot *portainer.DockerSnapshot, cli *client.Cl
snapshot.GpuUseAll = gpuUseAll
snapshot.GpuUseList = gpuUseList
stats, err := stats.CalculateContainerStats(ctx, cli, containers)
result, err := stats.CalculateContainerStats(ctx, cli, snapshot.Swarm, containers)
if err != nil {
return fmt.Errorf("failed to calculate container stats: %w", err)
}
snapshot.ContainerCount = stats.Total
snapshot.RunningContainerCount = stats.Running
snapshot.StoppedContainerCount = stats.Stopped
snapshot.HealthyContainerCount = stats.Healthy
snapshot.UnhealthyContainerCount = stats.Unhealthy
snapshot.ContainerCount = result.Total
snapshot.RunningContainerCount = result.Running
snapshot.StoppedContainerCount = result.Stopped
snapshot.HealthyContainerCount = result.Healthy
snapshot.UnhealthyContainerCount = result.Unhealthy
snapshot.StackCount += len(stacks)
for _, container := range containers {