optics-agent 0.3.1 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: baf3d71a589fb44d4c5334e9536b40bf102776be
4
- data.tar.gz: 5d2c8f79f113afb70a170d4464b577b83db45c9a
3
+ metadata.gz: c6c80fb6905bbb97a8edee4ae2fb1d57d9cca76c
4
+ data.tar.gz: 969d07c49e74405731ba62ab699982f86c41daa4
5
5
  SHA512:
6
- metadata.gz: c55989f7e56a1f233ca326e848e3869666714a2b0a892adb11c96d985e5331159fa69b23a27aa58332bf2ae155697a6f1b45bf613aad8a7511aa5fd0dcf594d0
7
- data.tar.gz: 877b3599a864ddb80e66df0f2c9b95578ececf581dc87136fb8db819f90beb4a4e2b2ea6b9a2c36272f18c7159006c8bf7db2e3bbd6dc67d9c51f87c2512bacc
6
+ metadata.gz: 8b6dbc2a2f0a7f259ba2b4994be4c054e05bdc8ec8fe6d8a4da484830bcdbc5c5afa739dea52a62a4765750b73947ebe9c2a096a7b345383ae0e7c7beb0e92fa
7
+ data.tar.gz: af83946a5b4e0990de46b802865a95fecd8612a2adb085fafe5c0b4b6d8caa69b86909c0cdb29077e9989ec0238ae55a56048dc83c90946fe91d1f50f4231063
data/README.md CHANGED
@@ -1,8 +1,6 @@
1
1
  # optics-agent-ruby
2
2
  Optics Agent for GraphQL Monitoring in Ruby.
3
3
 
4
- This is an alpha release, suitable for use in development contexts. There are still some outstanding improvements to make it ready for production contexts; see the [known limitations](#known-limitations) section below.
5
-
6
4
  [![Gem Version](https://badge.fury.io/rb/optics-agent.svg)](https://badge.fury.io/rb/optics-agent) [![Build Status](https://travis-ci.org/apollostack/optics-agent-ruby.svg?branch=master)](https://travis-ci.org/apollostack/optics-agent-ruby)
7
5
 
8
6
 
@@ -16,96 +14,23 @@ gem 'optics-agent'
16
14
 
17
15
  To your `Gemfile`
18
16
 
19
- ## Setup
20
-
21
17
  ### API key
22
18
 
23
- You'll need to run your app with the `OPTICS_API_KEY` environment variable set (or set via options) to the API key of your Apollo Optics service; you can get an API key by setting up a service at https://optics.apollodata.com.
24
-
25
- ### Configuration
26
-
27
- After creating an agent (see below), you can configure it with
28
-
29
- ```rb
30
- agent.set_options(option: value)
31
- ```
32
-
33
- Possible options are:
34
-
35
- - `api_key` - Your API key for the Optics service. This defaults to the OPTICS_API_KEY environment variable, but can be overridden here.
36
- - `endpoint_url ['https://optics-report.apollodata.com']` - Where to send the reports. Defaults to the production Optics endpoint, or the `OPTICS_ENDPOINT_URL` environment variable if it is set. You shouldn't need to set this unless you are debugging
37
- - `debug [false]` - Log detailed debugging messages
38
- - `disable_reporting [false]` - Don't report anything to Optics (useful for testing)
39
- - `print_reports [false]` - Print JSON versions of the data sent to Optics to the log
40
- - `schema_report_delay_ms [10000]` - How long to wait before sending a schema report after startup, in, milliseconds
41
- - `report_interval_ms [60000]` - How often to send reports in milliseconds. Defaults to 1 minute. Minimum 10 seconds. You shouldn't need to set this unless you are debugging.
42
-
43
- ### Basic Rack/Sinatra
44
-
45
- Create an agent
46
-
47
- ```ruby
48
- # we expect one day there'll be some options
49
- agent = OpticsAgent::Agent.instance
50
- ```
51
-
52
- Register the Rack middleware (say in a `config.ru`):
53
-
54
- ```ruby
55
- use agent.rack_middleware
56
- ```
57
-
58
- Register the GraphQL middleware:
59
-
60
- ```ruby
61
- agent.instrument_schema(YourSchema)
62
- ```
63
-
64
- Add something like this to your route:
65
-
66
- ```ruby
67
- post '/graphql' do
68
- request.body.rewind
69
- params = JSON.parse request.body.read
70
- document = params['query']
71
- variables = params['variables']
72
-
73
- result = Schema.execute(
74
- document,
75
- variables: variables,
76
- context: { optics_agent: env[:optics_agent].with_document(document) }
77
- )
78
-
79
- JSON.generate(result)
80
- end
81
- ```
19
+ You'll need to run your app with the `OPTICS_API_KEY` environment variable set (or set via `agent.configure`) to the API key of your Apollo Optics service; you can get an API key by setting up a service at https://optics.apollodata.com.
82
20
 
83
- ## Rails
84
-
85
- The equivalent of the above for Rails is:
86
-
87
- Create an agent in `config/application.rb`, and register the rack middleware:
21
+ ## Rails Setup
88
22
 
23
+ Create an agent in `config/initializers/optics_agent.rb`, and register the rack middleware:
89
24
  ```ruby
90
- module YourApplicationRails
91
- class Application < Rails::Application
92
- # ...
93
-
94
- config.optics_agent = OpticsAgent::Agent.instance
95
- config.middleware.use config.optics_agent.rack_middleware
96
- end
25
+ optics_agent = OpticsAgent::Agent.new
26
+ optics_agent.configure do
27
+ schema YourSchema
28
+ # See other configuration options below
97
29
  end
98
-
99
- ```
100
-
101
- Register the GraphQL middleware when you create your schema:
102
-
103
- ```ruby
104
- Rails.application.config.optics_agent.instrument_schema(YourSchema)
30
+ Rails.application.config.middleware.use optics_agent.rack_middleware
105
31
  ```
106
32
 
107
33
  Register Optics Agent on the GraphQL context within your `graphql` action as below:
108
-
109
34
  ```ruby
110
35
  def create
111
36
  query_string = params[:query]
@@ -125,16 +50,50 @@ end
125
50
 
126
51
  You can check out the GitHunt Rails API server example here: https://github.com/apollostack/githunt-api-rails
127
52
 
128
- ## Known limitations
53
+ ## General Setup
129
54
 
130
- Currently the agent is in alpha state; it is intended for early access use in development or basic (non-performance oriented) staging testing.
55
+ You must:
131
56
 
132
- We are working on resolving a [list of issues](https://github.com/apollostack/optics-agent-ruby/projects/1) to put together a production-ready beta launch. The headline issues as things stand are:
57
+ 1. Create an agent with `OpticsAgent::Agent.new`
58
+ 2. Register your schema with the `agent.configure` block
59
+ 3. Attach the `agent.rack_middleware` to your Rack router
60
+ 4. Ensure you pass the `optics_agent` context from the rack environment to your schema execution.
133
61
 
134
- - The agent is overly chatty and uses a naive threading mechanism that may lose reporting data when threads exit/etc.
135
- - Instrumentation timings may not be correct in all uses cases, and query times include the full rack request time.
62
+ ### Configuration
63
+
64
+ After creating an agent, you can configure it with:
65
+
66
+ ```rb
67
+ # defaults are show below
68
+ agent.configure do
69
+ # The schema you wish to instrument
70
+ schema YourSchema
71
+
72
+ # Your API key for the Optics service. This defaults to the OPTICS_API_KEY environment variable, but can be overridden here.
73
+ api_key ENV['OPTICS_API_KEY']
74
+
75
+ # Log detailed debugging messages
76
+ debug false
77
+
78
+ # Don't report anything to Optics (useful for testing)
79
+ disable_reporting false
136
80
 
137
- You can follow along with our [Beta Release Project](https://github.com/apollostack/optics-agent-ruby/projects/1), or even get in touch if you want to help out getting there!
81
+ # Print JSON versions of the data sent to Optics to the log
82
+ print_reports false
83
+
84
+ # Send detailed traces along with usage reports
85
+ report_traces true
86
+
87
+ # How long to wait before sending a schema report after startup, in, milliseconds
88
+ schema_report_delay_ms 10 * 1000
89
+
90
+ # How often to send reports in milliseconds. Defaults to 1 minute. Minimum 10 seconds. You shouldn't need to set this unless you are debugging.
91
+ report_interval_ms 60 * 1000
92
+
93
+ # Where to send the reports. Defaults to the production Optics endpoint, or the `OPTICS_ENDPOINT_URL` environment variable if it is set. You shouldn't need to set this unless you are debugging
94
+ endpoint_url 'https://optics-report.apollodata.com'
95
+ end
96
+ ```
138
97
 
139
98
  ## Development
140
99
 
data/lib/optics-agent.rb CHANGED
@@ -2,3 +2,4 @@ module OpticsAgent
2
2
  end
3
3
 
4
4
  require 'optics-agent/agent'
5
+ require 'optics-agent/instrumenters/patch-graphql-schema'
@@ -1,89 +1,112 @@
1
- require 'singleton'
2
1
  require 'optics-agent/rack-middleware'
3
- require 'optics-agent/graphql-middleware'
2
+ require 'optics-agent/instrumenters/field'
4
3
  require 'optics-agent/reporting/report_job'
5
4
  require 'optics-agent/reporting/schema_job'
6
5
  require 'optics-agent/reporting/query-trace'
7
6
  require 'net/http'
7
+ require 'faraday'
8
8
 
9
9
  module OpticsAgent
10
- # XXX: this is a class but acts as a singleton right now.
11
- # Need to figure out how to pass the agent into the middleware
12
- # (for instance we could dynamically generate a middleware class,
13
- # or ask the user to pass the agent as an option) to avoid it
14
10
  class Agent
15
- include Singleton
16
11
  include OpticsAgent::Reporting
17
12
 
18
- attr_reader :schema
13
+ attr_reader :schema, :report_traces
19
14
 
20
15
  def initialize
21
16
  @query_queue = []
22
17
  @semaphone = Mutex.new
23
18
 
24
19
  # set defaults
25
- self.set_options
26
- end
27
-
28
- def set_options(
29
- debug: false,
30
- disable_reporting: false,
31
- print_reports: false,
32
- schema_report_delay_ms: 10 * 1000,
33
- report_interval_ms: 60 * 1000,
34
- api_key: ENV['OPTICS_API_KEY'],
35
- endpoint_url: ENV['OPTICS_ENDPOINT_URL'] || 'https://optics-report.apollodata.com'
36
- )
37
- @debug = debug
38
- @disable_reporting = disable_reporting || !endpoint_url || endpoint_url.nil?
39
- @print_reports = print_reports
40
- @schema_report_delay_ms = schema_report_delay_ms
41
- @report_interval_ms = report_interval_ms
42
- @api_key = api_key
43
- @endpoint_url = endpoint_url
20
+ @configuration = Configuration.new
21
+ end
22
+
23
+ def configure(&block)
24
+ @configuration.instance_eval(&block)
25
+
26
+ if @configuration.schema && @schema != @configuration.schema
27
+ instrument_schema(@configuration.schema)
28
+ end
29
+ end
30
+
31
+ def disabled?
32
+ @configuration.disable_reporting || !@configuration.api_key || !@schema
33
+ end
34
+
35
+ def report_traces?
36
+ @configuration.report_traces
44
37
  end
45
38
 
46
39
  def instrument_schema(schema)
40
+ unless @configuration.api_key
41
+ warn """No api_key set.
42
+ Either configure it or use the OPTICS_API_KEY environment variable.
43
+ """
44
+ return
45
+ end
46
+
47
+ if @schema
48
+ warn """Agent has already instrumented a schema.
49
+ Perhaps you are calling both `agent.configure { schema YourSchema }` and
50
+ `agent.instrument_schema YourSchema`?
51
+ """
52
+ return
53
+ end
54
+
47
55
  @schema = schema
48
- debug "adding middleware to schema"
49
- schema.middleware << graphql_middleware
56
+ @schema._attach_optics_agent(self)
50
57
 
51
- unless @disable_reporting
58
+ unless disabled?
52
59
  debug "spawning schema thread"
53
60
  Thread.new do
54
61
  debug "schema thread spawned"
55
- sleep @schema_report_delay_ms / 1000
62
+ sleep @configuration.schema_report_delay_ms / 1000.0
56
63
  debug "running schema job"
57
64
  SchemaJob.new.perform(self)
58
65
  end
59
66
  end
60
67
  end
61
68
 
62
- # we call this method on every request to ensure that the reporting thread
69
+ # We call this method on every request to ensure that the reporting thread
63
70
  # is active in the correct process for pre-forking webservers like unicorn
64
71
  def ensure_reporting!
65
- unless @reporting_thread_active
72
+ unless @schema
73
+ warn """No schema instrumented.
74
+ Use the `schema` configuration setting, or call `agent.instrument_schema`
75
+ """
76
+ return
77
+ end
78
+
79
+ unless @reporting_thread_active || disabled?
66
80
  schedule_report
67
81
  @reporting_thread_active = true
68
82
  end
69
83
  end
70
84
 
85
+ def reporting_connection
86
+ @reporting_connection ||=
87
+ Faraday.new(:url => @configuration.endpoint_url) do |faraday|
88
+ # XXX: allow setting adaptor in config
89
+ faraday.adapter :net_http_persistent
90
+ end
91
+ end
92
+
71
93
  def schedule_report
72
94
  debug "spawning reporting thread"
73
95
  Thread.new do
74
96
  debug "reporting thread spawned"
75
97
  while true
76
- sleep @report_interval_ms / 1000
98
+ sleep @configuration.report_interval_ms / 1000.0
77
99
  debug "running reporting job"
78
100
  ReportJob.new.perform(self)
101
+ debug "finished running reporting job"
79
102
  end
80
103
  end
81
104
  end
82
105
 
83
- def add_query(query, rack_env, start_time, end_time)
106
+ def add_query(*args)
84
107
  @semaphone.synchronize {
85
108
  debug { "adding query to queue, queue length was #{@query_queue.length}" }
86
- @query_queue << [query, rack_env, start_time, end_time]
109
+ @query_queue << args
87
110
  }
88
111
  end
89
112
 
@@ -97,32 +120,31 @@ module OpticsAgent
97
120
  end
98
121
 
99
122
  def rack_middleware
123
+ # We need to pass ourselves to the class we return here because
124
+ # rack will instanciate it. (See comment at the top of RackMiddleware)
125
+ OpticsAgent::RackMiddleware.agent = self
100
126
  OpticsAgent::RackMiddleware
101
127
  end
102
128
 
103
129
  def graphql_middleware
104
- # graphql middleware doesn't seem to need the agent but certainly could have it
105
- OpticsAgent::GraphqlMiddleware.new
130
+ warn "You no longer need to pass the optics agent middleware, it now attaches itself"
106
131
  end
107
132
 
108
133
  def send_message(path, message)
109
- req = Net::HTTP::Post.new(path)
110
- req['x-api-key'] = @api_key
111
- req['user-agent'] = "optics-agent-rb"
112
-
113
- req.body = message.class.encode(message)
114
- if @debug || @print_reports
115
- log "sending message: #{message.class.encode_json(message)}"
134
+ response = reporting_connection.post do |request|
135
+ request.url path
136
+ request.headers['x-api-key'] = @configuration.api_key
137
+ request.headers['user-agent'] = "optics-agent-rb"
138
+
139
+ request.body = message.class.encode(message)
140
+ if @configuration.debug || @configuration.print_reports
141
+ log "sending message: #{message.class.encode_json(message)}"
142
+ end
116
143
  end
117
144
 
118
- uri = URI.parse(@endpoint_url)
119
- http = Net::HTTP.new(uri.host, uri.port)
120
- http.use_ssl = true
121
- res = http.request(req)
122
-
123
- if @debug || @print_reports
124
- log "got response: #{res.inspect}"
125
- log "response body: #{res.body.inspect}"
145
+ if @configuration.debug || @configuration.print_reports
146
+ log "got response: #{response}"
147
+ log "response body: #{response.body}"
126
148
  end
127
149
  end
128
150
 
@@ -131,11 +153,48 @@ module OpticsAgent
131
153
  puts "optics-agent: #{message}"
132
154
  end
133
155
 
156
+ def warn(message = nil)
157
+ log "WARNING: #{message}"
158
+ end
159
+
134
160
  def debug(message = nil)
135
- if @debug
161
+ if @configuration.debug
136
162
  message = yield unless message
137
163
  log "DEBUG: #{message} <#{Process.pid} | #{Thread.current.object_id}>"
138
164
  end
139
165
  end
140
166
  end
167
+
168
+ class Configuration
169
+ def self.defaults
170
+ {
171
+ schema: nil,
172
+ debug: false,
173
+ disable_reporting: false,
174
+ print_reports: false,
175
+ report_traces: true,
176
+ schema_report_delay_ms: 10 * 1000,
177
+ report_interval_ms: 60 * 1000,
178
+ api_key: ENV['OPTICS_API_KEY'],
179
+ endpoint_url: ENV['OPTICS_ENDPOINT_URL'] || 'https://optics-report.apollodata.com'
180
+ }
181
+ end
182
+
183
+ # Allow e.g. `debug false` == `debug = false` in configuration blocks
184
+ defaults.each_key do |key|
185
+ define_method key, ->(*maybe_value) do
186
+ if (maybe_value.length === 1)
187
+ self.instance_variable_set("@#{key}", maybe_value[0])
188
+ elsif (maybe_value.length === 0)
189
+ self.instance_variable_get("@#{key}")
190
+ else
191
+ throw new ArgumentError("0 or 1 argument expected")
192
+ end
193
+ end
194
+ end
195
+
196
+ def initialize
197
+ self.class.defaults.each { |key, value| self.send(key, value) }
198
+ end
199
+ end
141
200
  end
@@ -3,7 +3,11 @@ module OpticsAgent
3
3
  INTROSPECTION_QUERY ||= IO.read("#{File.dirname(__FILE__)}/introspection-query.graphql")
4
4
 
5
5
  def introspect_schema(schema)
6
- schema.execute(INTROSPECTION_QUERY)['data']['__schema']
6
+ result = schema.execute(INTROSPECTION_QUERY,
7
+ context: {optics_agent: :skip}
8
+ )
9
+
10
+ result['data']['__schema']
7
11
  end
8
12
  end
9
13
  end
@@ -0,0 +1,51 @@
1
+ module OpticsAgent
2
+ module Instrumenters
3
+ class Field
4
+ attr_accessor :agent
5
+
6
+ def instrument(type, field)
7
+ old_resolve_proc = field.resolve_proc
8
+ new_resolve_proc = ->(obj, args, ctx) {
9
+ if @agent
10
+ middleware(@agent, type, obj, field, args, ctx, ->() { old_resolve_proc.call(obj, args, ctx) })
11
+ else
12
+ old_resolve_proc.call(obj, args, ctx)
13
+ end
14
+ }
15
+
16
+ field.redefine do
17
+ resolve(new_resolve_proc)
18
+ end
19
+ end
20
+
21
+ def middleware(agent, parent_type, parent_object, field_definition, field_args, query_context, next_middleware)
22
+ agent_context = query_context[:optics_agent]
23
+
24
+ unless agent_context
25
+ agent.warn """No agent passed in graphql context.
26
+ Ensure you set `context: {optics_agent: env[:optics_agent].with_document(document) }``
27
+ when executing your graphql query.
28
+ If you don't want to instrument this query, pass `context: {optics_agent: :skip}`.
29
+ """
30
+ return
31
+ end
32
+
33
+ # This happens when an introspection query occurs (reporting schema)
34
+ # Also, people could potentially use it to skip reporting
35
+ if agent_context == :skip
36
+ return next_middleware.call
37
+ end
38
+
39
+ query = agent_context.query
40
+
41
+ start_offset = query.duration_so_far
42
+ result = next_middleware.call
43
+ duration = query.duration_so_far - start_offset
44
+
45
+ query.report_field(parent_type.to_s, field_definition.name, start_offset, duration)
46
+
47
+ result
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,29 @@
1
+ # Monkey patch GraphQL::Schema.define so that it automatically attaches our
2
+ # field instrumenter. Our instrumenter will do nothing unless we later attach
3
+ # an agent to it, which this will also allow us to do.
4
+
5
+ require 'graphql'
6
+ require 'optics-agent/instrumenters/field'
7
+
8
+ module OpticsAgent::GraphQLSchemaExtensions
9
+ def define(**kwargs, &block)
10
+ @instrumenter = OpticsAgent::Instrumenters::Field.new
11
+
12
+ class << self
13
+ def _attach_optics_agent(agent)
14
+ agent.debug "Attaching agent to field instrumenter"
15
+ @instrumenter.agent = agent
16
+ end
17
+ end
18
+
19
+ instrumenter = @instrumenter
20
+ super **kwargs do
21
+ instance_eval(&block) if block
22
+ instrument :field, instrumenter
23
+ end
24
+ end
25
+ end
26
+
27
+ class GraphQL::Schema
28
+ prepend OpticsAgent::GraphQLSchemaExtensions
29
+ end
@@ -58,7 +58,7 @@ module OpticsAgent
58
58
 
59
59
  visitor[Nodes::InlineFragment].leave << -> (node, parent) do
60
60
  selections = current[:selections]
61
- stack[0][:selections] << "... on #{node.type} #{block(selections)}"
61
+ stack[0][:selections] << "... on #{node.type.name} #{block(selections)}"
62
62
  end
63
63
 
64
64
  visitor[Nodes::FragmentSpread].leave << -> (node, parent) do
@@ -90,7 +90,7 @@ module OpticsAgent
90
90
  visitor[Nodes::FragmentDefinition].leave << -> (node, parent) do
91
91
  selections = current[:selections]
92
92
  if (used_fragment_names.include?(node.name))
93
- output << " fragment #{node.name} on #{node.type} " \
93
+ output << " fragment #{node.name} on #{node.type.name} " \
94
94
  + block(selections)
95
95
  end
96
96
  end
@@ -1,19 +1,25 @@
1
1
  require 'optics-agent/agent'
2
2
  require 'optics-agent/reporting/query'
3
-
4
3
  module OpticsAgent
5
4
  class RackMiddleware
5
+ # Right now we assume there'll only be a single rack middleware in an
6
+ # app, and set the agent via a class attribute. This means that in theory
7
+ # you could use more than one agent, but at most one middleware.
8
+ # In the future, if we see a need for more than one middleware, we could
9
+ # probably just copy the class when calling `agent.rack_middleware`
10
+ class << self
11
+ attr_accessor :agent
12
+ end
13
+
6
14
  def initialize(app, options={})
7
15
  @app = app
8
16
  end
9
17
 
10
18
  def call(env)
11
- start_time = Time.now
12
-
13
- # XXX: figure out a way to pass this in here
14
- agent = OpticsAgent::Agent.instance
19
+ agent = self.class.agent
15
20
  agent.ensure_reporting!
16
21
  agent.debug { "rack-middleware: request started" }
22
+
17
23
  query = OpticsAgent::Reporting::Query.new
18
24
 
19
25
  # Attach so resolver middleware can access
@@ -21,12 +27,11 @@ module OpticsAgent
21
27
 
22
28
  result = @app.call(env)
23
29
 
24
- # XXX: this approach means if the user forgets to call with_document
25
- # we just never log queries. Can we detect if the request is a graphql one?
26
30
  agent.debug { "rack-middleware: request finished" }
27
31
  if (query.document)
28
32
  agent.debug { "rack-middleware: adding query to agent" }
29
- agent.add_query(query, env, start_time, Time.now)
33
+ query.finish!
34
+ agent.add_query(query, env)
30
35
  end
31
36
 
32
37
  result
@@ -11,13 +11,16 @@ module OpticsAgent::Reporting
11
11
  def generate_timestamp(time)
12
12
  Apollo::Optics::Proto::Timestamp.new({
13
13
  seconds: time.to_i,
14
- nanos: time.to_i % 1 * 1e9
14
+ nanos: duration_nanos(time.to_i % 1)
15
15
  });
16
16
  end
17
17
 
18
- def duration_nanos(start_time, end_time)
19
- throw "start_time before end_time" if (start_time > end_time)
20
- ((end_time - start_time) * 1e9).to_i
18
+ def duration_nanos(duration_in_seconds)
19
+ (duration_in_seconds * 1e9).to_i
20
+ end
21
+
22
+ def duration_micros(duration_in_seconds)
23
+ (duration_in_seconds * 1e6).to_i
21
24
  end
22
25
 
23
26
  # XXX: implement
@@ -29,9 +32,11 @@ module OpticsAgent::Reporting
29
32
  }
30
33
  end
31
34
 
32
- def add_latency(counts, start_time, end_time)
33
- micros = (end_time - start_time) * 1e6
34
- bucket = latency_bucket(micros)
35
- counts[bucket] += 1
35
+ def add_latency(counts, duration_in_seconds)
36
+ counts[latency_bucket_for_duration(duration_in_seconds)] += 1
37
+ end
38
+
39
+ def latency_bucket_for_duration(duration_in_seconds)
40
+ latency_bucket(duration_micros(duration_in_seconds))
36
41
  end
37
42
  end
@@ -10,11 +10,11 @@ module OpticsAgent::Reporting
10
10
 
11
11
  attr_accessor :report
12
12
 
13
- def initialize(query, rack_env, start_time, end_time)
13
+ def initialize(query, rack_env)
14
14
  trace = Trace.new({
15
- start_time: generate_timestamp(start_time),
16
- end_time: generate_timestamp(end_time),
17
- duration_ns: duration_nanos(start_time, end_time),
15
+ start_time: generate_timestamp(query.start_time),
16
+ end_time: generate_timestamp(query.end_time),
17
+ duration_ns: duration_nanos(query.duration),
18
18
  signature: query.signature
19
19
  })
20
20
 
@@ -31,11 +31,11 @@ module OpticsAgent::Reporting
31
31
  })
32
32
 
33
33
  nodes = []
34
- query.each_report do |type_name, field_name, field_start_time, field_end_time|
34
+ query.each_report do |type_name, field_name, start_offset, duration|
35
35
  nodes << Trace::Node.new({
36
36
  field_name: "#{type_name}.#{field_name}",
37
- start_time: duration_nanos(start_time, field_start_time),
38
- end_time: duration_nanos(start_time, field_end_time)
37
+ start_time: duration_nanos(start_offset),
38
+ end_time: duration_nanos(start_offset + duration)
39
39
  })
40
40
  end
41
41
  trace.execute = Trace::Node.new({
@@ -2,6 +2,8 @@ require 'apollo/optics/proto/reports_pb'
2
2
  require 'optics-agent/reporting/helpers'
3
3
  require 'optics-agent/normalization/latency'
4
4
  require 'optics-agent/normalization/query'
5
+ require 'hitimes'
6
+ require 'forwardable'
5
7
 
6
8
  module OpticsAgent::Reporting
7
9
  # This is a convenience class that enables us to fairly blindly
@@ -12,13 +14,25 @@ module OpticsAgent::Reporting
12
14
  include OpticsAgent::Normalization
13
15
  include OpticsAgent::Normalization::Query
14
16
 
17
+ extend Forwardable
18
+
15
19
  attr_accessor :document
20
+ attr_reader :start_time, :end_time
21
+ def_delegators :@interval, :duration, :duration_so_far
16
22
 
17
23
  def initialize
18
24
  @reports = []
19
25
 
20
26
  @document = nil
21
- @signature
27
+ @signature = nil
28
+
29
+ @start_time = Time.now
30
+ @interval = Hitimes::Interval.now
31
+ end
32
+
33
+ def finish!
34
+ @end_time = Time.now
35
+ @interval.stop
22
36
  end
23
37
 
24
38
  def signature
@@ -32,8 +46,8 @@ module OpticsAgent::Reporting
32
46
  end
33
47
 
34
48
  # we do nothing when reporting to minimize impact
35
- def report_field(type_name, field_name, start_time, end_time)
36
- @reports << [type_name, field_name, start_time, end_time]
49
+ def report_field(type_name, field_name, start_offset, duration)
50
+ @reports << [type_name, field_name, start_offset, duration]
37
51
  end
38
52
 
39
53
  def each_report
@@ -44,7 +58,7 @@ module OpticsAgent::Reporting
44
58
 
45
59
  # add our results to an existing StatsPerSignature
46
60
  def add_to_stats(stats_per_signature)
47
- each_report do |type_name, field_name, start_time, end_time|
61
+ each_report do |type_name, field_name, start_offset, duration|
48
62
  type_stat = stats_per_signature.per_type.find { |ts| ts.name == type_name }
49
63
  unless type_stat
50
64
  type_stat = TypeStat.new({ name: type_name })
@@ -60,7 +74,7 @@ module OpticsAgent::Reporting
60
74
  type_stat.field << field_stat
61
75
  end
62
76
 
63
- add_latency(field_stat.latency_count, start_time, end_time)
77
+ add_latency(field_stat.latency_count, duration)
64
78
  end
65
79
  end
66
80
  end
@@ -1,6 +1,7 @@
1
1
  require 'apollo/optics/proto/reports_pb'
2
2
  require 'optics-agent/reporting/helpers'
3
3
  require 'optics-agent/normalization/latency'
4
+ require 'hitimes'
4
5
 
5
6
  module OpticsAgent::Reporting
6
7
  # This class represents a complete report that we send to the optics server
@@ -11,9 +12,10 @@ module OpticsAgent::Reporting
11
12
  include OpticsAgent::Reporting
12
13
  include OpticsAgent::Normalization
13
14
 
14
- attr_accessor :report
15
+ attr_reader :report
16
+ attr_reader :traces_to_report
15
17
 
16
- def initialize
18
+ def initialize(report_traces: true)
17
19
  # internal report that we encapsulate
18
20
  @report = StatsReport.new({
19
21
  header: ReportHeader.new({
@@ -21,28 +23,39 @@ module OpticsAgent::Reporting
21
23
  }),
22
24
  start_time: generate_timestamp(Time.now)
23
25
  })
26
+
27
+ @interval = Hitimes::Interval.now
28
+
29
+ @report_traces = report_traces
30
+ @traces_to_report = []
24
31
  end
25
32
 
26
33
  def finish!
27
34
  @report.end_time ||= generate_timestamp(Time.now)
28
- @report.realtime_duration || duration_nanos(@report.start_time, @report.end_time)
35
+ @report.realtime_duration || duration_nanos(@interval.stop)
29
36
  end
30
37
 
31
38
  def send_with(agent)
39
+ agent.debug do
40
+ n_queries = 0
41
+ @report.per_signature.values.each do |signature_stats|
42
+ signature_stats.per_client_name.values.each do |client_stats|
43
+ n_queries += client_stats.count_per_version.values.reduce(&:+)
44
+ end
45
+ end
46
+ "Sending #{n_queries} queries and #{@traces_to_report.length} traces"
47
+ end
32
48
  self.finish!
49
+ @traces_to_report.each do |trace|
50
+ trace.send_with(agent)
51
+ end
33
52
  agent.send_message('/api/ss/stats', @report)
34
53
  end
35
54
 
36
- # XXX: record timing / client
37
- def add_query(query, rack_env, start_time, end_time)
55
+ def add_query(query, rack_env)
38
56
  @report.per_signature[query.signature] ||= StatsPerSignature.new
39
57
  signature_stats = @report.per_signature[query.signature]
40
58
 
41
- add_client_stats(signature_stats, rack_env, start_time, end_time)
42
- query.add_to_stats(signature_stats)
43
- end
44
-
45
- def add_client_stats(signature_stats, rack_env, start_time, end_time)
46
59
  info = client_info(rack_env)
47
60
  signature_stats.per_client_name[info[:client_name]] ||= StatsPerClientName.new({
48
61
  latency_count: empty_latency_count,
@@ -51,10 +64,20 @@ module OpticsAgent::Reporting
51
64
  client_stats = signature_stats.per_client_name[info[:client_name]]
52
65
 
53
66
  # XXX: handle errors
54
- add_latency(client_stats.latency_count, start_time, end_time)
55
-
67
+ add_latency(client_stats.latency_count, query.duration)
56
68
  client_stats.count_per_version[info[:client_version]] ||= 0
57
69
  client_stats.count_per_version[info[:client_version]] += 1
70
+
71
+ query.add_to_stats(signature_stats)
72
+
73
+ if @report_traces
74
+ # Is this the first query we've seen in this reporting period and
75
+ # latency bucket? In which case we want to send a trace
76
+ bucket = latency_bucket_for_duration(query.duration)
77
+ if (client_stats.latency_count[bucket] == 1)
78
+ @traces_to_report << QueryTrace.new(query, rack_env)
79
+ end
80
+ end
58
81
  end
59
82
 
60
83
  # take a graphql schema and add returnTypes to all the fields on our report
@@ -3,17 +3,18 @@ require 'optics-agent/reporting/report'
3
3
  module OpticsAgent::Reporting
4
4
  class ReportJob
5
5
  def perform(agent)
6
- report = OpticsAgent::Reporting::Report.new
7
- agent.clear_query_queue.each do |item|
8
- report.add_query(*item)
6
+ begin
7
+ report = OpticsAgent::Reporting::Report.new(report_traces: agent.report_traces?)
8
+ agent.clear_query_queue.each do |item|
9
+ report.add_query(*item)
10
+ end
9
11
 
10
- # XXX: don't send *every* trace
11
- query_trace = QueryTrace.new(*item)
12
- query_trace.send_with(agent)
12
+ report.decorate_from_schema(agent.schema)
13
+ report.send_with(agent)
14
+ rescue StandardError => e
15
+ agent.debug "report failed #{e}"
16
+ raise
13
17
  end
14
-
15
- report.decorate_from_schema(agent.schema)
16
- report.send_with(agent)
17
18
  end
18
19
  end
19
20
  end
@@ -3,8 +3,14 @@ require 'optics-agent/reporting/schema'
3
3
  module OpticsAgent::Reporting
4
4
  class SchemaJob
5
5
  def perform(agent)
6
- schema = OpticsAgent::Reporting::Schema.new agent.schema
7
- schema.send_with(agent)
6
+ begin
7
+ schema = OpticsAgent::Reporting::Schema.new agent.schema
8
+ schema.send_with(agent)
9
+ rescue StandardError => e
10
+ agent.debug "schema report failed #{e}"
11
+ agent.debug e.backtrace
12
+ raise
13
+ end
8
14
  end
9
15
  end
10
16
  end
@@ -1,10 +1,10 @@
1
1
  require 'ostruct'
2
- require 'optics-agent/graphql-middleware'
2
+ require 'optics-agent/instrumenters/field'
3
3
  require 'graphql'
4
4
 
5
5
  include OpticsAgent
6
6
 
7
- describe GraphqlMiddleware do
7
+ describe Instrumenters::Field do
8
8
  it 'collects the correct query stats' do
9
9
  person_type = GraphQL::ObjectType.define do
10
10
  name "Person"
@@ -25,23 +25,26 @@ describe GraphqlMiddleware do
25
25
  end
26
26
  end
27
27
 
28
+ instrumenter = Instrumenters::Field.new
29
+ instrumenter.agent = true
28
30
  schema = GraphQL::Schema.define do
29
31
  query query_type
32
+ instrument :field, instrumenter
30
33
  end
31
34
 
32
- schema.middleware << GraphqlMiddleware.new
33
-
34
35
  query = spy("query")
36
+ allow(query).to receive(:duration_so_far).and_return(1.0)
37
+
35
38
  schema.execute('{ person { firstName lastName } }', {
36
39
  context: { optics_agent: OpenStruct.new(query: query) }
37
40
  })
38
41
 
39
42
  expect(query).to have_received(:report_field).exactly(3).times
40
43
  expect(query).to have_received(:report_field)
41
- .with('Query', 'person', be_instance_of(Time), be_instance_of(Time))
44
+ .with('Query', 'person', be_instance_of(Float), be_instance_of(Float))
42
45
  expect(query).to have_received(:report_field)
43
- .with('Person', 'firstName', be_instance_of(Time), be_instance_of(Time))
46
+ .with('Person', 'firstName', be_instance_of(Float), be_instance_of(Float))
44
47
  expect(query).to have_received(:report_field)
45
- .with('Person', 'lastName', be_instance_of(Time), be_instance_of(Time))
48
+ .with('Person', 'lastName', be_instance_of(Float), be_instance_of(Float))
46
49
  end
47
50
  end
@@ -9,12 +9,13 @@ include OpticsAgent::Reporting
9
9
  describe QueryTrace do
10
10
  it "can represent a simple query" do
11
11
  query = Query.new
12
- query.report_field 'Person', 'firstName', 1, 1.1
13
- query.report_field 'Person', 'lastName', 1, 1.1
14
- query.report_field 'Query', 'person', 1, 1.22
12
+ query.report_field 'Person', 'firstName', 1, 0.1
13
+ query.report_field 'Person', 'lastName', 1.1, 0.1
14
+ query.report_field 'Query', 'person', 1.2, 0.22
15
15
  query.document = '{field}'
16
+ query.finish!
16
17
 
17
- trace = QueryTrace.new(query, {}, 1, 1.25)
18
+ trace = QueryTrace.new(query, {})
18
19
 
19
20
  expect(trace.report).to be_instance_of(TracesReport)
20
21
  expect(trace.report.trace.length).to eq(1)
@@ -26,7 +27,7 @@ describe QueryTrace do
26
27
  match_array(['Query.person', 'Person.firstName', 'Person.lastName'])
27
28
 
28
29
  firstName_node = nodes.find { |n| n.field_name == 'Person.firstName' }
29
- expect(firstName_node.start_time).to eq(0)
30
- expect(firstName_node.end_time).to eq(0.1 * 1e9)
30
+ expect(firstName_node.start_time).to eq(1 * 1e9)
31
+ expect(firstName_node.end_time).to eq(1.1 * 1e9)
31
32
  end
32
33
  end
data/spec/report_spec.rb CHANGED
@@ -9,13 +9,14 @@ include Apollo::Optics::Proto
9
9
  describe Report do
10
10
  it "can represent a simple query" do
11
11
  query = Query.new
12
- query.report_field 'Person', 'firstName', 1, 1.1
13
- query.report_field 'Person', 'lastName', 1, 1.1
14
- query.report_field 'Query', 'person', 1, 1.22
12
+ query.report_field 'Person', 'firstName', 1, 0.1
13
+ query.report_field 'Person', 'lastName', 1.1, 0.1
14
+ query.report_field 'Query', 'person', 1.2, 0.22
15
+ query.finish!
15
16
  query.document = '{field}'
16
17
 
17
18
  report = Report.new
18
- report.add_query query, {}, 1, 1.25
19
+ report.add_query query, {}
19
20
  report.finish!
20
21
 
21
22
  expect(report.report).to be_an_instance_of(StatsReport)
@@ -38,20 +39,22 @@ describe Report do
38
39
 
39
40
  it "can aggregate the results of multiple queries with the same shape" do
40
41
  queryOne = Query.new
41
- queryOne.report_field 'Person', 'firstName', 1, 1.1
42
- queryOne.report_field 'Person', 'lastName', 1, 1.1
43
- queryOne.report_field 'Query', 'person', 1, 1.22
42
+ queryOne.report_field 'Person', 'firstName', 1, 0.1
43
+ queryOne.report_field 'Person', 'lastName', 1.1, 0.1
44
+ queryOne.report_field 'Query', 'person', 1.2, 0.22
45
+ queryOne.finish!
44
46
  queryOne.document = '{field}'
45
47
 
46
48
  queryTwo = Query.new
47
- queryTwo.report_field 'Person', 'firstName', 1, 1.05
48
- queryTwo.report_field 'Person', 'lastName', 1, 1.05
49
- queryTwo.report_field 'Query', 'person', 1, 1.2
49
+ queryTwo.report_field 'Person', 'firstName', 1, 0.05
50
+ queryTwo.report_field 'Person', 'lastName', 1.05, 0.05
51
+ queryTwo.report_field 'Query', 'person', 1.1, 0.20
52
+ queryTwo.finish!
50
53
  queryTwo.document = '{field}'
51
54
 
52
55
  report = Report.new
53
- report.add_query queryOne, {}, 1, 1.1
54
- report.add_query queryTwo, {}, 1, 1.1
56
+ report.add_query queryOne, {}
57
+ report.add_query queryTwo, {}
55
58
  report.finish!
56
59
 
57
60
  expect(report.report).to be_an_instance_of(StatsReport)
@@ -74,20 +77,22 @@ describe Report do
74
77
 
75
78
  it "can aggregate the results of multiple queries with a different shape" do
76
79
  queryOne = Query.new
77
- queryOne.report_field 'Person', 'firstName', 1, 1.1
78
- queryOne.report_field 'Person', 'lastName', 1, 1.1
79
- queryOne.report_field 'Query', 'person', 1, 1.22
80
+ queryOne.report_field 'Person', 'firstName', 1, 0.1
81
+ queryOne.report_field 'Person', 'lastName', 1.1, 0.1
82
+ queryOne.report_field 'Query', 'person', 1.2, 0.22
83
+ queryOne.finish!
80
84
  queryOne.document = '{fieldOne}'
81
85
 
82
86
  queryTwo = Query.new
83
- queryTwo.report_field 'Person', 'firstName', 1, 1.05
84
- queryTwo.report_field 'Person', 'lastName', 1, 1.05
85
- queryTwo.report_field 'Query', 'person', 1, 1.02
87
+ queryTwo.report_field 'Person', 'firstName', 1, 0.05
88
+ queryTwo.report_field 'Person', 'lastName', 1.05, 0.05
89
+ queryTwo.report_field 'Query', 'person', 1.1, 0.20
90
+ queryTwo.finish!
86
91
  queryTwo.document = '{fieldTwo}'
87
92
 
88
93
  report = Report.new
89
- report.add_query queryOne, {}, 1, 1.1
90
- report.add_query queryTwo, {}, 1, 1.1
94
+ report.add_query queryOne, {}
95
+ report.add_query queryTwo, {}
91
96
  report.finish!
92
97
 
93
98
  expect(report.report).to be_an_instance_of(StatsReport)
@@ -110,12 +115,13 @@ describe Report do
110
115
 
111
116
  it "can decorate it's fields with resultTypes from a schema" do
112
117
  query = Query.new
113
- query.report_field 'Person', 'firstName', 1, 1.1
114
- query.report_field 'Person', 'age', 1, 1.1
118
+ query.report_field 'Person', 'firstName', 1, 0.1
119
+ query.report_field 'Person', 'age', 1.1, 0.1
120
+ query.finish!
115
121
  query.document = '{field}'
116
122
 
117
123
  report = Report.new
118
- report.add_query query, {}, 1, 1.25
124
+ report.add_query query, {}
119
125
  report.finish!
120
126
 
121
127
  person_type = GraphQL::ObjectType.define do
@@ -147,13 +153,14 @@ describe Report do
147
153
 
148
154
  it "can handle introspection fields" do
149
155
  query = Query.new
150
- query.report_field 'Query', '__schema', 1, 1.1
151
- query.report_field 'Query', '__typename', 1, 1.1
152
- query.report_field 'Query', '__type', 1, 1.1
156
+ query.report_field 'Query', '__schema', 1, 0.1
157
+ query.report_field 'Query', '__typename', 1.1, 0.1
158
+ query.report_field 'Query', '__type', 1.2, 1.1
159
+ query.finish!
153
160
  query.document = '{field}'
154
161
 
155
162
  report = Report.new
156
- report.add_query query, {}, 1, 1.25
163
+ report.add_query query, {}
157
164
  report.finish!
158
165
 
159
166
  query_type = GraphQL::ObjectType.define do
@@ -180,4 +187,54 @@ describe Report do
180
187
  expect(typename_stats.returnType).to eq('Query')
181
188
  end
182
189
 
190
+ describe "trace reporting" do
191
+ class QueryMock
192
+ attr_reader :signature, :duration, :start_time, :end_time
193
+ def initialize(signature, duration)
194
+ @signature = signature
195
+ @duration = duration
196
+ @start_time = Time.now
197
+ @end_time = Time.now
198
+ end
199
+
200
+ def add_to_stats(_); end
201
+ def each_report(); end
202
+ end
203
+
204
+ it "only sends one trace for two queries of the same shape and latency" do
205
+ queryOne = QueryMock.new '{field}', 1
206
+ queryTwo = QueryMock.new '{field}', 1
207
+
208
+ report = Report.new
209
+ report.add_query queryOne, {}
210
+ report.add_query queryTwo, {}
211
+ report.finish!
212
+
213
+ expect(report.traces_to_report.length).to be(1)
214
+ end
215
+
216
+ it "sends two traces for two queries of the same shape and different latencies" do
217
+ queryOne = QueryMock.new '{field}', 1
218
+ queryTwo = QueryMock.new '{field}', 1.1
219
+
220
+ report = Report.new
221
+ report.add_query queryOne, {}
222
+ report.add_query queryTwo, {}
223
+ report.finish!
224
+
225
+ expect(report.traces_to_report.length).to be(2)
226
+ end
227
+
228
+ it "sends two traces for two queries of different shapes and the same latency" do
229
+ queryOne = QueryMock.new '{fieldOne}', 1
230
+ queryTwo = QueryMock.new '{fieldTwo}', 1
231
+
232
+ report = Report.new
233
+ report.add_query queryOne, {}
234
+ report.add_query queryTwo, {}
235
+ report.finish!
236
+
237
+ expect(report.traces_to_report.length).to be(2)
238
+ end
239
+ end
183
240
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: optics-agent
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - 'Tom Coleman '
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 0.19.0
19
+ version: 1.1.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 0.19.0
26
+ version: 1.1.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: google-protobuf
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -38,6 +38,48 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: 3.1.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: faraday
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.9.2
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.9.2
55
+ - !ruby/object:Gem::Dependency
56
+ name: net-http-persistent
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 2.0.0
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 2.0.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: hitimes
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 1.2.4
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 1.2.4
41
83
  - !ruby/object:Gem::Dependency
42
84
  name: rake
43
85
  requirement: !ruby/object:Gem::Requirement
@@ -77,9 +119,10 @@ files:
77
119
  - lib/apollo/optics/proto/reports_pb.rb
78
120
  - lib/optics-agent.rb
79
121
  - lib/optics-agent/agent.rb
80
- - lib/optics-agent/graphql-middleware.rb
81
122
  - lib/optics-agent/instrumentation/introspection-query.graphql
82
123
  - lib/optics-agent/instrumentation/query-schema.rb
124
+ - lib/optics-agent/instrumenters/field.rb
125
+ - lib/optics-agent/instrumenters/patch-graphql-schema.rb
83
126
  - lib/optics-agent/normalization/latency.rb
84
127
  - lib/optics-agent/normalization/query.rb
85
128
  - lib/optics-agent/rack-middleware.rb
@@ -91,7 +134,7 @@ files:
91
134
  - lib/optics-agent/reporting/schema.rb
92
135
  - lib/optics-agent/reporting/schema_job.rb
93
136
  - spec/benchmark/benchmark.rb
94
- - spec/graphql-middleware_spec.rb
137
+ - spec/field_instrumenter_spec.rb
95
138
  - spec/latency_spec.rb
96
139
  - spec/query-normalization_spec.rb
97
140
  - spec/query_trace_spec.rb
@@ -126,7 +169,7 @@ specification_version: 4
126
169
  summary: An Agent for Apollo Optics
127
170
  test_files:
128
171
  - spec/benchmark/benchmark.rb
129
- - spec/graphql-middleware_spec.rb
172
+ - spec/field_instrumenter_spec.rb
130
173
  - spec/latency_spec.rb
131
174
  - spec/query-normalization_spec.rb
132
175
  - spec/query_trace_spec.rb
@@ -1,18 +0,0 @@
1
- module OpticsAgent
2
- class GraphqlMiddleware
3
- def call(parent_type, parent_object, field_definition, field_args, query_context, next_middleware)
4
- # This happens when an introspection query occurs (reporting schema)
5
- # However, we could also use it to tell people if they've set things up wrong.
6
- return next_middleware.call unless query_context[:optics_agent]
7
-
8
- start_time = Time.now
9
- result = next_middleware.call
10
- end_time = Time.now
11
-
12
- query = query_context[:optics_agent].query
13
- query.report_field(parent_type.to_s, field_definition.name, start_time, end_time)
14
-
15
- result
16
- end
17
- end
18
- end