Skip to content

Commit

Permalink
feat: add httptrace info to loadstats (wundergraph#681)
Browse files Browse the repository at this point in the history
  • Loading branch information
jensneuse authored and pvormste committed Feb 19, 2024
1 parent 24472d4 commit 7755d67
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 3 deletions.
2 changes: 2 additions & 0 deletions v2/pkg/engine/datasource/httpclient/nethttpclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ type TraceHTTPResponse struct {
StatusCode int `json:"status_code"`
Status string `json:"status"`
Headers http.Header `json:"headers"`
BodySize int `json:"body_size"`
}

func Do(client *http.Client, ctx context.Context, requestInput []byte, out io.Writer) (err error) {
Expand Down Expand Up @@ -154,6 +155,7 @@ func Do(client *http.Client, ctx context.Context, requestInput []byte, out io.Wr
StatusCode: response.StatusCode,
Status: response.Status,
Headers: response.Header,
BodySize: buf.Len(),
},
}
trace, err := json.Marshal(responseTrace)
Expand Down
4 changes: 4 additions & 0 deletions v2/pkg/engine/resolve/fetch.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ func (_ *SingleFetch) FetchKind() FetchKind {
// should be used only for object fields which could be fetched parallel
type ParallelFetch struct {
Fetches []Fetch
Trace *DataSourceLoadTrace
}

func (_ *ParallelFetch) FetchKind() FetchKind {
Expand All @@ -70,6 +71,7 @@ func (_ *ParallelFetch) FetchKind() FetchKind {
// should be used only for object fields which should be fetched serial
type SerialFetch struct {
Fetches []Fetch
Trace *DataSourceLoadTrace
}

func (_ *SerialFetch) FetchKind() FetchKind {
Expand Down Expand Up @@ -134,6 +136,7 @@ func (_ *EntityFetch) FetchKind() FetchKind {
type ParallelListItemFetch struct {
Fetch *SingleFetch
Traces []*SingleFetch
Trace *DataSourceLoadTrace
}

func (_ *ParallelListItemFetch) FetchKind() FetchKind {
Expand Down Expand Up @@ -183,6 +186,7 @@ type DataSourceLoadTrace struct {
SingleFlightSharedResponse bool `json:"single_flight_shared_response"`
LoadSkipped bool `json:"load_skipped"`
LoadStats *LoadStats `json:"load_stats,omitempty"`
Path string `json:"-"`
}

type LoadStats struct {
Expand Down
53 changes: 50 additions & 3 deletions v2/pkg/engine/resolve/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ import (
"net/http/httptrace"
"runtime"
"runtime/debug"
"strings"
"sync"
"time"

"github.com/buger/jsonparser"
"github.com/pkg/errors"
"github.com/tidwall/gjson"
"github.com/wundergraph/graphql-go-tools/v2/pkg/ast"
"golang.org/x/sync/errgroup"

"github.com/TykTechnologies/graphql-go-tools/v2/pkg/astjson"
Expand All @@ -30,9 +32,11 @@ type Loader struct {
enableSingleFlight bool
path []string
traceOptions RequestTraceOptions
info *GraphQLResponseInfo
}

func (l *Loader) Free() {
l.info = nil
l.ctx = nil
l.sf = nil
l.data = nil
Expand All @@ -48,6 +52,7 @@ func (l *Loader) LoadGraphQLResponseData(ctx *Context, response *GraphQLResponse
l.errorsRoot = resolvable.errorsRoot
l.traceOptions = resolvable.requestTraceOptions
l.ctx = ctx
l.info = response.Info
return l.walkNode(response.Data, []int{resolvable.dataRoot})
}

Expand Down Expand Up @@ -78,6 +83,28 @@ func (l *Loader) popArrayPath() {
l.path = l.path[:len(l.path)-1]
}

func (l *Loader) renderPath() string {
builder := strings.Builder{}
if l.info != nil {
switch l.info.OperationType {
case ast.OperationTypeQuery:
builder.WriteString("query")
case ast.OperationTypeMutation:
builder.WriteString("mutation")
case ast.OperationTypeSubscription:
builder.WriteString("subscription")
}
}
if len(l.path) == 0 {
return builder.String()
}
for i := range l.path {
builder.WriteByte('.')
builder.WriteString(l.path[i])
}
return builder.String()
}

func (l *Loader) walkObject(object *Object, parentItems []int) (err error) {
l.pushPath(object.Path)
defer l.popPath(object.Path)
Expand Down Expand Up @@ -164,13 +191,23 @@ func (l *Loader) resolveAndMergeFetch(fetch Fetch, items []int) error {
}
return l.mergeResult(res, items)
case *SerialFetch:
if l.traceOptions.Enable {
f.Trace = &DataSourceLoadTrace{
Path: l.renderPath(),
}
}
for i := range f.Fetches {
err := l.resolveAndMergeFetch(f.Fetches[i], items)
if err != nil {
return errors.WithStack(err)
}
}
case *ParallelFetch:
if l.traceOptions.Enable {
f.Trace = &DataSourceLoadTrace{
Path: l.renderPath(),
}
}
results := make([]*result, len(f.Fetches))
g, ctx := errgroup.WithContext(l.ctx.ctx)
for i := range f.Fetches {
Expand Down Expand Up @@ -200,6 +237,11 @@ func (l *Loader) resolveAndMergeFetch(fetch Fetch, items []int) error {
}
}
case *ParallelListItemFetch:
if l.traceOptions.Enable {
f.Trace = &DataSourceLoadTrace{
Path: l.renderPath(),
}
}
results := make([]*result, len(items))
g, ctx := errgroup.WithContext(l.ctx.ctx)
for i := range items {
Expand Down Expand Up @@ -678,8 +720,10 @@ WithNextItem:

func (l *Loader) executeSourceLoad(ctx context.Context, disallowSingleFlight bool, source DataSource, input []byte, out io.Writer, trace *DataSourceLoadTrace) error {
if l.traceOptions.Enable {
trace.Path = l.renderPath()
if !l.traceOptions.ExcludeInput {
trace.Input = []byte(string(input)) // copy input explicitly, omit __trace__ field
trace.Input = make([]byte, len(input))
copy(trace.Input, input) // copy input explicitly, omit __trace__ field
}
if gjson.ValidBytes(input) {
inputCopy := make([]byte, len(input))
Expand Down Expand Up @@ -801,9 +845,12 @@ func (l *Loader) executeSourceLoad(ctx context.Context, disallowSingleFlight boo
if l.traceOptions.Enable {
if !l.traceOptions.ExcludeOutput && data != nil {
if l.traceOptions.EnablePredictableDebugTimings {
trace.Output = jsonparser.Delete([]byte(string(data)), "extensions", "trace", "response", "headers", "Date")
dataCopy := make([]byte, len(data))
copy(dataCopy, data)
trace.Output = jsonparser.Delete(dataCopy, "extensions", "trace", "response", "headers", "Date")
} else {
trace.Output = []byte(string(data))
trace.Output = make([]byte, len(data))
copy(trace.Output, data)
}
}
trace.SingleFlightSharedResponse = shared
Expand Down
14 changes: 14 additions & 0 deletions v2/pkg/engine/resolve/trace.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package resolve
import (
"context"
"encoding/json"

"github.com/google/uuid"
)

Expand Down Expand Up @@ -78,6 +79,7 @@ const (
type TraceFetch struct {
Id string `json:"id,omitempty"`
Type TraceFetchType `json:"type,omitempty"`
Path string `json:"path,omitempty"`
DataSourceID string `json:"data_source_id,omitempty"`
Fetches []*TraceFetch `json:"fetches,omitempty"`
DataSourceLoadTrace *DataSourceLoadTrace `json:"datasource_load_trace,omitempty"`
Expand Down Expand Up @@ -170,25 +172,35 @@ func parseFetch(fetch Fetch) *TraceFetch {
traceFetch.Type = TraceFetchTypeSingle
if f.Trace != nil {
traceFetch.DataSourceLoadTrace = f.Trace
traceFetch.Path = f.Trace.Path
}
if f.Info != nil {
traceFetch.DataSourceID = f.Info.DataSourceID
}

case *ParallelFetch:
traceFetch.Type = TraceFetchTypeParallel
if f.Trace != nil {
traceFetch.Path = f.Trace.Path
}
for _, subFetch := range f.Fetches {
traceFetch.Fetches = append(traceFetch.Fetches, parseFetch(subFetch))
}

case *SerialFetch:
traceFetch.Type = TraceFetchTypeSerial
if f.Trace != nil {
traceFetch.Path = f.Trace.Path
}
for _, subFetch := range f.Fetches {
traceFetch.Fetches = append(traceFetch.Fetches, parseFetch(subFetch))
}

case *ParallelListItemFetch:
traceFetch.Type = TraceFetchTypeParallelListItem
if f.Trace != nil {
traceFetch.Path = f.Trace.Path
}
traceFetch.Fetches = append(traceFetch.Fetches, parseFetch(f.Fetch))
if f.Traces != nil {
for _, trace := range f.Traces {
Expand All @@ -204,6 +216,7 @@ func parseFetch(fetch Fetch) *TraceFetch {
traceFetch.Type = TraceFetchTypeEntity
if f.Trace != nil {
traceFetch.DataSourceLoadTrace = f.Trace
traceFetch.Path = f.Trace.Path
}
if f.Info != nil {
traceFetch.DataSourceID = f.Info.DataSourceID
Expand All @@ -213,6 +226,7 @@ func parseFetch(fetch Fetch) *TraceFetch {
traceFetch.Type = TraceFetchTypeBatchEntity
if f.Trace != nil {
traceFetch.DataSourceLoadTrace = f.Trace
traceFetch.Path = f.Trace.Path
}
if f.Info != nil {
traceFetch.DataSourceID = f.Info.DataSourceID
Expand Down

0 comments on commit 7755d67

Please sign in to comment.