optics-agent 0.1.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.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ NDY0NThhZDNkNTNhNGYwOWEyMmFlMDhjOGFkNGVlNjM0MjAzYWNkNw==
5
+ data.tar.gz: !binary |-
6
+ YWIzNWIxMzNhMGRkMTA3ZGZjMWRiODNjYWVmNTY3OTY4ODA1ZmIxNg==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ Yzk3ODZjY2UzNWRlMzExMGEyMmY1MmRmNDBhNDA3MzVkYzg4ZjRkMWY1NTU3
10
+ ZjUwNzU1OWJhNjVhOGQ0OTYxZTk0YmQ0NWMzMDdmYzM4N2M3ZTdmMDQyMDMx
11
+ NWUyMDY4NmE1ODVlYjk1MWM4ZDY2MzY0Y2U1ZTM3ODljMzIzY2Q=
12
+ data.tar.gz: !binary |-
13
+ NGVlZGUyNTRmM2E3OGRjMWE1MGJkNmQ4ODQ2MjQ3MWIxOTY1YWM0ZDI1NDJi
14
+ NzUzYTk2MTc4ZDNmNzJmZDdkZjFiOWZmNDM2MzQwOGZhYzM4ZjExZWFkYjM3
15
+ ZmUxZjM0NDZkYjJmMDc2ODFiMjUyMTI2M2NiYzY5ZGY5MmFiOWM=
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2016 Meteor Development Group, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,139 @@
1
+ # optics-agent-ruby
2
+ Optics Agent for GraphQL Monitoring in Ruby.
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
+ [![Build Status](https://travis-ci.org/apollostack/optics-agent-ruby.svg?branch=master)](https://travis-ci.org/apollostack/optics-agent-ruby)
7
+
8
+ ## Installing
9
+
10
+ Add
11
+
12
+ ```ruby
13
+ gem 'optics-agent'
14
+ ```
15
+
16
+ To your `Gemfile`
17
+
18
+ ## Setup
19
+
20
+ ### API key
21
+
22
+ You'll need to run your app with the `OPTICS_API_KEY` environment variable set to the name of your Apollo Optics service; at the moment Optics is in early access alpha--[get in touch](http://www.apollostack.com/optics) if you want to be part of our early access program.
23
+
24
+ ### Basic Rack/Sinatra
25
+
26
+ Create an agent
27
+
28
+ ```ruby
29
+ # we expect one day there'll be some options
30
+ agent = OpticsAgent::Agent.instance
31
+ ```
32
+
33
+ Register the Rack middleware (say in a `config.ru`):
34
+
35
+ ```ruby
36
+ use agent.rack_middleware
37
+ ```
38
+
39
+ Register the Graphql middleware:
40
+
41
+ ```ruby
42
+ agent.instrument_schema(YourSchema)
43
+ ```
44
+
45
+ Add something like this to your route:
46
+
47
+ ```ruby
48
+ post '/graphql' do
49
+ request.body.rewind
50
+ document = JSON.parse request.body.read
51
+ result = Schema.execute(document["query"],
52
+ variables: document["variables"],
53
+ context: { optics_agent: env[:optics_agent].with_document(document) }
54
+ )
55
+ JSON.generate(result)
56
+ end
57
+ ```
58
+
59
+ ## Rails
60
+
61
+ The equivalent of the above for Rails is (I welcome better solutions!)
62
+
63
+ Create an agent in `config/apllication.rb`, and register the rack middleware:
64
+
65
+ ```ruby
66
+ module XXXRails
67
+ class Application < Rails::Application
68
+ # ...
69
+
70
+ config.optics_agent = OpticsAgent::Agent.instance
71
+ config.middleware.use config.optics_agent.rack_middleware
72
+ end
73
+ end
74
+
75
+ ```
76
+
77
+ Register the Graphql middleware when you create your schema:
78
+
79
+ ```ruby
80
+ Rails.application.config.optics_agent.instrument_schema(YourSchema)
81
+ ```
82
+
83
+ Add something like this to your `graphql` action:
84
+
85
+ ```ruby
86
+ def create
87
+ query_string = params[:query]
88
+ query_variables = ensure_hash(params[:variables])
89
+ result = YourSchema.execute(query_string,
90
+ variables: query_variables,
91
+ context: {
92
+ optics_agent: env[:optics_agent].with_document(params)
93
+ }
94
+ )
95
+ render json: result
96
+ end
97
+ ```
98
+
99
+ You can check out the GitHunt Rails API server example here: https://github.com/apollostack/githunt-api-rails
100
+
101
+ ## Known limitations
102
+
103
+ Currently the agent is in alpha state; it is intended for early access use in development or basic (non-performance oriented) staging testing.
104
+
105
+ 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:
106
+
107
+ - The agent is overly chatty and uses a naive threading mechanism that may lose reporting data when threads exit/etc.
108
+ - Instrumentation timings may not be correct in all uses cases, and query times include the full rack request time.
109
+
110
+ 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!
111
+
112
+ ## Development
113
+
114
+ ### Running tests
115
+
116
+ ```
117
+ bundle install
118
+ bundle exec rspec
119
+ ```
120
+
121
+ ### Building protobuf definitions
122
+
123
+ Ensure you've installed `protobuf` and development dependencies.
124
+
125
+ ```bash
126
+ # this works on OSX
127
+ brew install protobuf
128
+
129
+ # ensure it's version 3
130
+ protoc --version
131
+
132
+ bundle install
133
+ ````
134
+
135
+ Compile the `.proto` definitions with
136
+
137
+ ```bash
138
+ bundle exec rake proto:compile
139
+ ```
@@ -0,0 +1,145 @@
1
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
2
+ # source: reports.proto
3
+
4
+ require 'google/protobuf'
5
+
6
+ Google::Protobuf::DescriptorPool.generated_pool.build do
7
+ add_message "apollo.optics.proto.Id128" do
8
+ optional :high, :sfixed64, 1
9
+ optional :low, :sfixed64, 2
10
+ end
11
+ add_message "apollo.optics.proto.Timestamp" do
12
+ optional :seconds, :int64, 1
13
+ optional :nanos, :int32, 2
14
+ end
15
+ add_message "apollo.optics.proto.Error" do
16
+ optional :message, :string, 1
17
+ end
18
+ add_message "apollo.optics.proto.Trace" do
19
+ optional :server_id, :message, 1, "apollo.optics.proto.Id128"
20
+ optional :client_id, :message, 2, "apollo.optics.proto.Id128"
21
+ optional :start_time, :message, 4, "apollo.optics.proto.Timestamp"
22
+ optional :signature, :string, 5
23
+ optional :details, :message, 6, "apollo.optics.proto.Trace.Details"
24
+ optional :client_name, :string, 7
25
+ optional :client_version, :string, 8
26
+ optional :client_address, :string, 9
27
+ optional :http, :message, 10, "apollo.optics.proto.Trace.HTTPInfo"
28
+ optional :parse, :message, 12, "apollo.optics.proto.Trace.Node"
29
+ optional :validate, :message, 13, "apollo.optics.proto.Trace.Node"
30
+ optional :execute, :message, 14, "apollo.optics.proto.Trace.Node"
31
+ end
32
+ add_message "apollo.optics.proto.Trace.Details" do
33
+ map :variables, :string, :bytes, 1
34
+ optional :raw_query, :string, 2
35
+ optional :operation_name, :string, 3
36
+ end
37
+ add_message "apollo.optics.proto.Trace.HTTPInfo" do
38
+ optional :method, :enum, 1, "apollo.optics.proto.Trace.HTTPInfo.Method"
39
+ optional :host, :string, 2
40
+ optional :path, :string, 3
41
+ map :headers, :string, :string, 4
42
+ optional :secure, :bool, 8
43
+ optional :protocol, :string, 9
44
+ end
45
+ add_enum "apollo.optics.proto.Trace.HTTPInfo.Method" do
46
+ value :UNKNOWN, 0
47
+ value :OPTIONS, 1
48
+ value :GET, 2
49
+ value :HEAD, 3
50
+ value :POST, 4
51
+ value :PUT, 5
52
+ value :DELETE, 6
53
+ value :TRACE, 7
54
+ value :CONNECT, 8
55
+ value :PATCH, 9
56
+ end
57
+ add_message "apollo.optics.proto.Trace.Node" do
58
+ optional :type, :string, 3
59
+ optional :alias, :string, 4
60
+ optional :start_time, :uint64, 8
61
+ optional :end_time, :uint64, 9
62
+ repeated :error, :message, 11, "apollo.optics.proto.Error"
63
+ repeated :child, :message, 12, "apollo.optics.proto.Trace.Node"
64
+ oneof :id do
65
+ optional :field_name, :string, 1
66
+ optional :index, :uint32, 2
67
+ end
68
+ end
69
+ add_message "apollo.optics.proto.ReportHeader" do
70
+ optional :service, :string, 3
71
+ optional :hostname, :string, 5
72
+ optional :agent_version, :string, 6
73
+ optional :service_version, :string, 7
74
+ optional :runtime_version, :string, 8
75
+ optional :uname, :string, 9
76
+ end
77
+ add_message "apollo.optics.proto.StatsPerClientName" do
78
+ repeated :latency_count, :uint64, 1
79
+ repeated :error_count, :uint64, 2
80
+ map :count_per_version, :string, :uint64, 3
81
+ end
82
+ add_message "apollo.optics.proto.FieldStat" do
83
+ optional :name, :string, 2
84
+ optional :returnType, :string, 3
85
+ repeated :latency_count, :uint64, 8
86
+ end
87
+ add_message "apollo.optics.proto.TypeStat" do
88
+ optional :name, :string, 1
89
+ repeated :field, :message, 2, "apollo.optics.proto.FieldStat"
90
+ end
91
+ add_message "apollo.optics.proto.StatsPerSignature" do
92
+ map :per_client_name, :string, :message, 1, "apollo.optics.proto.StatsPerClientName"
93
+ repeated :per_type, :message, 2, "apollo.optics.proto.TypeStat"
94
+ end
95
+ add_message "apollo.optics.proto.TracesReport" do
96
+ optional :header, :message, 1, "apollo.optics.proto.ReportHeader"
97
+ repeated :trace, :message, 2, "apollo.optics.proto.Trace"
98
+ end
99
+ add_message "apollo.optics.proto.Field" do
100
+ optional :name, :string, 2
101
+ optional :returnType, :string, 3
102
+ end
103
+ add_message "apollo.optics.proto.Type" do
104
+ optional :name, :string, 1
105
+ repeated :field, :message, 2, "apollo.optics.proto.Field"
106
+ end
107
+ add_message "apollo.optics.proto.StatsReport" do
108
+ optional :header, :message, 1, "apollo.optics.proto.ReportHeader"
109
+ optional :start_time, :message, 8, "apollo.optics.proto.Timestamp"
110
+ optional :end_time, :message, 9, "apollo.optics.proto.Timestamp"
111
+ optional :realtime_duration, :uint64, 10
112
+ map :per_signature, :string, :message, 12, "apollo.optics.proto.StatsPerSignature"
113
+ repeated :type, :message, 13, "apollo.optics.proto.Type"
114
+ end
115
+ add_message "apollo.optics.proto.SchemaReport" do
116
+ optional :header, :message, 1, "apollo.optics.proto.ReportHeader"
117
+ optional :introspection_result, :string, 8
118
+ repeated :type, :message, 9, "apollo.optics.proto.Type"
119
+ end
120
+ end
121
+
122
+ module Apollo
123
+ module Optics
124
+ module Proto
125
+ Id128 = Google::Protobuf::DescriptorPool.generated_pool.lookup("apollo.optics.proto.Id128").msgclass
126
+ Timestamp = Google::Protobuf::DescriptorPool.generated_pool.lookup("apollo.optics.proto.Timestamp").msgclass
127
+ Error = Google::Protobuf::DescriptorPool.generated_pool.lookup("apollo.optics.proto.Error").msgclass
128
+ Trace = Google::Protobuf::DescriptorPool.generated_pool.lookup("apollo.optics.proto.Trace").msgclass
129
+ Trace::Details = Google::Protobuf::DescriptorPool.generated_pool.lookup("apollo.optics.proto.Trace.Details").msgclass
130
+ Trace::HTTPInfo = Google::Protobuf::DescriptorPool.generated_pool.lookup("apollo.optics.proto.Trace.HTTPInfo").msgclass
131
+ Trace::HTTPInfo::Method = Google::Protobuf::DescriptorPool.generated_pool.lookup("apollo.optics.proto.Trace.HTTPInfo.Method").enummodule
132
+ Trace::Node = Google::Protobuf::DescriptorPool.generated_pool.lookup("apollo.optics.proto.Trace.Node").msgclass
133
+ ReportHeader = Google::Protobuf::DescriptorPool.generated_pool.lookup("apollo.optics.proto.ReportHeader").msgclass
134
+ StatsPerClientName = Google::Protobuf::DescriptorPool.generated_pool.lookup("apollo.optics.proto.StatsPerClientName").msgclass
135
+ FieldStat = Google::Protobuf::DescriptorPool.generated_pool.lookup("apollo.optics.proto.FieldStat").msgclass
136
+ TypeStat = Google::Protobuf::DescriptorPool.generated_pool.lookup("apollo.optics.proto.TypeStat").msgclass
137
+ StatsPerSignature = Google::Protobuf::DescriptorPool.generated_pool.lookup("apollo.optics.proto.StatsPerSignature").msgclass
138
+ TracesReport = Google::Protobuf::DescriptorPool.generated_pool.lookup("apollo.optics.proto.TracesReport").msgclass
139
+ Field = Google::Protobuf::DescriptorPool.generated_pool.lookup("apollo.optics.proto.Field").msgclass
140
+ Type = Google::Protobuf::DescriptorPool.generated_pool.lookup("apollo.optics.proto.Type").msgclass
141
+ StatsReport = Google::Protobuf::DescriptorPool.generated_pool.lookup("apollo.optics.proto.StatsReport").msgclass
142
+ SchemaReport = Google::Protobuf::DescriptorPool.generated_pool.lookup("apollo.optics.proto.SchemaReport").msgclass
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,4 @@
1
+ module OpticsAgent
2
+ end
3
+
4
+ require 'optics-agent/agent'
@@ -0,0 +1,56 @@
1
+ require 'singleton'
2
+ require 'optics-agent/rack-middleware'
3
+ require 'optics-agent/graphql-middleware'
4
+ require 'optics-agent/reporting/report_job'
5
+ require 'optics-agent/reporting/schema_job'
6
+ require 'optics-agent/reporting/query-trace'
7
+
8
+ module OpticsAgent
9
+ # XXX: this is a class but acts as a singleton right now.
10
+ # Need to figure out how to pass the agent into the middleware
11
+ # (for instance we could dynamically generate a middleware class,
12
+ # or ask the user to pass the agent as an option) to avoid it
13
+ class Agent
14
+ include Singleton
15
+ include OpticsAgent::Reporting
16
+
17
+ attr_reader :schema
18
+
19
+ def initialize
20
+ @query_queue = []
21
+ @semaphone = Mutex.new
22
+ ReportJob.perform_in(60, self)
23
+ end
24
+
25
+ def instrument_schema(schema)
26
+ @schema = schema
27
+ schema.middleware << graphql_middleware
28
+
29
+ puts 'scheduling schema job'
30
+ SchemaJob.perform_in(10, self)
31
+ end
32
+
33
+ def add_query(query, rack_env, start_time, end_time)
34
+ @semaphone.synchronize {
35
+ @query_queue << [query, rack_env, start_time, end_time]
36
+ }
37
+ end
38
+
39
+ def clear_query_queue
40
+ @semaphone.synchronize {
41
+ queue = @query_queue
42
+ @query_queue = []
43
+ queue
44
+ }
45
+ end
46
+
47
+ def rack_middleware
48
+ OpticsAgent::RackMiddleware
49
+ end
50
+
51
+ def graphql_middleware
52
+ # graphql middleware doesn't seem to need the agent but certainly could have it
53
+ OpticsAgent::GraphqlMiddleware.new
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,18 @@
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
@@ -0,0 +1,91 @@
1
+ query ShorterIntrospectionQuery {
2
+ __schema {
3
+ queryType { name }
4
+ mutationType { name }
5
+ subscriptionType { name }
6
+ types {
7
+ ...FullType
8
+ }
9
+ directives {
10
+ name
11
+ # description
12
+ locations
13
+ args {
14
+ ...InputValue
15
+ }
16
+ }
17
+ }
18
+ }
19
+
20
+ fragment FullType on __Type {
21
+ kind
22
+ name
23
+ # description
24
+ fields(includeDeprecated: true) {
25
+ name
26
+ # description
27
+ args {
28
+ ...InputValue
29
+ }
30
+ type {
31
+ ...TypeRef
32
+ }
33
+ isDeprecated
34
+ # deprecationReason
35
+ }
36
+ inputFields {
37
+ ...InputValue
38
+ }
39
+ interfaces {
40
+ ...TypeRef
41
+ }
42
+ enumValues(includeDeprecated: true) {
43
+ name
44
+ # description
45
+ isDeprecated
46
+ # deprecationReason
47
+ }
48
+ possibleTypes {
49
+ ...TypeRef
50
+ }
51
+ }
52
+
53
+ fragment InputValue on __InputValue {
54
+ name
55
+ # description
56
+ type { ...TypeRef }
57
+ # defaultValue
58
+ }
59
+
60
+ fragment TypeRef on __Type {
61
+ kind
62
+ name
63
+ ofType {
64
+ kind
65
+ name
66
+ ofType {
67
+ kind
68
+ name
69
+ ofType {
70
+ kind
71
+ name
72
+ ofType {
73
+ kind
74
+ name
75
+ ofType {
76
+ kind
77
+ name
78
+ ofType {
79
+ kind
80
+ name
81
+ ofType {
82
+ kind
83
+ name
84
+ }
85
+ }
86
+ }
87
+ }
88
+ }
89
+ }
90
+ }
91
+ }