gql_metrics_tracer 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/gql_metrics_tracer.rb +192 -0
  3. metadata +87 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0b1b248815920370432ba4165e3b544f5baa430d8805be210af0c590dde54db2
4
+ data.tar.gz: ed0898b557cd27f7a1286ede425b410e590b09aaa96c5526338a54d8e43cffd4
5
+ SHA512:
6
+ metadata.gz: a787ae6298818afc34946b44285474e9d590fa987d169f47aa17d5f61cf11ee8ed25d17302d2031688a8d3b38e21ba2affb23dc2c6c4db83a2f14498d4bf30fa
7
+ data.tar.gz: 9950c53fe9f6681e39b9a2f4e94897100ded43f209fd308e96789e08399b40c1e9fc0187228bf6150e6f437e58ef43df20917d30ce65f86ac16091e3057feef5
@@ -0,0 +1,192 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "typhoeus"
4
+ require "json"
5
+
6
+ class GQLMetricsTracer
7
+ def self.use(schema_definition, _options = {})
8
+ tracer = new(schema_definition)
9
+ schema_definition.instrument(:query, tracer)
10
+ schema_definition.tracer(tracer)
11
+ end
12
+
13
+ def initialize(schema_definition)
14
+ @schema_id = nil
15
+ @schema_definition = schema_definition
16
+
17
+ publish_schema!
18
+ end
19
+
20
+ def before_query(query)
21
+ query.context["gqlmetrics-trace"] = {
22
+ "starts-ns" => precise_time,
23
+ "starts-timestamp" => Time.current,
24
+ "traces" => [],
25
+ }
26
+ end
27
+
28
+ def after_query(query)
29
+ ended_at_ns = precise_time
30
+ ended_timestamp = Time.current
31
+ started_at = query.context.dig("gqlmetrics-trace", "starts-timestamp")
32
+ started_at_ns = query.context.dig("gqlmetrics-trace", "starts-ns")
33
+ traces = query.context.dig("gqlmetrics-trace", "traces")
34
+
35
+ send_trace!(
36
+ query: query.document.to_query_string,
37
+ duration_ns: ended_at_ns - started_at_ns,
38
+ started_at: started_at,
39
+ ended_at: ended_timestamp,
40
+ traces: traces,
41
+ )
42
+ end
43
+
44
+ def trace(key, data, &block)
45
+ if key == "execute_field"
46
+ field = data[:context].present? ? data[:context].field : data[:field]
47
+ path = data[:context].present? ? data[:context].path : data[:path]
48
+ parent_type = data[:context].present? ? data[:context].parent_type : data[:owner].graphql_name
49
+ context = data[:context] || data[:object].context
50
+
51
+ trace_field!(field: field, path: path, parent_type: parent_type, context: context, &block)
52
+ else
53
+ yield
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ attr_reader :schema_id, :schema_definition
60
+
61
+ def send_trace!(query:, duration_ns:, started_at:, ended_at:, traces:)
62
+ return unless graph_token_configured?
63
+ return if @schema_id.nil?
64
+
65
+ Thread.new do
66
+ response = Typhoeus.post(
67
+ "#{gqlmetrics_http_host}/api/traces",
68
+ headers: {
69
+ 'Content-Type': "application/json",
70
+ 'Authorization': "Bearer #{ENV['GQLMETRICS_GRAPH_ID']}",
71
+ },
72
+ timeout: 30,
73
+ body: JSON.dump(
74
+ {
75
+ schema_id: @schema_id,
76
+ query: query,
77
+ started_at_ms: timestamp_to_ms(started_at),
78
+ ended_at_ms: timestamp_to_ms(ended_at),
79
+ duration_ns: duration_ns,
80
+ traces: traces,
81
+ },
82
+ ),
83
+ )
84
+
85
+ trace_id = JSON.parse(response.body)["id"]
86
+
87
+ if trace_id.present?
88
+ Rails.logger.info "[GQLMetricsTracer] Sent trace #{trace_id}"
89
+ else
90
+ Rails.logger.warn "[GQLMetricsTracer] Failed to send the trace..."
91
+ end
92
+ end
93
+ end
94
+
95
+ def trace_field!(field:, path:, parent_type:, context:)
96
+ query_starts_at = context["gqlmetrics-trace"]["starts-ns"]
97
+
98
+ sql_queries = []
99
+ ActiveSupport::Notifications.subscribe "sql.active_record" do |event|
100
+ unless /(pg_)|(max_identifier_length)/.match?(event.payload[:sql])
101
+ sql_queries.push(
102
+ {
103
+ sql: event.payload[:sql],
104
+ start_time: event.time,
105
+ end_time: event.end,
106
+ duration_ns: event.duration * 1_000_000,
107
+ allocations_count: event.allocations,
108
+ },
109
+ )
110
+ end
111
+ end
112
+
113
+ starts_at = precise_time
114
+ yield
115
+ ensure
116
+ ends_at = precise_time
117
+ ActiveSupport::Notifications.unsubscribe("sql.active_record")
118
+
119
+ trace_node = {
120
+ "response_name" => field.name,
121
+ "original_field_name" => field.name,
122
+ "type" => field.type.to_s,
123
+ "parent_type" => parent_type.to_s,
124
+ "start_time" => starts_at - query_starts_at,
125
+ "end_time" => ends_at - query_starts_at,
126
+ "sql_queries" => sql_queries,
127
+ "path" => path,
128
+ }
129
+
130
+ context["gqlmetrics-trace"]["traces"] << trace_node
131
+ end
132
+
133
+ # Introspect the schema and upload to the ingress server
134
+ def publish_schema!
135
+ return unless graph_token_configured?
136
+
137
+ schema = schema_definition.execute(GraphQL::Introspection::INTROSPECTION_QUERY)
138
+
139
+ Thread.new do
140
+ payload = {
141
+ query: PUBLISH_SCHEMA_QUERY,
142
+ variables: {
143
+ schema: schema.to_json,
144
+ },
145
+ }
146
+
147
+ response = Typhoeus.post(
148
+ "#{gqlmetrics_http_host}/graphql",
149
+ headers: {
150
+ 'Content-Type': "application/json",
151
+ 'Authorization': "Bearer #{ENV['GQLMETRICS_GRAPH_ID']}",
152
+ },
153
+ timeout: 10,
154
+ body: payload.to_json,
155
+ )
156
+
157
+ json_response = JSON.parse(response.body)
158
+ schema_id = json_response.dig("data", "publishSchema", "id")
159
+
160
+ if schema_id.present?
161
+ @schema_id = schema_id
162
+ else
163
+ Rails.logger.warn "[GQLMetricsTracer] Failed to publish the latest schema..."
164
+ nil
165
+ end
166
+ end
167
+ end
168
+
169
+ def graph_token_configured?
170
+ ENV["GQLMETRICS_GRAPH_ID"].present?
171
+ end
172
+
173
+ def gqlmetrics_http_host
174
+ ENV.fetch("GQLMETRICS_HTTP_HOST", "http://localhost:5000")
175
+ end
176
+
177
+ def precise_time
178
+ Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
179
+ end
180
+
181
+ def timestamp_to_ms(timestamp)
182
+ (timestamp.to_f * 1_000).to_i
183
+ end
184
+
185
+ PUBLISH_SCHEMA_QUERY = <<~PUBLISH_SCHEMA_QUERY
186
+ mutation PublishSchema($schema: String!) {
187
+ publishSchema(schema: $schema) {
188
+ id
189
+ }
190
+ }
191
+ PUBLISH_SCHEMA_QUERY
192
+ end
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gql_metrics_tracer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Dan Andreasson
8
+ - André Ligné
9
+ - Ante Wall
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2020-06-16 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: typhoeus
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - "~>"
20
+ - !ruby/object:Gem::Version
21
+ version: '1.0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - "~>"
27
+ - !ruby/object:Gem::Version
28
+ version: '1.0'
29
+ - !ruby/object:Gem::Dependency
30
+ name: json
31
+ requirement: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - "~>"
34
+ - !ruby/object:Gem::Version
35
+ version: '2.0'
36
+ type: :runtime
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - "~>"
41
+ - !ruby/object:Gem::Version
42
+ version: '2.0'
43
+ - !ruby/object:Gem::Dependency
44
+ name: rspec
45
+ requirement: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: '3.9'
50
+ type: :development
51
+ prerelease: false
52
+ version_requirements: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - "~>"
55
+ - !ruby/object:Gem::Version
56
+ version: '3.9'
57
+ description: Explore your GraphQL metrics
58
+ email: dan@saits.se
59
+ executables: []
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - lib/gql_metrics_tracer.rb
64
+ homepage: https://rubygems.org/gems/tracer
65
+ licenses:
66
+ - MIT
67
+ metadata: {}
68
+ post_install_message:
69
+ rdoc_options: []
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ requirements: []
83
+ rubygems_version: 3.1.2
84
+ signing_key:
85
+ specification_version: 4
86
+ summary: Explore your GraphQL metrics
87
+ test_files: []