mirror of
https://github.com/containers/podman.git
synced 2025-10-30 01:17:00 +08:00
currently, setting any sort of resource limit in a pod does nothing. With the newly refactored creation process in c/common, podman ca now set resources at a pod level meaning that resource related flags can now be exposed to podman pod create. cgroupfs and systemd are both supported with varying completion. cgroupfs is a much simpler process and one that is virtually complete for all resource types, the flags now just need to be added. systemd on the other hand has to be handeled via the dbus api meaning that the limits need to be passed as recognized properties to systemd. The properties added so far are the ones that podman pod create supports as well as `cpuset-mems` as this will be the next flag I work on. Signed-off-by: Charlie Doern <cdoern@redhat.com>
233 lines
6.5 KiB
Go
233 lines
6.5 KiB
Go
package compat
|
|
|
|
import (
|
|
"encoding/json"
|
|
"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"
|
|
docker "github.com/docker/docker/api/types"
|
|
"github.com/gorilla/schema"
|
|
runccgroups "github.com/opencontainers/runc/libcontainer/cgroups"
|
|
"github.com/pkg/errors"
|
|
"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, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
|
|
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, errors.Wrapf(err, "failed to obtain Container %s stats", name))
|
|
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: "",
|
|
}
|
|
|
|
cfg := ctnr.Config()
|
|
memoryLimit := cgroupStat.MemoryStats.Usage.Limit
|
|
if cfg.Spec.Linux != nil && cfg.Spec.Linux.Resources != nil && cfg.Spec.Linux.Resources.Memory != nil && *cfg.Spec.Linux.Resources.Memory.Limit > 0 {
|
|
memoryLimit = uint64(*cfg.Spec.Linux.Resources.Memory.Limit)
|
|
}
|
|
|
|
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.Limit,
|
|
Stats: nil,
|
|
Failcnt: 0,
|
|
Limit: memoryLimit,
|
|
Commit: 0,
|
|
CommitPeak: 0,
|
|
PrivateWorkingSet: 0,
|
|
},
|
|
},
|
|
Name: stats.Name,
|
|
ID: stats.ContainerID,
|
|
Networks: net,
|
|
}
|
|
|
|
if err := coder.Encode(s); 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
|
|
}
|