appsignal 2.9.2.alpha.1 → 2.9.18.beta.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE/bug_report.md +31 -0
- data/.github/ISSUE_TEMPLATE/chore.md +14 -0
- data/.gitignore +1 -2
- data/.rubocop.yml +3 -0
- data/.travis.yml +25 -27
- data/CHANGELOG.md +632 -535
- data/README.md +8 -3
- data/Rakefile +118 -122
- data/SUPPORT.md +16 -0
- data/appsignal.gemspec +14 -4
- data/build_matrix.yml +16 -8
- data/ext/Rakefile +2 -3
- data/ext/agent.yml +40 -37
- data/ext/base.rb +37 -14
- data/ext/extconf.rb +3 -4
- data/gemfiles/capistrano2.gemfile +5 -0
- data/gemfiles/capistrano3.gemfile +5 -0
- data/gemfiles/grape.gemfile +5 -0
- data/gemfiles/no_dependencies.gemfile +5 -0
- data/gemfiles/padrino.gemfile +5 -0
- data/gemfiles/que.gemfile +5 -0
- data/gemfiles/que_beta.gemfile +10 -0
- data/gemfiles/rails-3.2.gemfile +5 -0
- data/gemfiles/rails-4.0.gemfile +5 -0
- data/gemfiles/rails-4.1.gemfile +5 -0
- data/gemfiles/rails-4.2.gemfile +5 -0
- data/gemfiles/rails-6.0.gemfile +1 -1
- data/gemfiles/resque.gemfile +5 -0
- data/lib/appsignal.rb +1 -4
- data/lib/appsignal/cli/demo.rb +5 -2
- data/lib/appsignal/cli/diagnose/utils.rb +2 -0
- data/lib/appsignal/cli/install.rb +34 -10
- data/lib/appsignal/cli/notify_of_deploy.rb +10 -0
- data/lib/appsignal/event_formatter/action_view/render_formatter.rb +10 -8
- data/lib/appsignal/helpers/instrumentation.rb +18 -9
- data/lib/appsignal/helpers/metrics.rb +0 -1
- data/lib/appsignal/hooks.rb +3 -1
- data/lib/appsignal/hooks/active_support_notifications.rb +2 -5
- data/lib/appsignal/hooks/puma.rb +15 -13
- data/lib/appsignal/hooks/sequel.rb +1 -1
- data/lib/appsignal/hooks/sidekiq.rb +33 -8
- data/lib/appsignal/integrations/que.rb +9 -8
- data/lib/appsignal/minutely.rb +38 -19
- data/lib/appsignal/transaction.rb +5 -0
- data/lib/appsignal/utils/rails_helper.rb +4 -0
- data/lib/appsignal/version.rb +1 -1
- data/lib/puma/plugin/appsignal.rb +26 -0
- data/spec/lib/appsignal/cli/diagnose/utils_spec.rb +40 -0
- data/spec/lib/appsignal/cli/install_spec.rb +51 -7
- data/spec/lib/appsignal/cli/notify_of_deploy_spec.rb +10 -0
- data/spec/lib/appsignal/config_spec.rb +10 -8
- data/spec/lib/appsignal/event_formatter/action_view/render_formatter_spec.rb +38 -28
- data/spec/lib/appsignal/hooks/active_support_notifications_spec.rb +104 -25
- data/spec/lib/appsignal/hooks/puma_spec.rb +69 -39
- data/spec/lib/appsignal/hooks/sidekiq_spec.rb +65 -3
- data/spec/lib/appsignal/hooks_spec.rb +4 -0
- data/spec/lib/appsignal/minutely_spec.rb +150 -88
- data/spec/lib/appsignal/transaction_spec.rb +27 -4
- data/spec/lib/appsignal_spec.rb +62 -11
- data/spec/lib/puma/appsignal_spec.rb +91 -0
- data/spec/support/{project_fixture → fixtures/projects/valid}/config/application.rb +0 -0
- data/spec/support/{project_fixture → fixtures/projects/valid}/config/appsignal.yml +0 -0
- data/spec/support/{project_fixture → fixtures/projects/valid}/config/environments/development.rb +0 -0
- data/spec/support/{project_fixture → fixtures/projects/valid}/config/environments/production.rb +0 -0
- data/spec/support/{project_fixture → fixtures/projects/valid}/config/environments/test.rb +0 -0
- data/spec/support/{project_fixture → fixtures/projects/valid}/log/.gitkeep +0 -0
- data/spec/support/helpers/config_helpers.rb +1 -1
- data/spec/support/helpers/wait_for_helper.rb +28 -0
- data/spec/support/mocks/mock_probe.rb +11 -0
- metadata +37 -30
- data/spec/support/fixtures/containers/cgroups/docker +0 -14
- data/spec/support/fixtures/containers/cgroups/docker_systemd +0 -8
- data/spec/support/fixtures/containers/cgroups/lxc +0 -10
- data/spec/support/fixtures/containers/cgroups/no_permission +0 -0
- data/spec/support/fixtures/containers/cgroups/none +0 -1
@@ -8,11 +8,6 @@ describe Appsignal::Hooks::PumaHook do
|
|
8
8
|
def self.cli_config
|
9
9
|
@cli_config ||= CliConfig.new
|
10
10
|
end
|
11
|
-
|
12
|
-
class Cluster
|
13
|
-
def stop_workers
|
14
|
-
end
|
15
|
-
end
|
16
11
|
end
|
17
12
|
|
18
13
|
class CliConfig
|
@@ -31,49 +26,84 @@ describe Appsignal::Hooks::PumaHook do
|
|
31
26
|
it { is_expected.to be_truthy }
|
32
27
|
end
|
33
28
|
|
34
|
-
|
35
|
-
before
|
36
|
-
Appsignal::Hooks::PumaHook.new.install
|
37
|
-
end
|
29
|
+
describe "installation" do
|
30
|
+
before { Appsignal::Minutely.probes.clear }
|
38
31
|
|
39
|
-
|
40
|
-
|
32
|
+
context "when not clustered mode" do
|
33
|
+
it "does not add AppSignal stop behavior Puma::Cluster" do
|
34
|
+
expect(defined?(::Puma::Cluster)).to be_falsy
|
35
|
+
# Does not error on call
|
36
|
+
Appsignal::Hooks::PumaHook.new.install
|
37
|
+
end
|
41
38
|
|
42
|
-
|
43
|
-
|
39
|
+
context "with APPSIGNAL_PUMA_PLUGIN_LOADED defined" do
|
40
|
+
before do
|
41
|
+
# Set in lib/puma/appsignal.rb
|
42
|
+
APPSIGNAL_PUMA_PLUGIN_LOADED = true
|
43
|
+
end
|
44
|
+
after { Object.send :remove_const, :APPSIGNAL_PUMA_PLUGIN_LOADED }
|
44
45
|
|
45
|
-
|
46
|
-
|
46
|
+
it "does not add the Puma minutely probe" do
|
47
|
+
Appsignal::Hooks::PumaHook.new.install
|
48
|
+
expect(Appsignal::Minutely.probes[:puma]).to be_nil
|
49
|
+
end
|
50
|
+
end
|
47
51
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
end
|
52
|
-
end
|
52
|
+
context "without APPSIGNAL_PUMA_PLUGIN_LOADED defined" do
|
53
|
+
it "adds the Puma minutely probe" do
|
54
|
+
expect(defined?(APPSIGNAL_PUMA_PLUGIN_LOADED)).to be_nil
|
53
55
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
56
|
+
Appsignal::Hooks::PumaHook.new.install
|
57
|
+
probe = Appsignal::Minutely.probes[:puma]
|
58
|
+
expect(probe).to eql(Appsignal::Hooks::PumaProbe)
|
59
|
+
end
|
60
|
+
end
|
59
61
|
end
|
60
62
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
63
|
+
context "when in clustered mode" do
|
64
|
+
before do
|
65
|
+
class Puma
|
66
|
+
class Cluster
|
67
|
+
def stop_workers
|
68
|
+
@called = true
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
after { Puma.send(:remove_const, :Cluster) }
|
66
74
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
75
|
+
it "adds behavior to Puma::Cluster.stop_workers" do
|
76
|
+
Appsignal::Hooks::PumaHook.new.install
|
77
|
+
cluster = Puma::Cluster.new
|
78
|
+
|
79
|
+
expect(cluster.instance_variable_defined?(:@called)).to be_falsy
|
80
|
+
expect(Appsignal).to receive(:stop).and_call_original
|
81
|
+
cluster.stop_workers
|
82
|
+
expect(cluster.instance_variable_get(:@called)).to be(true)
|
83
|
+
end
|
84
|
+
|
85
|
+
context "with APPSIGNAL_PUMA_PLUGIN_LOADED defined" do
|
86
|
+
before do
|
87
|
+
# Set in lib/puma/appsignal.rb
|
88
|
+
APPSIGNAL_PUMA_PLUGIN_LOADED = true
|
89
|
+
end
|
90
|
+
after { Object.send :remove_const, :APPSIGNAL_PUMA_PLUGIN_LOADED }
|
91
|
+
|
92
|
+
it "does not add the Puma minutely probe" do
|
93
|
+
Appsignal::Hooks::PumaHook.new.install
|
94
|
+
expect(Appsignal::Minutely.probes[:puma]).to be_nil
|
95
|
+
end
|
96
|
+
end
|
73
97
|
|
74
|
-
|
75
|
-
|
76
|
-
|
98
|
+
context "without APPSIGNAL_PUMA_PLUGIN_LOADED defined" do
|
99
|
+
it "adds the Puma minutely probe" do
|
100
|
+
expect(defined?(APPSIGNAL_PUMA_PLUGIN_LOADED)).to be_nil
|
101
|
+
|
102
|
+
Appsignal::Hooks::PumaHook.new.install
|
103
|
+
probe = Appsignal::Minutely.probes[:puma]
|
104
|
+
expect(probe).to eql(Appsignal::Hooks::PumaProbe)
|
105
|
+
end
|
106
|
+
end
|
77
107
|
end
|
78
108
|
end
|
79
109
|
end
|
@@ -665,12 +665,52 @@ describe Appsignal::Hooks::SidekiqProbe do
|
|
665
665
|
end
|
666
666
|
after { Object.send(:remove_const, "Sidekiq") }
|
667
667
|
|
668
|
+
describe ".dependencies_present?" do
|
669
|
+
before do
|
670
|
+
class Redis; end
|
671
|
+
Redis.const_set(:VERSION, version)
|
672
|
+
end
|
673
|
+
after { Object.send(:remove_const, "Redis") }
|
674
|
+
|
675
|
+
context "when Redis version is < 3.3.5" do
|
676
|
+
let(:version) { "3.3.4" }
|
677
|
+
|
678
|
+
it "does not start probe" do
|
679
|
+
expect(described_class.dependencies_present?).to be_falsy
|
680
|
+
end
|
681
|
+
end
|
682
|
+
|
683
|
+
context "when Redis version is >= 3.3.5" do
|
684
|
+
let(:version) { "3.3.5" }
|
685
|
+
|
686
|
+
it "does not start probe" do
|
687
|
+
expect(described_class.dependencies_present?).to be_truthy
|
688
|
+
end
|
689
|
+
end
|
690
|
+
end
|
691
|
+
|
668
692
|
it "loads Sidekiq::API" do
|
669
693
|
expect(defined?(Sidekiq::API)).to be_falsy
|
670
694
|
probe
|
671
695
|
expect(defined?(Sidekiq::API)).to be_truthy
|
672
696
|
end
|
673
697
|
|
698
|
+
it "logs config on initialize" do
|
699
|
+
log = capture_logs { probe }
|
700
|
+
expect(log).to contains_log(:debug, "Initializing Sidekiq probe\n")
|
701
|
+
end
|
702
|
+
|
703
|
+
it "logs used hostname on call once" do
|
704
|
+
log = capture_logs { probe.call }
|
705
|
+
expect(log).to contains_log(
|
706
|
+
:debug,
|
707
|
+
%(Sidekiq probe: Using Redis server hostname "localhost" as hostname)
|
708
|
+
)
|
709
|
+
log = capture_logs { probe.call }
|
710
|
+
# Match more logs with incompelete message
|
711
|
+
expect(log).to_not contains_log(:debug, %(Sidekiq probe: ))
|
712
|
+
end
|
713
|
+
|
674
714
|
it "collects custom metrics" do
|
675
715
|
expect_gauge("worker_count", 24).twice
|
676
716
|
expect_gauge("process_count", 25).twice
|
@@ -684,21 +724,43 @@ describe Appsignal::Hooks::SidekiqProbe do
|
|
684
724
|
expect_gauge("job_count", 14, :status => :scheduled).twice
|
685
725
|
expect_gauge("job_count", 15, :status => :enqueued).twice
|
686
726
|
expect_gauge("queue_length", 10, :queue => "default").twice
|
687
|
-
expect_gauge("queue_latency",
|
727
|
+
expect_gauge("queue_latency", 12_000, :queue => "default").twice
|
688
728
|
expect_gauge("queue_length", 1, :queue => "critical").twice
|
689
|
-
expect_gauge("queue_latency",
|
729
|
+
expect_gauge("queue_latency", 2_000, :queue => "critical").twice
|
690
730
|
# Call probe twice so we can calculate the delta for some gauge values
|
691
731
|
probe.call
|
692
732
|
probe.call
|
693
733
|
end
|
694
734
|
|
735
|
+
context "when `redis_info` is not defined" do
|
736
|
+
before do
|
737
|
+
allow(Sidekiq).to receive(:respond_to?).with(:redis_info).and_return(false)
|
738
|
+
end
|
739
|
+
|
740
|
+
it "does not collect redis metrics" do
|
741
|
+
expect_gauge("connection_count", 2).never
|
742
|
+
expect_gauge("memory_usage", 1024).never
|
743
|
+
expect_gauge("memory_usage_rss", 512).never
|
744
|
+
probe.call
|
745
|
+
end
|
746
|
+
end
|
747
|
+
|
695
748
|
context "when hostname is configured for probe" do
|
696
749
|
let(:redis_hostname) { "my_redis_server" }
|
697
750
|
let(:probe) { described_class.new(:hostname => redis_hostname) }
|
698
751
|
|
699
752
|
it "uses the redis hostname for the hostname tag" do
|
700
753
|
allow(Appsignal).to receive(:set_gauge).and_call_original
|
701
|
-
probe
|
754
|
+
log = capture_logs { probe }
|
755
|
+
expect(log).to contains_log(
|
756
|
+
:debug,
|
757
|
+
%(Initializing Sidekiq probe with config: {:hostname=>"#{redis_hostname}"})
|
758
|
+
)
|
759
|
+
log = capture_logs { probe.call }
|
760
|
+
expect(log).to contains_log(
|
761
|
+
:debug,
|
762
|
+
"Sidekiq probe: Using hostname config option #{redis_hostname.inspect} as hostname"
|
763
|
+
)
|
702
764
|
expect(Appsignal).to have_received(:set_gauge)
|
703
765
|
.with(anything, anything, :hostname => redis_hostname).at_least(:once)
|
704
766
|
end
|
@@ -68,6 +68,10 @@ describe Appsignal::Hooks do
|
|
68
68
|
expect(Appsignal::Hooks.hooks[:mock_error_hook].installed?).to be_falsy
|
69
69
|
|
70
70
|
expect(Appsignal.logger).to receive(:error).with("Error while installing mock_error_hook hook: error").once
|
71
|
+
expect(Appsignal.logger).to receive(:debug).once do |message|
|
72
|
+
# Start of the error backtrace as debug log
|
73
|
+
expect(message).to start_with(File.expand_path("../../../../", __FILE__))
|
74
|
+
end
|
71
75
|
|
72
76
|
Appsignal::Hooks.load_hooks
|
73
77
|
|
@@ -1,4 +1,6 @@
|
|
1
1
|
describe Appsignal::Minutely do
|
2
|
+
include WaitForHelper
|
3
|
+
|
2
4
|
before { Appsignal::Minutely.probes.clear }
|
3
5
|
|
4
6
|
it "returns a ProbeCollection" do
|
@@ -7,78 +9,151 @@ describe Appsignal::Minutely do
|
|
7
9
|
end
|
8
10
|
|
9
11
|
describe ".start" do
|
10
|
-
class
|
11
|
-
|
12
|
-
|
13
|
-
def initialize
|
14
|
-
@calls = 0
|
12
|
+
class ProbeWithoutDependency < MockProbe
|
13
|
+
def self.dependencies_present?
|
14
|
+
true
|
15
15
|
end
|
16
|
+
end
|
16
17
|
|
17
|
-
|
18
|
-
|
18
|
+
class ProbeWithMissingDependency < MockProbe
|
19
|
+
def self.dependencies_present?
|
20
|
+
false
|
19
21
|
end
|
20
22
|
end
|
21
23
|
|
22
|
-
class BrokenProbe <
|
24
|
+
class BrokenProbe < MockProbe
|
23
25
|
def call
|
24
26
|
super
|
25
27
|
raise "oh no!"
|
26
28
|
end
|
27
29
|
end
|
28
30
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
31
|
+
class BrokenProbeOnInitialize < MockProbe
|
32
|
+
def initialize
|
33
|
+
super
|
34
|
+
raise "oh no initialize!"
|
35
|
+
end
|
36
|
+
|
37
|
+
def call
|
38
|
+
true
|
39
|
+
end
|
33
40
|
end
|
41
|
+
|
42
|
+
let(:log_stream) { StringIO.new }
|
43
|
+
let(:log) { log_contents(log_stream) }
|
34
44
|
before do
|
35
|
-
Appsignal.logger =
|
45
|
+
Appsignal.logger = test_logger(log_stream)
|
36
46
|
# Speed up test time
|
47
|
+
allow(Appsignal::Minutely).to receive(:initial_wait_time).and_return(0.001)
|
37
48
|
allow(Appsignal::Minutely).to receive(:wait_time).and_return(0.001)
|
38
49
|
end
|
39
50
|
|
40
51
|
context "with an instance of a class" do
|
41
52
|
it "calls the probe every <wait_time>" do
|
42
|
-
probe =
|
53
|
+
probe = MockProbe.new
|
43
54
|
Appsignal::Minutely.probes.register :my_probe, probe
|
44
55
|
Appsignal::Minutely.start
|
45
56
|
|
46
57
|
wait_for("enough probe calls") { probe.calls >= 2 }
|
47
|
-
expect(log).to
|
48
|
-
expect(log).to
|
58
|
+
expect(log).to contains_log(:debug, "Gathering minutely metrics with 1 probe")
|
59
|
+
expect(log).to contains_log(:debug, "Gathering minutely metrics with 'my_probe' probe")
|
60
|
+
end
|
61
|
+
|
62
|
+
context "when dependency requirement is not met" do
|
63
|
+
it "does not initialize the probe" do
|
64
|
+
# Working probe which we can use to wait for X ticks
|
65
|
+
working_probe = ProbeWithoutDependency.new
|
66
|
+
Appsignal::Minutely.probes.register :probe_without_dep, working_probe
|
67
|
+
|
68
|
+
probe = ProbeWithMissingDependency.new
|
69
|
+
Appsignal::Minutely.probes.register :probe_with_missing_dep, probe
|
70
|
+
Appsignal::Minutely.start
|
71
|
+
|
72
|
+
wait_for("enough probe calls") { working_probe.calls >= 2 }
|
73
|
+
# Only counts initialized probes
|
74
|
+
expect(log).to contains_log(:debug, "Gathering minutely metrics with 1 probe")
|
75
|
+
expect(log).to contains_log :debug, "Skipping 'probe_with_missing_dep' probe, " \
|
76
|
+
"ProbeWithMissingDependency.dependency_present? returned falsy"
|
77
|
+
end
|
49
78
|
end
|
50
79
|
end
|
51
80
|
|
52
81
|
context "with probe class" do
|
53
82
|
it "creates an instance of the class and call that every <wait time>" do
|
54
|
-
probe =
|
55
|
-
probe_instance =
|
83
|
+
probe = MockProbe
|
84
|
+
probe_instance = MockProbe.new
|
56
85
|
expect(probe).to receive(:new).and_return(probe_instance)
|
57
86
|
Appsignal::Minutely.probes.register :my_probe, probe
|
58
87
|
Appsignal::Minutely.start
|
59
88
|
|
60
89
|
wait_for("enough probe calls") { probe_instance.calls >= 2 }
|
61
|
-
expect(log).to
|
62
|
-
expect(log).to
|
90
|
+
expect(log).to contains_log(:debug, "Gathering minutely metrics with 1 probe")
|
91
|
+
expect(log).to contains_log(:debug, "Gathering minutely metrics with 'my_probe' probe")
|
92
|
+
end
|
93
|
+
|
94
|
+
context "when dependency requirement is not met" do
|
95
|
+
it "does not initialize the probe" do
|
96
|
+
# Working probe which we can use to wait for X ticks
|
97
|
+
working_probe = ProbeWithoutDependency
|
98
|
+
working_probe_instance = working_probe.new
|
99
|
+
expect(working_probe).to receive(:new).and_return(working_probe_instance)
|
100
|
+
Appsignal::Minutely.probes.register :probe_without_dep, working_probe
|
101
|
+
|
102
|
+
probe = ProbeWithMissingDependency
|
103
|
+
Appsignal::Minutely.probes.register :probe_with_missing_dep, probe
|
104
|
+
Appsignal::Minutely.start
|
105
|
+
|
106
|
+
wait_for("enough probe calls") { working_probe_instance.calls >= 2 }
|
107
|
+
# Only counts initialized probes
|
108
|
+
expect(log).to contains_log(:debug, "Gathering minutely metrics with 1 probe")
|
109
|
+
expect(log).to contains_log :debug, "Skipping 'probe_with_missing_dep' probe, " \
|
110
|
+
"ProbeWithMissingDependency.dependency_present? returned falsy"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
context "when there is a problem initializing the probe" do
|
115
|
+
it "logs an error" do
|
116
|
+
# Working probe which we can use to wait for X ticks
|
117
|
+
working_probe = ProbeWithoutDependency
|
118
|
+
working_probe_instance = working_probe.new
|
119
|
+
expect(working_probe).to receive(:new).and_return(working_probe_instance)
|
120
|
+
Appsignal::Minutely.probes.register :probe_without_dep, working_probe
|
121
|
+
|
122
|
+
probe = BrokenProbeOnInitialize
|
123
|
+
Appsignal::Minutely.probes.register :broken_probe_on_initialize, probe
|
124
|
+
Appsignal::Minutely.start
|
125
|
+
|
126
|
+
wait_for("enough probe calls") { working_probe_instance.calls >= 2 }
|
127
|
+
# Only counts initialized probes
|
128
|
+
expect(log).to contains_log(:debug, "Gathering minutely metrics with 1 probe")
|
129
|
+
# Logs error
|
130
|
+
expect(log).to contains_log(
|
131
|
+
:error,
|
132
|
+
"Error while initializing minutely probe 'broken_probe_on_initialize': " \
|
133
|
+
"oh no initialize!"
|
134
|
+
)
|
135
|
+
# Start of the error backtrace as debug log
|
136
|
+
expect(log).to contains_log :debug, File.expand_path("../../../../", __FILE__)
|
137
|
+
end
|
63
138
|
end
|
64
139
|
end
|
65
140
|
|
66
141
|
context "with a lambda" do
|
67
142
|
it "calls the lambda every <wait time>" do
|
68
143
|
calls = 0
|
69
|
-
probe =
|
144
|
+
probe = lambda { calls += 1 }
|
70
145
|
Appsignal::Minutely.probes.register :my_probe, probe
|
71
146
|
Appsignal::Minutely.start
|
72
147
|
|
73
148
|
wait_for("enough probe calls") { calls >= 2 }
|
74
|
-
expect(log).to
|
75
|
-
expect(log).to
|
149
|
+
expect(log).to contains_log(:debug, "Gathering minutely metrics with 1 probe")
|
150
|
+
expect(log).to contains_log(:debug, "Gathering minutely metrics with 'my_probe' probe")
|
76
151
|
end
|
77
152
|
end
|
78
153
|
|
79
154
|
context "with a broken probe" do
|
80
155
|
it "logs the error and continues calling the probes every <wait_time>" do
|
81
|
-
probe =
|
156
|
+
probe = MockProbe.new
|
82
157
|
broken_probe = BrokenProbe.new
|
83
158
|
Appsignal::Minutely.probes.register :my_probe, probe
|
84
159
|
Appsignal::Minutely.probes.register :broken_probe, broken_probe
|
@@ -87,24 +162,31 @@ describe Appsignal::Minutely do
|
|
87
162
|
wait_for("enough probe calls") { probe.calls >= 2 }
|
88
163
|
wait_for("enough broken_probe calls") { broken_probe.calls >= 2 }
|
89
164
|
|
90
|
-
expect(log).to
|
91
|
-
expect(log).to
|
92
|
-
expect(log).to
|
93
|
-
expect(log).to
|
165
|
+
expect(log).to contains_log(:debug, "Gathering minutely metrics with 2 probes")
|
166
|
+
expect(log).to contains_log(:debug, "Gathering minutely metrics with 'my_probe' probe")
|
167
|
+
expect(log).to contains_log(:debug, "Gathering minutely metrics with 'broken_probe' probe")
|
168
|
+
expect(log).to contains_log(:error, "Error in minutely probe 'broken_probe': oh no!")
|
169
|
+
gem_path = File.expand_path("../../../../", __FILE__) # Start of backtrace
|
170
|
+
expect(log).to contains_log(:debug, gem_path)
|
94
171
|
end
|
95
172
|
end
|
96
173
|
|
97
174
|
it "ensures only one minutely probes thread is active at a time" do
|
98
175
|
alive_thread_counter = proc { Thread.list.reject { |t| t.status == "dead" }.length }
|
99
|
-
probe =
|
176
|
+
probe = MockProbe.new
|
100
177
|
Appsignal::Minutely.probes.register :my_probe, probe
|
101
178
|
expect do
|
102
179
|
Appsignal::Minutely.start
|
103
180
|
end.to change { alive_thread_counter.call }.by(1)
|
104
181
|
|
105
182
|
wait_for("enough probe calls") { probe.calls >= 2 }
|
106
|
-
expect(
|
107
|
-
expect(
|
183
|
+
expect(Appsignal::Minutely).to have_received(:initial_wait_time).once
|
184
|
+
expect(Appsignal::Minutely).to have_received(:wait_time).at_least(:once)
|
185
|
+
expect(log).to contains_log(:debug, "Gathering minutely metrics with 1 probe")
|
186
|
+
expect(log).to contains_log(:debug, "Gathering minutely metrics with 'my_probe' probe")
|
187
|
+
|
188
|
+
# Starting twice in this spec, so expecting it more than once
|
189
|
+
expect(Appsignal::Minutely).to have_received(:initial_wait_time).once
|
108
190
|
expect do
|
109
191
|
# Fetch old thread
|
110
192
|
thread = Appsignal::Minutely.class_variable_get(:@@thread)
|
@@ -112,36 +194,13 @@ describe Appsignal::Minutely do
|
|
112
194
|
thread && thread.join # Wait for old thread to exit
|
113
195
|
end.to_not(change { alive_thread_counter.call })
|
114
196
|
end
|
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
197
|
end
|
143
198
|
|
144
199
|
describe ".stop" do
|
200
|
+
before do
|
201
|
+
allow(Appsignal::Minutely).to receive(:initial_wait_time).and_return(0.001)
|
202
|
+
end
|
203
|
+
|
145
204
|
it "stops the minutely thread" do
|
146
205
|
Appsignal::Minutely.start
|
147
206
|
thread = Appsignal::Minutely.class_variable_get(:@@thread)
|
@@ -152,9 +211,12 @@ describe Appsignal::Minutely do
|
|
152
211
|
end
|
153
212
|
|
154
213
|
it "clears the probe instances array" do
|
155
|
-
Appsignal::Minutely.probes.register :my_probe,
|
214
|
+
Appsignal::Minutely.probes.register :my_probe, lambda {}
|
156
215
|
Appsignal::Minutely.start
|
157
216
|
thread = Appsignal::Minutely.class_variable_get(:@@thread)
|
217
|
+
wait_for("probes initialized") do
|
218
|
+
!Appsignal::Minutely.send(:probe_instances).empty?
|
219
|
+
end
|
158
220
|
expect(Appsignal::Minutely.send(:probe_instances)).to_not be_empty
|
159
221
|
Appsignal::Minutely.stop
|
160
222
|
thread.join
|
@@ -164,20 +226,30 @@ describe Appsignal::Minutely do
|
|
164
226
|
|
165
227
|
describe ".wait_time" do
|
166
228
|
it "gets the time to the next minute" do
|
167
|
-
|
168
|
-
|
229
|
+
time = Time.new(2019, 4, 9, 12, 0, 20)
|
230
|
+
Timecop.freeze time do
|
231
|
+
expect(Appsignal::Minutely.wait_time).to eq 40
|
232
|
+
end
|
169
233
|
end
|
170
234
|
end
|
171
235
|
|
172
|
-
describe ".
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
236
|
+
describe ".initial_wait_time" do
|
237
|
+
context "when started in the last 30 seconds of a minute" do
|
238
|
+
it "waits for the number of seconds + 60" do
|
239
|
+
time = Time.new(2019, 4, 9, 12, 0, 31)
|
240
|
+
Timecop.freeze time do
|
241
|
+
expect(Appsignal::Minutely.send(:initial_wait_time)).to eql(29 + 60)
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
177
245
|
|
178
|
-
|
179
|
-
|
180
|
-
.
|
246
|
+
context "when started in the first 30 seconds of a minute" do
|
247
|
+
it "waits the remaining seconds in the minute" do
|
248
|
+
time = Time.new(2019, 4, 9, 12, 0, 29)
|
249
|
+
Timecop.freeze time do
|
250
|
+
expect(Appsignal::Minutely.send(:initial_wait_time)).to eql(31)
|
251
|
+
end
|
252
|
+
end
|
181
253
|
end
|
182
254
|
end
|
183
255
|
|
@@ -187,17 +259,17 @@ describe Appsignal::Minutely do
|
|
187
259
|
describe "#count" do
|
188
260
|
it "returns how many probes are registered" do
|
189
261
|
expect(collection.count).to eql(0)
|
190
|
-
collection.register :my_probe_1,
|
262
|
+
collection.register :my_probe_1, lambda {}
|
191
263
|
expect(collection.count).to eql(1)
|
192
|
-
collection.register :my_probe_2,
|
264
|
+
collection.register :my_probe_2, lambda {}
|
193
265
|
expect(collection.count).to eql(2)
|
194
266
|
end
|
195
267
|
end
|
196
268
|
|
197
269
|
describe "#clear" do
|
198
270
|
it "clears the list of probes" do
|
199
|
-
collection.register :my_probe_1,
|
200
|
-
collection.register :my_probe_2,
|
271
|
+
collection.register :my_probe_1, lambda {}
|
272
|
+
collection.register :my_probe_2, lambda {}
|
201
273
|
expect(collection.count).to eql(2)
|
202
274
|
collection.clear
|
203
275
|
expect(collection.count).to eql(0)
|
@@ -206,7 +278,7 @@ describe Appsignal::Minutely do
|
|
206
278
|
|
207
279
|
describe "#[]" do
|
208
280
|
it "returns the probe for that name" do
|
209
|
-
probe =
|
281
|
+
probe = lambda {}
|
210
282
|
collection.register :my_probe, probe
|
211
283
|
expect(collection[:my_probe]).to eql(probe)
|
212
284
|
end
|
@@ -220,7 +292,7 @@ describe Appsignal::Minutely do
|
|
220
292
|
before { Appsignal.logger = test_logger(log_stream) }
|
221
293
|
|
222
294
|
it "adds the probe, but prints a deprecation warning" do
|
223
|
-
probe =
|
295
|
+
probe = lambda {}
|
224
296
|
capture_stdout(out_stream) { collection << probe }
|
225
297
|
deprecation_message = "Deprecated " \
|
226
298
|
"`Appsignal::Minute.probes <<` call. Please use " \
|
@@ -237,14 +309,14 @@ describe Appsignal::Minutely do
|
|
237
309
|
before { Appsignal.logger = test_logger(log_stream) }
|
238
310
|
|
239
311
|
it "adds the by key probe" do
|
240
|
-
probe =
|
312
|
+
probe = lambda {}
|
241
313
|
collection.register :my_probe, probe
|
242
314
|
expect(collection[:my_probe]).to eql(probe)
|
243
315
|
end
|
244
316
|
|
245
317
|
context "when a probe is already registered with the same key" do
|
246
318
|
it "logs a debug message" do
|
247
|
-
probe =
|
319
|
+
probe = lambda {}
|
248
320
|
collection.register :my_probe, probe
|
249
321
|
collection.register :my_probe, probe
|
250
322
|
expect(log).to contains_log :debug, "A probe with the name " \
|
@@ -257,7 +329,7 @@ describe Appsignal::Minutely do
|
|
257
329
|
|
258
330
|
describe "#each" do
|
259
331
|
it "loops over the registered probes" do
|
260
|
-
probe =
|
332
|
+
probe = lambda {}
|
261
333
|
collection.register :my_probe, probe
|
262
334
|
list = []
|
263
335
|
collection.each do |name, p|
|
@@ -267,14 +339,4 @@ describe Appsignal::Minutely do
|
|
267
339
|
end
|
268
340
|
end
|
269
341
|
end
|
270
|
-
|
271
|
-
describe Appsignal::Minutely::GCProbe do
|
272
|
-
describe "#call" do
|
273
|
-
it "collects GC metrics" do
|
274
|
-
expect(Appsignal).to receive(:set_process_gauge).at_least(8).times
|
275
|
-
|
276
|
-
Appsignal::Minutely::GCProbe.new.call
|
277
|
-
end
|
278
|
-
end
|
279
|
-
end
|
280
342
|
end
|