optics-agent 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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