stats.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. package daemon
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "fmt"
  6. "runtime"
  7. "golang.org/x/net/context"
  8. "github.com/docker/docker/api/types/backend"
  9. "github.com/docker/docker/container"
  10. "github.com/docker/docker/pkg/ioutils"
  11. "github.com/docker/engine-api/types"
  12. "github.com/docker/engine-api/types/versions"
  13. "github.com/docker/engine-api/types/versions/v1p20"
  14. )
  15. // ContainerStats writes information about the container to the stream
  16. // given in the config object.
  17. func (daemon *Daemon) ContainerStats(ctx context.Context, prefixOrName string, config *backend.ContainerStatsConfig) error {
  18. if runtime.GOOS == "windows" {
  19. return errors.New("Windows does not support stats")
  20. }
  21. // Remote API version (used for backwards compatibility)
  22. apiVersion := config.Version
  23. container, err := daemon.GetContainer(prefixOrName)
  24. if err != nil {
  25. return err
  26. }
  27. // If the container is not running and requires no stream, return an empty stats.
  28. if !container.IsRunning() && !config.Stream {
  29. return json.NewEncoder(config.OutStream).Encode(&types.Stats{})
  30. }
  31. outStream := config.OutStream
  32. if config.Stream {
  33. wf := ioutils.NewWriteFlusher(outStream)
  34. defer wf.Close()
  35. wf.Flush()
  36. outStream = wf
  37. }
  38. var preCPUStats types.CPUStats
  39. getStatJSON := func(v interface{}) *types.StatsJSON {
  40. ss := v.(types.StatsJSON)
  41. ss.PreCPUStats = preCPUStats
  42. preCPUStats = ss.CPUStats
  43. return &ss
  44. }
  45. enc := json.NewEncoder(outStream)
  46. updates := daemon.subscribeToContainerStats(container)
  47. defer daemon.unsubscribeToContainerStats(container, updates)
  48. noStreamFirstFrame := true
  49. for {
  50. select {
  51. case v, ok := <-updates:
  52. if !ok {
  53. return nil
  54. }
  55. var statsJSON interface{}
  56. statsJSONPost120 := getStatJSON(v)
  57. if versions.LessThan(apiVersion, "1.21") {
  58. var (
  59. rxBytes uint64
  60. rxPackets uint64
  61. rxErrors uint64
  62. rxDropped uint64
  63. txBytes uint64
  64. txPackets uint64
  65. txErrors uint64
  66. txDropped uint64
  67. )
  68. for _, v := range statsJSONPost120.Networks {
  69. rxBytes += v.RxBytes
  70. rxPackets += v.RxPackets
  71. rxErrors += v.RxErrors
  72. rxDropped += v.RxDropped
  73. txBytes += v.TxBytes
  74. txPackets += v.TxPackets
  75. txErrors += v.TxErrors
  76. txDropped += v.TxDropped
  77. }
  78. statsJSON = &v1p20.StatsJSON{
  79. Stats: statsJSONPost120.Stats,
  80. Network: types.NetworkStats{
  81. RxBytes: rxBytes,
  82. RxPackets: rxPackets,
  83. RxErrors: rxErrors,
  84. RxDropped: rxDropped,
  85. TxBytes: txBytes,
  86. TxPackets: txPackets,
  87. TxErrors: txErrors,
  88. TxDropped: txDropped,
  89. },
  90. }
  91. } else {
  92. statsJSON = statsJSONPost120
  93. }
  94. if !config.Stream && noStreamFirstFrame {
  95. // prime the cpu stats so they aren't 0 in the final output
  96. noStreamFirstFrame = false
  97. continue
  98. }
  99. if err := enc.Encode(statsJSON); err != nil {
  100. return err
  101. }
  102. if !config.Stream {
  103. return nil
  104. }
  105. case <-ctx.Done():
  106. return nil
  107. }
  108. }
  109. }
  110. func (daemon *Daemon) subscribeToContainerStats(c *container.Container) chan interface{} {
  111. return daemon.statsCollector.collect(c)
  112. }
  113. func (daemon *Daemon) unsubscribeToContainerStats(c *container.Container, ch chan interface{}) {
  114. daemon.statsCollector.unsubscribe(c, ch)
  115. }
  116. // GetContainerStats collects all the stats published by a container
  117. func (daemon *Daemon) GetContainerStats(container *container.Container) (*types.StatsJSON, error) {
  118. stats, err := daemon.stats(container)
  119. if err != nil {
  120. return nil, err
  121. }
  122. if stats.Networks, err = daemon.getNetworkStats(container); err != nil {
  123. return nil, err
  124. }
  125. return stats, nil
  126. }
  127. // Resolve Network SandboxID in case the container reuse another container's network stack
  128. func (daemon *Daemon) getNetworkSandboxID(c *container.Container) (string, error) {
  129. curr := c
  130. for curr.HostConfig.NetworkMode.IsContainer() {
  131. containerID := curr.HostConfig.NetworkMode.ConnectedContainer()
  132. connected, err := daemon.GetContainer(containerID)
  133. if err != nil {
  134. return "", fmt.Errorf("Could not get container for %s", containerID)
  135. }
  136. curr = connected
  137. }
  138. return curr.NetworkSettings.SandboxID, nil
  139. }
  140. func (daemon *Daemon) getNetworkStats(c *container.Container) (map[string]types.NetworkStats, error) {
  141. sandboxID, err := daemon.getNetworkSandboxID(c)
  142. if err != nil {
  143. return nil, err
  144. }
  145. sb, err := daemon.netController.SandboxByID(sandboxID)
  146. if err != nil {
  147. return nil, err
  148. }
  149. lnstats, err := sb.Statistics()
  150. if err != nil {
  151. return nil, err
  152. }
  153. stats := make(map[string]types.NetworkStats)
  154. // Convert libnetwork nw stats into engine-api stats
  155. for ifName, ifStats := range lnstats {
  156. stats[ifName] = types.NetworkStats{
  157. RxBytes: ifStats.RxBytes,
  158. RxPackets: ifStats.RxPackets,
  159. RxErrors: ifStats.RxErrors,
  160. RxDropped: ifStats.RxDropped,
  161. TxBytes: ifStats.TxBytes,
  162. TxPackets: ifStats.TxPackets,
  163. TxErrors: ifStats.TxErrors,
  164. TxDropped: ifStats.TxDropped,
  165. }
  166. }
  167. return stats, nil
  168. }