apollo-federation 0.3.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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:
|