Skip to content

Commit ae7eceb

Browse files
committed
Add limit: option
1 parent bf7cf65 commit ae7eceb

File tree

6 files changed

+62
-17
lines changed

6 files changed

+62
-17
lines changed

lib/graphql/tracing/perfetto_sampler.rb

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,16 @@
55
module GraphQL
66
module Tracing
77
class PerfettoSampler
8-
def self.use(schema, trace_mode: :perfetto_sample, memory: false, redis: nil, active_record: true)
8+
# @param trace_mode [Symbol] When this is set as `context[:trace_mode]`, a profile will be taken
9+
# @param redis [Redis] If provided, profiles will be stored in Redis for later review
10+
# @param limit [Integer] A maximum number of profiles to store
11+
def self.use(schema, trace_mode: :perfetto_sample, memory: false, redis: nil, limit: nil)
912
storage = if redis
10-
RedisBackend.new(redis: redis)
13+
RedisBackend.new(redis: redis, limit: limit)
1114
elsif memory
12-
MemoryBackend.new
13-
elsif active_record != false
14-
ActiveRecordBackend.new
15+
MemoryBackend.new(limit: limit)
1516
else
16-
raise ArgumentError, "A storage option must be chosen"
17+
raise ArgumentError, "Pass `redis: ...` to store traces in Redis for later review"
1718
end
1819
schema.perfetto_sampler = self.new(storage: storage)
1920
schema.trace_with(PerfettoTrace, mode: trace_mode, save_trace_mode: trace_mode)

lib/graphql/tracing/perfetto_sampler/memory_backend.rb

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ class PerfettoSampler
66
# An in-memory trace storage backend. Suitable for testing and development only.
77
# It won't work for multi-process deployments and everything is erased when the app is restarted.
88
class MemoryBackend
9-
def initialize
9+
def initialize(limit: nil)
10+
@limit = limit
1011
@traces = {}
12+
@next_id = 0
1113
end
1214

1315
def traces(last:, before:)
@@ -27,7 +29,7 @@ def find_trace(id)
2729
end
2830

2931
def delete_trace(id)
30-
@traces.delete(id.to_i)
32+
@traces.delete(id)
3133
nil
3234
end
3335

@@ -37,14 +39,19 @@ def delete_all_traces
3739
end
3840

3941
def save_trace(operation_name, duration, timestamp, trace_data)
40-
id = @traces.size
42+
id = @next_id
43+
@next_id += 1
4144
@traces[id] = PerfettoSampler::StoredTrace.new(
4245
id: id,
4346
operation_name: operation_name,
4447
duration_ms: duration,
4548
timestamp: timestamp,
4649
trace_data: trace_data
4750
)
51+
if @limit && @traces.size > @limit
52+
del_keys = @traces.keys[0...-@limit]
53+
del_keys.each { |k| @traces.delete(k) }
54+
end
4855
id
4956
end
5057
end

lib/graphql/tracing/perfetto_sampler/redis_backend.rb

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ module Tracing
55
class PerfettoSampler
66
class RedisBackend
77
KEY_PREFIX = "gql:trace:"
8-
def initialize(redis:)
8+
def initialize(redis:, limit: nil)
99
@redis = redis
1010
@key = KEY_PREFIX + "traces"
11+
@remrangebyrank_limit = limit ? -limit - 1 : nil
1112
end
1213

1314
def traces(last:, before:)
@@ -44,7 +45,12 @@ def find_trace(id)
4445
def save_trace(operation_name, duration_ms, begin_ms, trace_data)
4546
id = begin_ms
4647
data = JSON.dump({ "o" => operation_name, "d" => duration_ms, "b" => begin_ms, "t" => Base64.encode64(trace_data) })
47-
@redis.zadd(@key, id, data)
48+
@redis.pipelined do |pipeline|
49+
pipeline.zadd(@key, id, data)
50+
if @remrangebyrank_limit
51+
pipeline.zremrangebyrank(@key, 0, @remrangebyrank_limit)
52+
end
53+
end
4854
id
4955
end
5056

spec/graphql/tracing/perfetto_sampler/backend_assertions.rb

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ def self.included(child_class)
55
child_class.instance_eval do
66
describe "BackendAssertions" do
77
before do
8+
@backend = new_backend
89
@backend.delete_all_traces
910
end
1011

@@ -57,6 +58,38 @@ def self.included(child_class)
5758
it "returns nil for nonexistent IDs" do
5859
assert_nil @backend.find_trace(999_999_999)
5960
end
61+
62+
it "implements a limit" do
63+
limit_backend = new_backend(limit: 10)
64+
65+
10.times do |n|
66+
limit_backend.save_trace(
67+
"Trace#{n}",
68+
1.5,
69+
5000 + n,
70+
"some-data"
71+
)
72+
end
73+
74+
all_traces = limit_backend.traces(last: nil, before: nil)
75+
assert_equal 10, all_traces.size
76+
assert_equal "Trace9", all_traces.first.operation_name
77+
assert_equal "Trace0", all_traces.last.operation_name
78+
79+
3.times do |n|
80+
limit_backend.save_trace(
81+
"Trace 2-#{n}",
82+
1.5,
83+
5020 + n,
84+
"some-data"
85+
)
86+
end
87+
88+
all_traces = limit_backend.traces(last: nil, before: nil)
89+
assert_equal 10, all_traces.size
90+
assert_equal "Trace 2-2", all_traces.first.operation_name
91+
assert_equal "Trace3", all_traces.last.operation_name
92+
end
6093
end
6194
end
6295
end

spec/graphql/tracing/perfetto_sampler/memory_backend_spec.rb

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44

55
describe GraphQL::Tracing::PerfettoSampler::MemoryBackend do
66
include GraphQLTracingPerfettoSamplerBackendAssertions
7-
8-
before do
9-
@backend = GraphQL::Tracing::PerfettoSampler::MemoryBackend.new
7+
def new_backend(**kwargs)
8+
GraphQL::Tracing::PerfettoSampler::MemoryBackend.new(**kwargs)
109
end
1110
end

spec/graphql/tracing/perfetto_sampler/redis_backend_spec.rb

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@
55
if testing_redis?
66
describe GraphQL::Tracing::PerfettoSampler::RedisBackend do
77
include GraphQLTracingPerfettoSamplerBackendAssertions
8-
9-
before do
10-
@backend = GraphQL::Tracing::PerfettoSampler::RedisBackend.new(redis: Redis.new)
8+
def new_backend(**kwargs)
9+
GraphQL::Tracing::PerfettoSampler::RedisBackend.new(redis: Redis.new, **kwargs)
1110
end
1211
end
1312
end

0 commit comments

Comments
 (0)