appsignal 2.8.4-java → 2.9.0-java
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/.gitignore +1 -1
- data/.rubocop_todo.yml +7 -16
- data/.travis.yml +4 -1
- data/CHANGELOG.md +16 -0
- data/README.md +23 -0
- data/Rakefile +10 -7
- data/appsignal.gemspec +3 -0
- data/build_matrix.yml +5 -1
- data/ext/Rakefile +23 -16
- data/ext/agent.yml +37 -37
- data/ext/base.rb +86 -24
- data/ext/extconf.rb +33 -26
- data/gemfiles/rails-6.0.gemfile +5 -0
- data/lib/appsignal.rb +14 -489
- data/lib/appsignal/cli/diagnose.rb +84 -4
- data/lib/appsignal/cli/diagnose/paths.rb +0 -5
- data/lib/appsignal/cli/diagnose/utils.rb +17 -0
- data/lib/appsignal/cli/helpers.rb +6 -0
- data/lib/appsignal/cli/install.rb +13 -7
- data/lib/appsignal/config.rb +1 -2
- data/lib/appsignal/event_formatter.rb +4 -5
- data/lib/appsignal/event_formatter/moped/query_formatter.rb +60 -59
- data/lib/appsignal/extension.rb +2 -2
- data/lib/appsignal/helpers/instrumentation.rb +485 -0
- data/lib/appsignal/helpers/metrics.rb +55 -0
- data/lib/appsignal/hooks.rb +9 -8
- data/lib/appsignal/hooks/puma.rb +65 -9
- data/lib/appsignal/hooks/sidekiq.rb +90 -0
- data/lib/appsignal/integrations/mongo_ruby_driver.rb +7 -0
- data/lib/appsignal/integrations/railtie.rb +2 -1
- data/lib/appsignal/marker.rb +2 -3
- data/lib/appsignal/minutely.rb +164 -14
- data/lib/appsignal/rack/sinatra_instrumentation.rb +1 -1
- data/lib/appsignal/system.rb +16 -18
- data/lib/appsignal/utils/rails_helper.rb +16 -0
- data/lib/appsignal/version.rb +1 -1
- data/spec/lib/appsignal/cli/diagnose_spec.rb +129 -22
- data/spec/lib/appsignal/cli/install_spec.rb +6 -1
- data/spec/lib/appsignal/config_spec.rb +3 -3
- data/spec/lib/appsignal/event_formatter/moped/query_formatter_spec.rb +6 -0
- data/spec/lib/appsignal/event_formatter_spec.rb +168 -69
- data/spec/lib/appsignal/hooks/puma_spec.rb +129 -0
- data/spec/lib/appsignal/hooks/sidekiq_spec.rb +147 -0
- data/spec/lib/appsignal/integrations/mongo_ruby_driver_spec.rb +24 -1
- data/spec/lib/appsignal/minutely_spec.rb +251 -21
- data/spec/lib/appsignal/system_spec.rb +0 -35
- data/spec/lib/appsignal/utils/hash_sanitizer_spec.rb +39 -31
- data/spec/lib/appsignal/utils/json_spec.rb +7 -3
- data/spec/lib/appsignal_spec.rb +27 -2
- data/spec/spec_helper.rb +13 -0
- data/spec/support/helpers/log_helpers.rb +6 -0
- data/spec/support/project_fixture/config/appsignal.yml +1 -0
- data/spec/support/stubs/sidekiq/api.rb +4 -0
- metadata +8 -2
@@ -2,6 +2,9 @@ describe Appsignal::Hooks::PumaHook do
|
|
2
2
|
context "with puma" do
|
3
3
|
before(:context) do
|
4
4
|
class Puma
|
5
|
+
def self.stats
|
6
|
+
end
|
7
|
+
|
5
8
|
def self.cli_config
|
6
9
|
@cli_config ||= CliConfig.new
|
7
10
|
end
|
@@ -41,6 +44,11 @@ describe Appsignal::Hooks::PumaHook do
|
|
41
44
|
|
42
45
|
cluster.stop_workers
|
43
46
|
end
|
47
|
+
|
48
|
+
it "adds the Puma minutely probe" do
|
49
|
+
probe = Appsignal::Minutely.probes[:puma]
|
50
|
+
expect(probe).to eql(Appsignal::Hooks::PumaProbe)
|
51
|
+
end
|
44
52
|
end
|
45
53
|
|
46
54
|
context "with nil hooks" do
|
@@ -78,3 +86,124 @@ describe Appsignal::Hooks::PumaHook do
|
|
78
86
|
end
|
79
87
|
end
|
80
88
|
end
|
89
|
+
|
90
|
+
describe Appsignal::Hooks::PumaProbe do
|
91
|
+
before(:context) do
|
92
|
+
Appsignal.config = project_fixture_config
|
93
|
+
end
|
94
|
+
after(:context) do
|
95
|
+
Appsignal.config = nil
|
96
|
+
end
|
97
|
+
|
98
|
+
let(:probe) { Appsignal::Hooks::PumaProbe.new }
|
99
|
+
|
100
|
+
describe "hostname" do
|
101
|
+
it "returns the socket hostname" do
|
102
|
+
expect(probe.send(:hostname)).to eql(Socket.gethostname)
|
103
|
+
end
|
104
|
+
|
105
|
+
context "with overridden hostname" do
|
106
|
+
around do |sample|
|
107
|
+
Appsignal.config[:hostname] = "frontend1"
|
108
|
+
sample.run
|
109
|
+
Appsignal.config[:hostname] = nil
|
110
|
+
end
|
111
|
+
it "returns the configured host" do
|
112
|
+
expect(probe.send(:hostname)).to eql("frontend1")
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
describe "#call" do
|
118
|
+
let(:expected_default_tags) { { :hostname => Socket.gethostname } }
|
119
|
+
|
120
|
+
context "with multiple worker stats" do
|
121
|
+
before(:context) do
|
122
|
+
class Puma
|
123
|
+
def self.stats
|
124
|
+
{
|
125
|
+
"workers" => 2,
|
126
|
+
"booted_workers" => 2,
|
127
|
+
"old_workers" => 0,
|
128
|
+
"worker_status" => [
|
129
|
+
{
|
130
|
+
"last_status" => {
|
131
|
+
"backlog" => 0,
|
132
|
+
"running" => 5,
|
133
|
+
"pool_capacity" => 5,
|
134
|
+
"max_threads" => 5
|
135
|
+
}
|
136
|
+
},
|
137
|
+
{
|
138
|
+
"last_status" => {
|
139
|
+
"backlog" => 0,
|
140
|
+
"running" => 5,
|
141
|
+
"pool_capacity" => 5,
|
142
|
+
"max_threads" => 5
|
143
|
+
}
|
144
|
+
}
|
145
|
+
]
|
146
|
+
}.to_json
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
after(:context) { Object.send(:remove_const, :Puma) }
|
151
|
+
|
152
|
+
it "calls `puma_gauge` with the (summed) worker metrics" do
|
153
|
+
expect_gauge(:workers, 2, :type => :count)
|
154
|
+
expect_gauge(:workers, 2, :type => :booted)
|
155
|
+
expect_gauge(:workers, 0, :type => :old)
|
156
|
+
|
157
|
+
expect_gauge(:connection_backlog, 0)
|
158
|
+
expect_gauge(:pool_capacity, 10)
|
159
|
+
expect_gauge(:threads, 10, :type => :running)
|
160
|
+
expect_gauge(:threads, 10, :type => :max)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
context "with single worker stats" do
|
165
|
+
before(:context) do
|
166
|
+
class Puma
|
167
|
+
def self.stats
|
168
|
+
{
|
169
|
+
"backlog" => 0,
|
170
|
+
"running" => 5,
|
171
|
+
"pool_capacity" => 5,
|
172
|
+
"max_threads" => 5
|
173
|
+
}.to_json
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
after(:context) { Object.send(:remove_const, :Puma) }
|
178
|
+
|
179
|
+
it "calls `puma_gauge` with the (summed) worker metrics" do
|
180
|
+
expect_gauge(:connection_backlog, 0)
|
181
|
+
expect_gauge(:pool_capacity, 5)
|
182
|
+
expect_gauge(:threads, 5, :type => :running)
|
183
|
+
expect_gauge(:threads, 5, :type => :max)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
context "without stats" do
|
188
|
+
before(:context) do
|
189
|
+
class Puma
|
190
|
+
def self.stats
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
after(:context) { Object.send(:remove_const, :Puma) }
|
195
|
+
|
196
|
+
it "does not track metrics" do
|
197
|
+
expect(probe).to_not receive(:puma_gauge)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
after { probe.call }
|
202
|
+
|
203
|
+
def expect_gauge(key, value, tags = {})
|
204
|
+
expect(Appsignal).to receive(:set_gauge)
|
205
|
+
.with("puma_#{key}", value, expected_default_tags.merge(tags))
|
206
|
+
.and_call_original
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
@@ -431,6 +431,11 @@ describe Appsignal::Hooks::SidekiqPlugin, :with_yaml_parse_error => false do
|
|
431
431
|
let(:error) { ExampleException }
|
432
432
|
|
433
433
|
it "creates a transaction and adds the error" do
|
434
|
+
expect(Appsignal).to receive(:increment_counter)
|
435
|
+
.with("sidekiq_queue_job_count", 1, :queue => "default", :status => :failed)
|
436
|
+
expect(Appsignal).to receive(:increment_counter)
|
437
|
+
.with("sidekiq_queue_job_count", 1, :queue => "default", :status => :processed)
|
438
|
+
|
434
439
|
expect do
|
435
440
|
perform_job { raise error, "uh oh" }
|
436
441
|
end.to raise_error(error)
|
@@ -466,6 +471,9 @@ describe Appsignal::Hooks::SidekiqPlugin, :with_yaml_parse_error => false do
|
|
466
471
|
|
467
472
|
context "without an error" do
|
468
473
|
it "creates a transaction with events" do
|
474
|
+
expect(Appsignal).to receive(:increment_counter)
|
475
|
+
.with("sidekiq_queue_job_count", 1, :queue => "default", :status => :processed)
|
476
|
+
|
469
477
|
perform_job
|
470
478
|
|
471
479
|
transaction_hash = transaction.to_h
|
@@ -541,6 +549,7 @@ describe Appsignal::Hooks::SidekiqHook do
|
|
541
549
|
|
542
550
|
describe "#install" do
|
543
551
|
before do
|
552
|
+
Appsignal.config = project_fixture_config
|
544
553
|
class Sidekiq
|
545
554
|
def self.middlewares
|
546
555
|
@middlewares ||= Set.new
|
@@ -564,3 +573,141 @@ describe Appsignal::Hooks::SidekiqHook do
|
|
564
573
|
end
|
565
574
|
end
|
566
575
|
end
|
576
|
+
|
577
|
+
describe Appsignal::Hooks::SidekiqProbe do
|
578
|
+
describe "#call" do
|
579
|
+
let(:probe) { described_class.new }
|
580
|
+
let(:redis_hostname) { "localhost" }
|
581
|
+
let(:expected_default_tags) { { :hostname => "localhost" } }
|
582
|
+
before do
|
583
|
+
Appsignal.config = project_fixture_config
|
584
|
+
class Sidekiq
|
585
|
+
def self.redis_info
|
586
|
+
{
|
587
|
+
"connected_clients" => 2,
|
588
|
+
"used_memory" => 1024,
|
589
|
+
"used_memory_rss" => 512
|
590
|
+
}
|
591
|
+
end
|
592
|
+
|
593
|
+
def self.redis
|
594
|
+
yield Client.new
|
595
|
+
end
|
596
|
+
|
597
|
+
class Client
|
598
|
+
def connection
|
599
|
+
{ :host => "localhost" }
|
600
|
+
end
|
601
|
+
end
|
602
|
+
|
603
|
+
class Stats
|
604
|
+
class << self
|
605
|
+
attr_reader :calls
|
606
|
+
|
607
|
+
def count_call
|
608
|
+
@calls ||= -1
|
609
|
+
@calls += 1
|
610
|
+
end
|
611
|
+
end
|
612
|
+
|
613
|
+
def workers_size
|
614
|
+
# First method called, so count it towards a call
|
615
|
+
self.class.count_call
|
616
|
+
24
|
617
|
+
end
|
618
|
+
|
619
|
+
def processes_size
|
620
|
+
25
|
621
|
+
end
|
622
|
+
|
623
|
+
# Return two different values for two separate calls.
|
624
|
+
# This allows us to test the delta of the value send as a gauge.
|
625
|
+
def processed
|
626
|
+
[10, 15][self.class.calls]
|
627
|
+
end
|
628
|
+
|
629
|
+
# Return two different values for two separate calls.
|
630
|
+
# This allows us to test the delta of the value send as a gauge.
|
631
|
+
def failed
|
632
|
+
[10, 13][self.class.calls]
|
633
|
+
end
|
634
|
+
|
635
|
+
def retry_size
|
636
|
+
12
|
637
|
+
end
|
638
|
+
|
639
|
+
# Return two different values for two separate calls.
|
640
|
+
# This allows us to test the delta of the value send as a gauge.
|
641
|
+
def dead_size
|
642
|
+
[10, 12][self.class.calls]
|
643
|
+
end
|
644
|
+
|
645
|
+
def scheduled_size
|
646
|
+
14
|
647
|
+
end
|
648
|
+
|
649
|
+
def enqueued
|
650
|
+
15
|
651
|
+
end
|
652
|
+
end
|
653
|
+
|
654
|
+
class Queue
|
655
|
+
Queue = Struct.new(:name, :size, :latency)
|
656
|
+
|
657
|
+
def self.all
|
658
|
+
[
|
659
|
+
Queue.new("default", 10, 12),
|
660
|
+
Queue.new("critical", 1, 2)
|
661
|
+
]
|
662
|
+
end
|
663
|
+
end
|
664
|
+
end
|
665
|
+
end
|
666
|
+
after { Object.send(:remove_const, "Sidekiq") }
|
667
|
+
|
668
|
+
it "loads Sidekiq::API" do
|
669
|
+
expect(defined?(Sidekiq::API)).to be_falsy
|
670
|
+
probe
|
671
|
+
expect(defined?(Sidekiq::API)).to be_truthy
|
672
|
+
end
|
673
|
+
|
674
|
+
it "collects custom metrics" do
|
675
|
+
expect_gauge("worker_count", 24).twice
|
676
|
+
expect_gauge("process_count", 25).twice
|
677
|
+
expect_gauge("connection_count", 2).twice
|
678
|
+
expect_gauge("memory_usage", 1024).twice
|
679
|
+
expect_gauge("memory_usage_rss", 512).twice
|
680
|
+
expect_gauge("job_count", 5, :status => :processed) # Gauge delta
|
681
|
+
expect_gauge("job_count", 3, :status => :failed) # Gauge delta
|
682
|
+
expect_gauge("job_count", 12, :status => :retry_queue).twice
|
683
|
+
expect_gauge("job_count", 2, :status => :died) # Gauge delta
|
684
|
+
expect_gauge("job_count", 14, :status => :scheduled).twice
|
685
|
+
expect_gauge("job_count", 15, :status => :enqueued).twice
|
686
|
+
expect_gauge("queue_length", 10, :queue => "default").twice
|
687
|
+
expect_gauge("queue_latency", 12, :queue => "default").twice
|
688
|
+
expect_gauge("queue_length", 1, :queue => "critical").twice
|
689
|
+
expect_gauge("queue_latency", 2, :queue => "critical").twice
|
690
|
+
# Call probe twice so we can calculate the delta for some gauge values
|
691
|
+
probe.call
|
692
|
+
probe.call
|
693
|
+
end
|
694
|
+
|
695
|
+
context "when hostname is configured for probe" do
|
696
|
+
let(:redis_hostname) { "my_redis_server" }
|
697
|
+
let(:probe) { described_class.new(:hostname => redis_hostname) }
|
698
|
+
|
699
|
+
it "uses the redis hostname for the hostname tag" do
|
700
|
+
allow(Appsignal).to receive(:set_gauge).and_call_original
|
701
|
+
probe.call
|
702
|
+
expect(Appsignal).to have_received(:set_gauge)
|
703
|
+
.with(anything, anything, :hostname => redis_hostname).at_least(:once)
|
704
|
+
end
|
705
|
+
end
|
706
|
+
|
707
|
+
def expect_gauge(key, value, tags = {})
|
708
|
+
expect(Appsignal).to receive(:set_gauge)
|
709
|
+
.with("sidekiq_#{key}", value, expected_default_tags.merge(tags))
|
710
|
+
.and_call_original
|
711
|
+
end
|
712
|
+
end
|
713
|
+
end
|
@@ -62,7 +62,8 @@ describe Appsignal::Hooks::MongoMonitorSubscriber do
|
|
62
62
|
double(
|
63
63
|
:request_id => 2,
|
64
64
|
:command_name => :find,
|
65
|
-
:database_name => "test"
|
65
|
+
:database_name => "test",
|
66
|
+
:duration => 0.9919
|
66
67
|
)
|
67
68
|
end
|
68
69
|
|
@@ -71,6 +72,16 @@ describe Appsignal::Hooks::MongoMonitorSubscriber do
|
|
71
72
|
store[2] = command
|
72
73
|
end
|
73
74
|
|
75
|
+
it "should emit a measurement" do
|
76
|
+
expect(Appsignal).to receive(:add_distribution_value).with(
|
77
|
+
"mongodb_query_duration",
|
78
|
+
0.9919,
|
79
|
+
:database => "test"
|
80
|
+
).and_call_original
|
81
|
+
|
82
|
+
subscriber.finish("SUCCEEDED", event)
|
83
|
+
end
|
84
|
+
|
74
85
|
it "should get the query from the store" do
|
75
86
|
expect(transaction).to receive(:store).with("mongo_driver").and_return(command)
|
76
87
|
|
@@ -106,6 +117,12 @@ describe Appsignal::Hooks::MongoMonitorSubscriber do
|
|
106
117
|
|
107
118
|
subscriber.finish("SUCCEEDED", double)
|
108
119
|
end
|
120
|
+
|
121
|
+
it "should not attempt to send duration metrics" do
|
122
|
+
expect(Appsignal).to_not receive(:add_distribution_value)
|
123
|
+
|
124
|
+
subscriber.finish("SUCCEEDED", double)
|
125
|
+
end
|
109
126
|
end
|
110
127
|
|
111
128
|
context "when appsignal is paused" do
|
@@ -123,5 +140,11 @@ describe Appsignal::Hooks::MongoMonitorSubscriber do
|
|
123
140
|
|
124
141
|
subscriber.finish("SUCCEEDED", double)
|
125
142
|
end
|
143
|
+
|
144
|
+
it "should not attempt to send duration metrics" do
|
145
|
+
expect(Appsignal).to_not receive(:add_distribution_value)
|
146
|
+
|
147
|
+
subscriber.finish("SUCCEEDED", double)
|
148
|
+
end
|
126
149
|
end
|
127
150
|
end
|
@@ -1,46 +1,276 @@
|
|
1
1
|
describe Appsignal::Minutely do
|
2
|
-
before
|
3
|
-
Appsignal::Minutely.probes.clear
|
4
|
-
end
|
2
|
+
before { Appsignal::Minutely.probes.clear }
|
5
3
|
|
6
|
-
it "
|
7
|
-
expect(Appsignal::Minutely.probes)
|
4
|
+
it "returns a ProbeCollection" do
|
5
|
+
expect(Appsignal::Minutely.probes)
|
6
|
+
.to be_instance_of(Appsignal::Minutely::ProbeCollection)
|
8
7
|
end
|
9
8
|
|
10
9
|
describe ".start" do
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
10
|
+
class Probe
|
11
|
+
attr_reader :calls
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@calls = 0
|
15
|
+
end
|
16
|
+
|
17
|
+
def call
|
18
|
+
@calls += 1
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class BrokenProbe < Probe
|
23
|
+
def call
|
24
|
+
super
|
25
|
+
raise "oh no!"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
let(:log_stream) { StringIO.new }
|
30
|
+
let(:log) do
|
31
|
+
log_stream.rewind
|
32
|
+
log_stream.read
|
33
|
+
end
|
34
|
+
before do
|
35
|
+
Appsignal.logger = Logger.new(log_stream)
|
36
|
+
# Speed up test time
|
37
|
+
allow(Appsignal::Minutely).to receive(:wait_time).and_return(0.001)
|
38
|
+
end
|
39
|
+
|
40
|
+
context "with an instance of a class" do
|
41
|
+
it "calls the probe every <wait_time>" do
|
42
|
+
probe = Probe.new
|
43
|
+
Appsignal::Minutely.probes.register :my_probe, probe
|
44
|
+
Appsignal::Minutely.start
|
45
|
+
|
46
|
+
wait_for("enough probe calls") { probe.calls >= 2 }
|
47
|
+
expect(log).to include("Gathering minutely metrics with 1 probe")
|
48
|
+
expect(log).to include("Gathering minutely metrics with 'my_probe' probe")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context "with probe class" do
|
53
|
+
it "creates an instance of the class and call that every <wait time>" do
|
54
|
+
probe = Probe
|
55
|
+
probe_instance = Probe.new
|
56
|
+
expect(probe).to receive(:new).and_return(probe_instance)
|
57
|
+
Appsignal::Minutely.probes.register :my_probe, probe
|
58
|
+
Appsignal::Minutely.start
|
59
|
+
|
60
|
+
wait_for("enough probe calls") { probe_instance.calls >= 2 }
|
61
|
+
expect(log).to include("Gathering minutely metrics with 1 probe")
|
62
|
+
expect(log).to include("Gathering minutely metrics with 'my_probe' probe")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context "with a lambda" do
|
67
|
+
it "calls the lambda every <wait time>" do
|
68
|
+
calls = 0
|
69
|
+
probe = -> { calls += 1 }
|
70
|
+
Appsignal::Minutely.probes.register :my_probe, probe
|
71
|
+
Appsignal::Minutely.start
|
72
|
+
|
73
|
+
wait_for("enough probe calls") { calls >= 2 }
|
74
|
+
expect(log).to include("Gathering minutely metrics with 1 probe")
|
75
|
+
expect(log).to include("Gathering minutely metrics with 'my_probe' probe")
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
context "with a broken probe" do
|
80
|
+
it "logs the error and continues calling the probes every <wait_time>" do
|
81
|
+
probe = Probe.new
|
82
|
+
broken_probe = BrokenProbe.new
|
83
|
+
Appsignal::Minutely.probes.register :my_probe, probe
|
84
|
+
Appsignal::Minutely.probes.register :broken_probe, broken_probe
|
85
|
+
Appsignal::Minutely.start
|
86
|
+
|
87
|
+
wait_for("enough probe calls") { probe.calls >= 2 }
|
88
|
+
wait_for("enough broken_probe calls") { broken_probe.calls >= 2 }
|
89
|
+
|
90
|
+
expect(log).to include("Gathering minutely metrics with 2 probes")
|
91
|
+
expect(log).to include("Gathering minutely metrics with 'my_probe' probe")
|
92
|
+
expect(log).to include("Gathering minutely metrics with 'broken_probe' probe")
|
93
|
+
expect(log).to include("Error in minutely probe 'broken_probe': oh no!")
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
it "ensures only one minutely probes thread is active at a time" do
|
98
|
+
alive_thread_counter = proc { Thread.list.reject { |t| t.status == "dead" }.length }
|
99
|
+
probe = Probe.new
|
100
|
+
Appsignal::Minutely.probes.register :my_probe, probe
|
101
|
+
expect do
|
102
|
+
Appsignal::Minutely.start
|
103
|
+
end.to change { alive_thread_counter.call }.by(1)
|
104
|
+
|
105
|
+
wait_for("enough probe calls") { probe.calls >= 2 }
|
106
|
+
expect(log).to include("Gathering minutely metrics with 1 probe")
|
107
|
+
expect(log).to include("Gathering minutely metrics with 'my_probe' probe")
|
108
|
+
expect do
|
109
|
+
# Fetch old thread
|
110
|
+
thread = Appsignal::Minutely.class_variable_get(:@@thread)
|
111
|
+
Appsignal::Minutely.start
|
112
|
+
thread && thread.join # Wait for old thread to exit
|
113
|
+
end.to_not(change { alive_thread_counter.call })
|
114
|
+
end
|
16
115
|
|
116
|
+
# Wait for a condition to be met
|
117
|
+
#
|
118
|
+
# @example
|
119
|
+
# # Perform threaded operation
|
120
|
+
# wait_for("enough probe calls") { probe.calls >= 2 }
|
121
|
+
# # Assert on result
|
122
|
+
#
|
123
|
+
# @param name [String] The name of the condition to check. Used in the
|
124
|
+
# error when it fails.
|
125
|
+
# @yield Assertion to check.
|
126
|
+
# @yieldreturn [Boolean] True/False value that indicates if the condition
|
127
|
+
# is met.
|
128
|
+
# @raise [StandardError] Raises error if the condition is not met after 5
|
129
|
+
# seconds, 5_000 tries.
|
130
|
+
def wait_for(name)
|
131
|
+
max_wait = 5_000
|
132
|
+
i = 0
|
133
|
+
while i <= max_wait
|
134
|
+
break if yield
|
135
|
+
i += 1
|
136
|
+
sleep 0.001
|
137
|
+
end
|
138
|
+
|
139
|
+
return unless i == max_wait
|
140
|
+
raise "Waited 5 seconds for #{name} condition, but was not met."
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
describe ".stop" do
|
145
|
+
it "stops the minutely thread" do
|
17
146
|
Appsignal::Minutely.start
|
147
|
+
thread = Appsignal::Minutely.class_variable_get(:@@thread)
|
148
|
+
expect(%w[sleep run]).to include(thread.status)
|
149
|
+
Appsignal::Minutely.stop
|
150
|
+
thread.join
|
151
|
+
expect(thread.status).to eql(false)
|
152
|
+
end
|
18
153
|
|
19
|
-
|
154
|
+
it "clears the probe instances array" do
|
155
|
+
Appsignal::Minutely.probes.register :my_probe, -> {}
|
156
|
+
Appsignal::Minutely.start
|
157
|
+
thread = Appsignal::Minutely.class_variable_get(:@@thread)
|
158
|
+
expect(Appsignal::Minutely.send(:probe_instances)).to_not be_empty
|
159
|
+
Appsignal::Minutely.stop
|
160
|
+
thread.join
|
161
|
+
expect(Appsignal::Minutely.send(:probe_instances)).to be_empty
|
20
162
|
end
|
21
163
|
end
|
22
164
|
|
23
165
|
describe ".wait_time" do
|
24
|
-
it "
|
25
|
-
allow_any_instance_of(Time).to receive(:sec).and_return(
|
26
|
-
expect(Appsignal::Minutely.wait_time).to eq
|
166
|
+
it "gets the time to the next minute" do
|
167
|
+
allow_any_instance_of(Time).to receive(:sec).and_return(20)
|
168
|
+
expect(Appsignal::Minutely.wait_time).to eq 40
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
describe ".register_garbage_collection_probe" do
|
173
|
+
it "adds the GC probe to the probes list" do
|
174
|
+
expect(Appsignal::Minutely.probes.count).to eql(0)
|
175
|
+
|
176
|
+
Appsignal::Minutely.register_garbage_collection_probe
|
177
|
+
|
178
|
+
expect(Appsignal::Minutely.probes.count).to eql(1)
|
179
|
+
expect(Appsignal::Minutely.probes[:garbage_collection])
|
180
|
+
.to be_instance_of(Appsignal::Minutely::GCProbe)
|
27
181
|
end
|
28
182
|
end
|
29
183
|
|
30
|
-
describe
|
31
|
-
|
32
|
-
expect(Appsignal::Minutely.probes).to be_empty
|
184
|
+
describe Appsignal::Minutely::ProbeCollection do
|
185
|
+
let(:collection) { described_class.new }
|
33
186
|
|
34
|
-
|
187
|
+
describe "#count" do
|
188
|
+
it "returns how many probes are registered" do
|
189
|
+
expect(collection.count).to eql(0)
|
190
|
+
collection.register :my_probe_1, -> {}
|
191
|
+
expect(collection.count).to eql(1)
|
192
|
+
collection.register :my_probe_2, -> {}
|
193
|
+
expect(collection.count).to eql(2)
|
194
|
+
end
|
195
|
+
end
|
35
196
|
|
36
|
-
|
37
|
-
|
197
|
+
describe "#clear" do
|
198
|
+
it "clears the list of probes" do
|
199
|
+
collection.register :my_probe_1, -> {}
|
200
|
+
collection.register :my_probe_2, -> {}
|
201
|
+
expect(collection.count).to eql(2)
|
202
|
+
collection.clear
|
203
|
+
expect(collection.count).to eql(0)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
describe "#[]" do
|
208
|
+
it "returns the probe for that name" do
|
209
|
+
probe = -> {}
|
210
|
+
collection.register :my_probe, probe
|
211
|
+
expect(collection[:my_probe]).to eql(probe)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
describe "#<<" do
|
216
|
+
let(:out_stream) { std_stream }
|
217
|
+
let(:output) { out_stream.read }
|
218
|
+
let(:log_stream) { std_stream }
|
219
|
+
let(:log) { log_contents(log_stream) }
|
220
|
+
before { Appsignal.logger = test_logger(log_stream) }
|
221
|
+
|
222
|
+
it "adds the probe, but prints a deprecation warning" do
|
223
|
+
probe = -> {}
|
224
|
+
capture_stdout(out_stream) { collection << probe }
|
225
|
+
deprecation_message = "Deprecated " \
|
226
|
+
"`Appsignal::Minute.probes <<` call. Please use " \
|
227
|
+
"`Appsignal::Minutely.probes.register` instead."
|
228
|
+
expect(output).to include "appsignal WARNING: #{deprecation_message}"
|
229
|
+
expect(log).to contains_log :warn, deprecation_message
|
230
|
+
expect(collection[probe.object_id]).to eql(probe)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
describe "#register" do
|
235
|
+
let(:log_stream) { std_stream }
|
236
|
+
let(:log) { log_contents(log_stream) }
|
237
|
+
before { Appsignal.logger = test_logger(log_stream) }
|
238
|
+
|
239
|
+
it "adds the by key probe" do
|
240
|
+
probe = -> {}
|
241
|
+
collection.register :my_probe, probe
|
242
|
+
expect(collection[:my_probe]).to eql(probe)
|
243
|
+
end
|
244
|
+
|
245
|
+
context "when a probe is already registered with the same key" do
|
246
|
+
it "logs a debug message" do
|
247
|
+
probe = -> {}
|
248
|
+
collection.register :my_probe, probe
|
249
|
+
collection.register :my_probe, probe
|
250
|
+
expect(log).to contains_log :debug, "A probe with the name " \
|
251
|
+
"`my_probe` is already registered. Overwriting the entry " \
|
252
|
+
"with the new probe."
|
253
|
+
expect(collection[:my_probe]).to eql(probe)
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
describe "#each" do
|
259
|
+
it "loops over the registered probes" do
|
260
|
+
probe = -> {}
|
261
|
+
collection.register :my_probe, probe
|
262
|
+
list = []
|
263
|
+
collection.each do |name, p|
|
264
|
+
list << [name, p]
|
265
|
+
end
|
266
|
+
expect(list).to eql([[:my_probe, probe]])
|
267
|
+
end
|
38
268
|
end
|
39
269
|
end
|
40
270
|
|
41
271
|
describe Appsignal::Minutely::GCProbe do
|
42
272
|
describe "#call" do
|
43
|
-
it "
|
273
|
+
it "collects GC metrics" do
|
44
274
|
expect(Appsignal).to receive(:set_process_gauge).at_least(8).times
|
45
275
|
|
46
276
|
Appsignal::Minutely::GCProbe.new.call
|