| // tracetool is a command-line tool for interrogating a tracedb server. |
| package main |
| |
| import ( |
| "context" |
| "crypto/md5" |
| "flag" |
| "fmt" |
| "math/rand" |
| "os" |
| "regexp" |
| "sort" |
| "strings" |
| "time" |
| |
| "go.skia.org/infra/go/common" |
| "go.skia.org/infra/go/human" |
| "go.skia.org/infra/go/sklog" |
| "go.skia.org/infra/go/trace/service" |
| "go.skia.org/infra/go/util" |
| "google.golang.org/grpc" |
| ) |
| |
| // flags |
| var ( |
| address = flag.String("address", "localhost:9090", "The address of the traceservice gRPC endpoint.") |
| begin = flag.String("begin", "1w", "Select the commit ids for the range beginning this long ago.") |
| end = flag.String("end", "0s", "Select the commit ids for the range ending this long ago.") |
| id = flag.String("id", "", "Selects the CommitID with an ID that begins with id.") |
| regex = flag.String("regex", "", "A regular expression to match against traceids.") |
| verbose = flag.Bool("verbose", false, "Verbose output.") |
| only = flag.Bool("only", false, "If true then only print values, otherwise print keys and values.") |
| showMD5 = flag.Bool("md5", false, "If true then include the MD5 hash value for each commit id to compare across databases. Warning: Slow !") |
| ) |
| |
| var Usage = func() { |
| fmt.Printf(`Usage: tracetool <command> [OPTIONS]... |
| Inspect and interrogate a running tracedb server. |
| |
| Commands: |
| |
| ls List all the commit ids for the given time range. |
| |
| Flags: --begin --end --md5 |
| |
| count Return the number of samples stored for all commits in the given time range. |
| |
| Flags: --begin --end |
| |
| ping Call the Ping service method every 1s. |
| |
| sample Get a sampling of values for the given ID. |
| Flags: --begin --end --id --regex --only |
| |
| The first commitid with an ID that begins with the value of --id |
| will be loaded and a sampling of 10 values will be displayed. |
| |
| param_grep Find parameter values that match a regular expression. |
| Flags: --begin --end --regex |
| |
| It loads all commits in the defined range and matches the |
| parameter values of each trace in those commits against the |
| regular expression. For each commit it outputs the paramaters |
| and their values that match the regex. |
| |
| value_grep Find commits where at least one value matches a regular expression. |
| Flags: --begin --end --regex --verbose |
| |
| It loads all commits in the defined range and matches the |
| values of each trace in those commits against the |
| regular expression. For each commit it outputs the values of the |
| "name" parameter across traces. |
| This only makes sense for Gold data since the digests are stored |
| strings. |
| |
| Examples: |
| |
| To list all the commits for the first 6 days of the previous week: |
| |
| tracetool ls -begin 1w -end 1d |
| |
| To count all the values for every commit id in the last day: |
| |
| tracetool -begin 1d |
| |
| Flags: |
| |
| `) |
| flag.PrintDefaults() |
| } |
| |
| func _list(client traceservice.TraceServiceClient) (*traceservice.ListResponse, error) { |
| req := &traceservice.ListRequest{} |
| now := time.Now() |
| b, err := human.ParseDuration(*begin) |
| if err != nil { |
| return nil, fmt.Errorf("Invalid begin value: %s", err) |
| } |
| e, err := human.ParseDuration(*end) |
| if err != nil { |
| return nil, fmt.Errorf("Invalid begin value: %s", err) |
| } |
| |
| req.Begin = now.Add(-b).Unix() |
| req.End = now.Add(-e).Unix() |
| if *verbose { |
| fmt.Printf("Requesting from %s to %s\n", now.Add(-b), now.Add(-e)) |
| } |
| return client.List(context.Background(), req) |
| } |
| |
| func count(client traceservice.TraceServiceClient) { |
| listResp, err := _list(client) |
| if err != nil { |
| fmt.Printf("Failed to retrieve the list: %s\n", err) |
| return |
| } |
| for _, cid := range listResp.Commitids { |
| if *id != "" && !strings.HasPrefix(cid.Id, *id) { |
| continue |
| } |
| req := &traceservice.GetValuesRequest{ |
| Commitid: cid, |
| } |
| resp, err := client.GetValues(context.Background(), req) |
| if err != nil { |
| fmt.Printf("Failed to retrieve values: %s", err) |
| return |
| } |
| fmt.Printf("%s %s %s: Count %d\n", cid.Id, cid.Source, time.Unix(cid.Timestamp, 0), len(resp.Values)) |
| } |
| } |
| |
| func list(client traceservice.TraceServiceClient) { |
| resp, err := _list(client) |
| if err != nil { |
| sklog.Fatalf("Failed to retrieve the list: %s\n", err) |
| } |
| |
| for _, cid := range resp.Commitids { |
| if *id != "" && !strings.HasPrefix(cid.Id, *id) { |
| continue |
| } |
| fmt.Printf("%s %s %s", cid.Id, cid.Source, time.Unix(cid.Timestamp, 0).UTC()) |
| if *showMD5 { |
| fmt.Printf(" %s", calcMD5FromValues(client, cid)) |
| } |
| fmt.Printf("\n") |
| } |
| } |
| |
| // calcMD5FromValues loads the values for the given commit ID and sorts the bytes of the |
| // values in lexicographical order and calculates the hash. This can be used to compare |
| // commit values across databases. |
| func calcMD5FromValues(client traceservice.TraceServiceClient, cid *traceservice.CommitID) string { |
| req := &traceservice.GetValuesRequest{Commitid: cid} |
| resp, err := client.GetValues(context.Background(), req) |
| if err != nil { |
| sklog.Fatalf("Failed to retrieve value: %s", err) |
| } |
| |
| sortedStr := make([]string, 0, len(resp.Values)) |
| for _, val := range resp.Values { |
| sortedStr = append(sortedStr, string(val.Value)) |
| } |
| sort.Strings(sortedStr) |
| |
| ret := md5.New() |
| for _, val := range sortedStr { |
| _, err := ret.Write([]byte(val)) |
| if err != nil { |
| sklog.Fatalf("Error writint to MD5: %s", err) |
| } |
| } |
| return fmt.Sprintf("%x", ret.Sum(nil)) |
| } |
| |
| func _pingStep(ctx context.Context, req *traceservice.Empty, client traceservice.TraceServiceClient) { |
| begin := time.Now() |
| _, err := client.Ping(ctx, req) |
| end := time.Now() |
| if err != nil { |
| fmt.Printf("Failure: %s\n", err) |
| } else { |
| fmt.Printf("Success: time=%s\n", end.Sub(begin)) |
| } |
| } |
| |
| func ping(client traceservice.TraceServiceClient) { |
| ctx := context.Background() |
| req := &traceservice.Empty{} |
| _pingStep(ctx, req, client) |
| for range time.Tick(time.Second) { |
| _pingStep(ctx, req, client) |
| } |
| } |
| |
| // converter is a func that will convert the raw byte slice returned into the correct type. |
| type converter func([]byte) interface{} |
| |
| // goldConverter in an implementation of converter for digests (strings). |
| func goldConverter(b []byte) interface{} { |
| return string(b) |
| } |
| |
| func sample(client traceservice.TraceServiceClient) { |
| // Get all the CommitIDs in the given time range. |
| listResp, err := _list(client) |
| if err != nil { |
| fmt.Printf("Failed to retrieve the list: %s\n", err) |
| return |
| } |
| |
| for _, cid := range listResp.Commitids { |
| if *id != "" && !strings.HasPrefix(cid.Id, *id) { |
| continue |
| } |
| req := &traceservice.GetValuesRequest{ |
| Commitid: cid, |
| } |
| resp, err := client.GetValues(context.Background(), req) |
| if err != nil { |
| fmt.Printf("Failed to retrieve values: %s", err) |
| return |
| } |
| if *regex == "" { |
| // Dump a sample of at most 10 values along with their traceids. |
| N := 10 |
| if len(resp.Values) < N { |
| N = len(resp.Values) |
| } |
| for i := 0; i < N; i++ { |
| pair := resp.Values[rand.Intn(len(resp.Values))] |
| if *only { |
| fmt.Printf("%v\n", goldConverter(pair.Value)) |
| } else { |
| fmt.Printf("%110s - %v\n", pair.Key, goldConverter(pair.Value)) |
| } |
| } |
| } else { |
| r, err := regexp.Compile(*regex) |
| if err != nil { |
| fmt.Printf("Invalid value for regex %q: %s\n", *regex, err) |
| } |
| for _, pair := range resp.Values { |
| if r.MatchString(pair.Key) { |
| if *only { |
| fmt.Printf("%v\n", goldConverter(pair.Value)) |
| } else { |
| fmt.Printf("%110s - %v\n", pair.Key, goldConverter(pair.Value)) |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| func param_grep(client traceservice.TraceServiceClient) { |
| if *regex == "" { |
| sklog.Fatalf("No regex given for param_grep") |
| } |
| r, err := regexp.Compile(*regex) |
| if err != nil { |
| sklog.Fatalf("Invalid value for regex %q: %s\n", *regex, err) |
| } |
| |
| ctx := context.Background() |
| resp, err := _list(client) |
| if err != nil { |
| sklog.Fatalf("Failed to retrieve the list: %s\n", err) |
| } |
| |
| for _, cid := range resp.Commitids { |
| traceIdsResp, err := client.GetValues(ctx, &traceservice.GetValuesRequest{Commitid: cid}) |
| if err != nil { |
| sklog.Errorf("Could not get trace ids: %s", err) |
| continue |
| } |
| |
| traceIds := make([]string, 0, len(traceIdsResp.Values)) |
| for _, valuePair := range traceIdsResp.Values { |
| traceIds = append(traceIds, valuePair.Key) |
| } |
| |
| paramsResp, err := client.GetParams(ctx, &traceservice.GetParamsRequest{Traceids: traceIds}) |
| if err != nil { |
| sklog.Errorf("Unable to retrieve params for %s. Error: %s", cid, err) |
| continue |
| } |
| |
| result := make(map[string]bool, len(paramsResp.Params)) |
| for _, p := range paramsResp.Params { |
| for key, val := range p.Params { |
| if r.MatchString(val) { |
| result[fmt.Sprintf("%s = %s", key, val)] = true |
| } |
| } |
| } |
| |
| fmt.Printf("%.3d %s %s %s \n", len(result), cid.Id, cid.Source, time.Unix(cid.Timestamp, 0)) |
| for p := range result { |
| fmt.Printf(" %s\n", p) |
| } |
| } |
| } |
| |
| func value_grep(client traceservice.TraceServiceClient) { |
| if *regex == "" { |
| sklog.Fatalf("No regex given for param_grep") |
| } |
| r, err := regexp.Compile(*regex) |
| if err != nil { |
| sklog.Fatalf("Invalid value for regex %q: %s\n", *regex, err) |
| } |
| |
| ctx := context.Background() |
| resp, err := _list(client) |
| if err != nil { |
| sklog.Fatalf("Failed to retrieve the list: %s\n", err) |
| } |
| |
| for _, cid := range resp.Commitids { |
| traceIdsResp, err := client.GetValues(ctx, &traceservice.GetValuesRequest{Commitid: cid}) |
| if err != nil { |
| sklog.Errorf("Could not get trace ids: %s", err) |
| continue |
| } |
| |
| traceIds := make([]string, 0, len(traceIdsResp.Values)) |
| for _, valuePair := range traceIdsResp.Values { |
| if r.MatchString(string(valuePair.Value)) { |
| traceIds = append(traceIds, valuePair.Key) |
| } |
| } |
| |
| if len(traceIds) == 0 { |
| if *verbose { |
| fmt.Printf("NOT FOUND IN %s %s %s\n", cid.Id, cid.Source, time.Unix(cid.Timestamp, 0)) |
| } |
| continue |
| } |
| |
| paramsResp, err := client.GetParams(ctx, &traceservice.GetParamsRequest{Traceids: traceIds}) |
| if err != nil { |
| sklog.Errorf("Unable to retrieve params for %s. Error: %s", cid, err) |
| continue |
| } |
| |
| result := map[string]bool{} |
| for _, p := range paramsResp.Params { |
| if name, ok := p.Params["name"]; ok { |
| result[name] = true |
| } |
| } |
| |
| fmt.Printf("%.3d %s %s %s : ", len(result), cid.Id, cid.Source, time.Unix(cid.Timestamp, 0)) |
| for name := range result { |
| fmt.Print(name + " ") |
| } |
| fmt.Println() |
| } |
| } |
| |
| func main() { |
| rand.Seed(time.Now().Unix()) |
| // Grab the first argument off of os.Args, the command, before we call flag.Parse. |
| if len(os.Args) < 2 { |
| Usage() |
| return |
| } |
| cmd := os.Args[1] |
| os.Args = append([]string{os.Args[0]}, os.Args[2:]...) |
| |
| // Now parge the flags. |
| common.Init() |
| |
| // Set up a connection to the server. |
| conn, err := grpc.Dial(*address, grpc.WithInsecure()) |
| if err != nil { |
| sklog.Fatalf("did not connect: %v", err) |
| } |
| defer util.Close(conn) |
| client := traceservice.NewTraceServiceClient(conn) |
| |
| switch cmd { |
| case "ls": |
| list(client) |
| case "ping": |
| ping(client) |
| case "count": |
| count(client) |
| case "sample": |
| sample(client) |
| case "param_grep": |
| param_grep(client) |
| case "value_grep": |
| value_grep(client) |
| default: |
| fmt.Printf("Unknown command: %s\n", cmd) |
| Usage() |
| } |
| } |