graphql-metrics 2.0.1 → 3.0.0
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.
- checksums.yaml +5 -5
- data/.github/workflows/ruby.yml +20 -0
- data/.rubocop-http---shopify-github-io-ruby-style-guide-rubocop-yml +1027 -0
- data/.rubocop.yml +5 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +39 -0
- data/Gemfile.lock +23 -20
- data/README.md +179 -107
- data/Rakefile +6 -0
- data/bin/console +4 -6
- data/graphql_metrics.gemspec +7 -7
- data/lib/graphql/metrics.rb +80 -0
- data/lib/graphql/metrics/analyzer.rb +129 -0
- data/lib/graphql/metrics/instrumentation.rb +54 -0
- data/lib/graphql/metrics/tracer.rb +109 -0
- data/lib/graphql/metrics/version.rb +7 -0
- metadata +55 -24
- data/.travis.yml +0 -5
- data/lib/graphql_metrics.rb +0 -6
- data/lib/graphql_metrics/extractor.rb +0 -277
- data/lib/graphql_metrics/instrumentation.rb +0 -107
- data/lib/graphql_metrics/timed_batch_executor.rb +0 -80
- data/lib/graphql_metrics/version.rb +0 -5
data/Rakefile
CHANGED
@@ -5,6 +5,12 @@ Rake::TestTask.new(:test) do |t|
|
|
5
5
|
t.libs << "test"
|
6
6
|
t.libs << "lib"
|
7
7
|
t.test_files = FileList["test/**/*_test.rb"]
|
8
|
+
|
9
|
+
# TODO: We should remove this line. See `puts` line below.
|
10
|
+
t.warning = false
|
11
|
+
|
12
|
+
puts "Reminder: Remove `t.warning = false` in Rakefile once graphql-ruby fixes all instances of"\
|
13
|
+
"`warning: instance variable @<ivar> not initialized` and `mismatched indentations`"
|
8
14
|
end
|
9
15
|
|
10
16
|
task :default => :test
|
data/bin/console
CHANGED
@@ -1,14 +1,12 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
require "bundler/setup"
|
4
|
-
require
|
4
|
+
require 'graphql'
|
5
|
+
require "graphql/metrics"
|
5
6
|
|
6
7
|
# You can add fixtures and/or initialization code here to make experimenting
|
7
8
|
# with your gem easier. You can also use a different console, if you like.
|
8
9
|
|
9
10
|
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
require "irb"
|
14
|
-
IRB.start(__FILE__)
|
11
|
+
require "pry"
|
12
|
+
Pry.start
|
data/graphql_metrics.gemspec
CHANGED
@@ -1,11 +1,8 @@
|
|
1
|
-
|
2
|
-
lib = File.expand_path("../lib", __FILE__)
|
3
|
-
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require "graphql_metrics/version"
|
1
|
+
require_relative "lib/graphql/metrics/version"
|
5
2
|
|
6
3
|
Gem::Specification.new do |spec|
|
7
4
|
spec.name = "graphql-metrics"
|
8
|
-
spec.version =
|
5
|
+
spec.version = GraphQL::Metrics::VERSION
|
9
6
|
spec.authors = ["Christopher Butcher"]
|
10
7
|
spec.email = ["gems@shopify.com"]
|
11
8
|
|
@@ -33,16 +30,19 @@ Gem::Specification.new do |spec|
|
|
33
30
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
34
31
|
spec.require_paths = ["lib"]
|
35
32
|
|
36
|
-
spec.
|
33
|
+
spec.add_runtime_dependency "concurrent-ruby", "~> 1.1.0"
|
34
|
+
spec.add_runtime_dependency "graphql", "~> 1.9.15"
|
35
|
+
|
36
|
+
spec.add_development_dependency "bundler", "~> 2.0.2"
|
37
37
|
spec.add_development_dependency "rake", "~> 10.0"
|
38
38
|
spec.add_development_dependency "minitest", "~> 5.0"
|
39
39
|
spec.add_development_dependency 'graphql-batch'
|
40
|
-
spec.add_development_dependency "graphql", "~> 1.8.2"
|
41
40
|
spec.add_development_dependency "activesupport", "~> 5.1.5"
|
42
41
|
spec.add_development_dependency "pry"
|
43
42
|
spec.add_development_dependency "pry-byebug"
|
44
43
|
spec.add_development_dependency "mocha"
|
45
44
|
spec.add_development_dependency "diffy"
|
45
|
+
spec.add_development_dependency "hashdiff"
|
46
46
|
spec.add_development_dependency "fakeredis"
|
47
47
|
spec.add_development_dependency "minitest-focus"
|
48
48
|
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "concurrent"
|
4
|
+
require "graphql/metrics/version"
|
5
|
+
require "graphql/metrics/instrumentation"
|
6
|
+
require "graphql/metrics/tracer"
|
7
|
+
require "graphql/metrics/analyzer"
|
8
|
+
|
9
|
+
module GraphQL
|
10
|
+
module Metrics
|
11
|
+
# The context namespace for all values stored by this gem.
|
12
|
+
CONTEXT_NAMESPACE = :graphql_metrics_analysis
|
13
|
+
|
14
|
+
# Skip metrics capture altogher, by setting `skip_graphql_metrics_analysis: true` in query context.
|
15
|
+
SKIP_GRAPHQL_METRICS_ANALYSIS = :skip_graphql_metrics_analysis
|
16
|
+
|
17
|
+
# Skips just field and argument logging, when query metrics logging is still desirable
|
18
|
+
SKIP_FIELD_AND_ARGUMENT_METRICS = :skip_field_and_argument_metrics
|
19
|
+
|
20
|
+
# Timings related constants.
|
21
|
+
TIMINGS_CAPTURE_ENABLED = :timings_capture_enabled
|
22
|
+
ANALYZER_INSTANCE_KEY = :analyzer_instance
|
23
|
+
|
24
|
+
# Context keys to store timings for query phases of execution, field resolver timings.
|
25
|
+
QUERY_START_TIME = :query_start_time
|
26
|
+
QUERY_START_TIME_MONOTONIC = :query_start_time_monotonic
|
27
|
+
PARSING_START_TIME_OFFSET = :parsing_start_time_offset
|
28
|
+
PARSING_DURATION = :parsing_duration
|
29
|
+
VALIDATION_START_TIME_OFFSET = :validation_start_time_offset
|
30
|
+
VALIDATION_DURATION = :validation_duration
|
31
|
+
INLINE_FIELD_TIMINGS = :inline_field_timings
|
32
|
+
LAZY_FIELD_TIMINGS = :lazy_field_timings
|
33
|
+
|
34
|
+
def self.timings_capture_enabled?(context)
|
35
|
+
return false unless context
|
36
|
+
!!context.namespace(CONTEXT_NAMESPACE)[TIMINGS_CAPTURE_ENABLED]
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.current_time
|
40
|
+
Process.clock_gettime(Process::CLOCK_REALTIME)
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.current_time_monotonic
|
44
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.time(*args)
|
48
|
+
TimedResult.new(*args) { yield }
|
49
|
+
end
|
50
|
+
|
51
|
+
class TimedResult
|
52
|
+
# NOTE: `time_since_offset` is used to produce start times timed phases of execution (validation, field
|
53
|
+
# resolution). These start times are relative to the executed operation's start time, which is captured at the
|
54
|
+
# outset of document parsing.
|
55
|
+
#
|
56
|
+
# The times produced are intentionally similar to:
|
57
|
+
# https://github.com/apollographql/apollo-tracing#response-format
|
58
|
+
#
|
59
|
+
# Taking a field resolver start offset example:
|
60
|
+
#
|
61
|
+
# < start offset >
|
62
|
+
# |------------------|----------|--------->
|
63
|
+
# OS (t=0) FS (t=1) FE (t=2)
|
64
|
+
#
|
65
|
+
# OS = Operation start time
|
66
|
+
# FS = Field resolver start time
|
67
|
+
# FE = Field resolver end time
|
68
|
+
#
|
69
|
+
attr_reader :result, :start_time, :duration, :time_since_offset
|
70
|
+
|
71
|
+
def initialize(offset_time = nil)
|
72
|
+
@offset_time = offset_time
|
73
|
+
@start_time = GraphQL::Metrics.current_time_monotonic
|
74
|
+
@result = yield
|
75
|
+
@duration = GraphQL::Metrics.current_time_monotonic - @start_time
|
76
|
+
@time_since_offset = @start_time - @offset_time if @offset_time
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module Metrics
|
5
|
+
class Analyzer < GraphQL::Analysis::AST::Analyzer
|
6
|
+
attr_reader :query
|
7
|
+
|
8
|
+
def initialize(query_or_multiplex)
|
9
|
+
super
|
10
|
+
|
11
|
+
@query = query_or_multiplex
|
12
|
+
ns = query.context.namespace(CONTEXT_NAMESPACE)
|
13
|
+
ns[ANALYZER_INSTANCE_KEY] = self
|
14
|
+
|
15
|
+
@static_query_metrics = nil
|
16
|
+
@static_field_metrics = []
|
17
|
+
end
|
18
|
+
|
19
|
+
def analyze?
|
20
|
+
query.valid? && !query.context[GraphQL::Metrics::SKIP_GRAPHQL_METRICS_ANALYSIS]
|
21
|
+
end
|
22
|
+
|
23
|
+
def extract_query(runtime_query_metrics: {})
|
24
|
+
query_extracted(@static_query_metrics.merge(runtime_query_metrics)) if @static_query_metrics
|
25
|
+
end
|
26
|
+
|
27
|
+
def on_enter_operation_definition(_node, _parent, visitor)
|
28
|
+
@static_query_metrics = {
|
29
|
+
operation_type: visitor.query.selected_operation.operation_type,
|
30
|
+
operation_name: visitor.query.selected_operation.name,
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
def on_leave_field(node, _parent, visitor)
|
35
|
+
return if visitor.field_definition.introspection?
|
36
|
+
return if query.context[SKIP_FIELD_AND_ARGUMENT_METRICS]
|
37
|
+
|
38
|
+
# NOTE: @rmosolgo "I think it could be reduced to `arguments = visitor.arguments_for(ast_node)`"
|
39
|
+
arguments = visitor.arguments_for(node, visitor.field_definition)
|
40
|
+
extract_arguments(arguments.argument_values.values, visitor.field_definition)
|
41
|
+
|
42
|
+
static_metrics = {
|
43
|
+
field_name: node.name,
|
44
|
+
return_type_name: visitor.type_definition.name,
|
45
|
+
parent_type_name: visitor.parent_type_definition.name,
|
46
|
+
deprecated: visitor.field_definition.deprecation_reason.present?,
|
47
|
+
path: visitor.response_path,
|
48
|
+
}
|
49
|
+
|
50
|
+
if GraphQL::Metrics.timings_capture_enabled?(query.context)
|
51
|
+
@static_field_metrics << static_metrics
|
52
|
+
else
|
53
|
+
field_extracted(static_metrics)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def extract_fields_with_runtime_metrics
|
58
|
+
return if query.context[SKIP_FIELD_AND_ARGUMENT_METRICS]
|
59
|
+
|
60
|
+
ns = query.context.namespace(CONTEXT_NAMESPACE)
|
61
|
+
|
62
|
+
@static_field_metrics.each do |static_metrics|
|
63
|
+
resolver_timings = ns[GraphQL::Metrics::INLINE_FIELD_TIMINGS][static_metrics[:path]]
|
64
|
+
lazy_field_timings = ns[GraphQL::Metrics::LAZY_FIELD_TIMINGS][static_metrics[:path]]
|
65
|
+
|
66
|
+
metrics = static_metrics.merge(
|
67
|
+
resolver_timings: resolver_timings || [],
|
68
|
+
lazy_resolver_timings: lazy_field_timings || [],
|
69
|
+
)
|
70
|
+
|
71
|
+
field_extracted(metrics)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def result
|
76
|
+
return if GraphQL::Metrics.timings_capture_enabled?(query.context)
|
77
|
+
return if query.context[GraphQL::Metrics::SKIP_GRAPHQL_METRICS_ANALYSIS]
|
78
|
+
|
79
|
+
# NOTE: If we're running as a static analyzer (i.e. not with instrumentation and tracing), we still need to
|
80
|
+
# flush static query metrics somewhere other than `after_query`.
|
81
|
+
ns = query.context.namespace(CONTEXT_NAMESPACE)
|
82
|
+
analyzer = ns[GraphQL::Metrics::ANALYZER_INSTANCE_KEY]
|
83
|
+
analyzer.extract_query
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def extract_arguments(argument, field_defn, parent_input_object = nil)
|
89
|
+
case argument
|
90
|
+
when Array
|
91
|
+
argument.each do |a|
|
92
|
+
extract_arguments(a, field_defn, parent_input_object)
|
93
|
+
end
|
94
|
+
when Hash
|
95
|
+
argument.each_value do |a|
|
96
|
+
extract_arguments(a, field_defn, parent_input_object)
|
97
|
+
end
|
98
|
+
when ::GraphQL::Query::Arguments
|
99
|
+
argument.each_value do |arg_val|
|
100
|
+
extract_arguments(arg_val, field_defn, parent_input_object)
|
101
|
+
end
|
102
|
+
when ::GraphQL::Query::Arguments::ArgumentValue
|
103
|
+
extract_argument(argument, field_defn, parent_input_object)
|
104
|
+
extract_arguments(argument.value, field_defn, parent_input_object)
|
105
|
+
when ::GraphQL::Schema::InputObject
|
106
|
+
input_object_argument_values = argument.arguments.argument_values.values
|
107
|
+
parent_input_object = input_object_argument_values.first&.definition&.metadata&.fetch(:type_class, nil)&.owner
|
108
|
+
|
109
|
+
extract_arguments(input_object_argument_values, field_defn, parent_input_object)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def extract_argument(value, field_defn, parent_input_object = nil)
|
114
|
+
static_metrics = {
|
115
|
+
argument_name: value.definition.expose_as,
|
116
|
+
argument_type_name: value.definition.type.unwrap.to_s,
|
117
|
+
parent_field_name: field_defn.name,
|
118
|
+
parent_field_type_name: field_defn.metadata[:type_class].owner.graphql_name,
|
119
|
+
parent_input_object_type: parent_input_object&.graphql_name,
|
120
|
+
default_used: value.default_used?,
|
121
|
+
value_is_null: value.value.nil?,
|
122
|
+
value: value,
|
123
|
+
}
|
124
|
+
|
125
|
+
argument_extracted(static_metrics)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module Metrics
|
5
|
+
class Instrumentation
|
6
|
+
def before_query(query)
|
7
|
+
unless query_present_and_valid?(query)
|
8
|
+
# Setting this prevents Analyzer and Tracer from trying to gather runtime metrics for invalid queries.
|
9
|
+
query.context[GraphQL::Metrics::SKIP_GRAPHQL_METRICS_ANALYSIS] = true
|
10
|
+
end
|
11
|
+
|
12
|
+
# Even if queries are present and valid, applications may set this context value in order to opt out of
|
13
|
+
# having Analyzer and Tracer gather runtime metrics.
|
14
|
+
# If we're skipping runtime metrics, then both Instrumentation before_ and after_query can and should be
|
15
|
+
# short-circuited as well.
|
16
|
+
return if query.context[GraphQL::Metrics::SKIP_GRAPHQL_METRICS_ANALYSIS]
|
17
|
+
|
18
|
+
ns = query.context.namespace(CONTEXT_NAMESPACE)
|
19
|
+
ns[GraphQL::Metrics::TIMINGS_CAPTURE_ENABLED] = true
|
20
|
+
ns[GraphQL::Metrics::INLINE_FIELD_TIMINGS] = {}
|
21
|
+
ns[GraphQL::Metrics::LAZY_FIELD_TIMINGS] = {}
|
22
|
+
end
|
23
|
+
|
24
|
+
def after_query(query)
|
25
|
+
return if query.context[GraphQL::Metrics::SKIP_GRAPHQL_METRICS_ANALYSIS]
|
26
|
+
|
27
|
+
ns = query.context.namespace(CONTEXT_NAMESPACE)
|
28
|
+
|
29
|
+
# NOTE: The start time stored at `ns[GraphQL::Metrics::QUERY_START_TIME_MONOTONIC]` is captured during query
|
30
|
+
# parsing, which occurs before `Instrumentation#before_query`.
|
31
|
+
query_duration = GraphQL::Metrics.current_time_monotonic - ns[GraphQL::Metrics::QUERY_START_TIME_MONOTONIC]
|
32
|
+
|
33
|
+
runtime_query_metrics = {
|
34
|
+
query_start_time: ns[GraphQL::Metrics::QUERY_START_TIME],
|
35
|
+
query_duration: query_duration,
|
36
|
+
parsing_start_time_offset: ns[GraphQL::Metrics::PARSING_START_TIME_OFFSET],
|
37
|
+
parsing_duration: ns[GraphQL::Metrics::PARSING_DURATION],
|
38
|
+
validation_start_time_offset: ns[GraphQL::Metrics::VALIDATION_START_TIME_OFFSET],
|
39
|
+
validation_duration: ns[GraphQL::Metrics::VALIDATION_DURATION],
|
40
|
+
}
|
41
|
+
|
42
|
+
analyzer = ns[GraphQL::Metrics::ANALYZER_INSTANCE_KEY]
|
43
|
+
analyzer.extract_fields_with_runtime_metrics
|
44
|
+
analyzer.extract_query(runtime_query_metrics: runtime_query_metrics)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def query_present_and_valid?(query)
|
50
|
+
query.valid? && query.document.to_query_string.present?
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module Metrics
|
5
|
+
class Tracer
|
6
|
+
# NOTE: These constants come from the graphql ruby gem.
|
7
|
+
GRAPHQL_GEM_LEXING_KEY = 'lex'
|
8
|
+
GRAPHQL_GEM_PARSING_KEY = 'parse'
|
9
|
+
GRAPHQL_GEM_VALIDATION_KEYS = ['validate', 'analyze_query']
|
10
|
+
GRAPHQL_GEM_TRACING_FIELD_KEYS = [
|
11
|
+
GRAPHQL_GEM_TRACING_FIELD_KEY = 'execute_field',
|
12
|
+
GRAPHQL_GEM_TRACING_LAZY_FIELD_KEY = 'execute_field_lazy'
|
13
|
+
]
|
14
|
+
|
15
|
+
def trace(key, data, &block)
|
16
|
+
# NOTE: Context doesn't exist yet during lexing, parsing.
|
17
|
+
possible_context = data[:query]&.context
|
18
|
+
|
19
|
+
skip_tracing = possible_context&.fetch(GraphQL::Metrics::SKIP_GRAPHQL_METRICS_ANALYSIS, false)
|
20
|
+
return yield if skip_tracing
|
21
|
+
|
22
|
+
# NOTE: Not all tracing events are handled here, but those that are are handled in this case statement in
|
23
|
+
# chronological order.
|
24
|
+
case key
|
25
|
+
when GRAPHQL_GEM_LEXING_KEY
|
26
|
+
return setup_tracing_before_lexing(&block)
|
27
|
+
when GRAPHQL_GEM_PARSING_KEY
|
28
|
+
return capture_parsing_time(&block)
|
29
|
+
when *GRAPHQL_GEM_VALIDATION_KEYS
|
30
|
+
context = possible_context
|
31
|
+
|
32
|
+
return yield unless context.query.valid?
|
33
|
+
return capture_validation_time(context, &block)
|
34
|
+
when *GRAPHQL_GEM_TRACING_FIELD_KEYS
|
35
|
+
return yield if data[:query].context[SKIP_FIELD_AND_ARGUMENT_METRICS]
|
36
|
+
return yield unless GraphQL::Metrics.timings_capture_enabled?(data[:query].context)
|
37
|
+
|
38
|
+
pre_context = nil
|
39
|
+
|
40
|
+
context_key = case key
|
41
|
+
when GRAPHQL_GEM_TRACING_FIELD_KEY
|
42
|
+
GraphQL::Metrics::INLINE_FIELD_TIMINGS
|
43
|
+
when GRAPHQL_GEM_TRACING_LAZY_FIELD_KEY
|
44
|
+
GraphQL::Metrics::LAZY_FIELD_TIMINGS
|
45
|
+
end
|
46
|
+
|
47
|
+
trace_field(context_key, data, &block)
|
48
|
+
else
|
49
|
+
return yield
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def pre_context
|
56
|
+
# NOTE: This is used to store timings from lexing, parsing, validation, before we have a context to store
|
57
|
+
# values in. Uses thread-safe Concurrent::ThreadLocalVar to store a set of values per thread.
|
58
|
+
@pre_context ||= Concurrent::ThreadLocalVar.new(OpenStruct.new)
|
59
|
+
end
|
60
|
+
|
61
|
+
def setup_tracing_before_lexing
|
62
|
+
pre_context.value.query_start_time = GraphQL::Metrics.current_time
|
63
|
+
pre_context.value.query_start_time_monotonic = GraphQL::Metrics.current_time_monotonic
|
64
|
+
|
65
|
+
yield
|
66
|
+
end
|
67
|
+
|
68
|
+
def capture_parsing_time
|
69
|
+
timed_result = GraphQL::Metrics.time { yield }
|
70
|
+
|
71
|
+
pre_context.value.parsing_start_time_offset = timed_result.start_time
|
72
|
+
pre_context.value.parsing_duration = timed_result.duration
|
73
|
+
|
74
|
+
timed_result.result
|
75
|
+
end
|
76
|
+
|
77
|
+
def capture_validation_time(context)
|
78
|
+
timed_result = GraphQL::Metrics.time(pre_context.value.query_start_time_monotonic) { yield }
|
79
|
+
|
80
|
+
ns = context.namespace(CONTEXT_NAMESPACE)
|
81
|
+
previous_validation_duration = ns[GraphQL::Metrics::VALIDATION_DURATION] || 0
|
82
|
+
|
83
|
+
ns[QUERY_START_TIME] = pre_context.value.query_start_time
|
84
|
+
ns[QUERY_START_TIME_MONOTONIC] = pre_context.value.query_start_time_monotonic
|
85
|
+
ns[PARSING_START_TIME_OFFSET] = pre_context.value.parsing_start_time_offset
|
86
|
+
ns[PARSING_DURATION] = pre_context.value.parsing_duration
|
87
|
+
ns[VALIDATION_START_TIME_OFFSET] = timed_result.time_since_offset
|
88
|
+
ns[VALIDATION_DURATION] = timed_result.duration + previous_validation_duration
|
89
|
+
|
90
|
+
timed_result.result
|
91
|
+
end
|
92
|
+
|
93
|
+
def trace_field(context_key, data)
|
94
|
+
ns = data[:query].context.namespace(CONTEXT_NAMESPACE)
|
95
|
+
query_start_time_monotonic = ns[GraphQL::Metrics::QUERY_START_TIME_MONOTONIC]
|
96
|
+
|
97
|
+
timed_result = GraphQL::Metrics.time(query_start_time_monotonic) { yield }
|
98
|
+
|
99
|
+
path_excluding_numeric_indicies = data[:path].select { |p| p.is_a?(String) }
|
100
|
+
ns[context_key][path_excluding_numeric_indicies] ||= []
|
101
|
+
ns[context_key][path_excluding_numeric_indicies] << {
|
102
|
+
start_time_offset: timed_result.time_since_offset, duration: timed_result.duration
|
103
|
+
}
|
104
|
+
|
105
|
+
timed_result.result
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|