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 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: