fiveruns-dash-sensor 0.8.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.
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
+