apollo-federation 0.3.2 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +24 -0
- data/bin/generate-protos.sh +15 -0
- data/lib/apollo-federation.rb +4 -0
- data/lib/apollo-federation/tracing.rb +52 -0
- data/lib/apollo-federation/tracing/node_map.rb +72 -0
- data/lib/apollo-federation/tracing/proto.rb +12 -0
- data/lib/apollo-federation/tracing/proto/apollo.proto +488 -0
- data/lib/apollo-federation/tracing/proto/apollo_pb.rb +305 -0
- data/lib/apollo-federation/tracing/tracer.rb +182 -0
- data/lib/apollo-federation/version.rb +1 -1
- metadata +23 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 72d35c557f29e6039362b933cbaa55847b0d359f8d9da703b0a26615598d1642
|
4
|
+
data.tar.gz: be611b3e1516a3dfc91b08f0c2c44473efac2f49508e53482a3fff277aa0bb42
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 79ab490f10fdd9faec720d447f76e4c12d63acb974087d22d8f61fb1cb8fbbf5f56f89d7f7edb4bf995c6313bff734e291d4ed6e726d01a9ab7f8548309150b9
|
7
|
+
data.tar.gz: 4ea53d9aee497cd334ecbeaf486db7514b6e6499f76641b6e8150deae73824eff725765ca38adbbaabe3576d46f63bad8c8a728575801dd1bfbf7eac9f16a1e6
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
# [0.4.0](https://github.com/Gusto/apollo-federation-ruby/compare/v0.3.2...v0.4.0) (2019-09-10)
|
2
|
+
|
3
|
+
|
4
|
+
### Features
|
5
|
+
|
6
|
+
* add support for federated tracing ([#16](https://github.com/Gusto/apollo-federation-ruby/issues/16)) ([57ecc5b](https://github.com/Gusto/apollo-federation-ruby/commit/57ecc5b)), closes [#14](https://github.com/Gusto/apollo-federation-ruby/issues/14)
|
7
|
+
|
1
8
|
## [0.3.2](https://github.com/Gusto/apollo-federation-ruby/compare/v0.3.1...v0.3.2) (2019-09-03)
|
2
9
|
|
3
10
|
|
data/README.md
CHANGED
@@ -147,6 +147,30 @@ class User < BaseObject
|
|
147
147
|
end
|
148
148
|
```
|
149
149
|
|
150
|
+
### Tracing
|
151
|
+
|
152
|
+
To support [federated tracing](https://www.apollographql.com/docs/apollo-server/federation/metrics/):
|
153
|
+
|
154
|
+
1. Add `use ApolloFederation::Tracing` to your schema class.
|
155
|
+
2. Change your controller to add `tracing_enabled: true` to the execution context based on the presence of the "include trace" header:
|
156
|
+
```ruby
|
157
|
+
def execute
|
158
|
+
# ...
|
159
|
+
context = {
|
160
|
+
tracing_enabled: ApolloFederation::Tracing.should_add_traces(headers)
|
161
|
+
}
|
162
|
+
# ...
|
163
|
+
end
|
164
|
+
```
|
165
|
+
3. Change your controller to attach the traces to the response:
|
166
|
+
```ruby
|
167
|
+
def execute
|
168
|
+
# ...
|
169
|
+
result = YourSchema.execute(query, ...)
|
170
|
+
render json: ApolloFederation::Tracing.attach_trace_to_result(result)
|
171
|
+
end
|
172
|
+
```
|
173
|
+
|
150
174
|
## Known Issues and Limitations
|
151
175
|
- Currently only works with class-based schemas
|
152
176
|
- Does not add directives to the output of `Schema.to_definition`. Since `graphql-ruby` doesn't natively support schema directives, the directives will only be visible to the [Apollo Gateway](https://www.apollographql.com/docs/apollo-server/api/apollo-gateway/) through the `Query._service` field (see the [Apollo Federation specification](https://www.apollographql.com/docs/apollo-server/federation/federation-spec/))
|
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
set -eo pipefail
|
4
|
+
|
5
|
+
DIR=`dirname "$0"`
|
6
|
+
OUTPUT_DIR=$DIR/../lib/apollo-federation/tracing/proto
|
7
|
+
|
8
|
+
echo "Removing old client"
|
9
|
+
rm -f $OUTPUT_DIR/apollo.proto $OUTPUT_DIR/apollo_pb.rb
|
10
|
+
|
11
|
+
echo "Downloading latest Apollo Protobuf IDL"
|
12
|
+
curl --silent --output lib/apollo-federation/tracing/proto/apollo.proto https://raw.githubusercontent.com/apollographql/apollo-server/master/packages/apollo-engine-reporting-protobuf/src/reports.proto
|
13
|
+
|
14
|
+
echo "Generating Ruby client stubs"
|
15
|
+
protoc -I lib/apollo-federation/tracing/proto --ruby_out lib/apollo-federation/tracing/proto lib/apollo-federation/tracing/proto/apollo.proto
|
data/lib/apollo-federation.rb
CHANGED
@@ -4,3 +4,7 @@ require 'apollo-federation/version'
|
|
4
4
|
require 'apollo-federation/schema'
|
5
5
|
require 'apollo-federation/object'
|
6
6
|
require 'apollo-federation/field'
|
7
|
+
require 'apollo-federation/tracing/proto'
|
8
|
+
require 'apollo-federation/tracing/node_map'
|
9
|
+
require 'apollo-federation/tracing/tracer'
|
10
|
+
require 'apollo-federation/tracing'
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ApolloFederation
|
4
|
+
module Tracing
|
5
|
+
KEY = :ftv1
|
6
|
+
DEBUG_KEY = "#{KEY}_debug".to_sym
|
7
|
+
|
8
|
+
module_function
|
9
|
+
|
10
|
+
def use(schema)
|
11
|
+
schema.tracer ApolloFederation::Tracing::Tracer
|
12
|
+
end
|
13
|
+
|
14
|
+
def should_add_traces(headers)
|
15
|
+
headers && headers['apollo-federation-include-trace'] == KEY.to_s
|
16
|
+
end
|
17
|
+
|
18
|
+
def attach_trace_to_result(result)
|
19
|
+
return result unless result.context[:tracing_enabled]
|
20
|
+
|
21
|
+
trace = result.context.namespace(KEY)
|
22
|
+
unless trace[:start_time]
|
23
|
+
raise StandardError.new, 'Apollo Federation Tracing not installed. \
|
24
|
+
Add `use ApollFederation::Tracing` to your schema.'
|
25
|
+
end
|
26
|
+
|
27
|
+
result['errors']&.each do |error|
|
28
|
+
trace[:node_map].add_error(error)
|
29
|
+
end
|
30
|
+
|
31
|
+
proto = ApolloFederation::Tracing::Trace.new(
|
32
|
+
start_time: to_proto_timestamp(trace[:start_time]),
|
33
|
+
end_time: to_proto_timestamp(trace[:end_time]),
|
34
|
+
duration_ns: trace[:end_time_nanos] - trace[:start_time_nanos],
|
35
|
+
root: trace[:node_map].root,
|
36
|
+
)
|
37
|
+
|
38
|
+
result[:extensions] ||= {}
|
39
|
+
result[:extensions][KEY] = Base64.encode64(proto.class.encode(proto))
|
40
|
+
|
41
|
+
if result.context[:debug_tracing]
|
42
|
+
result[:extensions][DEBUG_KEY] = proto.to_h
|
43
|
+
end
|
44
|
+
|
45
|
+
result.to_h
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_proto_timestamp(time)
|
49
|
+
Google::Protobuf::Timestamp.new(seconds: time.to_i, nanos: time.nsec)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/core_ext/array/wrap'
|
4
|
+
require 'apollo-federation/tracing/proto'
|
5
|
+
|
6
|
+
module ApolloFederation
|
7
|
+
module Tracing
|
8
|
+
# NodeMap stores a flat map of trace nodes by stringified paths
|
9
|
+
# (i.e. "_entities.0.id") for fast lookup when we need to alter
|
10
|
+
# nodes (to add end times or errors.)
|
11
|
+
#
|
12
|
+
# When adding a node to the NodeMap, it will create any missing
|
13
|
+
# parent nodes and ensure the tree is consistent.
|
14
|
+
#
|
15
|
+
# Only the "root" node is attached to the trace extension.
|
16
|
+
class NodeMap
|
17
|
+
ROOT_KEY = ''
|
18
|
+
|
19
|
+
attr_reader :nodes
|
20
|
+
def initialize
|
21
|
+
@nodes = {
|
22
|
+
ROOT_KEY => ApolloFederation::Tracing::Node.new,
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
def root
|
27
|
+
nodes[ROOT_KEY]
|
28
|
+
end
|
29
|
+
|
30
|
+
def node_for_path(path)
|
31
|
+
nodes[Array.wrap(path).join('.')]
|
32
|
+
end
|
33
|
+
|
34
|
+
def add(path)
|
35
|
+
node = ApolloFederation::Tracing::Node.new
|
36
|
+
node_key = path.join('.')
|
37
|
+
key = path.last
|
38
|
+
|
39
|
+
case key
|
40
|
+
when String # field
|
41
|
+
node.response_name = key
|
42
|
+
when Integer # index of an array
|
43
|
+
node.index = key
|
44
|
+
end
|
45
|
+
|
46
|
+
nodes[node_key] = node
|
47
|
+
|
48
|
+
# find or create a parent node and add this node to its children
|
49
|
+
parent_path = path[0..-2]
|
50
|
+
parent_node = nodes[parent_path.join('.')] || add(parent_path)
|
51
|
+
parent_node.child << node
|
52
|
+
|
53
|
+
node
|
54
|
+
end
|
55
|
+
|
56
|
+
def add_error(error)
|
57
|
+
path = Array.wrap(error['path']).join('.')
|
58
|
+
node = nodes[path] || root
|
59
|
+
|
60
|
+
locations = Array.wrap(error['locations']).map do |location|
|
61
|
+
ApolloFederation::Tracing::Location.new(location)
|
62
|
+
end
|
63
|
+
|
64
|
+
node.error << ApolloFederation::Tracing::Error.new(
|
65
|
+
message: error['message'],
|
66
|
+
location: locations,
|
67
|
+
json: JSON.dump(error),
|
68
|
+
)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'proto/apollo_pb'
|
4
|
+
|
5
|
+
module ApolloFederation
|
6
|
+
module Tracing
|
7
|
+
Trace = ::Mdg::Engine::Proto::Trace
|
8
|
+
Node = ::Mdg::Engine::Proto::Trace::Node
|
9
|
+
Location = ::Mdg::Engine::Proto::Trace::Location
|
10
|
+
Error = ::Mdg::Engine::Proto::Trace::Error
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,488 @@
|
|
1
|
+
syntax = "proto3";
|
2
|
+
|
3
|
+
package mdg.engine.proto;
|
4
|
+
|
5
|
+
import "google/protobuf/timestamp.proto";
|
6
|
+
|
7
|
+
message Trace {
|
8
|
+
message CachePolicy {
|
9
|
+
enum Scope {
|
10
|
+
UNKNOWN = 0;
|
11
|
+
PUBLIC = 1;
|
12
|
+
PRIVATE = 2;
|
13
|
+
}
|
14
|
+
|
15
|
+
Scope scope = 1;
|
16
|
+
int64 max_age_ns = 2; // use 0 for absent, -1 for 0
|
17
|
+
}
|
18
|
+
|
19
|
+
message Details {
|
20
|
+
// The variables associated with this query (unless the reporting agent is
|
21
|
+
// configured to keep them all private). Values are JSON: ie, strings are
|
22
|
+
// enclosed in double quotes, etc. The value of a private variable is
|
23
|
+
// the empty string.
|
24
|
+
map<string, string> variables_json = 4;
|
25
|
+
// Deprecated. Engineproxy did not encode variable values as JSON, so you
|
26
|
+
// couldn't tell numbers from numeric strings. Send variables_json instead.
|
27
|
+
map<string, bytes> variables = 1;
|
28
|
+
// Optional: this is the original full query before the signature algorithm
|
29
|
+
// is applied. Engineproxy always sent this in all traces, which meant that
|
30
|
+
// literal-masking done by the signature algorithm didn't fully hide
|
31
|
+
// sensitive data from Engine servers. apollo-engine-reporting does not
|
32
|
+
// include this by default. (The Engine frontend does not currently show
|
33
|
+
// this field.)
|
34
|
+
string raw_query = 2;
|
35
|
+
// Don't include this in traces inside a FullTracesReport; the operation
|
36
|
+
// name for these traces comes from the key of the traces_per_query map.
|
37
|
+
string operation_name = 3;
|
38
|
+
}
|
39
|
+
|
40
|
+
message Error {
|
41
|
+
string message = 1; // required
|
42
|
+
repeated Location location = 2;
|
43
|
+
uint64 time_ns = 3;
|
44
|
+
string json = 4;
|
45
|
+
}
|
46
|
+
|
47
|
+
message HTTP {
|
48
|
+
message Values {
|
49
|
+
repeated string value = 1;
|
50
|
+
}
|
51
|
+
|
52
|
+
enum Method {
|
53
|
+
UNKNOWN = 0;
|
54
|
+
OPTIONS = 1;
|
55
|
+
GET = 2;
|
56
|
+
HEAD = 3;
|
57
|
+
POST = 4;
|
58
|
+
PUT = 5;
|
59
|
+
DELETE = 6;
|
60
|
+
TRACE = 7;
|
61
|
+
CONNECT = 8;
|
62
|
+
PATCH = 9;
|
63
|
+
}
|
64
|
+
Method method = 1;
|
65
|
+
string host = 2;
|
66
|
+
string path = 3;
|
67
|
+
|
68
|
+
// Should exclude manual blacklist ("Auth" by default)
|
69
|
+
map<string, Values> request_headers = 4;
|
70
|
+
map<string, Values> response_headers = 5;
|
71
|
+
|
72
|
+
uint32 status_code = 6;
|
73
|
+
|
74
|
+
bool secure = 8; // TLS was used
|
75
|
+
string protocol = 9; // by convention "HTTP/1.0", "HTTP/1.1", "HTTP/2" or "h2"
|
76
|
+
}
|
77
|
+
|
78
|
+
message Location {
|
79
|
+
uint32 line = 1;
|
80
|
+
uint32 column = 2;
|
81
|
+
}
|
82
|
+
|
83
|
+
// We store information on each resolver execution as a Node on a tree.
|
84
|
+
// The structure of the tree corresponds to the structure of the GraphQL
|
85
|
+
// response; it does not indicate the order in which resolvers were
|
86
|
+
// invoked. Note that nodes representing indexes (and the root node)
|
87
|
+
// don't contain all Node fields (eg types and times).
|
88
|
+
message Node {
|
89
|
+
// The name of the field (for Nodes representing a resolver call) or the
|
90
|
+
// index in a list (for intermediate Nodes representing elements of a list).
|
91
|
+
// field_name is the name of the field as it appears in the GraphQL
|
92
|
+
// response: ie, it may be an alias. (In that case, the original_field_name
|
93
|
+
// field holds the actual field name from the schema.) In any context where
|
94
|
+
// we're building up a path, we use the response_name rather than the
|
95
|
+
// original_field_name.
|
96
|
+
oneof id {
|
97
|
+
string response_name = 1;
|
98
|
+
uint32 index = 2;
|
99
|
+
}
|
100
|
+
|
101
|
+
string original_field_name = 14;
|
102
|
+
|
103
|
+
// The field's return type; e.g. "String!" for User.email:String!
|
104
|
+
string type = 3;
|
105
|
+
|
106
|
+
// The field's parent type; e.g. "User" for User.email:String!
|
107
|
+
string parent_type = 13;
|
108
|
+
|
109
|
+
CachePolicy cache_policy = 5;
|
110
|
+
|
111
|
+
// relative to the trace's start_time, in ns
|
112
|
+
uint64 start_time = 8;
|
113
|
+
// relative to the trace's start_time, in ns
|
114
|
+
uint64 end_time = 9;
|
115
|
+
|
116
|
+
repeated Error error = 11;
|
117
|
+
repeated Node child = 12;
|
118
|
+
|
119
|
+
reserved 4;
|
120
|
+
}
|
121
|
+
|
122
|
+
// represents a node in the query plan, under which there is a trace tree for that service fetch.
|
123
|
+
// In particular, each fetch node represents a call to an implementing service, and calls to implementing
|
124
|
+
// services may not be unique. See https://github.com/apollographql/apollo-server/blob/master/packages/apollo-gateway/src/QueryPlan.ts
|
125
|
+
// for more information and details.
|
126
|
+
message QueryPlanNode {
|
127
|
+
// This represents a set of nodes to be executed sequentially by the Gateway executor
|
128
|
+
message SequenceNode {
|
129
|
+
repeated QueryPlanNode nodes = 1;
|
130
|
+
}
|
131
|
+
// This represents a set of nodes to be executed in parallel by the Gateway executor
|
132
|
+
message ParallelNode {
|
133
|
+
repeated QueryPlanNode nodes = 1;
|
134
|
+
}
|
135
|
+
// This represents a node to send an operation to an implementing service
|
136
|
+
message FetchNode {
|
137
|
+
// XXX When we want to include more details about the sub-operation that was
|
138
|
+
// executed against this service, we should include that here in each fetch node.
|
139
|
+
// This might include an operation signature, requires directive, reference resolutions, etc.
|
140
|
+
string serviceName = 1;
|
141
|
+
|
142
|
+
bool traceParsingFailed = 2;
|
143
|
+
|
144
|
+
// This Trace only contains start_time, end_time, duration_ns, and root;
|
145
|
+
// all timings were calculated **on the federated service**, and clock skew
|
146
|
+
// will be handled by the ingress server.
|
147
|
+
Trace trace = 3;
|
148
|
+
|
149
|
+
// relative to the outer trace's start_time, in ns, measured in the gateway.
|
150
|
+
uint64 sent_time_offset = 4;
|
151
|
+
|
152
|
+
// Wallclock times measured in the gateway for when this operation was
|
153
|
+
// sent and received.
|
154
|
+
google.protobuf.Timestamp sent_time = 5;
|
155
|
+
google.protobuf.Timestamp received_time = 6;
|
156
|
+
}
|
157
|
+
|
158
|
+
// This node represents a way to reach into the response path and attach related entities.
|
159
|
+
// XXX Flatten is really not the right name and this node may be renamed in the query planner.
|
160
|
+
message FlattenNode {
|
161
|
+
repeated ResponsePathElement response_path = 1;
|
162
|
+
QueryPlanNode node = 2;
|
163
|
+
}
|
164
|
+
message ResponsePathElement {
|
165
|
+
oneof id {
|
166
|
+
string field_name = 1;
|
167
|
+
uint32 index = 2;
|
168
|
+
}
|
169
|
+
}
|
170
|
+
oneof node {
|
171
|
+
SequenceNode sequence = 1;
|
172
|
+
ParallelNode parallel = 2;
|
173
|
+
FetchNode fetch = 3;
|
174
|
+
FlattenNode flatten = 4;
|
175
|
+
}
|
176
|
+
}
|
177
|
+
|
178
|
+
// Wallclock time when the trace began.
|
179
|
+
google.protobuf.Timestamp start_time = 4; // required
|
180
|
+
// Wallclock time when the trace ended.
|
181
|
+
google.protobuf.Timestamp end_time = 3; // required
|
182
|
+
// High precision duration of the trace; may not equal end_time-start_time
|
183
|
+
// (eg, if your machine's clock changed during the trace).
|
184
|
+
uint64 duration_ns = 11; // required
|
185
|
+
// A tree containing information about all resolvers run directly by this
|
186
|
+
// service, including errors.
|
187
|
+
Node root = 14;
|
188
|
+
|
189
|
+
// -------------------------------------------------------------------------
|
190
|
+
// Fields below this line are *not* included in federated traces (the traces
|
191
|
+
// sent from federated services to the gateway).
|
192
|
+
|
193
|
+
// In addition to details.raw_query, we include a "signature" of the query,
|
194
|
+
// which can be normalized: for example, you may want to discard aliases, drop
|
195
|
+
// unused operations and fragments, sort fields, etc. The most important thing
|
196
|
+
// here is that the signature match the signature in StatsReports. In
|
197
|
+
// StatsReports signatures show up as the key in the per_query map (with the
|
198
|
+
// operation name prepended). The signature should be a valid GraphQL query.
|
199
|
+
// All traces must have a signature; if this Trace is in a FullTracesReport
|
200
|
+
// that signature is in the key of traces_per_query rather than in this field.
|
201
|
+
// Engineproxy provides the signature in legacy_signature_needs_resigning
|
202
|
+
// instead.
|
203
|
+
string signature = 19;
|
204
|
+
|
205
|
+
Details details = 6;
|
206
|
+
|
207
|
+
// Note: engineproxy always sets client_name, client_version, and client_address to "none".
|
208
|
+
// apollo-engine-reporting allows for them to be set by the user.
|
209
|
+
string client_name = 7;
|
210
|
+
string client_version = 8;
|
211
|
+
string client_address = 9;
|
212
|
+
string client_reference_id = 23;
|
213
|
+
|
214
|
+
HTTP http = 10;
|
215
|
+
|
216
|
+
CachePolicy cache_policy = 18;
|
217
|
+
|
218
|
+
// If this Trace was created by a gateway, this is the query plan, including
|
219
|
+
// sub-Traces for federated services. Note that the 'root' tree on the
|
220
|
+
// top-level Trace won't contain any resolvers (though it could contain errors
|
221
|
+
// that occurred in the gateway itself).
|
222
|
+
QueryPlanNode query_plan = 26;
|
223
|
+
|
224
|
+
// Was this response served from a full query response cache? (In that case
|
225
|
+
// the node tree will have no resolvers.)
|
226
|
+
bool full_query_cache_hit = 20;
|
227
|
+
|
228
|
+
// Was this query specified successfully as a persisted query hash?
|
229
|
+
bool persisted_query_hit = 21;
|
230
|
+
// Did this query contain both a full query string and a persisted query hash?
|
231
|
+
// (This typically means that a previous request was rejected as an unknown
|
232
|
+
// persisted query.)
|
233
|
+
bool persisted_query_register = 22;
|
234
|
+
|
235
|
+
// Was this operation registered and a part of the safelist?
|
236
|
+
bool registered_operation = 24;
|
237
|
+
|
238
|
+
// Was this operation forbidden due to lack of safelisting?
|
239
|
+
bool forbidden_operation = 25;
|
240
|
+
|
241
|
+
// --------------------------------------------------------------
|
242
|
+
// Fields below this line are only set by the old Go engineproxy.
|
243
|
+
google.protobuf.Timestamp origin_reported_start_time = 15;
|
244
|
+
google.protobuf.Timestamp origin_reported_end_time = 16;
|
245
|
+
uint64 origin_reported_duration_ns = 17;
|
246
|
+
|
247
|
+
// Older agents (eg the Go engineproxy) relied to some degree on the Engine
|
248
|
+
// backend to run their own semi-compatible implementation of a specific
|
249
|
+
// variant of query signatures. The backend does not do this for new agents (which
|
250
|
+
// set the above 'signature' field). It used to still "re-sign" signatures
|
251
|
+
// from engineproxy, but we've now simplified the backend to no longer do this.
|
252
|
+
// Deprecated and ignored in FullTracesReports.
|
253
|
+
string legacy_signature_needs_resigning = 5;
|
254
|
+
|
255
|
+
|
256
|
+
// removed: Node parse = 12; Node validate = 13;
|
257
|
+
// Id128 server_id = 1; Id128 client_id = 2;
|
258
|
+
reserved 12, 13, 1, 2;
|
259
|
+
}
|
260
|
+
|
261
|
+
// The `service` value embedded within the header key is not guaranteed to contain an actual service,
|
262
|
+
// and, in most cases, the service information is trusted to come from upstream processing. If the
|
263
|
+
// service _is_ specified in this header, then it is checked to match the context that is reporting it.
|
264
|
+
// Otherwise, the service information is deduced from the token context of the reporter and then sent
|
265
|
+
// along via other mechanisms (in Kafka, the `ReportKafkaKey). The other information (hostname,
|
266
|
+
// agent_version, etc.) is sent by the Apollo Engine Reporting agent, but we do not currently save that
|
267
|
+
// information to any of our persistent storage.
|
268
|
+
message ReportHeader {
|
269
|
+
string service = 3;
|
270
|
+
// eg "host-01.example.com"
|
271
|
+
string hostname = 5;
|
272
|
+
|
273
|
+
// eg "engineproxy 0.1.0"
|
274
|
+
string agent_version = 6; // required
|
275
|
+
// eg "prod-4279-20160804T065423Z-5-g3cf0aa8" (taken from `git describe --tags`)
|
276
|
+
string service_version = 7;
|
277
|
+
// eg "node v4.6.0"
|
278
|
+
string runtime_version = 8;
|
279
|
+
// eg "Linux box 4.6.5-1-ec2 #1 SMP Mon Aug 1 02:31:38 PDT 2016 x86_64 GNU/Linux"
|
280
|
+
string uname = 9;
|
281
|
+
// eg "current", "prod"
|
282
|
+
string schema_tag = 10;
|
283
|
+
// The hex representation of the sha512 of the introspection response
|
284
|
+
string schema_hash = 11;
|
285
|
+
}
|
286
|
+
|
287
|
+
message PathErrorStats {
|
288
|
+
map<string, PathErrorStats> children = 1;
|
289
|
+
uint64 errors_count = 4;
|
290
|
+
uint64 requests_with_errors_count = 5;
|
291
|
+
}
|
292
|
+
|
293
|
+
message ClientNameStats {
|
294
|
+
// Duration histogram for non-cache-hit queries.
|
295
|
+
// (See docs/histograms.md for the histogram format.)
|
296
|
+
repeated int64 latency_count = 1;
|
297
|
+
reserved 2; // removed: repeated uint64 error_count = 2;
|
298
|
+
// These per-version fields were used to understand what versions contributed to this sample
|
299
|
+
// when we were implementing the aggregation of this information ourselves using BigTable.
|
300
|
+
// However, since the per-version stats don't separate out latency, it makes more sense to
|
301
|
+
// have stats reported with contextual information so we can have the specific breakdown we're
|
302
|
+
// looking for. These fields are somewhat misleading as we never actually do any per-version
|
303
|
+
// awareness with anything reporting in the legacy "per_client_name" stats, and instead use
|
304
|
+
// "query_stats_with_context" to have more contextual information.
|
305
|
+
map<string, uint64> requests_count_per_version = 3; // required
|
306
|
+
map<string, uint64> cache_hits_per_version = 4;
|
307
|
+
map<string, uint64> persisted_query_hits_per_version = 10;
|
308
|
+
map<string, uint64> persisted_query_misses_per_version = 11;
|
309
|
+
map<string, uint64> registered_operation_count_per_version = 12;
|
310
|
+
map<string, uint64> forbidden_operation_count_per_version = 13;
|
311
|
+
repeated int64 cache_latency_count = 5; // Duration histogram; see docs/histograms.md
|
312
|
+
PathErrorStats root_error_stats = 6;
|
313
|
+
uint64 requests_with_errors_count = 7;
|
314
|
+
// TTL histograms for cache misses for the public cache.
|
315
|
+
repeated int64 public_cache_ttl_count = 8;
|
316
|
+
// TTL histograms for cache misses for the private cache.
|
317
|
+
repeated int64 private_cache_ttl_count = 9;
|
318
|
+
}
|
319
|
+
|
320
|
+
message QueryLatencyStats {
|
321
|
+
repeated int64 latency_count = 1;
|
322
|
+
uint64 request_count = 2;
|
323
|
+
uint64 cache_hits = 3;
|
324
|
+
uint64 persisted_query_hits = 4;
|
325
|
+
uint64 persisted_query_misses = 5;
|
326
|
+
repeated int64 cache_latency_count = 6;
|
327
|
+
PathErrorStats root_error_stats = 7;
|
328
|
+
uint64 requests_with_errors_count = 8;
|
329
|
+
repeated int64 public_cache_ttl_count = 9;
|
330
|
+
repeated int64 private_cache_ttl_count = 10;
|
331
|
+
uint64 registered_operation_count = 11;
|
332
|
+
uint64 forbidden_operation_count = 12;
|
333
|
+
}
|
334
|
+
|
335
|
+
message StatsContext {
|
336
|
+
string client_reference_id = 1;
|
337
|
+
string client_name = 2;
|
338
|
+
string client_version = 3;
|
339
|
+
}
|
340
|
+
|
341
|
+
message ContextualizedQueryLatencyStats {
|
342
|
+
QueryLatencyStats query_latency_stats = 1;
|
343
|
+
StatsContext context = 2;
|
344
|
+
}
|
345
|
+
|
346
|
+
message ContextualizedTypeStats {
|
347
|
+
StatsContext context = 1;
|
348
|
+
map<string, TypeStat> per_type_stat = 2;
|
349
|
+
}
|
350
|
+
|
351
|
+
message FieldStat {
|
352
|
+
string name = 2; // deprecated; only set when stored in TypeStat.field
|
353
|
+
string return_type = 3; // required; eg "String!" for User.email:String!
|
354
|
+
uint64 errors_count = 4;
|
355
|
+
uint64 count = 5;
|
356
|
+
uint64 requests_with_errors_count = 6;
|
357
|
+
repeated int64 latency_count = 8; // Duration histogram; see docs/histograms.md
|
358
|
+
}
|
359
|
+
|
360
|
+
message TypeStat {
|
361
|
+
string name = 1; // deprecated; only set when stored in QueryStats.per_type
|
362
|
+
repeated FieldStat field = 2; // deprecated; use per_field_stat instead
|
363
|
+
// Key is (eg) "email" for User.email:String!
|
364
|
+
map<string, FieldStat> per_field_stat = 3;
|
365
|
+
}
|
366
|
+
|
367
|
+
message QueryStats {
|
368
|
+
// Either per_client_name (for back-compat) or query_stats_with_context must be specified. If both are
|
369
|
+
// specified, then query_stats_with_context will be used and per_client_name will be ignored. Although
|
370
|
+
// the fields in ClientNameStats mention things "per-version," the information in the "per-version"
|
371
|
+
// fields will only ever be over the default version, the empty String: "", if arrived at via the
|
372
|
+
// FullTracesAggregator.
|
373
|
+
map<string, ClientNameStats> per_client_name = 1; // deprecated; use stats_with_context instead
|
374
|
+
repeated ContextualizedQueryLatencyStats query_stats_with_context = 4;
|
375
|
+
repeated TypeStat per_type = 2; // deprecated; use type_stats_with_context instead
|
376
|
+
// Key is the parent type, e.g. "User" for User.email:String!
|
377
|
+
map<string, TypeStat> per_type_stat = 3; // deprecated; use type_stats_with_context instead
|
378
|
+
repeated ContextualizedTypeStats type_stats_with_context = 5;
|
379
|
+
}
|
380
|
+
|
381
|
+
// Top-level message type for the server-side traces endpoint
|
382
|
+
message TracesReport {
|
383
|
+
ReportHeader header = 1; // required
|
384
|
+
repeated Trace trace = 2; // required
|
385
|
+
}
|
386
|
+
|
387
|
+
message Field {
|
388
|
+
string name = 2; // required; eg "email" for User.email:String!
|
389
|
+
string return_type = 3; // required; eg "String!" for User.email:String!
|
390
|
+
}
|
391
|
+
|
392
|
+
message Type {
|
393
|
+
string name = 1; // required; eg "User" for User.email:String!
|
394
|
+
repeated Field field = 2;
|
395
|
+
}
|
396
|
+
|
397
|
+
message MemStats {
|
398
|
+
uint64 total_bytes = 1; // MemStats.Sys
|
399
|
+
uint64 stack_bytes = 2; // MemStats.StackSys
|
400
|
+
uint64 heap_bytes = 3; // MemStats.HeapSys
|
401
|
+
uint64 heap_released_bytes = 13; // MemStats.HeapReleased
|
402
|
+
uint64 gc_overhead_bytes = 4; // MemStats.GCSys
|
403
|
+
|
404
|
+
uint64 stack_used_bytes = 5; // MemStats.StackInuse
|
405
|
+
uint64 heap_allocated_bytes = 6; // MemStats.HeapAlloc
|
406
|
+
uint64 heap_allocated_objects = 7; // MemStats.HeapObjects
|
407
|
+
|
408
|
+
uint64 heap_allocated_bytes_delta = 8; // MemStats.TotalAlloc delta
|
409
|
+
uint64 heap_allocated_objects_delta = 9; // MemStats.Mallocs delta
|
410
|
+
uint64 heap_freed_objects_delta = 10; // MemStats.Frees delta
|
411
|
+
|
412
|
+
uint64 gc_stw_ns_delta = 11; // MemStats.PauseTotalNs delta
|
413
|
+
uint64 gc_count_delta = 12; // MemStats.NumGC delta
|
414
|
+
}
|
415
|
+
|
416
|
+
message TimeStats {
|
417
|
+
uint64 uptime_ns = 1;
|
418
|
+
uint64 real_ns_delta = 2;
|
419
|
+
uint64 user_ns_delta = 3;
|
420
|
+
uint64 sys_ns_delta = 4;
|
421
|
+
}
|
422
|
+
|
423
|
+
// Top-level message type for the server-side stats endpoint
|
424
|
+
message StatsReport {
|
425
|
+
ReportHeader header = 1; // required
|
426
|
+
|
427
|
+
// These fields are about properties of the engineproxy and are not generated
|
428
|
+
// from FullTracesReports.
|
429
|
+
MemStats mem_stats = 2;
|
430
|
+
TimeStats time_stats = 3;
|
431
|
+
|
432
|
+
// Beginning of the period over which stats are collected.
|
433
|
+
google.protobuf.Timestamp start_time = 8;
|
434
|
+
// End of the period of which stats are collected.
|
435
|
+
google.protobuf.Timestamp end_time = 9;
|
436
|
+
// Only used to interpret mem_stats and time_stats; not generated from
|
437
|
+
// FullTracesReports.
|
438
|
+
uint64 realtime_duration = 10;
|
439
|
+
|
440
|
+
|
441
|
+
// Maps from query descriptor to QueryStats. Required unless
|
442
|
+
// legacy_per_query_missing_operation_name is set. The keys are strings of the
|
443
|
+
// form `# operationName\nsignature` (literal hash and space), with
|
444
|
+
// operationName - if there is no operation name.
|
445
|
+
map<string, QueryStats> per_query = 14;
|
446
|
+
|
447
|
+
// Older agents (Go engineproxy) didn't explicitly include the operation name
|
448
|
+
// in the key of this map, and the server had to parse it out (after a
|
449
|
+
// re-signing operation which is no longer performed). The key here is just the query
|
450
|
+
// signature. Deprecated.
|
451
|
+
map<string, QueryStats> legacy_per_query_implicit_operation_name = 12;
|
452
|
+
|
453
|
+
// Deprecated: it was useful in Optics where we had access to the whole schema
|
454
|
+
// but has not been ever used in Engine. apollo-engine-reporting will not
|
455
|
+
// send it.
|
456
|
+
repeated Type type = 13;
|
457
|
+
}
|
458
|
+
|
459
|
+
// This is the top-level message used by the new traces ingress. This
|
460
|
+
// is designed for the apollo-engine-reporting TypeScript agent and will
|
461
|
+
// eventually be documented as a public ingress API. This message consists
|
462
|
+
// solely of traces; the equivalent of the StatsReport is automatically
|
463
|
+
// generated server-side from this message. Agents should send traces
|
464
|
+
// for all requests in this report. Generally, buffering up until a large
|
465
|
+
// size has been reached (say, 4MB) or 5-10 seconds has passed is appropriate.
|
466
|
+
message FullTracesReport {
|
467
|
+
ReportHeader header = 1;
|
468
|
+
|
469
|
+
// key is statsReportKey (# operationName\nsignature) Note that the nested
|
470
|
+
// traces will *not* have a signature or details.operationName (because the
|
471
|
+
// key is adequate).
|
472
|
+
//
|
473
|
+
// We also assume that traces don't have
|
474
|
+
// legacy_per_query_implicit_operation_name, and we don't require them to have
|
475
|
+
// details.raw_query (which would consume a lot of space and has privacy/data
|
476
|
+
// access issues, and isn't currently exposed by our app anyway).
|
477
|
+
map<string, Traces> traces_per_query = 5;
|
478
|
+
}
|
479
|
+
|
480
|
+
// Just a sequence of traces with the same statsReportKey.
|
481
|
+
message Traces {
|
482
|
+
repeated Trace trace = 1;
|
483
|
+
}
|
484
|
+
|
485
|
+
message TraceV1 {
|
486
|
+
ReportHeader header = 1;
|
487
|
+
Trace trace = 2;
|
488
|
+
}
|
@@ -0,0 +1,305 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
4
|
+
# source: apollo.proto
|
5
|
+
|
6
|
+
require 'google/protobuf'
|
7
|
+
|
8
|
+
require 'google/protobuf/timestamp_pb'
|
9
|
+
Google::Protobuf::DescriptorPool.generated_pool.build do
|
10
|
+
add_file('apollo.proto', syntax: :proto3) do
|
11
|
+
add_message 'mdg.engine.proto.Trace' do
|
12
|
+
optional :start_time, :message, 4, 'google.protobuf.Timestamp'
|
13
|
+
optional :end_time, :message, 3, 'google.protobuf.Timestamp'
|
14
|
+
optional :duration_ns, :uint64, 11
|
15
|
+
optional :root, :message, 14, 'mdg.engine.proto.Trace.Node'
|
16
|
+
optional :signature, :string, 19
|
17
|
+
optional :details, :message, 6, 'mdg.engine.proto.Trace.Details'
|
18
|
+
optional :client_name, :string, 7
|
19
|
+
optional :client_version, :string, 8
|
20
|
+
optional :client_address, :string, 9
|
21
|
+
optional :client_reference_id, :string, 23
|
22
|
+
optional :http, :message, 10, 'mdg.engine.proto.Trace.HTTP'
|
23
|
+
optional :cache_policy, :message, 18, 'mdg.engine.proto.Trace.CachePolicy'
|
24
|
+
optional :query_plan, :message, 26, 'mdg.engine.proto.Trace.QueryPlanNode'
|
25
|
+
optional :full_query_cache_hit, :bool, 20
|
26
|
+
optional :persisted_query_hit, :bool, 21
|
27
|
+
optional :persisted_query_register, :bool, 22
|
28
|
+
optional :registered_operation, :bool, 24
|
29
|
+
optional :forbidden_operation, :bool, 25
|
30
|
+
optional :origin_reported_start_time, :message, 15, 'google.protobuf.Timestamp'
|
31
|
+
optional :origin_reported_end_time, :message, 16, 'google.protobuf.Timestamp'
|
32
|
+
optional :origin_reported_duration_ns, :uint64, 17
|
33
|
+
optional :legacy_signature_needs_resigning, :string, 5
|
34
|
+
end
|
35
|
+
add_message 'mdg.engine.proto.Trace.CachePolicy' do
|
36
|
+
optional :scope, :enum, 1, 'mdg.engine.proto.Trace.CachePolicy.Scope'
|
37
|
+
optional :max_age_ns, :int64, 2
|
38
|
+
end
|
39
|
+
add_enum 'mdg.engine.proto.Trace.CachePolicy.Scope' do
|
40
|
+
value :UNKNOWN, 0
|
41
|
+
value :PUBLIC, 1
|
42
|
+
value :PRIVATE, 2
|
43
|
+
end
|
44
|
+
add_message 'mdg.engine.proto.Trace.Details' do
|
45
|
+
map :variables_json, :string, :string, 4
|
46
|
+
map :variables, :string, :bytes, 1
|
47
|
+
optional :raw_query, :string, 2
|
48
|
+
optional :operation_name, :string, 3
|
49
|
+
end
|
50
|
+
add_message 'mdg.engine.proto.Trace.Error' do
|
51
|
+
optional :message, :string, 1
|
52
|
+
repeated :location, :message, 2, 'mdg.engine.proto.Trace.Location'
|
53
|
+
optional :time_ns, :uint64, 3
|
54
|
+
optional :json, :string, 4
|
55
|
+
end
|
56
|
+
add_message 'mdg.engine.proto.Trace.HTTP' do
|
57
|
+
optional :method, :enum, 1, 'mdg.engine.proto.Trace.HTTP.Method'
|
58
|
+
optional :host, :string, 2
|
59
|
+
optional :path, :string, 3
|
60
|
+
map :request_headers, :string, :message, 4, 'mdg.engine.proto.Trace.HTTP.Values'
|
61
|
+
map :response_headers, :string, :message, 5, 'mdg.engine.proto.Trace.HTTP.Values'
|
62
|
+
optional :status_code, :uint32, 6
|
63
|
+
optional :secure, :bool, 8
|
64
|
+
optional :protocol, :string, 9
|
65
|
+
end
|
66
|
+
add_message 'mdg.engine.proto.Trace.HTTP.Values' do
|
67
|
+
repeated :value, :string, 1
|
68
|
+
end
|
69
|
+
add_enum 'mdg.engine.proto.Trace.HTTP.Method' do
|
70
|
+
value :UNKNOWN, 0
|
71
|
+
value :OPTIONS, 1
|
72
|
+
value :GET, 2
|
73
|
+
value :HEAD, 3
|
74
|
+
value :POST, 4
|
75
|
+
value :PUT, 5
|
76
|
+
value :DELETE, 6
|
77
|
+
value :TRACE, 7
|
78
|
+
value :CONNECT, 8
|
79
|
+
value :PATCH, 9
|
80
|
+
end
|
81
|
+
add_message 'mdg.engine.proto.Trace.Location' do
|
82
|
+
optional :line, :uint32, 1
|
83
|
+
optional :column, :uint32, 2
|
84
|
+
end
|
85
|
+
add_message 'mdg.engine.proto.Trace.Node' do
|
86
|
+
optional :original_field_name, :string, 14
|
87
|
+
optional :type, :string, 3
|
88
|
+
optional :parent_type, :string, 13
|
89
|
+
optional :cache_policy, :message, 5, 'mdg.engine.proto.Trace.CachePolicy'
|
90
|
+
optional :start_time, :uint64, 8
|
91
|
+
optional :end_time, :uint64, 9
|
92
|
+
repeated :error, :message, 11, 'mdg.engine.proto.Trace.Error'
|
93
|
+
repeated :child, :message, 12, 'mdg.engine.proto.Trace.Node'
|
94
|
+
oneof :id do
|
95
|
+
optional :response_name, :string, 1
|
96
|
+
optional :index, :uint32, 2
|
97
|
+
end
|
98
|
+
end
|
99
|
+
add_message 'mdg.engine.proto.Trace.QueryPlanNode' do
|
100
|
+
oneof :node do
|
101
|
+
optional :sequence, :message, 1, 'mdg.engine.proto.Trace.QueryPlanNode.SequenceNode'
|
102
|
+
optional :parallel, :message, 2, 'mdg.engine.proto.Trace.QueryPlanNode.ParallelNode'
|
103
|
+
optional :fetch, :message, 3, 'mdg.engine.proto.Trace.QueryPlanNode.FetchNode'
|
104
|
+
optional :flatten, :message, 4, 'mdg.engine.proto.Trace.QueryPlanNode.FlattenNode'
|
105
|
+
end
|
106
|
+
end
|
107
|
+
add_message 'mdg.engine.proto.Trace.QueryPlanNode.SequenceNode' do
|
108
|
+
repeated :nodes, :message, 1, 'mdg.engine.proto.Trace.QueryPlanNode'
|
109
|
+
end
|
110
|
+
add_message 'mdg.engine.proto.Trace.QueryPlanNode.ParallelNode' do
|
111
|
+
repeated :nodes, :message, 1, 'mdg.engine.proto.Trace.QueryPlanNode'
|
112
|
+
end
|
113
|
+
add_message 'mdg.engine.proto.Trace.QueryPlanNode.FetchNode' do
|
114
|
+
optional :serviceName, :string, 1
|
115
|
+
optional :traceParsingFailed, :bool, 2
|
116
|
+
optional :trace, :message, 3, 'mdg.engine.proto.Trace'
|
117
|
+
optional :sent_time_offset, :uint64, 4
|
118
|
+
optional :sent_time, :message, 5, 'google.protobuf.Timestamp'
|
119
|
+
optional :received_time, :message, 6, 'google.protobuf.Timestamp'
|
120
|
+
end
|
121
|
+
add_message 'mdg.engine.proto.Trace.QueryPlanNode.FlattenNode' do
|
122
|
+
repeated :response_path, :message, 1, 'mdg.engine.proto.Trace.QueryPlanNode.ResponsePathElement'
|
123
|
+
optional :node, :message, 2, 'mdg.engine.proto.Trace.QueryPlanNode'
|
124
|
+
end
|
125
|
+
add_message 'mdg.engine.proto.Trace.QueryPlanNode.ResponsePathElement' do
|
126
|
+
oneof :id do
|
127
|
+
optional :field_name, :string, 1
|
128
|
+
optional :index, :uint32, 2
|
129
|
+
end
|
130
|
+
end
|
131
|
+
add_message 'mdg.engine.proto.ReportHeader' do
|
132
|
+
optional :service, :string, 3
|
133
|
+
optional :hostname, :string, 5
|
134
|
+
optional :agent_version, :string, 6
|
135
|
+
optional :service_version, :string, 7
|
136
|
+
optional :runtime_version, :string, 8
|
137
|
+
optional :uname, :string, 9
|
138
|
+
optional :schema_tag, :string, 10
|
139
|
+
optional :schema_hash, :string, 11
|
140
|
+
end
|
141
|
+
add_message 'mdg.engine.proto.PathErrorStats' do
|
142
|
+
map :children, :string, :message, 1, 'mdg.engine.proto.PathErrorStats'
|
143
|
+
optional :errors_count, :uint64, 4
|
144
|
+
optional :requests_with_errors_count, :uint64, 5
|
145
|
+
end
|
146
|
+
add_message 'mdg.engine.proto.ClientNameStats' do
|
147
|
+
repeated :latency_count, :int64, 1
|
148
|
+
map :requests_count_per_version, :string, :uint64, 3
|
149
|
+
map :cache_hits_per_version, :string, :uint64, 4
|
150
|
+
map :persisted_query_hits_per_version, :string, :uint64, 10
|
151
|
+
map :persisted_query_misses_per_version, :string, :uint64, 11
|
152
|
+
map :registered_operation_count_per_version, :string, :uint64, 12
|
153
|
+
map :forbidden_operation_count_per_version, :string, :uint64, 13
|
154
|
+
repeated :cache_latency_count, :int64, 5
|
155
|
+
optional :root_error_stats, :message, 6, 'mdg.engine.proto.PathErrorStats'
|
156
|
+
optional :requests_with_errors_count, :uint64, 7
|
157
|
+
repeated :public_cache_ttl_count, :int64, 8
|
158
|
+
repeated :private_cache_ttl_count, :int64, 9
|
159
|
+
end
|
160
|
+
add_message 'mdg.engine.proto.QueryLatencyStats' do
|
161
|
+
repeated :latency_count, :int64, 1
|
162
|
+
optional :request_count, :uint64, 2
|
163
|
+
optional :cache_hits, :uint64, 3
|
164
|
+
optional :persisted_query_hits, :uint64, 4
|
165
|
+
optional :persisted_query_misses, :uint64, 5
|
166
|
+
repeated :cache_latency_count, :int64, 6
|
167
|
+
optional :root_error_stats, :message, 7, 'mdg.engine.proto.PathErrorStats'
|
168
|
+
optional :requests_with_errors_count, :uint64, 8
|
169
|
+
repeated :public_cache_ttl_count, :int64, 9
|
170
|
+
repeated :private_cache_ttl_count, :int64, 10
|
171
|
+
optional :registered_operation_count, :uint64, 11
|
172
|
+
optional :forbidden_operation_count, :uint64, 12
|
173
|
+
end
|
174
|
+
add_message 'mdg.engine.proto.StatsContext' do
|
175
|
+
optional :client_reference_id, :string, 1
|
176
|
+
optional :client_name, :string, 2
|
177
|
+
optional :client_version, :string, 3
|
178
|
+
end
|
179
|
+
add_message 'mdg.engine.proto.ContextualizedQueryLatencyStats' do
|
180
|
+
optional :query_latency_stats, :message, 1, 'mdg.engine.proto.QueryLatencyStats'
|
181
|
+
optional :context, :message, 2, 'mdg.engine.proto.StatsContext'
|
182
|
+
end
|
183
|
+
add_message 'mdg.engine.proto.ContextualizedTypeStats' do
|
184
|
+
optional :context, :message, 1, 'mdg.engine.proto.StatsContext'
|
185
|
+
map :per_type_stat, :string, :message, 2, 'mdg.engine.proto.TypeStat'
|
186
|
+
end
|
187
|
+
add_message 'mdg.engine.proto.FieldStat' do
|
188
|
+
optional :name, :string, 2
|
189
|
+
optional :return_type, :string, 3
|
190
|
+
optional :errors_count, :uint64, 4
|
191
|
+
optional :count, :uint64, 5
|
192
|
+
optional :requests_with_errors_count, :uint64, 6
|
193
|
+
repeated :latency_count, :int64, 8
|
194
|
+
end
|
195
|
+
add_message 'mdg.engine.proto.TypeStat' do
|
196
|
+
optional :name, :string, 1
|
197
|
+
repeated :field, :message, 2, 'mdg.engine.proto.FieldStat'
|
198
|
+
map :per_field_stat, :string, :message, 3, 'mdg.engine.proto.FieldStat'
|
199
|
+
end
|
200
|
+
add_message 'mdg.engine.proto.QueryStats' do
|
201
|
+
map :per_client_name, :string, :message, 1, 'mdg.engine.proto.ClientNameStats'
|
202
|
+
repeated :query_stats_with_context, :message, 4, 'mdg.engine.proto.ContextualizedQueryLatencyStats'
|
203
|
+
repeated :per_type, :message, 2, 'mdg.engine.proto.TypeStat'
|
204
|
+
map :per_type_stat, :string, :message, 3, 'mdg.engine.proto.TypeStat'
|
205
|
+
repeated :type_stats_with_context, :message, 5, 'mdg.engine.proto.ContextualizedTypeStats'
|
206
|
+
end
|
207
|
+
add_message 'mdg.engine.proto.TracesReport' do
|
208
|
+
optional :header, :message, 1, 'mdg.engine.proto.ReportHeader'
|
209
|
+
repeated :trace, :message, 2, 'mdg.engine.proto.Trace'
|
210
|
+
end
|
211
|
+
add_message 'mdg.engine.proto.Field' do
|
212
|
+
optional :name, :string, 2
|
213
|
+
optional :return_type, :string, 3
|
214
|
+
end
|
215
|
+
add_message 'mdg.engine.proto.Type' do
|
216
|
+
optional :name, :string, 1
|
217
|
+
repeated :field, :message, 2, 'mdg.engine.proto.Field'
|
218
|
+
end
|
219
|
+
add_message 'mdg.engine.proto.MemStats' do
|
220
|
+
optional :total_bytes, :uint64, 1
|
221
|
+
optional :stack_bytes, :uint64, 2
|
222
|
+
optional :heap_bytes, :uint64, 3
|
223
|
+
optional :heap_released_bytes, :uint64, 13
|
224
|
+
optional :gc_overhead_bytes, :uint64, 4
|
225
|
+
optional :stack_used_bytes, :uint64, 5
|
226
|
+
optional :heap_allocated_bytes, :uint64, 6
|
227
|
+
optional :heap_allocated_objects, :uint64, 7
|
228
|
+
optional :heap_allocated_bytes_delta, :uint64, 8
|
229
|
+
optional :heap_allocated_objects_delta, :uint64, 9
|
230
|
+
optional :heap_freed_objects_delta, :uint64, 10
|
231
|
+
optional :gc_stw_ns_delta, :uint64, 11
|
232
|
+
optional :gc_count_delta, :uint64, 12
|
233
|
+
end
|
234
|
+
add_message 'mdg.engine.proto.TimeStats' do
|
235
|
+
optional :uptime_ns, :uint64, 1
|
236
|
+
optional :real_ns_delta, :uint64, 2
|
237
|
+
optional :user_ns_delta, :uint64, 3
|
238
|
+
optional :sys_ns_delta, :uint64, 4
|
239
|
+
end
|
240
|
+
add_message 'mdg.engine.proto.StatsReport' do
|
241
|
+
optional :header, :message, 1, 'mdg.engine.proto.ReportHeader'
|
242
|
+
optional :mem_stats, :message, 2, 'mdg.engine.proto.MemStats'
|
243
|
+
optional :time_stats, :message, 3, 'mdg.engine.proto.TimeStats'
|
244
|
+
optional :start_time, :message, 8, 'google.protobuf.Timestamp'
|
245
|
+
optional :end_time, :message, 9, 'google.protobuf.Timestamp'
|
246
|
+
optional :realtime_duration, :uint64, 10
|
247
|
+
map :per_query, :string, :message, 14, 'mdg.engine.proto.QueryStats'
|
248
|
+
map :legacy_per_query_implicit_operation_name, :string, :message, 12, 'mdg.engine.proto.QueryStats'
|
249
|
+
repeated :type, :message, 13, 'mdg.engine.proto.Type'
|
250
|
+
end
|
251
|
+
add_message 'mdg.engine.proto.FullTracesReport' do
|
252
|
+
optional :header, :message, 1, 'mdg.engine.proto.ReportHeader'
|
253
|
+
map :traces_per_query, :string, :message, 5, 'mdg.engine.proto.Traces'
|
254
|
+
end
|
255
|
+
add_message 'mdg.engine.proto.Traces' do
|
256
|
+
repeated :trace, :message, 1, 'mdg.engine.proto.Trace'
|
257
|
+
end
|
258
|
+
add_message 'mdg.engine.proto.TraceV1' do
|
259
|
+
optional :header, :message, 1, 'mdg.engine.proto.ReportHeader'
|
260
|
+
optional :trace, :message, 2, 'mdg.engine.proto.Trace'
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
module Mdg
|
266
|
+
module Engine
|
267
|
+
module Proto
|
268
|
+
Trace = Google::Protobuf::DescriptorPool.generated_pool.lookup('mdg.engine.proto.Trace').msgclass
|
269
|
+
Trace::CachePolicy = Google::Protobuf::DescriptorPool.generated_pool.lookup('mdg.engine.proto.Trace.CachePolicy').msgclass
|
270
|
+
Trace::CachePolicy::Scope = Google::Protobuf::DescriptorPool.generated_pool.lookup('mdg.engine.proto.Trace.CachePolicy.Scope').enummodule
|
271
|
+
Trace::Details = Google::Protobuf::DescriptorPool.generated_pool.lookup('mdg.engine.proto.Trace.Details').msgclass
|
272
|
+
Trace::Error = Google::Protobuf::DescriptorPool.generated_pool.lookup('mdg.engine.proto.Trace.Error').msgclass
|
273
|
+
Trace::HTTP = Google::Protobuf::DescriptorPool.generated_pool.lookup('mdg.engine.proto.Trace.HTTP').msgclass
|
274
|
+
Trace::HTTP::Values = Google::Protobuf::DescriptorPool.generated_pool.lookup('mdg.engine.proto.Trace.HTTP.Values').msgclass
|
275
|
+
Trace::HTTP::Method = Google::Protobuf::DescriptorPool.generated_pool.lookup('mdg.engine.proto.Trace.HTTP.Method').enummodule
|
276
|
+
Trace::Location = Google::Protobuf::DescriptorPool.generated_pool.lookup('mdg.engine.proto.Trace.Location').msgclass
|
277
|
+
Trace::Node = Google::Protobuf::DescriptorPool.generated_pool.lookup('mdg.engine.proto.Trace.Node').msgclass
|
278
|
+
Trace::QueryPlanNode = Google::Protobuf::DescriptorPool.generated_pool.lookup('mdg.engine.proto.Trace.QueryPlanNode').msgclass
|
279
|
+
Trace::QueryPlanNode::SequenceNode = Google::Protobuf::DescriptorPool.generated_pool.lookup('mdg.engine.proto.Trace.QueryPlanNode.SequenceNode').msgclass
|
280
|
+
Trace::QueryPlanNode::ParallelNode = Google::Protobuf::DescriptorPool.generated_pool.lookup('mdg.engine.proto.Trace.QueryPlanNode.ParallelNode').msgclass
|
281
|
+
Trace::QueryPlanNode::FetchNode = Google::Protobuf::DescriptorPool.generated_pool.lookup('mdg.engine.proto.Trace.QueryPlanNode.FetchNode').msgclass
|
282
|
+
Trace::QueryPlanNode::FlattenNode = Google::Protobuf::DescriptorPool.generated_pool.lookup('mdg.engine.proto.Trace.QueryPlanNode.FlattenNode').msgclass
|
283
|
+
Trace::QueryPlanNode::ResponsePathElement = Google::Protobuf::DescriptorPool.generated_pool.lookup('mdg.engine.proto.Trace.QueryPlanNode.ResponsePathElement').msgclass
|
284
|
+
ReportHeader = Google::Protobuf::DescriptorPool.generated_pool.lookup('mdg.engine.proto.ReportHeader').msgclass
|
285
|
+
PathErrorStats = Google::Protobuf::DescriptorPool.generated_pool.lookup('mdg.engine.proto.PathErrorStats').msgclass
|
286
|
+
ClientNameStats = Google::Protobuf::DescriptorPool.generated_pool.lookup('mdg.engine.proto.ClientNameStats').msgclass
|
287
|
+
QueryLatencyStats = Google::Protobuf::DescriptorPool.generated_pool.lookup('mdg.engine.proto.QueryLatencyStats').msgclass
|
288
|
+
StatsContext = Google::Protobuf::DescriptorPool.generated_pool.lookup('mdg.engine.proto.StatsContext').msgclass
|
289
|
+
ContextualizedQueryLatencyStats = Google::Protobuf::DescriptorPool.generated_pool.lookup('mdg.engine.proto.ContextualizedQueryLatencyStats').msgclass
|
290
|
+
ContextualizedTypeStats = Google::Protobuf::DescriptorPool.generated_pool.lookup('mdg.engine.proto.ContextualizedTypeStats').msgclass
|
291
|
+
FieldStat = Google::Protobuf::DescriptorPool.generated_pool.lookup('mdg.engine.proto.FieldStat').msgclass
|
292
|
+
TypeStat = Google::Protobuf::DescriptorPool.generated_pool.lookup('mdg.engine.proto.TypeStat').msgclass
|
293
|
+
QueryStats = Google::Protobuf::DescriptorPool.generated_pool.lookup('mdg.engine.proto.QueryStats').msgclass
|
294
|
+
TracesReport = Google::Protobuf::DescriptorPool.generated_pool.lookup('mdg.engine.proto.TracesReport').msgclass
|
295
|
+
Field = Google::Protobuf::DescriptorPool.generated_pool.lookup('mdg.engine.proto.Field').msgclass
|
296
|
+
Type = Google::Protobuf::DescriptorPool.generated_pool.lookup('mdg.engine.proto.Type').msgclass
|
297
|
+
MemStats = Google::Protobuf::DescriptorPool.generated_pool.lookup('mdg.engine.proto.MemStats').msgclass
|
298
|
+
TimeStats = Google::Protobuf::DescriptorPool.generated_pool.lookup('mdg.engine.proto.TimeStats').msgclass
|
299
|
+
StatsReport = Google::Protobuf::DescriptorPool.generated_pool.lookup('mdg.engine.proto.StatsReport').msgclass
|
300
|
+
FullTracesReport = Google::Protobuf::DescriptorPool.generated_pool.lookup('mdg.engine.proto.FullTracesReport').msgclass
|
301
|
+
Traces = Google::Protobuf::DescriptorPool.generated_pool.lookup('mdg.engine.proto.Traces').msgclass
|
302
|
+
TraceV1 = Google::Protobuf::DescriptorPool.generated_pool.lookup('mdg.engine.proto.TraceV1').msgclass
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
@@ -0,0 +1,182 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Trace events are nested and fire in this order
|
4
|
+
# for a simple single-field query like `{ foo }`:
|
5
|
+
#
|
6
|
+
# <execute_multiplex>
|
7
|
+
# <lex></lex>
|
8
|
+
# <parse></parse>
|
9
|
+
# <validate></validate>
|
10
|
+
# <analyze_multiplex>
|
11
|
+
# <analyze_query></analyze_query>
|
12
|
+
# </analyze_multiplex>
|
13
|
+
#
|
14
|
+
# <execute_query>
|
15
|
+
# <execute_field></execute_field>
|
16
|
+
# </execute_query>
|
17
|
+
#
|
18
|
+
# <execute_query_lazy>
|
19
|
+
#
|
20
|
+
# # `execute_field_lazy` fires *only* when the field is lazy
|
21
|
+
# # (https://graphql-ruby.org/schema/lazy_execution.html)
|
22
|
+
# # so if it fires we should overwrite the ending times recorded
|
23
|
+
# # in `execute_field` to capture the total execution time.
|
24
|
+
#
|
25
|
+
# <execute_field_lazy></execute_field_lazy>
|
26
|
+
#
|
27
|
+
# </execute_query_lazy>
|
28
|
+
#
|
29
|
+
# # `execute_query_lazy` *always* fires, so it's a
|
30
|
+
# # safe place to capture ending times of the full query.
|
31
|
+
#
|
32
|
+
# </execute_multiplex>
|
33
|
+
|
34
|
+
module ApolloFederation
|
35
|
+
module Tracing
|
36
|
+
module Tracer
|
37
|
+
# store string constants to avoid creating new strings for each call to .trace
|
38
|
+
EXECUTE_QUERY = 'execute_query'
|
39
|
+
EXECUTE_QUERY_LAZY = 'execute_query_lazy'
|
40
|
+
EXECUTE_FIELD = 'execute_field'
|
41
|
+
EXECUTE_FIELD_LAZY = 'execute_field_lazy'
|
42
|
+
|
43
|
+
def self.trace(key, data, &block)
|
44
|
+
case key
|
45
|
+
when EXECUTE_QUERY
|
46
|
+
execute_query(data, &block)
|
47
|
+
when EXECUTE_QUERY_LAZY
|
48
|
+
execute_query_lazy(data, &block)
|
49
|
+
when EXECUTE_FIELD
|
50
|
+
execute_field(data, &block)
|
51
|
+
when EXECUTE_FIELD_LAZY
|
52
|
+
execute_field_lazy(data, &block)
|
53
|
+
else
|
54
|
+
yield
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Step 1:
|
59
|
+
# Create a trace hash on the query context and record start times.
|
60
|
+
def self.execute_query(data, &block)
|
61
|
+
query = data.fetch(:query)
|
62
|
+
return block.call unless query.context && query.context[:tracing_enabled]
|
63
|
+
|
64
|
+
query.context.namespace(ApolloFederation::Tracing::KEY).merge!(
|
65
|
+
start_time: Time.now.utc,
|
66
|
+
start_time_nanos: Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond),
|
67
|
+
node_map: NodeMap.new,
|
68
|
+
)
|
69
|
+
|
70
|
+
block.call
|
71
|
+
end
|
72
|
+
|
73
|
+
# Step 4:
|
74
|
+
# Record end times and merge them into the trace hash.
|
75
|
+
def self.execute_query_lazy(data, &block)
|
76
|
+
result = block.call
|
77
|
+
|
78
|
+
query = data.fetch(:query)
|
79
|
+
return result unless query.context && query.context[:tracing_enabled]
|
80
|
+
|
81
|
+
trace = query.context.namespace(ApolloFederation::Tracing::KEY)
|
82
|
+
|
83
|
+
trace.merge!(
|
84
|
+
end_time: Time.now.utc,
|
85
|
+
end_time_nanos: Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond),
|
86
|
+
)
|
87
|
+
|
88
|
+
result
|
89
|
+
end
|
90
|
+
|
91
|
+
# Step 2:
|
92
|
+
# * Record start and end times for the field resolver.
|
93
|
+
# * Rescue errors so the method doesn't exit early.
|
94
|
+
# * Create a trace "node" and attach field details.
|
95
|
+
# * Propagate the error (if necessary) so it ends up in the top-level errors array.
|
96
|
+
#
|
97
|
+
# The values in `data` are different depending on the executor runtime.
|
98
|
+
# https://graphql-ruby.org/api-doc/1.9.3/GraphQL/Tracing
|
99
|
+
#
|
100
|
+
# Nodes are added the NodeMap stored in the trace hash.
|
101
|
+
#
|
102
|
+
# Errors are added to nodes in `ApolloFederation::Tracing.attach_trace_to_result`
|
103
|
+
# because we don't have the error `location` here.
|
104
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
105
|
+
def self.execute_field(data, &block)
|
106
|
+
context = data.fetch(:context) || data.fetch(:query).context
|
107
|
+
return block.call unless context && context[:tracing_enabled]
|
108
|
+
|
109
|
+
start_time_nanos = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
|
110
|
+
|
111
|
+
begin
|
112
|
+
result = block.call
|
113
|
+
rescue StandardError => e
|
114
|
+
error = e
|
115
|
+
end
|
116
|
+
|
117
|
+
end_time_nanos = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
|
118
|
+
|
119
|
+
# interpreter runtime
|
120
|
+
if data.include?(:context)
|
121
|
+
path = context.path
|
122
|
+
field_name = context.field.graphql_name
|
123
|
+
field_type = context.field.type.to_s
|
124
|
+
parent_type = context.parent_type.graphql_name
|
125
|
+
else # legacy runtime
|
126
|
+
path = data.fetch(:path)
|
127
|
+
field_name = data.fetch(:field).graphql_name
|
128
|
+
field_type = data.fetch(:field).type.unwrap.graphql_name
|
129
|
+
parent_type = data.fetch(:owner).graphql_name
|
130
|
+
end
|
131
|
+
|
132
|
+
trace = context.namespace(ApolloFederation::Tracing::KEY)
|
133
|
+
node = trace[:node_map].add(path)
|
134
|
+
|
135
|
+
# original_field_name is set only for aliased fields
|
136
|
+
node.original_field_name = field_name if field_name != path.last
|
137
|
+
node.type = field_type
|
138
|
+
node.parent_type = parent_type
|
139
|
+
node.start_time = start_time_nanos - trace[:start_time_nanos]
|
140
|
+
node.end_time = end_time_nanos - trace[:start_time_nanos]
|
141
|
+
|
142
|
+
raise error if error
|
143
|
+
|
144
|
+
result
|
145
|
+
end
|
146
|
+
|
147
|
+
# Optional Step 3:
|
148
|
+
# Overwrite the end times on the trace node if the resolver was lazy.
|
149
|
+
def self.execute_field_lazy(data, &block)
|
150
|
+
context = data.fetch(:context) || data.fetch(:query).context
|
151
|
+
return block.call unless context && context[:tracing_enabled]
|
152
|
+
|
153
|
+
begin
|
154
|
+
result = block.call
|
155
|
+
rescue StandardError => e
|
156
|
+
error = e
|
157
|
+
end
|
158
|
+
|
159
|
+
end_time_nanos = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
|
160
|
+
|
161
|
+
# interpreter runtime
|
162
|
+
if data.include?(:context)
|
163
|
+
context = data.fetch(:context)
|
164
|
+
path = context.path
|
165
|
+
else # legacy runtime
|
166
|
+
context = data.fetch(:query).context
|
167
|
+
path = data.fetch(:path)
|
168
|
+
end
|
169
|
+
|
170
|
+
trace = context.namespace(ApolloFederation::Tracing::KEY)
|
171
|
+
|
172
|
+
node = trace[:node_map].node_for_path(path)
|
173
|
+
node.end_time = end_time_nanos - trace[:start_time_nanos]
|
174
|
+
|
175
|
+
raise error if error
|
176
|
+
|
177
|
+
result
|
178
|
+
end
|
179
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: apollo-federation
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Noa Elad
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2019-09-
|
12
|
+
date: 2019-09-10 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: graphql
|
@@ -25,6 +25,20 @@ dependencies:
|
|
25
25
|
- - ">="
|
26
26
|
- !ruby/object:Gem::Version
|
27
27
|
version: '0'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: google-protobuf
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
28
42
|
- !ruby/object:Gem::Dependency
|
29
43
|
name: actionpack
|
30
44
|
requirement: !ruby/object:Gem::Requirement
|
@@ -134,6 +148,7 @@ files:
|
|
134
148
|
- CHANGELOG.md
|
135
149
|
- LICENSE
|
136
150
|
- README.md
|
151
|
+
- bin/generate-protos.sh
|
137
152
|
- bin/prepare.rb
|
138
153
|
- bin/publish.rb
|
139
154
|
- lib/apollo-federation.rb
|
@@ -147,6 +162,12 @@ files:
|
|
147
162
|
- lib/apollo-federation/schema.rb
|
148
163
|
- lib/apollo-federation/service.rb
|
149
164
|
- lib/apollo-federation/service_field.rb
|
165
|
+
- lib/apollo-federation/tracing.rb
|
166
|
+
- lib/apollo-federation/tracing/node_map.rb
|
167
|
+
- lib/apollo-federation/tracing/proto.rb
|
168
|
+
- lib/apollo-federation/tracing/proto/apollo.proto
|
169
|
+
- lib/apollo-federation/tracing/proto/apollo_pb.rb
|
170
|
+
- lib/apollo-federation/tracing/tracer.rb
|
150
171
|
- lib/apollo-federation/version.rb
|
151
172
|
homepage: https://github.com/Gusto/apollo-federation-ruby
|
152
173
|
licenses:
|