graphql-metrics 1.1.5 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/Gemfile.lock +4 -1
- data/README.md +28 -1
- data/graphql_metrics.gemspec +1 -0
- data/lib/graphql_metrics/extractor.rb +74 -93
- data/lib/graphql_metrics/instrumentation.rb +106 -0
- data/lib/graphql_metrics/timed_batch_executor.rb +2 -2
- data/lib/graphql_metrics/version.rb +1 -1
- data/lib/graphql_metrics.rb +1 -0
- metadata +18 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a37ad6af9b19ef274d8f36142f5d73f92c43dc8c
|
4
|
+
data.tar.gz: b6a7180811bbef742741324af59dc6b78d25fb6a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9a6f311bc6c4235ec3390784f13b2b6396a87970e549d2696001755879d0c196fd45275f01953e36bee7120b6db75abaaa7446e5cd95cf175df182d3f1a98dd8
|
7
|
+
data.tar.gz: b483a7642ee22f357a4e116fce3aacfed9e18d9d2f79782ca671e26d284fffc4381f63d599cde5095741e9a0290d0542476763748aec861b3d4907219061248e
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
graphql-metrics (
|
4
|
+
graphql-metrics (2.0.0)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
@@ -26,6 +26,8 @@ GEM
|
|
26
26
|
metaclass (0.0.4)
|
27
27
|
method_source (0.9.0)
|
28
28
|
minitest (5.11.3)
|
29
|
+
minitest-focus (1.1.2)
|
30
|
+
minitest (>= 4, < 6)
|
29
31
|
mocha (1.5.0)
|
30
32
|
metaclass (~> 0.0.1)
|
31
33
|
promise.rb (0.7.4)
|
@@ -53,6 +55,7 @@ DEPENDENCIES
|
|
53
55
|
graphql-batch
|
54
56
|
graphql-metrics!
|
55
57
|
minitest (~> 5.0)
|
58
|
+
minitest-focus
|
56
59
|
mocha
|
57
60
|
pry
|
58
61
|
pry-byebug
|
data/README.md
CHANGED
@@ -53,7 +53,7 @@ implementing the methods below, as needed.
|
|
53
53
|
Here's an example of a simple extractor that logs out all GraphQL query details.
|
54
54
|
|
55
55
|
```ruby
|
56
|
-
class LoggingExtractor < GraphQLMetrics::
|
56
|
+
class LoggingExtractor < GraphQLMetrics::Instrumentation
|
57
57
|
def query_extracted(metrics, _metadata)
|
58
58
|
Rails.logger.debug({
|
59
59
|
query_string: metrics[:query_string], # "query Project { project(name: "GraphQL") { tagline } }"
|
@@ -103,6 +103,7 @@ class LoggingExtractor < GraphQLMetrics::Extractor
|
|
103
103
|
default_value_type: metrics[:default_value_type], # "IMPLICIT_NULL"
|
104
104
|
provided_value: metrics[:provided_value], # false
|
105
105
|
default_used: metrics[:default_used], # false
|
106
|
+
used_in_operation: metrics[:used_in_operation], # true
|
106
107
|
})
|
107
108
|
end
|
108
109
|
|
@@ -133,6 +134,32 @@ class LoggingExtractor < GraphQLMetrics::Extractor
|
|
133
134
|
end
|
134
135
|
```
|
135
136
|
|
137
|
+
You can also define ad hoc query Extractors that can work with instances of GraphQL::Query, for example:
|
138
|
+
|
139
|
+
```ruby
|
140
|
+
class TypeUsageExtractor < GraphQLMetrics::Extractor
|
141
|
+
attr_reader :types_used
|
142
|
+
|
143
|
+
def initialize
|
144
|
+
@types_used = Set.new
|
145
|
+
end
|
146
|
+
|
147
|
+
def field_extracted(metrics, _metadata)
|
148
|
+
@types_used << metrics[:type_name]
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# ...
|
153
|
+
|
154
|
+
extractor = TypeUsageExtractor.new
|
155
|
+
extractor.extract!(query)
|
156
|
+
puts extractor.types_used
|
157
|
+
# => ["Comment", "Post", "QueryRoot"]
|
158
|
+
```
|
159
|
+
|
160
|
+
Note that resolver-timing related data like `duration` in `query_extracted` and `resolver_times` in `field_extracted`
|
161
|
+
won't be available when using an ad hoc Extractor, since the query isn't actually being run; it's only analyzed.
|
162
|
+
|
136
163
|
## Development
|
137
164
|
|
138
165
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/graphql_metrics.gemspec
CHANGED
@@ -2,117 +2,76 @@
|
|
2
2
|
|
3
3
|
module GraphQLMetrics
|
4
4
|
class Extractor
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
class DummyInstrumentor
|
6
|
+
def after_query_start_and_end_time
|
7
|
+
[nil, nil]
|
8
|
+
end
|
9
|
+
|
10
|
+
def after_query_resolver_times(_ast_node)
|
11
|
+
[]
|
12
|
+
end
|
13
|
+
|
14
|
+
def ctx_namespace
|
15
|
+
{}
|
16
|
+
end
|
17
|
+
end
|
8
18
|
|
9
19
|
EXPLICIT_NULL = 'EXPLICIT_NULL'
|
10
20
|
IMPLICIT_NULL = 'IMPLICIT_NULL'
|
11
21
|
NON_NULL = 'NON_NULL'
|
12
22
|
|
13
|
-
attr_reader :query
|
14
|
-
|
15
|
-
def self.use(schema_definition)
|
16
|
-
extractor = self.new
|
17
|
-
return unless extractor.extractor_defines_any_visitors?
|
18
|
-
|
19
|
-
extractor.setup_instrumentation(schema_definition)
|
20
|
-
end
|
21
|
-
|
22
|
-
def use(schema_definition)
|
23
|
-
return unless extractor_defines_any_visitors?
|
24
|
-
setup_instrumentation(schema_definition)
|
25
|
-
end
|
23
|
+
attr_reader :query
|
26
24
|
|
27
|
-
def
|
28
|
-
|
29
|
-
schema_definition.instrument(:field, self)
|
25
|
+
def initialize(instrumentor = DummyInstrumentor.new)
|
26
|
+
@instrumentor = instrumentor
|
30
27
|
end
|
31
28
|
|
32
|
-
def
|
33
|
-
|
34
|
-
|
35
|
-
ns = query.context.namespace(CONTEXT_NAMESPACE)
|
36
|
-
ns[TIMING_CACHE_KEY] = {}
|
37
|
-
ns[START_TIME_KEY] = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
38
|
-
rescue StandardError => ex
|
39
|
-
handle_extraction_exception(ex)
|
29
|
+
def instrumentor
|
30
|
+
@instrumentor ||= DummyInstrumentor.new
|
40
31
|
end
|
41
32
|
|
42
|
-
def
|
43
|
-
return unless extractor_defines_any_visitors?
|
44
|
-
|
33
|
+
def extract!(query)
|
45
34
|
@query = query
|
35
|
+
|
46
36
|
return unless query.valid?
|
47
|
-
return if respond_to?(:skip_extraction?) && skip_extraction?(query)
|
48
|
-
return unless @ctx_namespace = query.context.namespace(CONTEXT_NAMESPACE)
|
49
37
|
return unless query.irep_selection
|
50
38
|
|
51
|
-
before_query_extracted(query, query.context) if respond_to?(:before_query_extracted)
|
52
39
|
extract_query
|
53
40
|
|
41
|
+
used_variables = extract_used_variables
|
42
|
+
|
54
43
|
query.operations.each_value do |operation|
|
55
|
-
extract_variables(operation)
|
44
|
+
extract_variables(operation, used_variables)
|
56
45
|
end
|
57
46
|
|
58
47
|
extract_node(query.irep_selection)
|
59
48
|
extract_batch_loaders
|
60
|
-
|
61
|
-
after_query_teardown(query) if respond_to?(:after_query_teardown)
|
62
|
-
rescue StandardError => ex
|
63
|
-
handle_extraction_exception(ex)
|
64
|
-
end
|
65
|
-
|
66
|
-
def instrument(type, field)
|
67
|
-
return field unless respond_to?(:field_extracted)
|
68
|
-
return field if type.introspection?
|
69
|
-
|
70
|
-
old_resolve_proc = field.resolve_proc
|
71
|
-
new_resolve_proc = ->(obj, args, ctx) do
|
72
|
-
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
73
|
-
result = old_resolve_proc.call(obj, args, ctx)
|
74
|
-
|
75
|
-
begin
|
76
|
-
next result if respond_to?(:skip_field_resolution_timing?) &&
|
77
|
-
skip_field_resolution_timing?(query, ctx)
|
78
|
-
|
79
|
-
end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
80
|
-
|
81
|
-
ns = ctx.namespace(CONTEXT_NAMESPACE)
|
82
|
-
|
83
|
-
ns[TIMING_CACHE_KEY][ctx.ast_node] ||= []
|
84
|
-
ns[TIMING_CACHE_KEY][ctx.ast_node] << end_time - start_time
|
85
|
-
|
86
|
-
result
|
87
|
-
rescue StandardError => ex
|
88
|
-
handle_extraction_exception(ex)
|
89
|
-
result
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
field.redefine { resolve(new_resolve_proc) }
|
94
49
|
end
|
95
50
|
|
96
51
|
def extractor_defines_any_visitors?
|
97
|
-
|
98
|
-
respond_to?(:
|
99
|
-
respond_to?(:
|
100
|
-
respond_to?(:
|
101
|
-
respond_to?(:
|
102
|
-
respond_to?(:
|
52
|
+
[self, instrumentor].any? do |extractor_definer|
|
53
|
+
extractor_definer.respond_to?(:query_extracted) ||
|
54
|
+
extractor_definer.respond_to?(:field_extracted) ||
|
55
|
+
extractor_definer.respond_to?(:argument_extracted) ||
|
56
|
+
extractor_definer.respond_to?(:variable_extracted) ||
|
57
|
+
extractor_definer.respond_to?(:batch_loaded_field_extracted) ||
|
58
|
+
extractor_definer.respond_to?(:before_query_extracted)
|
59
|
+
end
|
103
60
|
end
|
104
61
|
|
105
62
|
def handle_extraction_exception(ex)
|
106
63
|
raise ex
|
107
64
|
end
|
108
65
|
|
66
|
+
private
|
67
|
+
|
109
68
|
def extract_batch_loaders
|
110
|
-
return unless
|
69
|
+
return unless batch_loaded_field_extracted_method = extraction_method(:batch_loaded_field_extracted)
|
111
70
|
|
112
71
|
TimedBatchExecutor.timings.each do |key, resolve_meta|
|
113
72
|
key, identifiers = TimedBatchExecutor.serialize_loader_key(key)
|
114
73
|
|
115
|
-
|
74
|
+
batch_loaded_field_extracted_method.call(
|
116
75
|
{
|
117
76
|
key: key,
|
118
77
|
identifiers: identifiers,
|
@@ -131,19 +90,17 @@ module GraphQLMetrics
|
|
131
90
|
end
|
132
91
|
|
133
92
|
def extract_query
|
134
|
-
return unless
|
93
|
+
return unless query_extracted_method = extraction_method(:query_extracted)
|
135
94
|
|
136
|
-
start_time =
|
137
|
-
|
95
|
+
start_time, end_time = instrumentor.after_query_start_and_end_time
|
96
|
+
duration = start_time && end_time ? end_time - start_time : nil
|
138
97
|
|
139
|
-
|
140
|
-
|
141
|
-
query_extracted(
|
98
|
+
query_extracted_method.call(
|
142
99
|
{
|
143
100
|
query_string: query.document.to_query_string,
|
144
101
|
operation_type: query.selected_operation.operation_type,
|
145
102
|
operation_name: query.selected_operation_name,
|
146
|
-
duration:
|
103
|
+
duration: duration
|
147
104
|
},
|
148
105
|
{
|
149
106
|
query: query,
|
@@ -156,20 +113,22 @@ module GraphQLMetrics
|
|
156
113
|
end
|
157
114
|
|
158
115
|
def extract_field(irep_node)
|
159
|
-
return unless
|
116
|
+
return unless field_extracted_method = extraction_method(:field_extracted)
|
160
117
|
return unless irep_node.definition
|
161
118
|
|
162
|
-
|
119
|
+
resolver_times = instrumentor.after_query_resolver_times(irep_node.ast_node)
|
120
|
+
|
121
|
+
field_extracted_method.call(
|
163
122
|
{
|
164
123
|
type_name: irep_node.owner_type.name,
|
165
124
|
field_name: irep_node.definition.name,
|
166
125
|
deprecated: irep_node.definition.deprecation_reason.present?,
|
167
|
-
resolver_times:
|
126
|
+
resolver_times: resolver_times || [],
|
168
127
|
},
|
169
128
|
{
|
170
129
|
irep_node: irep_node,
|
171
130
|
query: query,
|
172
|
-
ctx_namespace: ctx_namespace
|
131
|
+
ctx_namespace: instrumentor.ctx_namespace
|
173
132
|
}
|
174
133
|
)
|
175
134
|
|
@@ -178,9 +137,9 @@ module GraphQLMetrics
|
|
178
137
|
end
|
179
138
|
|
180
139
|
def extract_argument(value, irep_node, types)
|
181
|
-
return unless
|
140
|
+
return unless argument_extracted_method = extraction_method(:argument_extracted)
|
182
141
|
|
183
|
-
|
142
|
+
argument_extracted_method.call(
|
184
143
|
{
|
185
144
|
name: value.definition.expose_as,
|
186
145
|
type: value.definition.type.unwrap.to_s,
|
@@ -200,8 +159,8 @@ module GraphQLMetrics
|
|
200
159
|
handle_extraction_exception(ex)
|
201
160
|
end
|
202
161
|
|
203
|
-
def extract_variables(operation)
|
204
|
-
return unless
|
162
|
+
def extract_variables(operation, used_variables)
|
163
|
+
return unless variable_extracted_method = extraction_method(:variable_extracted)
|
205
164
|
|
206
165
|
operation.variables.each do |variable|
|
207
166
|
value_provided = query.provided_variables.key?(variable.name)
|
@@ -217,14 +176,15 @@ module GraphQLMetrics
|
|
217
176
|
|
218
177
|
default_used = !value_provided && default_value_type != IMPLICIT_NULL
|
219
178
|
|
220
|
-
|
179
|
+
variable_extracted_method.call(
|
221
180
|
{
|
222
181
|
operation_name: operation.name,
|
223
182
|
unwrapped_type_name: unwrapped_type(variable.type),
|
224
183
|
type: variable.type.to_query_string,
|
225
184
|
default_value_type: default_value_type,
|
226
185
|
provided_value: value_provided,
|
227
|
-
default_used: default_used
|
186
|
+
default_used: default_used,
|
187
|
+
used_in_operation: used_variables.include?(variable.name)
|
228
188
|
},
|
229
189
|
{
|
230
190
|
query: query
|
@@ -235,6 +195,10 @@ module GraphQLMetrics
|
|
235
195
|
handle_extraction_exception(ex)
|
236
196
|
end
|
237
197
|
|
198
|
+
def extract_used_variables
|
199
|
+
query.irep_selection.ast_node.variables.each_with_object(Set.new) { |v, set| set << v.name }
|
200
|
+
end
|
201
|
+
|
238
202
|
def extract_arguments(irep_node)
|
239
203
|
return unless irep_node.ast_node.is_a?(GraphQL::Language::Nodes::Field)
|
240
204
|
|
@@ -292,5 +256,22 @@ module GraphQLMetrics
|
|
292
256
|
rescue StandardError => ex
|
293
257
|
handle_extraction_exception(ex)
|
294
258
|
end
|
259
|
+
|
260
|
+
def extraction_method(method_name)
|
261
|
+
@extraction_method_cache ||= {}
|
262
|
+
return @extraction_method_cache[method_name] if @extraction_method_cache.has_key?(method_name)
|
263
|
+
|
264
|
+
method = if respond_to?(method_name)
|
265
|
+
method(method_name)
|
266
|
+
elsif instrumentor && instrumentor.respond_to?(method_name)
|
267
|
+
instrumentor.method(method_name)
|
268
|
+
else
|
269
|
+
nil
|
270
|
+
end
|
271
|
+
|
272
|
+
method.tap do |method|
|
273
|
+
@extraction_method_cache[method_name] = method
|
274
|
+
end
|
275
|
+
end
|
295
276
|
end
|
296
277
|
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQLMetrics
|
4
|
+
class Instrumentation
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
CONTEXT_NAMESPACE = :extracted_metrics
|
8
|
+
TIMING_CACHE_KEY = :timing_cache
|
9
|
+
START_TIME_KEY = :query_start_time
|
10
|
+
|
11
|
+
attr_reader :ctx_namespace, :query
|
12
|
+
def_delegators :extractor, :extractor_defines_any_visitors?
|
13
|
+
|
14
|
+
def self.use(schema_definition)
|
15
|
+
instrumentation = self.new
|
16
|
+
return unless instrumentation.extractor_defines_any_visitors?
|
17
|
+
|
18
|
+
instrumentation.setup_instrumentation(schema_definition)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.current_time
|
22
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
23
|
+
end
|
24
|
+
|
25
|
+
def use(schema_definition)
|
26
|
+
return unless extractor_defines_any_visitors?
|
27
|
+
extractor.setup_instrumentation(schema_definition)
|
28
|
+
end
|
29
|
+
|
30
|
+
def setup_instrumentation(schema_definition)
|
31
|
+
schema_definition.instrument(:query, self)
|
32
|
+
schema_definition.instrument(:field, self)
|
33
|
+
end
|
34
|
+
|
35
|
+
def extractor
|
36
|
+
@extractor ||= Extractor.new(self)
|
37
|
+
end
|
38
|
+
|
39
|
+
def before_query(query)
|
40
|
+
return unless extractor_defines_any_visitors?
|
41
|
+
|
42
|
+
ns = query.context.namespace(CONTEXT_NAMESPACE)
|
43
|
+
ns[TIMING_CACHE_KEY] = {}
|
44
|
+
ns[START_TIME_KEY] = self.class.current_time
|
45
|
+
rescue StandardError => ex
|
46
|
+
extractor.handle_extraction_exception(ex)
|
47
|
+
end
|
48
|
+
|
49
|
+
def after_query(query)
|
50
|
+
@query = query
|
51
|
+
|
52
|
+
return unless extractor_defines_any_visitors?
|
53
|
+
return if respond_to?(:skip_extraction?) && skip_extraction?(query)
|
54
|
+
return unless @ctx_namespace = query.context.namespace(CONTEXT_NAMESPACE)
|
55
|
+
|
56
|
+
before_query_extracted(query, query.context) if respond_to?(:before_query_extracted)
|
57
|
+
|
58
|
+
extractor.extract!(query)
|
59
|
+
|
60
|
+
after_query_teardown(query) if respond_to?(:after_query_teardown)
|
61
|
+
rescue StandardError => ex
|
62
|
+
extractor.handle_extraction_exception(ex)
|
63
|
+
end
|
64
|
+
|
65
|
+
def instrument(type, field)
|
66
|
+
return field unless respond_to?(:field_extracted) || extractor.respond_to?(:field_extracted)
|
67
|
+
return field if type.introspection?
|
68
|
+
|
69
|
+
old_resolve_proc = field.resolve_proc
|
70
|
+
new_resolve_proc = ->(obj, args, ctx) do
|
71
|
+
start_time = self.class.current_time
|
72
|
+
result = old_resolve_proc.call(obj, args, ctx)
|
73
|
+
|
74
|
+
begin
|
75
|
+
next result if respond_to?(:skip_field_resolution_timing?) &&
|
76
|
+
skip_field_resolution_timing?(query, ctx)
|
77
|
+
|
78
|
+
end_time = self.class.current_time
|
79
|
+
|
80
|
+
ns = ctx.namespace(CONTEXT_NAMESPACE)
|
81
|
+
|
82
|
+
ns[TIMING_CACHE_KEY][ctx.ast_node] ||= []
|
83
|
+
ns[TIMING_CACHE_KEY][ctx.ast_node] << end_time - start_time
|
84
|
+
|
85
|
+
result
|
86
|
+
rescue StandardError => ex
|
87
|
+
extractor.handle_extraction_exception(ex)
|
88
|
+
result
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
field.redefine { resolve(new_resolve_proc) }
|
93
|
+
end
|
94
|
+
|
95
|
+
def after_query_resolver_times(ast_node)
|
96
|
+
ctx_namespace.dig(Instrumentation::TIMING_CACHE_KEY).fetch(ast_node, [])
|
97
|
+
end
|
98
|
+
|
99
|
+
def after_query_start_and_end_time
|
100
|
+
start_time = ctx_namespace[Instrumentation::START_TIME_KEY]
|
101
|
+
return unless start_time
|
102
|
+
|
103
|
+
[start_time, self.class.current_time]
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -55,7 +55,7 @@ module GraphQLMetrics
|
|
55
55
|
|
56
56
|
def resolve(loader)
|
57
57
|
@resolve_meta = {
|
58
|
-
start_time:
|
58
|
+
start_time: Instrumentation.current_time,
|
59
59
|
current_loader: loader,
|
60
60
|
perform_queue_sizes: loader.send(:queue).size
|
61
61
|
}
|
@@ -66,7 +66,7 @@ module GraphQLMetrics
|
|
66
66
|
def around_promise_callbacks
|
67
67
|
return super unless @resolve_meta
|
68
68
|
|
69
|
-
end_time =
|
69
|
+
end_time = Instrumentation.current_time
|
70
70
|
|
71
71
|
TIMINGS[@resolve_meta[:current_loader].loader_key] ||= { times: [], perform_queue_sizes: [] }
|
72
72
|
TIMINGS[@resolve_meta[:current_loader].loader_key][:times] << end_time - @resolve_meta[:start_time]
|
data/lib/graphql_metrics.rb
CHANGED
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
|
+
version: 2.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: 2018-
|
11
|
+
date: 2018-11-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -164,6 +164,20 @@ dependencies:
|
|
164
164
|
- - ">="
|
165
165
|
- !ruby/object:Gem::Version
|
166
166
|
version: '0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: minitest-focus
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ">="
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
167
181
|
description: |
|
168
182
|
Extract as much much detail as you want from GraphQL queries, served up from your Ruby app and the `graphql` gem.
|
169
183
|
Compatible with the `graphql-batch` gem, to extract batch-loaded fields resolution timings.
|
@@ -186,6 +200,7 @@ files:
|
|
186
200
|
- graphql_metrics.gemspec
|
187
201
|
- lib/graphql_metrics.rb
|
188
202
|
- lib/graphql_metrics/extractor.rb
|
203
|
+
- lib/graphql_metrics/instrumentation.rb
|
189
204
|
- lib/graphql_metrics/timed_batch_executor.rb
|
190
205
|
- lib/graphql_metrics/version.rb
|
191
206
|
homepage: https://github.com/Shopify/graphql-metrics
|
@@ -208,7 +223,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
208
223
|
version: '0'
|
209
224
|
requirements: []
|
210
225
|
rubyforge_project:
|
211
|
-
rubygems_version: 2.5.2
|
226
|
+
rubygems_version: 2.5.2.3
|
212
227
|
signing_key:
|
213
228
|
specification_version: 4
|
214
229
|
summary: GraphQL Metrics Extractor
|