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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cf09941ca4f9ed80c7d1080c6c121fa1cb1ac66ed55735dcb74cec0bbbe3a8a2
4
- data.tar.gz: 18a8b3feafc4bb5be6c065cf05b91da34fee5c826e77d67ad14ea48a23630459
3
+ metadata.gz: 2af911e9031c446b604b9c296caef2c89f00cb968ecf15350a6a196d6cab08d7
4
+ data.tar.gz: 94e65ae882fd6222bf3b4ed9395f32dfc395ec00c85e061a458cb5fe54b9dc67
5
5
  SHA512:
6
- metadata.gz: 4e11ef76ccd1d038fab97151f40418e5386b5d2d80b45ca1238f5c0ebf60b6a593980bde4efe4fc60c63b3c590bc7da803e8fcb04ce1c9906fa0cf6edecb0e74
7
- data.tar.gz: 32684bc8c1e3fac2e73b4efad7969bb84c38fc34a89510e450e0b423b5ec66104a8cd0fea016b91a47e26299a01df0b09da82597a4fd5dd763d64de4677c04f7
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
- Metrics/LineLength:
505
+ Layout/LineLength:
513
506
  Max: 120
514
507
  AllowHeredoc: true
515
508
  AllowURI: true
@@ -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
 
@@ -1,9 +1,9 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- graphql-metrics (3.0.1)
4
+ graphql-metrics (4.0.1)
5
5
  concurrent-ruby (~> 1.1.0)
6
- graphql (>= 1.9.5, < 1.10.0)
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.9.19)
23
- graphql-batch (0.4.1)
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
- tracer GraphQL::Metrics::Tracer.new
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
@@ -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
- # NOTE: @rmosolgo "I think it could be reduced to `arguments = visitor.arguments_for(ast_node)`"
39
- arguments = visitor.arguments_for(node, visitor.field_definition)
40
- extract_arguments(arguments.argument_values.values, visitor.field_definition)
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.name,
45
- parent_type_name: visitor.parent_type_definition.name,
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 extract_fields_with_runtime_metrics
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
- metrics = static_metrics.merge(
67
- resolver_timings: resolver_timings || [],
68
- lazy_resolver_timings: lazy_field_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
- field_extracted(metrics)
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::Query::Arguments
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::Query::Arguments::ArgumentValue
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&.metadata&.fetch(:type_class, nil)&.owner
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.expose_as,
116
- argument_type_name: value.definition.type.unwrap.to_s,
117
- parent_field_name: field_defn.name,
118
- parent_field_type_name: field_defn.metadata[:type_class].owner.graphql_name,
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
- # NOTE: The start time stored at `ns[GraphQL::Metrics::QUERY_START_TIME_MONOTONIC]` is captured during query
30
- # parsing, which occurs before `Instrumentation#before_query`.
31
- query_duration = GraphQL::Metrics.current_time_monotonic - ns[GraphQL::Metrics::QUERY_START_TIME_MONOTONIC]
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
- runtime_query_metrics = {
34
- query_start_time: ns[GraphQL::Metrics::QUERY_START_TIME],
35
- query_duration: query_duration,
36
- parsing_start_time_offset: ns[GraphQL::Metrics::PARSING_START_TIME_OFFSET],
37
- parsing_duration: ns[GraphQL::Metrics::PARSING_DURATION],
38
- validation_start_time_offset: ns[GraphQL::Metrics::VALIDATION_START_TIME_OFFSET],
39
- validation_duration: ns[GraphQL::Metrics::VALIDATION_DURATION],
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
- analyzer = ns[GraphQL::Metrics::ANALYZER_INSTANCE_KEY]
43
- analyzer.extract_fields_with_runtime_metrics
44
- analyzer.extract_query(runtime_query_metrics: runtime_query_metrics)
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
- query.valid? && query.document.to_query_string.present?
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module GraphQL
4
4
  module Metrics
5
- VERSION = "3.0.1"
5
+ VERSION = "4.0.2"
6
6
  end
7
7
  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: 3.0.1
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-03-16 00:00:00.000000000 Z
11
+ date: 2020-06-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: concurrent-ruby
14
+ name: graphql
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 1.1.0
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.1.0
26
+ version: 1.10.8
27
27
  - !ruby/object:Gem::Dependency
28
- name: graphql
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.10.0
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.10.0
40
+ version: 1.1.0
47
41
  - !ruby/object:Gem::Dependency
48
42
  name: rake
49
43
  requirement: !ruby/object:Gem::Requirement