gql_metrics_tracer 0.0.0 → 0.0.5.pre.beta1

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: 0b1b248815920370432ba4165e3b544f5baa430d8805be210af0c590dde54db2
4
- data.tar.gz: ed0898b557cd27f7a1286ede425b410e590b09aaa96c5526338a54d8e43cffd4
3
+ metadata.gz: 1c2653b0e1af097aff74971b5951dbc597f85051f7f97bf19ecc49e154c7a4ac
4
+ data.tar.gz: 4f9b60bc43d6b2ebfe5f4ac507446bf4671a2d410a1a505ceec12805c1809616
5
5
  SHA512:
6
- metadata.gz: a787ae6298818afc34946b44285474e9d590fa987d169f47aa17d5f61cf11ee8ed25d17302d2031688a8d3b38e21ba2affb23dc2c6c4db83a2f14498d4bf30fa
7
- data.tar.gz: 9950c53fe9f6681e39b9a2f4e94897100ded43f209fd308e96789e08399b40c1e9fc0187228bf6150e6f437e58ef43df20917d30ce65f86ac16091e3057feef5
6
+ metadata.gz: 9e174d161fcbf9c33844659368a079b31d13c0d5991a72a36260cb1df50e28a2b1184eaccfa6ae8e54c55b4fde4dc89d220fc34f29f70737f467d37d75f58057
7
+ data.tar.gz: 2095cbc96d3e7d51c6dd1727a086c55ccceb4238845e52b24bcd74dfbd8e19c9dfc490ec687bf9367eac42888facf2cdd0feb66a6bdea8ca53ee0da4057a1c78
@@ -1,192 +1,66 @@
1
- # frozen_string_literal: true
2
-
3
1
  require "typhoeus"
4
2
  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
3
+ require "active_support/all"
4
+
5
+ require "gql_metrics_tracer/field_tracer"
6
+ require "gql_metrics_tracer/query_tracer"
7
+ require "gql_metrics_tracer/api_client"
8
+
9
+ module GQLMetrics
10
+ class Tracer
11
+ def self.use(schema_definition, _options = {})
12
+ tracer = new(schema_definition)
13
+ schema_definition.instrument(:query, tracer)
14
+ schema_definition.tracer(tracer)
54
15
  end
55
- end
56
16
 
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?
17
+ def initialize(schema_definition)
18
+ @api_client = ApiClient.new schema_definition: schema_definition
19
+ Thread.new { @api_client.publish_schema! }
20
+ end
64
21
 
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
- },
22
+ def before_query(query)
23
+ query.context["gqlmetrics-trace"] = {
24
+ "query-tracer" => QueryTracer.new(
25
+ schema_id: schema_id,
26
+ query: query.document.to_query_string,
27
+ operation_name: query.context[:operation_name],
82
28
  ),
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
29
+ }
92
30
  end
93
- end
94
31
 
95
- def trace_field!(field:, path:, parent_type:, context:)
96
- query_starts_at = context["gqlmetrics-trace"]["starts-ns"]
32
+ def after_query(query)
33
+ query_tracer = query.context.dig("gqlmetrics-trace", "query-tracer")
34
+ query_tracer.end_trace!
97
35
 
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
36
+ Thread.new { api_client.send_trace!(query_tracer) }
111
37
  end
112
38
 
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
39
+ def trace(key, data, &block)
40
+ if key == "execute_field"
41
+ context = data[:context] || data[:object].context
42
+ query_tracer = context.dig("gqlmetrics-trace", "query-tracer")
43
+
44
+ field = !data[:context].nil? ? data[:context].field : data[:field]
45
+ path = !data[:context].nil? ? data[:context].path : data[:path]
46
+ parent_type = !data[:context].nil? ? data[:context].parent_type : data[:owner].graphql_name
47
+
48
+ query_tracer.trace_field!(
49
+ field: field,
50
+ path: path,
51
+ parent_type: parent_type,
52
+ context: context,
53
+ &block
54
+ )
162
55
  else
163
- Rails.logger.warn "[GQLMetricsTracer] Failed to publish the latest schema..."
164
- nil
56
+ yield
165
57
  end
166
58
  end
167
- end
168
-
169
- def graph_token_configured?
170
- ENV["GQLMETRICS_GRAPH_ID"].present?
171
- end
172
59
 
173
- def gqlmetrics_http_host
174
- ENV.fetch("GQLMETRICS_HTTP_HOST", "http://localhost:5000")
175
- end
60
+ private
176
61
 
177
- def precise_time
178
- Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
179
- end
62
+ attr_reader :api_client
180
63
 
181
- def timestamp_to_ms(timestamp)
182
- (timestamp.to_f * 1_000).to_i
64
+ delegate :schema_id, to: :api_client
183
65
  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
66
  end
@@ -0,0 +1,88 @@
1
+ require "gql_metrics_tracer/json_serializer"
2
+
3
+ module GQLMetrics
4
+ class ApiClient
5
+ attr_reader :schema_id
6
+
7
+ def initialize(schema_definition:)
8
+ @schema_id = nil
9
+ @schema_definition = schema_definition
10
+ end
11
+
12
+ def send_trace!(query_tracer)
13
+ return unless graph_token_configured?
14
+ return if schema_id.nil?
15
+
16
+ response = Typhoeus.post(
17
+ "#{gqlmetrics_http_host}/api/traces",
18
+ headers: {
19
+ 'Content-Type': "application/json",
20
+ 'Authorization': "Bearer #{ENV['GQLMETRICS_GRAPH_ID']}",
21
+ },
22
+ timeout: 30,
23
+ body: JSON.dump(JsonSerializer.new(query_tracer).as_json),
24
+ )
25
+
26
+ trace_id = JSON.parse(response.body)["id"]
27
+
28
+ if !trace_id.nil?
29
+ Rails.logger.info "[GQLMetricsTracer] Sent trace #{trace_id}"
30
+ else
31
+ Rails.logger.warn "[GQLMetricsTracer] Failed to send the trace..."
32
+ end
33
+ end
34
+
35
+ def publish_schema!
36
+ return unless graph_token_configured?
37
+
38
+ schema = schema_definition.execute(GraphQL::Introspection::INTROSPECTION_QUERY)
39
+
40
+ payload = {
41
+ query: PUBLISH_SCHEMA_QUERY,
42
+ variables: {
43
+ schema: schema.to_json,
44
+ },
45
+ }
46
+
47
+ response = Typhoeus.post(
48
+ "#{gqlmetrics_http_host}/graphql",
49
+ headers: {
50
+ 'Content-Type': "application/json",
51
+ 'Authorization': "Bearer #{ENV['GQLMETRICS_GRAPH_ID']}",
52
+ },
53
+ timeout: 10,
54
+ body: payload.to_json,
55
+ )
56
+
57
+ json_response = JSON.parse(response.body)
58
+ schema_id = json_response.dig("data", "publishSchema", "id")
59
+
60
+ if !schema_id.nil?
61
+ @schema_id = schema_id
62
+ else
63
+ Rails.logger.warn "[GQLMetricsTracer] Failed to publish the latest schema..."
64
+ nil
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ attr_reader :schema_definition
71
+
72
+ def graph_token_configured?
73
+ !ENV["GQLMETRICS_GRAPH_ID"].nil?
74
+ end
75
+
76
+ def gqlmetrics_http_host
77
+ ENV.fetch("GQLMETRICS_HTTP_HOST", "http://localhost:5000")
78
+ end
79
+
80
+ PUBLISH_SCHEMA_QUERY = <<~PUBLISH_SCHEMA_QUERY
81
+ mutation PublishSchema($schema: String!) {
82
+ publishSchema(schema: $schema) {
83
+ id
84
+ }
85
+ }
86
+ PUBLISH_SCHEMA_QUERY
87
+ end
88
+ end
@@ -0,0 +1,49 @@
1
+ require "gql_metrics_tracer/precise_time"
2
+ require "gql_metrics_tracer/sql_tracer"
3
+
4
+ module GQLMetrics
5
+ class FieldTracer
6
+ def self.trace!(query_start_time:, &block)
7
+ self.new(query_start_time, &block).tap(&:trace!)
8
+ end
9
+
10
+ attr_reader :started_at, :ended_at, :result
11
+
12
+ def duration_ns
13
+ precise_end - precise_start
14
+ end
15
+
16
+ def relative_start_time
17
+ precise_start - query_start_time
18
+ end
19
+
20
+ def relative_end_time
21
+ precise_end - query_start_time
22
+ end
23
+
24
+ def sql_queries
25
+ @sql_tracer.queries
26
+ end
27
+
28
+ def trace!
29
+ @started_at = Time.now
30
+ @precise_start = PreciseTime.now
31
+
32
+ @sql_tracer = SqlTracer.trace! do
33
+ @result = block.call
34
+ end
35
+ ensure
36
+ @precise_end = PreciseTime.now
37
+ @ended_at = Time.now
38
+ end
39
+
40
+ private
41
+
42
+ attr_reader :block, :sql_tracer, :precise_start, :precise_end, :query_start_time
43
+
44
+ def initialize(query_start_time, &block)
45
+ @query_start_time = query_start_time
46
+ @block = block
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,27 @@
1
+ module GQLMetrics
2
+ class JsonSerializer
3
+ def initialize(query_tracer)
4
+ @query_tracer = query_tracer
5
+ end
6
+
7
+ def as_json
8
+ {
9
+ schema_id: query_tracer.schema_id,
10
+ operation_name: query_tracer.operation_name,
11
+ query: query_tracer.query,
12
+ started_at_ms: timestamp_to_ms(query_tracer.start_timestamp),
13
+ ended_at_ms: timestamp_to_ms(query_tracer.end_timestamp),
14
+ duration_ns: query_tracer.duration_ns,
15
+ traces: query_tracer.trace_nodes,
16
+ }
17
+ end
18
+
19
+ private
20
+
21
+ attr_reader :query_tracer
22
+
23
+ def timestamp_to_ms(timestamp)
24
+ (timestamp.to_f * 1_000).to_i
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,7 @@
1
+ module GQLMetrics
2
+ module PreciseTime
3
+ def self.now
4
+ Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,49 @@
1
+ module GQLMetrics
2
+ class QueryTracer
3
+ attr_reader(
4
+ :schema_id,
5
+ :query,
6
+ :precise_start_time,
7
+ :precise_end_time,
8
+ :start_timestamp,
9
+ :end_timestamp,
10
+ :trace_nodes,
11
+ :operation_name,
12
+ )
13
+
14
+ def initialize(schema_id:, query:, operation_name:)
15
+ @schema_id = schema_id
16
+ @query = query
17
+ @operation_name = operation_name
18
+ @precise_start_time = PreciseTime.now
19
+ @start_timestamp = Time.now
20
+ @trace_nodes = []
21
+ end
22
+
23
+ def trace_field!(field:, path:, parent_type:, context:, &block)
24
+ field_trace = FieldTracer.trace!(query_start_time: precise_start_time, &block)
25
+
26
+ @trace_nodes << {
27
+ "response_name" => path.last.split(".").last,
28
+ "original_field_name" => field.name,
29
+ "type" => field.type.to_type_signature,
30
+ "parent_type" => parent_type.to_s,
31
+ "start_time" => field_trace.relative_start_time,
32
+ "end_time" => field_trace.relative_end_time,
33
+ "sql_queries" => field_trace.sql_queries,
34
+ "path" => path,
35
+ }
36
+
37
+ field_trace.result
38
+ end
39
+
40
+ def duration_ns
41
+ precise_end_time - precise_start_time
42
+ end
43
+
44
+ def end_trace!
45
+ @precise_end_time = PreciseTime.now
46
+ @end_timestamp = Time.now
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,38 @@
1
+ module GQLMetrics
2
+ class SqlTracer
3
+ attr_reader :queries
4
+
5
+ def self.trace!(&block)
6
+ self.new(&block).tap(&:trace!)
7
+ end
8
+
9
+ def trace!
10
+ ActiveSupport::Notifications.subscribe "sql.active_record" do |event|
11
+ unless /(pg_)|(max_identifier_length)/.match?(event.payload[:sql])
12
+ @queries.push(
13
+ {
14
+ sql: event.payload[:sql],
15
+ start_time: event.time,
16
+ end_time: event.end,
17
+ duration_ns: event.duration * 1_000_000,
18
+ allocations_count: event.allocations,
19
+ },
20
+ )
21
+ end
22
+ end
23
+
24
+ block.call
25
+ ensure
26
+ ActiveSupport::Notifications.unsubscribe("sql.active_record")
27
+ end
28
+
29
+ private
30
+
31
+ attr_reader :block
32
+
33
+ def initialize(&block)
34
+ @block = block
35
+ @queries = []
36
+ end
37
+ end
38
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gql_metrics_tracer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.0.5.pre.beta1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dan Andreasson
8
8
  - André Ligné
9
9
  - Ante Wall
10
- autorequire:
10
+ autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
13
  date: 2020-06-16 00:00:00.000000000 Z
@@ -27,19 +27,33 @@ dependencies:
27
27
  - !ruby/object:Gem::Version
28
28
  version: '1.0'
29
29
  - !ruby/object:Gem::Dependency
30
- name: json
30
+ name: graphql
31
31
  requirement: !ruby/object:Gem::Requirement
32
32
  requirements:
33
33
  - - "~>"
34
34
  - !ruby/object:Gem::Version
35
- version: '2.0'
35
+ version: '1.11'
36
36
  type: :runtime
37
37
  prerelease: false
38
38
  version_requirements: !ruby/object:Gem::Requirement
39
39
  requirements:
40
40
  - - "~>"
41
41
  - !ruby/object:Gem::Version
42
- version: '2.0'
42
+ version: '1.11'
43
+ - !ruby/object:Gem::Dependency
44
+ name: activesupport
45
+ requirement: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: '6.0'
50
+ type: :runtime
51
+ prerelease: false
52
+ version_requirements: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - "~>"
55
+ - !ruby/object:Gem::Version
56
+ version: '6.0'
43
57
  - !ruby/object:Gem::Dependency
44
58
  name: rspec
45
59
  requirement: !ruby/object:Gem::Requirement
@@ -54,6 +68,34 @@ dependencies:
54
68
  - - "~>"
55
69
  - !ruby/object:Gem::Version
56
70
  version: '3.9'
71
+ - !ruby/object:Gem::Dependency
72
+ name: rubocop
73
+ requirement: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - "~>"
76
+ - !ruby/object:Gem::Version
77
+ version: '1.0'
78
+ type: :development
79
+ prerelease: false
80
+ version_requirements: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - "~>"
83
+ - !ruby/object:Gem::Version
84
+ version: '1.0'
85
+ - !ruby/object:Gem::Dependency
86
+ name: timecop
87
+ requirement: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - "~>"
90
+ - !ruby/object:Gem::Version
91
+ version: '0.9'
92
+ type: :development
93
+ prerelease: false
94
+ version_requirements: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - "~>"
97
+ - !ruby/object:Gem::Version
98
+ version: '0.9'
57
99
  description: Explore your GraphQL metrics
58
100
  email: dan@saits.se
59
101
  executables: []
@@ -61,11 +103,18 @@ extensions: []
61
103
  extra_rdoc_files: []
62
104
  files:
63
105
  - lib/gql_metrics_tracer.rb
106
+ - lib/gql_metrics_tracer/api_client.rb
107
+ - lib/gql_metrics_tracer/field_tracer.rb
108
+ - lib/gql_metrics_tracer/json_serializer.rb
109
+ - lib/gql_metrics_tracer/precise_time.rb
110
+ - lib/gql_metrics_tracer/query_tracer.rb
111
+ - lib/gql_metrics_tracer/sql_tracer.rb
64
112
  homepage: https://rubygems.org/gems/tracer
65
113
  licenses:
66
114
  - MIT
67
- metadata: {}
68
- post_install_message:
115
+ metadata:
116
+ github_repo: ssh://github.com/gql-metrics/tracer
117
+ post_install_message:
69
118
  rdoc_options: []
70
119
  require_paths:
71
120
  - lib
@@ -76,12 +125,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
76
125
  version: '0'
77
126
  required_rubygems_version: !ruby/object:Gem::Requirement
78
127
  requirements:
79
- - - ">="
128
+ - - ">"
80
129
  - !ruby/object:Gem::Version
81
- version: '0'
130
+ version: 1.3.1
82
131
  requirements: []
83
132
  rubygems_version: 3.1.2
84
- signing_key:
133
+ signing_key:
85
134
  specification_version: 4
86
135
  summary: Explore your GraphQL metrics
87
136
  test_files: []