diff options
Diffstat (limited to 'pkg/api/handlers/generic/containers_stats.go')
-rw-r--r-- | pkg/api/handlers/generic/containers_stats.go | 198 |
1 files changed, 198 insertions, 0 deletions
diff --git a/pkg/api/handlers/generic/containers_stats.go b/pkg/api/handlers/generic/containers_stats.go new file mode 100644 index 000000000..0c4efc1df --- /dev/null +++ b/pkg/api/handlers/generic/containers_stats.go @@ -0,0 +1,198 @@ +package generic + +import ( + "encoding/json" + "net/http" + "time" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/api/handlers" + "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/containers/libpod/pkg/cgroups" + docker "github.com/docker/docker/api/types" + "github.com/gorilla/mux" + "github.com/gorilla/schema" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +const DefaultStatsPeriod = 5 * time.Second + +func StatsContainer(w http.ResponseWriter, r *http.Request) { + // 200 no error + // 404 no such + // 500 internal + runtime := r.Context().Value("runtime").(*libpod.Runtime) + decoder := r.Context().Value("decoder").(*schema.Decoder) + + query := struct { + Stream bool `schema:"stream"` + }{ + Stream: true, + } + if err := decoder.Decode(&query, r.URL.Query()); err != nil { + utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) + return + } + + name := mux.Vars(r)["name"] + ctnr, err := runtime.LookupContainer(name) + if err != nil { + utils.ContainerNotFound(w, name, err) + return + } + + state, err := ctnr.State() + if err != nil { + utils.InternalServerError(w, err) + return + } + if state != define.ContainerStateRunning && !query.Stream { + utils.WriteJSON(w, http.StatusOK, &handlers.Stats{StatsJSON: docker.StatsJSON{ + Name: ctnr.Name(), + ID: ctnr.ID(), + }}) + return + } + + var preRead time.Time + var preCPUStats docker.CPUStats + + stats, err := ctnr.GetContainerStats(&libpod.ContainerStats{}) + if err != nil { + utils.InternalServerError(w, errors.Wrapf(err, "Failed to obtain Container %s stats", name)) + return + } + + if query.Stream { + preRead = time.Now() + preCPUStats = docker.CPUStats{ + CPUUsage: docker.CPUUsage{ + TotalUsage: stats.CPUNano, + PercpuUsage: []uint64{uint64(stats.CPU)}, + UsageInKernelmode: 0, + UsageInUsermode: 0, + }, + SystemUsage: 0, + OnlineCPUs: 0, + ThrottlingData: docker.ThrottlingData{}, + } + time.Sleep(DefaultStatsPeriod) + } + + cgroupPath, _ := ctnr.CGroupPath() + cgroup, _ := cgroups.Load(cgroupPath) + + for ok := true; ok; ok = query.Stream { + state, _ := ctnr.State() + if state != define.ContainerStateRunning { + time.Sleep(10 * time.Second) + continue + } + + stats, _ := ctnr.GetContainerStats(stats) + cgroupStat, _ := cgroup.Stat() + inspect, _ := ctnr.Inspect(false) + + net := make(map[string]docker.NetworkStats) + net[inspect.NetworkSettings.EndpointID] = 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: "", + } + + s := handlers.Stats{StatsJSON: docker.StatsJSON{ + Stats: docker.Stats{ + Read: time.Now(), + PreRead: preRead, + PidsStats: docker.PidsStats{ + Current: cgroupStat.Pids.Current, + Limit: 0, + }, + BlkioStats: docker.BlkioStats{ + IoServiceBytesRecursive: toBlkioStatEntry(cgroupStat.Blkio.IoServiceBytesRecursive), + IoServicedRecursive: nil, + IoQueuedRecursive: nil, + IoServiceTimeRecursive: nil, + IoWaitTimeRecursive: nil, + IoMergedRecursive: nil, + IoTimeRecursive: nil, + SectorsRecursive: nil, + }, + NumProcs: 0, + StorageStats: docker.StorageStats{ + ReadCountNormalized: 0, + ReadSizeBytes: 0, + WriteCountNormalized: 0, + WriteSizeBytes: 0, + }, + CPUStats: docker.CPUStats{ + CPUUsage: docker.CPUUsage{ + TotalUsage: cgroupStat.CPU.Usage.Total, + PercpuUsage: []uint64{uint64(stats.CPU)}, + UsageInKernelmode: cgroupStat.CPU.Usage.Kernel, + UsageInUsermode: cgroupStat.CPU.Usage.Total - cgroupStat.CPU.Usage.Kernel, + }, + SystemUsage: 0, + OnlineCPUs: uint32(len(cgroupStat.CPU.Usage.PerCPU)), + ThrottlingData: docker.ThrottlingData{ + Periods: 0, + ThrottledPeriods: 0, + ThrottledTime: 0, + }, + }, + PreCPUStats: preCPUStats, + MemoryStats: docker.MemoryStats{ + Usage: cgroupStat.Memory.Usage.Usage, + MaxUsage: cgroupStat.Memory.Usage.Limit, + Stats: nil, + Failcnt: 0, + Limit: cgroupStat.Memory.Usage.Limit, + Commit: 0, + CommitPeak: 0, + PrivateWorkingSet: 0, + }, + }, + Name: stats.Name, + ID: stats.ContainerID, + Networks: net, + }} + + utils.WriteJSON(w, http.StatusOK, s) + if flusher, ok := w.(http.Flusher); ok { + flusher.Flush() + } + + 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) + } +} + +func toBlkioStatEntry(entries []cgroups.BlkIOEntry) []docker.BlkioStatEntry { + results := make([]docker.BlkioStatEntry, 0, 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 +} |