compat: Add a stub container/stats handler for FreeBSD

The container/stats endpoint was unimplemented.  Copy over the structure
from the Linux endpoint handler, albeit with most of the stats
unpopulated.  This is similar to how "podman stats" returns very little
info on FreeBSD.  On the other hand, some orchestration tools will query
the stats endpoint to check liveness of a container, so it's useful to
implement it even though some fields are unimplemented for now.

Signed-off-by: Mark Johnston <mark.johnston@klarasystems.com>
This commit is contained in:
Mark Johnston committed 2025-09-02 16:14:25 +00:00
1 parent 6d0b28a5c4
commit a341a4ee24
1 file changed
+122 -1
@@ -3,15 +3,136 @@
package compat
import (
"encoding/json"
"fmt"
"net/http"
"time"
"github.com/containers/podman/v5/libpod"
"github.com/containers/podman/v5/libpod/define"
"github.com/containers/podman/v5/pkg/api/handlers/utils"
api "github.com/containers/podman/v5/pkg/api/types"
"github.com/docker/docker/api/types/container"
"github.com/sirupsen/logrus"
)
const DefaultStatsPeriod = 5 * time.Second
func StatsContainer(w http.ResponseWriter, r *http.Request) {
utils.Error(w, http.StatusBadRequest, fmt.Errorf("compat.StatsContainer not supported on FreeBSD"))
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
decoder := utils.GetDecoder(r)
query := struct {
Stream bool `schema:"stream"`
OneShot bool `schema:"one-shot"` // added schema for one shot
}{
Stream: true,
}
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err))
return
}
if query.Stream && query.OneShot { // mismatch. one-shot can only be passed with stream=false
utils.Error(w, http.StatusBadRequest, define.ErrInvalidArg)
return
}
name := utils.GetName(r)
ctnr, err := runtime.LookupContainer(name)
if err != nil {
utils.ContainerNotFound(w, name, err)
return
}
stats, err := ctnr.GetContainerStats(nil)
if err != nil {
utils.InternalServerError(w, fmt.Errorf("failed to obtain Container %s stats: %w", name, err))
return
}
coder := json.NewEncoder(w)
// Write header and content type.
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
if flusher, ok := w.(http.Flusher); ok {
flusher.Flush()
}
// Set up JSON encoder for streaming.
coder.SetEscapeHTML(true)
var preRead time.Time
var preCPUStats CPUStats
if query.Stream {
preRead = time.Now()
preCPUStats = CPUStats{
CPUUsage: container.CPUUsage{
TotalUsage: stats.CPUNano,
},
CPU: stats.CPU,
OnlineCPUs: 0,
ThrottlingData: container.ThrottlingData{},
}
}
streamLabel: // A label to flatten the scope
select {
case <-r.Context().Done():
logrus.Debugf("Client connection (container stats) cancelled")
default:
stats, err = ctnr.GetContainerStats(stats)
if err != nil {
logrus.Errorf("Unable to get container stats: %v", err)
return
}
s := StatsJSON{
Stats: Stats{
Read: time.Now(),
PreRead: preRead,
CPUStats: CPUStats{
CPUUsage: container.CPUUsage{
TotalUsage: stats.CPUNano,
},
CPU: stats.CPU,
OnlineCPUs: 0,
ThrottlingData: container.ThrottlingData{},
},
PreCPUStats: preCPUStats,
MemoryStats: container.MemoryStats{},
},
Name: stats.Name,
ID: stats.ContainerID,
}
var jsonOut interface{}
if utils.IsLibpodRequest(r) {
jsonOut = s
} else {
jsonOut = DockerStatsJSON(s)
}
if err := coder.Encode(jsonOut); err != nil {
logrus.Errorf("Unable to encode stats: %v", err)
return
}
if flusher, ok := w.(http.Flusher); ok {
flusher.Flush()
}
if !query.Stream || query.OneShot {
return
}
preRead = s.Read
bits, err := json.Marshal(s.CPUStats)
if err != nil {
logrus.Errorf("Unable to marshal cpu stats: %q", err)
}
if err := json.Unmarshal(bits, &preCPUStats); err != nil {
logrus.Errorf("Unable to unmarshal previous stats: %q", err)
}
time.Sleep(DefaultStatsPeriod)
goto streamLabel
}
}