git.sr.ht/~sircmpwn/gqlgen@v0.0.0-20200522192042-c84d29a1c940/graphql/handler/apollotracing/tracer.go (about)

     1  package apollotracing
     2  
     3  import (
     4  	"context"
     5  	"sync"
     6  	"time"
     7  
     8  	"git.sr.ht/~sircmpwn/gqlgen/graphql"
     9  	"github.com/vektah/gqlparser/v2/ast"
    10  )
    11  
    12  type (
    13  	Tracer struct{}
    14  
    15  	TracingExtension struct {
    16  		mu         sync.Mutex
    17  		Version    int           `json:"version"`
    18  		StartTime  time.Time     `json:"startTime"`
    19  		EndTime    time.Time     `json:"endTime"`
    20  		Duration   time.Duration `json:"duration"`
    21  		Parsing    Span          `json:"parsing"`
    22  		Validation Span          `json:"validation"`
    23  		Execution  struct {
    24  			Resolvers []ResolverExecution `json:"resolvers"`
    25  		} `json:"execution"`
    26  	}
    27  
    28  	Span struct {
    29  		StartOffset time.Duration `json:"startOffset"`
    30  		Duration    time.Duration `json:"duration"`
    31  	}
    32  
    33  	ResolverExecution struct {
    34  		Path        ast.Path      `json:"path"`
    35  		ParentType  string        `json:"parentType"`
    36  		FieldName   string        `json:"fieldName"`
    37  		ReturnType  string        `json:"returnType"`
    38  		StartOffset time.Duration `json:"startOffset"`
    39  		Duration    time.Duration `json:"duration"`
    40  	}
    41  )
    42  
    43  var _ interface {
    44  	graphql.HandlerExtension
    45  	graphql.ResponseInterceptor
    46  	graphql.FieldInterceptor
    47  } = Tracer{}
    48  
    49  func (a Tracer) ExtensionName() string {
    50  	return "ApolloTracing"
    51  }
    52  
    53  func (a Tracer) Validate(schema graphql.ExecutableSchema) error {
    54  	return nil
    55  }
    56  
    57  func (a Tracer) InterceptField(ctx context.Context, next graphql.Resolver) (res interface{}, err error) {
    58  	rc := graphql.GetOperationContext(ctx)
    59  	td, ok := graphql.GetExtension(ctx, "tracing").(*TracingExtension)
    60  	if !ok {
    61  		panic("missing tracing extension")
    62  	}
    63  
    64  	start := graphql.Now()
    65  
    66  	defer func() {
    67  		td.mu.Lock()
    68  		defer td.mu.Unlock()
    69  		fc := graphql.GetFieldContext(ctx)
    70  
    71  		end := graphql.Now()
    72  
    73  		td.Execution.Resolvers = append(td.Execution.Resolvers, ResolverExecution{
    74  			Path:        fc.Path(),
    75  			ParentType:  fc.Object,
    76  			FieldName:   fc.Field.Name,
    77  			ReturnType:  fc.Field.Definition.Type.String(),
    78  			StartOffset: start.Sub(rc.Stats.OperationStart),
    79  			Duration:    end.Sub(start),
    80  		})
    81  	}()
    82  
    83  	return next(ctx)
    84  }
    85  
    86  func (a Tracer) InterceptResponse(ctx context.Context, next graphql.ResponseHandler) *graphql.Response {
    87  	rc := graphql.GetOperationContext(ctx)
    88  
    89  	start := rc.Stats.OperationStart
    90  
    91  	td := &TracingExtension{
    92  		Version:   1,
    93  		StartTime: start,
    94  		Parsing: Span{
    95  			StartOffset: rc.Stats.Parsing.Start.Sub(start),
    96  			Duration:    rc.Stats.Parsing.End.Sub(rc.Stats.Parsing.Start),
    97  		},
    98  
    99  		Validation: Span{
   100  			StartOffset: rc.Stats.Validation.Start.Sub(start),
   101  			Duration:    rc.Stats.Validation.End.Sub(rc.Stats.Validation.Start),
   102  		},
   103  
   104  		Execution: struct {
   105  			Resolvers []ResolverExecution `json:"resolvers"`
   106  		}{},
   107  	}
   108  
   109  	graphql.RegisterExtension(ctx, "tracing", td)
   110  	resp := next(ctx)
   111  
   112  	end := graphql.Now()
   113  	td.EndTime = end
   114  	td.Duration = end.Sub(start)
   115  
   116  	return resp
   117  }