graphql-metrics 4.1.0 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7c32ac33ad3d4dcce935518115cb51e8b619b4f06e5eaacf6eded57b0393e8d1
4
- data.tar.gz: 466c20309ac8e7ca599580f9145c936bbcb76cb29c9f019fa28c3be3c9c46ba7
3
+ metadata.gz: 8e2e02bf92da34c67af89cf112efe2525ca4a785f4f9169dd35320c687f5f9f4
4
+ data.tar.gz: 4ec1a5b37180e5e7be02934992347053d3d4a4d2f03866975600577704e141a1
5
5
  SHA512:
6
- metadata.gz: fc8fa91436e89b75faa0573007bb20ce3a381019231dcb6131f41a73395b4c62c990be07efadbf437c9f1c67f9a9a03ef328c3d379e57132dcee00332489bf7f
7
- data.tar.gz: d465979e9f3b3ea7b7f17c0275a48bd629ab45f80eeb89e69dc7d8040dd8f615e7a23bfefdab0befb04402d192e11dfb818417c65f930dd8fdcdd3eae030715b
6
+ metadata.gz: 213906efdd58b32a7cab558028fc96de33c9624f9e85ffbb572954486bb31bebc7e718f0aee4e6df4f7324d5c99c6cc1849d13a54e43bef1963a3b55517796e1
7
+ data.tar.gz: 4f6309cc4cdbe2244be1d744173f795df04e955af9cef6d52111f1181c074cece01dcf226ebe20a545f3f2d41306b5eb5226e4d90ec1a7ece70656707982934a
data/CHANGELOG.md CHANGED
@@ -1,3 +1,6 @@
1
+ 5.0.0
2
+ -----
3
+ - [50](https://github.com/Shopify/graphql-metrics/pull/50) Capture metrics for directives and their arguments.
1
4
  4.1.0
2
5
  -----
3
6
  - [42](https://github.com/Shopify/graphql-metrics/pull/42) Capture timing of the `lex` phase.
data/README.md CHANGED
@@ -141,13 +141,22 @@ even if you opt to collect them by using `GraphQL::Metrics::Analyzer` and `Graph
141
141
  store_metrics(:fields, metrics)
142
142
  end
143
143
 
144
+ # @param metrics [Hash] Directive metrics
145
+ # {
146
+ # directive_name: "customDirective",
147
+ # }
148
+ def directive_extracted(metrics)
149
+ store_metrics(:directives, metrics)
150
+ end
151
+
144
152
  # @param metrics [Hash] Argument usage metrics, including a few details about the query document itself, as well
145
153
  # as resolver timings metrics, also ahering to the Apollo Tracing spec referred to above.
146
154
  # {
147
- # argument_name: "id",
155
+ # argument_name: "ids",
148
156
  # argument_type_name: "ID",
149
- # parent_field_name: "post",
150
- # parent_field_type_name: "QueryRoot",
157
+ # parent_name: "comments",
158
+ # grandparent_type_name: "Post",
159
+ # grandparent_node_name: "post",
151
160
  # default_used: false,
152
161
  # value_is_null: false,
153
162
  # value: <GraphQL::Query::Arguments::ArgumentValue>,
@@ -171,6 +180,65 @@ even if you opt to collect them by using `GraphQL::Metrics::Analyzer` and `Graph
171
180
 
172
181
  Once defined, you can opt into capturing all metrics seen above by simply including GraphQL::Metrics as a plugin on your
173
182
  schema.
183
+ #### Metrics that are captured for arguments for fields and directives
184
+
185
+ Let's have a query example
186
+
187
+ ```graphql
188
+ query PostDetails($postId: ID!, $commentsTags: [String!] = null, $val: Int!) @customDirective(val: $val) {
189
+ post(id: $postId) {
190
+ title @skip(if: true)
191
+ comments(ids: [1, 2], tags: $commentsTags) {
192
+ id
193
+ body
194
+ }
195
+ }
196
+ }
197
+ ```
198
+ These are some of the arguments that are extracted
199
+
200
+ ```ruby
201
+ {
202
+ argument_name: "if", # argument name
203
+ argument_type_name: "Boolean", # argument type
204
+ parent_name: "skip", # argument belongs to `skip` directive
205
+ grandparent_type_name: "__Directive", # argument was applied to directive
206
+ grandparent_node_name: "title", # directive was applied to field title
207
+ default_used: false, # check if default value was used
208
+ value_is_null: false, # check if value was null
209
+ value: <GraphQL::Execution::Interpreter::ArgumentValue>
210
+ }, {
211
+ argument_name: "id",
212
+ argument_name: "ids",
213
+ argument_type_name: "ID",
214
+ parent_name: "comments", # name of the node that argument was applied to
215
+ grandparent_type_name: "Post", # grandparent node to uniquely identify which node the argument was applied to
216
+ grandparent_node_name: "post", # name of grandparend node
217
+ default_used: false,
218
+ value_is_null: false,
219
+ value: <GraphQL::Execution::Interpreter::ArgumentValue>
220
+ }, {
221
+ argument_name: "id",
222
+ argument_type_name: "ID",
223
+ parent_name: "post", # argument applied to post field
224
+ grandparent_type_name: "QueryRoot", # post is a QueryRoot
225
+ grandparent_node_name: "query", # post field is already in the query root
226
+ parent_input_object_type: nil,
227
+ default_used: false,
228
+ value_is_null: false,
229
+ value: <GraphQL::Execution::Interpreter::ArgumentValue>
230
+ }, {
231
+ argument_name: "val",
232
+ argument_type_name: "Int",
233
+ parent_name: "customDirective", # argument belongs to `customDirective` directive
234
+ grandparent_type_name: "__Directive", # argument was applied to directive
235
+ grandparent_node_name: "query", # directive was applied to query
236
+ parent_input_object_type: nil,
237
+ default_used: false,
238
+ value_is_null: false,
239
+ value: <GraphQL::Execution::Interpreter::ArgumentValue>
240
+ }
241
+ ```
174
242
 
175
243
  ### Make use of your analyzer
176
244
 
@@ -219,11 +287,11 @@ order in which methods defined on `GraphQL::Metrics::Instrumentation`, `GraphQL:
219
287
  Although you ideally will not need to care about these details if you are simply using this gem to gather metrics in
220
288
  your application as intended, here's a breakdown of the order of execution of the methods involved:
221
289
 
222
- When used as instrumentation, an analyzer and tracing, the order of execution is:
290
+ When used as instrumentation, an analyzer and tracing, the order of execution is usually:
223
291
 
292
+ * Tracer.capture_multiplex_start_time
224
293
  * Tracer.capture_lexing_time
225
294
  * Tracer.capture_parsing_time
226
- * Tracer.capture_multiplex_start_time
227
295
  * Instrumentation.before_query (context setup)
228
296
  * Tracer.capture_validation_time
229
297
  * Tracer.capture_analysis_time
@@ -30,7 +30,7 @@ Gem::Specification.new do |spec|
30
30
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
31
31
  spec.require_paths = ["lib"]
32
32
 
33
- spec.add_runtime_dependency "graphql", ">= 1.10.8"
33
+ spec.add_runtime_dependency "graphql", ">= 1.12.10"
34
34
  spec.add_runtime_dependency "concurrent-ruby", "~> 1.1.0"
35
35
  spec.add_development_dependency "rake", "~> 10.0"
36
36
  spec.add_development_dependency "minitest", "~> 5.0"
@@ -4,6 +4,7 @@ module GraphQL
4
4
  module Metrics
5
5
  class Analyzer < GraphQL::Analysis::AST::Analyzer
6
6
  attr_reader :query
7
+ DIRECTIVE_TYPE = "__Directive"
7
8
 
8
9
  def initialize(query_or_multiplex)
9
10
  super
@@ -31,20 +32,16 @@ module GraphQL
31
32
  }
32
33
  end
33
34
 
34
- def on_leave_field(node, _parent, visitor)
35
+ def on_leave_field(node, parent, visitor)
35
36
  return if visitor.field_definition.introspection?
36
37
  return if query.context[SKIP_FIELD_AND_ARGUMENT_METRICS]
37
38
 
38
- # Arguments can raise execution errors within their `prepare` methods
39
- # which aren't properly handled during analysis so we have to handle
40
- # them ourselves safely and return `nil`.
41
- argument_values = begin
42
- query.arguments_for(node, visitor.field_definition)
43
- rescue ::GraphQL::ExecutionError
44
- nil
45
- end
46
-
47
- extract_arguments(argument_values, visitor.field_definition) if argument_values
39
+ argument_values = arguments_for(node, visitor.field_definition)
40
+ extract_arguments(
41
+ argument: argument_values,
42
+ definition: visitor.field_definition,
43
+ parent: parent
44
+ ) if argument_values
48
45
 
49
46
  static_metrics = {
50
47
  field_name: node.name,
@@ -80,6 +77,17 @@ module GraphQL
80
77
  end
81
78
  end
82
79
 
80
+ def on_enter_directive(node, parent, visitor)
81
+ argument_values = arguments_for(node, visitor.directive_definition)
82
+ extract_arguments(
83
+ argument: argument_values,
84
+ definition: visitor.directive_definition,
85
+ parent: parent
86
+ ) if argument_values
87
+
88
+ directive_extracted({ directive_name: node.name })
89
+ end
90
+
83
91
  def result
84
92
  return if GraphQL::Metrics.timings_capture_enabled?(query.context)
85
93
  return if query.context[GraphQL::Metrics::SKIP_GRAPHQL_METRICS_ANALYSIS]
@@ -93,37 +101,91 @@ module GraphQL
93
101
 
94
102
  private
95
103
 
96
- def extract_arguments(argument, field_defn, parent_input_object = nil)
104
+ def arguments_for(node, definition)
105
+ # Arguments can raise execution errors within their `prepare` methods
106
+ # which aren't properly handled during analysis so we have to handle
107
+ # them ourselves safely and return `nil`.
108
+ query.arguments_for(node, definition)
109
+ rescue ::GraphQL::ExecutionError
110
+ nil
111
+ end
112
+
113
+ def extract_arguments(argument:, definition:, parent:, parent_input_object: nil)
97
114
  case argument
98
115
  when Array
99
116
  argument.each do |a|
100
- extract_arguments(a, field_defn, parent_input_object)
117
+ extract_arguments(
118
+ argument: a,
119
+ definition: definition,
120
+ parent_input_object: parent_input_object,
121
+ parent: parent
122
+ )
101
123
  end
102
124
  when Hash
103
125
  argument.each_value do |a|
104
- extract_arguments(a, field_defn, parent_input_object)
126
+ extract_arguments(
127
+ argument: a,
128
+ definition: definition,
129
+ parent_input_object: parent_input_object,
130
+ parent: parent)
105
131
  end
106
132
  when ::GraphQL::Execution::Interpreter::Arguments
107
133
  argument.each_value do |arg_val|
108
- extract_arguments(arg_val, field_defn, parent_input_object)
134
+ extract_arguments(
135
+ argument: arg_val,
136
+ definition: definition,
137
+ parent_input_object: parent_input_object,
138
+ parent: parent
139
+ )
109
140
  end
110
141
  when ::GraphQL::Execution::Interpreter::ArgumentValue
111
- extract_argument(argument, field_defn, parent_input_object)
112
- extract_arguments(argument.value, field_defn, parent_input_object)
142
+ extract_argument(
143
+ value: argument,
144
+ definition: definition,
145
+ parent_input_object: parent_input_object,
146
+ parent: parent
147
+ )
148
+ extract_arguments(
149
+ argument:argument.value,
150
+ definition: definition,
151
+ parent_input_object: parent_input_object,
152
+ parent: parent
153
+ )
113
154
  when ::GraphQL::Schema::InputObject
114
155
  input_object_argument_values = argument.arguments.argument_values.values
115
156
  parent_input_object = input_object_argument_values.first&.definition&.owner
116
157
 
117
- extract_arguments(input_object_argument_values, field_defn, parent_input_object)
158
+ extract_arguments(
159
+ argument: input_object_argument_values,
160
+ definition: definition,
161
+ parent_input_object: parent_input_object,
162
+ parent: parent
163
+ )
118
164
  end
119
165
  end
120
166
 
121
- def extract_argument(value, field_defn, parent_input_object = nil)
167
+ def extract_argument(value:, definition:, parent_input_object:, parent:)
168
+ parent_type_name = if definition.is_a?(GraphQL::Schema::Field)
169
+ definition.owner.graphql_name
170
+ else
171
+ DIRECTIVE_TYPE
172
+ end
173
+
174
+ grand_parent_name = case parent
175
+ when GraphQL::Language::Nodes::OperationDefinition
176
+ parent.operation_type
177
+ when GraphQL::Language::Nodes::InlineFragment
178
+ parent.type.name
179
+ else
180
+ parent.name
181
+ end
182
+
122
183
  static_metrics = {
123
184
  argument_name: value.definition.graphql_name,
124
185
  argument_type_name: value.definition.type.unwrap.graphql_name,
125
- parent_field_name: field_defn.graphql_name,
126
- parent_field_type_name: field_defn.owner.graphql_name,
186
+ parent_name: definition.graphql_name,
187
+ grandparent_type_name: parent_type_name,
188
+ grandparent_node_name: grand_parent_name,
127
189
  parent_input_object_type: parent_input_object&.graphql_name,
128
190
  default_used: value.default_used?,
129
191
  value_is_null: value.value.nil?,
@@ -4,10 +4,12 @@ module GraphQL
4
4
  module Metrics
5
5
  class Tracer
6
6
  # NOTE: These constants come from the graphql ruby gem and are in "chronological" order based on the phases
7
- # of execution of the graphql-ruby gem. Most of them can be run multiple times when multiplexing multiple queries.
7
+ # of execution of the graphql-ruby gem, though old versions of the gem aren't always consistent about this (see
8
+ # https://github.com/rmosolgo/graphql-ruby/issues/3393). Most of them can be run multiple times when
9
+ # multiplexing multiple queries.
10
+ GRAPHQL_GEM_EXECUTE_MULTIPLEX_KEY = 'execute_multiplex' # wraps everything below this line; only run once
8
11
  GRAPHQL_GEM_LEXING_KEY = 'lex' # may not trigger if the query is passed in pre-parsed
9
12
  GRAPHQL_GEM_PARSING_KEY = 'parse' # may not trigger if the query is passed in pre-parsed
10
- GRAPHQL_GEM_EXECUTE_MULTIPLEX_KEY = 'execute_multiplex' # wraps everything below this line; only run once
11
13
  GRAPHQL_GEM_VALIDATION_KEY = 'validate'
12
14
  GRAPHQL_GEM_ANALYZE_MULTIPLEX_KEY = 'analyze_multiplex' # wraps all `analyze_query`s; only run once
13
15
  GRAPHQL_GEM_ANALYZE_QUERY_KEY = 'analyze_query'
@@ -24,12 +26,12 @@ module GraphQL
24
26
  return yield if skip_tracing
25
27
 
26
28
  case key
29
+ when GRAPHQL_GEM_EXECUTE_MULTIPLEX_KEY
30
+ return capture_multiplex_start_time(&block)
27
31
  when GRAPHQL_GEM_LEXING_KEY
28
32
  return capture_lexing_time(&block)
29
33
  when GRAPHQL_GEM_PARSING_KEY
30
34
  return capture_parsing_time(&block)
31
- when GRAPHQL_GEM_EXECUTE_MULTIPLEX_KEY
32
- return capture_multiplex_start_time(&block)
33
35
  when GRAPHQL_GEM_VALIDATION_KEY
34
36
  return capture_validation_time(context, &block)
35
37
  when GRAPHQL_GEM_ANALYZE_MULTIPLEX_KEY
@@ -2,6 +2,6 @@
2
2
 
3
3
  module GraphQL
4
4
  module Metrics
5
- VERSION = "4.1.0"
5
+ VERSION = "5.0.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql-metrics
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.1.0
4
+ version: 5.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Christopher Butcher
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-03-16 00:00:00.000000000 Z
11
+ date: 2021-06-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: graphql
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 1.10.8
19
+ version: 1.12.10
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 1.10.8
26
+ version: 1.12.10
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: concurrent-ruby
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -241,7 +241,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
241
241
  - !ruby/object:Gem::Version
242
242
  version: '0'
243
243
  requirements: []
244
- rubygems_version: 3.1.2
244
+ rubygems_version: 3.0.3
245
245
  signing_key:
246
246
  specification_version: 4
247
247
  summary: GraphQL Metrics Extractor