123 lines
2.6 KiB
Go
123 lines
2.6 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"sync"
|
|
"time"
|
|
|
|
pb "git.ipng.ch/ipng/nginx-logtail/proto/logtailpb"
|
|
)
|
|
|
|
type topNResult struct {
|
|
target string
|
|
resp *pb.TopNResponse
|
|
err error
|
|
}
|
|
|
|
func runTopN(args []string) {
|
|
fs := flag.NewFlagSet("topn", flag.ExitOnError)
|
|
sf, targetFlag := bindShared(fs)
|
|
n := fs.Int("n", 10, "number of entries")
|
|
window := fs.String("window", "5m", "time window: 1m 5m 15m 60m 6h 24h")
|
|
groupBy := fs.String("group-by", "website", "group by: website prefix uri status")
|
|
fs.Parse(args)
|
|
sf.resolve(*targetFlag)
|
|
|
|
win := parseWindow(*window)
|
|
grp := parseGroupBy(*groupBy)
|
|
filter := buildFilter(sf)
|
|
|
|
results := fanOutTopN(sf.targets, filter, grp, *n, win)
|
|
|
|
for _, r := range results {
|
|
if hdr := targetHeader(r.target, r.resp.GetSource(), len(sf.targets)); hdr != "" {
|
|
fmt.Println(hdr)
|
|
}
|
|
if r.err != nil {
|
|
fmt.Fprintf(os.Stderr, "error from %s: %v\n", r.target, r.err)
|
|
continue
|
|
}
|
|
if sf.jsonOut {
|
|
printTopNJSON(r)
|
|
} else {
|
|
printTopNTable(r)
|
|
}
|
|
if len(sf.targets) > 1 {
|
|
fmt.Println()
|
|
}
|
|
}
|
|
}
|
|
|
|
func fanOutTopN(targets []string, filter *pb.Filter, groupBy pb.GroupBy, n int, window pb.Window) []topNResult {
|
|
results := make([]topNResult, len(targets))
|
|
var wg sync.WaitGroup
|
|
for i, t := range targets {
|
|
wg.Add(1)
|
|
go func(i int, addr string) {
|
|
defer wg.Done()
|
|
results[i].target = addr
|
|
conn, client, err := dial(addr)
|
|
if err != nil {
|
|
results[i].err = err
|
|
results[i].resp = &pb.TopNResponse{}
|
|
return
|
|
}
|
|
defer conn.Close()
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
resp, err := client.TopN(ctx, &pb.TopNRequest{
|
|
Filter: filter,
|
|
GroupBy: groupBy,
|
|
N: int32(n),
|
|
Window: window,
|
|
})
|
|
results[i].resp = resp
|
|
results[i].err = err
|
|
}(i, t)
|
|
}
|
|
wg.Wait()
|
|
return results
|
|
}
|
|
|
|
func printTopNTable(r topNResult) {
|
|
if len(r.resp.Entries) == 0 {
|
|
fmt.Println("(no data)")
|
|
return
|
|
}
|
|
rows := [][]string{{"RANK", "COUNT", "LABEL"}}
|
|
for i, e := range r.resp.Entries {
|
|
rows = append(rows, []string{
|
|
fmt.Sprintf("%4d", i+1),
|
|
fmtCount(e.Count),
|
|
e.Label,
|
|
})
|
|
}
|
|
printTable(os.Stdout, rows)
|
|
}
|
|
|
|
func printTopNJSON(r topNResult) {
|
|
type entry struct {
|
|
Label string `json:"label"`
|
|
Count int64 `json:"count"`
|
|
}
|
|
type out struct {
|
|
Source string `json:"source"`
|
|
Target string `json:"target"`
|
|
Entries []entry `json:"entries"`
|
|
}
|
|
o := out{
|
|
Source: r.resp.Source,
|
|
Target: r.target,
|
|
Entries: make([]entry, len(r.resp.Entries)),
|
|
}
|
|
for i, e := range r.resp.Entries {
|
|
o.Entries[i] = entry{Label: e.Label, Count: e.Count}
|
|
}
|
|
b, _ := json.Marshal(o)
|
|
fmt.Println(string(b))
|
|
}
|