Skip to content

Commit

Permalink
sysdump: distil events in to a nice filterable HTML table
Browse files Browse the repository at this point in the history
Kubernetes events are, as-is, pretty hard to use. Let's make them easier
to grok:
- sort by time
- write in a nice table
- make the table filterable / sortable, etc.

Signed-off-by: Casey Callendrello <cdc@isovalent.com>
  • Loading branch information
squeed committed Feb 14, 2023
1 parent a8bd8f1 commit ed53472
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 0 deletions.
72 changes: 72 additions & 0 deletions sysdump/eventSummary.html
@@ -0,0 +1,72 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<script src="https://code.jquery.com/jquery-3.6.3.min.js" integrity="sha384-Ft/vb48LwsAEtgltj7o+6vtS2esTU9PCpDqcXs4OCVQFZu5BqprHtUCZ4kjK+bpE" crossorigin="anonymous"></script>

<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4" crossorigin="anonymous"></script>

<link rel="stylesheet" href="https://unpkg.com/bootstrap-table@1.21.2/dist/bootstrap-table.min.css" integrity="sha384-rOuL3fofnnH7GPPPb2f67WUfBKYs133VKgf8XdxjPIlS5/YgJf/dly+Rs2KAZ/24" crossorigin="anonymous">
<script src="https://unpkg.com/bootstrap-table@1.21.2/dist/bootstrap-table.min.js" integrity="sha384-8at6Iy2orlmk4xawCuXCOocKf2kQnZakSdUUy/uZVpllyY9QSUgo5JWNlMAugFBu" crossorigin="anonymous"></script>
<script src="https://unpkg.com/bootstrap-table@1.21.2/dist/extensions/filter-control/bootstrap-table-filter-control.min.js" integrity="sha384-pAaLcAde7X+M80ngfMHGtPQ3OBBkoRBchgNw7ihMOwfJeJmDBbDy6gNsn3Eard3n" crossorigin="anonymous"></script>
<script src="https://unpkg.com/bootstrap-table@1.21.2/dist/extensions/toolbar/bootstrap-table-toolbar.min.js" integrity="sha384-iAFT227M9ZYSf6LjxhqNpsof4z0D5pEEqh+jT2QOK1Py4j05B9MQDR7cCQiwCumo" crossorigin="anonymous"></script>

<title>Events</title>
<style type="text/css">
body * {
font-size: 12px!important;
}
</style>
</head>

<body>

<table
id="events"
class="table table-bordered table-hover table-sm"
data-toggle="table"
data-search="true"
data-show-search-clear-button="true"
data-filter-control="true"
data-advanced-search="true"
data-id-table="advancedTable"
data-pagination="true"
data-page-size="100"
data-show-columns-toggle-all="true"
data-show-pagination-switch="true"
data-show-columns="true">
<thead>
<tr>
<th data-width="100" data-field="time" data-filter-control="input" data-sortable="true">Time</th>
<th data-width="200" data-field="namespace" data-filter-control="input" data-sortable="true">Namespace</th>
<th data-width="200" data-field="component" data-filter-control="input" data-sortable="true">Component</th>
<th data-width="200" data-field="host" data-filter-control="input" data-sortable="true">Host</th>
<th data-width="200" data-field="relatedobject" data-filter-control="input" data-sortable="true">RelatedObject</th>
<th data-field="reason" data-filter-control="input">Reason</th>
<th data-field="message" data-filter-control="input">Message</th>
</tr>
</thead>
<tbody>
{{range . }}
<tr>
<td>{{formatTime .ObjectMeta.CreationTimestamp .FirstTimestamp .LastTimestamp .Count}}</td>
<td>{{.Namespace}}</td>
<td>{{.Source.Component}}</td>
<td>{{.Source.Host}}</td>
<td>{{.InvolvedObject.Name}}</td>
<td {{reasonClass .Reason}}>{{.Reason}}</td>
<td>{{.Message}}</td>
</tr>
{{end}}
</tbody>
</table>

<script>
$(function() {
$('#events').bootstrapTable()
})
</script>
</body>
</html>
3 changes: 3 additions & 0 deletions sysdump/sysdump.go
Expand Up @@ -301,6 +301,9 @@ func (c *Collector) Run() error {
if err := c.WriteYAML(kubernetesEventsFileName, v); err != nil {
return fmt.Errorf("failed to collect Kubernetes events: %w", err)
}
if err := c.WriteBytes("events.html", makeEventTable(v.Items)); err != nil {
return fmt.Errorf("failed to write event tble: %w", err)
}
return nil
},
},
Expand Down
56 changes: 56 additions & 0 deletions sysdump/writers.go
Expand Up @@ -5,14 +5,24 @@ package sysdump

import (
"bytes"
_ "embed"
"fmt"
"html/template"
"os"
"sort"
"strings"
"time"

corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/cli-runtime/pkg/printers"
"k8s.io/client-go/kubernetes/scheme"
)

//go:embed eventSummary.html
var eventSummaryHTML string

func writeBytes(p string, b []byte) error {
return os.WriteFile(p, b, fileMode)
}
Expand Down Expand Up @@ -44,3 +54,49 @@ func writeYaml(p string, o runtime.Object) error {
}
return writeString(p, b.String())
}

// writeEventTable writes a html summary of cluster events.
func makeEventTable(events []corev1.Event) []byte {
// sort events by time
sort.Slice(events, func(i, j int) bool {
return events[i].LastTimestamp.Time.Before(events[j].LastTimestamp.Time)
})

t := template.Must(template.New("events").Funcs(template.FuncMap{
"formatTime": func(created, firstSeen, lastSeen metav1.Time, count int32) template.HTML {
countMsg := ""
if count > 1 {
countMsg = fmt.Sprintf(" <small>(x%d)</small>", count)
}
if lastSeen.IsZero() {
lastSeen = created
}
firstSeenUTC := firstSeen.In(time.UTC)
lastSeenUTC := lastSeen.In(time.UTC)
//nolint:gosec
return template.HTML(fmt.Sprintf(`<time datetime="%s" title="First Seen: %s">%s</time>%s`, lastSeenUTC.String(), firstSeenUTC.Format("15:04:05Z"), lastSeenUTC.Format("15:04:05Z"), countMsg))
},
"reasonClass": func(r string) template.HTMLAttr {
cssClass := "text-muted"
switch {
case strings.Contains(strings.ToLower(r), "fail"),
strings.Contains(strings.ToLower(r), "error"),
strings.Contains(strings.ToLower(r), "kill"),
strings.Contains(strings.ToLower(r), "backoff"):
cssClass = "text-danger"
case strings.Contains(strings.ToLower(r), "notready"),
strings.Contains(strings.ToLower(r), "unhealthy"),
strings.Contains(strings.ToLower(r), "missing"):
cssClass = "text-warning"
}
//nolint:gosec
return template.HTMLAttr(fmt.Sprintf(`class="%s"`, cssClass))
},
}).Parse(eventSummaryHTML))

out := bytes.NewBuffer([]byte{})
if err := t.Execute(out, events); err != nil {
panic(err) // unreachable
}
return out.Bytes()
}

0 comments on commit ed53472

Please sign in to comment.