77 "fmt"
88 "net/http"
99 "os"
10+ "time"
1011
1112 "github.com/wundergraph/cosmo/router/pkg/metric"
1213
@@ -53,6 +54,26 @@ type Planner struct {
5354 operationValidator * astvalidation.OperationValidator
5455}
5556
57+ type OperationTimes struct {
58+ ParseTime time.Duration
59+ NormalizeTime time.Duration
60+ ValidateTime time.Duration
61+ PlanTime time.Duration
62+ }
63+
64+ func (ot * OperationTimes ) TotalTime () time.Duration {
65+ return ot .ParseTime + ot .NormalizeTime + ot .ValidateTime + ot .PlanTime
66+ }
67+
68+ func (ot OperationTimes ) Merge (other OperationTimes ) OperationTimes {
69+ return OperationTimes {
70+ ParseTime : ot .ParseTime + other .ParseTime ,
71+ NormalizeTime : ot .NormalizeTime + other .NormalizeTime ,
72+ ValidateTime : ot .ValidateTime + other .ValidateTime ,
73+ PlanTime : ot .PlanTime + other .PlanTime ,
74+ }
75+ }
76+
5677type PlanOutputFormat string
5778
5879const (
@@ -75,57 +96,74 @@ func NewPlanner(planConfiguration *plan.Configuration, definition *ast.Document,
7596}
7697
7798// PlanOperation creates a query plan from an operation file in a pretty-printed text or JSON format
78- func (pl * Planner ) PlanOperation (operationFilePath string , outputFormat PlanOutputFormat ) (string , error ) {
79- operation , err := pl .ParseAndPrepareOperation (operationFilePath )
99+ func (pl * Planner ) PlanOperation (operationFilePath string , outputFormat PlanOutputFormat ) (string , OperationTimes , error ) {
100+ operation , opTimes , err := pl .ParseAndPrepareOperation (operationFilePath )
80101 if err != nil {
81- return "" , err
102+ return "" , opTimes , err
82103 }
83104
84- rawPlan , err := pl .PlanPreparedOperation (operation )
105+ rawPlan , opTimes2 , err := pl .PlanPreparedOperation (operation )
106+ opTimes = opTimes .Merge (opTimes2 )
85107 if err != nil {
86- return "" , fmt .Errorf ("failed to plan operation: %w" , err )
108+ return "" , opTimes , fmt .Errorf ("failed to plan operation: %w" , err )
87109 }
88110
89111 switch outputFormat {
90112 case PlanOutputFormatText :
91- return rawPlan .PrettyPrint (), nil
113+ return rawPlan .PrettyPrint (), opTimes , nil
92114 case PlanOutputFormatJSON :
93115 marshal , err := json .Marshal (rawPlan )
94116 if err != nil {
95- return "" , fmt .Errorf ("failed to marshal raw plan: %w" , err )
117+ return "" , opTimes , fmt .Errorf ("failed to marshal raw plan: %w" , err )
96118 }
97- return string (marshal ), nil
119+ return string (marshal ), opTimes , nil
98120 }
99121
100- return "" , fmt .Errorf ("invalid outputFormat specified: %q" , outputFormat )
122+ return "" , opTimes , fmt .Errorf ("invalid outputFormat specified: %q" , outputFormat )
101123}
102124
103125// ParseAndPrepareOperation parses, normalizes and validates the operation
104- func (pl * Planner ) ParseAndPrepareOperation (operationFilePath string ) (* ast.Document , error ) {
126+ func (pl * Planner ) ParseAndPrepareOperation (operationFilePath string ) (* ast.Document , OperationTimes , error ) {
127+ start := time .Now ()
105128 operation , err := pl .parseOperation (operationFilePath )
129+ parseTime := time .Since (start )
106130 if err != nil {
107- return nil , & PlannerOperationValidationError {err : err }
131+ return nil , OperationTimes { ParseTime : parseTime }, & PlannerOperationValidationError {err : err }
108132 }
109133
110- return pl .PrepareOperation (operation )
134+ operation , opTimes , err := pl .PrepareOperation (operation )
135+ opTimes .ParseTime = parseTime
136+ if err != nil {
137+ return nil , opTimes , err
138+ }
139+
140+ return operation , opTimes , nil
111141}
112142
113143// PrepareOperation normalizes and validates the operation
114- func (pl * Planner ) PrepareOperation (operation * ast.Document ) (* ast.Document , error ) {
144+ func (pl * Planner ) PrepareOperation (operation * ast.Document ) (* ast.Document , OperationTimes , error ) {
115145 operationName := findOperationName (operation )
116146 if operationName == nil {
117- return nil , & PlannerOperationValidationError {err : errors .New ("operation name not found" )}
147+ return nil , OperationTimes {}, & PlannerOperationValidationError {err : errors .New ("operation name not found" )}
118148 }
119149
120- if err := pl .normalizeOperation (operation , operationName ); err != nil {
121- return nil , & PlannerOperationValidationError {err : err }
150+ opTimes := OperationTimes {}
151+
152+ start := time .Now ()
153+ err := pl .normalizeOperation (operation , operationName )
154+ opTimes .NormalizeTime = time .Since (start )
155+ if err != nil {
156+ return nil , opTimes , & PlannerOperationValidationError {err : err }
122157 }
123158
124- if err := pl .validateOperation (operation ); err != nil {
125- return nil , & PlannerOperationValidationError {err : err }
159+ start = time .Now ()
160+ err = pl .validateOperation (operation )
161+ opTimes .ValidateTime = time .Since (start )
162+ if err != nil {
163+ return nil , opTimes , & PlannerOperationValidationError {err : err }
126164 }
127165
128- return operation , nil
166+ return operation , opTimes , nil
129167}
130168
131169func (pl * Planner ) normalizeOperation (operation * ast.Document , operationName []byte ) (err error ) {
@@ -160,7 +198,7 @@ func (pl *Planner) normalizeOperation(operation *ast.Document, operationName []b
160198}
161199
162200// PlanPreparedOperation creates a query plan from a normalized and validated operation
163- func (pl * Planner ) PlanPreparedOperation (operation * ast.Document ) (planNode * resolve.FetchTreeQueryPlanNode , err error ) {
201+ func (pl * Planner ) PlanPreparedOperation (operation * ast.Document ) (planNode * resolve.FetchTreeQueryPlanNode , opTimes OperationTimes , err error ) {
164202 defer func () {
165203 if r := recover (); r != nil {
166204 err = fmt .Errorf ("panic during plan generation: %v" , r )
@@ -172,25 +210,30 @@ func (pl *Planner) PlanPreparedOperation(operation *ast.Document) (planNode *res
172210 operationName := findOperationName (operation )
173211
174212 if operationName == nil {
175- return nil , errors .New ("operation name not found" )
213+ return nil , opTimes , errors .New ("operation name not found" )
176214 }
177215
178216 // create and postprocess the plan
217+ start := time .Now ()
179218 preparedPlan := pl .planner .Plan (operation , pl .definition , string (operationName ), & report , plan .IncludeQueryPlanInResponse ())
219+ opTimes .PlanTime = time .Since (start )
180220 if report .HasErrors () {
181- return nil , errors .New (report .Error ())
221+ return nil , opTimes , errors .New (report .Error ())
182222 }
223+
183224 post := postprocess .NewProcessor ()
184225 post .Process (preparedPlan )
226+ // measure postprocessing time as part of planning time
227+ opTimes .PlanTime = time .Since (start )
185228
186229 switch p := preparedPlan .(type ) {
187230 case * plan.SynchronousResponsePlan :
188- return p .Response .Fetches .QueryPlan (), nil
231+ return p .Response .Fetches .QueryPlan (), opTimes , nil
189232 case * plan.SubscriptionResponsePlan :
190- return p .Response .Response .Fetches .QueryPlan (), nil
233+ return p .Response .Response .Fetches .QueryPlan (), opTimes , nil
191234 }
192235
193- return & resolve.FetchTreeQueryPlanNode {}, nil
236+ return & resolve.FetchTreeQueryPlanNode {}, opTimes , nil
194237}
195238
196239func (pl * Planner ) validateOperation (operation * ast.Document ) (err error ) {
0 commit comments