graphql 2.4.8 → 2.4.11

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.

Potentially problematic release.


This version of graphql might be problematic. Click here for more details.

Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/backtrace/table.rb +95 -55
  3. data/lib/graphql/backtrace.rb +1 -19
  4. data/lib/graphql/current.rb +5 -0
  5. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.css +6 -0
  6. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js +7 -0
  7. data/lib/graphql/dashboard/statics/dashboard.css +3 -0
  8. data/lib/graphql/dashboard/statics/dashboard.js +78 -0
  9. data/lib/graphql/dashboard/statics/header-icon.png +0 -0
  10. data/lib/graphql/dashboard/statics/icon.png +0 -0
  11. data/lib/graphql/dashboard/views/graphql/dashboard/landings/show.html.erb +18 -0
  12. data/lib/graphql/dashboard/views/graphql/dashboard/traces/index.html.erb +63 -0
  13. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +60 -0
  14. data/lib/graphql/dashboard.rb +142 -0
  15. data/lib/graphql/dataloader/active_record_association_source.rb +64 -0
  16. data/lib/graphql/dataloader/active_record_source.rb +26 -0
  17. data/lib/graphql/dataloader/async_dataloader.rb +17 -5
  18. data/lib/graphql/dataloader/null_dataloader.rb +1 -1
  19. data/lib/graphql/dataloader/source.rb +2 -2
  20. data/lib/graphql/dataloader.rb +37 -5
  21. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +11 -4
  22. data/lib/graphql/execution/interpreter/runtime.rb +60 -33
  23. data/lib/graphql/execution/interpreter.rb +9 -1
  24. data/lib/graphql/execution/multiplex.rb +0 -4
  25. data/lib/graphql/introspection/directive_location_enum.rb +1 -1
  26. data/lib/graphql/invalid_name_error.rb +1 -1
  27. data/lib/graphql/invalid_null_error.rb +6 -12
  28. data/lib/graphql/language/parser.rb +1 -1
  29. data/lib/graphql/query.rb +8 -12
  30. data/lib/graphql/schema/enum.rb +36 -1
  31. data/lib/graphql/schema/input_object.rb +1 -1
  32. data/lib/graphql/schema/interface.rb +1 -0
  33. data/lib/graphql/schema/member/has_dataloader.rb +60 -0
  34. data/lib/graphql/schema/member.rb +1 -0
  35. data/lib/graphql/schema/object.rb +17 -8
  36. data/lib/graphql/schema/resolver.rb +2 -5
  37. data/lib/graphql/schema/validator/required_validator.rb +23 -6
  38. data/lib/graphql/schema/visibility/profile.rb +5 -5
  39. data/lib/graphql/schema/visibility.rb +14 -9
  40. data/lib/graphql/schema.rb +54 -28
  41. data/lib/graphql/static_validation/validator.rb +6 -1
  42. data/lib/graphql/subscriptions/serialize.rb +1 -3
  43. data/lib/graphql/tracing/active_support_notifications_trace.rb +6 -2
  44. data/lib/graphql/tracing/appoptics_trace.rb +3 -1
  45. data/lib/graphql/tracing/appsignal_trace.rb +6 -0
  46. data/lib/graphql/tracing/data_dog_trace.rb +5 -0
  47. data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
  48. data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
  49. data/lib/graphql/tracing/detailed_trace.rb +93 -0
  50. data/lib/graphql/tracing/new_relic_trace.rb +147 -41
  51. data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
  52. data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
  53. data/lib/graphql/tracing/perfetto_trace.rb +737 -0
  54. data/lib/graphql/tracing/prometheus_trace.rb +22 -0
  55. data/lib/graphql/tracing/scout_trace.rb +6 -0
  56. data/lib/graphql/tracing/sentry_trace.rb +5 -0
  57. data/lib/graphql/tracing/statsd_trace.rb +9 -0
  58. data/lib/graphql/tracing/trace.rb +125 -1
  59. data/lib/graphql/tracing.rb +2 -0
  60. data/lib/graphql/version.rb +1 -1
  61. data/lib/graphql.rb +3 -0
  62. metadata +148 -10
  63. data/lib/graphql/backtrace/inspect_result.rb +0 -38
  64. data/lib/graphql/backtrace/trace.rb +0 -93
  65. data/lib/graphql/backtrace/tracer.rb +0 -80
  66. data/lib/graphql/schema/null_mask.rb +0 -11
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
  require "set"
3
- require "ostruct"
4
-
5
3
  module GraphQL
6
4
  class Subscriptions
7
5
  # Serialization helpers for passing subscription data around.
@@ -148,7 +146,7 @@ module GraphQL
148
146
  elsif obj.is_a?(Date) || obj.is_a?(Time)
149
147
  # DateTime extends Date; for TimeWithZone, call `.utc` first.
150
148
  { TIMESTAMP_KEY => [obj.class.name, obj.strftime(TIMESTAMP_FORMAT)] }
151
- elsif obj.is_a?(OpenStruct)
149
+ elsif defined?(OpenStruct) && obj.is_a?(OpenStruct)
152
150
  { OPEN_STRUCT_KEY => dump_value(obj.to_h) }
153
151
  elsif defined?(ActiveRecord::Relation) && obj.is_a?(ActiveRecord::Relation)
154
152
  dump_value(obj.to_a)
@@ -4,8 +4,12 @@ require "graphql/tracing/notifications_trace"
4
4
 
5
5
  module GraphQL
6
6
  module Tracing
7
- # This implementation forwards events to ActiveSupport::Notifications
8
- # with a `graphql` suffix.
7
+ # This implementation forwards events to ActiveSupport::Notifications with a `graphql` suffix.
8
+ #
9
+ # @example Sending execution events to ActiveSupport::Notifications
10
+ # class MySchema < GraphQL::Schema
11
+ # trace_with(GraphQL::Tracing::ActiveSupportNotificationsTrace)
12
+ # end
9
13
  module ActiveSupportNotificationsTrace
10
14
  include NotificationsTrace
11
15
  def initialize(engine: ActiveSupport::Notifications, **rest)
@@ -22,10 +22,12 @@ module GraphQL
22
22
  # These GraphQL events will show up as 'graphql.execute' spans
23
23
  EXEC_KEYS = ['execute_multiplex', 'execute_query', 'execute_query_lazy'].freeze
24
24
 
25
+
25
26
  # During auto-instrumentation this version of AppOpticsTracing is compared
26
27
  # with the version provided in the appoptics_apm gem, so that the newer
27
28
  # version of the class can be used
28
29
 
30
+
29
31
  def self.version
30
32
  Gem::Version.new('1.0.0')
31
33
  end
@@ -83,7 +85,7 @@ module GraphQL
83
85
  end
84
86
  end
85
87
 
86
- def execute_field_lazy(query:, field:, ast_node:, arguments:, object:)
88
+ def execute_field_lazy(query:, field:, ast_node:, arguments:, object:) # rubocop:disable Development/TraceCallsSuperCop
87
89
  execute_field(query: query, field: field, ast_node: ast_node, arguments: arguments, object: object)
88
90
  end
89
91
 
@@ -4,6 +4,12 @@ require "graphql/tracing/platform_trace"
4
4
 
5
5
  module GraphQL
6
6
  module Tracing
7
+ # Instrumentation for reporting GraphQL-Ruby times to Appsignal.
8
+ #
9
+ # @example Installing the tracer
10
+ # class MySchema < GraphQL::Schema
11
+ # trace_with GraphQL::Tracing::AppsignalTrace
12
+ # end
7
13
  module AppsignalTrace
8
14
  include PlatformTrace
9
15
 
@@ -4,6 +4,11 @@ require "graphql/tracing/platform_trace"
4
4
 
5
5
  module GraphQL
6
6
  module Tracing
7
+ # A tracer for reporting to DataDog
8
+ # @example Adding this tracer to your schema
9
+ # class MySchema < GraphQL::Schema
10
+ # trace_with GraphQL::Tracing::DataDogTrace
11
+ # end
7
12
  module DataDogTrace
8
13
  # @param tracer [#trace] Deprecated
9
14
  # @param analytics_enabled [Boolean] Deprecated
@@ -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
@@ -4,73 +4,179 @@ require "graphql/tracing/platform_trace"
4
4
 
5
5
  module GraphQL
6
6
  module Tracing
7
+ # A tracer for reporting GraphQL-Ruby time to New Relic
8
+ #
9
+ # @example Installing the tracer
10
+ # class MySchema < GraphQL::Schema
11
+ # trace_with GraphQL::Tracing::NewRelicTrace
12
+ #
13
+ # # Optional, use the operation name to set the new relic transaction name:
14
+ # # trace_with GraphQL::Tracing::NewRelicTrace, set_transaction_name: true
15
+ # end
7
16
  module NewRelicTrace
8
- include PlatformTrace
9
-
10
17
  # @param set_transaction_name [Boolean] If true, the GraphQL operation name will be used as the transaction name.
11
18
  # This is not advised if you run more than one query per HTTP request, for example, with `graphql-client` or multiplexing.
12
19
  # It can also be specified per-query with `context[:set_new_relic_transaction_name]`.
13
- def initialize(set_transaction_name: false, **_rest)
20
+ # @param trace_authorized [Boolean] If `false`, skip tracing `authorized?` calls
21
+ # @param trace_resolve_type [Boolean] If `false`, skip tracing `resolve_type?` calls
22
+ def initialize(set_transaction_name: false, trace_authorized: true, trace_resolve_type: true, **_rest)
14
23
  @set_transaction_name = set_transaction_name
24
+ @trace_authorized = trace_authorized
25
+ @trace_resolve_type = trace_resolve_type
26
+ @nr_field_names = Hash.new do |h, field|
27
+ h[field] = "GraphQL/#{field.owner.graphql_name}/#{field.graphql_name}"
28
+ end.compare_by_identity
29
+
30
+ @nr_authorized_names = Hash.new do |h, type|
31
+ h[type] = "GraphQL/Authorized/#{type.graphql_name}"
32
+ end.compare_by_identity
33
+
34
+ @nr_resolve_type_names = Hash.new do |h, type|
35
+ h[type] = "GraphQL/ResolveType/#{type.graphql_name}"
36
+ end.compare_by_identity
37
+
38
+ @nr_source_names = Hash.new do |h, source_inst|
39
+ h[source_inst] = "GraphQL/Source/#{source_inst.class.name}"
40
+ end.compare_by_identity
41
+
42
+ @nr_parse = @nr_validate = @nr_analyze = @nr_execute = nil
43
+ super
44
+ end
45
+
46
+ def begin_parse(query_str)
47
+ @nr_parse = NewRelic::Agent::Tracer.start_transaction_or_segment(partial_name: "GraphQL/parse", category: :web)
48
+ super
49
+ end
50
+
51
+ def end_parse(query_str)
52
+ @nr_parse.finish
53
+ super
54
+ end
55
+
56
+ def begin_validate(query, validate)
57
+ @nr_validate = NewRelic::Agent::Tracer.start_transaction_or_segment(partial_name: "GraphQL/validate", category: :web)
58
+ super
59
+ end
60
+
61
+ def end_validate(query, validate, validation_errors)
62
+ @nr_validate.finish
63
+ super
64
+ end
65
+
66
+ def begin_analyze_multiplex(multiplex, analyzers)
67
+ @nr_analyze = NewRelic::Agent::Tracer.start_transaction_or_segment(partial_name: "GraphQL/analyze", category: :web)
68
+ super
69
+ end
70
+
71
+ def end_analyze_multiplex(multiplex, analyzers)
72
+ @nr_analyze.finish
15
73
  super
16
74
  end
17
75
 
18
- def execute_query(query:)
19
- set_this_txn_name = query.context[:set_new_relic_transaction_name]
20
- if set_this_txn_name == true || (set_this_txn_name.nil? && @set_transaction_name)
76
+ def begin_execute_multiplex(multiplex)
77
+ query = multiplex.queries.first
78
+ set_this_txn_name = query.context[:set_new_relic_transaction_name]
79
+ if set_this_txn_name || (set_this_txn_name.nil? && @set_transaction_name)
21
80
  NewRelic::Agent.set_transaction_name(transaction_name(query))
22
81
  end
23
- NewRelic::Agent::MethodTracerHelpers.trace_execution_scoped("GraphQL/execute") do
24
- super
82
+ @nr_execute = NewRelic::Agent::Tracer.start_transaction_or_segment(partial_name: "GraphQL/execute", category: :web)
83
+ super
84
+ end
85
+
86
+ def end_execute_multiplex(multiplex)
87
+ @nr_execute.finish
88
+ super
89
+ end
90
+
91
+ def begin_execute_field(field, object, arguments, query)
92
+ nr_segment_stack << NewRelic::Agent::Tracer.start_transaction_or_segment(partial_name: @nr_field_names[field], category: :web)
93
+ super
94
+ end
95
+
96
+ def end_execute_field(field, objects, arguments, query, result)
97
+ nr_segment_stack.pop.finish
98
+ super
99
+ end
100
+
101
+ def begin_authorized(type, obj, ctx)
102
+ if @trace_authorized
103
+ nr_segment_stack << NewRelic::Agent::Tracer.start_transaction_or_segment(partial_name: @nr_authorized_names[type], category: :web)
25
104
  end
105
+ super
26
106
  end
27
107
 
28
- {
29
- "lex" => "GraphQL/lex",
30
- "parse" => "GraphQL/parse",
31
- "validate" => "GraphQL/validate",
32
- "analyze_query" => "GraphQL/analyze",
33
- "analyze_multiplex" => "GraphQL/analyze",
34
- "execute_multiplex" => "GraphQL/execute",
35
- "execute_query_lazy" => "GraphQL/execute",
36
- }.each do |trace_method, platform_key|
37
- module_eval <<-RUBY, __FILE__, __LINE__
38
- def #{trace_method}(**_keys)
39
- NewRelic::Agent::MethodTracerHelpers.trace_execution_scoped("#{platform_key}") do
40
- super
41
- end
42
- end
43
- RUBY
44
- end
45
-
46
- def platform_execute_field(platform_key)
47
- NewRelic::Agent::MethodTracerHelpers.trace_execution_scoped(platform_key) do
48
- yield
108
+ def end_authorized(type, obj, ctx, is_authed)
109
+ if @trace_authorized
110
+ nr_segment_stack.pop.finish
49
111
  end
112
+ super
50
113
  end
51
114
 
52
- def platform_authorized(platform_key)
53
- NewRelic::Agent::MethodTracerHelpers.trace_execution_scoped(platform_key) do
54
- yield
115
+ def begin_resolve_type(type, value, context)
116
+ if @trace_resolve_type
117
+ nr_segment_stack << NewRelic::Agent::Tracer.start_transaction_or_segment(partial_name: @nr_resolve_type_names[type], category: :web)
55
118
  end
119
+ super
56
120
  end
57
121
 
58
- def platform_resolve_type(platform_key)
59
- NewRelic::Agent::MethodTracerHelpers.trace_execution_scoped(platform_key) do
60
- yield
122
+ def end_resolve_type(type, value, context, resolved_type)
123
+ if @trace_resolve_type
124
+ nr_segment_stack.pop.finish
61
125
  end
126
+ super
62
127
  end
63
128
 
64
- def platform_field_key(field)
65
- "GraphQL/#{field.owner.graphql_name}/#{field.graphql_name}"
129
+ def begin_dataloader(dl)
130
+ super
66
131
  end
67
132
 
68
- def platform_authorized_key(type)
69
- "GraphQL/Authorize/#{type.graphql_name}"
133
+ def end_dataloader(dl)
134
+ super
135
+ end
136
+
137
+ def begin_dataloader_source(source)
138
+ nr_segment_stack << NewRelic::Agent::Tracer.start_transaction_or_segment(partial_name: @nr_source_names[source], category: :web)
139
+ super
140
+ end
141
+
142
+ def end_dataloader_source(source)
143
+ nr_segment_stack.pop.finish
144
+ super
145
+ end
146
+
147
+ def dataloader_fiber_yield(source)
148
+ current_segment = nr_segment_stack.last
149
+ current_segment.finish
150
+ super
151
+ end
152
+
153
+ def dataloader_fiber_resume(source)
154
+ prev_segment = nr_segment_stack.pop
155
+ seg_partial_name = prev_segment.name.sub(/^.*(GraphQL.*)$/, '\1')
156
+ nr_segment_stack << NewRelic::Agent::Tracer.start_transaction_or_segment(partial_name: seg_partial_name, category: :web)
157
+ super
158
+ end
159
+
160
+ private
161
+
162
+ def nr_segment_stack
163
+ Fiber[:graphql_nr_segment_stack] ||= []
164
+ end
165
+
166
+ def transaction_name(query)
167
+ selected_op = query.selected_operation
168
+ txn_name = if selected_op
169
+ op_type = selected_op.operation_type
170
+ op_name = selected_op.name || fallback_transaction_name(query.context) || "anonymous"
171
+ "#{op_type}.#{op_name}"
172
+ else
173
+ "query.anonymous"
174
+ end
175
+ "GraphQL/#{txn_name}"
70
176
  end
71
177
 
72
- def platform_resolve_type_key(type)
73
- "GraphQL/ResolveType/#{type.graphql_name}"
178
+ def fallback_transaction_name(context)
179
+ context[:tracing_fallback_transaction_name]
74
180
  end
75
181
  end
76
182
  end
@@ -0,0 +1,141 @@
1
+ // This is an abbreviated version of the full Perfetto schema.
2
+ // Most of them are for OS or Chrome traces and we'll never use them.
3
+ // Full doc: https://github.com/google/perfetto/tree/main/protos/perfetto
4
+ //
5
+ // Build it with
6
+ // protoc --ruby_out=lib/graphql/tracing/perfetto_trace --proto_path=lib/graphql/tracing/perfetto_trace trace.proto
7
+ syntax = "proto2";
8
+ package perfetto_trace.protos;
9
+ option ruby_package = "GraphQL::Tracing::PerfettoTrace";
10
+
11
+ message Trace {
12
+ repeated TracePacket packet = 1;
13
+ }
14
+
15
+ message TracePacket {
16
+ optional uint64 timestamp = 8;
17
+ oneof data {
18
+ TrackEvent track_event = 11;
19
+ TrackDescriptor track_descriptor = 60;
20
+ }
21
+ oneof optional_trusted_packet_sequence_id {
22
+ uint32 trusted_packet_sequence_id = 10;
23
+ }
24
+ optional InternedData interned_data = 12;
25
+ optional bool first_packet_on_sequence = 87;
26
+ optional bool previous_packet_dropped = 42;
27
+ optional uint32 sequence_flags = 13;
28
+ }
29
+
30
+ message TrackEvent {
31
+ repeated uint64 category_iids = 3;
32
+ repeated string categories = 22;
33
+ oneof name_field {
34
+ uint64 name_iid = 10;
35
+ string name = 23;
36
+ }
37
+ enum Type {
38
+ TYPE_UNSPECIFIED = 0;
39
+ TYPE_SLICE_BEGIN = 1;
40
+ TYPE_SLICE_END = 2;
41
+ TYPE_INSTANT = 3;
42
+ TYPE_COUNTER = 4;
43
+ }
44
+ optional Type type = 9;
45
+ optional uint64 track_uuid = 11;
46
+ oneof counter_value_field {
47
+ int64 counter_value = 30;
48
+ double double_counter_value = 44;
49
+ }
50
+ repeated uint64 extra_counter_track_uuids = 31;
51
+ repeated int64 extra_counter_values = 12;
52
+ repeated uint64 extra_double_counter_track_uuids = 45;
53
+ repeated double extra_double_counter_values = 46;
54
+ repeated fixed64 flow_ids = 47;
55
+ repeated fixed64 terminating_flow_ids = 48;
56
+ repeated DebugAnnotation debug_annotations = 4;
57
+ }
58
+
59
+ message DebugAnnotation {
60
+ oneof name_field {
61
+ uint64 name_iid = 1;
62
+ string name = 10;
63
+ }
64
+ oneof value {
65
+ bool bool_value = 2;
66
+ uint64 uint_value = 3;
67
+ int64 int_value = 4;
68
+ double double_value = 5;
69
+ string string_value = 6;
70
+ uint64 string_value_iid = 17;
71
+ }
72
+ repeated DebugAnnotation dict_entries = 11;
73
+ repeated DebugAnnotation array_values = 12;
74
+ uint64 string_value_iid = 17;
75
+ }
76
+
77
+ message TrackDescriptor {
78
+ optional uint64 uuid = 1;
79
+ optional uint64 parent_uuid = 5;
80
+
81
+ oneof static_or_dynamic_name {
82
+ string name = 2;
83
+ }
84
+
85
+ optional CounterDescriptor counter = 8;
86
+ enum ChildTracksOrdering {
87
+ UNKNOWN = 0;
88
+ LEXICOGRAPHIC = 1;
89
+ CHRONOLOGICAL = 2;
90
+ EXPLICIT = 3;
91
+ }
92
+ optional ChildTracksOrdering child_ordering = 11;
93
+ optional int32 sibling_order_rank = 12;
94
+ }
95
+
96
+ message CounterDescriptor {
97
+ enum BuiltinCounterType {
98
+ COUNTER_UNSPECIFIED = 0;
99
+ COUNTER_THREAD_TIME_NS = 1;
100
+ COUNTER_THREAD_INSTRUCTION_COUNT = 2;
101
+ }
102
+ enum Unit {
103
+ UNIT_UNSPECIFIED = 0;
104
+ UNIT_TIME_NS = 1;
105
+ UNIT_COUNT = 2;
106
+ UNIT_SIZE_BYTES = 3;
107
+ }
108
+ optional BuiltinCounterType type = 1;
109
+ repeated string categories = 2;
110
+ optional Unit unit = 3;
111
+ optional string unit_name = 6;
112
+ optional int64 unit_multiplier = 4;
113
+ optional bool is_incremental = 5;
114
+ }
115
+
116
+ message InternedData {
117
+ repeated EventCategory event_categories = 1;
118
+ repeated EventName event_names = 2;
119
+ repeated DebugAnnotationName debug_annotation_names = 3;
120
+ repeated InternedString debug_annotation_string_values = 29;
121
+ }
122
+
123
+ message InternedString {
124
+ optional uint64 iid = 1;
125
+ optional bytes str = 2;
126
+ }
127
+
128
+ message EventCategory {
129
+ optional uint64 iid = 1;
130
+ optional string name = 2;
131
+ }
132
+
133
+ message EventName {
134
+ optional uint64 iid = 1;
135
+ optional string name = 2;
136
+ }
137
+
138
+ message DebugAnnotationName {
139
+ optional uint64 iid = 1;
140
+ optional string name = 2;
141
+ }