fork of https://github.com/sourcegraph/zoekt
0

Configure Feed

Select the types of activity you want to include in your feed.

api: implement succinct output for SearchOptions.String (#719)

I am often reading the output of String in traces and logs, and it is
really hard to parse since there are many fields and most are unset.
This is a quality of life improvement so it is much easier to scan the
output.

For example the default zoekt-webserver struct's string output goes from
a 456 byte string to

zoekt.SearchOptions{ ShardMaxMatchCount=100000 TotalMaxMatchCount=1000000 MaxWallTime=10s }

Test Plan: go test. The unit tests ensure I cover every field now and in
the future when fields are added.

+139 -3
+65 -1
api.go
··· 21 21 "fmt" 22 22 "reflect" 23 23 "strconv" 24 + "strings" 24 25 "time" 25 26 26 27 "github.com/sourcegraph/zoekt/query" ··· 945 946 SpanContext map[string]string 946 947 } 947 948 949 + // String returns a succinct representation of the options. This is meant for 950 + // human consumption in logs and traces. 951 + // 952 + // Note: some tracing systems have limits on length of values, so we take care 953 + // to try and make this small, and include the important information near the 954 + // front incase of truncation. 948 955 func (s *SearchOptions) String() string { 949 - return fmt.Sprintf("%#v", s) 956 + var b strings.Builder 957 + 958 + add := func(name, value string) { 959 + b.WriteString(name) 960 + b.WriteByte('=') 961 + b.WriteString(value) 962 + b.WriteByte(' ') 963 + } 964 + addInt := func(name string, value int) { 965 + if value != 0 { 966 + add(name, strconv.Itoa(value)) 967 + } 968 + } 969 + addDuration := func(name string, value time.Duration) { 970 + if value != 0 { 971 + add(name, value.String()) 972 + } 973 + } 974 + addBool := func(name string, value bool) { 975 + if !value { 976 + return 977 + } 978 + b.WriteString(name) 979 + b.WriteByte(' ') 980 + } 981 + 982 + b.WriteString("zoekt.SearchOptions{ ") 983 + 984 + addInt("ShardMaxMatchCount", s.ShardMaxMatchCount) 985 + addInt("TotalMaxMatchCount", s.TotalMaxMatchCount) 986 + addInt("ShardRepoMaxMatchCount", s.ShardRepoMaxMatchCount) 987 + addInt("ShardMaxImportantMatch", s.ShardMaxImportantMatch) 988 + addInt("TotalMaxImportantMatch", s.TotalMaxImportantMatch) 989 + addInt("MaxDocDisplayCount", s.MaxDocDisplayCount) 990 + addInt("MaxMatchDisplayCount", s.MaxMatchDisplayCount) 991 + addInt("NumContextLines", s.NumContextLines) 992 + 993 + addDuration("MaxWallTime", s.MaxWallTime) 994 + addDuration("FlushWallTime", s.FlushWallTime) 995 + 996 + if s.DocumentRanksWeight > 0 { 997 + add("DocumentRanksWeight", strconv.FormatFloat(s.DocumentRanksWeight, 'g', -1, 64)) 998 + } 999 + 1000 + addBool("EstimateDocCount", s.EstimateDocCount) 1001 + addBool("Whole", s.Whole) 1002 + addBool("ChunkMatches", s.ChunkMatches) 1003 + addBool("UseDocumentRanks", s.UseDocumentRanks) 1004 + addBool("UseKeywordScoring", s.UseKeywordScoring) 1005 + addBool("Trace", s.Trace) 1006 + addBool("DebugScore", s.DebugScore) 1007 + 1008 + for k, v := range s.SpanContext { 1009 + add("SpanContext."+k, strconv.Quote(v)) 1010 + } 1011 + 1012 + b.WriteByte('}') 1013 + return b.String() 950 1014 } 951 1015 952 1016 // Sender is the interface that wraps the basic Send method.
+74 -2
api_test.go
··· 21 21 "strings" 22 22 "testing" 23 23 "time" 24 + 25 + "github.com/grafana/regexp" 24 26 ) 25 27 26 28 /* 27 - BenchmarkMinimalRepoListEncodings/slice-8 570 2145665 ns/op 753790 bytes 3981 B/op 0 allocs/op 28 - BenchmarkMinimalRepoListEncodings/map-8 360 3337522 ns/op 740778 bytes 377777 B/op 13002 allocs/op 29 + BenchmarkMinimalRepoListEncodings/slice-8 570 2145665 ns/op 753790 bytes 3981 B/op 0 allocs/op 30 + BenchmarkMinimalRepoListEncodings/map-8 360 3337522 ns/op 740778 bytes 377777 B/op 13002 allocs/op 29 31 */ 30 32 func BenchmarkMinimalRepoListEncodings(b *testing.B) { 31 33 size := uint32(13000) // 2021-06-24 rough estimate of number of repos on a replica. ··· 165 167 } 166 168 } 167 169 } 170 + 171 + func TestSearchOptions_String(t *testing.T) { 172 + // To make sure we don't forget to update the string implementation we use 173 + // reflection to generate a SearchOptions with every field being non 174 + // default. We then check that the field name is present in the output. 175 + opts := SearchOptions{} 176 + var fieldNames []string 177 + rv := reflect.ValueOf(&opts).Elem() 178 + for i := 0; i < rv.NumField(); i++ { 179 + f := rv.Field(i) 180 + name := rv.Type().Field(i).Name 181 + fieldNames = append(fieldNames, name) 182 + switch f.Kind() { 183 + case reflect.Bool: 184 + f.SetBool(true) 185 + case reflect.Int: 186 + f.SetInt(1) 187 + case reflect.Int64: 188 + f.SetInt(1) 189 + case reflect.Float64: 190 + f.SetFloat(1) 191 + case reflect.Map: 192 + // Only map is SpanContext 193 + f.Set(reflect.ValueOf(map[string]string{"key": "value"})) 194 + default: 195 + t.Fatalf("add support for %s field (%s)", f.Kind(), name) 196 + } 197 + } 198 + 199 + s := opts.String() 200 + for _, name := range fieldNames { 201 + found, err := regexp.MatchString("\\b"+regexp.QuoteMeta(name)+"\\b", s) 202 + if err != nil { 203 + t.Fatal(err) 204 + } 205 + if !found { 206 + t.Errorf("could not find field %q in string output of SearchOptions:\n%s", name, s) 207 + } 208 + } 209 + 210 + webDefaults := SearchOptions{ 211 + MaxWallTime: 10 * time.Second, 212 + } 213 + webDefaults.SetDefaults() 214 + 215 + // Now we hand craft a few corner and common cases 216 + cases := []struct { 217 + Opts SearchOptions 218 + Want string 219 + }{{ 220 + // Empty 221 + Opts: SearchOptions{}, 222 + Want: "zoekt.SearchOptions{ }", 223 + }, { 224 + // healthz options 225 + Opts: SearchOptions{ShardMaxMatchCount: 1, TotalMaxMatchCount: 1, MaxDocDisplayCount: 1}, 226 + Want: "zoekt.SearchOptions{ ShardMaxMatchCount=1 TotalMaxMatchCount=1 MaxDocDisplayCount=1 }", 227 + }, { 228 + // zoekt-webserver defaults 229 + Opts: webDefaults, 230 + Want: "zoekt.SearchOptions{ ShardMaxMatchCount=100000 TotalMaxMatchCount=1000000 MaxWallTime=10s }", 231 + }} 232 + 233 + for _, tc := range cases { 234 + got := tc.Opts.String() 235 + if got != tc.Want { 236 + t.Errorf("unexpected String for %#v:\ngot: %s\nwant: %s", tc.Opts, got, tc.Want) 237 + } 238 + } 239 + }