GraphQL Metrics
Extract as much detail as you want from GraphQL queries, served up from your Ruby app and the graphql
gem.
Compatible with the graphql-batch
gem, to extract batch-loaded fields resolution timings.
Be sure to read the CHANGELOG to stay updated on feature additions, breaking changes made to this gem.
Installation
Add this line to your application's Gemfile:
gem 'graphql-metrics'
You can require it with in your code as needed with:
require 'graphql/metrics'
Or globally in the Gemfile with:
gem 'graphql-metrics', require: 'graphql/metrics'
And then execute:
$ bundle
Or install it yourself as:
$ gem install graphql-metrics
Usage
Get started by defining your own Analyzer, inheriting from GraphQL::Metrics::Analyzer
.
The following analyzer demonstrates a simple way to capture commonly used metrics sourced from key parts of your schema
definition, the query document being served, as well as runtime query and resolver timings. In this toy example, all of
this data is simply stored on the GraphQL::Query
context, under a namespace to avoid collisions with other analyzers
etc.
What you do with these captured metrics is up to you!
NOTE: If any non-graphql-ruby
gem-related exceptions occur in your application during query document
parsing and validation, runtime metrics for queries (like query_duration
, etc.) as well as field
resolver timings (like resolver_timings
, lazy_resolver_timings
) may not be present in the extracted metrics
hash,
even if you opt to collect them.
Define your own analyzer subclass
class SimpleAnalyzer < GraphQL::Metrics::Analyzer
ANALYZER_NAMESPACE = :simple_analyzer_namespace
def initialize(query_or_multiplex)
super
ns = query.context.namespace(ANALYZER_NAMESPACE)
ns[:simple_extractor_results] = {}
end
def query_extracted(metrics)
custom_metrics_from_context = {
request_id: query.context[:request_id],
}
store_metrics(:queries, metrics.merge(custom_metrics_from_context))
end
def field_extracted(metrics)
store_metrics(:fields, metrics)
end
def directive_extracted(metrics)
store_metrics(:directives, metrics)
end
def argument_extracted(metrics)
store_metrics(:arguments, metrics)
end
private
def store_metrics(context_key, metrics)
ns = query.context.namespace(ANALYZER_NAMESPACE)
ns[:simple_extractor_results][context_key] ||= []
ns[:simple_extractor_results][context_key] << metrics
end
end
Once defined, you can opt into capturing all metrics seen above by simply including GraphQL::Metrics
as a plugin on your
schema.
Metrics that are captured for arguments for fields and directives
Example query:
query PostDetails($postId: ID!, $commentsTags: [String!] = null, $val: Int!) @customDirective(val: $val) {
post(id: $postId) {
title @skip(if: true)
comments(ids: [1, 2], tags: $commentsTags) {
id
body
}
}
}
These are some of the arguments that are extracted
{
argument_name: "if",
argument_type_name: "Boolean",
parent_name: "skip",
grandparent_type_name: "__Directive",
grandparent_node_name: "title",
default_used: false,
value_is_null: false,
value: <GraphQL::Execution::Interpreter::ArgumentValue>
}, {
argument_name: "id",
argument_name: "ids",
argument_type_name: "ID",
parent_name: "comments",
grandparent_type_name: "Post",
grandparent_node_name: "post",
default_used: false,
value_is_null: false,
value: <GraphQL::Execution::Interpreter::ArgumentValue>
}, {
argument_name: "id",
argument_type_name: "ID",
parent_name: "post",
grandparent_type_name: "QueryRoot",
grandparent_node_name: "query",
parent_input_object_type: nil,
default_used: false,
value_is_null: false,
value: <GraphQL::Execution::Interpreter::ArgumentValue>
}, {
argument_name: "val",
argument_type_name: "Int",
parent_name: "customDirective",
grandparent_type_name: "__Directive",
grandparent_node_name: "query",
parent_input_object_type: nil,
default_used: false,
value_is_null: false,
value: <GraphQL::Execution::Interpreter::ArgumentValue>
}
Make use of your analyzer
Add the GraphQL::Metrics
plugin to your schema. This opts you in to capturing all static and runtime metrics seen above.
class Schema < GraphQL::Schema
query QueryRoot
mutation MutationRoot
use GraphQL::Metrics, analyzer: SimpleAnalyzer
end
Optionally, only gather static metrics
If you don't care to capture field timings metrics, they can be disabled with the capture_field_timings
option:
class Schema < GraphQL::Schema
query QueryRoot
mutation MutationRoot
use GraphQL::Metrics, analyzer: SimpleAnalyzer, capture_field_timings: false
end
Your analyzer will still be called with query_extracted
, field_extracted
, but with timings metrics omitted.
argument_extracted
will work exactly the same.
And if you want to disable tracing metrics entirely, use the capture_timings
option:
class Schema < GraphQL::Schema
query QueryRoot
mutation MutationRoot
use GraphQL::Metrics, analyzer: SimpleAnalyzer, capture_timings: false
end
Order of execution
Because of the structure of graphql-ruby's plugin architecture, it may be difficult to build an intuition around the
order in which methods defined on GraphQL::Metrics::Instrumentation
, GraphQL::Metrics::Trace
and subclasses of
GraphQL::Metrics::Analyzer
run.
Although you ideally will not need to care about these details if you are simply using this gem to gather metrics in
your application as intended, here's a breakdown of the order of execution of the methods involved:
When used as instrumentation, an analyzer and tracing, the order of execution is usually:
- Instrumentation.execute_multiplex (context setup)
- Trace.capture_lexing_time
- Trace.capture_parsing_time
- Trace.capture_validation_time
- Trace.capture_analysis_time
- Analyzer#initialize (bit more context setup, instance vars setup)
- Analyzer#result
- Trace.capture_query_start_time
- Trace.trace_field (n times)
- Instrumentation.execute_multiplex (call query and field callbacks, now that we have all static and runtime metrics gathered)
- Analyzer#extract_query
- Analyzer#query_extracted
- Analyzer#extract_fields_with_runtime_metrics
- calls Analyzer#field_extracted n times
Development
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.
To install this gem onto your local machine, run bundle exec rake install
. To release a new version, update the version number in version.rb
, and then run bundle exec rake release
, which will create a git tag for the version, push git commits and tags, and push the .gem
file to rubygems.org.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/Shopify/graphql-metrics. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.
License
The gem is available as open source under the terms of the MIT License.
Code of Conduct
Everyone interacting in the GraphQL::Metrics project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.