riemann-tools 0.2.14 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +5 -5
  2. data/.docker/Dockerfile +7 -0
  3. data/.docker/publish.sh +35 -0
  4. data/.github/dependabot.yml +11 -0
  5. data/.github/workflows/ci.yml +42 -0
  6. data/.github/workflows/codeql-analysis.yml +72 -0
  7. data/.gitignore +6 -0
  8. data/.rspec +2 -0
  9. data/.rubocop.yml +32 -0
  10. data/.travis.yml +31 -0
  11. data/CHANGELOG.md +430 -0
  12. data/Gemfile +6 -0
  13. data/ISSUE_TEMPLATE.md +15 -0
  14. data/README.markdown +13 -16
  15. data/Rakefile +23 -0
  16. data/SECURITY.md +42 -0
  17. data/bin/riemann-apache-status +92 -77
  18. data/bin/riemann-bench +54 -48
  19. data/bin/riemann-cloudant +44 -39
  20. data/bin/riemann-consul +82 -75
  21. data/bin/riemann-dir-files-count +53 -46
  22. data/bin/riemann-dir-space +53 -46
  23. data/bin/riemann-diskstats +78 -74
  24. data/bin/riemann-fd +68 -47
  25. data/bin/riemann-freeswitch +108 -102
  26. data/bin/riemann-haproxy +46 -39
  27. data/bin/riemann-health +4 -335
  28. data/bin/riemann-kvminstance +18 -12
  29. data/bin/riemann-memcached +35 -28
  30. data/bin/riemann-net +4 -103
  31. data/bin/riemann-nginx-status +74 -66
  32. data/bin/riemann-ntp +4 -32
  33. data/bin/riemann-portcheck +40 -30
  34. data/bin/riemann-proc +96 -89
  35. data/bin/riemann-varnish +51 -44
  36. data/bin/riemann-zookeeper +38 -33
  37. data/lib/riemann/tools/health.rb +347 -0
  38. data/lib/riemann/tools/net.rb +104 -0
  39. data/lib/riemann/tools/ntp.rb +41 -0
  40. data/lib/riemann/tools/utils.rb +17 -0
  41. data/lib/riemann/tools/version.rb +7 -0
  42. data/lib/riemann/tools.rb +38 -31
  43. data/riemann-tools.gemspec +42 -0
  44. data/tools/riemann-aws/LICENSE +21 -0
  45. data/tools/riemann-aws/README.md +54 -0
  46. data/tools/riemann-aws/Rakefile +37 -0
  47. data/tools/riemann-aws/bin/riemann-aws-billing +93 -0
  48. data/tools/riemann-aws/bin/riemann-aws-rds-status +68 -0
  49. data/tools/riemann-aws/bin/riemann-aws-sqs-status +50 -0
  50. data/tools/riemann-aws/bin/riemann-aws-status +83 -0
  51. data/tools/riemann-aws/bin/riemann-elb-metrics +168 -0
  52. data/tools/riemann-aws/bin/riemann-s3-list +87 -0
  53. data/tools/riemann-aws/bin/riemann-s3-status +102 -0
  54. data/tools/riemann-chronos/LICENSE +21 -0
  55. data/tools/riemann-chronos/README.md +10 -0
  56. data/tools/riemann-chronos/Rakefile +37 -0
  57. data/tools/riemann-chronos/bin/riemann-chronos +161 -0
  58. data/tools/riemann-docker/LICENSE +21 -0
  59. data/tools/riemann-docker/README.md +10 -0
  60. data/tools/riemann-docker/Rakefile +36 -0
  61. data/tools/riemann-docker/bin/riemann-docker +206 -0
  62. data/tools/riemann-elasticsearch/LICENSE +21 -0
  63. data/tools/riemann-elasticsearch/README.md +10 -0
  64. data/tools/riemann-elasticsearch/Rakefile +37 -0
  65. data/tools/riemann-elasticsearch/bin/riemann-elasticsearch +174 -0
  66. data/tools/riemann-marathon/LICENSE +21 -0
  67. data/tools/riemann-marathon/README.md +10 -0
  68. data/tools/riemann-marathon/Rakefile +37 -0
  69. data/tools/riemann-marathon/bin/riemann-marathon +163 -0
  70. data/tools/riemann-mesos/LICENSE +21 -0
  71. data/tools/riemann-mesos/README.md +10 -0
  72. data/tools/riemann-mesos/Rakefile +37 -0
  73. data/tools/riemann-mesos/bin/riemann-mesos +146 -0
  74. data/tools/riemann-munin/LICENSE +21 -0
  75. data/tools/riemann-munin/README.md +10 -0
  76. data/tools/riemann-munin/Rakefile +36 -0
  77. data/tools/riemann-munin/bin/riemann-munin +43 -0
  78. data/tools/riemann-rabbitmq/LICENSE +21 -0
  79. data/tools/riemann-rabbitmq/README.md +10 -0
  80. data/tools/riemann-rabbitmq/Rakefile +37 -0
  81. data/tools/riemann-rabbitmq/bin/riemann-rabbitmq +273 -0
  82. data/tools/riemann-riak/LICENSE +21 -0
  83. data/tools/riemann-riak/README.md +10 -0
  84. data/tools/riemann-riak/Rakefile +36 -0
  85. data/tools/riemann-riak/bin/riemann-riak +323 -0
  86. data/tools/riemann-riak/bin/riemann-riak-keys +13 -0
  87. data/tools/riemann-riak/bin/riemann-riak-ring +9 -0
  88. data/tools/riemann-riak/riak_status/key_count.erl +13 -0
  89. data/tools/riemann-riak/riak_status/riak_status.rb +152 -0
  90. data/tools/riemann-riak/riak_status/ringready.erl +9 -0
  91. metadata +186 -37
@@ -1,53 +1,60 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
- # Gets the number of files present on a directory and submits it to riemann
4
-
5
- require File.expand_path('../../lib/riemann/tools', __FILE__)
6
-
7
- class Riemann::Tools::DirFilesCount
8
- include Riemann::Tools
4
+ Process.setproctitle($PROGRAM_NAME)
9
5
 
10
- opt :directory, "", :default => '/var/log'
11
- opt :service_prefix, "The first part of the service name, before the directory path", :default => "dir-files-count"
12
- opt :warning, "Dir files number warning threshold", :type => Integer
13
- opt :critical, "Dir files number critical threshold", :type => Integer
14
- opt :alert_on_missing, "Send a critical metric if the directory is missing?", :default => true
15
-
16
- def initialize
17
- @dir = opts.fetch(:directory)
18
- @service_prefix = opts.fetch(:service_prefix)
19
- @warning = opts.fetch(:warning, nil)
20
- @critical = opts.fetch(:critical, nil)
21
- @alert_on_missing = opts.fetch(:alert_on_missing)
22
- end
23
-
24
- def tick
25
- if Dir.exists?(@dir)
26
- metric = Dir.entries(@dir).size - 2
27
- report(
28
- :service => "#{@service_prefix} #{@dir}",
29
- :metric => metric,
30
- :state => state(metric),
31
- :tags => ['dir_files_count']
32
- )
33
- elsif @alert_on_missing
34
- report(
35
- :service => "#{@service_prefix} #{@dir} missing",
36
- :description => "#{@service_prefix} #{@dir} does not exist",
37
- :metric => metric,
38
- :state => 'critical',
39
- :tags => ['dir_files_count']
40
- )
41
- end
42
- end
6
+ # Gets the number of files present on a directory and submits it to riemann
43
7
 
44
- def state(metric)
45
- if @critical && metric > @critical
46
- 'critical'
47
- elsif @warning && metric > @warning
48
- 'warning'
49
- else
50
- 'ok'
8
+ require File.expand_path('../lib/riemann/tools', __dir__)
9
+
10
+ module Riemann
11
+ module Tools
12
+ class DirFilesCount
13
+ include Riemann::Tools
14
+
15
+ opt :directory, '', default: '/var/log'
16
+ opt :service_prefix, 'The first part of the service name, before the directory path', default: 'dir-files-count'
17
+ opt :warning, 'Dir files number warning threshold', type: Integer
18
+ opt :critical, 'Dir files number critical threshold', type: Integer
19
+ opt :alert_on_missing, 'Send a critical metric if the directory is missing?', default: true
20
+
21
+ def initialize
22
+ @dir = opts.fetch(:directory)
23
+ @service_prefix = opts.fetch(:service_prefix)
24
+ @warning = opts.fetch(:warning, nil)
25
+ @critical = opts.fetch(:critical, nil)
26
+ @alert_on_missing = opts.fetch(:alert_on_missing)
27
+ end
28
+
29
+ def tick
30
+ if Dir.exist?(@dir)
31
+ metric = Dir.entries(@dir).size - 2
32
+ report(
33
+ service: "#{@service_prefix} #{@dir}",
34
+ metric: metric,
35
+ state: state(metric),
36
+ tags: ['dir_files_count'],
37
+ )
38
+ elsif @alert_on_missing
39
+ report(
40
+ service: "#{@service_prefix} #{@dir} missing",
41
+ description: "#{@service_prefix} #{@dir} does not exist",
42
+ metric: metric,
43
+ state: 'critical',
44
+ tags: ['dir_files_count'],
45
+ )
46
+ end
47
+ end
48
+
49
+ def state(metric)
50
+ if @critical && metric > @critical
51
+ 'critical'
52
+ elsif @warning && metric > @warning
53
+ 'warning'
54
+ else
55
+ 'ok'
56
+ end
57
+ end
51
58
  end
52
59
  end
53
60
  end
@@ -1,53 +1,60 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
- # Gathers the space used by a directory and submits it to riemann
4
-
5
- require File.expand_path('../../lib/riemann/tools', __FILE__)
6
-
7
- class Riemann::Tools::DirSpace
8
- include Riemann::Tools
4
+ Process.setproctitle($PROGRAM_NAME)
9
5
 
10
- opt :directory, "", :default => '/var/log'
11
- opt :service_prefix, "The first part of the service name, before the directory path", :default => "dir-space"
12
- opt :warning, "Dir space warning threshold (in bytes)", :type => Integer
13
- opt :critical, "Dir space critical threshold (in bytes)", :type => Integer
14
- opt :alert_on_missing, "Send a critical metric if the directory is missing?", :default => true
15
-
16
- def initialize
17
- @dir = opts.fetch(:directory)
18
- @service_prefix = opts.fetch(:service_prefix)
19
- @warning = opts.fetch(:warning, nil)
20
- @critical = opts.fetch(:critical, nil)
21
- @alert_on_missing = opts.fetch(:alert_on_missing)
22
- end
23
-
24
- def tick
25
- if Dir.exists?(@dir)
26
- metric = `du '#{@dir}'`.lines.to_a.last.split("\t")[0].to_i
27
- report(
28
- :service => "#{@service_prefix} #{@dir}",
29
- :metric => metric,
30
- :state => state(metric),
31
- :tags => ['dir_space']
32
- )
33
- elsif @alert_on_missing
34
- report(
35
- :service => "#{@service_prefix} #{@dir} missing",
36
- :description => "#{@service_prefix} #{@dir} does not exist",
37
- :metric => metric,
38
- :state => 'critical',
39
- :tags => ['dir_space']
40
- )
41
- end
42
- end
6
+ # Gathers the space used by a directory and submits it to riemann
43
7
 
44
- def state(metric)
45
- if @critical && metric > @critical
46
- 'critical'
47
- elsif @warning && metric > @warning
48
- 'warning'
49
- else
50
- 'ok'
8
+ require File.expand_path('../lib/riemann/tools', __dir__)
9
+
10
+ module Riemann
11
+ module Tools
12
+ class DirSpace
13
+ include Riemann::Tools
14
+
15
+ opt :directory, '', default: '/var/log'
16
+ opt :service_prefix, 'The first part of the service name, before the directory path', default: 'dir-space'
17
+ opt :warning, 'Dir space warning threshold (in bytes)', type: Integer
18
+ opt :critical, 'Dir space critical threshold (in bytes)', type: Integer
19
+ opt :alert_on_missing, 'Send a critical metric if the directory is missing?', default: true
20
+
21
+ def initialize
22
+ @dir = opts.fetch(:directory)
23
+ @service_prefix = opts.fetch(:service_prefix)
24
+ @warning = opts.fetch(:warning, nil)
25
+ @critical = opts.fetch(:critical, nil)
26
+ @alert_on_missing = opts.fetch(:alert_on_missing)
27
+ end
28
+
29
+ def tick
30
+ if Dir.exist?(@dir)
31
+ metric = `du '#{@dir}'`.lines.to_a.last.split("\t")[0].to_i
32
+ report(
33
+ service: "#{@service_prefix} #{@dir}",
34
+ metric: metric,
35
+ state: state(metric),
36
+ tags: ['dir_space'],
37
+ )
38
+ elsif @alert_on_missing
39
+ report(
40
+ service: "#{@service_prefix} #{@dir} missing",
41
+ description: "#{@service_prefix} #{@dir} does not exist",
42
+ metric: metric,
43
+ state: 'critical',
44
+ tags: ['dir_space'],
45
+ )
46
+ end
47
+ end
48
+
49
+ def state(metric)
50
+ if @critical && metric > @critical
51
+ 'critical'
52
+ elsif @warning && metric > @warning
53
+ 'warning'
54
+ else
55
+ 'ok'
56
+ end
57
+ end
51
58
  end
52
59
  end
53
60
  end
@@ -1,94 +1,98 @@
1
1
  #!/usr/bin/env ruby
2
- #
2
+ # frozen_string_literal: true
3
+
4
+ Process.setproctitle($PROGRAM_NAME)
3
5
  require 'rubygems'
4
6
  require 'riemann/tools'
5
7
 
6
- class Riemann::Tools::Diskstats
7
- include Riemann::Tools
8
+ module Riemann
9
+ module Tools
10
+ class Diskstats
11
+ include Riemann::Tools
8
12
 
9
- opt :devices, "Devices to monitor", :type => :strings, :default => nil
10
- opt :ignore_devices, "Devices to ignore", :type => :strings, :default =>nil
13
+ opt :devices, 'Devices to monitor', type: :strings, default: nil
14
+ opt :ignore_devices, 'Devices to ignore', type: :strings, default: nil
11
15
 
12
- def initialize
13
- @old_state = nil
14
- end
16
+ def initialize
17
+ @old_state = nil
18
+ end
15
19
 
16
- def state
17
- f = File.read('/proc/diskstats')
18
- state = f.split("\n").reject { |d| d =~ /(ram|loop)/ }.inject({}) do |s, line|
19
- if line =~ /^(?:\s+\d+){2}\s+([\w\d\-]+) (.*)$/
20
- dev = $1
21
-
22
- ['reads reqs',
23
- 'reads merged',
24
- 'reads sector',
25
- 'reads time',
26
- 'writes reqs',
27
- 'writes merged',
28
- 'writes sector',
29
- 'writes time',
30
- 'io reqs',
31
- 'io time',
32
- 'io weighted'
33
- ].map do |service|
34
- "#{dev} #{service}"
35
- end.zip(
36
- $2.split(/\s+/).map { |str| str.to_i }
37
- ).each do |service, value|
38
- s[service] = value
20
+ def state
21
+ f = File.read('/proc/diskstats')
22
+ state = f.split("\n").reject { |d| d =~ /(ram|loop)/ }.each_with_object({}) do |line, s|
23
+ next unless line =~ /^(?:\s+\d+){2}\s+([\w\d\-]+) (.*)$/
24
+
25
+ dev = Regexp.last_match(1)
26
+
27
+ ['reads reqs',
28
+ 'reads merged',
29
+ 'reads sector',
30
+ 'reads time',
31
+ 'writes reqs',
32
+ 'writes merged',
33
+ 'writes sector',
34
+ 'writes time',
35
+ 'io reqs',
36
+ 'io time',
37
+ 'io weighted',].map do |service|
38
+ "#{dev} #{service}"
39
+ end.zip( # rubocop:disable Style/MultilineBlockChain
40
+ Regexp.last_match(2).split(/\s+/).map(&:to_i),
41
+ ).each do |service, value|
42
+ s[service] = value
43
+ end
39
44
  end
40
- end
41
45
 
42
- s
43
- end
46
+ # Filter interfaces
47
+ if (is = opts[:devices])
48
+ state = state.select do |service, _value|
49
+ is.include? service.split(' ').first
50
+ end
51
+ end
44
52
 
45
- # Filter interfaces
46
- if is = opts[:devices]
47
- state = state.select do |service, value|
48
- is.include? service.split(' ').first
49
- end
50
- end
53
+ if (ign = opts[:ignore_devices])
54
+ state = state.reject do |service, _value|
55
+ ign.include? service.split(' ').first
56
+ end
57
+ end
51
58
 
52
- if ign = opts[:ignore_devices]
53
- state = state.reject do |service, value|
54
- ign.include? service.split(' ').first
59
+ state
55
60
  end
56
- end
57
61
 
58
- state
59
- end
60
-
61
- def tick
62
- state = self.state
63
-
64
- if @old_state
65
- state.each do |service, metric|
66
-
67
- if service =~ /io reqs$/
68
- report(
69
- :service => "diskstats " + service,
70
- :metric => metric,
71
- :state => "ok"
72
- )
73
- else
74
- delta = metric - @old_state[service]
75
-
76
- report(
77
- :service => "diskstats " + service,
78
- :metric => (delta.to_f / opts[:interval]),
79
- :state => "ok"
80
- )
62
+ def tick
63
+ state = self.state
64
+
65
+ if @old_state
66
+ state.each do |service, metric|
67
+ if service =~ /io reqs$/
68
+ report(
69
+ service: "diskstats #{service}",
70
+ metric: metric,
71
+ state: 'ok',
72
+ )
73
+ else
74
+ delta = metric - @old_state[service]
75
+
76
+ report(
77
+ service: "diskstats #{service}",
78
+ metric: (delta.to_f / opts[:interval]),
79
+ state: 'ok',
80
+ )
81
+ end
82
+
83
+ next unless service =~ /io time$/
84
+
85
+ report(
86
+ service: "diskstats #{service.gsub(/time/, 'util')}",
87
+ metric: (delta.to_f / (opts[:interval] * 1000)),
88
+ state: 'ok',
89
+ )
90
+ end
81
91
  end
82
92
 
83
- if service =~ /io time$/
84
- report(:service => "diskstats " + service.gsub(/time/, 'util'),
85
- :metric => (delta.to_f / (opts[:interval]*1000)),
86
- :state => "ok")
87
- end
93
+ @old_state = state
88
94
  end
89
95
  end
90
-
91
- @old_state = state
92
96
  end
93
97
  end
94
98
 
data/bin/riemann-fd CHANGED
@@ -1,65 +1,86 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ Process.setproctitle($PROGRAM_NAME)
2
5
 
3
6
  # Reports current file descriptor use to riemann.
4
7
  # By default reports the total system fd usage, can also report usage of individual processes
5
8
 
6
- require File.expand_path('../../lib/riemann/tools', __FILE__)
9
+ require File.expand_path('../lib/riemann/tools', __dir__)
7
10
 
8
- class Riemann::Tools::Health
9
- include Riemann::Tools
11
+ module Riemann
12
+ module Tools
13
+ class Health
14
+ include Riemann::Tools
10
15
 
11
- opt :fd_sys_warning, "open file descriptor threshold for system", :default => 800
12
- opt :fd_sys_critical, "open file descriptor critical threshold for system", :default => 900
13
- opt :fd_proc_warning, "open file descriptor threshold for process", :default => 800
14
- opt :fd_proc_critical, "open file descriptor critical threshold for process", :default => 900
15
- opt :processes, "list of processes to measure fd usage in addition to system total", :type => :ints
16
+ opt :fd_sys_warning, 'open file descriptor threshold for system', default: 800
17
+ opt :fd_sys_critical, 'open file descriptor critical threshold for system', default: 900
18
+ opt :fd_proc_warning, 'open file descriptor threshold for process', default: 800
19
+ opt :fd_proc_critical, 'open file descriptor critical threshold for process', default: 900
20
+ opt :processes, 'list of processes to measure fd usage in addition to system total', type: :ints
16
21
 
17
- def initialize
18
- @limits = {
19
- :fd => {:critical => opts[:fd_sys_critical], :warning => opts[:fd_sys_warning]},
20
- :process => {:critical => opts[:fd_proc_critical], :warning => opts[:fd_proc_warning]},
21
- }
22
- ostype = `uname -s`.chomp.downcase
23
- puts "WARNING: OS '#{ostype}' not explicitly supported. Falling back to Linux" unless ostype == "linux"
24
- @fd = method :linux_fd
25
- end
22
+ def initialize
23
+ @limits = {
24
+ fd: { critical: opts[:fd_sys_critical], warning: opts[:fd_sys_warning] },
25
+ process: { critical: opts[:fd_proc_critical], warning: opts[:fd_proc_warning] },
26
+ }
27
+ ostype = `uname -s`.chomp.downcase
28
+ case ostype
29
+ when 'freebsd'
30
+ @fd = method :freebsd_fd
31
+ else
32
+ puts "WARNING: OS '#{ostype}' not explicitly supported. Falling back to Linux" unless ostype == 'linux'
33
+ @fd = method :linux_fd
34
+ end
35
+ end
26
36
 
27
- def alert(service, state, metric, description)
28
- report(
29
- :service => service.to_s,
30
- :state => state.to_s,
31
- :metric => metric.to_f,
32
- :description => description
33
- )
34
- end
37
+ def alert(service, state, metric, description)
38
+ report(
39
+ service: service.to_s,
40
+ state: state.to_s,
41
+ metric: metric.to_f,
42
+ description: description,
43
+ )
44
+ end
35
45
 
36
- def linux_fd
37
- sys_used = Integer(`lsof | wc -l`)
38
- if sys_used > @limits[:fd][:critical]
39
- alert "fd sys", :critical, sys_used, "system is using #{sys_used} fds"
40
- elsif sys_used > @limits[:fd][:warning]
41
- alert "fd sys", :warning, sys_used, "system is using #{sys_used} fds"
42
- else
43
- alert "fd sys", :ok, sys_used, "system is using #{sys_used} fds"
44
- end
46
+ def freebsd_fd
47
+ sys_used = Integer(`sysctl -n kern.openfiles`)
48
+ if sys_used > @limits[:fd][:critical]
49
+ alert 'fd sys', :critical, sys_used, "system is using #{sys_used} fds"
50
+ elsif sys_used > @limits[:fd][:warning]
51
+ alert 'fd sys', :warning, sys_used, "system is using #{sys_used} fds"
52
+ else
53
+ alert 'fd sys', :ok, sys_used, "system is using #{sys_used} fds"
54
+ end
55
+ end
45
56
 
46
- unless opts[:processes].nil?
47
- opts[:processes].each do |process|
48
- used = Integer(`lsof -p #{process} | wc -l`)
49
- name, pid = `ps axo comm,pid | grep -w #{process}`.split
50
- if used > @limits[:process][:critical]
51
- alert "fd #{name} #{process}", :critical, used, "process #{name} #{process} is using #{used} fds"
52
- elsif used > @limits[:process][:warning]
53
- alert "fd #{name} #{process}", :warning, used, "process #{name} #{process} is using #{used} fds"
57
+ def linux_fd
58
+ sys_used = Integer(`lsof | wc -l`)
59
+ if sys_used > @limits[:fd][:critical]
60
+ alert 'fd sys', :critical, sys_used, "system is using #{sys_used} fds"
61
+ elsif sys_used > @limits[:fd][:warning]
62
+ alert 'fd sys', :warning, sys_used, "system is using #{sys_used} fds"
54
63
  else
55
- alert "fd #{name} #{process}", :ok, used, "process #{name} #{process} is using #{used} fds"
64
+ alert 'fd sys', :ok, sys_used, "system is using #{sys_used} fds"
65
+ end
66
+
67
+ opts[:processes]&.each do |process|
68
+ used = Integer(`lsof -p #{process} | wc -l`)
69
+ name, _pid = `ps axo comm,pid | grep -w #{process}`.split
70
+ if used > @limits[:process][:critical]
71
+ alert "fd #{name} #{process}", :critical, used, "process #{name} #{process} is using #{used} fds"
72
+ elsif used > @limits[:process][:warning]
73
+ alert "fd #{name} #{process}", :warning, used, "process #{name} #{process} is using #{used} fds"
74
+ else
75
+ alert "fd #{name} #{process}", :ok, used, "process #{name} #{process} is using #{used} fds"
76
+ end
56
77
  end
57
78
  end
58
- end
59
- end
60
79
 
61
- def tick
62
- @fd.call
80
+ def tick
81
+ @fd.call
82
+ end
83
+ end
63
84
  end
64
85
  end
65
86