Skip to content

Commit

Permalink
Sorting (#79)
Browse files Browse the repository at this point in the history
Add display sorting across most aggregators to support text, numeric, contextual, date-parsing, and by aggregated value. This commit breaks backwards compatibility by replacing --sort-key and --reverse in many aggregators with simply --sort=name
  • Loading branch information
zix99 committed Sep 10, 2022
1 parent c8a6060 commit f7b4e4e
Show file tree
Hide file tree
Showing 25 changed files with 787 additions and 174 deletions.
12 changes: 5 additions & 7 deletions cmd/bargraph.go
Expand Up @@ -16,8 +16,8 @@ go run . bars -sz -m "\[(.+?)\].*\" (\d+)" -e "{$ {buckettime {1} year nginx} {2

func bargraphFunction(c *cli.Context) error {
var (
stacked = c.Bool("stacked")
reverseSort = c.Bool("reverse")
stacked = c.Bool("stacked")
sortName = c.String(helpers.DefaultSortFlag.Name)
)

counter := aggregation.NewSubKeyCounter()
Expand All @@ -26,12 +26,13 @@ func bargraphFunction(c *cli.Context) error {

batcher := helpers.BuildBatcherFromArguments(c)
ext := helpers.BuildExtractorFromArguments(c, batcher)
sorter := helpers.BuildSorterOrFail(sortName)

helpers.RunAggregationLoop(ext, counter, func() {
line := 0

writer.SetKeys(counter.SubKeys()...)
for _, row := range counter.ItemsSorted(reverseSort) {
for _, row := range counter.ItemsSorted(sorter) {
writer.WriteBar(line, row.Name, row.Item.Items()...)
line++
}
Expand Down Expand Up @@ -61,10 +62,7 @@ func bargraphCommand() *cli.Command {
Aliases: []string{"s"},
Usage: "Display bargraph as stacked",
},
&cli.BoolFlag{
Name: "reverse",
Usage: "Reverses the display sort-order",
},
helpers.DefaultSortFlag,
},
})
}
16 changes: 15 additions & 1 deletion cmd/heatmap.go
Expand Up @@ -21,12 +21,16 @@ func heatmapFunction(c *cli.Context) error {
minVal = c.Int64("min")
maxFixed = c.IsSet("max")
maxVal = c.Int64("max")
sortRows = c.String("sort-rows")
sortCols = c.String("sort-cols")
)

counter := aggregation.NewTable(delim)

batcher := helpers.BuildBatcherFromArguments(c)
ext := helpers.BuildExtractorFromArguments(c, batcher)
rowSorter := helpers.BuildSorterOrFail(sortRows)
colSorter := helpers.BuildSorterOrFail(sortCols)

writer := termrenderers.NewHeatmap(multiterm.New(), numRows, numCols)

Expand All @@ -37,7 +41,7 @@ func heatmapFunction(c *cli.Context) error {
}

helpers.RunAggregationLoop(ext, counter, func() {
writer.WriteTable(counter)
writer.WriteTable(counter, rowSorter, colSorter)
writer.WriteFooter(0, helpers.FWriteExtractorSummary(ext, counter.ParseErrors(),
fmt.Sprintf("(R: %v; C: %v)", color.Wrapi(color.Yellow, counter.RowCount()), color.Wrapi(color.BrightBlue, counter.ColumnCount()))))
writer.WriteFooter(1, batcher.StatusString())
Expand Down Expand Up @@ -80,6 +84,16 @@ func heatmapCommand() *cli.Command {
Name: "max",
Usage: "Sets the upper bounds of the heatmap (default: auto)",
},
&cli.StringFlag{
Name: "sort-rows",
Usage: helpers.DefaultSortFlag.Usage,
Value: helpers.DefaultSortFlag.Value,
},
&cli.StringFlag{
Name: "sort-cols",
Usage: helpers.DefaultSortFlag.Usage,
Value: helpers.DefaultSortFlag.Value,
},
},
})
}
95 changes: 95 additions & 0 deletions cmd/helpers/sorting.go
@@ -0,0 +1,95 @@
package helpers

import (
"errors"
"fmt"
"rare/pkg/aggregation/sorting"
"rare/pkg/logger"
"rare/pkg/stringSplitter"
"strings"

"github.com/urfave/cli/v2"
)

var DefaultSortFlag = &cli.StringFlag{
Name: "sort",
Usage: "Sorting method for display (value, text, numeric, contextual, date)",
Value: "numeric",
}

// Create a sort flag with a different default value
func DefaultSortFlagWithDefault(dflt string) *cli.StringFlag {
if _, err := lookupSorter(dflt); err != nil {
panic(err)
}

flag := *DefaultSortFlag
flag.Value = dflt
return &flag
}

func BuildSorterOrFail(fullName string) sorting.NameValueSorter {
sorter, err := BuildSorter(fullName)
if err != nil {
logger.Fatal(err)
}
return sorter
}

func BuildSorter(fullName string) (sorting.NameValueSorter, error) {
name, reverse, err := parseSort(fullName)
if err != nil {
return nil, fmt.Errorf("error parsing sort: %v", err)
}

sorter, err := lookupSorter(name)
if err != nil {
return nil, fmt.Errorf("unknown sort: %s", name)
}
if reverse {
sorter = sorting.Reverse(sorter)
}
return sorter, nil
}

func parseSort(name string) (realname string, reverse bool, err error) {
splitter := stringSplitter.Splitter{
S: name,
Delim: ":",
}

realname = strings.ToLower(splitter.Next())
reverse = (realname == "value") // Value defaults descending

if modifier, hasModifier := splitter.NextOk(); hasModifier {
switch strings.ToLower(modifier) {
case "rev", "reverse":
reverse = !reverse
case "desc":
reverse = true
case "asc":
reverse = false
default:
return "", false, errors.New("invalid sort modifier")
}
}

return
}

func lookupSorter(name string) (sorting.NameValueSorter, error) {
name = strings.ToLower(name)
switch name {
case "text", "":
return sorting.ValueNilSorter(sorting.ByName), nil
case "numeric":
return sorting.ValueNilSorter(sorting.ByNameSmart), nil
case "contextual", "context":
return sorting.ValueNilSorter(sorting.ByContextual()), nil
case "date":
return sorting.ValueNilSorter(sorting.ByDateWithContextual()), nil
case "value":
return sorting.ValueSorterEx(sorting.ByName), nil
}
return nil, errors.New("unknown sort")
}
93 changes: 93 additions & 0 deletions cmd/helpers/sorting_test.go
@@ -0,0 +1,93 @@
package helpers

import (
"rare/pkg/aggregation/sorting"
"testing"

"github.com/stretchr/testify/assert"
)

func TestBuildSorter(t *testing.T) {
assert.NotNil(t, BuildSorterOrFail("text"))
assert.NotNil(t, BuildSorterOrFail("numeric"))
assert.NotNil(t, BuildSorterOrFail("contextual"))
assert.NotNil(t, BuildSorterOrFail("value"))
assert.NotNil(t, BuildSorterOrFail("value:reverse"))
}

func TestOrderResults(t *testing.T) {
assertSortEquals(t, "text", 1, 4, 2, 0, 3)
assertSortEquals(t, "text:asc", 1, 4, 2, 0, 3)
assertSortEquals(t, "text:reverse", 3, 0, 2, 4, 1)
assertSortEquals(t, "text:desc", 3, 0, 2, 4, 1)

assertSortEquals(t, "numeric", 1, 4, 2, 0, 3)
assertSortEquals(t, "numeric:asc", 1, 4, 2, 0, 3)
assertSortEquals(t, "numeric:reverse", 3, 0, 2, 4, 1)
assertSortEquals(t, "numeric:desc", 3, 0, 2, 4, 1)

assertSortEquals(t, "value", 3, 2, 1, 0, 4)
assertSortEquals(t, "value:desc", 3, 2, 1, 0, 4)
assertSortEquals(t, "value:reverse", 4, 0, 1, 2, 3)
assertSortEquals(t, "value:asc", 4, 0, 1, 2, 3)
}

func TestInvalidSortNames(t *testing.T) {
sorter, err := BuildSorter("bla")
assert.Nil(t, sorter)
assert.Error(t, err)

sorter, err = BuildSorter("numeric:bla")
assert.Nil(t, sorter)
assert.Error(t, err)
}

// Given a hardcoded set of values, and a sort name assert the order is as expected
func assertSortEquals(t *testing.T, sortName string, order ...int) {
sorter, err := BuildSorter(sortName)
assert.NoError(t, err)

type orderedPair struct {
sorting.NameValuePair
id int
}

vals := []orderedPair{
{sorting.NameValuePair{Name: "qef", Value: 5}, 0},
{sorting.NameValuePair{Name: "abc", Value: 12}, 1},
{sorting.NameValuePair{Name: "egf", Value: 52}, 2},
{sorting.NameValuePair{Name: "zac", Value: 52}, 3},
{sorting.NameValuePair{Name: "bbb", Value: 3}, 4},
}

if len(order) != len(vals) {
panic("bad test")
}

sorting.SortBy(vals, sorter, func(obj orderedPair) sorting.NameValuePair {
return obj.NameValuePair
})

for i := 0; i < len(vals); i++ {
assert.Equal(t, order[i], vals[i].id)
}

}

func TestDefaultSortResolves(t *testing.T) {
sortName, _, err := parseSort(DefaultSortFlag.Value)
assert.NoError(t, err)

sorter, sorterErr := lookupSorter(sortName)
assert.NoError(t, sorterErr)
assert.NotNil(t, sorter)
}

func TestBuildSortFlag(t *testing.T) {
flag := DefaultSortFlagWithDefault("contextual")
assert.Equal(t, "contextual", flag.Value)

assert.Panics(t, func() {
DefaultSortFlagWithDefault("fake")
})
}
36 changes: 12 additions & 24 deletions cmd/histo.go
Expand Up @@ -5,20 +5,16 @@ import (
"os"
"rare/cmd/helpers"
"rare/pkg/aggregation"
"rare/pkg/aggregation/sorting"
"rare/pkg/color"
"rare/pkg/multiterm"
"rare/pkg/multiterm/termrenderers"

"github.com/urfave/cli/v2"
)

func writeHistoOutput(writer *termrenderers.HistoWriter, counter *aggregation.MatchCounter, count int, reverse bool, sortByKey bool, atLeast int64) {
var items []aggregation.MatchPair
if sortByKey {
items = counter.ItemsSortedByKey(count, reverse)
} else {
items = counter.ItemsSorted(count, reverse)
}
func writeHistoOutput(writer *termrenderers.HistoWriter, counter *aggregation.MatchCounter, count int, sorter sorting.NameValueSorter, atLeast int64) {
items := counter.ItemsSortedBy(count, sorter)
line := 0
writer.UpdateSamples(counter.Count())
for _, match := range items {
Expand All @@ -32,12 +28,11 @@ func writeHistoOutput(writer *termrenderers.HistoWriter, counter *aggregation.Ma

func histoFunction(c *cli.Context) error {
var (
topItems = c.Int("n")
reverseSort = c.Bool("reverse")
sortByKey = c.Bool("sk")
atLeast = c.Int64("atleast")
extra = c.Bool("extra")
all = c.Bool("all")
topItems = c.Int("n")
atLeast = c.Int64("atleast")
extra = c.Bool("extra")
all = c.Bool("all")
sortName = c.String(helpers.DefaultSortFlag.Name)
)

counter := aggregation.NewCounter()
Expand All @@ -47,6 +42,7 @@ func histoFunction(c *cli.Context) error {

batcher := helpers.BuildBatcherFromArguments(c)
ext := helpers.BuildExtractorFromArguments(c, batcher)
sorter := helpers.BuildSorterOrFail(sortName)

progressString := func() string {
return helpers.FWriteExtractorSummary(ext,
Expand All @@ -55,7 +51,7 @@ func histoFunction(c *cli.Context) error {
}

helpers.RunAggregationLoop(ext, counter, func() {
writeHistoOutput(writer, counter, topItems, reverseSort, sortByKey, atLeast)
writeHistoOutput(writer, counter, topItems, sorter, atLeast)
writer.WriteFooter(0, progressString())
writer.WriteFooter(1, batcher.StatusString())
})
Expand All @@ -66,7 +62,7 @@ func histoFunction(c *cli.Context) error {
fmt.Println("Full Table:")
vterm := multiterm.NewVirtualTerm()
vWriter := termrenderers.NewHistogram(vterm, counter.GroupCount())
writeHistoOutput(vWriter, counter, counter.GroupCount(), reverseSort, sortByKey, atLeast)
writeHistoOutput(vWriter, counter, counter.GroupCount(), sorter, atLeast)

vterm.WriteToOutput(os.Stdout)
fmt.Println(progressString())
Expand Down Expand Up @@ -121,15 +117,7 @@ func histogramCommand() *cli.Command {
Usage: "Only show results if there are at least this many samples",
Value: 0,
},
&cli.BoolFlag{
Name: "reverse",
Usage: "Reverses the display sort-order",
},
&cli.BoolFlag{
Name: "sortkey",
Aliases: []string{"sk"},
Usage: "Sort by key, rather than value",
},
helpers.DefaultSortFlagWithDefault("value"),
},
})
}

0 comments on commit f7b4e4e

Please sign in to comment.