Merge pull request #66643 from smarterclayton/improve_events

Automatic merge from submit-queue (batch tested with PRs 66445, 66643, 60551). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>.

Improve the output of `kubectl get events`

Events have long shown the most data of the core objects in their output, but that data is of varying use to a user. Following the principle that events are intended for the system to communicate information back to the user, and that Message is the primary human readable field, this commit alters the default columns to ensure event is shown with the most width given to the message, and all other fields organized by their relevance to the message.

1. Events are no longer sorted in the printer (this was a bug and was broken with paging and server side rendering)
2. Only the last seen, type, reason, kind, and message fields are shown by default, which makes the message prominent
3. Source, subobject, count, and first seen are only shown under `-o wide`
4. The duration fields were changed to be the more precise output introduced for job duration (2-3 sig figs)
5. Prioritized the column order for scanning - when, how important, what kind of error, what kind of object, and the message.
6. Trim trailing newlines on the message.

```release-note
Improved the output of `kubectl get events` to prioritize showing the message, and move some fields to `-o wide`.
```

```
$ kubectl get events --sort-by lastTimestamp
LAST SEEN TYPE      REASON                   KIND                    MESSAGE
16m       Normal    SawCompletedJob          CronJob                 Saw completed job: image-mirror-origin-v3.11-quay-1532581200
16m       Normal    SuccessfulDelete         CronJob                 Deleted job image-mirror-origin-v3.11-quay-1532577600
14m       Normal    Scheduled                Pod                     Successfully assigned 50c42204-9091-11e8-b2a1-0a58ac101869 to origin-ci-ig-n-fqfh
14m       Normal    Pulling                  Pod                     pulling image "docker-registry.default.svc:5000/ci/commenter:latest"
14m       Normal    Created                  Pod                     Created container
14m       Normal    Pulled                   Pod                     Successfully pulled image "docker-registry.default.svc:5000/ci/commenter:latest"
14m       Normal    Started                  Pod                     Started container
14m       Normal    SandboxChanged           Pod                     Pod sandbox changed, it will be killed and re-created.
4m14s     Normal    ScaleDown                Pod                     deleting pod for node scale down
4m14s     Normal    ScaleDown                Pod                     deleting pod for node scale down
4m14s     Normal    ScaleDown                Pod                     deleting pod for node scale down
4m14s     Normal    ScaleDown                Pod                     deleting pod for node scale down
4m14s     Normal    ScaleDown                Pod                     deleting pod for node scale down
4m14s     Normal    ScaleDown                Pod                     deleting pod for node scale down
4m14s     Normal    ScaleDown                Pod                     deleting pod for node scale down
4m13s     Normal    SuccessfulCreate         ReplicationController   Created pod: tide-30-hmncf
4m13s     Normal    Scheduled                Pod                     Successfully assigned tide-30-hmncf to origin-ci-ig-n-x64l
4m12s     Normal    SuccessfulCreate         ReplicationController   Created pod: console-jenkins-operator-16-dd5k8
4m12s     Normal    SuccessfulCreate         ReplicationController   Created pod: sinker-23-scfmt
```
pull/8/head
Kubernetes Submit Queue 2018-07-31 15:56:07 -07:00 committed by GitHub
commit f49708bd49
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 53 additions and 26 deletions

View File

@ -21,7 +21,6 @@ import (
"fmt"
"io"
"net"
"sort"
"strconv"
"strings"
"time"
@ -44,7 +43,6 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/duration"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/kubernetes/pkg/api/events"
"k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/apis/autoscaling"
"k8s.io/kubernetes/pkg/apis/batch"
@ -236,15 +234,15 @@ func AddHandlers(h printers.PrintHandler) {
eventColumnDefinitions := []metav1beta1.TableColumnDefinition{
{Name: "Last Seen", Type: "string", Description: apiv1.Event{}.SwaggerDoc()["lastTimestamp"]},
{Name: "First Seen", Type: "string", Description: apiv1.Event{}.SwaggerDoc()["firstTimestamp"]},
{Name: "Count", Type: "string", Description: apiv1.Event{}.SwaggerDoc()["count"]},
{Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]},
{Name: "Kind", Type: "string", Description: apiv1.Event{}.InvolvedObject.SwaggerDoc()["kind"]},
{Name: "Subobject", Type: "string", Description: apiv1.Event{}.InvolvedObject.SwaggerDoc()["fieldPath"]},
{Name: "Type", Type: "string", Description: apiv1.Event{}.SwaggerDoc()["type"]},
{Name: "Reason", Type: "string", Description: apiv1.Event{}.SwaggerDoc()["reason"]},
{Name: "Source", Type: "string", Description: apiv1.Event{}.SwaggerDoc()["source"]},
{Name: "Kind", Type: "string", Description: apiv1.Event{}.InvolvedObject.SwaggerDoc()["kind"]},
{Name: "Source", Type: "string", Priority: 1, Description: apiv1.Event{}.SwaggerDoc()["source"]},
{Name: "Message", Type: "string", Description: apiv1.Event{}.SwaggerDoc()["message"]},
{Name: "Subobject", Type: "string", Priority: 1, Description: apiv1.Event{}.InvolvedObject.SwaggerDoc()["fieldPath"]},
{Name: "First Seen", Type: "string", Priority: 1, Description: apiv1.Event{}.SwaggerDoc()["firstTimestamp"]},
{Name: "Count", Type: "string", Priority: 1, Description: apiv1.Event{}.SwaggerDoc()["count"]},
{Name: "Name", Type: "string", Priority: 1, Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]},
}
h.TableHandler(eventColumnDefinitions, printEvent)
h.TableHandler(eventColumnDefinitions, printEventList)
@ -515,7 +513,7 @@ func translateTimestampSince(timestamp metav1.Time) string {
return "<unknown>"
}
return duration.ShortHumanDuration(time.Since(timestamp.Time))
return duration.HumanDuration(time.Since(timestamp.Time))
}
// translateTimestampUntil returns the elapsed time until timestamp in
@ -525,7 +523,7 @@ func translateTimestampUntil(timestamp metav1.Time) string {
return "<unknown>"
}
return duration.ShortHumanDuration(time.Until(timestamp.Time))
return duration.HumanDuration(time.Until(timestamp.Time))
}
var (
@ -1328,25 +1326,42 @@ func printEvent(obj *api.Event, options printers.PrintOptions) ([]metav1beta1.Ta
Object: runtime.RawExtension{Object: obj},
}
// While watching event, we should print absolute time.
var FirstTimestamp, LastTimestamp string
var firstTimestamp, lastTimestamp string
if options.AbsoluteTimestamps {
FirstTimestamp = obj.FirstTimestamp.String()
LastTimestamp = obj.LastTimestamp.String()
firstTimestamp = obj.FirstTimestamp.String()
lastTimestamp = obj.LastTimestamp.String()
} else {
FirstTimestamp = translateTimestampSince(obj.FirstTimestamp)
LastTimestamp = translateTimestampSince(obj.LastTimestamp)
firstTimestamp = translateTimestampSince(obj.FirstTimestamp)
lastTimestamp = translateTimestampSince(obj.LastTimestamp)
}
if options.Wide {
row.Cells = append(row.Cells,
lastTimestamp,
obj.Type,
obj.Reason,
obj.InvolvedObject.Kind,
formatEventSource(obj.Source),
strings.TrimSpace(obj.Message),
obj.InvolvedObject.FieldPath,
firstTimestamp,
int64(obj.Count),
obj.Name,
)
} else {
row.Cells = append(row.Cells,
lastTimestamp,
obj.Type,
obj.Reason,
obj.InvolvedObject.Kind,
strings.TrimSpace(obj.Message),
)
}
row.Cells = append(row.Cells, LastTimestamp, FirstTimestamp,
int64(obj.Count), obj.Name, obj.InvolvedObject.Kind,
obj.InvolvedObject.FieldPath, obj.Type, obj.Reason,
formatEventSource(obj.Source), obj.Message)
return []metav1beta1.TableRow{row}, nil
}
// Sorts and prints the EventList in a human-friendly format.
func printEventList(list *api.EventList, options printers.PrintOptions) ([]metav1beta1.TableRow, error) {
sort.Sort(events.SortableEvents(list.Items))
rows := make([]metav1beta1.TableRow, 0, len(list.Items))
for i := range list.Items {
r, err := printEvent(&list.Items[i], options)

View File

@ -1927,7 +1927,7 @@ func TestTranslateTimestampSince(t *testing.T) {
{"unknown", translateTimestampSince(metav1.Time{}), "<unknown>"},
{"30 seconds ago", translateTimestampSince(metav1.Time{Time: time.Now().Add(-3e10)}), "30s"},
{"5 minutes ago", translateTimestampSince(metav1.Time{Time: time.Now().Add(-3e11)}), "5m"},
{"an hour ago", translateTimestampSince(metav1.Time{Time: time.Now().Add(-6e12)}), "1h"},
{"an hour ago", translateTimestampSince(metav1.Time{Time: time.Now().Add(-6e12)}), "100m"},
{"2 days ago", translateTimestampSince(metav1.Time{Time: time.Now().UTC().AddDate(0, 0, -2)}), "2d"},
{"months ago", translateTimestampSince(metav1.Time{Time: time.Now().UTC().AddDate(0, 0, -90)}), "90d"},
{"10 years ago", translateTimestampSince(metav1.Time{Time: time.Now().UTC().AddDate(-10, 0, 0)}), "10y"},
@ -1952,7 +1952,7 @@ func TestTranslateTimestampUntil(t *testing.T) {
{"unknown", translateTimestampUntil(metav1.Time{}), "<unknown>"},
{"in 30 seconds", translateTimestampUntil(metav1.Time{Time: time.Now().Add(3e10 + buf)}), "30s"},
{"in 5 minutes", translateTimestampUntil(metav1.Time{Time: time.Now().Add(3e11 + buf)}), "5m"},
{"in an hour", translateTimestampUntil(metav1.Time{Time: time.Now().Add(6e12 + buf)}), "1h"},
{"in an hour", translateTimestampUntil(metav1.Time{Time: time.Now().Add(6e12 + buf)}), "100m"},
{"in 2 days", translateTimestampUntil(metav1.Time{Time: time.Now().UTC().AddDate(0, 0, 2).Add(buf)}), "2d"},
{"in months", translateTimestampUntil(metav1.Time{Time: time.Now().UTC().AddDate(0, 0, 90).Add(buf)}), "90d"},
{"in 10 years", translateTimestampUntil(metav1.Time{Time: time.Now().UTC().AddDate(10, 0, 0).Add(buf)}), "10y"},

View File

@ -463,7 +463,7 @@ func TestConvertToTableList(t *testing.T) {
out: &metav1beta1.Table{
ColumnDefinitions: columns,
Rows: []metav1beta1.TableRow{
{Cells: []interface{}{"foo", "1/2", "Pending", int64(10), "1y", "10.1.2.3", "test-node", "nominated-node"}, Object: runtime.RawExtension{Object: pod1}},
{Cells: []interface{}{"foo", "1/2", "Pending", int64(10), "370d", "10.1.2.3", "test-node", "nominated-node"}, Object: runtime.RawExtension{Object: pod1}},
},
},
},

View File

@ -57,17 +57,29 @@ func HumanDuration(d time.Duration) string {
}
minutes := int(d / time.Minute)
if minutes < 10 {
return fmt.Sprintf("%dm%ds", minutes, int(d/time.Second)%60)
s := int(d/time.Second) % 60
if s == 0 {
return fmt.Sprintf("%dm", minutes)
}
return fmt.Sprintf("%dm%ds", minutes, s)
} else if minutes < 60*3 {
return fmt.Sprintf("%dm", minutes)
}
hours := int(d / time.Hour)
if hours < 8 {
return fmt.Sprintf("%dh%dm", hours, int(d/time.Minute)%60)
m := int(d/time.Minute) % 60
if m == 0 {
return fmt.Sprintf("%dh", hours)
}
return fmt.Sprintf("%dh%dm", hours, m)
} else if hours < 48 {
return fmt.Sprintf("%dh", hours)
} else if hours < 24*8 {
return fmt.Sprintf("%dd%dh", hours/24, hours%24)
h := hours % 24
if h == 0 {
return fmt.Sprintf("%dd", hours/24)
}
return fmt.Sprintf("%dd%dh", hours/24, h)
} else if hours < 24*365*2 {
return fmt.Sprintf("%dd", hours/24)
} else if hours < 24*365*8 {