mirror of
https://github.com/containers/podman.git
synced 2025-08-06 19:44:14 +08:00

We use the same endpoint for libpod and docker compat API. However as reported docker returns "id" lowercase. Because we cannot break the libpod API right now keep the output for the libpod endpoint and only change the docker one. To do so simply use two types that we can cast with different JSON tags. Fixes #17869 Signed-off-by: Paul Holzinger <pholzing@redhat.com>
251 lines
6.9 KiB
Go
251 lines
6.9 KiB
Go
package compat
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/containers/common/pkg/cgroups"
|
|
"github.com/containers/podman/v4/libpod"
|
|
"github.com/containers/podman/v4/libpod/define"
|
|
"github.com/containers/podman/v4/pkg/api/handlers/utils"
|
|
api "github.com/containers/podman/v4/pkg/api/types"
|
|
"github.com/containers/storage/pkg/system"
|
|
docker "github.com/docker/docker/api/types"
|
|
"github.com/gorilla/schema"
|
|
runccgroups "github.com/opencontainers/runc/libcontainer/cgroups"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
const DefaultStatsPeriod = 5 * time.Second
|
|
|
|
func StatsContainer(w http.ResponseWriter, r *http.Request) {
|
|
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
|
|
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
|
|
|
|
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()
|
|
systemUsage, _ := cgroups.GetSystemCPUUsage()
|
|
preCPUStats = CPUStats{
|
|
CPUUsage: docker.CPUUsage{
|
|
TotalUsage: stats.CPUNano,
|
|
PercpuUsage: stats.PerCPU,
|
|
UsageInKernelmode: stats.CPUSystemNano,
|
|
UsageInUsermode: stats.CPUNano - stats.CPUSystemNano,
|
|
},
|
|
CPU: stats.CPU,
|
|
SystemUsage: systemUsage,
|
|
OnlineCPUs: 0,
|
|
ThrottlingData: docker.ThrottlingData{},
|
|
}
|
|
}
|
|
|
|
streamLabel: // A label to flatten the scope
|
|
select {
|
|
case <-r.Context().Done():
|
|
logrus.Debugf("Client connection (container stats) cancelled")
|
|
|
|
default:
|
|
// Container stats
|
|
stats, err = ctnr.GetContainerStats(stats)
|
|
if err != nil {
|
|
logrus.Errorf("Unable to get container stats: %v", err)
|
|
return
|
|
}
|
|
inspect, err := ctnr.Inspect(false)
|
|
if err != nil {
|
|
logrus.Errorf("Unable to inspect container: %v", err)
|
|
return
|
|
}
|
|
// Cgroup stats
|
|
cgroupPath, err := ctnr.CgroupPath()
|
|
if err != nil {
|
|
logrus.Errorf("Unable to get cgroup path of container: %v", err)
|
|
return
|
|
}
|
|
cgroup, err := cgroups.Load(cgroupPath)
|
|
if err != nil {
|
|
logrus.Errorf("Unable to load cgroup: %v", err)
|
|
return
|
|
}
|
|
cgroupStat, err := cgroup.Stat()
|
|
if err != nil {
|
|
logrus.Errorf("Unable to get cgroup stats: %v", err)
|
|
return
|
|
}
|
|
|
|
// FIXME: network inspection does not yet work entirely
|
|
net := make(map[string]docker.NetworkStats)
|
|
networkName := inspect.NetworkSettings.EndpointID
|
|
if networkName == "" {
|
|
networkName = "network"
|
|
}
|
|
net[networkName] = docker.NetworkStats{
|
|
RxBytes: stats.NetInput,
|
|
RxPackets: 0,
|
|
RxErrors: 0,
|
|
RxDropped: 0,
|
|
TxBytes: stats.NetOutput,
|
|
TxPackets: 0,
|
|
TxErrors: 0,
|
|
TxDropped: 0,
|
|
EndpointID: inspect.NetworkSettings.EndpointID,
|
|
InstanceID: "",
|
|
}
|
|
|
|
resources := ctnr.LinuxResources()
|
|
memoryLimit := cgroupStat.MemoryStats.Usage.Limit
|
|
if resources != nil && resources.Memory != nil && *resources.Memory.Limit > 0 {
|
|
memoryLimit = uint64(*resources.Memory.Limit)
|
|
}
|
|
|
|
memInfo, err := system.ReadMemInfo()
|
|
if err != nil {
|
|
logrus.Errorf("Unable to get cgroup stats: %v", err)
|
|
return
|
|
}
|
|
// cap the memory limit to the available memory.
|
|
if memInfo.MemTotal > 0 && memoryLimit > uint64(memInfo.MemTotal) {
|
|
memoryLimit = uint64(memInfo.MemTotal)
|
|
}
|
|
|
|
systemUsage, _ := cgroups.GetSystemCPUUsage()
|
|
s := StatsJSON{
|
|
Stats: Stats{
|
|
Read: time.Now(),
|
|
PreRead: preRead,
|
|
PidsStats: docker.PidsStats{
|
|
Current: cgroupStat.PidsStats.Current,
|
|
Limit: 0,
|
|
},
|
|
BlkioStats: docker.BlkioStats{
|
|
IoServiceBytesRecursive: toBlkioStatEntry(cgroupStat.BlkioStats.IoServiceBytesRecursive),
|
|
IoServicedRecursive: nil,
|
|
IoQueuedRecursive: nil,
|
|
IoServiceTimeRecursive: nil,
|
|
IoWaitTimeRecursive: nil,
|
|
IoMergedRecursive: nil,
|
|
IoTimeRecursive: nil,
|
|
SectorsRecursive: nil,
|
|
},
|
|
CPUStats: CPUStats{
|
|
CPUUsage: docker.CPUUsage{
|
|
TotalUsage: cgroupStat.CpuStats.CpuUsage.TotalUsage,
|
|
PercpuUsage: cgroupStat.CpuStats.CpuUsage.PercpuUsage,
|
|
UsageInKernelmode: cgroupStat.CpuStats.CpuUsage.UsageInKernelmode,
|
|
UsageInUsermode: cgroupStat.CpuStats.CpuUsage.TotalUsage - cgroupStat.CpuStats.CpuUsage.UsageInKernelmode,
|
|
},
|
|
CPU: stats.CPU,
|
|
SystemUsage: systemUsage,
|
|
OnlineCPUs: uint32(len(cgroupStat.CpuStats.CpuUsage.PercpuUsage)),
|
|
ThrottlingData: docker.ThrottlingData{
|
|
Periods: 0,
|
|
ThrottledPeriods: 0,
|
|
ThrottledTime: 0,
|
|
},
|
|
},
|
|
PreCPUStats: preCPUStats,
|
|
MemoryStats: docker.MemoryStats{
|
|
Usage: cgroupStat.MemoryStats.Usage.Usage,
|
|
MaxUsage: cgroupStat.MemoryStats.Usage.MaxUsage,
|
|
Stats: nil,
|
|
Failcnt: 0,
|
|
Limit: memoryLimit,
|
|
Commit: 0,
|
|
CommitPeak: 0,
|
|
PrivateWorkingSet: 0,
|
|
},
|
|
},
|
|
Name: stats.Name,
|
|
ID: stats.ContainerID,
|
|
Networks: net,
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
func toBlkioStatEntry(entries []runccgroups.BlkioStatEntry) []docker.BlkioStatEntry {
|
|
results := make([]docker.BlkioStatEntry, len(entries))
|
|
for i, e := range entries {
|
|
bits, err := json.Marshal(e)
|
|
if err != nil {
|
|
logrus.Errorf("Unable to marshal blkio stats: %q", err)
|
|
}
|
|
if err := json.Unmarshal(bits, &results[i]); err != nil {
|
|
logrus.Errorf("Unable to unmarshal blkio stats: %q", err)
|
|
}
|
|
}
|
|
return results
|
|
}
|