From 0aa58620485cb9e1e50a441bb02b8fa5cacae1ea Mon Sep 17 00:00:00 2001 From: Arya Bhimani Date: Mon, 28 Oct 2024 14:56:36 -0400 Subject: [PATCH 01/23] fix: create a new BoundedQueue for Hive --- lib/graphql-hive/bounded_queue.rb | 22 ++++++++++++ .../graphql-hive/bounded_queue_spec.rb | 35 +++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 lib/graphql-hive/bounded_queue.rb create mode 100644 spec/graphql/graphql-hive/bounded_queue_spec.rb diff --git a/lib/graphql-hive/bounded_queue.rb b/lib/graphql-hive/bounded_queue.rb new file mode 100644 index 0000000..5cd2d3e --- /dev/null +++ b/lib/graphql-hive/bounded_queue.rb @@ -0,0 +1,22 @@ +module GraphQL + class Hive < GraphQL::Tracing::PlatformTracing + class BoundedQueue < Thread::Queue + def initialize(size:, logger:) + @size = size + @logger = logger + + super() + end + + def push(item) + # call size on the instance of this queue + if size >= @size + @logger.error("BoundedQueue is full, discarding operation") + return + end + + super(item) + end + end + end +end diff --git a/spec/graphql/graphql-hive/bounded_queue_spec.rb b/spec/graphql/graphql-hive/bounded_queue_spec.rb new file mode 100644 index 0000000..11236c2 --- /dev/null +++ b/spec/graphql/graphql-hive/bounded_queue_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require "spec_helper" +require "graphql-hive" + +RSpec.describe GraphQL::Hive::BoundedQueue do + subject(:queue) { GraphQL::Hive::BoundedQueue.new(size: 2, logger: logger) } + + let(:logger) { instance_double("Logger") } + + before do + allow(logger).to receive(:error) + end + + it "should be a subclass of Thread::Queue" do + expect(GraphQL::Hive::BoundedQueue.superclass).to eq(Thread::Queue) + end + + it "should be able to push items up to size" do + queue.push("one") + queue.push("two") + + expect(queue.size).to eq(2) + end + + it "should discard items and log when full" do + queue.push("one") + queue.push("two") + queue.push("three") + queue.push("four") + + expect(queue.size).to eq(2) + expect(logger).to have_received(:error).with("BoundedQueue is full, discarding operation").twice + end +end \ No newline at end of file From e5e43149ebe1350aa1678b47fe7ad4cc72cb8dce Mon Sep 17 00:00:00 2001 From: Arya Bhimani Date: Mon, 28 Oct 2024 15:32:42 -0400 Subject: [PATCH 02/23] gitignores --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitignore b/.gitignore index 1a9c908..8479644 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,9 @@ .rspec_status *.gem k6/node_modules/ + +# IDEs +.idea + +# k6 +node_modules \ No newline at end of file From 72e9da0162af0cf6323f929a2d35680bf172e108 Mon Sep 17 00:00:00 2001 From: Arya Bhimani Date: Mon, 28 Oct 2024 15:46:38 -0400 Subject: [PATCH 03/23] fix: use bounded queue for usage reporter --- lib/graphql-hive.rb | 4 +++- lib/graphql-hive/usage_reporter.rb | 9 +++++---- spec/graphql/graphql-hive/bounded_queue_spec.rb | 12 ++++++++++++ 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/lib/graphql-hive.rb b/lib/graphql-hive.rb index 5bbb6b0..ea7be94 100644 --- a/lib/graphql-hive.rb +++ b/lib/graphql-hive.rb @@ -36,6 +36,7 @@ class Hive < GraphQL::Tracing::PlatformTracing read_operations: true, report_schema: true, buffer_size: 50, + bounded_queue_multiple: 5, logger: nil, collect_usage_sampling: 1.0 }.freeze @@ -134,7 +135,8 @@ def initialize_options!(options) options[:logger] = Logger.new($stderr) original_formatter = Logger::Formatter.new options[:logger].formatter = proc { |severity, datetime, progname, msg| - original_formatter.call(severity, datetime, progname, "[hive] #{msg.dump}") + msg = msg.respond_to?(:dump) ? msg.dump : msg + original_formatter.call(severity, datetime, progname, "[hive] #{msg}") } options[:logger].level = options[:debug] ? Logger::DEBUG : Logger::INFO end diff --git a/lib/graphql-hive/usage_reporter.rb b/lib/graphql-hive/usage_reporter.rb index 5d6d7e1..3ea1153 100644 --- a/lib/graphql-hive/usage_reporter.rb +++ b/lib/graphql-hive/usage_reporter.rb @@ -3,6 +3,7 @@ require "digest" require "graphql-hive/analyzer" require "graphql-hive/printer" +require "graphql-hive/bounded_queue" module GraphQL class Hive < GraphQL::Tracing::PlatformTracing @@ -16,15 +17,14 @@ def self.instance def initialize(options, client) @@instance = self - @options = options @client = client - @options_mutex = Mutex.new - @queue = Queue.new - @sampler = Sampler.new(options[:collect_usage_sampling], options[:logger]) # NOTE: logs for deprecated field + queue_bound = (options[:buffer_size] * options[:bounded_queue_multiple]).to_int + @queue = BoundedQueue.new(size: queue_bound, logger: options[:logger]) + start_thread end @@ -51,6 +51,7 @@ def start_thread @thread = Thread.new do buffer = [] + while (operation = @queue.pop(false)) @options[:logger].debug("processing operation from queue: #{operation}") buffer << operation if @sampler.sample?(operation) diff --git a/spec/graphql/graphql-hive/bounded_queue_spec.rb b/spec/graphql/graphql-hive/bounded_queue_spec.rb index 11236c2..7bb0dc2 100644 --- a/spec/graphql/graphql-hive/bounded_queue_spec.rb +++ b/spec/graphql/graphql-hive/bounded_queue_spec.rb @@ -32,4 +32,16 @@ expect(queue.size).to eq(2) expect(logger).to have_received(:error).with("BoundedQueue is full, discarding operation").twice end + + it "allows pushes after pops" do + queue.push("one") + queue.push("two") + + queue.push("invalid") + expect(queue.size).to eq(2) + + queue.pop + queue.push("three") + expect(queue.size).to eq(2) + end end \ No newline at end of file From 8aa6edb59cdb85b16a62a679e4a732d9c93ca4a8 Mon Sep 17 00:00:00 2001 From: Arya Bhimani Date: Mon, 28 Oct 2024 15:53:12 -0400 Subject: [PATCH 04/23] fix: specs --- spec/graphql/graphql-hive/usage_reporter_spec.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/spec/graphql/graphql-hive/usage_reporter_spec.rb b/spec/graphql/graphql-hive/usage_reporter_spec.rb index 64f0856..d3b8a2b 100644 --- a/spec/graphql/graphql-hive/usage_reporter_spec.rb +++ b/spec/graphql/graphql-hive/usage_reporter_spec.rb @@ -4,7 +4,7 @@ RSpec.describe GraphQL::Hive::UsageReporter do let(:usage_reporter_instance) { described_class.new(options, client) } - let(:options) { {logger: logger} } + let(:options) { {logger: logger, buffer_size: 1, bounded_queue_multiple: 1} } let(:logger) { instance_double("Logger") } let(:client) { instance_double("Hive::Client") } @@ -32,7 +32,7 @@ expect(usage_reporter_instance.instance_variable_get(:@client)).to eq(client) expect(usage_reporter_instance.instance_variable_get(:@options_mutex)).to be_an_instance_of(Mutex) - expect(usage_reporter_instance.instance_variable_get(:@queue)).to be_an_instance_of(Queue) + expect(usage_reporter_instance.instance_variable_get(:@queue)).to be_an_instance_of(GraphQL::Hive::BoundedQueue) expect(usage_reporter_instance.instance_variable_get(:@sampler)).to be_an_instance_of(GraphQL::Hive::Sampler) end end @@ -76,7 +76,8 @@ let(:options) do { logger: logger, - buffer_size: 1 + buffer_size: 1, + bounded_queue_multiple: 1, } end From ca348a86e684fa858425866b34fb0f53d87d1d5e Mon Sep 17 00:00:00 2001 From: Arya Bhimani Date: Mon, 28 Oct 2024 15:53:58 -0400 Subject: [PATCH 05/23] fix: cops --- lib/graphql-hive/bounded_queue.rb | 2 +- spec/graphql/graphql-hive/bounded_queue_spec.rb | 2 +- spec/graphql/graphql-hive/usage_reporter_spec.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/graphql-hive/bounded_queue.rb b/lib/graphql-hive/bounded_queue.rb index 5cd2d3e..5de1f0e 100644 --- a/lib/graphql-hive/bounded_queue.rb +++ b/lib/graphql-hive/bounded_queue.rb @@ -15,7 +15,7 @@ def push(item) return end - super(item) + super end end end diff --git a/spec/graphql/graphql-hive/bounded_queue_spec.rb b/spec/graphql/graphql-hive/bounded_queue_spec.rb index 7bb0dc2..9396b2a 100644 --- a/spec/graphql/graphql-hive/bounded_queue_spec.rb +++ b/spec/graphql/graphql-hive/bounded_queue_spec.rb @@ -44,4 +44,4 @@ queue.push("three") expect(queue.size).to eq(2) end -end \ No newline at end of file +end diff --git a/spec/graphql/graphql-hive/usage_reporter_spec.rb b/spec/graphql/graphql-hive/usage_reporter_spec.rb index d3b8a2b..7ee0424 100644 --- a/spec/graphql/graphql-hive/usage_reporter_spec.rb +++ b/spec/graphql/graphql-hive/usage_reporter_spec.rb @@ -77,7 +77,7 @@ { logger: logger, buffer_size: 1, - bounded_queue_multiple: 1, + bounded_queue_multiple: 1 } end From 3d8ad831b91166037b1e8af440834d17802aa5b3 Mon Sep 17 00:00:00 2001 From: Arya Bhimani Date: Mon, 28 Oct 2024 16:09:26 -0400 Subject: [PATCH 06/23] fix: use name bound --- lib/graphql-hive/bounded_queue.rb | 6 +++--- spec/graphql/graphql-hive/bounded_queue_spec.rb | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/graphql-hive/bounded_queue.rb b/lib/graphql-hive/bounded_queue.rb index 5de1f0e..a9c59d6 100644 --- a/lib/graphql-hive/bounded_queue.rb +++ b/lib/graphql-hive/bounded_queue.rb @@ -1,8 +1,8 @@ module GraphQL class Hive < GraphQL::Tracing::PlatformTracing class BoundedQueue < Thread::Queue - def initialize(size:, logger:) - @size = size + def initialize(bound:, logger:) + @bound = bound @logger = logger super() @@ -10,7 +10,7 @@ def initialize(size:, logger:) def push(item) # call size on the instance of this queue - if size >= @size + if size >= @bound @logger.error("BoundedQueue is full, discarding operation") return end diff --git a/spec/graphql/graphql-hive/bounded_queue_spec.rb b/spec/graphql/graphql-hive/bounded_queue_spec.rb index 9396b2a..dcee81c 100644 --- a/spec/graphql/graphql-hive/bounded_queue_spec.rb +++ b/spec/graphql/graphql-hive/bounded_queue_spec.rb @@ -4,7 +4,7 @@ require "graphql-hive" RSpec.describe GraphQL::Hive::BoundedQueue do - subject(:queue) { GraphQL::Hive::BoundedQueue.new(size: 2, logger: logger) } + subject(:queue) { GraphQL::Hive::BoundedQueue.new(bound: 2, logger: logger) } let(:logger) { instance_double("Logger") } From 11689a4f31711e5015fd05d5104fa2cf45799334 Mon Sep 17 00:00:00 2001 From: Arya Bhimani Date: Mon, 28 Oct 2024 16:09:39 -0400 Subject: [PATCH 07/23] fix: bound --- lib/graphql-hive/usage_reporter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/graphql-hive/usage_reporter.rb b/lib/graphql-hive/usage_reporter.rb index 3ea1153..c818e41 100644 --- a/lib/graphql-hive/usage_reporter.rb +++ b/lib/graphql-hive/usage_reporter.rb @@ -23,7 +23,7 @@ def initialize(options, client) @sampler = Sampler.new(options[:collect_usage_sampling], options[:logger]) # NOTE: logs for deprecated field queue_bound = (options[:buffer_size] * options[:bounded_queue_multiple]).to_int - @queue = BoundedQueue.new(size: queue_bound, logger: options[:logger]) + @queue = BoundedQueue.new(bound: queue_bound, logger: options[:logger]) start_thread end From 7468bd7e08dfe5c49efd3153f6ce8a75d83d988d Mon Sep 17 00:00:00 2001 From: Arya Bhimani Date: Mon, 28 Oct 2024 17:28:25 -0400 Subject: [PATCH 08/23] fix: use mutex for queue, and add @running variable --- lib/graphql-hive/bounded_queue.rb | 12 +++++++----- lib/graphql-hive/usage_reporter.rb | 16 +++++++++++----- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/lib/graphql-hive/bounded_queue.rb b/lib/graphql-hive/bounded_queue.rb index a9c59d6..c9efa99 100644 --- a/lib/graphql-hive/bounded_queue.rb +++ b/lib/graphql-hive/bounded_queue.rb @@ -4,18 +4,20 @@ class BoundedQueue < Thread::Queue def initialize(bound:, logger:) @bound = bound @logger = logger + @lock = Mutex.new super() end def push(item) # call size on the instance of this queue - if size >= @bound - @logger.error("BoundedQueue is full, discarding operation") - return + @lock.synchronize do + if size >= @bound + @logger.error("BoundedQueue is full, discarding operation") + return + end + super end - - super end end end diff --git a/lib/graphql-hive/usage_reporter.rb b/lib/graphql-hive/usage_reporter.rb index c818e41..9afc645 100644 --- a/lib/graphql-hive/usage_reporter.rb +++ b/lib/graphql-hive/usage_reporter.rb @@ -25,6 +25,7 @@ def initialize(options, client) queue_bound = (options[:buffer_size] * options[:bounded_queue_multiple]).to_int @queue = BoundedQueue.new(bound: queue_bound, logger: options[:logger]) + @running = true start_thread end @@ -34,6 +35,7 @@ def add_operation(operation) def on_exit @queue.close + @running = false @thread.join end @@ -51,8 +53,13 @@ def start_thread @thread = Thread.new do buffer = [] + loop do + break unless @running while (operation = @queue.pop(false)) + operation = @queue.pop(false) + next if operation.nil? + @options[:logger].debug("processing operation from queue: #{operation}") buffer << operation if @sampler.sample?(operation) @@ -63,16 +70,15 @@ def start_thread buffer = [] end end + rescue => e + buffer = [] + @options[:logger].error(e) end unless buffer.empty? - @options[:logger].debug("shuting down with buffer, sending!") + @options[:logger].debug("shutting down with buffer, sending!") process_operations(buffer) end - rescue => e - # ensure configured logger receives exception as well in setups where STDERR might not be - # monitored. - @options[:logger].error(e) end end From 81dfa297e6554032bb519bb7a1489bf88d3fdcc8 Mon Sep 17 00:00:00 2001 From: Arya Bhimani Date: Tue, 29 Oct 2024 09:04:31 -0400 Subject: [PATCH 09/23] fix: use loop instead of while --- lib/graphql-hive/usage_reporter.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/graphql-hive/usage_reporter.rb b/lib/graphql-hive/usage_reporter.rb index 9afc645..8fc2aa1 100644 --- a/lib/graphql-hive/usage_reporter.rb +++ b/lib/graphql-hive/usage_reporter.rb @@ -56,7 +56,6 @@ def start_thread loop do break unless @running - while (operation = @queue.pop(false)) operation = @queue.pop(false) next if operation.nil? @@ -138,7 +137,7 @@ def add_operation_to_report(report, operation) if results[0] context = results[0].query.context - operation_record[:metadata] = {client: @options[:client_info].call(context)} if @options[:client_info] + operation_record[:metadata] = { client: @options[:client_info].call(context) } if @options[:client_info] end report[:map][operation_map_key] = { @@ -151,7 +150,7 @@ def add_operation_to_report(report, operation) end def errors_from_results(results) - acc = {errorsTotal: 0} + acc = { errorsTotal: 0 } results.each do |result| errors = result.to_h.fetch("errors", []) errors.each do From 8a4ca18f92a76544d8a294f6a42aac6865b80c10 Mon Sep 17 00:00:00 2001 From: Arya Bhimani Date: Tue, 29 Oct 2024 09:13:15 -0400 Subject: [PATCH 10/23] fix: test for thread safety --- lib/graphql-hive/bounded_queue.rb | 1 - spec/graphql/graphql-hive/bounded_queue_spec.rb | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/graphql-hive/bounded_queue.rb b/lib/graphql-hive/bounded_queue.rb index c9efa99..361bd10 100644 --- a/lib/graphql-hive/bounded_queue.rb +++ b/lib/graphql-hive/bounded_queue.rb @@ -10,7 +10,6 @@ def initialize(bound:, logger:) end def push(item) - # call size on the instance of this queue @lock.synchronize do if size >= @bound @logger.error("BoundedQueue is full, discarding operation") diff --git a/spec/graphql/graphql-hive/bounded_queue_spec.rb b/spec/graphql/graphql-hive/bounded_queue_spec.rb index dcee81c..85f423f 100644 --- a/spec/graphql/graphql-hive/bounded_queue_spec.rb +++ b/spec/graphql/graphql-hive/bounded_queue_spec.rb @@ -44,4 +44,18 @@ queue.push("three") expect(queue.size).to eq(2) end + + it "should be thsead-safe and discard items when full" do + threads = [] + 10.times do |i| + threads << Thread.new do + queue.push(i) + end + end + + threads.each(&:join) + + expect(queue.size).to eq(2) + expect(logger).to have_received(:error).with("BoundedQueue is full, discarding operation").exactly(8).times + end end From 1037961fa8e3cd9487e5584d3c757ad51f40875a Mon Sep 17 00:00:00 2001 From: Arya Bhimani Date: Tue, 29 Oct 2024 09:23:33 -0400 Subject: [PATCH 11/23] fix: updated test code for synch --- .../graphql-hive/bounded_queue_spec.rb | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/spec/graphql/graphql-hive/bounded_queue_spec.rb b/spec/graphql/graphql-hive/bounded_queue_spec.rb index 85f423f..05af56c 100644 --- a/spec/graphql/graphql-hive/bounded_queue_spec.rb +++ b/spec/graphql/graphql-hive/bounded_queue_spec.rb @@ -47,7 +47,7 @@ it "should be thsead-safe and discard items when full" do threads = [] - 10.times do |i| + 20.times do |i| threads << Thread.new do queue.push(i) end @@ -56,6 +56,24 @@ threads.each(&:join) expect(queue.size).to eq(2) - expect(logger).to have_received(:error).with("BoundedQueue is full, discarding operation").exactly(8).times + expect(logger).to have_received(:error).with("BoundedQueue is full, discarding operation").exactly(18).times + end + + it "should be thread-safe and discard items when full - 2" do + threads = [] + mutex = Mutex.new + + 20.times do |i| + threads << Thread.new do + mutex.synchronize do + queue.push(i) + end + end + end + + threads.each(&:join) + + expect(queue.size).to eq(2) + expect(logger).to have_received(:error).with("BoundedQueue is full, discarding operation").exactly(18).times end end From b896219373961f715340472b6aeeaae538923c0e Mon Sep 17 00:00:00 2001 From: Arya Bhimani Date: Tue, 29 Oct 2024 09:24:41 -0400 Subject: [PATCH 12/23] fix: remove test --- .../graphql/graphql-hive/bounded_queue_spec.rb | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/spec/graphql/graphql-hive/bounded_queue_spec.rb b/spec/graphql/graphql-hive/bounded_queue_spec.rb index 05af56c..5f80c42 100644 --- a/spec/graphql/graphql-hive/bounded_queue_spec.rb +++ b/spec/graphql/graphql-hive/bounded_queue_spec.rb @@ -58,22 +58,4 @@ expect(queue.size).to eq(2) expect(logger).to have_received(:error).with("BoundedQueue is full, discarding operation").exactly(18).times end - - it "should be thread-safe and discard items when full - 2" do - threads = [] - mutex = Mutex.new - - 20.times do |i| - threads << Thread.new do - mutex.synchronize do - queue.push(i) - end - end - end - - threads.each(&:join) - - expect(queue.size).to eq(2) - expect(logger).to have_received(:error).with("BoundedQueue is full, discarding operation").exactly(18).times - end end From 745e41ad02d0c0600dc0f3b25e30d7a917d81b6b Mon Sep 17 00:00:00 2001 From: Arya Bhimani Date: Tue, 29 Oct 2024 11:24:10 -0400 Subject: [PATCH 13/23] fix: usage reporter while loop + tests for invalid operation --- lib/graphql-hive/usage_reporter.rb | 41 +++++----- .../graphql-hive/usage_reporter_spec.rb | 75 ++++++++++++++++++- 2 files changed, 93 insertions(+), 23 deletions(-) diff --git a/lib/graphql-hive/usage_reporter.rb b/lib/graphql-hive/usage_reporter.rb index 8fc2aa1..8fe81d7 100644 --- a/lib/graphql-hive/usage_reporter.rb +++ b/lib/graphql-hive/usage_reporter.rb @@ -25,7 +25,6 @@ def initialize(options, client) queue_bound = (options[:buffer_size] * options[:bounded_queue_multiple]).to_int @queue = BoundedQueue.new(bound: queue_bound, logger: options[:logger]) - @running = true start_thread end @@ -34,8 +33,8 @@ def add_operation(operation) end def on_exit - @queue.close @running = false + @queue.close @thread.join end @@ -53,31 +52,32 @@ def start_thread @thread = Thread.new do buffer = [] - loop do - break unless @running - - operation = @queue.pop(false) - next if operation.nil? - - @options[:logger].debug("processing operation from queue: #{operation}") - buffer << operation if @sampler.sample?(operation) - - @options_mutex.synchronize do - if buffer.size >= @options[:buffer_size] - @options[:logger].debug("buffer is full, sending!") - process_operations(buffer) - buffer = [] + while (operation = @queue.pop(false)) + begin + @options[:logger].debug("processing operation from queue: #{operation}") + buffer << operation if @sampler.sample?(operation) + + @options_mutex.synchronize do + if buffer.size >= @options[:buffer_size] + @options[:logger].debug("buffer is full, sending!") + process_operations(buffer) + buffer = [] + end end + rescue => e + buffer = [] + @options[:logger].error(e) end - rescue => e - buffer = [] - @options[:logger].error(e) end unless buffer.empty? - @options[:logger].debug("shutting down with buffer, sending!") + @options[:logger].debug("shuting down with buffer, sending!") process_operations(buffer) end + rescue => e + # ensure configured logger receives exception as well in setups where STDERR might not be + # monitored. + @options[:logger].error(e) end end @@ -94,6 +94,7 @@ def process_operations(operations) @options[:logger].debug("sending report: #{report}") + p "sending report size: #{report[:size]}" @client.send(:"/usage", report, :usage) end diff --git a/spec/graphql/graphql-hive/usage_reporter_spec.rb b/spec/graphql/graphql-hive/usage_reporter_spec.rb index 7ee0424..9d7e04e 100644 --- a/spec/graphql/graphql-hive/usage_reporter_spec.rb +++ b/spec/graphql/graphql-hive/usage_reporter_spec.rb @@ -4,9 +4,10 @@ RSpec.describe GraphQL::Hive::UsageReporter do let(:usage_reporter_instance) { described_class.new(options, client) } - let(:options) { {logger: logger, buffer_size: 1, bounded_queue_multiple: 1} } + let(:options) { {logger: logger, buffer_size: buffer_size, bounded_queue_multiple: 1} } let(:logger) { instance_double("Logger") } let(:client) { instance_double("Hive::Client") } + let(:buffer_size) { 1 } let(:timestamp) { 1_720_705_946_333 } let(:queries) { [] } @@ -65,11 +66,13 @@ describe "#start_thread" do it "logs a warning if the thread is already alive" do - usage_reporter_instance.instance_variable_set(:@thread, Thread.new do + thread = Thread.new do # do nothing - end) + end + usage_reporter_instance.instance_variable_set(:@thread, thread) expect(logger).to receive(:warn) usage_reporter_instance.on_start + thread.join end context "when configured with sampling" do @@ -107,6 +110,72 @@ usage_reporter_instance.add_operation(operation) end end + + context "with erroneous operations" do + let(:sampler_class) { class_double(GraphQL::Hive::Sampler).as_stubbed_const } + let(:sampler_instance) { instance_double("GraphQL::Hive::Sampler") } + let(:schema) { GraphQL::Schema.from_definition("type Query { test: String }") } + let(:queries_valid) { [GraphQL::Query.new(schema, "query TestingHiveValid { test }", variables: {})] } + let(:queries_invalid) { [GraphQL::Query.new(schema, "query TestingHiveInvalid { test }", variables: {})] } + let(:results) { [GraphQL::Query::Result.new(query: queries_valid[0], values: {"data" => {"test" => "test"}})] } + let(:buffer_size) { 1 } + + before do + allow(sampler_class).to receive(:new).and_return(sampler_instance) + end + + it "can still process the operations after erroneous operation" do + raise_exception = true + operation_error = StandardError.new("First operation") + allow(sampler_instance).to receive(:sample?) do |_operation| + if raise_exception + raise_exception = false + raise operation_error + else + true + end + end + + mutex = Mutex.new + logger_condition = ConditionVariable.new + client_condition = ConditionVariable.new + allow(logger).to receive(:error) do |_e| + mutex.synchronize { logger_condition.signal } + end + + allow(client).to receive(:send) do |_endpoint| + mutex.synchronize { client_condition.signal } + end + + mutex.synchronize do + usage_reporter_instance.add_operation([timestamp, queries_invalid, results, duration]) + logger_condition.wait(mutex) + + usage_reporter_instance.add_operation([timestamp, queries_valid, results, duration]) + client_condition.wait(mutex) + end + + expect(client).to have_received(:send).once.with( + :"/usage", + { + map: {"a69918853baf60d89b871e1fbe13915b" => + { + fields: ["Query", "Query.test"], + operation: "query TestingHiveValid {\n test\n}", + operationName: "TestingHiveValid" + }}, + operations: [{ + execution: {duration: 100000, errorsTotal: 0, ok: true}, + operationMapKey: "a69918853baf60d89b871e1fbe13915b", + timestamp: 1720705946333 + }], + size: 1 + }, + :usage + ) + expect(logger).to have_received(:error).with(operation_error).once + end + end end describe "#process_operation" do From 0262ce4ac99950e52ddf6dac96c9c9bd95025d4d Mon Sep 17 00:00:00 2001 From: Arya Bhimani Date: Tue, 29 Oct 2024 11:26:54 -0400 Subject: [PATCH 14/23] fix: extra @running --- lib/graphql-hive/usage_reporter.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/graphql-hive/usage_reporter.rb b/lib/graphql-hive/usage_reporter.rb index 8fe81d7..d76461c 100644 --- a/lib/graphql-hive/usage_reporter.rb +++ b/lib/graphql-hive/usage_reporter.rb @@ -33,7 +33,6 @@ def add_operation(operation) end def on_exit - @running = false @queue.close @thread.join end @@ -138,7 +137,7 @@ def add_operation_to_report(report, operation) if results[0] context = results[0].query.context - operation_record[:metadata] = { client: @options[:client_info].call(context) } if @options[:client_info] + operation_record[:metadata] = {client: @options[:client_info].call(context)} if @options[:client_info] end report[:map][operation_map_key] = { @@ -151,7 +150,7 @@ def add_operation_to_report(report, operation) end def errors_from_results(results) - acc = { errorsTotal: 0 } + acc = {errorsTotal: 0} results.each do |result| errors = result.to_h.fetch("errors", []) errors.each do From 3079776f8a20bded107ace70b554045a1287f36b Mon Sep 17 00:00:00 2001 From: Arya Bhimani Date: Tue, 29 Oct 2024 11:27:17 -0400 Subject: [PATCH 15/23] fix: prints --- lib/graphql-hive/usage_reporter.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/graphql-hive/usage_reporter.rb b/lib/graphql-hive/usage_reporter.rb index d76461c..4a6d0fc 100644 --- a/lib/graphql-hive/usage_reporter.rb +++ b/lib/graphql-hive/usage_reporter.rb @@ -93,7 +93,6 @@ def process_operations(operations) @options[:logger].debug("sending report: #{report}") - p "sending report size: #{report[:size]}" @client.send(:"/usage", report, :usage) end From 985691e622490942fe23039d372bab56b0db5822 Mon Sep 17 00:00:00 2001 From: Arya Bhimani Date: Tue, 29 Oct 2024 11:35:44 -0400 Subject: [PATCH 16/23] fix: readme --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9e3433c..0812290 100644 --- a/README.md +++ b/README.md @@ -155,8 +155,9 @@ class MySchema < GraphQL::Schema logger: MyLogger.new, endpoint: 'app.graphql-hive.com', port: 80, - buffer_size: 50, # forward the operations data to Hive every 50 requests - + buffer_size: 50, # how many operations can be sent to hive in a single batch (AFTER sampling) + bounded_queue_multiple: 5, # how many operations can be added to the queue, before we start dropping them + collect_usage: true, # report usage to Hive collect_usage_sampling: { # optional members of `collect_usage_sampling` @@ -164,8 +165,7 @@ class MySchema < GraphQL::Schema sampler: proc { |context| context.operation_name.includes?('someQuery') 1 : 0.5 }, # assign custom sampling rates (overrides `sampling rate`) at_least_once: true, # sample every distinct operation at least once key_generator: proc { |context| context.operation_name } # assign custom keys to distinguish between distinct operations - } - + }, report_schema: true, # publish schema to Hive # mandatory if `report_schema: true` reporting: { From 78b10e03352478e5bcb4e1d9864cf7a9e4a10af7 Mon Sep 17 00:00:00 2001 From: Arya Bhimani Date: Tue, 29 Oct 2024 11:36:49 -0400 Subject: [PATCH 17/23] fix: readme --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0812290..8d64433 100644 --- a/README.md +++ b/README.md @@ -155,8 +155,9 @@ class MySchema < GraphQL::Schema logger: MyLogger.new, endpoint: 'app.graphql-hive.com', port: 80, + buffer_size: 50, # how many operations can be sent to hive in a single batch (AFTER sampling) - bounded_queue_multiple: 5, # how many operations can be added to the queue, before we start dropping them + bounded_queue_multiple: 5, # how many operations can be added to the queue, before we start dropping them, (buffer_size * bounded_queue_multiple) collect_usage: true, # report usage to Hive collect_usage_sampling: { From 04e4e859a328df4bdadefa0fe2250c37ca898762 Mon Sep 17 00:00:00 2001 From: Arya Bhimani Date: Tue, 29 Oct 2024 11:37:13 -0400 Subject: [PATCH 18/23] fix: operation queue multiple Co-authored-by: Cassia Scheffer --- lib/graphql-hive/usage_reporter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/graphql-hive/usage_reporter.rb b/lib/graphql-hive/usage_reporter.rb index 4a6d0fc..23e73a4 100644 --- a/lib/graphql-hive/usage_reporter.rb +++ b/lib/graphql-hive/usage_reporter.rb @@ -22,7 +22,7 @@ def initialize(options, client) @options_mutex = Mutex.new @sampler = Sampler.new(options[:collect_usage_sampling], options[:logger]) # NOTE: logs for deprecated field - queue_bound = (options[:buffer_size] * options[:bounded_queue_multiple]).to_int + queue_bound = (options[:buffer_size].to_i * 5) @queue = BoundedQueue.new(bound: queue_bound, logger: options[:logger]) start_thread From 3e77fdda68f64fee29ee93abecbd0cff44c430ba Mon Sep 17 00:00:00 2001 From: Arya Bhimani Date: Tue, 29 Oct 2024 13:38:23 -0400 Subject: [PATCH 19/23] fix: remove bounded queue size --- README.md | 4 +--- lib/graphql-hive.rb | 1 - lib/graphql-hive/usage_reporter.rb | 4 +--- spec/graphql/graphql-hive/usage_reporter_spec.rb | 5 ++--- 4 files changed, 4 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 8d64433..dad4305 100644 --- a/README.md +++ b/README.md @@ -154,10 +154,8 @@ class MySchema < GraphQL::Schema debug: false, # verbose logs logger: MyLogger.new, endpoint: 'app.graphql-hive.com', - port: 80, - + port: 80, buffer_size: 50, # how many operations can be sent to hive in a single batch (AFTER sampling) - bounded_queue_multiple: 5, # how many operations can be added to the queue, before we start dropping them, (buffer_size * bounded_queue_multiple) collect_usage: true, # report usage to Hive collect_usage_sampling: { diff --git a/lib/graphql-hive.rb b/lib/graphql-hive.rb index ea7be94..a380bac 100644 --- a/lib/graphql-hive.rb +++ b/lib/graphql-hive.rb @@ -36,7 +36,6 @@ class Hive < GraphQL::Tracing::PlatformTracing read_operations: true, report_schema: true, buffer_size: 50, - bounded_queue_multiple: 5, logger: nil, collect_usage_sampling: 1.0 }.freeze diff --git a/lib/graphql-hive/usage_reporter.rb b/lib/graphql-hive/usage_reporter.rb index 23e73a4..90c21ea 100644 --- a/lib/graphql-hive/usage_reporter.rb +++ b/lib/graphql-hive/usage_reporter.rb @@ -21,9 +21,7 @@ def initialize(options, client) @client = client @options_mutex = Mutex.new @sampler = Sampler.new(options[:collect_usage_sampling], options[:logger]) # NOTE: logs for deprecated field - - queue_bound = (options[:buffer_size].to_i * 5) - @queue = BoundedQueue.new(bound: queue_bound, logger: options[:logger]) + @queue = BoundedQueue.new(bound: options[:buffer_size], logger: options[:logger]) start_thread end diff --git a/spec/graphql/graphql-hive/usage_reporter_spec.rb b/spec/graphql/graphql-hive/usage_reporter_spec.rb index 9d7e04e..4ed8422 100644 --- a/spec/graphql/graphql-hive/usage_reporter_spec.rb +++ b/spec/graphql/graphql-hive/usage_reporter_spec.rb @@ -4,7 +4,7 @@ RSpec.describe GraphQL::Hive::UsageReporter do let(:usage_reporter_instance) { described_class.new(options, client) } - let(:options) { {logger: logger, buffer_size: buffer_size, bounded_queue_multiple: 1} } + let(:options) { {logger: logger, buffer_size: buffer_size} } let(:logger) { instance_double("Logger") } let(:client) { instance_double("Hive::Client") } let(:buffer_size) { 1 } @@ -79,8 +79,7 @@ let(:options) do { logger: logger, - buffer_size: 1, - bounded_queue_multiple: 1 + buffer_size: 1 } end From ae8faca109d7a9f90d06bd78355e7bdccc589ccc Mon Sep 17 00:00:00 2001 From: Arya Bhimani Date: Tue, 29 Oct 2024 13:50:30 -0400 Subject: [PATCH 20/23] fix: updated specs for code comments --- .../graphql-hive/bounded_queue_spec.rb | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/spec/graphql/graphql-hive/bounded_queue_spec.rb b/spec/graphql/graphql-hive/bounded_queue_spec.rb index 5f80c42..7fa3ff5 100644 --- a/spec/graphql/graphql-hive/bounded_queue_spec.rb +++ b/spec/graphql/graphql-hive/bounded_queue_spec.rb @@ -47,7 +47,7 @@ it "should be thsead-safe and discard items when full" do threads = [] - 20.times do |i| + 10.times do |i| threads << Thread.new do queue.push(i) end @@ -56,6 +56,23 @@ threads.each(&:join) expect(queue.size).to eq(2) - expect(logger).to have_received(:error).with("BoundedQueue is full, discarding operation").exactly(18).times + expect(logger).to have_received(:error).with("BoundedQueue is full, discarding operation").exactly(8).times + end + + it "should be able to push after pop in multi-threaded environment" do + threads = [] + perform_operations = [:push, :push, :pop, :push, :push, :push, :push] + + perform_operations.each do |operation| + threads << Thread.new do + queue.push("operation") if operation == :push + queue.pop if operation == :pop + end + end + + threads.each(&:join) + + expect(queue.size).to eq(2) + expect(logger).to have_received(:error).with("BoundedQueue is full, discarding operation").exactly(3).times end end From 81e15528e1ae6690caf29a56529b61e666ea5e0e Mon Sep 17 00:00:00 2001 From: Arya Bhimani Date: Tue, 29 Oct 2024 14:08:10 -0400 Subject: [PATCH 21/23] fix: comment for SizedQueue --- lib/graphql-hive/bounded_queue.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/graphql-hive/bounded_queue.rb b/lib/graphql-hive/bounded_queue.rb index 361bd10..252e011 100644 --- a/lib/graphql-hive/bounded_queue.rb +++ b/lib/graphql-hive/bounded_queue.rb @@ -1,5 +1,8 @@ module GraphQL class Hive < GraphQL::Tracing::PlatformTracing + # BoundedQueue is being used so that the queue does not grow indefinitely + # We do not use `SizedQueue` because it blocks the thread when the queue is full with a .wait call + # This would go against us not impacting the application performance with the usage reporter class BoundedQueue < Thread::Queue def initialize(bound:, logger:) @bound = bound From 8b086af1ed453dc86cd5d68e0be92c5b65df0402 Mon Sep 17 00:00:00 2001 From: Arya Bhimani Date: Wed, 30 Oct 2024 10:09:42 -0400 Subject: [PATCH 22/23] fix: test comments --- spec/graphql/graphql-hive/usage_reporter_spec.rb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/spec/graphql/graphql-hive/usage_reporter_spec.rb b/spec/graphql/graphql-hive/usage_reporter_spec.rb index 4ed8422..7da5b81 100644 --- a/spec/graphql/graphql-hive/usage_reporter_spec.rb +++ b/spec/graphql/graphql-hive/usage_reporter_spec.rb @@ -66,13 +66,14 @@ describe "#start_thread" do it "logs a warning if the thread is already alive" do - thread = Thread.new do - # do nothing - end - usage_reporter_instance.instance_variable_set(:@thread, thread) + usage_reporter_instance.instance_variable_set( + :@thread, + Thread.new do + # do nothing + end + ) expect(logger).to receive(:warn) usage_reporter_instance.on_start - thread.join end context "when configured with sampling" do From 14411f7bdd801ba6beca1a692baf789263e591d4 Mon Sep 17 00:00:00 2001 From: Arya Bhimani Date: Wed, 30 Oct 2024 11:30:54 -0400 Subject: [PATCH 23/23] fix: version bump --- Gemfile.lock | 2 +- k6/graphql-api/Gemfile.lock | 2 +- lib/graphql-hive/version.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 6980564..dcfe6d0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - graphql-hive (0.4.3) + graphql-hive (0.5.3) graphql (>= 2.3, < 3) GEM diff --git a/k6/graphql-api/Gemfile.lock b/k6/graphql-api/Gemfile.lock index 1a2ef32..bccfe50 100644 --- a/k6/graphql-api/Gemfile.lock +++ b/k6/graphql-api/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: ../.. specs: - graphql-hive (0.4.3) + graphql-hive (0.5.3) graphql (>= 2.3, < 3) GEM diff --git a/lib/graphql-hive/version.rb b/lib/graphql-hive/version.rb index 772007e..42eef1c 100644 --- a/lib/graphql-hive/version.rb +++ b/lib/graphql-hive/version.rb @@ -2,6 +2,6 @@ module Graphql module Hive - VERSION = "0.4.3" + VERSION = "0.5.3" end end