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)) }