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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d6c7a99d02e6858f18fe04358315b4d6060a2fc902a91a8da4bbf52004afd491
4
- data.tar.gz: d1286d1a6c85738c3b9ae280df3082176a95a5889c20ae066caa29b76ed41408
3
+ metadata.gz: 72d35c557f29e6039362b933cbaa55847b0d359f8d9da703b0a26615598d1642
4
+ data.tar.gz: be611b3e1516a3dfc91b08f0c2c44473efac2f49508e53482a3fff277aa0bb42
5
5
  SHA512:
6
- metadata.gz: 2a4dac5928783d076a61f2762e29d64cf8455be62ec7c625e5d9386eb536ccdf5bab79b410a22b84139a0c3014ed27e16fc99f90cf2e688c227537d8090c3842
7
- data.tar.gz: 4a684dbe64963aa70df38f324a54c4c86cfdfa8a508b7bc3bc825c4a426783dd7fedf08b1f468463d223f3da0ed8bdd97a1ab5db37d116de9be8df3cda0ccf8c
6
+ metadata.gz: 79ab490f10fdd9faec720d447f76e4c12d63acb974087d22d8f61fb1cb8fbbf5f56f89d7f7edb4bf995c6313bff734e291d4ed6e726d01a9ab7f8548309150b9
7
+ data.tar.gz: 4ea53d9aee497cd334ecbeaf486db7514b6e6499f76641b6e8150deae73824eff725765ca38adbbaabe3576d46f63bad8c8a728575801dd1bfbf7eac9f16a1e6
@@ -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
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ApolloFederation
4
- VERSION = '0.3.2'
4
+ VERSION = '0.4.0'
5
5
  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.3.2
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-03 00:00:00.000000000 Z
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: