Class: GraphQLMetrics::Instrumentation

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Defined in:
lib/graphql_metrics/instrumentation.rb

Constant Summary collapse

CONTEXT_NAMESPACE =
:extracted_metrics
TIMING_CACHE_KEY =
:timing_cache
START_TIME_KEY =
:query_start_time

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#ctx_namespaceObject (readonly)

Returns the value of attribute ctx_namespace.



11
12
13
# File 'lib/graphql_metrics/instrumentation.rb', line 11

def ctx_namespace
  @ctx_namespace
end

#queryObject (readonly)

Returns the value of attribute query.



11
12
13
# File 'lib/graphql_metrics/instrumentation.rb', line 11

def query
  @query
end

Class Method Details

.current_timeObject



21
22
23
# File 'lib/graphql_metrics/instrumentation.rb', line 21

def self.current_time
  Process.clock_gettime(Process::CLOCK_MONOTONIC)
end

.use(schema_definition) ⇒ Object



14
15
16
17
18
19
# File 'lib/graphql_metrics/instrumentation.rb', line 14

def self.use(schema_definition)
  instrumentation = self.new
  return unless instrumentation.extractor_defines_any_visitors?

  instrumentation.setup_instrumentation(schema_definition)
end

Instance Method Details

#after_query(query) ⇒ Object



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/graphql_metrics/instrumentation.rb', line 49

def after_query(query)
  @query = query

  return unless extractor_defines_any_visitors?
  return if respond_to?(:skip_extraction?) && skip_extraction?(query)
  return unless @ctx_namespace = query.context.namespace(CONTEXT_NAMESPACE)

  before_query_extracted(query, query.context) if respond_to?(:before_query_extracted)

  extractor.extract!(query)

  after_query_teardown(query) if respond_to?(:after_query_teardown)
rescue StandardError => ex
  extractor.handle_extraction_exception(ex)
end

#after_query_resolver_times(ast_node) ⇒ Object



95
96
97
# File 'lib/graphql_metrics/instrumentation.rb', line 95

def after_query_resolver_times(ast_node)
  ctx_namespace.dig(Instrumentation::TIMING_CACHE_KEY).fetch(ast_node, [])
end

#after_query_start_and_end_timeObject



99
100
101
102
103
104
# File 'lib/graphql_metrics/instrumentation.rb', line 99

def after_query_start_and_end_time
  start_time = ctx_namespace[Instrumentation::START_TIME_KEY]
  return unless start_time

  [start_time, self.class.current_time]
end

#before_query(query) ⇒ Object



39
40
41
42
43
44
45
46
47
# File 'lib/graphql_metrics/instrumentation.rb', line 39

def before_query(query)
  return unless extractor_defines_any_visitors?

  ns = query.context.namespace(CONTEXT_NAMESPACE)
  ns[TIMING_CACHE_KEY] = {}
  ns[START_TIME_KEY] = self.class.current_time
rescue StandardError => ex
  extractor.handle_extraction_exception(ex)
end

#extractorObject



35
36
37
# File 'lib/graphql_metrics/instrumentation.rb', line 35

def extractor
  @extractor ||= Extractor.new(self)
end

#instrument(type, field) ⇒ Object



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/graphql_metrics/instrumentation.rb', line 65

def instrument(type, field)
  return field unless respond_to?(:field_extracted) || extractor.respond_to?(:field_extracted)
  return field if type.introspection?

  old_resolve_proc = field.resolve_proc
  new_resolve_proc = ->(obj, args, ctx) do
    start_time = self.class.current_time
    result = old_resolve_proc.call(obj, args, ctx)

    begin
      next result if respond_to?(:skip_field_resolution_timing?) &&
        skip_field_resolution_timing?(query, ctx)

      end_time = self.class.current_time

      ns = ctx.namespace(CONTEXT_NAMESPACE)

      ns[TIMING_CACHE_KEY][ctx.ast_node] ||= []
      ns[TIMING_CACHE_KEY][ctx.ast_node] << end_time - start_time

      result
    rescue StandardError => ex
      extractor.handle_extraction_exception(ex)
      result
    end
  end

  field.redefine { resolve(new_resolve_proc) }
end

#setup_instrumentation(schema_definition) ⇒ Object



30
31
32
33
# File 'lib/graphql_metrics/instrumentation.rb', line 30

def setup_instrumentation(schema_definition)
  schema_definition.instrument(:query, self)
  schema_definition.instrument(:field, self)
end

#use(schema_definition) ⇒ Object



25
26
27
28
# File 'lib/graphql_metrics/instrumentation.rb', line 25

def use(schema_definition)
  return unless extractor_defines_any_visitors?
  extractor.setup_instrumentation(schema_definition)
end