Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 52 additions & 4 deletions lib/graphql/tracing/detailed_trace.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@ module Tracing
# When `MySchema.detailed_trace?(query)` returns `true`, a profiler-specific `trace_mode: ...` will be used for the query,
# overriding the one in `context[:trace_mode]`.
#
# By default, the detailed tracer calls `.inspect` on application objects returned from fields. You can customize
# this behavior by extending {DetailedTrace} and overriding {#inspect_object}. You can opt out of debug annotations
# entirely with `use ..., debug: false` or for a single query with `context: { detailed_trace_debug: false }`.
#
# __Redis__: The sampler stores its results in a provided Redis database. Depending on your needs,
# You can configure this database to retail all data (persistent) or to expire data according to your rules.
# You can configure this database to retain all data (persistent) or to expire data according to your rules.
# If you need to save traces indefinitely, you can download them from Perfetto after opening them there.
#
# @example Adding the sampler to your schema
Expand All @@ -27,24 +31,45 @@ module Tracing
# end
#
# @see Graphql::Dashboard GraphQL::Dashboard for viewing stored results
#
# @example Customizing debug output in traces
# class CustomDetailedTrace < GraphQL::Tracing::DetailedTrace
# def inspect_object(object)
# if object.is_a?(SomeThing)
# # handle it specially ...
# else
# super
# end
# end
# end
#
# @example disabling debug annotations completely
# use DetailedTrace, debug: false, ...
#
# @example disabling debug annotations for one query
# MySchema.execute(query_str, context: { detailed_trace_debug: false })
#
class DetailedTrace
# @param redis [Redis] If provided, profiles will be stored in Redis for later review
# @param limit [Integer] A maximum number of profiles to store
def self.use(schema, trace_mode: :profile_sample, memory: false, redis: nil, limit: nil)
# @param debug [Boolean] if `false`, it won't create `debug` annotations in Perfetto traces (reduces overhead)
def self.use(schema, trace_mode: :profile_sample, memory: false, debug: debug?, redis: nil, limit: nil)
storage = if redis
RedisBackend.new(redis: redis, limit: limit)
elsif memory
MemoryBackend.new(limit: limit)
else
raise ArgumentError, "Pass `redis: ...` to store traces in Redis for later review"
end
schema.detailed_trace = self.new(storage: storage, trace_mode: trace_mode)
detailed_trace = self.new(storage: storage, trace_mode: trace_mode, debug: debug)
schema.detailed_trace = detailed_trace
schema.trace_with(PerfettoTrace, mode: trace_mode, save_profile: true)
end

def initialize(storage:, trace_mode:)
def initialize(storage:, trace_mode:, debug:)
@storage = storage
@trace_mode = trace_mode
@debug = debug
end

# @return [Symbol] The trace mode to use when {Schema.detailed_trace?} returns `true`
Expand All @@ -55,6 +80,11 @@ def save_trace(operation_name, duration_ms, begin_ms, trace_data)
@storage.save_trace(operation_name, duration_ms, begin_ms, trace_data)
end

# @return [Boolean]
def debug?
@debug
end

# @param last [Integer]
# @param before [Integer] Timestamp in milliseconds since epoch
# @return [Enumerable<StoredTrace>]
Expand All @@ -77,6 +107,24 @@ def delete_all_traces
@storage.delete_all_traces
end

def inspect_object(object)
self.class.inspect_object(object)
end

def self.inspect_object(object)
if defined?(ActiveRecord::Relation) && object.is_a?(ActiveRecord::Relation)
"#{object.class}, .to_sql=#{object.to_sql.inspect}"
else
object.inspect
end
end

# Default debug setting
# @return [true]
def self.debug?
true
end

class StoredTrace
def initialize(id:, operation_name:, duration_ms:, begin_ms:, trace_data:)
@id = id
Expand Down
Loading
Loading