graphql 2.4.3 → 2.5.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.
Files changed (166) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/analysis/analyzer.rb +2 -1
  3. data/lib/graphql/analysis/visitor.rb +38 -41
  4. data/lib/graphql/analysis.rb +15 -12
  5. data/lib/graphql/autoload.rb +38 -0
  6. data/lib/graphql/backtrace/table.rb +118 -55
  7. data/lib/graphql/backtrace.rb +1 -19
  8. data/lib/graphql/current.rb +6 -1
  9. data/lib/graphql/dashboard/detailed_traces.rb +47 -0
  10. data/lib/graphql/dashboard/installable.rb +22 -0
  11. data/lib/graphql/dashboard/limiters.rb +93 -0
  12. data/lib/graphql/dashboard/operation_store.rb +199 -0
  13. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.css +6 -0
  14. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js +7 -0
  15. data/lib/graphql/dashboard/statics/charts.min.css +1 -0
  16. data/lib/graphql/dashboard/statics/dashboard.css +30 -0
  17. data/lib/graphql/dashboard/statics/dashboard.js +143 -0
  18. data/lib/graphql/dashboard/statics/header-icon.png +0 -0
  19. data/lib/graphql/dashboard/statics/icon.png +0 -0
  20. data/lib/graphql/dashboard/subscriptions.rb +96 -0
  21. data/lib/graphql/dashboard/views/graphql/dashboard/detailed_traces/traces/index.html.erb +45 -0
  22. data/lib/graphql/dashboard/views/graphql/dashboard/landings/show.html.erb +18 -0
  23. data/lib/graphql/dashboard/views/graphql/dashboard/limiters/limiters/show.html.erb +62 -0
  24. data/lib/graphql/dashboard/views/graphql/dashboard/not_installed.html.erb +18 -0
  25. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +23 -0
  26. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +21 -0
  27. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +69 -0
  28. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +7 -0
  29. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +39 -0
  30. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/show.html.erb +32 -0
  31. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/index.html.erb +81 -0
  32. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +71 -0
  33. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/subscriptions/show.html.erb +41 -0
  34. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/index.html.erb +55 -0
  35. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +40 -0
  36. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +108 -0
  37. data/lib/graphql/dashboard.rb +158 -0
  38. data/lib/graphql/dataloader/active_record_association_source.rb +64 -0
  39. data/lib/graphql/dataloader/active_record_source.rb +26 -0
  40. data/lib/graphql/dataloader/async_dataloader.rb +21 -9
  41. data/lib/graphql/dataloader/null_dataloader.rb +1 -1
  42. data/lib/graphql/dataloader/source.rb +3 -3
  43. data/lib/graphql/dataloader.rb +43 -14
  44. data/lib/graphql/execution/interpreter/resolve.rb +3 -3
  45. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +34 -4
  46. data/lib/graphql/execution/interpreter/runtime.rb +94 -51
  47. data/lib/graphql/execution/interpreter.rb +16 -7
  48. data/lib/graphql/execution/multiplex.rb +1 -5
  49. data/lib/graphql/introspection/directive_location_enum.rb +1 -1
  50. data/lib/graphql/invalid_name_error.rb +1 -1
  51. data/lib/graphql/invalid_null_error.rb +5 -15
  52. data/lib/graphql/language/cache.rb +13 -0
  53. data/lib/graphql/language/document_from_schema_definition.rb +8 -7
  54. data/lib/graphql/language/lexer.rb +11 -4
  55. data/lib/graphql/language/nodes.rb +3 -0
  56. data/lib/graphql/language/parser.rb +15 -8
  57. data/lib/graphql/language/printer.rb +8 -8
  58. data/lib/graphql/language/static_visitor.rb +37 -33
  59. data/lib/graphql/language/visitor.rb +59 -55
  60. data/lib/graphql/pagination/connection.rb +1 -1
  61. data/lib/graphql/query/context/scoped_context.rb +1 -1
  62. data/lib/graphql/query/context.rb +6 -5
  63. data/lib/graphql/query/variable_validation_error.rb +1 -1
  64. data/lib/graphql/query.rb +19 -23
  65. data/lib/graphql/railtie.rb +7 -0
  66. data/lib/graphql/schema/addition.rb +1 -1
  67. data/lib/graphql/schema/argument.rb +7 -8
  68. data/lib/graphql/schema/build_from_definition.rb +99 -53
  69. data/lib/graphql/schema/directive/flagged.rb +3 -1
  70. data/lib/graphql/schema/directive.rb +2 -2
  71. data/lib/graphql/schema/enum.rb +36 -1
  72. data/lib/graphql/schema/enum_value.rb +1 -1
  73. data/lib/graphql/schema/field/scope_extension.rb +1 -1
  74. data/lib/graphql/schema/field.rb +27 -13
  75. data/lib/graphql/schema/field_extension.rb +1 -1
  76. data/lib/graphql/schema/has_single_input_argument.rb +3 -1
  77. data/lib/graphql/schema/input_object.rb +77 -40
  78. data/lib/graphql/schema/interface.rb +3 -2
  79. data/lib/graphql/schema/loader.rb +1 -1
  80. data/lib/graphql/schema/member/has_arguments.rb +25 -17
  81. data/lib/graphql/schema/member/has_dataloader.rb +60 -0
  82. data/lib/graphql/schema/member/has_deprecation_reason.rb +15 -0
  83. data/lib/graphql/schema/member/has_directives.rb +4 -4
  84. data/lib/graphql/schema/member/has_fields.rb +19 -1
  85. data/lib/graphql/schema/member/has_interfaces.rb +5 -5
  86. data/lib/graphql/schema/member/has_validators.rb +1 -1
  87. data/lib/graphql/schema/member/scoped.rb +1 -1
  88. data/lib/graphql/schema/member/type_system_helpers.rb +1 -1
  89. data/lib/graphql/schema/member.rb +1 -0
  90. data/lib/graphql/schema/object.rb +25 -8
  91. data/lib/graphql/schema/relay_classic_mutation.rb +0 -1
  92. data/lib/graphql/schema/resolver.rb +12 -10
  93. data/lib/graphql/schema/subscription.rb +52 -6
  94. data/lib/graphql/schema/union.rb +1 -1
  95. data/lib/graphql/schema/validator/required_validator.rb +23 -6
  96. data/lib/graphql/schema/validator.rb +1 -1
  97. data/lib/graphql/schema/visibility/migration.rb +1 -0
  98. data/lib/graphql/schema/visibility/profile.rb +95 -243
  99. data/lib/graphql/schema/visibility/visit.rb +190 -0
  100. data/lib/graphql/schema/visibility.rb +169 -28
  101. data/lib/graphql/schema/warden.rb +18 -5
  102. data/lib/graphql/schema.rb +93 -44
  103. data/lib/graphql/static_validation/all_rules.rb +1 -1
  104. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +1 -1
  105. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +1 -1
  106. data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -1
  107. data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +1 -1
  108. data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -0
  109. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
  110. data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +26 -0
  111. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +1 -1
  112. data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +1 -1
  113. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +1 -1
  114. data/lib/graphql/static_validation/validation_context.rb +1 -0
  115. data/lib/graphql/static_validation/validator.rb +6 -1
  116. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -1
  117. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +12 -10
  118. data/lib/graphql/subscriptions/event.rb +12 -1
  119. data/lib/graphql/subscriptions/serialize.rb +1 -1
  120. data/lib/graphql/subscriptions.rb +1 -1
  121. data/lib/graphql/testing/helpers.rb +7 -4
  122. data/lib/graphql/tracing/active_support_notifications_trace.rb +14 -3
  123. data/lib/graphql/tracing/active_support_notifications_tracing.rb +1 -1
  124. data/lib/graphql/tracing/appoptics_trace.rb +9 -1
  125. data/lib/graphql/tracing/appoptics_tracing.rb +7 -0
  126. data/lib/graphql/tracing/appsignal_trace.rb +32 -55
  127. data/lib/graphql/tracing/appsignal_tracing.rb +2 -0
  128. data/lib/graphql/tracing/call_legacy_tracers.rb +66 -0
  129. data/lib/graphql/tracing/data_dog_trace.rb +46 -158
  130. data/lib/graphql/tracing/data_dog_tracing.rb +2 -0
  131. data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
  132. data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
  133. data/lib/graphql/tracing/detailed_trace.rb +93 -0
  134. data/lib/graphql/tracing/legacy_hooks_trace.rb +1 -0
  135. data/lib/graphql/tracing/legacy_trace.rb +4 -61
  136. data/lib/graphql/tracing/monitor_trace.rb +283 -0
  137. data/lib/graphql/tracing/new_relic_trace.rb +47 -54
  138. data/lib/graphql/tracing/new_relic_tracing.rb +2 -0
  139. data/lib/graphql/tracing/notifications_trace.rb +182 -34
  140. data/lib/graphql/tracing/notifications_tracing.rb +2 -0
  141. data/lib/graphql/tracing/null_trace.rb +9 -0
  142. data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
  143. data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
  144. data/lib/graphql/tracing/perfetto_trace.rb +734 -0
  145. data/lib/graphql/tracing/platform_trace.rb +5 -0
  146. data/lib/graphql/tracing/prometheus_trace/graphql_collector.rb +2 -0
  147. data/lib/graphql/tracing/prometheus_trace.rb +72 -68
  148. data/lib/graphql/tracing/prometheus_tracing.rb +2 -0
  149. data/lib/graphql/tracing/scout_trace.rb +32 -55
  150. data/lib/graphql/tracing/scout_tracing.rb +2 -0
  151. data/lib/graphql/tracing/sentry_trace.rb +62 -94
  152. data/lib/graphql/tracing/statsd_trace.rb +33 -41
  153. data/lib/graphql/tracing/statsd_tracing.rb +2 -0
  154. data/lib/graphql/tracing/trace.rb +111 -1
  155. data/lib/graphql/tracing.rb +31 -30
  156. data/lib/graphql/types/relay/connection_behaviors.rb +3 -3
  157. data/lib/graphql/types/relay/edge_behaviors.rb +2 -2
  158. data/lib/graphql/types.rb +18 -11
  159. data/lib/graphql/version.rb +1 -1
  160. data/lib/graphql.rb +55 -47
  161. metadata +146 -11
  162. data/lib/graphql/backtrace/inspect_result.rb +0 -38
  163. data/lib/graphql/backtrace/trace.rb +0 -93
  164. data/lib/graphql/backtrace/tracer.rb +0 -80
  165. data/lib/graphql/schema/null_mask.rb +0 -11
  166. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +0 -17
@@ -1,183 +1,71 @@
1
1
  # frozen_string_literal: true
2
+ require "graphql/tracing/monitor_trace"
2
3
 
3
4
  module GraphQL
4
5
  module Tracing
6
+ # A tracer for reporting to DataDog
7
+ # @example Adding this tracer to your schema
8
+ # class MySchema < GraphQL::Schema
9
+ # trace_with GraphQL::Tracing::DataDogTrace
10
+ # end
11
+ # @example Skipping `resolve_type` and `authorized` events
12
+ # trace_with GraphQL::Tracing::DataDogTrace, trace_authorized: false, trace_resolve_type: false
13
+ DataDogTrace = MonitorTrace.create_module("datadog")
5
14
  module DataDogTrace
6
- # @param tracer [#trace] Deprecated
7
- # @param analytics_enabled [Boolean] Deprecated
8
- # @param analytics_sample_rate [Float] Deprecated
9
- def initialize(tracer: nil, analytics_enabled: false, analytics_sample_rate: 1.0, service: nil, **rest)
10
- if tracer.nil?
11
- tracer = defined?(Datadog::Tracing) ? Datadog::Tracing : Datadog.tracer
15
+ class DatadogMonitor < MonitorTrace::Monitor
16
+ def initialize(set_transaction_name:, service: nil, tracer: nil, **_rest)
17
+ super
18
+ if tracer.nil?
19
+ tracer = defined?(Datadog::Tracing) ? Datadog::Tracing : Datadog.tracer
20
+ end
21
+ @tracer = tracer
22
+ @service_name = service
23
+ @has_prepare_span = @trace.respond_to?(:prepare_span)
12
24
  end
13
- @tracer = tracer
14
-
15
- @analytics_enabled = analytics_enabled
16
- @analytics_sample_rate = analytics_sample_rate
17
-
18
- @service_name = service
19
- @has_prepare_span = respond_to?(:prepare_span)
20
- super
21
- end
22
-
23
- {
24
- 'lex' => 'lex.graphql',
25
- 'parse' => 'parse.graphql',
26
- 'validate' => 'validate.graphql',
27
- 'analyze_query' => 'analyze.graphql',
28
- 'analyze_multiplex' => 'analyze.graphql',
29
- 'execute_multiplex' => 'execute.graphql',
30
- 'execute_query' => 'execute.graphql',
31
- 'execute_query_lazy' => 'execute.graphql',
32
- }.each do |trace_method, trace_key|
33
- module_eval <<-RUBY, __FILE__, __LINE__
34
- def #{trace_method}(**data)
35
- @tracer.trace("#{trace_key}", service: @service_name, type: 'custom') do |span|
36
- span.set_tag('component', 'graphql')
37
- span.set_tag('operation', '#{trace_method}')
38
-
39
- #{
40
- if trace_method == 'execute_multiplex'
41
- <<-RUBY
42
- operations = data[:multiplex].queries.map(&:selected_operation_name).join(', ')
43
25
 
44
- resource = if operations.empty?
45
- first_query = data[:multiplex].queries.first
46
- fallback_transaction_name(first_query && first_query.context)
47
- else
48
- operations
49
- end
50
- span.resource = resource if resource
26
+ attr_reader :tracer, :service_name
51
27
 
52
- # [Deprecated] will be removed in the future
53
- span.set_metric('_dd1.sr.eausr', @analytics_sample_rate) if @analytics_enabled
54
- RUBY
55
- elsif trace_method == 'execute_query'
56
- <<-RUBY
57
- span.set_tag(:selected_operation_name, data[:query].selected_operation_name)
58
- span.set_tag(:selected_operation_type, data[:query].selected_operation.operation_type)
59
- span.set_tag(:query_string, data[:query].query_string)
60
- RUBY
61
- end
62
- }
63
- if @has_prepare_span
64
- prepare_span("#{trace_method.sub("platform_", "")}", data, span)
28
+ def instrument(keyword, object)
29
+ trace_key = name_for(keyword, object)
30
+ @tracer.trace(trace_key, service: @service_name, type: 'custom') do |span|
31
+ span.set_tag('component', 'graphql')
32
+ op_name = keyword.respond_to?(:name) ? keyword.name : keyword.to_s
33
+ span.set_tag('operation', op_name)
34
+
35
+ if keyword == :execute
36
+ operations = object.queries.map(&:selected_operation_name).join(', ')
37
+ first_query = object.queries.first
38
+ resource = if operations.empty?
39
+ fallback_transaction_name(first_query && first_query.context)
40
+ else
41
+ operations
65
42
  end
66
- super
67
- end
68
- end
69
- RUBY
70
- end
43
+ span.resource = resource if resource
71
44
 
72
- def execute_field_span(span_key, query, field, ast_node, arguments, object)
73
- return_type = field.type.unwrap
74
- trace_field = if return_type.kind.scalar? || return_type.kind.enum?
75
- (field.trace.nil? && @trace_scalars) || field.trace
76
- else
77
- true
78
- end
79
- platform_key = if trace_field
80
- @platform_key_cache[DataDogTrace].platform_field_key_cache[field]
81
- else
82
- nil
83
- end
84
- if platform_key && trace_field
85
- @tracer.trace(platform_key, service: @service_name, type: 'custom') do |span|
86
- span.set_tag('component', 'graphql')
87
- span.set_tag('operation', span_key)
45
+ span.set_tag("selected_operation_name", first_query.selected_operation_name)
46
+ span.set_tag("selected_operation_type", first_query.selected_operation&.operation_type)
47
+ span.set_tag("query_string", first_query.query_string)
48
+ end
88
49
 
89
50
  if @has_prepare_span
90
- prepare_span_data = { query: query, field: field, ast_node: ast_node, arguments: arguments, object: object }
91
- prepare_span(span_key, prepare_span_data, span)
51
+ @trace.prepare_span(keyword, object, span)
92
52
  end
93
53
  yield
94
54
  end
95
- else
96
- yield
97
- end
98
- end
99
- def execute_field(query:, field:, ast_node:, arguments:, object:)
100
- execute_field_span("execute_field", query, field, ast_node, arguments, object) do
101
- super(query: query, field: field, ast_node: ast_node, arguments: arguments, object: object)
102
- end
103
- end
104
-
105
- def execute_field_lazy(query:, field:, ast_node:, arguments:, object:)
106
- execute_field_span("execute_field_lazy", query, field, ast_node, arguments, object) do
107
- super(query: query, field: field, ast_node: ast_node, arguments: arguments, object: object)
108
- end
109
- end
110
-
111
- def authorized(query:, type:, object:)
112
- authorized_span("authorized", object, type, query) do
113
- super(query: query, type: type, object: object)
114
55
  end
115
- end
116
-
117
- def authorized_span(span_key, object, type, query)
118
- platform_key = @platform_key_cache[DataDogTrace].platform_authorized_key_cache[type]
119
- @tracer.trace(platform_key, service: @service_name, type: 'custom') do |span|
120
- span.set_tag('component', 'graphql')
121
- span.set_tag('operation', span_key)
122
56
 
123
- if @has_prepare_span
124
- prepare_span(span_key, {object: object, type: type, query: query}, span)
57
+ include MonitorTrace::Monitor::GraphQLSuffixNames
58
+ class Event < MonitorTrace::Monitor::Event
59
+ def start
60
+ name = @monitor.name_for(keyword, object)
61
+ @dd_span = @monitor.tracer.trace(name, service: @monitor.service_name, type: 'custom')
125
62
  end
126
- yield
127
- end
128
- end
129
-
130
- def authorized_lazy(object:, type:, query:)
131
- authorized_span("authorized_lazy", object, type, query) do
132
- super(query: query, type: type, object: object)
133
- end
134
- end
135
-
136
- def resolve_type(object:, type:, query:)
137
- resolve_type_span("resolve_type", object, type, query) do
138
- super(object: object, query: query, type: type)
139
- end
140
- end
141
-
142
- def resolve_type_lazy(object:, type:, query:)
143
- resolve_type_span("resolve_type_lazy", object, type, query) do
144
- super(object: object, query: query, type: type)
145
- end
146
- end
147
-
148
- def resolve_type_span(span_key, object, type, query)
149
- platform_key = @platform_key_cache[DataDogTrace].platform_resolve_type_key_cache[type]
150
- @tracer.trace(platform_key, service: @service_name, type: 'custom') do |span|
151
- span.set_tag('component', 'graphql')
152
- span.set_tag('operation', span_key)
153
63
 
154
- if @has_prepare_span
155
- prepare_span(span_key, {object: object, type: type, query: query}, span)
64
+ def finish
65
+ @dd_span.finish
156
66
  end
157
- yield
158
67
  end
159
68
  end
160
-
161
- include PlatformTrace
162
-
163
- # Implement this method in a subclass to apply custom tags to datadog spans
164
- # @param key [String] The event being traced
165
- # @param data [Hash] The runtime data for this event (@see GraphQL::Tracing for keys for each event)
166
- # @param span [Datadog::Tracing::SpanOperation] The datadog span for this event
167
- # def prepare_span(key, data, span)
168
- # end
169
-
170
- def platform_field_key(field)
171
- field.path
172
- end
173
-
174
- def platform_authorized_key(type)
175
- "#{type.graphql_name}.authorized"
176
- end
177
-
178
- def platform_resolve_type_key(type)
179
- "#{type.graphql_name}.resolve_type"
180
- end
181
69
  end
182
70
  end
183
71
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "graphql/tracing/platform_tracing"
4
+
3
5
  module GraphQL
4
6
  module Tracing
5
7
  class DataDogTracing < PlatformTracing
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Tracing
5
+ class DetailedTrace
6
+ # An in-memory trace storage backend. Suitable for testing and development only.
7
+ # It won't work for multi-process deployments and everything is erased when the app is restarted.
8
+ class MemoryBackend
9
+ def initialize(limit: nil)
10
+ @limit = limit
11
+ @traces = {}
12
+ @next_id = 0
13
+ end
14
+
15
+ def traces(last:, before:)
16
+ page = []
17
+ @traces.values.reverse_each do |trace|
18
+ if page.size == last
19
+ break
20
+ elsif before.nil? || trace.begin_ms < before
21
+ page << trace
22
+ end
23
+ end
24
+ page
25
+ end
26
+
27
+ def find_trace(id)
28
+ @traces[id]
29
+ end
30
+
31
+ def delete_trace(id)
32
+ @traces.delete(id.to_i)
33
+ nil
34
+ end
35
+
36
+ def delete_all_traces
37
+ @traces.clear
38
+ nil
39
+ end
40
+
41
+ def save_trace(operation_name, duration, begin_ms, trace_data)
42
+ id = @next_id
43
+ @next_id += 1
44
+ @traces[id] = DetailedTrace::StoredTrace.new(
45
+ id: id,
46
+ operation_name: operation_name,
47
+ duration_ms: duration,
48
+ begin_ms: begin_ms,
49
+ trace_data: trace_data
50
+ )
51
+ if @limit && @traces.size > @limit
52
+ del_keys = @traces.keys[0...-@limit]
53
+ del_keys.each { |k| @traces.delete(k) }
54
+ end
55
+ id
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Tracing
5
+ class DetailedTrace
6
+ class RedisBackend
7
+ KEY_PREFIX = "gql:trace:"
8
+ def initialize(redis:, limit: nil)
9
+ @redis = redis
10
+ @key = KEY_PREFIX + "traces"
11
+ @remrangebyrank_limit = limit ? -limit - 1 : nil
12
+ end
13
+
14
+ def traces(last:, before:)
15
+ before = case before
16
+ when Numeric
17
+ "(#{before}"
18
+ when nil
19
+ "+inf"
20
+ end
21
+ str_pairs = @redis.zrange(@key, before, 0, byscore: true, rev: true, limit: [0, last || 100], withscores: true)
22
+ str_pairs.map do |(str_data, score)|
23
+ entry_to_trace(score, str_data)
24
+ end
25
+ end
26
+
27
+ def delete_trace(id)
28
+ @redis.zremrangebyscore(@key, id, id)
29
+ nil
30
+ end
31
+
32
+ def delete_all_traces
33
+ @redis.del(@key)
34
+ end
35
+
36
+ def find_trace(id)
37
+ str_data = @redis.zrange(@key, id, id, byscore: true).first
38
+ if str_data.nil?
39
+ nil
40
+ else
41
+ entry_to_trace(id, str_data)
42
+ end
43
+ end
44
+
45
+ def save_trace(operation_name, duration_ms, begin_ms, trace_data)
46
+ id = begin_ms
47
+ data = JSON.dump({ "o" => operation_name, "d" => duration_ms, "b" => begin_ms, "t" => Base64.encode64(trace_data) })
48
+ @redis.pipelined do |pipeline|
49
+ pipeline.zadd(@key, id, data)
50
+ if @remrangebyrank_limit
51
+ pipeline.zremrangebyrank(@key, 0, @remrangebyrank_limit)
52
+ end
53
+ end
54
+ id
55
+ end
56
+
57
+ private
58
+
59
+ def entry_to_trace(id, json_str)
60
+ data = JSON.parse(json_str)
61
+ StoredTrace.new(
62
+ id: id,
63
+ operation_name: data["o"],
64
+ duration_ms: data["d"].to_f,
65
+ begin_ms: data["b"].to_i,
66
+ trace_data: Base64.decode64(data["t"]),
67
+ )
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+ require "graphql/tracing/detailed_trace/memory_backend"
3
+ require "graphql/tracing/detailed_trace/redis_backend"
4
+
5
+ module GraphQL
6
+ module Tracing
7
+ # `DetailedTrace` can make detailed profiles for a subset of production traffic.
8
+ #
9
+ # When `MySchema.detailed_trace?(query)` returns `true`, a profiler-specific `trace_mode: ...` will be used for the query,
10
+ # overriding the one in `context[:trace_mode]`.
11
+ #
12
+ # __Redis__: The sampler stores its results in a provided Redis database. Depending on your needs,
13
+ # You can configure this database to retail all data (persistent) or to expire data according to your rules.
14
+ # If you need to save traces indefinitely, you can download them from Perfetto after opening them there.
15
+ #
16
+ # @example Adding the sampler to your schema
17
+ # class MySchema < GraphQL::Schema
18
+ # # Add the sampler:
19
+ # use GraphQL::Tracing::DetailedTrace, redis: Redis.new(...), limit: 100
20
+ #
21
+ # # And implement this hook to tell it when to take a sample:
22
+ # def self.detailed_trace?(query)
23
+ # # Could use `query.context`, `query.selected_operation_name`, `query.query_string` here
24
+ # # Could call out to Flipper, etc
25
+ # rand <= 0.000_1 # one in ten thousand
26
+ # end
27
+ # end
28
+ #
29
+ # @see Graphql::Dashboard GraphQL::Dashboard for viewing stored results
30
+ class DetailedTrace
31
+ # @param redis [Redis] If provided, profiles will be stored in Redis for later review
32
+ # @param limit [Integer] A maximum number of profiles to store
33
+ def self.use(schema, trace_mode: :profile_sample, memory: false, redis: nil, limit: nil)
34
+ storage = if redis
35
+ RedisBackend.new(redis: redis, limit: limit)
36
+ elsif memory
37
+ MemoryBackend.new(limit: limit)
38
+ else
39
+ raise ArgumentError, "Pass `redis: ...` to store traces in Redis for later review"
40
+ end
41
+ schema.detailed_trace = self.new(storage: storage, trace_mode: trace_mode)
42
+ schema.trace_with(PerfettoTrace, mode: trace_mode, save_profile: true)
43
+ end
44
+
45
+ def initialize(storage:, trace_mode:)
46
+ @storage = storage
47
+ @trace_mode = trace_mode
48
+ end
49
+
50
+ # @return [Symbol] The trace mode to use when {Schema.detailed_trace?} returns `true`
51
+ attr_reader :trace_mode
52
+
53
+ # @return [String] ID of saved trace
54
+ def save_trace(operation_name, duration_ms, begin_ms, trace_data)
55
+ @storage.save_trace(operation_name, duration_ms, begin_ms, trace_data)
56
+ end
57
+
58
+ # @param last [Integer]
59
+ # @param before [Integer] Timestamp in milliseconds since epoch
60
+ # @return [Enumerable<StoredTrace>]
61
+ def traces(last: nil, before: nil)
62
+ @storage.traces(last: last, before: before)
63
+ end
64
+
65
+ # @return [StoredTrace, nil]
66
+ def find_trace(id)
67
+ @storage.find_trace(id)
68
+ end
69
+
70
+ # @return [void]
71
+ def delete_trace(id)
72
+ @storage.delete_trace(id)
73
+ end
74
+
75
+ # @return [void]
76
+ def delete_all_traces
77
+ @storage.delete_all_traces
78
+ end
79
+
80
+ class StoredTrace
81
+ def initialize(id:, operation_name:, duration_ms:, begin_ms:, trace_data:)
82
+ @id = id
83
+ @operation_name = operation_name
84
+ @duration_ms = duration_ms
85
+ @begin_ms = begin_ms
86
+ @trace_data = trace_data
87
+ end
88
+
89
+ attr_reader :id, :operation_name, :duration_ms, :begin_ms, :trace_data
90
+ end
91
+ end
92
+ end
93
+ end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module GraphQL
3
4
  module Tracing
4
5
  module LegacyHooksTrace
@@ -1,67 +1,10 @@
1
1
  # frozen_string_literal: true
2
- module GraphQL
3
- module Tracing
4
- # This trace class calls legacy-style tracer with payload hashes.
5
- # New-style `trace_with` modules significantly reduce the overhead of tracing,
6
- # but that advantage is lost when legacy-style tracers are also used (since the payload hashes are still constructed).
7
- module CallLegacyTracers
8
- def lex(query_string:)
9
- (@multiplex || @query).trace("lex", { query_string: query_string }) { super }
10
- end
11
-
12
- def parse(query_string:)
13
- (@multiplex || @query).trace("parse", { query_string: query_string }) { super }
14
- end
15
-
16
- def validate(query:, validate:)
17
- query.trace("validate", { validate: validate, query: query }) { super }
18
- end
19
-
20
- def analyze_multiplex(multiplex:)
21
- multiplex.trace("analyze_multiplex", { multiplex: multiplex }) { super }
22
- end
23
-
24
- def analyze_query(query:)
25
- query.trace("analyze_query", { query: query }) { super }
26
- end
27
-
28
- def execute_multiplex(multiplex:)
29
- multiplex.trace("execute_multiplex", { multiplex: multiplex }) { super }
30
- end
31
-
32
- def execute_query(query:)
33
- query.trace("execute_query", { query: query }) { super }
34
- end
35
-
36
- def execute_query_lazy(query:, multiplex:)
37
- multiplex.trace("execute_query_lazy", { multiplex: multiplex, query: query }) { super }
38
- end
39
2
 
40
- def execute_field(field:, query:, ast_node:, arguments:, object:)
41
- query.trace("execute_field", { field: field, query: query, ast_node: ast_node, arguments: arguments, object: object, owner: field.owner, path: query.context[:current_path] }) { super }
42
- end
43
-
44
- def execute_field_lazy(field:, query:, ast_node:, arguments:, object:)
45
- query.trace("execute_field_lazy", { field: field, query: query, ast_node: ast_node, arguments: arguments, object: object, owner: field.owner, path: query.context[:current_path] }) { super }
46
- end
47
-
48
- def authorized(query:, type:, object:)
49
- query.trace("authorized", { context: query.context, type: type, object: object, path: query.context[:current_path] }) { super }
50
- end
51
-
52
- def authorized_lazy(query:, type:, object:)
53
- query.trace("authorized_lazy", { context: query.context, type: type, object: object, path: query.context[:current_path] }) { super }
54
- end
55
-
56
- def resolve_type(query:, type:, object:)
57
- query.trace("resolve_type", { context: query.context, type: type, object: object, path: query.context[:current_path] }) { super }
58
- end
59
-
60
- def resolve_type_lazy(query:, type:, object:)
61
- query.trace("resolve_type_lazy", { context: query.context, type: type, object: object, path: query.context[:current_path] }) { super }
62
- end
63
- end
3
+ require "graphql/tracing/trace"
4
+ require "graphql/tracing/call_legacy_tracers"
64
5
 
6
+ module GraphQL
7
+ module Tracing
65
8
  class LegacyTrace < Trace
66
9
  include CallLegacyTracers
67
10
  end