fiveruns-dash-sensor 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
data/History.rdoc ADDED
@@ -0,0 +1,5 @@
1
+ = Changelog
2
+
3
+ == 0.8.0 (2009-01-07)
4
+
5
+ * Initial internal release.
data/README.rdoc ADDED
@@ -0,0 +1,71 @@
1
+ = fiveruns-dash-sensor
2
+
3
+ Please see http://dash.fiveruns.com for an overview of the Dash service.
4
+
5
+ == Summary
6
+
7
+ Sensor provides a mechanism for "external" monitoring of infrastructure with Dash.
8
+ Since Dash is usually embedded within an application (i.e. internal), there's no easy
9
+ way to monitor things like Apache, memcached, or other pieces. Sensor is designed to
10
+ be installed on a machine and upload metrics for any infrastructure on that machine.
11
+ The user just needs to configure which elements they want to monitor.
12
+
13
+ == Installation
14
+
15
+ gem install fiveruns-dash-sensor -s http://gems.github.com
16
+
17
+ == Configuration
18
+
19
+ When you run 'fiveruns-dash-sensor start' for the first time, it will create a blank
20
+ configuration for you in ~/.fiveruns-dash-sensor/config.rb. You will need to edit
21
+ this file with your app token and activate the plugins for your environment. Once
22
+ done, you should be able to run 'fiveruns-dash-sensor run' for a few minutes to verify
23
+ everything is working as designed. If there are no problems, you can run
24
+ 'fiveruns-dash-sensor start' to spin off the daemon into the background and forget about it.
25
+
26
+ == Command Summary
27
+
28
+ 'fiveruns-dash-sensor run' - start the daemon in the foreground, for debugging purposes.
29
+ 'fiveruns-dash-sensor start' - start the daemon in the background, output and PID file goes
30
+ in the ~/.fiveruns-dash-sensor/log/ directory.
31
+ 'fiveruns-dash-sensor stop' - stop the daemon in the background
32
+
33
+ You can run Sensor in verbose mode to get more debug output:
34
+
35
+ 'fiveruns-dash-sensor run -- -v'
36
+
37
+ == Creating your own Plugins
38
+
39
+ Sensor allows you to create your own custom plugins for your own infrastructure. Each
40
+ plugin should reside in ~/.fiveruns-dash-sensor/<name>.rb and config.rb should have an
41
+ entry in it like this:
42
+
43
+ sensor.plugin :<name>, any options here...
44
+
45
+ Please see the existing plugins for examples of how your code should work.
46
+
47
+
48
+ == License
49
+
50
+ # (The FiveRuns License)
51
+ #
52
+ # Copyright (c) 2009 FiveRuns Corporation
53
+ #
54
+ # Permission is hereby granted, free of charge, to any person obtaining
55
+ # a copy of this software and associated documentation files (the
56
+ # 'Software'), to deal in the Software without restriction, including
57
+ # without limitation the rights to use, copy, modify, merge, publish,
58
+ # distribute, sublicense, and/or sell copies of the Software, and to
59
+ # permit persons to whom the Software is furnished to do so, subject to
60
+ # the following conditions:
61
+ #
62
+ # The above copyright notice and this permission notice shall be
63
+ # included in all copies or substantial portions of the Software.
64
+ #
65
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
66
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
67
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
68
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
69
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
70
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
71
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+
4
+ unless File.exist?("#{ENV['HOME']}/.fiveruns-dash-sensor/config.rb")
5
+ local = File.join(File.dirname(__FILE__), "..", "config.rb")
6
+ dir = File.expand_path("~/.fiveruns-dash-sensor")
7
+ if File.exist?(local)
8
+ require 'fileutils'
9
+ FileUtils.mkdir_p dir
10
+ FileUtils.cp local, dir
11
+ puts ""
12
+ puts "Configuration Required"
13
+ puts ""
14
+ puts "A blank configuration file has been placed in ~/.fiveruns-dash-sensor/config.rb"
15
+ puts "Please edit this file to add your application token and configure the plugins"
16
+ puts "appropriate to this environment."
17
+ puts ""
18
+ end
19
+ exit
20
+ end
21
+
22
+ root = File.expand_path("~/.fiveruns-dash-sensor")
23
+ log_dir = File.join(root, "log")
24
+ unless File.exist? log_dir
25
+ require 'fileutils'
26
+ FileUtils.mkdir_p log_dir
27
+ end
28
+
29
+ gem 'daemons'
30
+ require 'daemons'
31
+
32
+ options = {
33
+ :app_name => "dash-sensor",
34
+ :dir_mode => :normal,
35
+ :dir => log_dir,
36
+ :multiple => true,
37
+ :backtrace => true,
38
+ :monitor => false,
39
+ :log_output => true
40
+ }
41
+
42
+ Daemons.run(File.join(File.dirname(__FILE__), '..', 'lib', 'daemon.rb'), options)
data/config.rb ADDED
@@ -0,0 +1,19 @@
1
+ # Example configuration for FiveRuns Dash Sensor daemon.
2
+ # Configure your Dash sensor instance here.
3
+ # This file should reside in ~/.fiveruns-dash-sensor/config.rb
4
+ #
5
+ # We suggest you have a single Dash Sensor instance running per machine
6
+ # and an application token per environment. So if you have 8 machines
7
+ # in your application's production cluster, you would have 8 sensor instances
8
+ # running for one application named "<App> Production Cluster".
9
+
10
+ #sensor.token = 'change-to-your-application-token'
11
+
12
+ # One line per piece of infrastructure you wish to monitor.
13
+ # The plugins are in the plugins directory.
14
+
15
+ # Available options and their defaults are listed.
16
+ #sensor.plugin 'memcached', :iface => 'localhost', :port => 11211
17
+ #sensor.plugin 'starling', :iface => 'localhost', :port => 22122
18
+ #sensor.plugin 'apache', :url => 'http://localhost/server-status?auto'
19
+ #sensor.plugin 'nginx', :url => 'http://localhost/nginx_status'
data/lib/daemon.rb ADDED
@@ -0,0 +1,46 @@
1
+ require 'ostruct'
2
+ require 'optparse'
3
+
4
+ # Sent by daemons when you run '<script> stop'
5
+ Signal.trap('TERM') { puts "fiveruns-dash-sensor PID #{$$} exiting at #{Time.now}..."; exit(0) }
6
+ # Sent by daemons when you hit Ctrl-C after '<script> run'
7
+ Signal.trap('INT') { puts "fiveruns-dash-sensor terminated at #{Time.now}..."; exit(0) }
8
+
9
+ options = OpenStruct.new
10
+ options.environment = ENV['RAILS_ENV'] || 'production'
11
+ options.verbose = false
12
+ options.config_file = "#{ENV['HOME']}/.fiveruns-dash-sensor/config.rb"
13
+
14
+ op = OptionParser.new do |opts|
15
+ opts.banner = "Usage: fiveruns-dash-sensor [options]"
16
+ opts.separator "General Options:"
17
+ opts.on("-e ENVIRONMENT", "--environment NAME", "Select environment [default: #{options.environment}]") do |v|
18
+ options.environment = v
19
+ end
20
+ opts.on("-c CONFIG", "--config FILE", "Select config file [default: #{options.config_file}]") do |v|
21
+ options.config_file = v
22
+ end
23
+ opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
24
+ options.verbose = v
25
+ end
26
+ opts.separator "Other Options:"
27
+ opts.on("-h", "--help", "Display this message") do |v|
28
+ STDERR.puts opts
29
+ exit
30
+ end
31
+ end
32
+ op.parse!(ARGV)
33
+
34
+ unless File.exist? options.config_file
35
+ puts "Please create a configuration file for your environment in ~/.fiveruns-dash-sensor/config.rb"
36
+ exit(1)
37
+ end
38
+
39
+ require 'logger'
40
+ LOG = Logger.new(STDOUT)
41
+ LOG.level = options.verbose ? Logger::DEBUG : Logger::INFO
42
+
43
+ $LOAD_PATH.unshift File.dirname(__FILE__)
44
+ require "sensor"
45
+ LOG.info("Starting Dash Sensor [#{$$}] at #{Time.now}")
46
+ Dash::Sensor::Engine.new.start(options)
data/lib/sensor.rb ADDED
@@ -0,0 +1,93 @@
1
+ require 'fiveruns/dash'
2
+ require 'sensor_plugin'
3
+
4
+ module Dash
5
+ module Sensor
6
+
7
+ RECIPES = []
8
+
9
+ class Engine
10
+
11
+ def self.registered(name, url, klass)
12
+ klass.instance = klass.new
13
+ RECIPES << [name, url, klass]
14
+ end
15
+
16
+ def start(options)
17
+ load_plugins(options)
18
+ wait_forever
19
+ end
20
+
21
+ private
22
+
23
+ def load_plugins(options)
24
+ # Add the user's config directory to the load path so they can load custom plugins
25
+ # without modifying this gem.
26
+ $PLUGIN_PATH = [
27
+ File.dirname(options.config_file),
28
+ File.expand_path(File.join(File.dirname(__FILE__), '..', 'plugins')),
29
+ nil
30
+ ]
31
+
32
+ setup = Setup.new
33
+ config = File.read(options.config_file)
34
+ setup.instance_eval(config, options.config_file)
35
+
36
+ start_dash(setup)
37
+ end
38
+
39
+ def start_dash(setup)
40
+ LOG.info("Configured Dash Sensor for #{setup.name} [#{setup.token}]")
41
+ Fiveruns::Dash.configure :app => setup.token do |config|
42
+ RECIPES.each do |(name, url, _)|
43
+ config.add_recipe name.to_sym, url
44
+ end
45
+ end
46
+ Fiveruns::Dash.session.reporter.interval = 60
47
+ Fiveruns::Dash.session.start
48
+ end
49
+
50
+ def wait_forever
51
+ while true
52
+ sleep 60
53
+ end
54
+ end
55
+ end
56
+
57
+ class Setup
58
+
59
+ attr_accessor :token, :name
60
+
61
+ def initialize
62
+ self.name = 'My App Name'
63
+ self.token = 'change-me-to-your-app-token'
64
+ end
65
+
66
+ def plugin(name, options={})
67
+ LOG.info("Loading plugin #{name} with options #{options.inspect}")
68
+
69
+ $PLUGIN_PATH.each do |path|
70
+ raise ArgumentError, "Unable to find #{name}.rb in plugin path: #{$PLUGIN_PATH[0..-2].inspect}" unless path
71
+ file = File.join(path, name)
72
+ if File.exist?("#{file}.rb")
73
+ LOG.debug "Loading #{file}"
74
+ require file
75
+ break
76
+ end
77
+ end
78
+
79
+ RECIPES.last.last.instance.configure(options)
80
+ end
81
+
82
+ def url=(value)
83
+ ENV['DASH_UPDATE'] = value
84
+ end
85
+
86
+ def sensor
87
+ self
88
+ end
89
+
90
+ end
91
+
92
+ end
93
+ end
@@ -0,0 +1,16 @@
1
+ module SensorPlugin
2
+
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+ end
6
+
7
+ module ClassMethods
8
+ attr_accessor :instance
9
+
10
+ def register(name, url, &block)
11
+ Fiveruns::Dash.register_recipe(name, url, &block)
12
+ Dash::Sensor::Engine.registered(name, url, self)
13
+ end
14
+ end
15
+
16
+ end
data/plugins/apache.rb ADDED
@@ -0,0 +1,56 @@
1
+ require 'open-uri'
2
+
3
+ module Dash::Sensor::Plugins
4
+ class Apache
5
+ include SensorPlugin
6
+
7
+ ONE_KB = 1024
8
+
9
+ register :apache, :url => 'http://dash.fiveruns.com' do |recipe|
10
+ recipe.counter :requests, 'Requests' do
11
+ total = Integer(stats['Total Accesses'])
12
+ last = Integer(old_stats['Total Accesses'])
13
+ this_minute = total - last
14
+ this_minute > 0 && last > 0 ? this_minute : 0
15
+ end
16
+ recipe.counter :mbytes, 'Transferred', :unit => 'MB' do
17
+ total = Integer(stats['Total kBytes'])
18
+ last = Integer(old_stats['Total kBytes'])
19
+ this_minute = Float(total - last) / ONE_KB
20
+ this_minute > 0 && last > 0 ? this_minute : 0
21
+ end
22
+ end
23
+
24
+ def configure(options)
25
+ @url = options.fetch(:url, 'http://localhost/server-status?auto')
26
+ end
27
+
28
+ private
29
+
30
+ def self.stats
31
+ if !@time || @time < Time.now - 55
32
+ @old_stats = @stats || Hash.new(0)
33
+ LOG.debug "Fetching status at #{Time.now}"
34
+ @stats = instance.send(:stats_data)
35
+ @time = Time.now
36
+ end
37
+ @stats
38
+ end
39
+
40
+ def self.old_stats
41
+ @old_stats
42
+ end
43
+
44
+ def stats_data
45
+ begin
46
+ lines = open(@url).read.split("\n")
47
+ Hash[*lines.map {|line| line.split(':') }.flatten]
48
+ rescue => e
49
+ LOG.error "Error contacting #{@url}"
50
+ LOG.error "#{e.class.name}: #{e.message}"
51
+ Hash.new(0)
52
+ end
53
+ end
54
+
55
+ end
56
+ end
@@ -0,0 +1,65 @@
1
+ module Dash::Sensor::Plugins
2
+ class Memcached
3
+ include SensorPlugin
4
+
5
+ register :memcached, :url => 'http://dash.fiveruns.com' do |recipe|
6
+ recipe.absolute :bytes, 'Cache Size', :unit => 'MB' do
7
+ Float(stats['bytes']) / (1024 * 1024)
8
+ end
9
+ recipe.percentage :hit_rate, 'Cache Hit Rate' do
10
+ hits = Integer(stats['get_hits'])
11
+ misses = Integer(stats['get_misses'])
12
+ total = hits + misses
13
+ total == 0 ? 0 : ((Float(hits) / total) * 100)
14
+ end
15
+ end
16
+
17
+ def configure(options)
18
+ @host = options.fetch(:iface, 'localhost')
19
+ @port = Integer(options.fetch(:port, 11211))
20
+ end
21
+
22
+ private
23
+
24
+ def self.stats
25
+ if !@time || @time < Time.now - 55
26
+ LOG.debug "Fetching stats at #{Time.now}"
27
+ @stats = parse(instance.send(:stats_data))
28
+ @time = Time.now
29
+ end
30
+ @stats
31
+ end
32
+
33
+ def stats_data
34
+ data = ''
35
+ begin
36
+ sock = TCPSocket.new(@host, @port)
37
+ sock.print("stats\r\n")
38
+ sock.flush
39
+ # memcached does not close the socket once it is done writing
40
+ # the stats data. We need to read line by line until we detect
41
+ # the END line and then stop/close on our side.
42
+ stats = sock.gets
43
+ while true
44
+ data += stats
45
+ break if stats.strip == 'END'
46
+ stats = sock.gets
47
+ end
48
+ sock.close
49
+ rescue Exception => e
50
+ LOG.error "Error contacting Starling at #{@host}:#{@port}"
51
+ LOG.error "#{e.class.name}: #{e.message}"
52
+ end
53
+ data
54
+ end
55
+
56
+ def self.parse(stats_data)
57
+ stats = Hash.new(0)
58
+ stats_data.each_line do |line|
59
+ stats[$1] = $2 if line =~ /STAT (\w+) (\S+)/
60
+ end
61
+ stats
62
+ end
63
+
64
+ end
65
+ end
data/plugins/nginx.rb ADDED
@@ -0,0 +1,62 @@
1
+ require 'open-uri'
2
+
3
+ module Dash::Sensor::Plugins
4
+ class Nginx
5
+ include SensorPlugin
6
+
7
+ ONE_KB = 1024
8
+
9
+ register :nginx, :url => 'http://dash.fiveruns.com' do |recipe|
10
+ recipe.counter :requests, 'Requests' do
11
+ total = stats[:requests]
12
+ last = old_stats[:requests]
13
+ this_minute = total - last
14
+ this_minute > 0 && last > 0 ? this_minute : 0
15
+ end
16
+ recipe.absolute :connections, 'Active Connections' do
17
+ stats[:active]
18
+ end
19
+ end
20
+
21
+ def configure(options)
22
+ @url = options.fetch(:url, 'http://localhost/nginx-status')
23
+ end
24
+
25
+ private
26
+
27
+ def self.stats
28
+ if !@time || @time < Time.now - 55
29
+ @old_stats = @stats || Hash.new(0)
30
+ LOG.debug "Fetching status at #{Time.now}"
31
+ @stats = instance.send(:stats_data)
32
+ @time = Time.now
33
+ end
34
+ @stats
35
+ end
36
+
37
+ def self.old_stats
38
+ @old_stats
39
+ end
40
+
41
+ def stats_data
42
+ begin
43
+ results = Hash.new(0)
44
+ data = open(@url).read
45
+ data.each_line do |line|
46
+ case line
47
+ when /^Active connections:\s+(\d+)/
48
+ results[:active] = Integer($1)
49
+ when /^\s+(\d+)\s+(\d+)\s+(\d+)/
50
+ results[:requests] = Integer($3)
51
+ end
52
+ end
53
+ results
54
+ rescue Exception => e
55
+ LOG.error "Error contacting #{@url}"
56
+ LOG.error "#{e.class.name}: #{e.message}"
57
+ Hash.new(0)
58
+ end
59
+ end
60
+
61
+ end
62
+ end
@@ -0,0 +1,64 @@
1
+ module Dash::Sensor::Plugins
2
+ class Starling
3
+ include SensorPlugin
4
+
5
+ register :starling, :url => 'http://dash.fiveruns.com' do |recipe|
6
+ recipe.absolute :processed, 'Processed Items' do
7
+ queues = stats.keys.map { |name| name =~ /queue_(\w+)_total_items/; $1 }.compact
8
+ queues.inject(0) { |total, queue_name| total + Integer(stats["queue_#{queue_name}_total_items"]) - Integer(stats["queue_#{queue_name}_items"]) }
9
+ end
10
+ recipe.absolute :queue_size, 'Current Size' do
11
+ queues = stats.keys.find_all { |name| name =~ /queue_\w+_items/ }
12
+ queues.inject(0) { |total, queue_name| total + Integer(stats[queue_name]) }
13
+ end
14
+ end
15
+
16
+ def configure(options)
17
+ @host = options.fetch(:iface, 'localhost')
18
+ @port = Integer(options.fetch(:port, 22122))
19
+ end
20
+
21
+ private
22
+
23
+ def self.stats
24
+ if !@time || @time < Time.now - 55
25
+ LOG.debug "Fetching stats at #{Time.now}"
26
+ @stats = parse(instance.send(:stats_data))
27
+ @time = Time.now
28
+ end
29
+ @stats
30
+ end
31
+
32
+ def stats_data
33
+ data = ''
34
+ begin
35
+ sock = TCPSocket.new(@host, @port)
36
+ sock.print("stats\r\n")
37
+ sock.flush
38
+ # memcached does not close the socket once it is done writing
39
+ # the stats data. We need to read line by line until we detect
40
+ # the END line and then stop/close on our side.
41
+ stats = sock.gets
42
+ while true
43
+ data += stats
44
+ break if stats.strip == 'END'
45
+ stats = sock.gets
46
+ end
47
+ sock.close
48
+ rescue Exception => e
49
+ LOG.error "Error contacting Starling at #{@host}:#{@port}"
50
+ LOG.error "#{e.class.name}: #{e.message}"
51
+ end
52
+ data
53
+ end
54
+
55
+ def self.parse(stats_data)
56
+ stats = Hash.new(0)
57
+ stats_data.each_line do |line|
58
+ stats[$1] = $2 if line =~ /STAT (\w+) (\S+)/
59
+ end
60
+ stats
61
+ end
62
+
63
+ end
64
+ end
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fiveruns-dash-sensor
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.8.2
5
+ platform: ruby
6
+ authors:
7
+ - FiveRuns
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-02-05 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Sensor allows Dash to monitor ad-hoc infrastructure non-invasively
17
+ email: support@fiveruns.com
18
+ executables:
19
+ - fiveruns-dash-sensor
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.rdoc
24
+ - History.rdoc
25
+ files:
26
+ - bin/fiveruns-dash-sensor
27
+ - config.rb
28
+ - lib/daemon.rb
29
+ - lib/sensor.rb
30
+ - lib/sensor_plugin.rb
31
+ - plugins/memcached.rb
32
+ - plugins/starling.rb
33
+ - plugins/nginx.rb
34
+ - plugins/apache.rb
35
+ - README.rdoc
36
+ - History.rdoc
37
+ has_rdoc: false
38
+ homepage: http://github.com/fiveruns/dash-sensor/
39
+ post_install_message:
40
+ rdoc_options: []
41
+
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: "0"
49
+ version:
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: "0"
55
+ version:
56
+ requirements: []
57
+
58
+ rubyforge_project:
59
+ rubygems_version: 1.2.0
60
+ signing_key:
61
+ specification_version: 2
62
+ summary: Sensor allows Dash to monitor ad-hoc infrastructure non-invasively
63
+ test_files: []
64
+