graphql-metrics 5.0.8 → 6.0.1
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 +4 -4
- data/.github/dependabot.yml +24 -0
- data/.github/workflows/dependabot_auto_merge.yml +23 -0
- data/.github/workflows/release.yml +52 -0
- data/.github/workflows/ruby.yml +9 -5
- data/.gitignore +0 -1
- data/.rubocop.yml +0 -3
- data/.ruby-version +1 -1
- data/CHANGELOG.md +44 -0
- data/Gemfile.lock +71 -0
- data/README.md +33 -50
- data/RELEASING +11 -11
- data/gemfiles/{graphql_1.13.gemfile → graphql_2.3.gemfile} +1 -1
- data/graphql_metrics.gemspec +5 -5
- data/lib/graphql/metrics/analyzer.rb +12 -18
- data/lib/graphql/metrics/instrumentation.rb +28 -28
- data/lib/graphql/metrics/trace.rb +30 -106
- data/lib/graphql/metrics/version.rb +1 -1
- data/lib/graphql/metrics.rb +24 -43
- metadata +15 -44
- data/gemfiles/graphql_2.0.gemfile +0 -5
- data/lib/graphql/metrics/tracer.rb +0 -70
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 315fadbeefcfba11dcde6e9f60786b414c6351e891800ac908bd491b40a50ecd
|
|
4
|
+
data.tar.gz: cae83709e2add17cf2534486932c504d216784402fae02c994afd911d0bb42be
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f9e9b56eabf8f9993501b9bf805c05515ba5bba50e908f1aa10c205534208f1b60b6da90c3a2e951bb08fa6387860089decca0bcaea0b87649a5d42e5842cc6c
|
|
7
|
+
data.tar.gz: 3a513f4d6f25732972db556526bba42884ef41389cee0f372a0bd373ce71853889ea70e4cad5c042ad3956f3ea4e22cbbc1d5871f9d340e6e433499a7ffbbde2
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
registries:
|
|
3
|
+
ruby-shopify:
|
|
4
|
+
type: rubygems-server
|
|
5
|
+
url: https://pkgs.shopify.io/basic/gems/ruby
|
|
6
|
+
username: ${{secrets.RUBYGEMS_SERVER_PKGS_SHOPIFY_IO_USERNAME}}
|
|
7
|
+
password: ${{secrets.RUBYGEMS_SERVER_PKGS_SHOPIFY_IO_PASSWORD}}
|
|
8
|
+
github-com:
|
|
9
|
+
type: git
|
|
10
|
+
url: https://github.com
|
|
11
|
+
username: ${{secrets.DEPENDENCIES_GITHUB_USER}}
|
|
12
|
+
password: ${{secrets.DEPENDENCIES_GITHUB_TOKEN}}
|
|
13
|
+
updates:
|
|
14
|
+
- package-ecosystem: github-actions
|
|
15
|
+
directory: "/"
|
|
16
|
+
schedule:
|
|
17
|
+
interval: weekly
|
|
18
|
+
- package-ecosystem: bundler
|
|
19
|
+
directory: "/"
|
|
20
|
+
schedule:
|
|
21
|
+
interval: weekly
|
|
22
|
+
open-pull-requests-limit: 100
|
|
23
|
+
insecure-external-code-execution: allow
|
|
24
|
+
registries: "*"
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
name: Dependabot auto-merge
|
|
2
|
+
on: pull_request_target
|
|
3
|
+
|
|
4
|
+
permissions:
|
|
5
|
+
pull-requests: write
|
|
6
|
+
contents: write
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
dependabot:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
if: ${{ github.event.pull_request.user.login == 'dependabot[bot]' }}
|
|
12
|
+
steps:
|
|
13
|
+
- name: Dependabot metadata
|
|
14
|
+
id: metadata
|
|
15
|
+
uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98 # v3.1.0
|
|
16
|
+
with:
|
|
17
|
+
github-token: "${{ secrets.GITHUB_TOKEN }}"
|
|
18
|
+
- name: Enable auto-merge for Dependabot PRs
|
|
19
|
+
if: ${{ steps.metadata.outputs.update-type == 'version-update:semver-patch' }}
|
|
20
|
+
run: gh pr merge --auto --merge "$PR_URL"
|
|
21
|
+
env:
|
|
22
|
+
PR_URL: ${{ github.event.pull_request.html_url }}
|
|
23
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
name: Release gem on RubyGems.org and create GitHub release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'v[0-9]+.[0-9]+.[0-9]+'
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
release_gem:
|
|
10
|
+
name: Release gem on RubyGems.org
|
|
11
|
+
if: github.repository == 'Shopify/graphql-metrics'
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
|
|
14
|
+
permissions:
|
|
15
|
+
contents: write
|
|
16
|
+
id-token: write
|
|
17
|
+
|
|
18
|
+
environment: release
|
|
19
|
+
|
|
20
|
+
steps:
|
|
21
|
+
- name: Checkout repository
|
|
22
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
23
|
+
with:
|
|
24
|
+
persist-credentials: false
|
|
25
|
+
|
|
26
|
+
- name: Set up Ruby
|
|
27
|
+
uses: ruby/setup-ruby@b3aaceaef7e0ee231b499e7b5f49c03fc1d32199 # v1.338.0
|
|
28
|
+
with:
|
|
29
|
+
bundler-cache: true
|
|
30
|
+
|
|
31
|
+
- name: Release gem on RubyGems.org
|
|
32
|
+
uses: rubygems/release-gem@7f9650160c1a4e7989fdc9855807bdbd421d8b6b # v1.4.1
|
|
33
|
+
|
|
34
|
+
release_github:
|
|
35
|
+
name: Create GitHub release
|
|
36
|
+
if: github.repository == 'Shopify/graphql-metrics'
|
|
37
|
+
needs: release_gem
|
|
38
|
+
runs-on: ubuntu-latest
|
|
39
|
+
|
|
40
|
+
permissions:
|
|
41
|
+
contents: write
|
|
42
|
+
|
|
43
|
+
steps:
|
|
44
|
+
- name: Checkout repository
|
|
45
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
46
|
+
with:
|
|
47
|
+
persist-credentials: false
|
|
48
|
+
|
|
49
|
+
- name: Create GitHub release
|
|
50
|
+
run: gh release create "${GITHUB_REF_NAME}" --verify-tag --generate-notes
|
|
51
|
+
env:
|
|
52
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
data/.github/workflows/ruby.yml
CHANGED
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
name: Tests
|
|
2
|
-
on:
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
pull_request:
|
|
5
|
+
schedule:
|
|
6
|
+
- cron: '0 8 * * 1'
|
|
3
7
|
jobs:
|
|
4
8
|
test:
|
|
5
9
|
strategy:
|
|
6
10
|
fail-fast: false
|
|
7
11
|
matrix:
|
|
8
12
|
os: [ubuntu-latest]
|
|
9
|
-
ruby: ['
|
|
10
|
-
gemfile: ['
|
|
13
|
+
ruby: ['3.3', '3.4', '4.0']
|
|
14
|
+
gemfile: ['graphql_2.3', 'graphql_head']
|
|
11
15
|
runs-on: ${{ matrix.os }}
|
|
12
16
|
env:
|
|
13
17
|
BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile
|
|
14
18
|
steps:
|
|
15
|
-
- uses: actions/checkout@
|
|
16
|
-
- uses: ruby/setup-ruby@v1
|
|
19
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
20
|
+
- uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0
|
|
17
21
|
with:
|
|
18
22
|
ruby-version: ${{ matrix.ruby }}
|
|
19
23
|
bundler-cache: true
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
data/.ruby-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
4.0.9
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,47 @@
|
|
|
1
|
+
6.0.1
|
|
2
|
+
-----
|
|
3
|
+
- [149](https://github.com/Shopify/graphql-metrics/pull/149) Require Ruby 3.3 or newer
|
|
4
|
+
- [110](https://github.com/Shopify/graphql-metrics/pull/110) Fix missing `parent_input_object_type` in nested input object metrics
|
|
5
|
+
|
|
6
|
+
6.0.0
|
|
7
|
+
-----
|
|
8
|
+
|
|
9
|
+
**Usage change**
|
|
10
|
+
Due to Instrumentation plugins being deprecated in the `graphql` gem, `graphql-metrics` has been refactored to use the [new `Tracing` API](https://graphql-ruby.org/queries/tracing.html).
|
|
11
|
+
This changes how `graphql-metrics` is used in your application.
|
|
12
|
+
|
|
13
|
+
Before:
|
|
14
|
+
```ruby
|
|
15
|
+
class MySchema < GraphQL::Schema
|
|
16
|
+
instrument :query, GraphQLMetrics::Instrumentation.new
|
|
17
|
+
query_analyzer MyMetricsAnalyzer
|
|
18
|
+
tracer GraphQL::Metrics::Tracer.new
|
|
19
|
+
end
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
After:
|
|
23
|
+
```ruby
|
|
24
|
+
class MySchema < GraphQL::Schema
|
|
25
|
+
use GraphQL::Metrics, analyzer: MyMetricsAnalyzer
|
|
26
|
+
end
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Other breaking changes:
|
|
30
|
+
* `graphql` >= 2.3 is now required.
|
|
31
|
+
* Apollo tracing spec support has been removed (the spec has been deprecated for years). All `start_time_offset` metrics have been removed.
|
|
32
|
+
* Support for the old tracing API has been removed.
|
|
33
|
+
* The `multiplex_start_time_monotonic` operation mettric has been removed.
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
Changes:
|
|
37
|
+
- [87](https://github.com/Shopify/graphql-metrics/pull/87) Migrate Instrumentation to tracing
|
|
38
|
+
- [88](https://github.com/Shopify/graphql-metrics/pull/88) Update `analyzer` option
|
|
39
|
+
- [86](https://github.com/Shopify/graphql-metrics/pull/86) Refactor static metric mode
|
|
40
|
+
- [85](https://github.com/Shopify/graphql-metrics/pull/85) Remove old tracing API support
|
|
41
|
+
- [84](https://github.com/Shopify/graphql-metrics/pull/84) Remove Apollo tracing spec support
|
|
42
|
+
- [80](https://github.com/Shopify/graphql-metrics/pull/80) Support new parser
|
|
43
|
+
- [79](https://github.com/Shopify/graphql-metrics/pull/79) Centralize Ruby Version to `.ruby-version`
|
|
44
|
+
|
|
1
45
|
5.0.8
|
|
2
46
|
-----
|
|
3
47
|
- [76](https://github.com/Shopify/graphql-metrics/pull/76) Reduce object allocations
|
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
graphql-metrics (6.0.1)
|
|
5
|
+
graphql (>= 2.3)
|
|
6
|
+
|
|
7
|
+
GEM
|
|
8
|
+
remote: https://rubygems.org/
|
|
9
|
+
specs:
|
|
10
|
+
base64 (0.3.0)
|
|
11
|
+
byebug (13.0.0)
|
|
12
|
+
reline (>= 0.6.0)
|
|
13
|
+
coderay (1.1.3)
|
|
14
|
+
diffy (3.4.3)
|
|
15
|
+
drb (2.2.3)
|
|
16
|
+
fakeredis (0.9.2)
|
|
17
|
+
redis (~> 4.8)
|
|
18
|
+
fiber-storage (1.0.1)
|
|
19
|
+
graphql (2.4.18)
|
|
20
|
+
base64
|
|
21
|
+
fiber-storage
|
|
22
|
+
logger
|
|
23
|
+
graphql-batch (0.6.0)
|
|
24
|
+
graphql (>= 1.12.18, < 3)
|
|
25
|
+
promise.rb (~> 0.7.2)
|
|
26
|
+
hashdiff (1.1.2)
|
|
27
|
+
io-console (0.9.5)
|
|
28
|
+
logger (1.7.0)
|
|
29
|
+
method_source (1.1.0)
|
|
30
|
+
minitest (6.1.0)
|
|
31
|
+
drb (~> 2.0)
|
|
32
|
+
prism (>= 1.5, < 3)
|
|
33
|
+
minitest-focus (1.4.1)
|
|
34
|
+
minitest (> 5.0)
|
|
35
|
+
mocha (2.7.1)
|
|
36
|
+
ruby2_keywords (>= 0.0.5)
|
|
37
|
+
prism (2.1.0-arm64-darwin)
|
|
38
|
+
prism (2.1.0-x86_64-linux-gnu)
|
|
39
|
+
promise.rb (0.7.4)
|
|
40
|
+
pry (0.16.1)
|
|
41
|
+
coderay (~> 1.1)
|
|
42
|
+
method_source (~> 1.0)
|
|
43
|
+
reline (>= 0.6.0)
|
|
44
|
+
pry-byebug (3.12.0)
|
|
45
|
+
byebug (~> 13.0)
|
|
46
|
+
pry (>= 0.13, < 0.17)
|
|
47
|
+
rake (13.2.1)
|
|
48
|
+
redis (4.8.1)
|
|
49
|
+
reline (0.7.0)
|
|
50
|
+
io-console (~> 0.5)
|
|
51
|
+
ruby2_keywords (0.0.5)
|
|
52
|
+
|
|
53
|
+
PLATFORMS
|
|
54
|
+
arm64-darwin-23
|
|
55
|
+
x86_64-linux
|
|
56
|
+
|
|
57
|
+
DEPENDENCIES
|
|
58
|
+
diffy
|
|
59
|
+
fakeredis
|
|
60
|
+
graphql-batch
|
|
61
|
+
graphql-metrics!
|
|
62
|
+
hashdiff
|
|
63
|
+
minitest (~> 6.0)
|
|
64
|
+
minitest-focus
|
|
65
|
+
mocha
|
|
66
|
+
pry
|
|
67
|
+
pry-byebug
|
|
68
|
+
rake
|
|
69
|
+
|
|
70
|
+
BUNDLED WITH
|
|
71
|
+
2.4.1
|
data/README.md
CHANGED
|
@@ -7,9 +7,6 @@ Compatible with the [`graphql-batch` gem](https://github.com/Shopify/graphql-bat
|
|
|
7
7
|
|
|
8
8
|
Be sure to read the [CHANGELOG](CHANGELOG.md) to stay updated on feature additions, breaking changes made to this gem.
|
|
9
9
|
|
|
10
|
-
**NOTE**: Not tested with graphql-ruby's multiplexing feature. Metrics may not
|
|
11
|
-
be accurate if you execute multiple operations at once.
|
|
12
|
-
|
|
13
10
|
## Installation
|
|
14
11
|
|
|
15
12
|
Add this line to your application's Gemfile:
|
|
@@ -42,15 +39,15 @@ Get started by defining your own Analyzer, inheriting from `GraphQL::Metrics::An
|
|
|
42
39
|
|
|
43
40
|
The following analyzer demonstrates a simple way to capture commonly used metrics sourced from key parts of your schema
|
|
44
41
|
definition, the query document being served, as well as runtime query and resolver timings. In this toy example, all of
|
|
45
|
-
this data is simply stored on the GraphQL::Query context, under a namespace to avoid collisions with other analyzers
|
|
42
|
+
this data is simply stored on the `GraphQL::Query` context, under a namespace to avoid collisions with other analyzers
|
|
46
43
|
etc.
|
|
47
44
|
|
|
48
45
|
What you do with these captured metrics is up to you!
|
|
49
46
|
|
|
50
47
|
**NOTE**: If any non-`graphql-ruby` gem-related exceptions occur in your application during query document
|
|
51
|
-
parsing and validation, **runtime metrics** for queries (like `query_duration`,
|
|
48
|
+
parsing and validation, **runtime metrics** for queries (like `query_duration`, etc.) as well as field
|
|
52
49
|
resolver timings (like `resolver_timings`, `lazy_resolver_timings`) **may not be present** in the extracted `metrics` hash,
|
|
53
|
-
even if you opt to collect them
|
|
50
|
+
even if you opt to collect them.
|
|
54
51
|
|
|
55
52
|
### Define your own analyzer subclass
|
|
56
53
|
|
|
@@ -75,15 +72,10 @@ even if you opt to collect them by using `GraphQL::Metrics::Analyzer` and `Graph
|
|
|
75
72
|
# operation_name: "PostDetails",
|
|
76
73
|
# query_start_time: 1573833076.027327,
|
|
77
74
|
# query_duration: 2.0207119999686256,
|
|
78
|
-
# lexing_start_time_offset: 0.0010339999571442604,
|
|
79
75
|
# lexing_duration: 0.0008190000080503523,
|
|
80
|
-
# parsing_start_time_offset: 0.0010339999571442604,
|
|
81
76
|
# parsing_duration: 0.0008190000080503523,
|
|
82
|
-
# validation_start_time_offset: 0.0030819999519735575,
|
|
83
77
|
# validation_duration: 0.01704599999357015,
|
|
84
|
-
# analysis_start_time_offset: 0.0010339999571442604,
|
|
85
78
|
# analysis_duration: 0.0008190000080503523,
|
|
86
|
-
# multiplex_start_time: 0.0008190000080503523,
|
|
87
79
|
# }
|
|
88
80
|
#
|
|
89
81
|
# You can use these metrics to track high-level query performance, along with any other details you wish to
|
|
@@ -129,12 +121,10 @@ even if you opt to collect them by using `GraphQL::Metrics::Analyzer` and `Graph
|
|
|
129
121
|
# deprecated: false,
|
|
130
122
|
# path: ["post", "id"],
|
|
131
123
|
# resolver_timings: [
|
|
132
|
-
#
|
|
133
|
-
# duration: 5.999987479299307e-06
|
|
124
|
+
# 5.999987479299307e-06,
|
|
134
125
|
# ],
|
|
135
126
|
# lazy_resolver_timings: [
|
|
136
|
-
#
|
|
137
|
-
# duration: 5.999987479299307e-06
|
|
127
|
+
# 5.999987479299307e-06,
|
|
138
128
|
# ],
|
|
139
129
|
# }
|
|
140
130
|
def field_extracted(metrics)
|
|
@@ -178,12 +168,12 @@ even if you opt to collect them by using `GraphQL::Metrics::Analyzer` and `Graph
|
|
|
178
168
|
end
|
|
179
169
|
```
|
|
180
170
|
|
|
181
|
-
Once defined, you can opt into capturing all metrics seen above by simply including GraphQL::Metrics as a plugin on your
|
|
171
|
+
Once defined, you can opt into capturing all metrics seen above by simply including `GraphQL::Metrics` as a plugin on your
|
|
182
172
|
schema.
|
|
183
|
-
#### Metrics that are captured for arguments for fields and directives
|
|
184
173
|
|
|
185
|
-
|
|
174
|
+
#### Metrics that are captured for arguments for fields and directives
|
|
186
175
|
|
|
176
|
+
Example query:
|
|
187
177
|
```graphql
|
|
188
178
|
query PostDetails($postId: ID!, $commentsTags: [String!] = null, $val: Int!) @customDirective(val: $val) {
|
|
189
179
|
post(id: $postId) {
|
|
@@ -242,46 +232,48 @@ These are some of the arguments that are extracted
|
|
|
242
232
|
|
|
243
233
|
### Make use of your analyzer
|
|
244
234
|
|
|
245
|
-
|
|
246
|
-
engine, and then simply add the below `GraphQL::Metrics` plugins.
|
|
247
|
-
|
|
248
|
-
This opts you in to capturing all static and runtime metrics seen above.
|
|
235
|
+
Add the `GraphQL::Metrics` plugin to your schema. This opts you in to capturing all static and runtime metrics seen above.
|
|
249
236
|
|
|
250
237
|
```ruby
|
|
251
238
|
class Schema < GraphQL::Schema
|
|
252
239
|
query QueryRoot
|
|
253
240
|
mutation MutationRoot
|
|
254
241
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
instrument :query, GraphQL::Metrics::Instrumentation.new # Both of these are required if either is used.
|
|
258
|
-
tracer GraphQL::Metrics::Tracer.new # <-- Note!
|
|
259
|
-
|
|
260
|
-
use GraphQL::Batch # Optional, but highly recommended. See https://github.com/Shopify/graphql-batch/.
|
|
242
|
+
use GraphQL::Metrics, analyzer: SimpleAnalyzer
|
|
261
243
|
end
|
|
262
244
|
```
|
|
263
245
|
|
|
264
246
|
### Optionally, only gather static metrics
|
|
265
247
|
|
|
266
|
-
If you don't care to capture
|
|
267
|
-
analyzer without `GraphQL::Metrics::Instrumentation` and `tracer GraphQL::Metrics::Tracer`, like so:
|
|
248
|
+
If you don't care to capture field timings metrics, they can be disabled with the `capture_field_timings` option:
|
|
268
249
|
|
|
269
250
|
```ruby
|
|
270
251
|
class Schema < GraphQL::Schema
|
|
271
252
|
query QueryRoot
|
|
272
253
|
mutation MutationRoot
|
|
273
254
|
|
|
274
|
-
|
|
255
|
+
use GraphQL::Metrics, analyzer: SimpleAnalyzer, capture_field_timings: false
|
|
275
256
|
end
|
|
276
257
|
```
|
|
277
258
|
|
|
278
259
|
Your analyzer will still be called with `query_extracted`, `field_extracted`, but with timings metrics omitted.
|
|
279
|
-
`argument_extracted` will work exactly the same
|
|
260
|
+
`argument_extracted` will work exactly the same.
|
|
261
|
+
|
|
262
|
+
And if you want to disable tracing metrics entirely, use the `capture_timings` option:
|
|
263
|
+
|
|
264
|
+
```ruby
|
|
265
|
+
class Schema < GraphQL::Schema
|
|
266
|
+
query QueryRoot
|
|
267
|
+
mutation MutationRoot
|
|
268
|
+
|
|
269
|
+
use GraphQL::Metrics, analyzer: SimpleAnalyzer, capture_timings: false
|
|
270
|
+
end
|
|
271
|
+
```
|
|
280
272
|
|
|
281
273
|
## Order of execution
|
|
282
274
|
|
|
283
275
|
Because of the structure of graphql-ruby's plugin architecture, it may be difficult to build an intuition around the
|
|
284
|
-
order in which methods defined on `GraphQL::Metrics::Instrumentation`, `GraphQL::Metrics::
|
|
276
|
+
order in which methods defined on `GraphQL::Metrics::Instrumentation`, `GraphQL::Metrics::Trace` and subclasses of
|
|
285
277
|
`GraphQL::Metrics::Analyzer` run.
|
|
286
278
|
|
|
287
279
|
Although you ideally will not need to care about these details if you are simply using this gem to gather metrics in
|
|
@@ -289,30 +281,21 @@ your application as intended, here's a breakdown of the order of execution of th
|
|
|
289
281
|
|
|
290
282
|
When used as instrumentation, an analyzer and tracing, the order of execution is usually:
|
|
291
283
|
|
|
292
|
-
*
|
|
293
|
-
*
|
|
294
|
-
*
|
|
295
|
-
*
|
|
296
|
-
*
|
|
297
|
-
* Tracer.capture_analysis_time
|
|
284
|
+
* Instrumentation.execute_multiplex (context setup)
|
|
285
|
+
* Trace.capture_lexing_time
|
|
286
|
+
* Trace.capture_parsing_time
|
|
287
|
+
* Trace.capture_validation_time
|
|
288
|
+
* Trace.capture_analysis_time
|
|
298
289
|
* Analyzer#initialize (bit more context setup, instance vars setup)
|
|
299
290
|
* Analyzer#result
|
|
300
|
-
*
|
|
301
|
-
*
|
|
302
|
-
* Instrumentation.
|
|
303
|
-
gathered)
|
|
291
|
+
* Trace.capture_query_start_time
|
|
292
|
+
* Trace.trace_field (n times)
|
|
293
|
+
* Instrumentation.execute_multiplex (call query and field callbacks, now that we have all static and runtime metrics gathered)
|
|
304
294
|
* Analyzer#extract_query
|
|
305
295
|
* Analyzer#query_extracted
|
|
306
296
|
* Analyzer#extract_fields_with_runtime_metrics
|
|
307
297
|
* calls Analyzer#field_extracted n times
|
|
308
298
|
|
|
309
|
-
When used as a simple analyzer, which doesn't gather or emit any runtime metrics (timings, arg values):
|
|
310
|
-
* Analyzer#initialize
|
|
311
|
-
* Analyzer#field_extracted n times
|
|
312
|
-
* Analyzer#result
|
|
313
|
-
* Analyzer#extract_query
|
|
314
|
-
* Analyzer#query_extracted
|
|
315
|
-
|
|
316
299
|
## Development
|
|
317
300
|
|
|
318
301
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/RELEASING
CHANGED
|
@@ -2,14 +2,14 @@ Releasing graphql-metrics
|
|
|
2
2
|
|
|
3
3
|
1. Check the Semantic Versioning page for info on how to version the new release: http://semver.org
|
|
4
4
|
2. Update VERSION in lib/graphql/metrics/version.rb
|
|
5
|
-
3.
|
|
6
|
-
4.
|
|
7
|
-
5.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
$ git
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
5
|
+
3. Run `bundle install` so Gemfile.lock picks up the new version
|
|
6
|
+
4. Add a CHANGELOG entry for the new release
|
|
7
|
+
5. Open a pull request with a title like "Packaging for release X.Y.Z" and merge it
|
|
8
|
+
6. Tag the merge commit on main with the version and push the tag
|
|
9
|
+
$ git checkout main && git pull
|
|
10
|
+
$ git tag vX.Y.Z
|
|
11
|
+
$ git push origin vX.Y.Z
|
|
12
|
+
|
|
13
|
+
Pushing the tag runs the Release workflow (.github/workflows/release.yml),
|
|
14
|
+
which publishes the gem to RubyGems.org with trusted publishing and creates
|
|
15
|
+
a GitHub release with generated notes. No RubyGems API key is needed.
|
data/graphql_metrics.gemspec
CHANGED
|
@@ -14,6 +14,8 @@ Gem::Specification.new do |spec|
|
|
|
14
14
|
spec.homepage = 'https://github.com/Shopify/graphql-metrics'
|
|
15
15
|
spec.license = "MIT"
|
|
16
16
|
|
|
17
|
+
spec.required_ruby_version = ">= 3.3"
|
|
18
|
+
|
|
17
19
|
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
|
18
20
|
|
|
19
21
|
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
|
@@ -23,12 +25,10 @@ Gem::Specification.new do |spec|
|
|
|
23
25
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
24
26
|
spec.require_paths = ["lib"]
|
|
25
27
|
|
|
26
|
-
spec.add_runtime_dependency "graphql", ">=
|
|
27
|
-
spec.
|
|
28
|
-
spec.add_development_dependency "
|
|
29
|
-
spec.add_development_dependency "minitest", "~> 5.0"
|
|
28
|
+
spec.add_runtime_dependency "graphql", ">= 2.3"
|
|
29
|
+
spec.add_development_dependency "rake", "~> 13.2"
|
|
30
|
+
spec.add_development_dependency "minitest", "~> 6.0"
|
|
30
31
|
spec.add_development_dependency 'graphql-batch'
|
|
31
|
-
spec.add_development_dependency "activesupport", "~> 5.1.5"
|
|
32
32
|
spec.add_development_dependency "pry"
|
|
33
33
|
spec.add_development_dependency "pry-byebug"
|
|
34
34
|
spec.add_development_dependency "mocha"
|
|
@@ -51,20 +51,13 @@ module GraphQL
|
|
|
51
51
|
path: visitor.response_path,
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
@static_field_metrics << static_metrics
|
|
56
|
-
else
|
|
57
|
-
field_extracted(static_metrics)
|
|
58
|
-
end
|
|
54
|
+
@static_field_metrics << static_metrics
|
|
59
55
|
end
|
|
60
56
|
|
|
61
57
|
def extract_fields(with_runtime_metrics: true)
|
|
62
|
-
return if query.context[SKIP_FIELD_AND_ARGUMENT_METRICS]
|
|
63
|
-
|
|
64
58
|
ns = query.context.namespace(CONTEXT_NAMESPACE)
|
|
65
59
|
|
|
66
60
|
@static_field_metrics.each do |static_metrics|
|
|
67
|
-
|
|
68
61
|
if with_runtime_metrics
|
|
69
62
|
resolver_timings = ns[GraphQL::Metrics::INLINE_FIELD_TIMINGS][static_metrics[:path]]
|
|
70
63
|
lazy_resolver_timings = ns[GraphQL::Metrics::LAZY_FIELD_TIMINGS][static_metrics[:path]]
|
|
@@ -89,14 +82,6 @@ module GraphQL
|
|
|
89
82
|
end
|
|
90
83
|
|
|
91
84
|
def result
|
|
92
|
-
return if GraphQL::Metrics.timings_capture_enabled?(query.context)
|
|
93
|
-
return if query.context[GraphQL::Metrics::SKIP_GRAPHQL_METRICS_ANALYSIS]
|
|
94
|
-
|
|
95
|
-
# NOTE: If we're running as a static analyzer (i.e. not with instrumentation and tracing), we still need to
|
|
96
|
-
# flush static query metrics somewhere other than `after_query`.
|
|
97
|
-
ns = query.context.namespace(CONTEXT_NAMESPACE)
|
|
98
|
-
analyzer = ns[GraphQL::Metrics::ANALYZER_INSTANCE_KEY]
|
|
99
|
-
analyzer.extract_query
|
|
100
85
|
end
|
|
101
86
|
|
|
102
87
|
private
|
|
@@ -145,10 +130,19 @@ module GraphQL
|
|
|
145
130
|
parent_input_object: parent_input_object,
|
|
146
131
|
parent: parent
|
|
147
132
|
)
|
|
133
|
+
|
|
134
|
+
# For nested InputObject arguments, we need to pass the InputObject type as the parent
|
|
135
|
+
# for its child fields to be properly attributed
|
|
136
|
+
parent_for_nested_fields = if argument.value.is_a?(::GraphQL::Schema::InputObject)
|
|
137
|
+
argument.value.class
|
|
138
|
+
else
|
|
139
|
+
parent_input_object
|
|
140
|
+
end
|
|
141
|
+
|
|
148
142
|
extract_arguments(
|
|
149
|
-
argument:argument.value,
|
|
143
|
+
argument: argument.value,
|
|
150
144
|
definition: definition,
|
|
151
|
-
parent_input_object:
|
|
145
|
+
parent_input_object: parent_for_nested_fields,
|
|
152
146
|
parent: parent
|
|
153
147
|
)
|
|
154
148
|
when ::GraphQL::Schema::InputObject
|
|
@@ -2,28 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
module GraphQL
|
|
4
4
|
module Metrics
|
|
5
|
-
|
|
6
|
-
def
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
module Instrumentation
|
|
6
|
+
def initialize(capture_field_timings: true, **_options)
|
|
7
|
+
@capture_field_timings = capture_field_timings
|
|
8
|
+
|
|
9
|
+
super
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def execute_multiplex(multiplex:)
|
|
13
|
+
return super if multiplex.context[GraphQL::Metrics::SKIP_GRAPHQL_METRICS_ANALYSIS]
|
|
14
|
+
|
|
15
|
+
result = nil
|
|
16
|
+
|
|
17
|
+
multiplex.queries.each do |query|
|
|
18
|
+
ns = query.context.namespace(CONTEXT_NAMESPACE)
|
|
19
|
+
ns[GraphQL::Metrics::TIMINGS_CAPTURE_ENABLED] = @capture_field_timings
|
|
20
|
+
ns[GraphQL::Metrics::INLINE_FIELD_TIMINGS] = Hash.new { |h, k| h[k] = [] }
|
|
21
|
+
ns[GraphQL::Metrics::LAZY_FIELD_TIMINGS] = Hash.new { |h, k| h[k] = [] }
|
|
10
22
|
end
|
|
11
23
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
24
|
+
begin
|
|
25
|
+
result = super
|
|
26
|
+
ensure
|
|
27
|
+
multiplex.queries.each do |query|
|
|
28
|
+
handle_query(query) if query.valid?
|
|
29
|
+
end
|
|
30
|
+
end
|
|
17
31
|
|
|
18
|
-
|
|
19
|
-
ns[GraphQL::Metrics::TIMINGS_CAPTURE_ENABLED] = true
|
|
20
|
-
ns[GraphQL::Metrics::INLINE_FIELD_TIMINGS] = {}
|
|
21
|
-
ns[GraphQL::Metrics::LAZY_FIELD_TIMINGS] = {}
|
|
32
|
+
result
|
|
22
33
|
end
|
|
23
34
|
|
|
24
|
-
|
|
25
|
-
return if query.context[GraphQL::Metrics::SKIP_GRAPHQL_METRICS_ANALYSIS]
|
|
35
|
+
private
|
|
26
36
|
|
|
37
|
+
def handle_query(query)
|
|
27
38
|
ns = query.context.namespace(CONTEXT_NAMESPACE)
|
|
28
39
|
analyzer = ns[GraphQL::Metrics::ANALYZER_INSTANCE_KEY]
|
|
29
40
|
|
|
@@ -41,30 +52,19 @@ module GraphQL
|
|
|
41
52
|
runtime_query_metrics = {
|
|
42
53
|
query_start_time: ns[GraphQL::Metrics::QUERY_START_TIME],
|
|
43
54
|
query_duration: query_duration,
|
|
44
|
-
parsing_start_time_offset: ns[GraphQL::Metrics::PARSING_START_TIME_OFFSET],
|
|
45
55
|
parsing_duration: ns[GraphQL::Metrics::PARSING_DURATION],
|
|
46
|
-
validation_start_time_offset: ns[GraphQL::Metrics::VALIDATION_START_TIME_OFFSET],
|
|
47
56
|
validation_duration: ns[GraphQL::Metrics::VALIDATION_DURATION],
|
|
48
|
-
lexing_start_time_offset: ns[GraphQL::Metrics::LEXING_START_TIME_OFFSET],
|
|
49
57
|
lexing_duration: ns[GraphQL::Metrics::LEXING_DURATION],
|
|
50
|
-
analysis_start_time_offset: ns[GraphQL::Metrics::ANALYSIS_START_TIME_OFFSET],
|
|
51
58
|
analysis_duration: ns[GraphQL::Metrics::ANALYSIS_DURATION],
|
|
52
|
-
multiplex_start_time: ns[GraphQL::Metrics::MULTIPLEX_START_TIME],
|
|
53
59
|
}
|
|
54
60
|
|
|
55
|
-
analyzer.extract_fields
|
|
61
|
+
analyzer.extract_fields(with_runtime_metrics: @capture_field_timings)
|
|
56
62
|
analyzer.extract_query(runtime_query_metrics: runtime_query_metrics)
|
|
57
63
|
end
|
|
58
64
|
end
|
|
59
65
|
|
|
60
66
|
private
|
|
61
67
|
|
|
62
|
-
def query_present_and_valid?(query)
|
|
63
|
-
# Check for selected_operation as well for graphql 1.9 compatibility
|
|
64
|
-
# which did not reject "empty" documents in its parser.
|
|
65
|
-
query.valid? && !query.selected_operation.nil?
|
|
66
|
-
end
|
|
67
|
-
|
|
68
68
|
def runtime_metrics_interrupted?(context_namespace)
|
|
69
69
|
# NOTE: The start time stored at `ns[GraphQL::Metrics::QUERY_START_TIME_MONOTONIC]` is captured during query
|
|
70
70
|
# parsing, which occurs before `Instrumentation#before_query`.
|
|
@@ -8,6 +8,8 @@ module GraphQL
|
|
|
8
8
|
|
|
9
9
|
query_or_multiplex = @query || @multiplex
|
|
10
10
|
@skip_tracing = query_or_multiplex.context&.fetch(SKIP_GRAPHQL_METRICS_ANALYSIS, false) if query_or_multiplex
|
|
11
|
+
@parsing_duration = 0.0
|
|
12
|
+
@lexing_duration = 0.0
|
|
11
13
|
end
|
|
12
14
|
|
|
13
15
|
# NOTE: These methods come from the graphql ruby gem and are in "chronological" order based on the phases
|
|
@@ -15,12 +17,6 @@ module GraphQL
|
|
|
15
17
|
# https://github.com/rmosolgo/graphql-ruby/issues/3393). Most of them can be run multiple times when
|
|
16
18
|
# multiplexing multiple queries.
|
|
17
19
|
|
|
18
|
-
# wraps everything below this line; only run once
|
|
19
|
-
def execute_multiplex(multiplex:)
|
|
20
|
-
return super if skip_tracing?(multiplex)
|
|
21
|
-
capture_multiplex_start_time { super }
|
|
22
|
-
end
|
|
23
|
-
|
|
24
20
|
# may not trigger if the query is passed in pre-parsed
|
|
25
21
|
def lex(query_string:)
|
|
26
22
|
return super if @skip_tracing
|
|
@@ -34,139 +30,74 @@ module GraphQL
|
|
|
34
30
|
end
|
|
35
31
|
|
|
36
32
|
def validate(query:, validate:)
|
|
37
|
-
return super if skip_tracing
|
|
33
|
+
return super if @skip_tracing
|
|
38
34
|
capture_validation_time(query.context) { super }
|
|
39
35
|
end
|
|
40
36
|
|
|
41
|
-
# wraps all `analyze_query`s; only run once
|
|
42
|
-
def analyze_multiplex(multiplex:)
|
|
43
|
-
return super if skip_tracing?(multiplex)
|
|
44
|
-
# Ensures that we reset potentially long-lived PreContext objects between multiplexs. We reset at this point
|
|
45
|
-
# since all parsing and validation will be done by this point, and a GraphQL::Query::Context will exist.
|
|
46
|
-
pre_context.reset
|
|
47
|
-
super
|
|
48
|
-
end
|
|
49
|
-
|
|
50
37
|
def analyze_query(query:)
|
|
51
|
-
return super if skip_tracing
|
|
38
|
+
return super if @skip_tracing
|
|
52
39
|
capture_analysis_time(query.context) { super }
|
|
53
40
|
end
|
|
54
41
|
|
|
55
42
|
def execute_query(query:)
|
|
56
|
-
return super if skip_tracing
|
|
43
|
+
return super if @skip_tracing
|
|
57
44
|
capture_query_start_time(query.context) { super }
|
|
58
45
|
end
|
|
59
46
|
|
|
60
47
|
def execute_field(field:, query:, ast_node:, arguments:, object:)
|
|
61
|
-
return super if skip_tracing
|
|
62
|
-
return super unless
|
|
48
|
+
return super if @skip_tracing || query.context[SKIP_FIELD_AND_ARGUMENT_METRICS]
|
|
49
|
+
return super unless capture_field_timings?(query.context)
|
|
63
50
|
trace_field(GraphQL::Metrics::INLINE_FIELD_TIMINGS, query) { super }
|
|
64
51
|
end
|
|
65
52
|
|
|
66
53
|
def execute_field_lazy(field:, query:, ast_node:, arguments:, object:)
|
|
67
|
-
return super if skip_tracing
|
|
68
|
-
return super unless
|
|
54
|
+
return super if @skip_tracing || query.context[SKIP_FIELD_AND_ARGUMENT_METRICS]
|
|
55
|
+
return super unless capture_field_timings?(query.context)
|
|
69
56
|
trace_field(GraphQL::Metrics::LAZY_FIELD_TIMINGS, query) { super }
|
|
70
57
|
end
|
|
71
58
|
|
|
72
59
|
private
|
|
73
60
|
|
|
74
|
-
def
|
|
75
|
-
if !defined?(@
|
|
76
|
-
@
|
|
61
|
+
def capture_field_timings?(context)
|
|
62
|
+
if !defined?(@capture_field_timings)
|
|
63
|
+
@capture_field_timings = !!context.namespace(CONTEXT_NAMESPACE)[TIMINGS_CAPTURE_ENABLED]
|
|
77
64
|
end
|
|
78
65
|
|
|
79
|
-
@
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
PreContext = Struct.new(
|
|
83
|
-
:multiplex_start_time,
|
|
84
|
-
:multiplex_start_time_monotonic,
|
|
85
|
-
:parsing_start_time_offset,
|
|
86
|
-
:parsing_duration,
|
|
87
|
-
:lexing_start_time_offset,
|
|
88
|
-
:lexing_duration
|
|
89
|
-
) do
|
|
90
|
-
def reset
|
|
91
|
-
self[:multiplex_start_time] = nil
|
|
92
|
-
self[:multiplex_start_time_monotonic] = nil
|
|
93
|
-
self[:parsing_start_time_offset] = nil
|
|
94
|
-
self[:parsing_duration] = nil
|
|
95
|
-
self[:lexing_start_time_offset] = nil
|
|
96
|
-
self[:lexing_duration] = nil
|
|
97
|
-
end
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
def pre_context
|
|
101
|
-
# NOTE: This is used to store timings from lexing, parsing, validation, before we have a context to store
|
|
102
|
-
# values in. Uses thread-safe Concurrent::ThreadLocalVar to store a set of values per thread.
|
|
103
|
-
@pre_context ||= Concurrent::ThreadLocalVar.new(PreContext.new)
|
|
104
|
-
@pre_context.value
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
def capture_multiplex_start_time
|
|
108
|
-
pre_context.multiplex_start_time = GraphQL::Metrics.current_time
|
|
109
|
-
pre_context.multiplex_start_time_monotonic = GraphQL::Metrics.current_time_monotonic
|
|
110
|
-
|
|
111
|
-
yield
|
|
66
|
+
@capture_field_timings
|
|
112
67
|
end
|
|
113
68
|
|
|
114
69
|
def capture_lexing_time
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
pre_context.lexing_duration = timed_result.duration
|
|
119
|
-
|
|
120
|
-
timed_result.result
|
|
70
|
+
result, duration = GraphQL::Metrics.time { yield }
|
|
71
|
+
@lexing_duration = duration
|
|
72
|
+
result
|
|
121
73
|
end
|
|
122
74
|
|
|
123
75
|
def capture_parsing_time
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
pre_context.parsing_duration = timed_result.duration
|
|
128
|
-
|
|
129
|
-
timed_result.result
|
|
76
|
+
result, duration = GraphQL::Metrics.time { yield }
|
|
77
|
+
@parsing_duration = duration
|
|
78
|
+
result
|
|
130
79
|
end
|
|
131
80
|
|
|
132
|
-
# Also consolidates parsing timings (if any)
|
|
81
|
+
# Also consolidates parsing timings (if any)
|
|
133
82
|
def capture_validation_time(context)
|
|
134
|
-
|
|
135
|
-
# If we don't have those values, use some sane defaults.
|
|
136
|
-
if pre_context.lexing_duration.nil?
|
|
137
|
-
pre_context.lexing_start_time_offset = pre_context.multiplex_start_time
|
|
138
|
-
pre_context.lexing_duration = 0.0
|
|
139
|
-
end
|
|
140
|
-
if pre_context.parsing_duration.nil?
|
|
141
|
-
pre_context.parsing_start_time_offset = pre_context.multiplex_start_time
|
|
142
|
-
pre_context.parsing_duration = 0.0
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
timed_result = GraphQL::Metrics.time(pre_context.multiplex_start_time_monotonic) { yield }
|
|
83
|
+
result, duration = GraphQL::Metrics.time { yield }
|
|
146
84
|
|
|
147
85
|
ns = context.namespace(CONTEXT_NAMESPACE)
|
|
86
|
+
ns[LEXING_DURATION] = @lexing_duration
|
|
87
|
+
ns[PARSING_DURATION] = @parsing_duration
|
|
88
|
+
ns[VALIDATION_DURATION] = duration
|
|
148
89
|
|
|
149
|
-
|
|
150
|
-
ns[MULTIPLEX_START_TIME_MONOTONIC] = pre_context.multiplex_start_time_monotonic
|
|
151
|
-
ns[LEXING_START_TIME_OFFSET] = pre_context.lexing_start_time_offset
|
|
152
|
-
ns[LEXING_DURATION] = pre_context.lexing_duration
|
|
153
|
-
ns[PARSING_START_TIME_OFFSET] = pre_context.parsing_start_time_offset
|
|
154
|
-
ns[PARSING_DURATION] = pre_context.parsing_duration
|
|
155
|
-
ns[VALIDATION_START_TIME_OFFSET] = timed_result.time_since_offset
|
|
156
|
-
ns[VALIDATION_DURATION] = timed_result.duration
|
|
157
|
-
|
|
158
|
-
timed_result.result
|
|
90
|
+
result
|
|
159
91
|
end
|
|
160
92
|
|
|
161
93
|
def capture_analysis_time(context)
|
|
162
94
|
ns = context.namespace(CONTEXT_NAMESPACE)
|
|
163
95
|
|
|
164
|
-
|
|
96
|
+
result, duration = GraphQL::Metrics.time { yield }
|
|
165
97
|
|
|
166
|
-
ns[
|
|
167
|
-
ns[ANALYSIS_DURATION] = timed_result.duration
|
|
98
|
+
ns[ANALYSIS_DURATION] = duration
|
|
168
99
|
|
|
169
|
-
|
|
100
|
+
result
|
|
170
101
|
end
|
|
171
102
|
|
|
172
103
|
def capture_query_start_time(context)
|
|
@@ -179,20 +110,13 @@ module GraphQL
|
|
|
179
110
|
|
|
180
111
|
def trace_field(context_key, query)
|
|
181
112
|
ns = query.context.namespace(CONTEXT_NAMESPACE)
|
|
182
|
-
offset_time = ns[GraphQL::Metrics::QUERY_START_TIME_MONOTONIC]
|
|
183
|
-
start_time = GraphQL::Metrics.current_time_monotonic
|
|
184
113
|
path = query.context[:current_path]
|
|
185
114
|
|
|
186
|
-
result = yield
|
|
187
|
-
|
|
188
|
-
duration = GraphQL::Metrics.current_time_monotonic - start_time
|
|
189
|
-
time_since_offset = start_time - offset_time if offset_time
|
|
115
|
+
result, duration = GraphQL::Metrics.time { yield }
|
|
190
116
|
|
|
191
117
|
path_excluding_numeric_indicies = path.select { |p| p.is_a?(String) }
|
|
192
118
|
ns[context_key][path_excluding_numeric_indicies] ||= []
|
|
193
|
-
ns[context_key][path_excluding_numeric_indicies] <<
|
|
194
|
-
start_time_offset: time_since_offset, duration: duration
|
|
195
|
-
}
|
|
119
|
+
ns[context_key][path_excluding_numeric_indicies] << duration
|
|
196
120
|
|
|
197
121
|
result
|
|
198
122
|
end
|
data/lib/graphql/metrics.rb
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
require "graphql/metrics/tracer"
|
|
8
|
-
require "graphql/metrics/analyzer"
|
|
3
|
+
require_relative "metrics/version"
|
|
4
|
+
require_relative "metrics/instrumentation"
|
|
5
|
+
require_relative "metrics/trace"
|
|
6
|
+
require_relative "metrics/analyzer"
|
|
9
7
|
|
|
10
8
|
module GraphQL
|
|
11
9
|
module Metrics
|
|
@@ -27,22 +25,13 @@ module GraphQL
|
|
|
27
25
|
MULTIPLEX_START_TIME_MONOTONIC = :multiplex_start_time_monotonic
|
|
28
26
|
QUERY_START_TIME = :query_start_time
|
|
29
27
|
QUERY_START_TIME_MONOTONIC = :query_start_time_monotonic
|
|
30
|
-
LEXING_START_TIME_OFFSET = :lexing_start_time_offset
|
|
31
28
|
LEXING_DURATION = :lexing_duration
|
|
32
|
-
PARSING_START_TIME_OFFSET = :parsing_start_time_offset
|
|
33
29
|
PARSING_DURATION = :parsing_duration
|
|
34
|
-
VALIDATION_START_TIME_OFFSET = :validation_start_time_offset
|
|
35
30
|
VALIDATION_DURATION = :validation_duration
|
|
36
|
-
ANALYSIS_START_TIME_OFFSET = :analysis_start_time_offset
|
|
37
31
|
ANALYSIS_DURATION = :analysis_duration
|
|
38
32
|
INLINE_FIELD_TIMINGS = :inline_field_timings
|
|
39
33
|
LAZY_FIELD_TIMINGS = :lazy_field_timings
|
|
40
34
|
|
|
41
|
-
def self.timings_capture_enabled?(context)
|
|
42
|
-
return false unless context
|
|
43
|
-
!!context.namespace(CONTEXT_NAMESPACE)[TIMINGS_CAPTURE_ENABLED]
|
|
44
|
-
end
|
|
45
|
-
|
|
46
35
|
def self.current_time
|
|
47
36
|
Process.clock_gettime(Process::CLOCK_REALTIME)
|
|
48
37
|
end
|
|
@@ -51,40 +40,32 @@ module GraphQL
|
|
|
51
40
|
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
52
41
|
end
|
|
53
42
|
|
|
54
|
-
def self.time
|
|
43
|
+
def self.time
|
|
55
44
|
start_time = current_time_monotonic
|
|
56
45
|
result = yield
|
|
57
46
|
duration = current_time_monotonic - start_time
|
|
58
|
-
|
|
59
|
-
TimedResult.new(start_time, duration, time_since_offset, result)
|
|
47
|
+
[result, duration]
|
|
60
48
|
end
|
|
61
49
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
# < start offset >
|
|
73
|
-
# |------------------|----------|--------->
|
|
74
|
-
# OS (t=0) FS (t=1) FE (t=2)
|
|
75
|
-
#
|
|
76
|
-
# OS = Operation start time
|
|
77
|
-
# FS = Field resolver start time
|
|
78
|
-
# FE = Field resolver end time
|
|
79
|
-
#
|
|
80
|
-
attr_reader :result, :start_time, :duration, :time_since_offset
|
|
81
|
-
|
|
82
|
-
def initialize(start_time, duration, time_since_offset, result)
|
|
83
|
-
@start_time = start_time
|
|
84
|
-
@duration = duration
|
|
85
|
-
@time_since_offset = time_since_offset
|
|
86
|
-
@result = result
|
|
50
|
+
def self.use(
|
|
51
|
+
schema_defn,
|
|
52
|
+
analyzer:,
|
|
53
|
+
tracer: GraphQL::Metrics::Trace,
|
|
54
|
+
capture_timings: nil,
|
|
55
|
+
capture_field_timings: nil,
|
|
56
|
+
trace_mode: :default
|
|
57
|
+
)
|
|
58
|
+
if capture_field_timings && !capture_timings
|
|
59
|
+
raise ArgumentError, "Cannot capture field timings without capturing query timings"
|
|
87
60
|
end
|
|
61
|
+
|
|
62
|
+
capture_timings = true if capture_timings.nil?
|
|
63
|
+
capture_field_timings = true if capture_field_timings.nil?
|
|
64
|
+
capture_field_timings = false if !capture_timings
|
|
65
|
+
|
|
66
|
+
schema_defn.trace_with(GraphQL::Metrics::Instrumentation, capture_field_timings: capture_field_timings)
|
|
67
|
+
schema_defn.trace_with(tracer, mode: trace_mode) if capture_timings
|
|
68
|
+
schema_defn.query_analyzer(analyzer)
|
|
88
69
|
end
|
|
89
70
|
end
|
|
90
71
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: graphql-metrics
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 6.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Christopher Butcher
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: graphql
|
|
@@ -16,56 +15,42 @@ dependencies:
|
|
|
16
15
|
requirements:
|
|
17
16
|
- - ">="
|
|
18
17
|
- !ruby/object:Gem::Version
|
|
19
|
-
version:
|
|
18
|
+
version: '2.3'
|
|
20
19
|
type: :runtime
|
|
21
20
|
prerelease: false
|
|
22
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
22
|
requirements:
|
|
24
23
|
- - ">="
|
|
25
24
|
- !ruby/object:Gem::Version
|
|
26
|
-
version:
|
|
27
|
-
- !ruby/object:Gem::Dependency
|
|
28
|
-
name: concurrent-ruby
|
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
|
30
|
-
requirements:
|
|
31
|
-
- - ">="
|
|
32
|
-
- !ruby/object:Gem::Version
|
|
33
|
-
version: 1.1.0
|
|
34
|
-
type: :runtime
|
|
35
|
-
prerelease: false
|
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
-
requirements:
|
|
38
|
-
- - ">="
|
|
39
|
-
- !ruby/object:Gem::Version
|
|
40
|
-
version: 1.1.0
|
|
25
|
+
version: '2.3'
|
|
41
26
|
- !ruby/object:Gem::Dependency
|
|
42
27
|
name: rake
|
|
43
28
|
requirement: !ruby/object:Gem::Requirement
|
|
44
29
|
requirements:
|
|
45
30
|
- - "~>"
|
|
46
31
|
- !ruby/object:Gem::Version
|
|
47
|
-
version: '
|
|
32
|
+
version: '13.2'
|
|
48
33
|
type: :development
|
|
49
34
|
prerelease: false
|
|
50
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
36
|
requirements:
|
|
52
37
|
- - "~>"
|
|
53
38
|
- !ruby/object:Gem::Version
|
|
54
|
-
version: '
|
|
39
|
+
version: '13.2'
|
|
55
40
|
- !ruby/object:Gem::Dependency
|
|
56
41
|
name: minitest
|
|
57
42
|
requirement: !ruby/object:Gem::Requirement
|
|
58
43
|
requirements:
|
|
59
44
|
- - "~>"
|
|
60
45
|
- !ruby/object:Gem::Version
|
|
61
|
-
version: '
|
|
46
|
+
version: '6.0'
|
|
62
47
|
type: :development
|
|
63
48
|
prerelease: false
|
|
64
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
50
|
requirements:
|
|
66
51
|
- - "~>"
|
|
67
52
|
- !ruby/object:Gem::Version
|
|
68
|
-
version: '
|
|
53
|
+
version: '6.0'
|
|
69
54
|
- !ruby/object:Gem::Dependency
|
|
70
55
|
name: graphql-batch
|
|
71
56
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -80,20 +65,6 @@ dependencies:
|
|
|
80
65
|
- - ">="
|
|
81
66
|
- !ruby/object:Gem::Version
|
|
82
67
|
version: '0'
|
|
83
|
-
- !ruby/object:Gem::Dependency
|
|
84
|
-
name: activesupport
|
|
85
|
-
requirement: !ruby/object:Gem::Requirement
|
|
86
|
-
requirements:
|
|
87
|
-
- - "~>"
|
|
88
|
-
- !ruby/object:Gem::Version
|
|
89
|
-
version: 5.1.5
|
|
90
|
-
type: :development
|
|
91
|
-
prerelease: false
|
|
92
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
-
requirements:
|
|
94
|
-
- - "~>"
|
|
95
|
-
- !ruby/object:Gem::Version
|
|
96
|
-
version: 5.1.5
|
|
97
68
|
- !ruby/object:Gem::Dependency
|
|
98
69
|
name: pry
|
|
99
70
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -201,7 +172,10 @@ executables: []
|
|
|
201
172
|
extensions: []
|
|
202
173
|
extra_rdoc_files: []
|
|
203
174
|
files:
|
|
175
|
+
- ".github/dependabot.yml"
|
|
204
176
|
- ".github/workflows/cla.yml"
|
|
177
|
+
- ".github/workflows/dependabot_auto_merge.yml"
|
|
178
|
+
- ".github/workflows/release.yml"
|
|
205
179
|
- ".github/workflows/ruby.yml"
|
|
206
180
|
- ".gitignore"
|
|
207
181
|
- ".rubocop-http---shopify-github-io-ruby-style-guide-rubocop-yml"
|
|
@@ -210,28 +184,26 @@ files:
|
|
|
210
184
|
- CHANGELOG.md
|
|
211
185
|
- CODE_OF_CONDUCT.md
|
|
212
186
|
- Gemfile
|
|
187
|
+
- Gemfile.lock
|
|
213
188
|
- LICENSE.txt
|
|
214
189
|
- README.md
|
|
215
190
|
- RELEASING
|
|
216
191
|
- Rakefile
|
|
217
192
|
- bin/console
|
|
218
193
|
- bin/setup
|
|
219
|
-
- gemfiles/
|
|
220
|
-
- gemfiles/graphql_2.0.gemfile
|
|
194
|
+
- gemfiles/graphql_2.3.gemfile
|
|
221
195
|
- gemfiles/graphql_head.gemfile
|
|
222
196
|
- graphql_metrics.gemspec
|
|
223
197
|
- lib/graphql/metrics.rb
|
|
224
198
|
- lib/graphql/metrics/analyzer.rb
|
|
225
199
|
- lib/graphql/metrics/instrumentation.rb
|
|
226
200
|
- lib/graphql/metrics/trace.rb
|
|
227
|
-
- lib/graphql/metrics/tracer.rb
|
|
228
201
|
- lib/graphql/metrics/version.rb
|
|
229
202
|
homepage: https://github.com/Shopify/graphql-metrics
|
|
230
203
|
licenses:
|
|
231
204
|
- MIT
|
|
232
205
|
metadata:
|
|
233
206
|
allowed_push_host: https://rubygems.org
|
|
234
|
-
post_install_message:
|
|
235
207
|
rdoc_options: []
|
|
236
208
|
require_paths:
|
|
237
209
|
- lib
|
|
@@ -239,15 +211,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
239
211
|
requirements:
|
|
240
212
|
- - ">="
|
|
241
213
|
- !ruby/object:Gem::Version
|
|
242
|
-
version: '
|
|
214
|
+
version: '3.3'
|
|
243
215
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
244
216
|
requirements:
|
|
245
217
|
- - ">="
|
|
246
218
|
- !ruby/object:Gem::Version
|
|
247
219
|
version: '0'
|
|
248
220
|
requirements: []
|
|
249
|
-
rubygems_version:
|
|
250
|
-
signing_key:
|
|
221
|
+
rubygems_version: 4.0.20
|
|
251
222
|
specification_version: 4
|
|
252
223
|
summary: GraphQL Metrics Extractor
|
|
253
224
|
test_files: []
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module GraphQL
|
|
4
|
-
module Metrics
|
|
5
|
-
class Tracer
|
|
6
|
-
# NOTE: These constants come from the graphql ruby gem and are in "chronological" order based on the phases
|
|
7
|
-
# of execution of the graphql-ruby gem, though old versions of the gem aren't always consistent about this (see
|
|
8
|
-
# https://github.com/rmosolgo/graphql-ruby/issues/3393). Most of them can be run multiple times when
|
|
9
|
-
# multiplexing multiple queries.
|
|
10
|
-
GRAPHQL_GEM_EXECUTE_MULTIPLEX_KEY = 'execute_multiplex' # wraps everything below this line; only run once
|
|
11
|
-
GRAPHQL_GEM_LEXING_KEY = 'lex' # may not trigger if the query is passed in pre-parsed
|
|
12
|
-
GRAPHQL_GEM_PARSING_KEY = 'parse' # may not trigger if the query is passed in pre-parsed
|
|
13
|
-
GRAPHQL_GEM_VALIDATION_KEY = 'validate'
|
|
14
|
-
GRAPHQL_GEM_ANALYZE_MULTIPLEX_KEY = 'analyze_multiplex' # wraps all `analyze_query`s; only run once
|
|
15
|
-
GRAPHQL_GEM_ANALYZE_QUERY_KEY = 'analyze_query'
|
|
16
|
-
GRAPHQL_GEM_EXECUTE_QUERY_KEY = 'execute_query'
|
|
17
|
-
GRAPHQL_GEM_TRACING_FIELD_KEYS = [
|
|
18
|
-
GRAPHQL_GEM_TRACING_FIELD_KEY = 'execute_field',
|
|
19
|
-
GRAPHQL_GEM_TRACING_LAZY_FIELD_KEY = 'execute_field_lazy'
|
|
20
|
-
]
|
|
21
|
-
|
|
22
|
-
include GraphQL::Metrics::Trace
|
|
23
|
-
|
|
24
|
-
def initialize
|
|
25
|
-
# no-op, but don't want the behavior from GraphQL::Metrics::Trace
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def trace(key, data, &block)
|
|
29
|
-
# NOTE: Context doesn't exist yet during lexing, parsing.
|
|
30
|
-
context = data[:query]&.context
|
|
31
|
-
skip_tracing = context&.fetch(GraphQL::Metrics::SKIP_GRAPHQL_METRICS_ANALYSIS, false)
|
|
32
|
-
return yield if skip_tracing
|
|
33
|
-
|
|
34
|
-
case key
|
|
35
|
-
when GRAPHQL_GEM_EXECUTE_MULTIPLEX_KEY
|
|
36
|
-
return capture_multiplex_start_time(&block)
|
|
37
|
-
when GRAPHQL_GEM_LEXING_KEY
|
|
38
|
-
return capture_lexing_time(&block)
|
|
39
|
-
when GRAPHQL_GEM_PARSING_KEY
|
|
40
|
-
return capture_parsing_time(&block)
|
|
41
|
-
when GRAPHQL_GEM_VALIDATION_KEY
|
|
42
|
-
return capture_validation_time(context, &block)
|
|
43
|
-
when GRAPHQL_GEM_ANALYZE_MULTIPLEX_KEY
|
|
44
|
-
# Ensures that we reset potentially long-lived PreContext objects between multiplexs. We reset at this point
|
|
45
|
-
# since all parsing and validation will be done by this point, and a GraphQL::Query::Context will exist.
|
|
46
|
-
pre_context.reset
|
|
47
|
-
return yield
|
|
48
|
-
when GRAPHQL_GEM_ANALYZE_QUERY_KEY
|
|
49
|
-
return capture_analysis_time(context, &block)
|
|
50
|
-
when GRAPHQL_GEM_EXECUTE_QUERY_KEY
|
|
51
|
-
capture_query_start_time(context, &block)
|
|
52
|
-
when *GRAPHQL_GEM_TRACING_FIELD_KEYS
|
|
53
|
-
return yield if context[SKIP_FIELD_AND_ARGUMENT_METRICS]
|
|
54
|
-
return yield unless GraphQL::Metrics.timings_capture_enabled?(data[:query].context)
|
|
55
|
-
|
|
56
|
-
context_key = case key
|
|
57
|
-
when GRAPHQL_GEM_TRACING_FIELD_KEY
|
|
58
|
-
GraphQL::Metrics::INLINE_FIELD_TIMINGS
|
|
59
|
-
when GRAPHQL_GEM_TRACING_LAZY_FIELD_KEY
|
|
60
|
-
GraphQL::Metrics::LAZY_FIELD_TIMINGS
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
trace_field(context_key, data[:query], &block)
|
|
64
|
-
else
|
|
65
|
-
return yield
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
end
|