gql_metrics_tracer 0.0.4 → 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: 8a0d96d8b78c4b80c1625c6b5500cebac944464197884576612f00ba6bc584cf
4
- data.tar.gz: cf69077f4fc23379bf50e058df072aab751d4e676ef9c28e664d4632b2bd820f
3
+ metadata.gz: 1c2653b0e1af097aff74971b5951dbc597f85051f7f97bf19ecc49e154c7a4ac
4
+ data.tar.gz: 4f9b60bc43d6b2ebfe5f4ac507446bf4671a2d410a1a505ceec12805c1809616
5
5
  SHA512:
6
- metadata.gz: a65dd23fd1f46112ea4f389e373326e7da910df2f024135f5c49220e8becb7f85b21a35758432e37dc05583afd6342827f5f358ce2364f665c5a4f3ece3ba1ad
7
- data.tar.gz: 2cb1aa8955955c854c8b864fb9c9f7dae8aeaf4ea0396104c6f0bbb5a0547fc75b7753154e52f8828d3359acdc95e23cfb39f65dd55fd74177693c8cb84cd34c
6
+ metadata.gz: 9e174d161fcbf9c33844659368a079b31d13c0d5991a72a36260cb1df50e28a2b1184eaccfa6ae8e54c55b4fde4dc89d220fc34f29f70737f467d37d75f58057
7
+ data.tar.gz: 2095cbc96d3e7d51c6dd1727a086c55ccceb4238845e52b24bcd74dfbd8e19c9dfc490ec687bf9367eac42888facf2cdd0feb66a6bdea8ca53ee0da4057a1c78
@@ -1,194 +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_type_signature,
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
- Thread.new do
138
- # Sleep for 50ms so that the definition has time enough to initialize
139
- sleep(0.05)
140
- schema = schema_definition.execute(GraphQL::Introspection::INTROSPECTION_QUERY)
141
-
142
- payload = {
143
- query: PUBLISH_SCHEMA_QUERY,
144
- variables: {
145
- schema: schema.to_json,
146
- },
147
- }
148
-
149
- response = Typhoeus.post(
150
- "#{gqlmetrics_http_host}/graphql",
151
- headers: {
152
- 'Content-Type': "application/json",
153
- 'Authorization': "Bearer #{ENV['GQLMETRICS_GRAPH_ID']}",
154
- },
155
- timeout: 10,
156
- body: payload.to_json,
157
- )
158
-
159
- json_response = JSON.parse(response.body)
160
- schema_id = json_response.dig("data", "publishSchema", "id")
161
-
162
- if schema_id.present?
163
- @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
+ )
164
55
  else
165
- Rails.logger.warn "[GQLMetricsTracer] Failed to publish the latest schema..."
166
- nil
56
+ yield
167
57
  end
168
58
  end
169
- end
170
-
171
- def graph_token_configured?
172
- ENV["GQLMETRICS_GRAPH_ID"].present?
173
- end
174
59
 
175
- def gqlmetrics_http_host
176
- ENV.fetch("GQLMETRICS_HTTP_HOST", "http://localhost:5000")
177
- end
60
+ private
178
61
 
179
- def precise_time
180
- Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
181
- end
62
+ attr_reader :api_client
182
63
 
183
- def timestamp_to_ms(timestamp)
184
- (timestamp.to_f * 1_000).to_i
64
+ delegate :schema_id, to: :api_client
185
65
  end
186
-
187
- PUBLISH_SCHEMA_QUERY = <<~PUBLISH_SCHEMA_QUERY
188
- mutation PublishSchema($schema: String!) {
189
- publishSchema(schema: $schema) {
190
- id
191
- }
192
- }
193
- PUBLISH_SCHEMA_QUERY
194
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,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gql_metrics_tracer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5.pre.beta1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dan Andreasson
@@ -26,6 +26,34 @@ dependencies:
26
26
  - - "~>"
27
27
  - !ruby/object:Gem::Version
28
28
  version: '1.0'
29
+ - !ruby/object:Gem::Dependency
30
+ name: graphql
31
+ requirement: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - "~>"
34
+ - !ruby/object:Gem::Version
35
+ version: '1.11'
36
+ type: :runtime
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - "~>"
41
+ - !ruby/object:Gem::Version
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'
29
57
  - !ruby/object:Gem::Dependency
30
58
  name: rspec
31
59
  requirement: !ruby/object:Gem::Requirement
@@ -40,6 +68,34 @@ dependencies:
40
68
  - - "~>"
41
69
  - !ruby/object:Gem::Version
42
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'
43
99
  description: Explore your GraphQL metrics
44
100
  email: dan@saits.se
45
101
  executables: []
@@ -47,10 +103,17 @@ extensions: []
47
103
  extra_rdoc_files: []
48
104
  files:
49
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
50
112
  homepage: https://rubygems.org/gems/tracer
51
113
  licenses:
52
114
  - MIT
53
- metadata: {}
115
+ metadata:
116
+ github_repo: ssh://github.com/gql-metrics/tracer
54
117
  post_install_message:
55
118
  rdoc_options: []
56
119
  require_paths:
@@ -62,9 +125,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
62
125
  version: '0'
63
126
  required_rubygems_version: !ruby/object:Gem::Requirement
64
127
  requirements:
65
- - - ">="
128
+ - - ">"
66
129
  - !ruby/object:Gem::Version
67
- version: '0'
130
+ version: 1.3.1
68
131
  requirements: []
69
132
  rubygems_version: 3.1.2
70
133
  signing_key: