graphql 2.4.3 → 2.5.3

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 (171) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/analysis/analyzer.rb +2 -1
  3. data/lib/graphql/analysis/query_complexity.rb +87 -7
  4. data/lib/graphql/analysis/visitor.rb +38 -41
  5. data/lib/graphql/analysis.rb +15 -12
  6. data/lib/graphql/autoload.rb +38 -0
  7. data/lib/graphql/backtrace/table.rb +118 -55
  8. data/lib/graphql/backtrace.rb +1 -19
  9. data/lib/graphql/current.rb +7 -2
  10. data/lib/graphql/dashboard/detailed_traces.rb +47 -0
  11. data/lib/graphql/dashboard/installable.rb +22 -0
  12. data/lib/graphql/dashboard/limiters.rb +93 -0
  13. data/lib/graphql/dashboard/operation_store.rb +199 -0
  14. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.css +6 -0
  15. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js +7 -0
  16. data/lib/graphql/dashboard/statics/charts.min.css +1 -0
  17. data/lib/graphql/dashboard/statics/dashboard.css +30 -0
  18. data/lib/graphql/dashboard/statics/dashboard.js +143 -0
  19. data/lib/graphql/dashboard/statics/header-icon.png +0 -0
  20. data/lib/graphql/dashboard/statics/icon.png +0 -0
  21. data/lib/graphql/dashboard/subscriptions.rb +96 -0
  22. data/lib/graphql/dashboard/views/graphql/dashboard/detailed_traces/traces/index.html.erb +45 -0
  23. data/lib/graphql/dashboard/views/graphql/dashboard/landings/show.html.erb +18 -0
  24. data/lib/graphql/dashboard/views/graphql/dashboard/limiters/limiters/show.html.erb +62 -0
  25. data/lib/graphql/dashboard/views/graphql/dashboard/not_installed.html.erb +18 -0
  26. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +23 -0
  27. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +21 -0
  28. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +69 -0
  29. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +7 -0
  30. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +39 -0
  31. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/show.html.erb +32 -0
  32. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/index.html.erb +81 -0
  33. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +71 -0
  34. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/subscriptions/show.html.erb +41 -0
  35. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/index.html.erb +55 -0
  36. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +40 -0
  37. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +108 -0
  38. data/lib/graphql/dashboard.rb +158 -0
  39. data/lib/graphql/dataloader/active_record_association_source.rb +64 -0
  40. data/lib/graphql/dataloader/active_record_source.rb +26 -0
  41. data/lib/graphql/dataloader/async_dataloader.rb +21 -9
  42. data/lib/graphql/dataloader/null_dataloader.rb +1 -1
  43. data/lib/graphql/dataloader/source.rb +3 -3
  44. data/lib/graphql/dataloader.rb +43 -14
  45. data/lib/graphql/dig.rb +2 -1
  46. data/lib/graphql/execution/interpreter/resolve.rb +3 -3
  47. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +34 -4
  48. data/lib/graphql/execution/interpreter/runtime.rb +96 -52
  49. data/lib/graphql/execution/interpreter.rb +16 -7
  50. data/lib/graphql/execution/multiplex.rb +6 -5
  51. data/lib/graphql/introspection/directive_location_enum.rb +1 -1
  52. data/lib/graphql/invalid_name_error.rb +1 -1
  53. data/lib/graphql/invalid_null_error.rb +19 -16
  54. data/lib/graphql/language/cache.rb +13 -0
  55. data/lib/graphql/language/document_from_schema_definition.rb +8 -7
  56. data/lib/graphql/language/lexer.rb +11 -4
  57. data/lib/graphql/language/nodes.rb +3 -0
  58. data/lib/graphql/language/parser.rb +15 -8
  59. data/lib/graphql/language/printer.rb +8 -8
  60. data/lib/graphql/language/static_visitor.rb +37 -33
  61. data/lib/graphql/language/visitor.rb +59 -55
  62. data/lib/graphql/pagination/connection.rb +1 -1
  63. data/lib/graphql/query/context/scoped_context.rb +1 -1
  64. data/lib/graphql/query/context.rb +7 -5
  65. data/lib/graphql/query/variable_validation_error.rb +1 -1
  66. data/lib/graphql/query.rb +22 -32
  67. data/lib/graphql/railtie.rb +7 -0
  68. data/lib/graphql/schema/addition.rb +1 -1
  69. data/lib/graphql/schema/always_visible.rb +1 -0
  70. data/lib/graphql/schema/argument.rb +7 -8
  71. data/lib/graphql/schema/build_from_definition.rb +99 -53
  72. data/lib/graphql/schema/directive/flagged.rb +3 -1
  73. data/lib/graphql/schema/directive.rb +2 -2
  74. data/lib/graphql/schema/enum.rb +36 -1
  75. data/lib/graphql/schema/enum_value.rb +1 -1
  76. data/lib/graphql/schema/field/scope_extension.rb +1 -1
  77. data/lib/graphql/schema/field.rb +27 -13
  78. data/lib/graphql/schema/field_extension.rb +1 -1
  79. data/lib/graphql/schema/has_single_input_argument.rb +3 -1
  80. data/lib/graphql/schema/input_object.rb +77 -40
  81. data/lib/graphql/schema/interface.rb +3 -2
  82. data/lib/graphql/schema/list.rb +1 -1
  83. data/lib/graphql/schema/loader.rb +1 -1
  84. data/lib/graphql/schema/member/has_arguments.rb +25 -17
  85. data/lib/graphql/schema/member/has_dataloader.rb +62 -0
  86. data/lib/graphql/schema/member/has_deprecation_reason.rb +15 -0
  87. data/lib/graphql/schema/member/has_directives.rb +4 -4
  88. data/lib/graphql/schema/member/has_fields.rb +19 -1
  89. data/lib/graphql/schema/member/has_interfaces.rb +5 -5
  90. data/lib/graphql/schema/member/has_validators.rb +1 -1
  91. data/lib/graphql/schema/member/scoped.rb +1 -1
  92. data/lib/graphql/schema/member/type_system_helpers.rb +1 -1
  93. data/lib/graphql/schema/member.rb +1 -0
  94. data/lib/graphql/schema/object.rb +25 -8
  95. data/lib/graphql/schema/relay_classic_mutation.rb +0 -1
  96. data/lib/graphql/schema/resolver.rb +12 -10
  97. data/lib/graphql/schema/subscription.rb +52 -6
  98. data/lib/graphql/schema/union.rb +1 -1
  99. data/lib/graphql/schema/validator/required_validator.rb +23 -6
  100. data/lib/graphql/schema/validator.rb +1 -1
  101. data/lib/graphql/schema/visibility/migration.rb +1 -0
  102. data/lib/graphql/schema/visibility/profile.rb +98 -244
  103. data/lib/graphql/schema/visibility/visit.rb +190 -0
  104. data/lib/graphql/schema/visibility.rb +178 -38
  105. data/lib/graphql/schema/warden.rb +18 -5
  106. data/lib/graphql/schema.rb +266 -54
  107. data/lib/graphql/static_validation/all_rules.rb +1 -1
  108. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +1 -1
  109. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +47 -13
  110. data/lib/graphql/static_validation/rules/fields_will_merge.rb +79 -17
  111. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +10 -2
  112. data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +1 -1
  113. data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -0
  114. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
  115. data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +26 -0
  116. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +1 -1
  117. data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +1 -1
  118. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +1 -1
  119. data/lib/graphql/static_validation/validation_context.rb +1 -0
  120. data/lib/graphql/static_validation/validator.rb +6 -1
  121. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -1
  122. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +12 -10
  123. data/lib/graphql/subscriptions/event.rb +12 -1
  124. data/lib/graphql/subscriptions/serialize.rb +1 -1
  125. data/lib/graphql/subscriptions.rb +1 -1
  126. data/lib/graphql/testing/helpers.rb +7 -4
  127. data/lib/graphql/tracing/active_support_notifications_trace.rb +14 -3
  128. data/lib/graphql/tracing/active_support_notifications_tracing.rb +1 -1
  129. data/lib/graphql/tracing/appoptics_trace.rb +9 -1
  130. data/lib/graphql/tracing/appoptics_tracing.rb +7 -0
  131. data/lib/graphql/tracing/appsignal_trace.rb +32 -55
  132. data/lib/graphql/tracing/appsignal_tracing.rb +2 -0
  133. data/lib/graphql/tracing/call_legacy_tracers.rb +66 -0
  134. data/lib/graphql/tracing/data_dog_trace.rb +46 -158
  135. data/lib/graphql/tracing/data_dog_tracing.rb +2 -0
  136. data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
  137. data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
  138. data/lib/graphql/tracing/detailed_trace.rb +93 -0
  139. data/lib/graphql/tracing/legacy_hooks_trace.rb +1 -0
  140. data/lib/graphql/tracing/legacy_trace.rb +4 -61
  141. data/lib/graphql/tracing/monitor_trace.rb +283 -0
  142. data/lib/graphql/tracing/new_relic_trace.rb +47 -54
  143. data/lib/graphql/tracing/new_relic_tracing.rb +2 -0
  144. data/lib/graphql/tracing/notifications_trace.rb +182 -34
  145. data/lib/graphql/tracing/notifications_tracing.rb +2 -0
  146. data/lib/graphql/tracing/null_trace.rb +9 -0
  147. data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
  148. data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
  149. data/lib/graphql/tracing/perfetto_trace.rb +734 -0
  150. data/lib/graphql/tracing/platform_trace.rb +5 -0
  151. data/lib/graphql/tracing/prometheus_trace/graphql_collector.rb +2 -0
  152. data/lib/graphql/tracing/prometheus_trace.rb +72 -68
  153. data/lib/graphql/tracing/prometheus_tracing.rb +2 -0
  154. data/lib/graphql/tracing/scout_trace.rb +32 -55
  155. data/lib/graphql/tracing/scout_tracing.rb +2 -0
  156. data/lib/graphql/tracing/sentry_trace.rb +62 -94
  157. data/lib/graphql/tracing/statsd_trace.rb +33 -41
  158. data/lib/graphql/tracing/statsd_tracing.rb +2 -0
  159. data/lib/graphql/tracing/trace.rb +111 -1
  160. data/lib/graphql/tracing.rb +31 -30
  161. data/lib/graphql/types/relay/connection_behaviors.rb +3 -3
  162. data/lib/graphql/types/relay/edge_behaviors.rb +2 -2
  163. data/lib/graphql/types.rb +18 -11
  164. data/lib/graphql/version.rb +1 -1
  165. data/lib/graphql.rb +55 -47
  166. metadata +146 -11
  167. data/lib/graphql/backtrace/inspect_result.rb +0 -38
  168. data/lib/graphql/backtrace/trace.rb +0 -93
  169. data/lib/graphql/backtrace/tracer.rb +0 -80
  170. data/lib/graphql/schema/null_mask.rb +0 -11
  171. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +0 -17
@@ -0,0 +1,734 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module Tracing
4
+ # This produces a trace file for inspecting in the [Perfetto Trace Viewer](https://ui.perfetto.dev).
5
+ #
6
+ # To get the file, call {#write} on the trace.
7
+ #
8
+ # Use "trace modes" to configure this to run on command or on a sample of traffic.
9
+ #
10
+ # @example Writing trace output
11
+ #
12
+ # result = MySchema.execute(...)
13
+ # result.query.trace.write(file: "tmp/trace.dump")
14
+ #
15
+ # @example Running this instrumenter when `trace: true` is present in the request
16
+ #
17
+ # class MySchema < GraphQL::Schema
18
+ # # Only run this tracer when `context[:trace_mode]` is `:trace`
19
+ # trace_with GraphQL::Tracing::Perfetto, mode: :trace
20
+ # end
21
+ #
22
+ # # In graphql_controller.rb:
23
+ #
24
+ # context[:trace_mode] = params[:trace] ? :trace : nil
25
+ # result = MySchema.execute(query_str, context: context, variables: variables, ...)
26
+ # if context[:trace_mode] == :trace
27
+ # result.trace.write(file: ...)
28
+ # end
29
+ #
30
+ module PerfettoTrace
31
+ # TODOs:
32
+ # - Make debug annotations visible on both parts when dataloader is involved
33
+
34
+ PROTOBUF_AVAILABLE = begin
35
+ require "google/protobuf"
36
+ true
37
+ rescue LoadError
38
+ false
39
+ end
40
+
41
+ if PROTOBUF_AVAILABLE
42
+ require "graphql/tracing/perfetto_trace/trace_pb"
43
+ end
44
+
45
+ def self.included(_trace_class)
46
+ if !PROTOBUF_AVAILABLE
47
+ raise "#{self} can't be used because the `google-protobuf` gem wasn't available. Add it to your project, then try again."
48
+ end
49
+ end
50
+
51
+ DATALOADER_CATEGORY_IIDS = [5]
52
+ FIELD_EXECUTE_CATEGORY_IIDS = [6]
53
+ ACTIVE_SUPPORT_NOTIFICATIONS_CATEGORY_IIDS = [7]
54
+ AUTHORIZED_CATEGORY_IIDS = [8]
55
+ RESOLVE_TYPE_CATEGORY_IIDS = [9]
56
+
57
+ DA_OBJECT_IID = 10
58
+ DA_RESULT_IID = 11
59
+ DA_ARGUMENTS_IID = 12
60
+ DA_FETCH_KEYS_IID = 13
61
+ DA_STR_VAL_NIL_IID = 14
62
+
63
+ # @param active_support_notifications_pattern [String, RegExp, false] A filter for `ActiveSupport::Notifications`, if it's present. Or `false` to skip subscribing.
64
+ def initialize(active_support_notifications_pattern: nil, save_profile: false, **_rest)
65
+ super
66
+ @active_support_notifications_pattern = active_support_notifications_pattern
67
+ @save_profile = save_profile
68
+ Fiber[:graphql_flow_stack] = nil
69
+ @sequence_id = object_id
70
+ @pid = Process.pid
71
+ @flow_ids = Hash.new { |h, source_inst| h[source_inst] = [] }.compare_by_identity
72
+ @new_interned_event_names = {}
73
+ @interned_event_name_iids = Hash.new { |h, k|
74
+ new_id = 100 + h.size
75
+ @new_interned_event_names[k] = new_id
76
+ h[k] = new_id
77
+ }
78
+
79
+ @source_name_iids = Hash.new do |h, source_class|
80
+ h[source_class] = @interned_event_name_iids[source_class.name]
81
+ end.compare_by_identity
82
+
83
+ @auth_name_iids = Hash.new do |h, graphql_type|
84
+ h[graphql_type] = @interned_event_name_iids["Authorize: #{graphql_type.graphql_name}"]
85
+ end.compare_by_identity
86
+
87
+ @resolve_type_name_iids = Hash.new do |h, graphql_type|
88
+ h[graphql_type] = @interned_event_name_iids["Resolve Type: #{graphql_type.graphql_name}"]
89
+ end.compare_by_identity
90
+
91
+ @new_interned_da_names = {}
92
+ @interned_da_name_ids = Hash.new { |h, k|
93
+ next_id = 100 + h.size
94
+ @new_interned_da_names[k] = next_id
95
+ h[k] = next_id
96
+ }
97
+
98
+ @new_interned_da_string_values = {}
99
+ @interned_da_string_values = Hash.new do |h, k|
100
+ new_id = 100 + h.size
101
+ @new_interned_da_string_values[k] = new_id
102
+ h[k] = new_id
103
+ end
104
+
105
+ @class_name_iids = Hash.new do |h, k|
106
+ h[k] = @interned_da_string_values[k.name]
107
+ end.compare_by_identity
108
+
109
+ @starting_objects = GC.stat(:total_allocated_objects)
110
+ @objects_counter_id = :objects_counter.object_id
111
+ @fibers_counter_id = :fibers_counter.object_id
112
+ @fields_counter_id = :fields_counter.object_id
113
+ @begin_validate = nil
114
+ @begin_time = nil
115
+ @packets = []
116
+ @packets << TracePacket.new(
117
+ track_descriptor: TrackDescriptor.new(
118
+ uuid: tid,
119
+ name: "Main Thread",
120
+ child_ordering: TrackDescriptor::ChildTracksOrdering::CHRONOLOGICAL,
121
+ ),
122
+ first_packet_on_sequence: true,
123
+ previous_packet_dropped: true,
124
+ trusted_packet_sequence_id: @sequence_id,
125
+ sequence_flags: 3,
126
+ )
127
+ @packets << TracePacket.new(
128
+ interned_data: InternedData.new(
129
+ event_categories: [
130
+ EventCategory.new(name: "Dataloader", iid: DATALOADER_CATEGORY_IIDS.first),
131
+ EventCategory.new(name: "Field Execution", iid: FIELD_EXECUTE_CATEGORY_IIDS.first),
132
+ EventCategory.new(name: "ActiveSupport::Notifications", iid: ACTIVE_SUPPORT_NOTIFICATIONS_CATEGORY_IIDS.first),
133
+ EventCategory.new(name: "Authorized", iid: AUTHORIZED_CATEGORY_IIDS.first),
134
+ EventCategory.new(name: "Resolve Type", iid: RESOLVE_TYPE_CATEGORY_IIDS.first),
135
+ ],
136
+ debug_annotation_names: [
137
+ DebugAnnotationName.new(name: "object", iid: DA_OBJECT_IID),
138
+ DebugAnnotationName.new(name: "arguments", iid: DA_ARGUMENTS_IID),
139
+ DebugAnnotationName.new(name: "result", iid: DA_RESULT_IID),
140
+ DebugAnnotationName.new(name: "fetch keys", iid: DA_FETCH_KEYS_IID),
141
+ ],
142
+ debug_annotation_string_values: [
143
+ InternedString.new(str: "(nil)", iid: DA_STR_VAL_NIL_IID),
144
+ ],
145
+ ),
146
+ trusted_packet_sequence_id: @sequence_id,
147
+ sequence_flags: 2,
148
+ )
149
+ @main_fiber_id = fid
150
+ @packets << track_descriptor_packet(tid, fid, "Main Fiber")
151
+ @packets << track_descriptor_packet(tid, @objects_counter_id, "Allocated Objects", counter: {})
152
+ @packets << trace_packet(
153
+ type: TrackEvent::Type::TYPE_COUNTER,
154
+ track_uuid: @objects_counter_id,
155
+ counter_value: count_allocations,
156
+ )
157
+ @packets << track_descriptor_packet(tid, @fibers_counter_id, "Active Fibers", counter: {})
158
+ @fibers_count = 0
159
+ @packets << trace_packet(
160
+ type: TrackEvent::Type::TYPE_COUNTER,
161
+ track_uuid: @fibers_counter_id,
162
+ counter_value: count_fibers(0),
163
+ )
164
+
165
+ @packets << track_descriptor_packet(tid, @fields_counter_id, "Resolved Fields", counter: {})
166
+ @fields_count = -1
167
+ @packets << trace_packet(
168
+ type: TrackEvent::Type::TYPE_COUNTER,
169
+ track_uuid: @fields_counter_id,
170
+ counter_value: count_fields,
171
+ )
172
+ end
173
+
174
+ def execute_multiplex(multiplex:)
175
+ if defined?(ActiveSupport::Notifications) && @active_support_notifications_pattern != false
176
+ subscribe_to_active_support_notifications(@active_support_notifications_pattern)
177
+ end
178
+ @operation_name = multiplex.queries.map { |q| q.selected_operation_name || "anonymous" }.join(",")
179
+ @begin_time = Time.now
180
+ @packets << trace_packet(
181
+ type: TrackEvent::Type::TYPE_SLICE_BEGIN,
182
+ track_uuid: fid,
183
+ name: "Multiplex",
184
+ debug_annotations: [
185
+ payload_to_debug("query_string", multiplex.queries.map(&:sanitized_query_string).join("\n\n"))
186
+ ]
187
+ )
188
+ result = super
189
+
190
+ @packets << trace_packet(
191
+ type: TrackEvent::Type::TYPE_SLICE_END,
192
+ track_uuid: fid,
193
+ )
194
+
195
+ result
196
+ ensure
197
+ unsubscribe_from_active_support_notifications
198
+ if @save_profile
199
+ begin_ts = (@begin_time.to_f * 1000).round
200
+ end_ts = (Time.now.to_f * 1000).round
201
+ duration_ms = end_ts - begin_ts
202
+ multiplex.schema.detailed_trace.save_trace(@operation_name, duration_ms, begin_ts, Trace.encode(Trace.new(packet: @packets)))
203
+ end
204
+ end
205
+
206
+ def begin_execute_field(field, object, arguments, query)
207
+ packet = trace_packet(
208
+ type: TrackEvent::Type::TYPE_SLICE_BEGIN,
209
+ track_uuid: fid,
210
+ name: query.context.current_path.join("."),
211
+ category_iids: FIELD_EXECUTE_CATEGORY_IIDS,
212
+ extra_counter_track_uuids: [@objects_counter_id],
213
+ extra_counter_values: [count_allocations],
214
+ )
215
+ @packets << packet
216
+ fiber_flow_stack << packet
217
+ super
218
+ end
219
+
220
+ def end_execute_field(field, object, arguments, query, app_result)
221
+ start_field = fiber_flow_stack.pop
222
+ start_field.track_event = dup_with(start_field.track_event, {
223
+ debug_annotations: [
224
+ payload_to_debug(nil, object.object, iid: DA_OBJECT_IID, intern_value: true),
225
+ payload_to_debug(nil, arguments, iid: DA_ARGUMENTS_IID),
226
+ payload_to_debug(nil, app_result, iid: DA_RESULT_IID, intern_value: true)
227
+ ]
228
+ })
229
+
230
+ @packets << trace_packet(
231
+ type: TrackEvent::Type::TYPE_SLICE_END,
232
+ track_uuid: fid,
233
+ extra_counter_track_uuids: [@objects_counter_id, @fields_counter_id],
234
+ extra_counter_values: [count_allocations, count_fields],
235
+ )
236
+ super
237
+ end
238
+
239
+ def begin_analyze_multiplex(m, analyzers)
240
+ @packets << trace_packet(
241
+ type: TrackEvent::Type::TYPE_SLICE_BEGIN,
242
+ track_uuid: fid,
243
+ extra_counter_track_uuids: [@objects_counter_id],
244
+ extra_counter_values: [count_allocations],
245
+ name: "Analysis",
246
+ debug_annotations: [
247
+ payload_to_debug("analyzers_count", analyzers.size),
248
+ payload_to_debug("analyzers", analyzers),
249
+ ]
250
+ )
251
+ super
252
+ end
253
+
254
+ def end_analyze_multiplex(m, analyzers)
255
+ @packets << trace_packet(
256
+ type: TrackEvent::Type::TYPE_SLICE_END,
257
+ track_uuid: fid,
258
+ extra_counter_track_uuids: [@objects_counter_id],
259
+ extra_counter_values: [count_allocations],
260
+ )
261
+ super
262
+ end
263
+
264
+ def parse(query_string:)
265
+ @packets << trace_packet(
266
+ type: TrackEvent::Type::TYPE_SLICE_BEGIN,
267
+ track_uuid: fid,
268
+ extra_counter_track_uuids: [@objects_counter_id],
269
+ extra_counter_values: [count_allocations],
270
+ name: "Parse"
271
+ )
272
+ result = super
273
+ @packets << trace_packet(
274
+ type: TrackEvent::Type::TYPE_SLICE_END,
275
+ track_uuid: fid,
276
+ extra_counter_track_uuids: [@objects_counter_id],
277
+ extra_counter_values: [count_allocations],
278
+ )
279
+ result
280
+ end
281
+
282
+ def begin_validate(query, validate)
283
+ @packets << @begin_validate = trace_packet(
284
+ type: TrackEvent::Type::TYPE_SLICE_BEGIN,
285
+ track_uuid: fid,
286
+ extra_counter_track_uuids: [@objects_counter_id],
287
+ extra_counter_values: [count_allocations],
288
+ name: "Validate",
289
+ debug_annotations: [
290
+ payload_to_debug("validate?", validate),
291
+ ]
292
+ )
293
+ super
294
+ end
295
+
296
+ def end_validate(query, validate, validation_errors)
297
+ @packets << trace_packet(
298
+ type: TrackEvent::Type::TYPE_SLICE_END,
299
+ track_uuid: fid,
300
+ extra_counter_track_uuids: [@objects_counter_id],
301
+ extra_counter_values: [count_allocations],
302
+ )
303
+ @begin_validate.track_event = dup_with(
304
+ @begin_validate.track_event,
305
+ {
306
+ debug_annotations: [
307
+ @begin_validate.track_event.debug_annotations.first,
308
+ payload_to_debug("valid?", validation_errors.empty?)
309
+ ]
310
+ }
311
+ )
312
+ super
313
+ end
314
+
315
+ def dataloader_spawn_execution_fiber(jobs)
316
+ @packets << trace_packet(
317
+ type: TrackEvent::Type::TYPE_INSTANT,
318
+ track_uuid: fid,
319
+ name: "Create Execution Fiber",
320
+ category_iids: DATALOADER_CATEGORY_IIDS,
321
+ extra_counter_track_uuids: [@fibers_counter_id, @objects_counter_id],
322
+ extra_counter_values: [count_fibers(1), count_allocations]
323
+ )
324
+ @packets << track_descriptor_packet(@did, fid, "Exec Fiber ##{fid}")
325
+ super
326
+ end
327
+
328
+ def dataloader_spawn_source_fiber(pending_sources)
329
+ @packets << trace_packet(
330
+ type: TrackEvent::Type::TYPE_INSTANT,
331
+ track_uuid: fid,
332
+ name: "Create Source Fiber",
333
+ category_iids: DATALOADER_CATEGORY_IIDS,
334
+ extra_counter_track_uuids: [@fibers_counter_id, @objects_counter_id],
335
+ extra_counter_values: [count_fibers(1), count_allocations]
336
+ )
337
+ @packets << track_descriptor_packet(@did, fid, "Source Fiber ##{fid}")
338
+ super
339
+ end
340
+
341
+ def dataloader_fiber_yield(source)
342
+ ls = fiber_flow_stack.last
343
+ if (flow_id = ls.track_event.flow_ids.first)
344
+ # got it
345
+ else
346
+ flow_id = ls.track_event.name.object_id
347
+ ls.track_event = dup_with(ls.track_event, {flow_ids: [flow_id] }, delete_counters: true)
348
+ end
349
+ @flow_ids[source] << flow_id
350
+ @packets << trace_packet(
351
+ type: TrackEvent::Type::TYPE_SLICE_END,
352
+ track_uuid: fid,
353
+ )
354
+ @packets << trace_packet(
355
+ type: TrackEvent::Type::TYPE_INSTANT,
356
+ track_uuid: fid,
357
+ name: "Fiber Yield",
358
+ category_iids: DATALOADER_CATEGORY_IIDS,
359
+ )
360
+ super
361
+ end
362
+
363
+ def dataloader_fiber_resume(source)
364
+ @packets << trace_packet(
365
+ type: TrackEvent::Type::TYPE_INSTANT,
366
+ track_uuid: fid,
367
+ name: "Fiber Resume",
368
+ category_iids: DATALOADER_CATEGORY_IIDS,
369
+ )
370
+
371
+ ls = fiber_flow_stack.pop
372
+ @packets << packet = TracePacket.new(
373
+ timestamp: ts,
374
+ track_event: dup_with(ls.track_event, { type: TrackEvent::Type::TYPE_SLICE_BEGIN }),
375
+ trusted_packet_sequence_id: @sequence_id,
376
+ )
377
+ fiber_flow_stack << packet
378
+
379
+ super
380
+ end
381
+
382
+ def dataloader_fiber_exit
383
+ @packets << trace_packet(
384
+ type: TrackEvent::Type::TYPE_INSTANT,
385
+ track_uuid: fid,
386
+ name: "Fiber Exit",
387
+ category_iids: DATALOADER_CATEGORY_IIDS,
388
+ extra_counter_track_uuids: [@fibers_counter_id],
389
+ extra_counter_values: [count_fibers(-1)],
390
+ )
391
+ super
392
+ end
393
+
394
+ def begin_dataloader(dl)
395
+ @packets << trace_packet(
396
+ type: TrackEvent::Type::TYPE_COUNTER,
397
+ track_uuid: @fibers_counter_id,
398
+ counter_value: count_fibers(1),
399
+ )
400
+ @did = fid
401
+ @packets << track_descriptor_packet(@main_fiber_id, @did, "Dataloader Fiber ##{@did}")
402
+ super
403
+ end
404
+
405
+ def end_dataloader(dl)
406
+ @packets << trace_packet(
407
+ type: TrackEvent::Type::TYPE_COUNTER,
408
+ track_uuid: @fibers_counter_id,
409
+ counter_value: count_fibers(-1),
410
+ )
411
+ super
412
+ end
413
+
414
+ def begin_dataloader_source(source)
415
+ fds = @flow_ids[source]
416
+ fds_copy = fds.dup
417
+ fds.clear
418
+ packet = trace_packet(
419
+ type: TrackEvent::Type::TYPE_SLICE_BEGIN,
420
+ track_uuid: fid,
421
+ name_iid: @source_name_iids[source.class],
422
+ category_iids: DATALOADER_CATEGORY_IIDS,
423
+ flow_ids: fds_copy,
424
+ extra_counter_track_uuids: [@objects_counter_id],
425
+ extra_counter_values: [count_allocations],
426
+ debug_annotations: [
427
+ payload_to_debug(nil, source.pending.values, iid: DA_FETCH_KEYS_IID, intern_value: true),
428
+ *(source.instance_variables - [:@pending, :@fetching, :@results, :@dataloader]).map { |iv|
429
+ payload_to_debug(iv.to_s, source.instance_variable_get(iv), intern_value: true)
430
+ }
431
+ ]
432
+ )
433
+ @packets << packet
434
+ fiber_flow_stack << packet
435
+ super
436
+ end
437
+
438
+ def end_dataloader_source(source)
439
+ @packets << trace_packet(
440
+ type: TrackEvent::Type::TYPE_SLICE_END,
441
+ track_uuid: fid,
442
+ extra_counter_track_uuids: [@objects_counter_id],
443
+ extra_counter_values: [count_allocations],
444
+ )
445
+ fiber_flow_stack.pop
446
+ super
447
+ end
448
+
449
+ def begin_authorized(type, obj, ctx)
450
+ packet = trace_packet(
451
+ type: TrackEvent::Type::TYPE_SLICE_BEGIN,
452
+ track_uuid: fid,
453
+ category_iids: AUTHORIZED_CATEGORY_IIDS,
454
+ extra_counter_track_uuids: [@objects_counter_id],
455
+ extra_counter_values: [count_allocations],
456
+ name_iid: @auth_name_iids[type],
457
+ )
458
+ @packets << packet
459
+ fiber_flow_stack << packet
460
+ super
461
+ end
462
+
463
+ def end_authorized(type, obj, ctx, is_authorized)
464
+ @packets << trace_packet(
465
+ type: TrackEvent::Type::TYPE_SLICE_END,
466
+ track_uuid: fid,
467
+ extra_counter_track_uuids: [@objects_counter_id],
468
+ extra_counter_values: [count_allocations],
469
+ )
470
+ beg_auth = fiber_flow_stack.pop
471
+ beg_auth.track_event = dup_with(beg_auth.track_event, { debug_annotations: [payload_to_debug("authorized?", is_authorized)] })
472
+ super
473
+ end
474
+
475
+ def begin_resolve_type(type, value, context)
476
+ packet = trace_packet(
477
+ type: TrackEvent::Type::TYPE_SLICE_BEGIN,
478
+ track_uuid: fid,
479
+ category_iids: RESOLVE_TYPE_CATEGORY_IIDS,
480
+ extra_counter_track_uuids: [@objects_counter_id],
481
+ extra_counter_values: [count_allocations],
482
+ name_iid: @resolve_type_name_iids[type],
483
+ )
484
+ @packets << packet
485
+ fiber_flow_stack << packet
486
+ super
487
+ end
488
+
489
+ def end_resolve_type(type, value, context, resolved_type)
490
+ @packets << trace_packet(
491
+ type: TrackEvent::Type::TYPE_SLICE_END,
492
+ track_uuid: fid,
493
+ extra_counter_track_uuids: [@objects_counter_id],
494
+ extra_counter_values: [count_allocations],
495
+ )
496
+ rt_begin = fiber_flow_stack.pop
497
+ rt_begin.track_event = dup_with(rt_begin.track_event, { debug_annotations: [payload_to_debug("resolved_type", resolved_type, intern_value: true)] })
498
+ super
499
+ end
500
+
501
+ # Dump protobuf output in the specified file.
502
+ # @param file [String] path to a file in a directory that already exists
503
+ # @param debug_json [Boolean] True to print JSON instead of binary
504
+ # @return [nil, String, Hash] If `file` was given, `nil`. If `file` was `nil`, a Hash if `debug_json: true`, else binary data.
505
+ def write(file:, debug_json: false)
506
+ trace = Trace.new(
507
+ packet: @packets,
508
+ )
509
+ data = if debug_json
510
+ small_json = Trace.encode_json(trace)
511
+ JSON.pretty_generate(JSON.parse(small_json))
512
+ else
513
+ Trace.encode(trace)
514
+ end
515
+
516
+ if file
517
+ File.write(file, data, mode: 'wb')
518
+ nil
519
+ else
520
+ data
521
+ end
522
+ end
523
+
524
+ private
525
+
526
+ def ts
527
+ Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
528
+ end
529
+
530
+ def tid
531
+ Thread.current.object_id
532
+ end
533
+
534
+ def fid
535
+ Fiber.current.object_id
536
+ end
537
+
538
+ def debug_annotation(iid, value_key, value)
539
+ if iid
540
+ DebugAnnotation.new(name_iid: iid, value_key => value)
541
+ else
542
+ DebugAnnotation.new(value_key => value)
543
+ end
544
+ end
545
+
546
+ def payload_to_debug(k, v, iid: nil, intern_value: false)
547
+ if iid.nil?
548
+ iid = @interned_da_name_ids[k]
549
+ k = nil
550
+ end
551
+ case v
552
+ when String
553
+ if intern_value
554
+ v = @interned_da_string_values[v]
555
+ debug_annotation(iid, :string_value_iid, v)
556
+ else
557
+ debug_annotation(iid, :string_value, v)
558
+ end
559
+ when Float
560
+ debug_annotation(iid, :double_value, v)
561
+ when Integer
562
+ debug_annotation(iid, :int_value, v)
563
+ when true, false
564
+ debug_annotation(iid, :bool_value, v)
565
+ when nil
566
+ if iid
567
+ DebugAnnotation.new(name_iid: iid, string_value_iid: DA_STR_VAL_NIL_IID)
568
+ else
569
+ DebugAnnotation.new(name: k, string_value_iid: DA_STR_VAL_NIL_IID)
570
+ end
571
+ when Module
572
+ if intern_value
573
+ val_iid = @class_name_iids[v]
574
+ debug_annotation(iid, :string_value_iid, val_iid)
575
+ else
576
+ debug_annotation(iid, :string_value, v.name)
577
+ end
578
+ when Symbol
579
+ debug_annotation(iid, :string_value, v.inspect)
580
+ when Array
581
+ debug_annotation(iid, :array_values, v.map { |v2| payload_to_debug(nil, v2, intern_value: intern_value) }.compact)
582
+ when Hash
583
+ debug_annotation(iid, :dict_entries, v.map { |k2, v2| payload_to_debug(k2, v2, intern_value: intern_value) }.compact)
584
+ else
585
+ debug_str = if defined?(ActiveRecord::Relation) && v.is_a?(ActiveRecord::Relation)
586
+ "#{v.class}, .to_sql=#{v.to_sql.inspect}"
587
+ else
588
+ v.inspect
589
+ end
590
+ if intern_value
591
+ str_iid = @interned_da_string_values[debug_str]
592
+ debug_annotation(iid, :string_value_iid, str_iid)
593
+ else
594
+ debug_annotation(iid, :string_value, debug_str)
595
+ end
596
+ end
597
+ end
598
+
599
+ def count_allocations
600
+ GC.stat(:total_allocated_objects) - @starting_objects
601
+ end
602
+
603
+ def count_fibers(diff)
604
+ @fibers_count += diff
605
+ end
606
+
607
+ def count_fields
608
+ @fields_count += 1
609
+ end
610
+
611
+ def dup_with(message, attrs, delete_counters: false)
612
+ new_attrs = message.to_h
613
+ if delete_counters
614
+ new_attrs.delete(:extra_counter_track_uuids)
615
+ new_attrs.delete(:extra_counter_values)
616
+ end
617
+ new_attrs.merge!(attrs)
618
+ message.class.new(**new_attrs)
619
+ end
620
+
621
+ def fiber_flow_stack
622
+ Fiber[:graphql_flow_stack] ||= []
623
+ end
624
+
625
+ def trace_packet(event_attrs)
626
+ TracePacket.new(
627
+ timestamp: ts,
628
+ track_event: TrackEvent.new(event_attrs),
629
+ trusted_packet_sequence_id: @sequence_id,
630
+ sequence_flags: 2,
631
+ interned_data: new_interned_data
632
+ )
633
+ end
634
+
635
+ def new_interned_data
636
+ if !@new_interned_da_names.empty?
637
+ da_names = @new_interned_da_names.map { |(name, iid)| DebugAnnotationName.new(iid: iid, name: name) }
638
+ @new_interned_da_names.clear
639
+ end
640
+
641
+ if !@new_interned_event_names.empty?
642
+ ev_names = @new_interned_event_names.map { |(name, iid)| EventName.new(iid: iid, name: name) }
643
+ @new_interned_event_names.clear
644
+ end
645
+
646
+ if !@new_interned_da_string_values.empty?
647
+ str_vals = @new_interned_da_string_values.map { |name, iid| InternedString.new(iid: iid, str: name) }
648
+ @new_interned_da_string_values.clear
649
+ end
650
+
651
+ if ev_names || da_names || str_vals
652
+ InternedData.new(
653
+ event_names: ev_names,
654
+ debug_annotation_names: da_names,
655
+ debug_annotation_string_values: str_vals,
656
+ )
657
+ else
658
+ nil
659
+ end
660
+ end
661
+
662
+ def track_descriptor_packet(parent_uuid, uuid, name, counter: nil)
663
+ td = if counter
664
+ TrackDescriptor.new(
665
+ parent_uuid: parent_uuid,
666
+ uuid: uuid,
667
+ name: name,
668
+ counter: counter
669
+ )
670
+ else
671
+ TrackDescriptor.new(
672
+ parent_uuid: parent_uuid,
673
+ uuid: uuid,
674
+ name: name,
675
+ child_ordering: TrackDescriptor::ChildTracksOrdering::CHRONOLOGICAL,
676
+ )
677
+ end
678
+ TracePacket.new(
679
+ track_descriptor: td,
680
+ trusted_packet_sequence_id: @sequence_id,
681
+ sequence_flags: 2,
682
+ )
683
+ end
684
+
685
+ def unsubscribe_from_active_support_notifications
686
+ if defined?(@as_subscriber)
687
+ ActiveSupport::Notifications.unsubscribe(@as_subscriber)
688
+ end
689
+ end
690
+
691
+ def subscribe_to_active_support_notifications(pattern)
692
+ @as_subscriber = ActiveSupport::Notifications.monotonic_subscribe(pattern) do |name, start, finish, id, payload|
693
+ metadata = payload.map { |k, v| payload_to_debug(k, v, intern_value: true) }
694
+ metadata.compact!
695
+ te = if metadata.empty?
696
+ TrackEvent.new(
697
+ type: TrackEvent::Type::TYPE_SLICE_BEGIN,
698
+ track_uuid: fid,
699
+ category_iids: ACTIVE_SUPPORT_NOTIFICATIONS_CATEGORY_IIDS,
700
+ name: name,
701
+ )
702
+ else
703
+ TrackEvent.new(
704
+ type: TrackEvent::Type::TYPE_SLICE_BEGIN,
705
+ track_uuid: fid,
706
+ name: name,
707
+ category_iids: ACTIVE_SUPPORT_NOTIFICATIONS_CATEGORY_IIDS,
708
+ debug_annotations: metadata,
709
+ )
710
+ end
711
+ @packets << TracePacket.new(
712
+ timestamp: (start * 1_000_000_000).to_i,
713
+ track_event: te,
714
+ trusted_packet_sequence_id: @sequence_id,
715
+ sequence_flags: 2,
716
+ interned_data: new_interned_data
717
+ )
718
+ @packets << TracePacket.new(
719
+ timestamp: (finish * 1_000_000_000).to_i,
720
+ track_event: TrackEvent.new(
721
+ type: TrackEvent::Type::TYPE_SLICE_END,
722
+ track_uuid: fid,
723
+ name: name,
724
+ extra_counter_track_uuids: [@objects_counter_id],
725
+ extra_counter_values: [count_allocations]
726
+ ),
727
+ trusted_packet_sequence_id: @sequence_id,
728
+ sequence_flags: 2,
729
+ )
730
+ end
731
+ end
732
+ end
733
+ end
734
+ end