Improve service tasks grouping on printing#2098
Conversation
|
Thank you! I gave this PR a spin with the following steps; Create some services docker service create --detach --name=a --replicas=4 nginx:alpine
docker service create --detach --name=b --replicas=3 nginx:alpine
docker service create --detach --name=c --mode=global nginx:alpineUpdate the services, so that they have a "history" docker service update --detach --env-add FOO=bar a
docker service update --detach --env-add FOO=bar b
docker service update --detach --env-add BAR=qux b
docker service update --detach --env-add FOO=bar cBefore this PR: docker service ps a b c
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
xbzm6ed776yw c.j1afavbqqhr21jvnid3nnfoyt nginx:alpine docker-desktop Running Running 5 seconds ago
4mcsovp8ckwn \_ c.j1afavbqqhr21jvnid3nnfoyt nginx:alpine docker-desktop Shutdown Shutdown 6 seconds ago
qpcgdsx1r21a b.1 nginx:alpine docker-desktop Running Running 2 seconds ago
kfjo1hly92l4 a.1 nginx:alpine docker-desktop Running Running 5 seconds ago
pubrerosvsw5 b.1 nginx:alpine docker-desktop Shutdown Shutdown 3 seconds ago
fu08gfi8tfyv a.1 nginx:alpine docker-desktop Shutdown Shutdown 7 seconds ago
pu6qmgyoibq4 b.2 nginx:alpine docker-desktop Running Ready 1 second ago
tz1n4hjne6pk \_ b.2 nginx:alpine docker-desktop Shutdown Shutdown less than a second ago
xq8dogqcbxd2 a.2 nginx:alpine docker-desktop Running Running 44 seconds ago
rm40lofzed0h a.3 nginx:alpine docker-desktop Running Starting less than a second ago
sqqj2n9fpi82 b.3 nginx:alpine docker-desktop Running Running 5 seconds ago
prv3gymkvqk6 \_ b.3 nginx:alpine docker-desktop Shutdown Shutdown 6 seconds ago
qn7c7jmjuo76 a.3 nginx:alpine docker-desktop Shutdown Shutdown less than a second ago
wi9330mbabpg a.4 nginx:alpine docker-desktop Running Running 2 seconds ago
p5oy6h7nkvc3 \_ a.4 nginx:alpine docker-desktop Shutdown Shutdown 3 seconds ago After this PR: docker service ps a b c
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
xbzm6ed776yw c.j1afavbqqhr21jvnid3nnfoyt nginx:alpine docker-desktop Running Running 32 seconds ago
4mcsovp8ckwn \_ c.j1afavbqqhr21jvnid3nnfoyt nginx:alpine docker-desktop Shutdown Shutdown 33 seconds ago
kfjo1hly92l4 a.1 nginx:alpine docker-desktop Running Running 32 seconds ago
fu08gfi8tfyv \_ a.1 nginx:alpine docker-desktop Shutdown Shutdown 34 seconds ago
3pam0limnn24 a.2 nginx:alpine docker-desktop Running Running 23 seconds ago
xq8dogqcbxd2 \_ a.2 nginx:alpine docker-desktop Shutdown Shutdown 24 seconds ago
rm40lofzed0h a.3 nginx:alpine docker-desktop Running Running 26 seconds ago
qn7c7jmjuo76 \_ a.3 nginx:alpine docker-desktop Shutdown Shutdown 27 seconds ago
wi9330mbabpg a.4 nginx:alpine docker-desktop Running Running 29 seconds ago
p5oy6h7nkvc3 \_ a.4 nginx:alpine docker-desktop Shutdown Shutdown 30 seconds ago
qpcgdsx1r21a b.1 nginx:alpine docker-desktop Running Running 29 seconds ago
pubrerosvsw5 \_ b.1 nginx:alpine docker-desktop Shutdown Shutdown 30 seconds ago
pu6qmgyoibq4 b.2 nginx:alpine docker-desktop Running Running 26 seconds ago
tz1n4hjne6pk \_ b.2 nginx:alpine docker-desktop Shutdown Shutdown 27 seconds ago
sqqj2n9fpi82 b.3 nginx:alpine docker-desktop Running Running 32 seconds ago
prv3gymkvqk6 \_ b.3 nginx:alpine docker-desktop Shutdown Shutdown 33 seconds ago That definitely looks a lot better Perhaps we should sort the tasks on their name (instead of service ID)? That way the sort order looks more natural (in the example above, service |
|
Let me write up a quick patch with what I think would work 👍 |
|
We should also consider keeping the "indent" ( |
|
Ok, I think this should do the trick; I used a slightly different approach, and actually set the name of the task (which looks to be currently unused). Then, with the name being in the right format ( After that, we can detect if indentation is needed, and print it with the selected Patch is below (shown collapsed), and I uploaded it as well; Detailsdiff --git a/cli/command/task/print.go b/cli/command/task/print.go
index 2c803b92..32a496bb 100644
--- a/cli/command/task/print.go
+++ b/cli/command/task/print.go
@@ -10,6 +10,7 @@ import (
"github.com/docker/cli/cli/command/idresolver"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/docker/api/types/swarm"
+ "vbom.ml/util/sortorder"
)
type tasksSortable []swarm.Task
@@ -23,23 +24,10 @@ func (t tasksSortable) Swap(i, j int) {
}
func (t tasksSortable) Less(i, j int) bool {
- // Sort by service ID.
- if t[i].ServiceID != t[j].ServiceID {
- return t[i].ServiceID < t[j].ServiceID
+ if t[i].Name != t[j].Name {
+ return sortorder.NaturalLess(t[i].Name, t[j].Name)
}
-
- // If same service, sort by slot.
- if t[i].Slot != t[j].Slot {
- return t[i].Slot < t[j].Slot
- }
-
- // If same service and slot, sort by node ID.
- // This sorting is relevant only for global services.
- if t[i].NodeID != t[j].NodeID {
- return t[i].NodeID < t[j].NodeID
- }
-
- // If same service, slot and node - sort by most recent.
+ // Sort tasks for the same service and slot by most recent.
return t[j].Meta.CreatedAt.Before(t[i].CreatedAt)
}
@@ -47,6 +35,14 @@ func (t tasksSortable) Less(i, j int) bool {
// Besides this, command `docker node ps <node>`
// and `docker stack ps` will call this, too.
func Print(ctx context.Context, dockerCli command.Cli, tasks []swarm.Task, resolver *idresolver.IDResolver, trunc, quiet bool, format string) error {
+ tasks, err := generateTaskNames(ctx, tasks, resolver)
+ if err != nil {
+ return err
+ }
+
+ // First sort tasks, so that all tasks (including previous ones) of the same
+ // service and slot are together. This must be done first, to print "previous"
+ // tasks indented
sort.Stable(tasksSortable(tasks))
names := map[string]string{}
@@ -58,40 +54,55 @@ func Print(ctx context.Context, dockerCli command.Cli, tasks []swarm.Task, resol
Trunc: trunc,
}
+ var indent string
+ if tasksCtx.Format.IsTable() {
+ indent = ` \_ `
+ }
prevName := ""
for _, task := range tasks {
- serviceName, err := resolver.Resolve(ctx, swarm.Service{}, task.ServiceID)
- if err != nil {
- return err
+ if task.Name == prevName {
+ // Indent previous tasks of the same slot
+ names[task.ID] = indent
+ } else {
+ names[task.ID] = ""
}
+ prevName = task.Name
nodeValue, err := resolver.Resolve(ctx, swarm.Node{}, task.NodeID)
if err != nil {
return err
}
+ nodes[task.ID] = nodeValue
+ }
- var name string
- if task.Slot != 0 {
- name = fmt.Sprintf("%v.%v", serviceName, task.Slot)
- } else {
- name = fmt.Sprintf("%v.%v", serviceName, task.NodeID)
- }
+ return FormatWrite(tasksCtx, tasks, names, nodes)
+}
- // Indent the name if necessary
- indentedName := name
- if name == prevName {
- indentedName = fmt.Sprintf(" \\_ %s", indentedName)
+// generateTaskNames generates names for the given tasks, and returns a copy of
+// the slice with the 'Name' field set.
+//
+// Depending if the "--no-resolve" option is set, names have the following pattern:
+//
+// - ServiceName.Slot or ServiceID.Slot for tasks that are part of a replicated service
+// - ServiceName.NodeName or ServiceID.NodeID for tasks that are part of a global service
+//
+// Task-names are not unique in cases where "tasks" contains previous/rotated tasks.
+func generateTaskNames(ctx context.Context, tasks []swarm.Task, resolver *idresolver.IDResolver) ([]swarm.Task, error) {
+ // Use a copy of the tasks list, to not modify the original slice
+ t := append(tasks[:0:0], tasks...)
+
+ for i, task := range t {
+ serviceName, err := resolver.Resolve(ctx, swarm.Service{}, task.ServiceID)
+ if err != nil {
+ return nil, err
}
- prevName = name
-
- names[task.ID] = name
- if tasksCtx.Format.IsTable() {
- names[task.ID] = indentedName
+ if task.Slot != 0 {
+ t[i].Name = fmt.Sprintf("%v.%v", serviceName, task.Slot)
+ } else {
+ t[i].Name = fmt.Sprintf("%v.%v", serviceName, task.NodeID)
}
- nodes[task.ID] = nodeValue
}
-
- return FormatWrite(tasksCtx, tasks, names, nodes)
+ return t, nil
}
// DefaultFormat returns the default format from the config file, or table
|
|
Hey @thaJeztah , thank you for your response. Sorry, this is my first pull request, so I don't really know what am I supposed to do now. Should I close/discard my pull request so that you can merge your code? |
|
@SaretX sorry for the delay; feel free to update your PR with my suggested changes (you can take them from the branch I pushed), but let me know if you need a hand doing so |
When printing services' tasks with `docker service ps` command, tasks are grouped only by task slot. This leads to interleaving tasks from different services when `docker service ps` is called with multiple services. Besides this, global services do not have slots at all and printing tasks for them doesn't group and doesn't properly indent tasks with \_. With this patch all tasks are grouped by service ID, slot and node ID (relevant only for global services) and it fixes issue 533. Signed-off-by: Andrii Berehuliak <[email protected]>
When printing services' tasks with `docker service ps` command, tasks are grouped only by task slot. This leads to interleaving tasks from different services when `docker service ps` is called with multiple services. Besides this, global services do not have slots at all and printing tasks for them doesn't group and doesn't properly indent tasks with \_. With this patch all tasks are grouped by service ID, slot and node ID (relevant only for global services) and it fixes issue 533. Before this patch: ```console docker service ps a b c ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS xbzm6ed776yw c.j1afavbqqhr21jvnid3nnfoyt nginx:alpine docker-desktop Running Running 5 seconds ago 4mcsovp8ckwn \_ c.j1afavbqqhr21jvnid3nnfoyt nginx:alpine docker-desktop Shutdown Shutdown 6 seconds ago qpcgdsx1r21a b.1 nginx:alpine docker-desktop Running Running 2 seconds ago kfjo1hly92l4 a.1 nginx:alpine docker-desktop Running Running 5 seconds ago pubrerosvsw5 b.1 nginx:alpine docker-desktop Shutdown Shutdown 3 seconds ago fu08gfi8tfyv a.1 nginx:alpine docker-desktop Shutdown Shutdown 7 seconds ago pu6qmgyoibq4 b.2 nginx:alpine docker-desktop Running Ready 1 second ago tz1n4hjne6pk \_ b.2 nginx:alpine docker-desktop Shutdown Shutdown less than a second ago xq8dogqcbxd2 a.2 nginx:alpine docker-desktop Running Running 44 seconds ago rm40lofzed0h a.3 nginx:alpine docker-desktop Running Starting less than a second ago sqqj2n9fpi82 b.3 nginx:alpine docker-desktop Running Running 5 seconds ago prv3gymkvqk6 \_ b.3 nginx:alpine docker-desktop Shutdown Shutdown 6 seconds ago qn7c7jmjuo76 a.3 nginx:alpine docker-desktop Shutdown Shutdown less than a second ago wi9330mbabpg a.4 nginx:alpine docker-desktop Running Running 2 seconds ago p5oy6h7nkvc3 \_ a.4 nginx:alpine docker-desktop Shutdown Shutdown 3 seconds ago ``` After this patch: ```console docker service ps a b c ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS kfjo1hly92l4 a.1 nginx:alpine docker-desktop Running Running 32 seconds ago fu08gfi8tfyv \_ a.1 nginx:alpine docker-desktop Shutdown Shutdown 34 seconds ago 3pam0limnn24 a.2 nginx:alpine docker-desktop Running Running 23 seconds ago xq8dogqcbxd2 \_ a.2 nginx:alpine docker-desktop Shutdown Shutdown 24 seconds ago rm40lofzed0h a.3 nginx:alpine docker-desktop Running Running 26 seconds ago qn7c7jmjuo76 \_ a.3 nginx:alpine docker-desktop Shutdown Shutdown 27 seconds ago wi9330mbabpg a.4 nginx:alpine docker-desktop Running Running 29 seconds ago p5oy6h7nkvc3 \_ a.4 nginx:alpine docker-desktop Shutdown Shutdown 30 seconds ago qpcgdsx1r21a b.1 nginx:alpine docker-desktop Running Running 29 seconds ago pubrerosvsw5 \_ b.1 nginx:alpine docker-desktop Shutdown Shutdown 30 seconds ago pu6qmgyoibq4 b.2 nginx:alpine docker-desktop Running Running 26 seconds ago tz1n4hjne6pk \_ b.2 nginx:alpine docker-desktop Shutdown Shutdown 27 seconds ago sqqj2n9fpi82 b.3 nginx:alpine docker-desktop Running Running 32 seconds ago prv3gymkvqk6 \_ b.3 nginx:alpine docker-desktop Shutdown Shutdown 33 seconds ago xbzm6ed776yw c.j1afavbqqhr21jvnid3nnfoyt nginx:alpine docker-desktop Running Running 32 seconds ago 4mcsovp8ckwn \_ c.j1afavbqqhr21jvnid3nnfoyt nginx:alpine docker-desktop Shutdown Shutdown 33 seconds ago ``` Signed-off-by: Andrii Berehuliak <[email protected]> Signed-off-by: Sebastiaan van Stijn <[email protected]>
343a218 to
25cc7b7
Compare
|
hm.. looks like tests are failing. I'll need to have a look at what's causing that; |
| return t[i].Slot < t[j].Slot | ||
| func (t tasksSortable) Less(i, j int) bool { | ||
| if t[i].Name != t[j].Name { | ||
| return sortorder.NaturalLess(t[i].Name, t[j].Name) |
There was a problem hiding this comment.
Hello @SaretX, thank you for your contribution 👍
I think that as we update the sort behavior here, at one test should have been updated or added to reflect that change 🤔
Can you update your PR with that test? Please tell me if you need help for that 👍
|
fixed the failing test and added a new test to test the sorting behavior; #2341 |
When printing services' tasks with
docker service pscommand, tasks are grouped only by task slot.This leads to interleaving tasks from different services when
docker service psis called with multiple services.Besides this, global services do not have slots at all and printing tasks for them doesn't group and
doesn't properly indent tasks with _.
With this patch all tasks are grouped by service ID, slot and node ID (relevant only for global services) and it fixes issue #533.
Signed-off-by: Andrii Berehuliak [email protected]
- What I did
Improved tasks grouping on
docker service psfor multiple services and fixed issue with global services. Fixes #533- How I did it
Changed printing func.
- How to verify it
- Description for the changelog
Improves tasks printing for docker services