From 29e1b69f06d0cc221890c0416500cd2865fd785f Mon Sep 17 00:00:00 2001 From: Cameron Matheson Date: Mon, 26 Feb 2018 16:05:10 -0700 Subject: [PATCH] graphql: data dog instrumentation closes RECNVS-325 Test plan: * do something to monitor statsd traffic (maybe `nc -kul 8125`?) * queries written in graphiql should not be instrumented * student context card queries should be logged Change-Id: Ie86075b739df36e49b5f9ebcf320e50f8c140ecc Reviewed-on: https://gerrit.instructure.com/141965 Tested-by: Jenkins Reviewed-by: Jonathan Featherstone QA-Review: Collin Parrish Product-Review: Cameron Matheson --- Gemfile.d/app.rb | 1 + app/controllers/graphql_controller.rb | 12 +++++++- app/graphql/tracers/datadog_tracer.rb | 28 +++++++++++++++++++ .../GraphQLStudentContextTray.js | 1 + spec/controllers/graphql_controller_spec.rb | 13 +++++++++ 5 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 app/graphql/tracers/datadog_tracer.rb diff --git a/Gemfile.d/app.rb b/Gemfile.d/app.rb index 5edfafca99b..9bcc05c8ca3 100644 --- a/Gemfile.d/app.rb +++ b/Gemfile.d/app.rb @@ -163,3 +163,4 @@ gem 'twitter', path: 'gems/twitter' gem 'vericite_api', '1.5.3' gem 'utf8_cleaner', path: 'gems/utf8_cleaner' gem 'workflow', path: 'gems/workflow' +gem 'dogstatsd-ruby', '3.3.0' diff --git a/app/controllers/graphql_controller.rb b/app/controllers/graphql_controller.rb index f07f2aa1675..a70cfd42f1e 100644 --- a/app/controllers/graphql_controller.rb +++ b/app/controllers/graphql_controller.rb @@ -4,22 +4,32 @@ class GraphQLController < ApplicationController before_action :require_user, except: :execute before_action :require_graphql_feature_flag + def execute query = params[:query] variables = params[:variables] || {} + tracers = if request.headers["GraphQL-Metrics"] == "true" + domain = request.host_with_port.sub(':', '_') + [Tracers::DatadogTracer.new(domain)] + else + [] + end context = { current_user: @current_user, session: session, request: request, + tracers: tracers } + result = nil ActiveRecord::Base.transaction do timeout = Integer(Setting.get('graphql_statement_timeout', '60_000')) ActiveRecord::Base.connection.execute "SET statement_timeout = #{timeout}" result = CanvasSchema.execute(query, variables: variables, context: context) - render json: result end + + render json: result end def graphiql diff --git a/app/graphql/tracers/datadog_tracer.rb b/app/graphql/tracers/datadog_tracer.rb new file mode 100644 index 00000000000..6a38b7d496c --- /dev/null +++ b/app/graphql/tracers/datadog_tracer.rb @@ -0,0 +1,28 @@ +require 'datadog/statsd' + +module Tracers + class DatadogTracer + def initialize(domain) + @domain = domain + @statsd = Datadog::Statsd.new('localhost', 8125) + end + + def trace(key, metadata) + if key == "execute_query" + @statsd.batch do |statsd| + query_name = metadata[:query].operation_name || "unnamed" + tags = [ + "query_md5:#{Digest::MD5.hexdigest(metadata[:query].query_string)}", + "domain:#@domain", + ] + statsd.increment("graphql.#{query_name}.count", tags: tags) + statsd.time("graphql.#{query_name}.time", tags: tags) do + yield + end + end + else + yield + end + end + end +end diff --git a/app/jsx/context_cards/GraphQLStudentContextTray.js b/app/jsx/context_cards/GraphQLStudentContextTray.js index 349805fc2dc..949a661ddad 100644 --- a/app/jsx/context_cards/GraphQLStudentContextTray.js +++ b/app/jsx/context_cards/GraphQLStudentContextTray.js @@ -10,6 +10,7 @@ const client = new ApolloClient({ opts: { headers: { 'X-Requested-With': 'XMLHttpRequest', + 'GraphQL-Metrics': true, 'X-CSRF-Token': $.cookie('_csrf_token') // TODO: probably need to move this io a middleware (http://dev.apollodata.com/core/network.html) }, credentials: 'same-origin' diff --git a/spec/controllers/graphql_controller_spec.rb b/spec/controllers/graphql_controller_spec.rb index dc577e5057e..541ba4bd351 100644 --- a/spec/controllers/graphql_controller_spec.rb +++ b/spec/controllers/graphql_controller_spec.rb @@ -62,5 +62,18 @@ describe GraphQLController do post :execute, params: {query: "{}"} expect(JSON.parse(response.body)["errors"]).not_to be_blank end + + context "data dog metrics" do + it "reports data dog metrics if requested" do + expect_any_instance_of(Tracers::DatadogTracer).to receive :trace + request.headers["GraphQL-Metrics"] = "true" + post :execute, params: {query: '{legacyNode(User, 1) { id }'} + end + + it "doesn't report normally" do + expect_any_instance_of(Tracers::DatadogTracer).not_to receive :trace + post :execute, params: {query: '{legacyNode(User, 1) { id }'} + end + end end end