graphql 2.4.9 → 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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/current.rb +5 -0
  3. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.css +6 -0
  4. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js +7 -0
  5. data/lib/graphql/dashboard/statics/dashboard.css +3 -0
  6. data/lib/graphql/dashboard/statics/dashboard.js +78 -0
  7. data/lib/graphql/dashboard/statics/header-icon.png +0 -0
  8. data/lib/graphql/dashboard/statics/icon.png +0 -0
  9. data/lib/graphql/dashboard/views/graphql/dashboard/landings/show.html.erb +18 -0
  10. data/lib/graphql/dashboard/views/graphql/dashboard/traces/index.html.erb +63 -0
  11. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +60 -0
  12. data/lib/graphql/dashboard.rb +142 -0
  13. data/lib/graphql/dataloader/active_record_association_source.rb +64 -0
  14. data/lib/graphql/dataloader/active_record_source.rb +26 -0
  15. data/lib/graphql/dataloader/async_dataloader.rb +17 -5
  16. data/lib/graphql/dataloader/null_dataloader.rb +1 -1
  17. data/lib/graphql/dataloader/source.rb +2 -2
  18. data/lib/graphql/dataloader.rb +37 -5
  19. data/lib/graphql/execution/interpreter/runtime.rb +26 -7
  20. data/lib/graphql/execution/interpreter.rb +9 -1
  21. data/lib/graphql/invalid_name_error.rb +1 -1
  22. data/lib/graphql/invalid_null_error.rb +6 -12
  23. data/lib/graphql/language/parser.rb +1 -1
  24. data/lib/graphql/query.rb +8 -4
  25. data/lib/graphql/schema/build_from_definition.rb +0 -1
  26. data/lib/graphql/schema/enum.rb +17 -2
  27. data/lib/graphql/schema/input_object.rb +1 -1
  28. data/lib/graphql/schema/interface.rb +1 -0
  29. data/lib/graphql/schema/member/has_dataloader.rb +60 -0
  30. data/lib/graphql/schema/member.rb +1 -0
  31. data/lib/graphql/schema/object.rb +17 -8
  32. data/lib/graphql/schema/resolver.rb +1 -5
  33. data/lib/graphql/schema/visibility/profile.rb +4 -4
  34. data/lib/graphql/schema/visibility.rb +14 -9
  35. data/lib/graphql/schema.rb +52 -10
  36. data/lib/graphql/static_validation/validator.rb +6 -1
  37. data/lib/graphql/tracing/active_support_notifications_trace.rb +6 -2
  38. data/lib/graphql/tracing/appoptics_trace.rb +3 -1
  39. data/lib/graphql/tracing/appsignal_trace.rb +6 -0
  40. data/lib/graphql/tracing/data_dog_trace.rb +5 -0
  41. data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
  42. data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
  43. data/lib/graphql/tracing/detailed_trace.rb +93 -0
  44. data/lib/graphql/tracing/new_relic_trace.rb +147 -41
  45. data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
  46. data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
  47. data/lib/graphql/tracing/perfetto_trace.rb +737 -0
  48. data/lib/graphql/tracing/prometheus_trace.rb +22 -0
  49. data/lib/graphql/tracing/scout_trace.rb +6 -0
  50. data/lib/graphql/tracing/sentry_trace.rb +5 -0
  51. data/lib/graphql/tracing/statsd_trace.rb +9 -0
  52. data/lib/graphql/tracing/trace.rb +124 -0
  53. data/lib/graphql/tracing.rb +2 -0
  54. data/lib/graphql/version.rb +1 -1
  55. data/lib/graphql.rb +3 -0
  56. metadata +49 -3
  57. data/lib/graphql/schema/null_mask.rb +0 -11
@@ -0,0 +1,737 @@
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
+ @save_profile = save_profile
67
+ Fiber[:graphql_flow_stack] = nil
68
+ @sequence_id = object_id
69
+ @pid = Process.pid
70
+ @flow_ids = Hash.new { |h, source_inst| h[source_inst] = [] }.compare_by_identity
71
+ @new_interned_event_names = {}
72
+ @interned_event_name_iids = Hash.new { |h, k|
73
+ new_id = 100 + h.size
74
+ @new_interned_event_names[k] = new_id
75
+ h[k] = new_id
76
+ }
77
+
78
+ @source_name_iids = Hash.new do |h, source_class|
79
+ h[source_class] = @interned_event_name_iids[source_class.name]
80
+ end.compare_by_identity
81
+
82
+ @auth_name_iids = Hash.new do |h, graphql_type|
83
+ h[graphql_type] = @interned_event_name_iids["Authorize: #{graphql_type.graphql_name}"]
84
+ end.compare_by_identity
85
+
86
+ @resolve_type_name_iids = Hash.new do |h, graphql_type|
87
+ h[graphql_type] = @interned_event_name_iids["Resolve Type: #{graphql_type.graphql_name}"]
88
+ end.compare_by_identity
89
+
90
+ @new_interned_da_names = {}
91
+ @interned_da_name_ids = Hash.new { |h, k|
92
+ next_id = 100 + h.size
93
+ @new_interned_da_names[k] = next_id
94
+ h[k] = next_id
95
+ }
96
+
97
+ @new_interned_da_string_values = {}
98
+ @interned_da_string_values = Hash.new do |h, k|
99
+ new_id = 100 + h.size
100
+ @new_interned_da_string_values[k] = new_id
101
+ h[k] = new_id
102
+ end
103
+
104
+ @class_name_iids = Hash.new do |h, k|
105
+ h[k] = @interned_da_string_values[k.name]
106
+ end.compare_by_identity
107
+
108
+ @starting_objects = GC.stat(:total_allocated_objects)
109
+ @objects_counter_id = :objects_counter.object_id
110
+ @fibers_counter_id = :fibers_counter.object_id
111
+ @fields_counter_id = :fields_counter.object_id
112
+ @begin_validate = nil
113
+ @begin_time = nil
114
+ @packets = []
115
+ @packets << TracePacket.new(
116
+ track_descriptor: TrackDescriptor.new(
117
+ uuid: tid,
118
+ name: "Main Thread",
119
+ child_ordering: TrackDescriptor::ChildTracksOrdering::CHRONOLOGICAL,
120
+ ),
121
+ first_packet_on_sequence: true,
122
+ previous_packet_dropped: true,
123
+ trusted_packet_sequence_id: @sequence_id,
124
+ sequence_flags: 3,
125
+ )
126
+ @packets << TracePacket.new(
127
+ interned_data: InternedData.new(
128
+ event_categories: [
129
+ EventCategory.new(name: "Dataloader", iid: DATALOADER_CATEGORY_IIDS.first),
130
+ EventCategory.new(name: "Field Execution", iid: FIELD_EXECUTE_CATEGORY_IIDS.first),
131
+ EventCategory.new(name: "ActiveSupport::Notifications", iid: ACTIVE_SUPPORT_NOTIFICATIONS_CATEGORY_IIDS.first),
132
+ EventCategory.new(name: "Authorized", iid: AUTHORIZED_CATEGORY_IIDS.first),
133
+ EventCategory.new(name: "Resolve Type", iid: RESOLVE_TYPE_CATEGORY_IIDS.first),
134
+ ],
135
+ debug_annotation_names: [
136
+ DebugAnnotationName.new(name: "object", iid: DA_OBJECT_IID),
137
+ DebugAnnotationName.new(name: "arguments", iid: DA_ARGUMENTS_IID),
138
+ DebugAnnotationName.new(name: "result", iid: DA_RESULT_IID),
139
+ DebugAnnotationName.new(name: "fetch keys", iid: DA_FETCH_KEYS_IID),
140
+ ],
141
+ debug_annotation_string_values: [
142
+ InternedString.new(str: "(nil)", iid: DA_STR_VAL_NIL_IID),
143
+ ],
144
+ ),
145
+ trusted_packet_sequence_id: @sequence_id,
146
+ sequence_flags: 2,
147
+ )
148
+ @main_fiber_id = fid
149
+ @packets << track_descriptor_packet(tid, fid, "Main Fiber")
150
+ @packets << track_descriptor_packet(tid, @objects_counter_id, "Allocated Objects", counter: {})
151
+ @packets << trace_packet(
152
+ type: TrackEvent::Type::TYPE_COUNTER,
153
+ track_uuid: @objects_counter_id,
154
+ counter_value: count_allocations,
155
+ )
156
+ @packets << track_descriptor_packet(tid, @fibers_counter_id, "Active Fibers", counter: {})
157
+ @fibers_count = 0
158
+ @packets << trace_packet(
159
+ type: TrackEvent::Type::TYPE_COUNTER,
160
+ track_uuid: @fibers_counter_id,
161
+ counter_value: count_fibers(0),
162
+ )
163
+
164
+ @packets << track_descriptor_packet(tid, @fields_counter_id, "Resolved Fields", counter: {})
165
+ @fields_count = -1
166
+ @packets << trace_packet(
167
+ type: TrackEvent::Type::TYPE_COUNTER,
168
+ track_uuid: @fields_counter_id,
169
+ counter_value: count_fields,
170
+ )
171
+
172
+ if defined?(ActiveSupport::Notifications) && active_support_notifications_pattern != false
173
+ subscribe_to_active_support_notifications(active_support_notifications_pattern)
174
+ end
175
+ end
176
+
177
+ def begin_execute_multiplex(m)
178
+ @operation_name = m.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", m.queries.map(&:sanitized_query_string).join("\n\n"))
186
+ ]
187
+ )
188
+ super
189
+ end
190
+
191
+ def end_execute_multiplex(m)
192
+ @packets << trace_packet(
193
+ type: TrackEvent::Type::TYPE_SLICE_END,
194
+ track_uuid: fid,
195
+ )
196
+ unsubscribe_from_active_support_notifications
197
+ if @save_profile
198
+ begin_ts = (@begin_time.to_f * 1000).round
199
+ end_ts = (Time.now.to_f * 1000).round
200
+ duration_ms = end_ts - begin_ts
201
+ m.schema.detailed_trace.save_trace(@operation_name, duration_ms, begin_ts, Trace.encode(Trace.new(packet: @packets)))
202
+ end
203
+ super
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 begin_parse(str)
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
+ super
273
+ end
274
+
275
+ def end_parse(str)
276
+ @packets << trace_packet(
277
+ type: TrackEvent::Type::TYPE_SLICE_END,
278
+ track_uuid: fid,
279
+ extra_counter_track_uuids: [@objects_counter_id],
280
+ extra_counter_values: [count_allocations],
281
+ )
282
+ super
283
+ end
284
+
285
+ def begin_validate(query, validate)
286
+ @packets << @begin_validate = trace_packet(
287
+ type: TrackEvent::Type::TYPE_SLICE_BEGIN,
288
+ track_uuid: fid,
289
+ extra_counter_track_uuids: [@objects_counter_id],
290
+ extra_counter_values: [count_allocations],
291
+ name: "Validate",
292
+ debug_annotations: [
293
+ payload_to_debug("validate?", validate),
294
+ ]
295
+ )
296
+ super
297
+ end
298
+
299
+ def end_validate(query, validate, validation_errors)
300
+ @packets << trace_packet(
301
+ type: TrackEvent::Type::TYPE_SLICE_END,
302
+ track_uuid: fid,
303
+ extra_counter_track_uuids: [@objects_counter_id],
304
+ extra_counter_values: [count_allocations],
305
+ )
306
+ @begin_validate.track_event = dup_with(
307
+ @begin_validate.track_event,
308
+ {
309
+ debug_annotations: [
310
+ @begin_validate.track_event.debug_annotations.first,
311
+ payload_to_debug("valid?", validation_errors.empty?)
312
+ ]
313
+ }
314
+ )
315
+ super
316
+ end
317
+
318
+ def dataloader_spawn_execution_fiber(jobs)
319
+ @packets << trace_packet(
320
+ type: TrackEvent::Type::TYPE_INSTANT,
321
+ track_uuid: fid,
322
+ name: "Create Execution Fiber",
323
+ category_iids: DATALOADER_CATEGORY_IIDS,
324
+ extra_counter_track_uuids: [@fibers_counter_id, @objects_counter_id],
325
+ extra_counter_values: [count_fibers(1), count_allocations]
326
+ )
327
+ @packets << track_descriptor_packet(@did, fid, "Exec Fiber ##{fid}")
328
+ super
329
+ end
330
+
331
+ def dataloader_spawn_source_fiber(pending_sources)
332
+ @packets << trace_packet(
333
+ type: TrackEvent::Type::TYPE_INSTANT,
334
+ track_uuid: fid,
335
+ name: "Create Source Fiber",
336
+ category_iids: DATALOADER_CATEGORY_IIDS,
337
+ extra_counter_track_uuids: [@fibers_counter_id, @objects_counter_id],
338
+ extra_counter_values: [count_fibers(1), count_allocations]
339
+ )
340
+ @packets << track_descriptor_packet(@did, fid, "Source Fiber ##{fid}")
341
+ super
342
+ end
343
+
344
+ def dataloader_fiber_yield(source)
345
+ ls = fiber_flow_stack.last
346
+ if (flow_id = ls.track_event.flow_ids.first)
347
+ # got it
348
+ else
349
+ flow_id = ls.track_event.name.object_id
350
+ ls.track_event = dup_with(ls.track_event, {flow_ids: [flow_id] }, delete_counters: true)
351
+ end
352
+ @flow_ids[source] << flow_id
353
+ @packets << trace_packet(
354
+ type: TrackEvent::Type::TYPE_SLICE_END,
355
+ track_uuid: fid,
356
+ )
357
+ @packets << trace_packet(
358
+ type: TrackEvent::Type::TYPE_INSTANT,
359
+ track_uuid: fid,
360
+ name: "Fiber Yield",
361
+ category_iids: DATALOADER_CATEGORY_IIDS,
362
+ )
363
+ super
364
+ end
365
+
366
+ def dataloader_fiber_resume(source)
367
+ @packets << trace_packet(
368
+ type: TrackEvent::Type::TYPE_INSTANT,
369
+ track_uuid: fid,
370
+ name: "Fiber Resume",
371
+ category_iids: DATALOADER_CATEGORY_IIDS,
372
+ )
373
+
374
+ ls = fiber_flow_stack.pop
375
+ @packets << packet = TracePacket.new(
376
+ timestamp: ts,
377
+ track_event: dup_with(ls.track_event, { type: TrackEvent::Type::TYPE_SLICE_BEGIN }),
378
+ trusted_packet_sequence_id: @sequence_id,
379
+ )
380
+ fiber_flow_stack << packet
381
+
382
+ super
383
+ end
384
+
385
+ def dataloader_fiber_exit
386
+ @packets << trace_packet(
387
+ type: TrackEvent::Type::TYPE_INSTANT,
388
+ track_uuid: fid,
389
+ name: "Fiber Exit",
390
+ category_iids: DATALOADER_CATEGORY_IIDS,
391
+ extra_counter_track_uuids: [@fibers_counter_id],
392
+ extra_counter_values: [count_fibers(-1)],
393
+ )
394
+ super
395
+ end
396
+
397
+ def begin_dataloader(dl)
398
+ @packets << trace_packet(
399
+ type: TrackEvent::Type::TYPE_COUNTER,
400
+ track_uuid: @fibers_counter_id,
401
+ counter_value: count_fibers(1),
402
+ )
403
+ @did = fid
404
+ @packets << track_descriptor_packet(@main_fiber_id, @did, "Dataloader Fiber ##{@did}")
405
+ super
406
+ end
407
+
408
+ def end_dataloader(dl)
409
+ @packets << trace_packet(
410
+ type: TrackEvent::Type::TYPE_COUNTER,
411
+ track_uuid: @fibers_counter_id,
412
+ counter_value: count_fibers(-1),
413
+ )
414
+ super
415
+ end
416
+
417
+ def begin_dataloader_source(source)
418
+ fds = @flow_ids[source]
419
+ fds_copy = fds.dup
420
+ fds.clear
421
+ packet = trace_packet(
422
+ type: TrackEvent::Type::TYPE_SLICE_BEGIN,
423
+ track_uuid: fid,
424
+ name_iid: @source_name_iids[source.class],
425
+ category_iids: DATALOADER_CATEGORY_IIDS,
426
+ flow_ids: fds_copy,
427
+ extra_counter_track_uuids: [@objects_counter_id],
428
+ extra_counter_values: [count_allocations],
429
+ debug_annotations: [
430
+ payload_to_debug(nil, source.pending.values, iid: DA_FETCH_KEYS_IID, intern_value: true),
431
+ *(source.instance_variables - [:@pending, :@fetching, :@results, :@dataloader]).map { |iv|
432
+ payload_to_debug(iv.to_s, source.instance_variable_get(iv), intern_value: true)
433
+ }
434
+ ]
435
+ )
436
+ @packets << packet
437
+ fiber_flow_stack << packet
438
+ super
439
+ end
440
+
441
+ def end_dataloader_source(source)
442
+ @packets << trace_packet(
443
+ type: TrackEvent::Type::TYPE_SLICE_END,
444
+ track_uuid: fid,
445
+ extra_counter_track_uuids: [@objects_counter_id],
446
+ extra_counter_values: [count_allocations],
447
+ )
448
+ fiber_flow_stack.pop
449
+ super
450
+ end
451
+
452
+ def begin_authorized(type, obj, ctx)
453
+ packet = trace_packet(
454
+ type: TrackEvent::Type::TYPE_SLICE_BEGIN,
455
+ track_uuid: fid,
456
+ category_iids: AUTHORIZED_CATEGORY_IIDS,
457
+ extra_counter_track_uuids: [@objects_counter_id],
458
+ extra_counter_values: [count_allocations],
459
+ name_iid: @auth_name_iids[type],
460
+ )
461
+ @packets << packet
462
+ fiber_flow_stack << packet
463
+ super
464
+ end
465
+
466
+ def end_authorized(type, obj, ctx, is_authorized)
467
+ @packets << trace_packet(
468
+ type: TrackEvent::Type::TYPE_SLICE_END,
469
+ track_uuid: fid,
470
+ extra_counter_track_uuids: [@objects_counter_id],
471
+ extra_counter_values: [count_allocations],
472
+ )
473
+ beg_auth = fiber_flow_stack.pop
474
+ beg_auth.track_event = dup_with(beg_auth.track_event, { debug_annotations: [payload_to_debug("authorized?", is_authorized)] })
475
+ super
476
+ end
477
+
478
+ def begin_resolve_type(type, value, context)
479
+ packet = trace_packet(
480
+ type: TrackEvent::Type::TYPE_SLICE_BEGIN,
481
+ track_uuid: fid,
482
+ category_iids: RESOLVE_TYPE_CATEGORY_IIDS,
483
+ extra_counter_track_uuids: [@objects_counter_id],
484
+ extra_counter_values: [count_allocations],
485
+ name_iid: @resolve_type_name_iids[type],
486
+ )
487
+ @packets << packet
488
+ fiber_flow_stack << packet
489
+ super
490
+ end
491
+
492
+ def end_resolve_type(type, value, context, resolved_type)
493
+ @packets << trace_packet(
494
+ type: TrackEvent::Type::TYPE_SLICE_END,
495
+ track_uuid: fid,
496
+ extra_counter_track_uuids: [@objects_counter_id],
497
+ extra_counter_values: [count_allocations],
498
+ )
499
+ rt_begin = fiber_flow_stack.pop
500
+ rt_begin.track_event = dup_with(rt_begin.track_event, { debug_annotations: [payload_to_debug("resolved_type", resolved_type, intern_value: true)] })
501
+ super
502
+ end
503
+
504
+ # Dump protobuf output in the specified file.
505
+ # @param file [String] path to a file in a directory that already exists
506
+ # @param debug_json [Boolean] True to print JSON instead of binary
507
+ # @return [nil, String, Hash] If `file` was given, `nil`. If `file` was `nil`, a Hash if `debug_json: true`, else binary data.
508
+ def write(file:, debug_json: false)
509
+ trace = Trace.new(
510
+ packet: @packets,
511
+ )
512
+ data = if debug_json
513
+ small_json = Trace.encode_json(trace)
514
+ JSON.pretty_generate(JSON.parse(small_json))
515
+ else
516
+ Trace.encode(trace)
517
+ end
518
+
519
+ if file
520
+ File.write(file, data, mode: 'wb')
521
+ nil
522
+ else
523
+ data
524
+ end
525
+ end
526
+
527
+ private
528
+
529
+ def ts
530
+ Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
531
+ end
532
+
533
+ def tid
534
+ Thread.current.object_id
535
+ end
536
+
537
+ def fid
538
+ Fiber.current.object_id
539
+ end
540
+
541
+ def debug_annotation(iid, value_key, value)
542
+ if iid
543
+ DebugAnnotation.new(name_iid: iid, value_key => value)
544
+ else
545
+ DebugAnnotation.new(value_key => value)
546
+ end
547
+ end
548
+
549
+ def payload_to_debug(k, v, iid: nil, intern_value: false)
550
+ if iid.nil?
551
+ iid = @interned_da_name_ids[k]
552
+ k = nil
553
+ end
554
+ case v
555
+ when String
556
+ if intern_value
557
+ v = @interned_da_string_values[v]
558
+ debug_annotation(iid, :string_value_iid, v)
559
+ else
560
+ debug_annotation(iid, :string_value, v)
561
+ end
562
+ when Float
563
+ debug_annotation(iid, :double_value, v)
564
+ when Integer
565
+ debug_annotation(iid, :int_value, v)
566
+ when true, false
567
+ debug_annotation(iid, :bool_value, v)
568
+ when nil
569
+ if iid
570
+ DebugAnnotation.new(name_iid: iid, string_value_iid: DA_STR_VAL_NIL_IID)
571
+ else
572
+ DebugAnnotation.new(name: k, string_value_iid: DA_STR_VAL_NIL_IID)
573
+ end
574
+ when Module
575
+ if intern_value
576
+ val_iid = @class_name_iids[v]
577
+ debug_annotation(iid, :string_value_iid, val_iid)
578
+ else
579
+ debug_annotation(iid, :string_value, v.name)
580
+ end
581
+ when Symbol
582
+ debug_annotation(iid, :string_value, v.inspect)
583
+ when Array
584
+ debug_annotation(iid, :array_values, v.map { |v2| payload_to_debug(nil, v2, intern_value: intern_value) }.compact)
585
+ when Hash
586
+ debug_annotation(iid, :dict_entries, v.map { |k2, v2| payload_to_debug(k2, v2, intern_value: intern_value) }.compact)
587
+ else
588
+ debug_str = if defined?(ActiveRecord::Relation) && v.is_a?(ActiveRecord::Relation)
589
+ "#{v.class}, .to_sql=#{v.to_sql.inspect}"
590
+ else
591
+ v.inspect
592
+ end
593
+ if intern_value
594
+ str_iid = @interned_da_string_values[debug_str]
595
+ debug_annotation(iid, :string_value_iid, str_iid)
596
+ else
597
+ debug_annotation(iid, :string_value, debug_str)
598
+ end
599
+ end
600
+ end
601
+
602
+ def count_allocations
603
+ GC.stat(:total_allocated_objects) - @starting_objects
604
+ end
605
+
606
+ def count_fibers(diff)
607
+ @fibers_count += diff
608
+ end
609
+
610
+ def count_fields
611
+ @fields_count += 1
612
+ end
613
+
614
+ def dup_with(message, attrs, delete_counters: false)
615
+ new_attrs = message.to_h
616
+ if delete_counters
617
+ new_attrs.delete(:extra_counter_track_uuids)
618
+ new_attrs.delete(:extra_counter_values)
619
+ end
620
+ new_attrs.merge!(attrs)
621
+ message.class.new(**new_attrs)
622
+ end
623
+
624
+ def fiber_flow_stack
625
+ Fiber[:graphql_flow_stack] ||= []
626
+ end
627
+
628
+ def trace_packet(event_attrs)
629
+ TracePacket.new(
630
+ timestamp: ts,
631
+ track_event: TrackEvent.new(event_attrs),
632
+ trusted_packet_sequence_id: @sequence_id,
633
+ sequence_flags: 2,
634
+ interned_data: new_interned_data
635
+ )
636
+ end
637
+
638
+ def new_interned_data
639
+ if !@new_interned_da_names.empty?
640
+ da_names = @new_interned_da_names.map { |(name, iid)| DebugAnnotationName.new(iid: iid, name: name) }
641
+ @new_interned_da_names.clear
642
+ end
643
+
644
+ if !@new_interned_event_names.empty?
645
+ ev_names = @new_interned_event_names.map { |(name, iid)| EventName.new(iid: iid, name: name) }
646
+ @new_interned_event_names.clear
647
+ end
648
+
649
+ if !@new_interned_da_string_values.empty?
650
+ str_vals = @new_interned_da_string_values.map { |name, iid| InternedString.new(iid: iid, str: name) }
651
+ @new_interned_da_string_values.clear
652
+ end
653
+
654
+ if ev_names || da_names || str_vals
655
+ InternedData.new(
656
+ event_names: ev_names,
657
+ debug_annotation_names: da_names,
658
+ debug_annotation_string_values: str_vals,
659
+ )
660
+ else
661
+ nil
662
+ end
663
+ end
664
+
665
+ def track_descriptor_packet(parent_uuid, uuid, name, counter: nil)
666
+ td = if counter
667
+ TrackDescriptor.new(
668
+ parent_uuid: parent_uuid,
669
+ uuid: uuid,
670
+ name: name,
671
+ counter: counter
672
+ )
673
+ else
674
+ TrackDescriptor.new(
675
+ parent_uuid: parent_uuid,
676
+ uuid: uuid,
677
+ name: name,
678
+ child_ordering: TrackDescriptor::ChildTracksOrdering::CHRONOLOGICAL,
679
+ )
680
+ end
681
+ TracePacket.new(
682
+ track_descriptor: td,
683
+ trusted_packet_sequence_id: @sequence_id,
684
+ sequence_flags: 2,
685
+ )
686
+ end
687
+
688
+ def unsubscribe_from_active_support_notifications
689
+ if defined?(@as_subscriber)
690
+ ActiveSupport::Notifications.unsubscribe(@as_subscriber)
691
+ end
692
+ end
693
+
694
+ def subscribe_to_active_support_notifications(pattern)
695
+ @as_subscriber = ActiveSupport::Notifications.monotonic_subscribe(pattern) do |name, start, finish, id, payload|
696
+ metadata = payload.map { |k, v| payload_to_debug(k, v, intern_value: true) }
697
+ metadata.compact!
698
+ te = if metadata.empty?
699
+ TrackEvent.new(
700
+ type: TrackEvent::Type::TYPE_SLICE_BEGIN,
701
+ track_uuid: fid,
702
+ category_iids: ACTIVE_SUPPORT_NOTIFICATIONS_CATEGORY_IIDS,
703
+ name: name,
704
+ )
705
+ else
706
+ TrackEvent.new(
707
+ type: TrackEvent::Type::TYPE_SLICE_BEGIN,
708
+ track_uuid: fid,
709
+ name: name,
710
+ category_iids: ACTIVE_SUPPORT_NOTIFICATIONS_CATEGORY_IIDS,
711
+ debug_annotations: metadata,
712
+ )
713
+ end
714
+ @packets << TracePacket.new(
715
+ timestamp: (start * 1_000_000_000).to_i,
716
+ track_event: te,
717
+ trusted_packet_sequence_id: @sequence_id,
718
+ sequence_flags: 2,
719
+ interned_data: new_interned_data
720
+ )
721
+ @packets << TracePacket.new(
722
+ timestamp: (finish * 1_000_000_000).to_i,
723
+ track_event: TrackEvent.new(
724
+ type: TrackEvent::Type::TYPE_SLICE_END,
725
+ track_uuid: fid,
726
+ name: name,
727
+ extra_counter_track_uuids: [@objects_counter_id],
728
+ extra_counter_values: [count_allocations]
729
+ ),
730
+ trusted_packet_sequence_id: @sequence_id,
731
+ sequence_flags: 2,
732
+ )
733
+ end
734
+ end
735
+ end
736
+ end
737
+ end