mirror of
https://github.com/containers/podman.git
synced 2025-10-10 15:57:33 +08:00
Merge pull request #26912 from markjdb/main
Fix several FreeBSD integration problems
This commit is contained in:
@ -352,7 +352,7 @@ func (c *Container) jailName() (string, error) {
|
||||
ic = c
|
||||
}
|
||||
|
||||
if ic.state.NetNS != "" {
|
||||
if ic.state.NetNS != "" && ic != c {
|
||||
return ic.state.NetNS + "." + c.ID(), nil
|
||||
} else {
|
||||
return c.ID(), nil
|
||||
|
@ -57,7 +57,7 @@ func (c *Container) Top(descriptors []string) ([]string, error) {
|
||||
}
|
||||
}
|
||||
if supportedDescriptors {
|
||||
descriptors = []string{"-ao", strings.Join(descriptors, ",")}
|
||||
descriptors = []string{"-o", strings.Join(descriptors, ",")}
|
||||
}
|
||||
|
||||
// Note that the descriptors to ps(1) must be shlexed (see #12452).
|
||||
|
122
pkg/api/handlers/compat/containers_stats.go
Normal file
122
pkg/api/handlers/compat/containers_stats.go
Normal file
@ -0,0 +1,122 @@
|
||||
//go:build !remote
|
||||
|
||||
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/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const defaultStatsPeriod = 5 * time.Second
|
||||
|
||||
func StatsContainer(w http.ResponseWriter, r *http.Request) {
|
||||
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 = getPreCPUStats(stats)
|
||||
}
|
||||
|
||||
onlineCPUs, err := libpod.GetOnlineCPUs(ctnr)
|
||||
if err != nil {
|
||||
utils.InternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
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, err := statsContainerJSON(ctnr, stats, preCPUStats, onlineCPUs)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.Stats.PreRead = preRead
|
||||
|
||||
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
|
||||
}
|
||||
}
|
@ -3,15 +3,40 @@
|
||||
package compat
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/containers/podman/v5/pkg/api/handlers/utils"
|
||||
"github.com/containers/podman/v5/libpod"
|
||||
"github.com/containers/podman/v5/libpod/define"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
)
|
||||
|
||||
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"))
|
||||
func getPreCPUStats(stats *define.ContainerStats) CPUStats {
|
||||
return CPUStats{
|
||||
CPUUsage: container.CPUUsage{
|
||||
TotalUsage: stats.CPUNano,
|
||||
},
|
||||
CPU: stats.CPU,
|
||||
OnlineCPUs: 0,
|
||||
ThrottlingData: container.ThrottlingData{},
|
||||
}
|
||||
}
|
||||
|
||||
func statsContainerJSON(ctnr *libpod.Container, stats *define.ContainerStats, preCPUStats CPUStats, onlineCPUs int) (StatsJSON, error) {
|
||||
return StatsJSON{
|
||||
Stats: Stats{
|
||||
Read: time.Now(),
|
||||
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,
|
||||
}, nil
|
||||
}
|
||||
|
@ -4,14 +4,10 @@ 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"
|
||||
runccgroups "github.com/opencontainers/cgroups"
|
||||
"github.com/sirupsen/logrus"
|
||||
@ -19,223 +15,128 @@ import (
|
||||
"go.podman.io/storage/pkg/system"
|
||||
)
|
||||
|
||||
const DefaultStatsPeriod = 5 * time.Second
|
||||
|
||||
func StatsContainer(w http.ResponseWriter, r *http.Request) {
|
||||
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
|
||||
func getPreCPUStats(stats *define.ContainerStats) CPUStats {
|
||||
systemUsage, _ := cgroups.SystemCPUUsage()
|
||||
return CPUStats{
|
||||
CPUUsage: container.CPUUsage{
|
||||
TotalUsage: stats.CPUNano,
|
||||
PercpuUsage: stats.PerCPU,
|
||||
UsageInKernelmode: stats.CPUSystemNano,
|
||||
UsageInUsermode: stats.CPUNano - stats.CPUSystemNano,
|
||||
},
|
||||
CPU: stats.CPU,
|
||||
SystemUsage: systemUsage,
|
||||
OnlineCPUs: 0,
|
||||
ThrottlingData: container.ThrottlingData{},
|
||||
}
|
||||
}
|
||||
|
||||
name := utils.GetName(r)
|
||||
ctnr, err := runtime.LookupContainer(name)
|
||||
func statsContainerJSON(ctnr *libpod.Container, stats *define.ContainerStats, preCPUStats CPUStats, onlineCPUs int) (StatsJSON, error) {
|
||||
// Container stats
|
||||
inspect, err := ctnr.Inspect(false)
|
||||
if err != nil {
|
||||
utils.ContainerNotFound(w, name, err)
|
||||
return
|
||||
logrus.Errorf("Unable to inspect container: %v", err)
|
||||
return StatsJSON{}, err
|
||||
}
|
||||
|
||||
stats, err := ctnr.GetContainerStats(nil)
|
||||
// Cgroup stats
|
||||
cgroupPath, err := ctnr.CgroupPath()
|
||||
if err != nil {
|
||||
utils.InternalServerError(w, fmt.Errorf("failed to obtain Container %s stats: %w", name, err))
|
||||
return
|
||||
logrus.Errorf("Unable to get cgroup path of container: %v", err)
|
||||
return StatsJSON{}, err
|
||||
}
|
||||
cgroup, err := cgroups.Load(cgroupPath)
|
||||
if err != nil {
|
||||
logrus.Errorf("Unable to load cgroup: %v", err)
|
||||
return StatsJSON{}, err
|
||||
}
|
||||
cgroupStat, err := cgroup.Stat()
|
||||
if err != nil {
|
||||
logrus.Errorf("Unable to get cgroup stats: %v", err)
|
||||
return StatsJSON{}, err
|
||||
}
|
||||
|
||||
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()
|
||||
net := make(map[string]container.NetworkStats)
|
||||
for netName, netStats := range stats.Network {
|
||||
net[netName] = container.NetworkStats{
|
||||
RxBytes: netStats.RxBytes,
|
||||
RxPackets: netStats.RxPackets,
|
||||
RxErrors: netStats.RxErrors,
|
||||
RxDropped: netStats.RxDropped,
|
||||
TxBytes: netStats.TxBytes,
|
||||
TxPackets: netStats.TxPackets,
|
||||
TxErrors: netStats.TxErrors,
|
||||
TxDropped: netStats.TxDropped,
|
||||
EndpointID: inspect.NetworkSettings.EndpointID,
|
||||
InstanceID: "",
|
||||
}
|
||||
}
|
||||
|
||||
// Set up JSON encoder for streaming.
|
||||
coder.SetEscapeHTML(true)
|
||||
var preRead time.Time
|
||||
var preCPUStats CPUStats
|
||||
if query.Stream {
|
||||
preRead = time.Now()
|
||||
systemUsage, _ := cgroups.SystemCPUUsage()
|
||||
preCPUStats = CPUStats{
|
||||
CPUUsage: container.CPUUsage{
|
||||
TotalUsage: stats.CPUNano,
|
||||
PercpuUsage: stats.PerCPU,
|
||||
UsageInKernelmode: stats.CPUSystemNano,
|
||||
UsageInUsermode: stats.CPUNano - stats.CPUSystemNano,
|
||||
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 StatsJSON{}, err
|
||||
}
|
||||
// cap the memory limit to the available memory.
|
||||
if memInfo.MemTotal > 0 && memoryLimit > uint64(memInfo.MemTotal) {
|
||||
memoryLimit = uint64(memInfo.MemTotal)
|
||||
}
|
||||
|
||||
systemUsage, _ := cgroups.SystemCPUUsage()
|
||||
return StatsJSON{
|
||||
Stats: Stats{
|
||||
Read: time.Now(),
|
||||
PidsStats: container.PidsStats{
|
||||
Current: cgroupStat.PidsStats.Current,
|
||||
Limit: 0,
|
||||
},
|
||||
CPU: stats.CPU,
|
||||
SystemUsage: systemUsage,
|
||||
OnlineCPUs: 0,
|
||||
ThrottlingData: container.ThrottlingData{},
|
||||
}
|
||||
}
|
||||
onlineCPUs, err := libpod.GetOnlineCPUs(ctnr)
|
||||
if err != nil {
|
||||
utils.InternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
net := make(map[string]container.NetworkStats)
|
||||
for netName, netStats := range stats.Network {
|
||||
net[netName] = container.NetworkStats{
|
||||
RxBytes: netStats.RxBytes,
|
||||
RxPackets: netStats.RxPackets,
|
||||
RxErrors: netStats.RxErrors,
|
||||
RxDropped: netStats.RxDropped,
|
||||
TxBytes: netStats.TxBytes,
|
||||
TxPackets: netStats.TxPackets,
|
||||
TxErrors: netStats.TxErrors,
|
||||
TxDropped: netStats.TxDropped,
|
||||
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.SystemCPUUsage()
|
||||
s := StatsJSON{
|
||||
Stats: Stats{
|
||||
Read: time.Now(),
|
||||
PreRead: preRead,
|
||||
PidsStats: container.PidsStats{
|
||||
Current: cgroupStat.PidsStats.Current,
|
||||
Limit: 0,
|
||||
BlkioStats: container.BlkioStats{
|
||||
IoServiceBytesRecursive: toBlkioStatEntry(cgroupStat.BlkioStats.IoServiceBytesRecursive),
|
||||
IoServicedRecursive: nil,
|
||||
IoQueuedRecursive: nil,
|
||||
IoServiceTimeRecursive: nil,
|
||||
IoWaitTimeRecursive: nil,
|
||||
IoMergedRecursive: nil,
|
||||
IoTimeRecursive: nil,
|
||||
SectorsRecursive: nil,
|
||||
},
|
||||
CPUStats: CPUStats{
|
||||
CPUUsage: container.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,
|
||||
},
|
||||
BlkioStats: container.BlkioStats{
|
||||
IoServiceBytesRecursive: toBlkioStatEntry(cgroupStat.BlkioStats.IoServiceBytesRecursive),
|
||||
IoServicedRecursive: nil,
|
||||
IoQueuedRecursive: nil,
|
||||
IoServiceTimeRecursive: nil,
|
||||
IoWaitTimeRecursive: nil,
|
||||
IoMergedRecursive: nil,
|
||||
IoTimeRecursive: nil,
|
||||
SectorsRecursive: nil,
|
||||
},
|
||||
CPUStats: CPUStats{
|
||||
CPUUsage: container.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(onlineCPUs),
|
||||
ThrottlingData: container.ThrottlingData{
|
||||
Periods: 0,
|
||||
ThrottledPeriods: 0,
|
||||
ThrottledTime: 0,
|
||||
},
|
||||
},
|
||||
PreCPUStats: preCPUStats,
|
||||
MemoryStats: container.MemoryStats{
|
||||
Usage: cgroupStat.MemoryStats.Usage.Usage,
|
||||
MaxUsage: cgroupStat.MemoryStats.Usage.MaxUsage,
|
||||
Stats: nil,
|
||||
Failcnt: 0,
|
||||
Limit: memoryLimit,
|
||||
Commit: 0,
|
||||
CommitPeak: 0,
|
||||
PrivateWorkingSet: 0,
|
||||
CPU: stats.CPU,
|
||||
SystemUsage: systemUsage,
|
||||
OnlineCPUs: uint32(onlineCPUs),
|
||||
ThrottlingData: container.ThrottlingData{
|
||||
Periods: 0,
|
||||
ThrottledPeriods: 0,
|
||||
ThrottledTime: 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
|
||||
}
|
||||
PreCPUStats: preCPUStats,
|
||||
MemoryStats: container.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,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func toBlkioStatEntry(entries []runccgroups.BlkioStatEntry) []container.BlkioStatEntry {
|
||||
|
Reference in New Issue
Block a user