Skip to content
This repository was archived by the owner on Oct 13, 2023. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions api/types/stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,13 +169,17 @@ type Stats struct {
MemoryStats MemoryStats `json:"memory_stats,omitempty"`
}

type AutoRange map[string]map[string]string

// StatsJSON is newly used Networks
type StatsJSON struct {
Stats

Name string `json:"name,omitempty"`
ID string `json:"id,omitempty"`

AutoRange `json:"autorange,omitempty"`

// Networks request version >=1.21
Networks map[string]NetworkStats `json:"networks,omitempty"`
}
3 changes: 3 additions & 0 deletions api/types/swarm/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package swarm // import "github.com/docker/docker/api/types/swarm"

import "time"

type AutoRange map[string]map[string]string

// Service represents a service.
type Service struct {
ID string
Expand All @@ -28,6 +30,7 @@ type ServiceSpec struct {
// This field will be removed in a future release.
Networks []NetworkAttachmentConfig `json:",omitempty"`
EndpointSpec *EndpointSpec `json:",omitempty"`
AutoRange AutoRange `json:",omitempty"`
}

// ServiceMode represents the mode of a service.
Expand Down
3 changes: 3 additions & 0 deletions api/types/swarm/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ type TaskSpec struct {
ForceUpdate uint64

Runtime RuntimeType `json:",omitempty"`

// Autorange propagation from ServiceSpec to TaskSpec
AutoRange AutoRange `json:",omitempty"`
}

// Resources represents resources (CPU/Memory).
Expand Down
1 change: 1 addition & 0 deletions api/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ type ImageMetadata struct {
type Container struct {
ID string `json:"Id"`
Names []string
AutoRange AutoRange
Image string
ImageID string
Command string
Expand Down
1 change: 1 addition & 0 deletions container/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ type Container struct {
Managed bool
Path string
Args []string
AutoRange swarmtypes.AutoRange
Config *containertypes.Config
ImageID image.ID `json:"Image"`
NetworkSettings *network.Settings
Expand Down
56 changes: 56 additions & 0 deletions daemon/cluster/convert/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,11 @@ func serviceSpecFromGRPC(spec *swarmapi.ServiceSpec) (*types.ServiceSpec, error)
EndpointSpec: endpointSpecFromGRPC(spec.Endpoint),
}

autoRange := AutoRangeFromGRPC(spec.AutoRange)
if autoRange != nil {
convertedSpec.AutoRange = autoRange
}

// UpdateConfig
convertedSpec.UpdateConfig = updateConfigFromGRPC(spec.Update)
convertedSpec.RollbackConfig = updateConfigFromGRPC(spec.Rollback)
Expand All @@ -136,6 +141,46 @@ func serviceSpecFromGRPC(spec *swarmapi.ServiceSpec) (*types.ServiceSpec, error)
return convertedSpec, nil
}

// AutoRangeFromGRPC converts a AutoRange to a GRPC AutoRange
func AutoRangeFromGRPC(autoRange *swarmapi.AutoRange) types.AutoRange {
if autoRange == nil {
return types.AutoRange{}
}

sl := make(types.AutoRange)
for k := range autoRange.Range {
sl[k] = make(map[string]string)
for sk, sv := range autoRange.Range[k].Step {
sl[k][sk] = sv
}
}

return sl
}

// AutoRangeToGRPC converts a AutoRange from a GRPC AutoRange
func AutoRangeToGRPC(autoRange types.AutoRange) *swarmapi.AutoRange {
if len(autoRange) <= 0 {
return &swarmapi.AutoRange{}
}

sl := make([]swarmapi.AutoRange, 1)
sa := sl[0]
sa.Range = make(map[string]*swarmapi.Range)
for k := range autoRange {
var rg swarmapi.Range

rg.Step = make(map[string]string)

for sk, sv := range autoRange[k] {
rg.Step[sk] = sv
}
sa.Range[k] = &rg
}

return &sa
}

// ServiceSpecToGRPC converts a ServiceSpec to a grpc ServiceSpec.
func ServiceSpecToGRPC(s types.ServiceSpec) (swarmapi.ServiceSpec, error) {
name := s.Name
Expand Down Expand Up @@ -170,6 +215,12 @@ func ServiceSpecToGRPC(s types.ServiceSpec) (swarmapi.ServiceSpec, error) {
Networks: serviceNetworks,
}

autoRange := AutoRangeToGRPC(s.AutoRange)
if autoRange != nil {
spec.AutoRange = autoRange
spec.Task.AutoRange = autoRange
}

switch s.TaskTemplate.Runtime {
case types.RuntimeContainer, "": // if empty runtime default to container
if s.TaskTemplate.ContainerSpec != nil {
Expand Down Expand Up @@ -611,6 +662,11 @@ func taskSpecFromGRPC(taskSpec swarmapi.TaskSpec) (types.TaskSpec, error) {
ForceUpdate: taskSpec.ForceUpdate,
}

sl := AutoRangeFromGRPC(taskSpec.AutoRange)
if len(sl) > 0 {
t.AutoRange = sl
}

switch taskSpec.GetRuntime().(type) {
case *swarmapi.TaskSpec_Container, nil:
c := taskSpec.GetContainer()
Expand Down
11 changes: 11 additions & 0 deletions daemon/daemon_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -1420,6 +1420,17 @@ func (daemon *Daemon) stats(c *container.Container) (*types.StatsJSON, error) {
}
}

if len(c.AutoRange) > 0 {
sl := make(types.AutoRange)
for k := range c.AutoRange {
sl[k] = make(map[string]string)
for sk, sv := range c.AutoRange[k] {
sl[k][string(sk)] = sv
}
}
s.AutoRange = sl
}

return s, nil
}

Expand Down
80 changes: 80 additions & 0 deletions daemon/stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,42 @@ import (
"encoding/json"
"errors"
"runtime"
"strings"
"time"

"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/backend"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/versions"
"github.com/docker/docker/api/types/versions/v1p20"
"github.com/docker/docker/client"
"github.com/docker/docker/container"
"github.com/docker/docker/daemon/stats"
"github.com/docker/docker/pkg/ioutils"
)

func getAutoRange(ctx context.Context, containerID string) (swarm.AutoRange, string, bool) {
cli, err := client.NewEnvClient()
if err != nil {
return swarm.AutoRange{}, "", false
}
defer cli.Close()
container, err := cli.ContainerInspect(ctx, containerID)
if err != nil {
return swarm.AutoRange{}, "", false
}

// Swarm labels needed to get AutoRange configuration
serviceID, serviceName := container.Config.Labels["com.docker.swarm.service.id"], container.Config.Labels["com.docker.swarm.service.name"]
if serviceID != "" && serviceName != "" {
resp, _, _ := cli.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{})
if resp.Spec.AutoRange != nil {
return resp.Spec.AutoRange, serviceName, true
}
}
return swarm.AutoRange{}, "", false
}

// ContainerStats writes information about the container to the stream
// given in the config object.
func (daemon *Daemon) ContainerStats(ctx context.Context, prefixOrName string, config *backend.ContainerStatsConfig) error {
Expand All @@ -33,6 +59,29 @@ func (daemon *Daemon) ContainerStats(ctx context.Context, prefixOrName string, c
ID: container.ID})
}

// AutoRange initialisation
if autoRange, serviceName, ok := getAutoRange(ctx, container.ID); ok {
if _, exist := daemon.statsCollector.AutoRangeWatcher[container.ID]; !exist {
limit := 50 // Size limit of timeserie
daemon.statsCollector.AutoRangeWatcher[container.ID] = &stats.AutoRangeWatcher{
Config: autoRange,
TickRate: time.Second,
Target: container,
ServiceName: serviceName[:strings.LastIndex(serviceName, "_")],
Input: make(chan types.StatsJSON, 1),
Output: make(chan types.StatsJSON, 1),
WaitChan: make(chan bool, 1),
Obs: stats.NewObservor(limit),
Ctx: ctx,
Limit: limit,
Finished: false,
}
go daemon.statsCollector.AutoRangeWatcher[container.ID].Watch()
} else if !daemon.statsCollector.AutoRangeWatcher[container.ID].Finished {
daemon.statsCollector.AutoRangeWatcher[container.ID].SetNewContext(ctx)
}
}

outStream := config.OutStream
if config.Stream {
wf := ioutils.NewWriteFlusher(outStream)
Expand Down Expand Up @@ -60,6 +109,10 @@ func (daemon *Daemon) ContainerStats(ctx context.Context, prefixOrName string, c
defer daemon.unsubscribeToContainerStats(container, updates)

noStreamFirstFrame := true

var oldStats *types.StatsJSON
first := true

for {
select {
case v, ok := <-updates:
Expand All @@ -69,6 +122,33 @@ func (daemon *Daemon) ContainerStats(ctx context.Context, prefixOrName string, c

var statsJSON interface{}
statsJSONPost120 := getStatJSON(v)
if first {
oldStats = statsJSONPost120
first = false
}
if _, exist := daemon.statsCollector.AutoRangeWatcher[container.ID]; exist {
if !daemon.statsCollector.AutoRangeWatcher[container.ID].Finished {
select {
case daemon.statsCollector.AutoRangeWatcher[container.ID].Input <- *statsJSONPost120:
default:
}

select {
case up, ok := <-daemon.statsCollector.AutoRangeWatcher[container.ID].Output:
if !ok {
return nil
}

statsJSONPost120 = &up
oldStats = statsJSONPost120
default:
statsJSONPost120 = oldStats
}
} else {
statsJSONPost120.AutoRange = stats.ConvertAutoRange(daemon.statsCollector.AutoRangeWatcher[container.ID].Config)
}
}

if versions.LessThan(apiVersion, "1.21") {
if runtime.GOOS == "windows" {
return errors.New("API versions pre v1.21 do not support stats on Windows")
Expand Down
Loading