mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Merge pull request #23334 from WeiZhang555/cobra-stats
Migrate stats and events command to cobra
This commit is contained in:
commit
e884a515e9
9 changed files with 238 additions and 185 deletions
|
@ -5,7 +5,6 @@ func (cli *DockerCli) Command(name string) func(...string) error {
|
||||||
return map[string]func(...string) error{
|
return map[string]func(...string) error{
|
||||||
"commit": cli.CmdCommit,
|
"commit": cli.CmdCommit,
|
||||||
"cp": cli.CmdCp,
|
"cp": cli.CmdCp,
|
||||||
"events": cli.CmdEvents,
|
|
||||||
"exec": cli.CmdExec,
|
"exec": cli.CmdExec,
|
||||||
"info": cli.CmdInfo,
|
"info": cli.CmdInfo,
|
||||||
"inspect": cli.CmdInspect,
|
"inspect": cli.CmdInspect,
|
||||||
|
@ -16,7 +15,6 @@ func (cli *DockerCli) Command(name string) func(...string) error {
|
||||||
"pull": cli.CmdPull,
|
"pull": cli.CmdPull,
|
||||||
"push": cli.CmdPush,
|
"push": cli.CmdPush,
|
||||||
"save": cli.CmdSave,
|
"save": cli.CmdSave,
|
||||||
"stats": cli.CmdStats,
|
|
||||||
"update": cli.CmdUpdate,
|
"update": cli.CmdUpdate,
|
||||||
}[name]
|
}[name]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package client
|
package container
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -11,26 +11,46 @@ import (
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
Cli "github.com/docker/docker/cli"
|
"github.com/docker/docker/api/client"
|
||||||
|
"github.com/docker/docker/api/client/system"
|
||||||
|
"github.com/docker/docker/cli"
|
||||||
"github.com/docker/engine-api/types"
|
"github.com/docker/engine-api/types"
|
||||||
"github.com/docker/engine-api/types/events"
|
"github.com/docker/engine-api/types/events"
|
||||||
"github.com/docker/engine-api/types/filters"
|
"github.com/docker/engine-api/types/filters"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdStats displays a live stream of resource usage statistics for one or more containers.
|
type statsOptions struct {
|
||||||
//
|
all bool
|
||||||
|
noStream bool
|
||||||
|
|
||||||
|
containers []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStatsCommand creats a new cobra.Command for `docker stats`
|
||||||
|
func NewStatsCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||||
|
var opts statsOptions
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "stats [OPTIONS] [CONTAINER...]",
|
||||||
|
Short: "Display a live stream of container(s) resource usage statistics",
|
||||||
|
Args: cli.RequiresMinArgs(0),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
opts.containers = args
|
||||||
|
return runStats(dockerCli, &opts)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
flags := cmd.Flags()
|
||||||
|
flags.BoolVarP(&opts.all, "all", "a", false, "Show all containers (default shows just running)")
|
||||||
|
flags.BoolVar(&opts.noStream, "no-stream", false, "Disable streaming stats and only pull the first result")
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// runStats displays a live stream of resource usage statistics for one or more containers.
|
||||||
// This shows real-time information on CPU usage, memory usage, and network I/O.
|
// This shows real-time information on CPU usage, memory usage, and network I/O.
|
||||||
//
|
func runStats(dockerCli *client.DockerCli, opts *statsOptions) error {
|
||||||
// Usage: docker stats [OPTIONS] [CONTAINER...]
|
showAll := len(opts.containers) == 0
|
||||||
func (cli *DockerCli) CmdStats(args ...string) error {
|
|
||||||
cmd := Cli.Subcmd("stats", []string{"[CONTAINER...]"}, Cli.DockerCommands["stats"].Description, true)
|
|
||||||
all := cmd.Bool([]string{"a", "-all"}, false, "Show all containers (default shows just running)")
|
|
||||||
noStream := cmd.Bool([]string{"-no-stream"}, false, "Disable streaming stats and only pull the first result")
|
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
|
||||||
|
|
||||||
names := cmd.Args()
|
|
||||||
showAll := len(names) == 0
|
|
||||||
closeChan := make(chan error)
|
closeChan := make(chan error)
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
@ -43,7 +63,7 @@ func (cli *DockerCli) CmdStats(args ...string) error {
|
||||||
options := types.EventsOptions{
|
options := types.EventsOptions{
|
||||||
Filters: f,
|
Filters: f,
|
||||||
}
|
}
|
||||||
resBody, err := cli.client.Events(ctx, options)
|
resBody, err := dockerCli.Client().Events(ctx, options)
|
||||||
// Whether we successfully subscribed to events or not, we can now
|
// Whether we successfully subscribed to events or not, we can now
|
||||||
// unblock the main goroutine.
|
// unblock the main goroutine.
|
||||||
close(started)
|
close(started)
|
||||||
|
@ -53,7 +73,7 @@ func (cli *DockerCli) CmdStats(args ...string) error {
|
||||||
}
|
}
|
||||||
defer resBody.Close()
|
defer resBody.Close()
|
||||||
|
|
||||||
decodeEvents(resBody, func(event events.Message, err error) error {
|
system.DecodeEvents(resBody, func(event events.Message, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
closeChan <- err
|
closeChan <- err
|
||||||
return nil
|
return nil
|
||||||
|
@ -71,9 +91,9 @@ func (cli *DockerCli) CmdStats(args ...string) error {
|
||||||
// containers (only used when calling `docker stats` without arguments).
|
// containers (only used when calling `docker stats` without arguments).
|
||||||
getContainerList := func() {
|
getContainerList := func() {
|
||||||
options := types.ContainerListOptions{
|
options := types.ContainerListOptions{
|
||||||
All: *all,
|
All: opts.all,
|
||||||
}
|
}
|
||||||
cs, err := cli.client.ContainerList(ctx, options)
|
cs, err := dockerCli.Client().ContainerList(ctx, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
closeChan <- err
|
closeChan <- err
|
||||||
}
|
}
|
||||||
|
@ -81,7 +101,7 @@ func (cli *DockerCli) CmdStats(args ...string) error {
|
||||||
s := &containerStats{Name: container.ID[:12]}
|
s := &containerStats{Name: container.ID[:12]}
|
||||||
if cStats.add(s) {
|
if cStats.add(s) {
|
||||||
waitFirst.Add(1)
|
waitFirst.Add(1)
|
||||||
go s.Collect(ctx, cli.client, !*noStream, waitFirst)
|
go s.Collect(ctx, dockerCli.Client(), !opts.noStream, waitFirst)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,13 +112,13 @@ func (cli *DockerCli) CmdStats(args ...string) error {
|
||||||
// retrieving the list of running containers to avoid a race where we
|
// retrieving the list of running containers to avoid a race where we
|
||||||
// would "miss" a creation.
|
// would "miss" a creation.
|
||||||
started := make(chan struct{})
|
started := make(chan struct{})
|
||||||
eh := eventHandler{handlers: make(map[string]func(events.Message))}
|
eh := system.InitEventHandler()
|
||||||
eh.Handle("create", func(e events.Message) {
|
eh.Handle("create", func(e events.Message) {
|
||||||
if *all {
|
if opts.all {
|
||||||
s := &containerStats{Name: e.ID[:12]}
|
s := &containerStats{Name: e.ID[:12]}
|
||||||
if cStats.add(s) {
|
if cStats.add(s) {
|
||||||
waitFirst.Add(1)
|
waitFirst.Add(1)
|
||||||
go s.Collect(ctx, cli.client, !*noStream, waitFirst)
|
go s.Collect(ctx, dockerCli.Client(), !opts.noStream, waitFirst)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -107,12 +127,12 @@ func (cli *DockerCli) CmdStats(args ...string) error {
|
||||||
s := &containerStats{Name: e.ID[:12]}
|
s := &containerStats{Name: e.ID[:12]}
|
||||||
if cStats.add(s) {
|
if cStats.add(s) {
|
||||||
waitFirst.Add(1)
|
waitFirst.Add(1)
|
||||||
go s.Collect(ctx, cli.client, !*noStream, waitFirst)
|
go s.Collect(ctx, dockerCli.Client(), !opts.noStream, waitFirst)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
eh.Handle("die", func(e events.Message) {
|
eh.Handle("die", func(e events.Message) {
|
||||||
if !*all {
|
if !opts.all {
|
||||||
cStats.remove(e.ID[:12])
|
cStats.remove(e.ID[:12])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -129,11 +149,11 @@ func (cli *DockerCli) CmdStats(args ...string) error {
|
||||||
} else {
|
} else {
|
||||||
// Artificially send creation events for the containers we were asked to
|
// Artificially send creation events for the containers we were asked to
|
||||||
// monitor (same code path than we use when monitoring all containers).
|
// monitor (same code path than we use when monitoring all containers).
|
||||||
for _, name := range names {
|
for _, name := range opts.containers {
|
||||||
s := &containerStats{Name: name}
|
s := &containerStats{Name: name}
|
||||||
if cStats.add(s) {
|
if cStats.add(s) {
|
||||||
waitFirst.Add(1)
|
waitFirst.Add(1)
|
||||||
go s.Collect(ctx, cli.client, !*noStream, waitFirst)
|
go s.Collect(ctx, dockerCli.Client(), !opts.noStream, waitFirst)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,11 +181,11 @@ func (cli *DockerCli) CmdStats(args ...string) error {
|
||||||
// before print to screen, make sure each container get at least one valid stat data
|
// before print to screen, make sure each container get at least one valid stat data
|
||||||
waitFirst.Wait()
|
waitFirst.Wait()
|
||||||
|
|
||||||
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
|
w := tabwriter.NewWriter(dockerCli.Out(), 20, 1, 3, ' ', 0)
|
||||||
printHeader := func() {
|
printHeader := func() {
|
||||||
if !*noStream {
|
if !opts.noStream {
|
||||||
fmt.Fprint(cli.out, "\033[2J")
|
fmt.Fprint(dockerCli.Out(), "\033[2J")
|
||||||
fmt.Fprint(cli.out, "\033[H")
|
fmt.Fprint(dockerCli.Out(), "\033[H")
|
||||||
}
|
}
|
||||||
io.WriteString(w, "CONTAINER\tCPU %\tMEM USAGE / LIMIT\tMEM %\tNET I/O\tBLOCK I/O\tPIDS\n")
|
io.WriteString(w, "CONTAINER\tCPU %\tMEM USAGE / LIMIT\tMEM %\tNET I/O\tBLOCK I/O\tPIDS\n")
|
||||||
}
|
}
|
||||||
|
@ -174,13 +194,13 @@ func (cli *DockerCli) CmdStats(args ...string) error {
|
||||||
printHeader()
|
printHeader()
|
||||||
cStats.mu.Lock()
|
cStats.mu.Lock()
|
||||||
for _, s := range cStats.cs {
|
for _, s := range cStats.cs {
|
||||||
if err := s.Display(w); err != nil && !*noStream {
|
if err := s.Display(w); err != nil && !opts.noStream {
|
||||||
logrus.Debugf("stats: got error for %s: %v", s.Name, err)
|
logrus.Debugf("stats: got error for %s: %v", s.Name, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cStats.mu.Unlock()
|
cStats.mu.Unlock()
|
||||||
w.Flush()
|
w.Flush()
|
||||||
if *noStream {
|
if opts.noStream {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
select {
|
select {
|
|
@ -1,4 +1,4 @@
|
||||||
package client
|
package container
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
|
@ -1,4 +1,4 @@
|
||||||
package client
|
package container
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
|
@ -1,146 +0,0 @@
|
||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
Cli "github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/opts"
|
|
||||||
"github.com/docker/docker/pkg/jsonlog"
|
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
|
||||||
"github.com/docker/engine-api/types"
|
|
||||||
eventtypes "github.com/docker/engine-api/types/events"
|
|
||||||
"github.com/docker/engine-api/types/filters"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdEvents prints a live stream of real time events from the server.
|
|
||||||
//
|
|
||||||
// Usage: docker events [OPTIONS]
|
|
||||||
func (cli *DockerCli) CmdEvents(args ...string) error {
|
|
||||||
cmd := Cli.Subcmd("events", nil, Cli.DockerCommands["events"].Description, true)
|
|
||||||
since := cmd.String([]string{"-since"}, "", "Show all events created since timestamp")
|
|
||||||
until := cmd.String([]string{"-until"}, "", "Stream events until this timestamp")
|
|
||||||
flFilter := opts.NewListOpts(nil)
|
|
||||||
cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided")
|
|
||||||
cmd.Require(flag.Exact, 0)
|
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
|
||||||
|
|
||||||
eventFilterArgs := filters.NewArgs()
|
|
||||||
|
|
||||||
// Consolidate all filter flags, and sanity check them early.
|
|
||||||
// They'll get process in the daemon/server.
|
|
||||||
for _, f := range flFilter.GetAll() {
|
|
||||||
var err error
|
|
||||||
eventFilterArgs, err = filters.ParseFlag(f, eventFilterArgs)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
options := types.EventsOptions{
|
|
||||||
Since: *since,
|
|
||||||
Until: *until,
|
|
||||||
Filters: eventFilterArgs,
|
|
||||||
}
|
|
||||||
|
|
||||||
responseBody, err := cli.client.Events(context.Background(), options)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer responseBody.Close()
|
|
||||||
|
|
||||||
return streamEvents(responseBody, cli.out)
|
|
||||||
}
|
|
||||||
|
|
||||||
// streamEvents decodes prints the incoming events in the provided output.
|
|
||||||
func streamEvents(input io.Reader, output io.Writer) error {
|
|
||||||
return decodeEvents(input, func(event eventtypes.Message, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
printOutput(event, output)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
type eventProcessor func(event eventtypes.Message, err error) error
|
|
||||||
|
|
||||||
func decodeEvents(input io.Reader, ep eventProcessor) error {
|
|
||||||
dec := json.NewDecoder(input)
|
|
||||||
for {
|
|
||||||
var event eventtypes.Message
|
|
||||||
err := dec.Decode(&event)
|
|
||||||
if err != nil && err == io.EOF {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if procErr := ep(event, err); procErr != nil {
|
|
||||||
return procErr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// printOutput prints all types of event information.
|
|
||||||
// Each output includes the event type, actor id, name and action.
|
|
||||||
// Actor attributes are printed at the end if the actor has any.
|
|
||||||
func printOutput(event eventtypes.Message, output io.Writer) {
|
|
||||||
if event.TimeNano != 0 {
|
|
||||||
fmt.Fprintf(output, "%s ", time.Unix(0, event.TimeNano).Format(jsonlog.RFC3339NanoFixed))
|
|
||||||
} else if event.Time != 0 {
|
|
||||||
fmt.Fprintf(output, "%s ", time.Unix(event.Time, 0).Format(jsonlog.RFC3339NanoFixed))
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintf(output, "%s %s %s", event.Type, event.Action, event.Actor.ID)
|
|
||||||
|
|
||||||
if len(event.Actor.Attributes) > 0 {
|
|
||||||
var attrs []string
|
|
||||||
var keys []string
|
|
||||||
for k := range event.Actor.Attributes {
|
|
||||||
keys = append(keys, k)
|
|
||||||
}
|
|
||||||
sort.Strings(keys)
|
|
||||||
for _, k := range keys {
|
|
||||||
v := event.Actor.Attributes[k]
|
|
||||||
attrs = append(attrs, fmt.Sprintf("%s=%s", k, v))
|
|
||||||
}
|
|
||||||
fmt.Fprintf(output, " (%s)", strings.Join(attrs, ", "))
|
|
||||||
}
|
|
||||||
fmt.Fprint(output, "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
type eventHandler struct {
|
|
||||||
handlers map[string]func(eventtypes.Message)
|
|
||||||
mu sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *eventHandler) Handle(action string, h func(eventtypes.Message)) {
|
|
||||||
w.mu.Lock()
|
|
||||||
w.handlers[action] = h
|
|
||||||
w.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Watch ranges over the passed in event chan and processes the events based on the
|
|
||||||
// handlers created for a given action.
|
|
||||||
// To stop watching, close the event chan.
|
|
||||||
func (w *eventHandler) Watch(c <-chan eventtypes.Message) {
|
|
||||||
for e := range c {
|
|
||||||
w.mu.Lock()
|
|
||||||
h, exists := w.handlers[e.Action]
|
|
||||||
w.mu.Unlock()
|
|
||||||
if !exists {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
logrus.Debugf("event handler: received event: %v", e)
|
|
||||||
go h(e)
|
|
||||||
}
|
|
||||||
}
|
|
115
api/client/system/events.go
Normal file
115
api/client/system/events.go
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/client"
|
||||||
|
"github.com/docker/docker/cli"
|
||||||
|
"github.com/docker/docker/pkg/jsonlog"
|
||||||
|
"github.com/docker/engine-api/types"
|
||||||
|
eventtypes "github.com/docker/engine-api/types/events"
|
||||||
|
"github.com/docker/engine-api/types/filters"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
type eventsOptions struct {
|
||||||
|
since string
|
||||||
|
until string
|
||||||
|
filter []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEventsCommand creats a new cobra.Command for `docker events`
|
||||||
|
func NewEventsCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||||
|
var opts eventsOptions
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "events [OPTIONS]",
|
||||||
|
Short: "Get real time events from the server",
|
||||||
|
Args: cli.NoArgs,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return runEvents(dockerCli, &opts)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
flags := cmd.Flags()
|
||||||
|
flags.StringVar(&opts.since, "since", "", "Show all events created since timestamp")
|
||||||
|
flags.StringVar(&opts.until, "until", "", "Stream events until this timestamp")
|
||||||
|
flags.StringSliceVarP(&opts.filter, "filter", "f", []string{}, "Filter output based on conditions provided")
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func runEvents(dockerCli *client.DockerCli, opts *eventsOptions) error {
|
||||||
|
eventFilterArgs := filters.NewArgs()
|
||||||
|
|
||||||
|
// Consolidate all filter flags, and sanity check them early.
|
||||||
|
// They'll get process in the daemon/server.
|
||||||
|
for _, f := range opts.filter {
|
||||||
|
var err error
|
||||||
|
eventFilterArgs, err = filters.ParseFlag(f, eventFilterArgs)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
options := types.EventsOptions{
|
||||||
|
Since: opts.since,
|
||||||
|
Until: opts.until,
|
||||||
|
Filters: eventFilterArgs,
|
||||||
|
}
|
||||||
|
|
||||||
|
responseBody, err := dockerCli.Client().Events(context.Background(), options)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer responseBody.Close()
|
||||||
|
|
||||||
|
return streamEvents(responseBody, dockerCli.Out())
|
||||||
|
}
|
||||||
|
|
||||||
|
// streamEvents decodes prints the incoming events in the provided output.
|
||||||
|
func streamEvents(input io.Reader, output io.Writer) error {
|
||||||
|
return DecodeEvents(input, func(event eventtypes.Message, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
printOutput(event, output)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type eventProcessor func(event eventtypes.Message, err error) error
|
||||||
|
|
||||||
|
// printOutput prints all types of event information.
|
||||||
|
// Each output includes the event type, actor id, name and action.
|
||||||
|
// Actor attributes are printed at the end if the actor has any.
|
||||||
|
func printOutput(event eventtypes.Message, output io.Writer) {
|
||||||
|
if event.TimeNano != 0 {
|
||||||
|
fmt.Fprintf(output, "%s ", time.Unix(0, event.TimeNano).Format(jsonlog.RFC3339NanoFixed))
|
||||||
|
} else if event.Time != 0 {
|
||||||
|
fmt.Fprintf(output, "%s ", time.Unix(event.Time, 0).Format(jsonlog.RFC3339NanoFixed))
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(output, "%s %s %s", event.Type, event.Action, event.Actor.ID)
|
||||||
|
|
||||||
|
if len(event.Actor.Attributes) > 0 {
|
||||||
|
var attrs []string
|
||||||
|
var keys []string
|
||||||
|
for k := range event.Actor.Attributes {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
for _, k := range keys {
|
||||||
|
v := event.Actor.Attributes[k]
|
||||||
|
attrs = append(attrs, fmt.Sprintf("%s=%s", k, v))
|
||||||
|
}
|
||||||
|
fmt.Fprintf(output, " (%s)", strings.Join(attrs, ", "))
|
||||||
|
}
|
||||||
|
fmt.Fprint(output, "\n")
|
||||||
|
}
|
66
api/client/system/events_utils.go
Normal file
66
api/client/system/events_utils.go
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
eventtypes "github.com/docker/engine-api/types/events"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EventHandler is abstract interface for user to customize
|
||||||
|
// own handle functions of each type of events
|
||||||
|
type EventHandler interface {
|
||||||
|
Handle(action string, h func(eventtypes.Message))
|
||||||
|
Watch(c <-chan eventtypes.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitEventHandler initializes and returns an EventHandler
|
||||||
|
func InitEventHandler() EventHandler {
|
||||||
|
return &eventHandler{handlers: make(map[string]func(eventtypes.Message))}
|
||||||
|
}
|
||||||
|
|
||||||
|
type eventHandler struct {
|
||||||
|
handlers map[string]func(eventtypes.Message)
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *eventHandler) Handle(action string, h func(eventtypes.Message)) {
|
||||||
|
w.mu.Lock()
|
||||||
|
w.handlers[action] = h
|
||||||
|
w.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch ranges over the passed in event chan and processes the events based on the
|
||||||
|
// handlers created for a given action.
|
||||||
|
// To stop watching, close the event chan.
|
||||||
|
func (w *eventHandler) Watch(c <-chan eventtypes.Message) {
|
||||||
|
for e := range c {
|
||||||
|
w.mu.Lock()
|
||||||
|
h, exists := w.handlers[e.Action]
|
||||||
|
w.mu.Unlock()
|
||||||
|
if !exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
logrus.Debugf("event handler: received event: %v", e)
|
||||||
|
go h(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeEvents decodes event from input stream
|
||||||
|
func DecodeEvents(input io.Reader, ep eventProcessor) error {
|
||||||
|
dec := json.NewDecoder(input)
|
||||||
|
for {
|
||||||
|
var event eventtypes.Message
|
||||||
|
err := dec.Decode(&event)
|
||||||
|
if err != nil && err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if procErr := ep(event, err); procErr != nil {
|
||||||
|
return procErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -48,6 +48,7 @@ func NewCobraAdaptor(clientFlags *cliflags.ClientFlags) CobraAdaptor {
|
||||||
container.NewRmCommand(dockerCli),
|
container.NewRmCommand(dockerCli),
|
||||||
container.NewRunCommand(dockerCli),
|
container.NewRunCommand(dockerCli),
|
||||||
container.NewStartCommand(dockerCli),
|
container.NewStartCommand(dockerCli),
|
||||||
|
container.NewStatsCommand(dockerCli),
|
||||||
container.NewStopCommand(dockerCli),
|
container.NewStopCommand(dockerCli),
|
||||||
container.NewTopCommand(dockerCli),
|
container.NewTopCommand(dockerCli),
|
||||||
container.NewUnpauseCommand(dockerCli),
|
container.NewUnpauseCommand(dockerCli),
|
||||||
|
@ -60,6 +61,7 @@ func NewCobraAdaptor(clientFlags *cliflags.ClientFlags) CobraAdaptor {
|
||||||
image.NewImportCommand(dockerCli),
|
image.NewImportCommand(dockerCli),
|
||||||
image.NewTagCommand(dockerCli),
|
image.NewTagCommand(dockerCli),
|
||||||
network.NewNetworkCommand(dockerCli),
|
network.NewNetworkCommand(dockerCli),
|
||||||
|
system.NewEventsCommand(dockerCli),
|
||||||
system.NewVersionCommand(dockerCli),
|
system.NewVersionCommand(dockerCli),
|
||||||
volume.NewVolumeCommand(dockerCli),
|
volume.NewVolumeCommand(dockerCli),
|
||||||
)
|
)
|
||||||
|
|
|
@ -10,7 +10,6 @@ type Command struct {
|
||||||
var DockerCommandUsage = []Command{
|
var DockerCommandUsage = []Command{
|
||||||
{"commit", "Create a new image from a container's changes"},
|
{"commit", "Create a new image from a container's changes"},
|
||||||
{"cp", "Copy files/folders between a container and the local filesystem"},
|
{"cp", "Copy files/folders between a container and the local filesystem"},
|
||||||
{"events", "Get real time events from the server"},
|
|
||||||
{"exec", "Run a command in a running container"},
|
{"exec", "Run a command in a running container"},
|
||||||
{"info", "Display system-wide information"},
|
{"info", "Display system-wide information"},
|
||||||
{"inspect", "Return low-level information on a container or image"},
|
{"inspect", "Return low-level information on a container or image"},
|
||||||
|
@ -21,7 +20,6 @@ var DockerCommandUsage = []Command{
|
||||||
{"pull", "Pull an image or a repository from a registry"},
|
{"pull", "Pull an image or a repository from a registry"},
|
||||||
{"push", "Push an image or a repository to a registry"},
|
{"push", "Push an image or a repository to a registry"},
|
||||||
{"save", "Save one or more images to a tar archive"},
|
{"save", "Save one or more images to a tar archive"},
|
||||||
{"stats", "Display a live stream of container(s) resource usage statistics"},
|
|
||||||
{"update", "Update configuration of one or more containers"},
|
{"update", "Update configuration of one or more containers"},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue