appsignal 2.9.2.alpha.1 → 2.9.18.beta.2
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 +5 -5
- 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/config.rb +5 -2
- 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 +13 -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 +38 -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
|