Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

api: implement succinct output for SearchOptions.String #719

Merged
merged 2 commits into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 65 additions & 1 deletion api.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"fmt"
"reflect"
"strconv"
"strings"
"time"

"github.com/sourcegraph/zoekt/query"
Expand Down Expand Up @@ -945,8 +946,71 @@ type SearchOptions struct {
SpanContext map[string]string
}

// String returns a succinct representation of the options. This is meant for
// human consumption in logs and traces.
//
// Note: some tracing systems have limits on length of values, so we take care
// to try and make this small, and include the important information near the
// front incase of truncation.
func (s *SearchOptions) String() string {
return fmt.Sprintf("%#v", s)
var b strings.Builder

add := func(name, value string) {
b.WriteString(name)
b.WriteByte('=')
b.WriteString(value)
b.WriteByte(' ')
}
addInt := func(name string, value int) {
if value != 0 {
add(name, strconv.Itoa(value))
}
}
addDuration := func(name string, value time.Duration) {
if value != 0 {
add(name, value.String())
}
}
addBool := func(name string, value bool) {
if !value {
return
}
b.WriteString(name)
b.WriteByte(' ')
}

b.WriteString("zoekt.SearchOptions{ ")

addInt("ShardMaxMatchCount", s.ShardMaxMatchCount)
addInt("TotalMaxMatchCount", s.TotalMaxMatchCount)
addInt("ShardRepoMaxMatchCount", s.ShardRepoMaxMatchCount)
addInt("ShardMaxImportantMatch", s.ShardMaxImportantMatch)
addInt("TotalMaxImportantMatch", s.TotalMaxImportantMatch)
addInt("MaxDocDisplayCount", s.MaxDocDisplayCount)
addInt("MaxMatchDisplayCount", s.MaxMatchDisplayCount)
addInt("NumContextLines", s.NumContextLines)

addDuration("MaxWallTime", s.MaxWallTime)
addDuration("FlushWallTime", s.FlushWallTime)

if s.DocumentRanksWeight > 0 {
add("DocumentRanksWeight", strconv.FormatFloat(s.DocumentRanksWeight, 'g', -1, 64))
}

addBool("EstimateDocCount", s.EstimateDocCount)
addBool("Whole", s.Whole)
addBool("ChunkMatches", s.ChunkMatches)
addBool("UseDocumentRanks", s.UseDocumentRanks)
addBool("UseKeywordScoring", s.UseKeywordScoring)
addBool("Trace", s.Trace)
addBool("DebugScore", s.DebugScore)

for k, v := range s.SpanContext {
add("SpanContext."+k, strconv.Quote(v))
}

b.WriteByte('}')
return b.String()
}

// Sender is the interface that wraps the basic Send method.
Expand Down
76 changes: 74 additions & 2 deletions api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@ import (
"strings"
"testing"
"time"

"github.com/grafana/regexp"
)

/*
BenchmarkMinimalRepoListEncodings/slice-8 570 2145665 ns/op 753790 bytes 3981 B/op 0 allocs/op
BenchmarkMinimalRepoListEncodings/map-8 360 3337522 ns/op 740778 bytes 377777 B/op 13002 allocs/op
BenchmarkMinimalRepoListEncodings/slice-8 570 2145665 ns/op 753790 bytes 3981 B/op 0 allocs/op
BenchmarkMinimalRepoListEncodings/map-8 360 3337522 ns/op 740778 bytes 377777 B/op 13002 allocs/op
*/
func BenchmarkMinimalRepoListEncodings(b *testing.B) {
size := uint32(13000) // 2021-06-24 rough estimate of number of repos on a replica.
Expand Down Expand Up @@ -165,3 +167,73 @@ tool fieldalignment then update this test.`, c.v, c.size, got)
}
}
}

func TestSearchOptions_String(t *testing.T) {
// To make sure we don't forget to update the string implementation we use
// reflection to generate a SearchOptions with every field being non
// default. We then check that the field name is present in the output.
opts := SearchOptions{}
var fieldNames []string
rv := reflect.ValueOf(&opts).Elem()
for i := 0; i < rv.NumField(); i++ {
f := rv.Field(i)
name := rv.Type().Field(i).Name
fieldNames = append(fieldNames, name)
switch f.Kind() {
case reflect.Bool:
f.SetBool(true)
case reflect.Int:
f.SetInt(1)
case reflect.Int64:
f.SetInt(1)
case reflect.Float64:
f.SetFloat(1)
case reflect.Map:
// Only map is SpanContext
f.Set(reflect.ValueOf(map[string]string{"key": "value"}))
default:
t.Fatalf("add support for %s field (%s)", f.Kind(), name)
}
}

s := opts.String()
for _, name := range fieldNames {
found, err := regexp.MatchString("\\b"+regexp.QuoteMeta(name)+"\\b", s)
if err != nil {
t.Fatal(err)
}
if !found {
t.Errorf("could not find field %q in string output of SearchOptions:\n%s", name, s)
}
}

webDefaults := SearchOptions{
MaxWallTime: 10 * time.Second,
}
webDefaults.SetDefaults()

// Now we hand craft a few corner and common cases
cases := []struct {
Opts SearchOptions
Want string
}{{
// Empty
Opts: SearchOptions{},
Want: "zoekt.SearchOptions{ }",
}, {
// healthz options
Opts: SearchOptions{ShardMaxMatchCount: 1, TotalMaxMatchCount: 1, MaxDocDisplayCount: 1},
Want: "zoekt.SearchOptions{ ShardMaxMatchCount=1 TotalMaxMatchCount=1 MaxDocDisplayCount=1 }",
}, {
// zoekt-webserver defaults
Opts: webDefaults,
Want: "zoekt.SearchOptions{ ShardMaxMatchCount=100000 TotalMaxMatchCount=1000000 MaxWallTime=10s }",
}}

for _, tc := range cases {
got := tc.Opts.String()
if got != tc.Want {
t.Errorf("unexpected String for %#v:\ngot: %s\nwant: %s", tc.Opts, got, tc.Want)
}
}
}
Loading