diff --git a/benchutil/list_schema.go b/benchutil/list_schema.go new file mode 100644 index 00000000..196a7f5c --- /dev/null +++ b/benchutil/list_schema.go @@ -0,0 +1,110 @@ +package benchutil + +import ( + "fmt" + + "github.com/graphql-go/graphql" +) + +type color struct { + Hex string + R int + G int + B int +} + +func ListSchemaWithXItems(x int) graphql.Schema { + + list := generateXListItems(x) + + color := graphql.NewObject(graphql.ObjectConfig{ + Name: "Color", + Description: "A color", + Fields: graphql.Fields{ + "hex": &graphql.Field{ + Type: graphql.NewNonNull(graphql.String), + Description: "Hex color code.", + Resolve: func(p graphql.ResolveParams) (interface{}, error) { + if c, ok := p.Source.(color); ok { + return c.Hex, nil + } + return nil, nil + }, + }, + "r": &graphql.Field{ + Type: graphql.NewNonNull(graphql.Int), + Description: "Red value.", + Resolve: func(p graphql.ResolveParams) (interface{}, error) { + if c, ok := p.Source.(color); ok { + return c.R, nil + } + return nil, nil + }, + }, + "g": &graphql.Field{ + Type: graphql.NewNonNull(graphql.Int), + Description: "Green value.", + Resolve: func(p graphql.ResolveParams) (interface{}, error) { + if c, ok := p.Source.(color); ok { + return c.G, nil + } + return nil, nil + }, + }, + "b": &graphql.Field{ + Type: graphql.NewNonNull(graphql.Int), + Description: "Blue value.", + Resolve: func(p graphql.ResolveParams) (interface{}, error) { + if c, ok := p.Source.(color); ok { + return c.B, nil + } + return nil, nil + }, + }, + }, + }) + + queryType := graphql.NewObject(graphql.ObjectConfig{ + Name: "Query", + Fields: graphql.Fields{ + "colors": { + Type: graphql.NewList(color), + Resolve: func(p graphql.ResolveParams) (interface{}, error) { + return list, nil + }, + }, + }, + }) + + colorSchema, _ := graphql.NewSchema(graphql.SchemaConfig{ + Query: queryType, + }) + + return colorSchema +} + +var colors []color + +func init() { + colors = make([]color, 0, 256*16*16) + + for r := 0; r < 256; r++ { + for g := 0; g < 16; g++ { + for b := 0; b < 16; b++ { + colors = append(colors, color{ + Hex: fmt.Sprintf("#%x%x%x", r, g, b), + R: r, + G: g, + B: b, + }) + } + } + } +} + +func generateXListItems(x int) []color { + if x > len(colors) { + x = len(colors) + } + return colors[0:x] +} diff --git a/benchutil/wide_schema.go b/benchutil/wide_schema.go new file mode 100644 index 00000000..2c7c43a2 --- /dev/null +++ b/benchutil/wide_schema.go @@ -0,0 +1,144 @@ +package benchutil + +import ( + "fmt" + + "github.com/graphql-go/graphql" +) + +func WideSchemaWithXFieldsAndYItems(x int, y int) graphql.Schema { + wide := graphql.NewObject(graphql.ObjectConfig{ + Name: "Wide", + Description: "An object", + Fields: generateXWideFields(x), + }) + + queryType := graphql.NewObject(graphql.ObjectConfig{ + Name: "Query", + Fields: graphql.Fields{ + "wide": { + Type: graphql.NewList(wide), + Resolve: func(p graphql.ResolveParams) (interface{}, error) { + out := make([]struct{}, 0, y) + for i := 0; i < y; i++ { + out = append(out, struct{}{}) + } + return out, nil + }, + }, + }, + }) + + wideSchema, _ := graphql.NewSchema(graphql.SchemaConfig{ + Query: queryType, + }) + + return wideSchema +} + +func generateXWideFields(x int) graphql.Fields { + fields := graphql.Fields{} + for i := 0; i < x; i++ { + fields[generateFieldNameFromX(i)] = generateWideFieldFromX(i) + } + return fields +} + +func generateWideFieldFromX(x int) *graphql.Field { + return &graphql.Field{ + Type: generateWideTypeFromX(x), + Resolve: generateWideResolveFromX(x), + } +} + +func generateWideTypeFromX(x int) graphql.Type { + switch x % 8 { + case 0: + return graphql.String + case 1: + return graphql.NewNonNull(graphql.String) + case 2: + return graphql.Int + case 3: + return graphql.NewNonNull(graphql.Int) + case 4: + return graphql.Float + case 5: + return graphql.NewNonNull(graphql.Float) + case 6: + return graphql.Boolean + case 7: + return graphql.NewNonNull(graphql.Boolean) + } + + return nil +} + +func generateFieldNameFromX(x int) string { + var out string + alphabet := []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "z"} + v := x + for { + r := v % 10 + out = alphabet[r] + out + v = v / 10 + if v == 0 { + break + } + } + return out +} + +func generateWideResolveFromX(x int) func(p graphql.ResolveParams) (interface{}, error) { + switch x % 8 { + case 0: + return func(p graphql.ResolveParams) (interface{}, error) { + return fmt.Sprint(x), nil + } + case 1: + return func(p graphql.ResolveParams) (interface{}, error) { + return fmt.Sprint(x), nil + } + case 2: + return func(p graphql.ResolveParams) (interface{}, error) { + return x, nil + } + case 3: + return func(p graphql.ResolveParams) (interface{}, error) { + return x, nil + } + case 4: + return func(p graphql.ResolveParams) (interface{}, error) { + return float64(x), nil + } + case 5: + return func(p graphql.ResolveParams) (interface{}, error) { + return float64(x), nil + } + case 6: + return func(p graphql.ResolveParams) (interface{}, error) { + if x%2 == 0 { + return false, nil + } + return true, nil + } + case 7: + return func(p graphql.ResolveParams) (interface{}, error) { + if x%2 == 0 { + return false, nil + } + return true, nil + } + } + + return nil +} + +func WideSchemaQuery(x int) string { + var fields string + for i := 0; i < x; i++ { + fields = fields + generateFieldNameFromX(i) + " " + } + + return fmt.Sprintf("query { wide { %s} }", fields) +} diff --git a/graphql_bench_test.go b/graphql_bench_test.go new file mode 100644 index 00000000..5b135192 --- /dev/null +++ b/graphql_bench_test.go @@ -0,0 +1,124 @@ +package graphql_test + +import ( + "testing" + + "github.com/graphql-go/graphql" + "github.com/graphql-go/graphql/benchutil" +) + +type B struct { + Query string + Schema graphql.Schema +} + +func benchGraphql(bench B, p graphql.Params, t testing.TB) { + result := graphql.Do(p) + if len(result.Errors) > 0 { + t.Fatalf("wrong result, unexpected errors: %v", result.Errors) + } +} + +// Benchmark a reasonably large list of small items. +func BenchmarkListQuery_1(b *testing.B) { + nItemsListQueryBenchmark(1)(b) +} + +func BenchmarkListQuery_100(b *testing.B) { + nItemsListQueryBenchmark(100)(b) +} + +func BenchmarkListQuery_1K(b *testing.B) { + nItemsListQueryBenchmark(1000)(b) +} + +func BenchmarkListQuery_10K(b *testing.B) { + nItemsListQueryBenchmark(10 * 1000)(b) +} + +func BenchmarkListQuery_100K(b *testing.B) { + nItemsListQueryBenchmark(100 * 1000)(b) +} + +func nItemsListQueryBenchmark(x int) func(b *testing.B) { + return func(b *testing.B) { + schema := benchutil.ListSchemaWithXItems(x) + + bench := B{ + Query: ` + query { + colors { + hex + r + g + b + } + } + `, + Schema: schema, + } + + for i := 0; i < b.N; i++ { + + params := graphql.Params{ + Schema: schema, + RequestString: bench.Query, + } + benchGraphql(bench, params, b) + } + } +} + +func BenchmarkWideQuery_1_1(b *testing.B) { + nFieldsyItemsQueryBenchmark(1, 1)(b) +} + +func BenchmarkWideQuery_10_1(b *testing.B) { + nFieldsyItemsQueryBenchmark(10, 1)(b) +} + +func BenchmarkWideQuery_100_1(b *testing.B) { + nFieldsyItemsQueryBenchmark(100, 1)(b) +} + +func BenchmarkWideQuery_1K_1(b *testing.B) { + nFieldsyItemsQueryBenchmark(1000, 1)(b) +} + +func BenchmarkWideQuery_1_10(b *testing.B) { + nFieldsyItemsQueryBenchmark(1, 10)(b) +} + +func BenchmarkWideQuery_10_10(b *testing.B) { + nFieldsyItemsQueryBenchmark(10, 10)(b) +} + +func BenchmarkWideQuery_100_10(b *testing.B) { + nFieldsyItemsQueryBenchmark(100, 10)(b) +} + +func BenchmarkWideQuery_1K_10(b *testing.B) { + nFieldsyItemsQueryBenchmark(1000, 10)(b) +} + +func nFieldsyItemsQueryBenchmark(x int, y int) func(b *testing.B) { + return func(b *testing.B) { + schema := benchutil.WideSchemaWithXFieldsAndYItems(x, y) + query := benchutil.WideSchemaQuery(x) + + bench := B{ + Query: query, + Schema: schema, + } + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + params := graphql.Params{ + Schema: schema, + RequestString: bench.Query, + } + benchGraphql(bench, params, b) + } + } +} diff --git a/testutil/testutil.go b/testutil/testutil.go index a57992aa..ff49f6b0 100644 --- a/testutil/testutil.go +++ b/testutil/testutil.go @@ -302,7 +302,7 @@ func init() { Resolve: func(p graphql.ResolveParams) (interface{}, error) { id, err := strconv.Atoi(p.Args["id"].(string)) if err != nil { - return nil, err + return nil, err } return GetHuman(id), nil },