graphql-metrics 3.0.1 → 4.0.2
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/.rubocop-http---shopify-github-io-ruby-style-guide-rubocop-yml +1 -8
- data/CHANGELOG.md +23 -0
- data/Gemfile.lock +4 -4
- data/README.md +12 -2
- data/graphql_metrics.gemspec +1 -2
- data/lib/graphql/metrics/analyzer.rb +28 -20
- data/lib/graphql/metrics/instrumentation.rb +31 -15
- data/lib/graphql/metrics/version.rb +1 -1
- metadata +12 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2af911e9031c446b604b9c296caef2c89f00cb968ecf15350a6a196d6cab08d7
|
4
|
+
data.tar.gz: 94e65ae882fd6222bf3b4ed9395f32dfc395ec00c85e061a458cb5fe54b9dc67
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aa3ffe4208e67594c3e65786a0a0477cdcee71506dea37988db388433eb155175d87f92e30082931e5e8c493937876e9a5a4043327b74e9e17bc474d58bd4ed9
|
7
|
+
data.tar.gz: e876e9f5907cff0863a6ead3e319f34ab238b1904d0f72c8874420fdf50b5cb2941cb0f77fcfbefac35968c2bb86ec6364c5e338842dcee591eb165ee2ed14d6
|
@@ -76,13 +76,6 @@ Style/BlockDelimiters:
|
|
76
76
|
- proc
|
77
77
|
- it
|
78
78
|
|
79
|
-
Style/BracesAroundHashParameters:
|
80
|
-
EnforcedStyle: no_braces
|
81
|
-
SupportedStyles:
|
82
|
-
- braces
|
83
|
-
- no_braces
|
84
|
-
- context_dependent
|
85
|
-
|
86
79
|
Layout/CaseIndentation:
|
87
80
|
EnforcedStyle: end
|
88
81
|
SupportedStyles:
|
@@ -509,7 +502,7 @@ Style/WhileUntilModifier:
|
|
509
502
|
Metrics/BlockNesting:
|
510
503
|
Max: 3
|
511
504
|
|
512
|
-
|
505
|
+
Layout/LineLength:
|
513
506
|
Max: 120
|
514
507
|
AllowHeredoc: true
|
515
508
|
AllowURI: true
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,26 @@
|
|
1
|
+
4.0.2
|
2
|
+
-----
|
3
|
+
- [25](https://github.com/Shopify/graphql-metrics/pull/25) Safely handle interrupted runtime metrics.
|
4
|
+
|
5
|
+
4.0.1
|
6
|
+
-----
|
7
|
+
- [24](https://github.com/Shopify/graphql-metrics/pull/24) Safely call `arguments_for` to handle arguments which may
|
8
|
+
raise `ExecutionError`s in their `prepare` methods.
|
9
|
+
|
10
|
+
4.0.0
|
11
|
+
-----
|
12
|
+
- [23](https://github.com/Shopify/graphql-metrics/pull/23) graphql-ruby 1.10.8+ compatibility
|
13
|
+
|
14
|
+
3.0.3
|
15
|
+
-----
|
16
|
+
|
17
|
+
- [#22](https://github.com/Shopify/graphql-metrics/pull/22) Optimization: use hash assignment over merge
|
18
|
+
|
19
|
+
3.0.2
|
20
|
+
-----
|
21
|
+
|
22
|
+
- [#21](https://github.com/Shopify/graphql-metrics/pull/21) Optimize empty document check
|
23
|
+
|
1
24
|
3.0.1
|
2
25
|
-----
|
3
26
|
|
data/Gemfile.lock
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
graphql-metrics (
|
4
|
+
graphql-metrics (4.0.1)
|
5
5
|
concurrent-ruby (~> 1.1.0)
|
6
|
-
graphql (>= 1.
|
6
|
+
graphql (>= 1.10.8)
|
7
7
|
|
8
8
|
GEM
|
9
9
|
remote: https://rubygems.org/
|
@@ -19,8 +19,8 @@ GEM
|
|
19
19
|
diffy (3.3.0)
|
20
20
|
fakeredis (0.7.0)
|
21
21
|
redis (>= 3.2, < 5.0)
|
22
|
-
graphql (1.
|
23
|
-
graphql-batch (0.4.
|
22
|
+
graphql (1.10.8)
|
23
|
+
graphql-batch (0.4.2)
|
24
24
|
graphql (>= 1.3, < 2)
|
25
25
|
promise.rb (~> 0.7.2)
|
26
26
|
hashdiff (1.0.0)
|
data/README.md
CHANGED
@@ -5,6 +5,8 @@
|
|
5
5
|
Extract as much much detail as you want from GraphQL queries, served up from your Ruby app and the [`graphql` gem](https://github.com/rmosolgo/graphql-ruby).
|
6
6
|
Compatible with the [`graphql-batch` gem](https://github.com/Shopify/graphql-batch), to extract batch-loaded fields resolution timings.
|
7
7
|
|
8
|
+
Be sure to read the [CHANGELOG](CHANGELOG.md) to stay updated on feature additions, breaking changes made to this gem.
|
9
|
+
|
8
10
|
## Installation
|
9
11
|
|
10
12
|
Add this line to your application's Gemfile:
|
@@ -42,6 +44,13 @@ etc.
|
|
42
44
|
|
43
45
|
What you do with these captured metrics is up to you!
|
44
46
|
|
47
|
+
**NOTE**: Runtime metrics on for queries (like `query_duration`, `parsing_start_time_offset` etc.) as well as field
|
48
|
+
resolver timings (like `resolver_timings`, `lazy_resolver_timings`) may not be present in the extracted `metrics` hash,
|
49
|
+
even if you opt to collect them by using `GraphQL::Metrics::Analyzer` and `GraphQL::Metrics::Tracer`.
|
50
|
+
|
51
|
+
More specifically, if any non-`graphql-ruby` gem-related exceptions occur in your application during query document
|
52
|
+
parsing and validation runtime metrics will not be added to the `metrics` hash.
|
53
|
+
|
45
54
|
### Define your own analyzer subclass
|
46
55
|
|
47
56
|
```ruby
|
@@ -172,9 +181,10 @@ class Schema < GraphQL::Schema
|
|
172
181
|
use GraphQL::Execution::Interpreter # Required.
|
173
182
|
use GraphQL::Analysis::AST # Required.
|
174
183
|
|
175
|
-
instrument :query, GraphQL::Metrics::Instrumentation.new
|
176
184
|
query_analyzer SimpleAnalyzer
|
177
|
-
|
185
|
+
|
186
|
+
instrument :query, GraphQL::Metrics::Instrumentation.new # Both of these are required if either is used.
|
187
|
+
tracer GraphQL::Metrics::Tracer.new # <-- Note!
|
178
188
|
|
179
189
|
use GraphQL::Batch # Optional, but highly recommended. See https://github.com/Shopify/graphql-batch/.
|
180
190
|
end
|
data/graphql_metrics.gemspec
CHANGED
@@ -30,9 +30,8 @@ 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
34
|
spec.add_runtime_dependency "concurrent-ruby", "~> 1.1.0"
|
34
|
-
spec.add_runtime_dependency "graphql", ">= 1.9.5", "< 1.10.0"
|
35
|
-
|
36
35
|
spec.add_development_dependency "rake", "~> 10.0"
|
37
36
|
spec.add_development_dependency "minitest", "~> 5.0"
|
38
37
|
spec.add_development_dependency 'graphql-batch'
|
@@ -35,14 +35,21 @@ module GraphQL
|
|
35
35
|
return if visitor.field_definition.introspection?
|
36
36
|
return if query.context[SKIP_FIELD_AND_ARGUMENT_METRICS]
|
37
37
|
|
38
|
-
#
|
39
|
-
|
40
|
-
|
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
|
41
48
|
|
42
49
|
static_metrics = {
|
43
50
|
field_name: node.name,
|
44
|
-
return_type_name: visitor.type_definition.
|
45
|
-
parent_type_name: visitor.parent_type_definition.
|
51
|
+
return_type_name: visitor.type_definition.graphql_name,
|
52
|
+
parent_type_name: visitor.parent_type_definition.graphql_name,
|
46
53
|
deprecated: visitor.field_definition.deprecation_reason.present?,
|
47
54
|
path: visitor.response_path,
|
48
55
|
}
|
@@ -54,21 +61,22 @@ module GraphQL
|
|
54
61
|
end
|
55
62
|
end
|
56
63
|
|
57
|
-
def
|
64
|
+
def extract_fields(with_runtime_metrics: true)
|
58
65
|
return if query.context[SKIP_FIELD_AND_ARGUMENT_METRICS]
|
59
66
|
|
60
67
|
ns = query.context.namespace(CONTEXT_NAMESPACE)
|
61
68
|
|
62
69
|
@static_field_metrics.each do |static_metrics|
|
63
|
-
resolver_timings = ns[GraphQL::Metrics::INLINE_FIELD_TIMINGS][static_metrics[:path]]
|
64
|
-
lazy_field_timings = ns[GraphQL::Metrics::LAZY_FIELD_TIMINGS][static_metrics[:path]]
|
65
70
|
|
66
|
-
|
67
|
-
resolver_timings
|
68
|
-
lazy_resolver_timings
|
69
|
-
)
|
71
|
+
if with_runtime_metrics
|
72
|
+
resolver_timings = ns[GraphQL::Metrics::INLINE_FIELD_TIMINGS][static_metrics[:path]]
|
73
|
+
lazy_resolver_timings = ns[GraphQL::Metrics::LAZY_FIELD_TIMINGS][static_metrics[:path]]
|
70
74
|
|
71
|
-
|
75
|
+
static_metrics[:resolver_timings] = resolver_timings || []
|
76
|
+
static_metrics[:lazy_resolver_timings] = lazy_resolver_timings || []
|
77
|
+
end
|
78
|
+
|
79
|
+
field_extracted(static_metrics)
|
72
80
|
end
|
73
81
|
end
|
74
82
|
|
@@ -95,16 +103,16 @@ module GraphQL
|
|
95
103
|
argument.each_value do |a|
|
96
104
|
extract_arguments(a, field_defn, parent_input_object)
|
97
105
|
end
|
98
|
-
when ::GraphQL::
|
106
|
+
when ::GraphQL::Execution::Interpreter::Arguments
|
99
107
|
argument.each_value do |arg_val|
|
100
108
|
extract_arguments(arg_val, field_defn, parent_input_object)
|
101
109
|
end
|
102
|
-
when ::GraphQL::
|
110
|
+
when ::GraphQL::Execution::Interpreter::ArgumentValue
|
103
111
|
extract_argument(argument, field_defn, parent_input_object)
|
104
112
|
extract_arguments(argument.value, field_defn, parent_input_object)
|
105
113
|
when ::GraphQL::Schema::InputObject
|
106
114
|
input_object_argument_values = argument.arguments.argument_values.values
|
107
|
-
parent_input_object = input_object_argument_values.first&.definition&.
|
115
|
+
parent_input_object = input_object_argument_values.first&.definition&.owner
|
108
116
|
|
109
117
|
extract_arguments(input_object_argument_values, field_defn, parent_input_object)
|
110
118
|
end
|
@@ -112,10 +120,10 @@ module GraphQL
|
|
112
120
|
|
113
121
|
def extract_argument(value, field_defn, parent_input_object = nil)
|
114
122
|
static_metrics = {
|
115
|
-
argument_name: value.definition.
|
116
|
-
argument_type_name: value.definition.type.unwrap.
|
117
|
-
parent_field_name: field_defn.
|
118
|
-
parent_field_type_name: field_defn.
|
123
|
+
argument_name: value.definition.graphql_name,
|
124
|
+
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,
|
119
127
|
parent_input_object_type: parent_input_object&.graphql_name,
|
120
128
|
default_used: value.default_used?,
|
121
129
|
value_is_null: value.value.nil?,
|
@@ -25,29 +25,45 @@ module GraphQL
|
|
25
25
|
return if query.context[GraphQL::Metrics::SKIP_GRAPHQL_METRICS_ANALYSIS]
|
26
26
|
|
27
27
|
ns = query.context.namespace(CONTEXT_NAMESPACE)
|
28
|
+
analyzer = ns[GraphQL::Metrics::ANALYZER_INSTANCE_KEY]
|
28
29
|
|
29
|
-
|
30
|
-
|
31
|
-
|
30
|
+
if runtime_metrics_interrupted?(ns)
|
31
|
+
# If runtime metrics were interrupted, then it's most likely that the application raised an exception and that
|
32
|
+
# query parsing (which is instrumented by GraphQL::Metrics::Tracer) was abruptly stopped.
|
33
|
+
#
|
34
|
+
# In this scenario, we still attempt to log whatever static query metrics we've collected, with runtime
|
35
|
+
# metrics (like query, field resolver timings) excluded.
|
36
|
+
analyzer.extract_fields(with_runtime_metrics: false)
|
37
|
+
analyzer.extract_query
|
38
|
+
else
|
39
|
+
query_duration = GraphQL::Metrics.current_time_monotonic - ns[GraphQL::Metrics::QUERY_START_TIME_MONOTONIC]
|
32
40
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
+
runtime_query_metrics = {
|
42
|
+
query_start_time: ns[GraphQL::Metrics::QUERY_START_TIME],
|
43
|
+
query_duration: query_duration,
|
44
|
+
parsing_start_time_offset: ns[GraphQL::Metrics::PARSING_START_TIME_OFFSET],
|
45
|
+
parsing_duration: ns[GraphQL::Metrics::PARSING_DURATION],
|
46
|
+
validation_start_time_offset: ns[GraphQL::Metrics::VALIDATION_START_TIME_OFFSET],
|
47
|
+
validation_duration: ns[GraphQL::Metrics::VALIDATION_DURATION],
|
48
|
+
}
|
41
49
|
|
42
|
-
|
43
|
-
|
44
|
-
|
50
|
+
analyzer.extract_fields
|
51
|
+
analyzer.extract_query(runtime_query_metrics: runtime_query_metrics)
|
52
|
+
end
|
45
53
|
end
|
46
54
|
|
47
55
|
private
|
48
56
|
|
49
57
|
def query_present_and_valid?(query)
|
50
|
-
|
58
|
+
# Check for selected_operation as well for graphql 1.9 compatibility
|
59
|
+
# which did not reject "empty" documents in its parser.
|
60
|
+
query.valid? && !query.selected_operation.nil?
|
61
|
+
end
|
62
|
+
|
63
|
+
def runtime_metrics_interrupted?(context_namespace)
|
64
|
+
# NOTE: The start time stored at `ns[GraphQL::Metrics::QUERY_START_TIME_MONOTONIC]` is captured during query
|
65
|
+
# parsing, which occurs before `Instrumentation#before_query`.
|
66
|
+
context_namespace.key?(GraphQL::Metrics::QUERY_START_TIME_MONOTONIC) == false
|
51
67
|
end
|
52
68
|
end
|
53
69
|
end
|
metadata
CHANGED
@@ -1,49 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graphql-metrics
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 4.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Christopher Butcher
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-06-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: graphql
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 1.
|
19
|
+
version: 1.10.8
|
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.
|
26
|
+
version: 1.10.8
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: concurrent-ruby
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: 1.9.5
|
34
|
-
- - "<"
|
31
|
+
- - "~>"
|
35
32
|
- !ruby/object:Gem::Version
|
36
|
-
version: 1.
|
33
|
+
version: 1.1.0
|
37
34
|
type: :runtime
|
38
35
|
prerelease: false
|
39
36
|
version_requirements: !ruby/object:Gem::Requirement
|
40
37
|
requirements:
|
41
|
-
- - "
|
42
|
-
- !ruby/object:Gem::Version
|
43
|
-
version: 1.9.5
|
44
|
-
- - "<"
|
38
|
+
- - "~>"
|
45
39
|
- !ruby/object:Gem::Version
|
46
|
-
version: 1.
|
40
|
+
version: 1.1.0
|
47
41
|
- !ruby/object:Gem::Dependency
|
48
42
|
name: rake
|
49
43
|
requirement: !ruby/object:Gem::Requirement
|