mirror of
https://github.com/weaveworks/scope.git
synced 2026-07-18 12:59:31 +00:00
That way, as the top-level target.Merge(arg) on Report recursively descends into the data structure, all component Merge operations are invoked as target_component.Merge(arg_component). Merge is commutative, so semantically this does not matter. But it is easier to reason about and debug Merge code this way. Moreover, the merge order matters for performance since some merges, in particular LatestMap.Merge, are faster when the entries in the target are younger than the entries in the arg. So this change allows high-level code to invoke Report.Merge in the optimal order, in the knowledge that the order is preserved for component merges. Most of the Merge functions were already preserving order. Of the four that didn't, Nodes.Merge is the most significant - it is the only one that affects LatestMap.Merge order.
173 lines
4.3 KiB
Go
173 lines
4.3 KiB
Go
package report
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
log "github.com/Sirupsen/logrus"
|
|
"github.com/weaveworks/scope/common/mtime"
|
|
)
|
|
|
|
// MaxTableRows sets the limit on the table size to render
|
|
// TODO: this won't be needed once we send reports incrementally
|
|
const (
|
|
MaxTableRows = 20
|
|
TruncationCountPrefix = "table_truncation_count_"
|
|
)
|
|
|
|
// AddTable appends arbirary key-value pairs to the Node, returning a new node.
|
|
func (node Node) AddTable(prefix string, labels map[string]string) Node {
|
|
count := 0
|
|
for key, value := range labels {
|
|
if count >= MaxTableRows {
|
|
break
|
|
}
|
|
node = node.WithLatest(prefix+key, mtime.Now(), value)
|
|
count++
|
|
}
|
|
if len(labels) > MaxTableRows {
|
|
truncationCount := fmt.Sprintf("%d", len(labels)-MaxTableRows)
|
|
node = node.WithLatest(TruncationCountPrefix+prefix, mtime.Now(), truncationCount)
|
|
}
|
|
return node
|
|
}
|
|
|
|
// ExtractTable returns the key-value pairs with the given prefix from this Node,
|
|
func (node Node) ExtractTable(prefix string) (rows map[string]string, truncationCount int) {
|
|
rows = map[string]string{}
|
|
truncationCount = 0
|
|
node.Latest.ForEach(func(key, value string) {
|
|
if strings.HasPrefix(key, prefix) {
|
|
label := key[len(prefix):]
|
|
rows[label] = value
|
|
}
|
|
})
|
|
if str, ok := node.Latest.Lookup(TruncationCountPrefix + prefix); ok {
|
|
if n, err := fmt.Sscanf(str, "%d", &truncationCount); n != 1 || err != nil {
|
|
log.Warn("Unexpected truncation count format %q", str)
|
|
}
|
|
}
|
|
return rows, truncationCount
|
|
}
|
|
|
|
// Table is the type for a table in the UI.
|
|
type Table struct {
|
|
ID string `json:"id"`
|
|
Label string `json:"label"`
|
|
Rows []MetadataRow `json:"rows"`
|
|
TruncationCount int `json:"truncationCount,omitempty"`
|
|
}
|
|
|
|
type tablesByID []Table
|
|
|
|
func (t tablesByID) Len() int { return len(t) }
|
|
func (t tablesByID) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
|
|
func (t tablesByID) Less(i, j int) bool { return t[i].ID < t[j].ID }
|
|
|
|
// Copy returns a copy of the Table.
|
|
func (t Table) Copy() Table {
|
|
result := Table{
|
|
ID: t.ID,
|
|
Label: t.Label,
|
|
Rows: make([]MetadataRow, 0, len(t.Rows)),
|
|
}
|
|
for _, row := range t.Rows {
|
|
result.Rows = append(result.Rows, row)
|
|
}
|
|
return result
|
|
}
|
|
|
|
// TableTemplate describes how to render a table for the UI.
|
|
type TableTemplate struct {
|
|
ID string `json:"id"`
|
|
Label string `json:"label"`
|
|
Prefix string `json:"prefix"`
|
|
}
|
|
|
|
// Copy returns a value-copy of the TableTemplate
|
|
func (t TableTemplate) Copy() TableTemplate {
|
|
return t
|
|
}
|
|
|
|
// Merge other into t, returning a fresh copy. Does fieldwise max -
|
|
// whilst this isn't particularly meaningful, at least it idempotent,
|
|
// commutativite and associative.
|
|
func (t TableTemplate) Merge(other TableTemplate) TableTemplate {
|
|
max := func(s1, s2 string) string {
|
|
if s1 > s2 {
|
|
return s1
|
|
}
|
|
return s2
|
|
}
|
|
|
|
return TableTemplate{
|
|
ID: max(t.ID, other.ID),
|
|
Label: max(t.Label, other.Label),
|
|
Prefix: max(t.Prefix, other.Prefix),
|
|
}
|
|
}
|
|
|
|
// TableTemplates is a mergeable set of TableTemplate
|
|
type TableTemplates map[string]TableTemplate
|
|
|
|
// Tables renders the TableTemplates for a given node.
|
|
func (t TableTemplates) Tables(node Node) []Table {
|
|
var result []Table
|
|
for _, template := range t {
|
|
rows, truncationCount := node.ExtractTable(template.Prefix)
|
|
table := Table{
|
|
ID: template.ID,
|
|
Label: template.Label,
|
|
Rows: []MetadataRow{},
|
|
TruncationCount: truncationCount,
|
|
}
|
|
keys := make([]string, 0, len(rows))
|
|
for k := range rows {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Strings(keys)
|
|
for _, key := range keys {
|
|
table.Rows = append(table.Rows, MetadataRow{
|
|
ID: "label_" + key,
|
|
Label: key,
|
|
Value: rows[key],
|
|
})
|
|
}
|
|
result = append(result, table)
|
|
}
|
|
sort.Sort(tablesByID(result))
|
|
return result
|
|
}
|
|
|
|
// Copy returns a value copy of the TableTemplates
|
|
func (t TableTemplates) Copy() TableTemplates {
|
|
if t == nil {
|
|
return nil
|
|
}
|
|
result := TableTemplates{}
|
|
for k, v := range t {
|
|
result[k] = v.Copy()
|
|
}
|
|
return result
|
|
}
|
|
|
|
// Merge merges two sets of TableTemplates
|
|
func (t TableTemplates) Merge(other TableTemplates) TableTemplates {
|
|
if t == nil && other == nil {
|
|
return nil
|
|
}
|
|
result := make(TableTemplates, len(t))
|
|
for k, v := range t {
|
|
result[k] = v
|
|
}
|
|
for k, v := range other {
|
|
if existing, ok := result[k]; ok {
|
|
result[k] = existing.Merge(v)
|
|
} else {
|
|
result[k] = v
|
|
}
|
|
}
|
|
return result
|
|
}
|