github.com/containers/podman/v2@v2.2.2-0.20210501105131-c1e07d070c4c/pkg/api/handlers/compat/containers_stats.go (about)

     1  package compat
     2  
     3  import (
     4  	"encoding/json"
     5  	"net/http"
     6  	"time"
     7  
     8  	"github.com/containers/podman/v2/libpod"
     9  	"github.com/containers/podman/v2/libpod/define"
    10  	"github.com/containers/podman/v2/pkg/api/handlers/utils"
    11  	"github.com/containers/podman/v2/pkg/cgroups"
    12  	docker "github.com/docker/docker/api/types"
    13  	"github.com/gorilla/schema"
    14  	"github.com/pkg/errors"
    15  	"github.com/sirupsen/logrus"
    16  )
    17  
    18  const DefaultStatsPeriod = 5 * time.Second
    19  
    20  func StatsContainer(w http.ResponseWriter, r *http.Request) {
    21  	runtime := r.Context().Value("runtime").(*libpod.Runtime)
    22  	decoder := r.Context().Value("decoder").(*schema.Decoder)
    23  
    24  	query := struct {
    25  		Stream bool `schema:"stream"`
    26  	}{
    27  		Stream: true,
    28  	}
    29  	if err := decoder.Decode(&query, r.URL.Query()); err != nil {
    30  		utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
    31  		return
    32  	}
    33  
    34  	name := utils.GetName(r)
    35  	ctnr, err := runtime.LookupContainer(name)
    36  	if err != nil {
    37  		utils.ContainerNotFound(w, name, err)
    38  		return
    39  	}
    40  
    41  	// If the container isn't running, then let's not bother and return
    42  	// immediately.
    43  	state, err := ctnr.State()
    44  	if err != nil {
    45  		utils.InternalServerError(w, err)
    46  		return
    47  	}
    48  	if state != define.ContainerStateRunning {
    49  		utils.Error(w, "Container not running and streaming requested", http.StatusConflict, define.ErrCtrStateInvalid)
    50  		return
    51  	}
    52  
    53  	stats, err := ctnr.GetContainerStats(&define.ContainerStats{})
    54  	if err != nil {
    55  		utils.InternalServerError(w, errors.Wrapf(err, "failed to obtain Container %s stats", name))
    56  		return
    57  	}
    58  
    59  	var preRead time.Time
    60  	var preCPUStats CPUStats
    61  	if query.Stream {
    62  		preRead = time.Now()
    63  		systemUsage, _ := cgroups.GetSystemCPUUsage()
    64  		preCPUStats = CPUStats{
    65  			CPUUsage: docker.CPUUsage{
    66  				TotalUsage:        stats.CPUNano,
    67  				PercpuUsage:       stats.PerCPU,
    68  				UsageInKernelmode: stats.CPUSystemNano,
    69  				UsageInUsermode:   stats.CPUNano - stats.CPUSystemNano,
    70  			},
    71  			CPU:            stats.CPU,
    72  			SystemUsage:    systemUsage,
    73  			OnlineCPUs:     0,
    74  			ThrottlingData: docker.ThrottlingData{},
    75  		}
    76  	}
    77  
    78  	// Write header and content type.
    79  	w.WriteHeader(http.StatusOK)
    80  	w.Header().Add("Content-Type", "application/json")
    81  	if flusher, ok := w.(http.Flusher); ok {
    82  		flusher.Flush()
    83  	}
    84  
    85  	// Setup JSON encoder for streaming.
    86  	coder := json.NewEncoder(w)
    87  	coder.SetEscapeHTML(true)
    88  
    89  streamLabel: // A label to flatten the scope
    90  	select {
    91  	case <-r.Context().Done():
    92  		logrus.Debugf("Client connection (container stats) cancelled")
    93  
    94  	default:
    95  		// Container stats
    96  		stats, err := ctnr.GetContainerStats(stats)
    97  		if err != nil {
    98  			logrus.Errorf("Unable to get container stats: %v", err)
    99  			return
   100  		}
   101  		inspect, err := ctnr.Inspect(false)
   102  		if err != nil {
   103  			logrus.Errorf("Unable to inspect container: %v", err)
   104  			return
   105  		}
   106  		// Cgroup stats
   107  		cgroupPath, err := ctnr.CGroupPath()
   108  		if err != nil {
   109  			logrus.Errorf("Unable to get cgroup path of container: %v", err)
   110  			return
   111  		}
   112  		cgroup, err := cgroups.Load(cgroupPath)
   113  		if err != nil {
   114  			logrus.Errorf("Unable to load cgroup: %v", err)
   115  			return
   116  		}
   117  		cgroupStat, err := cgroup.Stat()
   118  		if err != nil {
   119  			logrus.Errorf("Unable to get cgroup stats: %v", err)
   120  			return
   121  		}
   122  
   123  		// FIXME: network inspection does not yet work entirely
   124  		net := make(map[string]docker.NetworkStats)
   125  		networkName := inspect.NetworkSettings.EndpointID
   126  		if networkName == "" {
   127  			networkName = "network"
   128  		}
   129  		net[networkName] = docker.NetworkStats{
   130  			RxBytes:    stats.NetInput,
   131  			RxPackets:  0,
   132  			RxErrors:   0,
   133  			RxDropped:  0,
   134  			TxBytes:    stats.NetOutput,
   135  			TxPackets:  0,
   136  			TxErrors:   0,
   137  			TxDropped:  0,
   138  			EndpointID: inspect.NetworkSettings.EndpointID,
   139  			InstanceID: "",
   140  		}
   141  
   142  		systemUsage, _ := cgroups.GetSystemCPUUsage()
   143  		s := StatsJSON{
   144  			Stats: Stats{
   145  				Read:    time.Now(),
   146  				PreRead: preRead,
   147  				PidsStats: docker.PidsStats{
   148  					Current: cgroupStat.Pids.Current,
   149  					Limit:   0,
   150  				},
   151  				BlkioStats: docker.BlkioStats{
   152  					IoServiceBytesRecursive: toBlkioStatEntry(cgroupStat.Blkio.IoServiceBytesRecursive),
   153  					IoServicedRecursive:     nil,
   154  					IoQueuedRecursive:       nil,
   155  					IoServiceTimeRecursive:  nil,
   156  					IoWaitTimeRecursive:     nil,
   157  					IoMergedRecursive:       nil,
   158  					IoTimeRecursive:         nil,
   159  					SectorsRecursive:        nil,
   160  				},
   161  				CPUStats: CPUStats{
   162  					CPUUsage: docker.CPUUsage{
   163  						TotalUsage:        cgroupStat.CPU.Usage.Total,
   164  						PercpuUsage:       cgroupStat.CPU.Usage.PerCPU,
   165  						UsageInKernelmode: cgroupStat.CPU.Usage.Kernel,
   166  						UsageInUsermode:   cgroupStat.CPU.Usage.Total - cgroupStat.CPU.Usage.Kernel,
   167  					},
   168  					CPU:         stats.CPU,
   169  					SystemUsage: systemUsage,
   170  					OnlineCPUs:  uint32(len(cgroupStat.CPU.Usage.PerCPU)),
   171  					ThrottlingData: docker.ThrottlingData{
   172  						Periods:          0,
   173  						ThrottledPeriods: 0,
   174  						ThrottledTime:    0,
   175  					},
   176  				},
   177  				PreCPUStats: preCPUStats,
   178  				MemoryStats: docker.MemoryStats{
   179  					Usage:             cgroupStat.Memory.Usage.Usage,
   180  					MaxUsage:          cgroupStat.Memory.Usage.Limit,
   181  					Stats:             nil,
   182  					Failcnt:           0,
   183  					Limit:             cgroupStat.Memory.Usage.Limit,
   184  					Commit:            0,
   185  					CommitPeak:        0,
   186  					PrivateWorkingSet: 0,
   187  				},
   188  			},
   189  			Name:     stats.Name,
   190  			ID:       stats.ContainerID,
   191  			Networks: net,
   192  		}
   193  
   194  		if err := coder.Encode(s); err != nil {
   195  			logrus.Errorf("Unable to encode stats: %v", err)
   196  			return
   197  		}
   198  		if flusher, ok := w.(http.Flusher); ok {
   199  			flusher.Flush()
   200  		}
   201  
   202  		if !query.Stream {
   203  			return
   204  		}
   205  
   206  		preRead = s.Read
   207  		bits, err := json.Marshal(s.CPUStats)
   208  		if err != nil {
   209  			logrus.Errorf("Unable to marshal cpu stats: %q", err)
   210  		}
   211  		if err := json.Unmarshal(bits, &preCPUStats); err != nil {
   212  			logrus.Errorf("Unable to unmarshal previous stats: %q", err)
   213  		}
   214  
   215  		time.Sleep(DefaultStatsPeriod)
   216  		goto streamLabel
   217  	}
   218  }
   219  
   220  func toBlkioStatEntry(entries []cgroups.BlkIOEntry) []docker.BlkioStatEntry {
   221  	results := make([]docker.BlkioStatEntry, len(entries))
   222  	for i, e := range entries {
   223  		bits, err := json.Marshal(e)
   224  		if err != nil {
   225  			logrus.Errorf("unable to marshal blkio stats: %q", err)
   226  		}
   227  		if err := json.Unmarshal(bits, &results[i]); err != nil {
   228  			logrus.Errorf("unable to unmarshal blkio stats: %q", err)
   229  		}
   230  	}
   231  	return results
   232  }