bipbip 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ffb2e47b0d064209e1459b7be4036cc1ab15f2bb
4
+ data.tar.gz: 6da35aa18a085d1bfcc572c5d7197d903b7a4ee1
5
+ SHA512:
6
+ metadata.gz: 3ef24076ec298fc436416646b4c09d936b767282d93fc77e083da7345d7ce506efd8cb3915b93997390038c5030169ec66e5f5df80841ef32aac5df397882478
7
+ data.tar.gz: 3c6330c8064fe34ca717e177e7f6e1be8f86b0008cd02ace5f3e46a286381a6e0e80f7c2d2690faac28fba85fadfe296cb5e1c5c0a495802ba62de387cfb0eae
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013 Cargo Media
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,55 @@
1
+ bipbip
2
+ ======
3
+ Agent to collect server metrics and send them to the [CopperEgg RevealMetrics](http://copperegg.com/) platform.
4
+ Plugins for different metrics available in the `plugin/`-directory.
5
+ Will spawn a child process for every plugin and server you tell it to monitor.
6
+
7
+ Configure and run
8
+ -----------------
9
+ Pass the path to your configuration file to `bipbip` using the `-c` command line argument.
10
+ ```sh
11
+ bipbip -c /etc/bipbip/bipbip.yml
12
+ ```
13
+
14
+ The configuration file should list the services you want to collect for, and the servers for each of them, e.g.:
15
+ ```yml
16
+ loglevel: INFO
17
+ copperegg:
18
+ apikey: YOUR_APIKEY
19
+ frequency: 15
20
+
21
+ services:
22
+ -
23
+ plugin: Memcached
24
+ hostname: localhost
25
+ port: 11211
26
+ -
27
+ plugin: Mysql
28
+ hostname: localhost
29
+ port: 3306
30
+ username: root
31
+ password: root
32
+ -
33
+ plugin: Redis
34
+ hostname: localhost
35
+ port: 6379
36
+ -
37
+ plugin: Gearman
38
+ hostname: localhost
39
+ port: 4730
40
+ ```
41
+
42
+ Include configuration
43
+ ---------------------
44
+ In your configuration you can specify a directory to include service configurations from:
45
+ ```
46
+ include: services.d/
47
+ ```
48
+ This will include files from `/etc/bipbip/services.d/` and load them into the `services` configuration.
49
+
50
+ You could then add a file `/etc/bipbip/services.d/memcached.yml`:
51
+ ```yml
52
+ plugin: Memcached
53
+ hostname: localhost
54
+ port: 11211
55
+ ```
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../lib')
4
+ require 'bipbip'
5
+ require 'optparse'
6
+
7
+ name = 'bipbip'
8
+ version = Bipbip::VERSION
9
+
10
+ options = {}
11
+ optparse = OptionParser.new do |opts|
12
+ opts.banner = "Usage: #{name} [options]"
13
+
14
+ opts.on("-c", "--config PATH", "Configuration file") do |c|
15
+ options[:config] = c
16
+ end
17
+
18
+ opts.on("-v", "--version", "Version") do |v|
19
+ puts "#{name} v#{version}"
20
+ exit
21
+ end
22
+
23
+ opts.on("-h", "--help", "Show this help") do |h|
24
+ puts opts
25
+ exit
26
+ end
27
+ end
28
+
29
+
30
+ begin
31
+ optparse.parse!
32
+ if not options[:config]
33
+ puts "Missing options: config file [-c PATH]"
34
+ exit
35
+ end
36
+ rescue OptionParser::InvalidOption, OptionParser::MissingArgument
37
+ puts $!.to_s
38
+ puts optparse
39
+ exit
40
+ end
41
+
42
+ Bipbip::Agent.new.run options[:config]
@@ -0,0 +1,29 @@
1
+ module Bipbip
2
+ require 'rubygems' # For ruby < 1.9
3
+
4
+ require 'copperegg'
5
+ require 'yaml'
6
+ require 'logger'
7
+ require 'socket'
8
+
9
+ require 'bipbip/version'
10
+ require 'bipbip/interruptible_sleep'
11
+ require 'bipbip/agent'
12
+ require 'bipbip/plugin'
13
+ require 'bipbip/plugin/memcached'
14
+ require 'bipbip/plugin/mysql'
15
+ require 'bipbip/plugin/redis'
16
+ require 'bipbip/plugin/gearman'
17
+
18
+ def self.logger
19
+ @logger
20
+ end
21
+
22
+ def self.logger=(logger)
23
+ @logger = logger
24
+ end
25
+
26
+ def self.fqdn
27
+ @fqdn ||= Socket.gethostbyname(Socket.gethostname).first
28
+ end
29
+ end
@@ -0,0 +1,103 @@
1
+ module Bipbip
2
+
3
+ class Agent
4
+
5
+ def initialize
6
+ @plugin_pids = []
7
+ end
8
+
9
+ def run(config_file)
10
+ config = YAML.load(File.open(config_file))
11
+
12
+ Bipbip.logger = Logger.new(STDOUT)
13
+ Bipbip.logger.level = Logger::const_get(config['loglevel'] || 'INFO')
14
+ Bipbip.logger.info 'Startup...'
15
+
16
+ CopperEgg::Api.apikey = config['copperegg']['apikey']
17
+ CopperEgg::Api.host = config['copperegg']['host'] if !config['copperegg']['host'].nil?
18
+ frequency = config['copperegg']['frequency'].to_i
19
+
20
+ if ![5, 15, 60, 300, 900, 3600, 21600].include?(frequency)
21
+ Bipbip.logger.fatal "Invalid frequency: #{frequency}"
22
+ exit
23
+ end
24
+
25
+ ['INT', 'TERM'].each { |sig| trap(sig) {
26
+ Thread.new { interrupt }
27
+ } }
28
+
29
+ services = config['services']
30
+ if config.has_key?('include')
31
+ include_path = File.expand_path(config['include'], File.dirname(config_file))
32
+ services += load_include_configs(include_path)
33
+ end
34
+
35
+ metric_groups = load_metric_groups
36
+ dashboards = load_dashboards
37
+
38
+ plugin_names = services.map { |service| service['plugin'] }
39
+ plugin_names.each do |plugin_name|
40
+ plugin = Plugin::const_get(plugin_name).new
41
+
42
+ metric_group = metric_groups.detect { |m| m.name == plugin_name }
43
+ if metric_group.nil? || !metric_group.is_a?(CopperEgg::MetricGroup)
44
+ Bipbip.logger.info "Creating metric group `#{plugin_name}`"
45
+ metric_group = CopperEgg::MetricGroup.new(:name => plugin_name, :label => plugin_name, :frequency => frequency)
46
+ end
47
+ metric_group.frequency = frequency
48
+ metric_group.metrics = plugin.metrics_schema
49
+ metric_group.save
50
+
51
+ dashboard = dashboards.detect { |d| d.name == plugin_name }
52
+ if dashboard.nil?
53
+ Bipbip.logger.info "Creating dashboard `#{plugin_name}`"
54
+ metrics = metric_group.metrics || []
55
+ CopperEgg::CustomDashboard.create(metric_group, :name => plugin_name, :identifiers => nil, :metrics => metrics)
56
+ end
57
+ end
58
+
59
+ services.each do |service|
60
+ plugin_name = service['plugin']
61
+ Bipbip.logger.info "Starting plugin #{plugin_name}"
62
+ plugin = Plugin::const_get(plugin_name).new
63
+ @plugin_pids.push plugin.run(service, frequency)
64
+ end
65
+
66
+ p Process.waitall
67
+ end
68
+
69
+ def load_metric_groups
70
+ Bipbip.logger.info 'Loading metric groups'
71
+ metric_groups = CopperEgg::MetricGroup.find
72
+ if metric_groups.nil?
73
+ raise 'Cannot load metric groups'
74
+ end
75
+ metric_groups
76
+ end
77
+
78
+ def load_dashboards
79
+ Bipbip.logger.info 'Loading dashboards'
80
+ dashboards = CopperEgg::CustomDashboard.find
81
+ if dashboards.nil?
82
+ raise 'Cannot load dashboards'
83
+ end
84
+ dashboards
85
+ end
86
+
87
+ def load_include_configs(directory)
88
+ files = Dir[directory + '/**/*.yaml', directory + '/**/*.yml']
89
+ services = files.map {|file| YAML.load(File.open(file))}
90
+ end
91
+
92
+ def interrupt
93
+ Bipbip.logger.info 'Interrupt, killing plugin processes...'
94
+ @plugin_pids.each { |pid| Process.kill('TERM', pid) }
95
+
96
+ Bipbip.logger.info 'Waiting for all plugin processes to exit...'
97
+ Process.waitall
98
+
99
+ Bipbip.logger.info 'Exiting'
100
+ exit
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,10 @@
1
+ module InterruptibleSleep
2
+ def interruptible_sleep(seconds)
3
+ @_sleep_check, @_sleep_interrupt = IO.pipe
4
+ IO.select([@_sleep_check], nil, nil, seconds)
5
+ end
6
+
7
+ def interrupt_sleep
8
+ @_sleep_interrupt.close if @_sleep_interrupt and !@_sleep_interrupt.closed?
9
+ end
10
+ end
@@ -0,0 +1,65 @@
1
+ module Bipbip
2
+
3
+ class Plugin
4
+ include InterruptibleSleep
5
+
6
+ def initialize
7
+ end
8
+
9
+ def run(server, frequency)
10
+ child_pid = fork do
11
+ ['INT', 'TERM'].each { |sig| trap(sig) {
12
+ Thread.new { interrupt } if !@interrupted
13
+ } }
14
+
15
+ retry_delay = frequency
16
+ metric_identifier = metric_identifier(server)
17
+ begin
18
+ until interrupted? do
19
+ time = Time.now
20
+ data = monitor(server)
21
+ Bipbip.logger.debug "#{name} #{metric_identifier}: Data: #{data}"
22
+ CopperEgg::MetricSample.save(name, metric_identifier, Time.now.to_i, data)
23
+ retry_delay = frequency
24
+ interruptible_sleep (frequency - (Time.now - time))
25
+ end
26
+ rescue => e
27
+ Bipbip.logger.error "#{name} #{metric_identifier}: Error getting data: #{e.inspect}"
28
+ interruptible_sleep retry_delay
29
+ retry_delay += frequency if retry_delay < frequency * 10
30
+ retry
31
+ end
32
+ end
33
+ end
34
+
35
+ def interrupt
36
+ Bipbip.logger.info "Interrupting plugin process #{Process.pid}"
37
+ @interrupted = true
38
+ interrupt_sleep
39
+ end
40
+
41
+ def interrupted?
42
+ @interrupted || Process.getpgid(Process.ppid) != Process.getpgrp
43
+ end
44
+
45
+ def name
46
+ self.class.name.split('::').last
47
+ end
48
+
49
+ def metric_identifier(server)
50
+ Bipbip.fqdn + '::' + server['hostname']
51
+ end
52
+
53
+ def metrics_names
54
+ metrics_schema.map {|metric| metric[:name] }
55
+ end
56
+
57
+ def metrics_schema
58
+ raise 'Missing method metrics_schema'
59
+ end
60
+
61
+ def monitor(server)
62
+ raise 'Missing method monitor'
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,29 @@
1
+ require 'gearman/server'
2
+ class GearmanServer < Gearman::Server
3
+ end
4
+
5
+ module Bipbip
6
+
7
+ class Plugin::Gearman < Plugin
8
+
9
+ def metrics_schema
10
+ [
11
+ {:name => 'jobs_queued_total', :type => 'ce_gauge', :unit => 'Jobs'},
12
+ ]
13
+ end
14
+
15
+ def monitor(server)
16
+ gearman = GearmanServer.new(server['hostname'] + ':' + server['port'].to_s)
17
+ stats = gearman.status
18
+
19
+ jobs_queued_total = 0
20
+ stats.each do |function_name, data|
21
+ data.each do |queue, stats|
22
+ jobs_queued_total += queue.to_i
23
+ end
24
+ end
25
+
26
+ {:jobs_queued_total => jobs_queued_total}
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,30 @@
1
+ require 'memcached'
2
+ class MemcachedClient < Memcached
3
+ end
4
+
5
+ module Bipbip
6
+
7
+ class Plugin::Memcached < Plugin
8
+
9
+ def metrics_schema
10
+ [
11
+ {:name => 'cmd_get', :type => 'ce_counter'},
12
+ {:name => 'cmd_set', :type => 'ce_counter'},
13
+ {:name => 'get_misses', :type => 'ce_counter'},
14
+ {:name => 'limit_maxbytes', :type => 'ce_gauge', :unit => 'b'},
15
+ {:name => 'bytes', :type => 'ce_gauge', :unit => 'b'},
16
+ ]
17
+ end
18
+
19
+ def monitor(server)
20
+ cache = MemcachedClient.new(server['hostname'] + ':' + server['port'].to_s)
21
+ stats = cache.stats
22
+
23
+ data = {}
24
+ metrics_names.each do |key|
25
+ data[key] = stats[key.to_sym].shift.to_i
26
+ end
27
+ data
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,75 @@
1
+ require 'mysql2'
2
+
3
+ module Bipbip
4
+
5
+ class Plugin::Mysql < Plugin
6
+
7
+ def metrics_schema
8
+ [
9
+ {:name => 'max_connections', :type => 'ce_gauge', :unit => 'Connections'},
10
+ {:name => 'Max_used_connections', :type => 'ce_gauge', :unit => 'Connections'},
11
+ {:name => 'Connections', :type => 'ce_counter', :unit => 'Connections'},
12
+ {:name => 'Threads_connected', :type => 'ce_gauge', :unit => 'Threads'},
13
+
14
+ {:name => 'Seconds_Behind_Master', :type => 'ce_gauge', :unit => 'Seconds'},
15
+
16
+ {:name => 'Created_tmp_disk_tables', :type => 'ce_counter', :unit => 'Tables'},
17
+
18
+ {:name => 'Queries', :type => 'ce_counter', :unit => 'Queries'},
19
+ {:name => 'Slow_queries', :type => 'ce_counter', :unit => 'Queries'},
20
+
21
+ {:name => 'Bytes_received', :type => 'ce_counter', :unit => 'b'},
22
+ {:name => 'Bytes_sent', :type => 'ce_counter', :unit => 'b'},
23
+
24
+ {:name => 'Table_locks_immediate', :type => 'ce_counter', :unit => 'Locks'},
25
+ {:name => 'Table_locks_waited', :type => 'ce_counter', :unit => 'Locks'},
26
+
27
+ {:name => 'Processlist', :type => 'ce_gauge', :unit => 'Processes'},
28
+ {:name => 'Processlist_Locked', :type => 'ce_gauge', :unit => 'Processes'},
29
+ {:name => 'Processlist_Sending_data', :type => 'ce_gauge', :unit => 'Processes'},
30
+
31
+ {:name => 'Com_select', :type => 'ce_counter', :unit => 'Commands'},
32
+ {:name => 'Com_delete', :type => 'ce_counter', :unit => 'Commands'},
33
+ {:name => 'Com_insert', :type => 'ce_counter', :unit => 'Commands'},
34
+ {:name => 'Com_update', :type => 'ce_counter', :unit => 'Commands'},
35
+ {:name => 'Com_replace', :type => 'ce_counter', :unit => 'Commands'},
36
+ ]
37
+ end
38
+
39
+ def monitor(server)
40
+ mysql = Mysql2::Client.new(
41
+ :host => server['hostname'],
42
+ :port => server['port'],
43
+ :username => server['username'],
44
+ :password => server['password']
45
+ )
46
+
47
+ stats = Hash.new(0)
48
+
49
+ mysql.query('SHOW GLOBAL STATUS').each do |v|
50
+ stats[v['Variable_name']] = v['Value'].to_i
51
+ end
52
+
53
+ mysql.query('SHOW VARIABLES').each do |v|
54
+ stats[v['Variable_name']] = v['Value'].to_i
55
+ end
56
+
57
+ slave_status = mysql.query('SHOW SLAVE STATUS').first
58
+ if slave_status
59
+ stats['Seconds_Behind_Master'] = slave_status['Seconds_Behind_Master'].to_i
60
+ end
61
+
62
+ processlist = mysql.query('SHOW PROCESSLIST')
63
+ stats['Processlist'] = processlist.count
64
+ processlist.each do |process|
65
+ stats['Processlist_' + process['State'].sub(' ', '_')] += 1 unless process['State'].empty?
66
+ end
67
+
68
+ data = {}
69
+ metrics_names.each do |key|
70
+ data[key] = stats[key]
71
+ end
72
+ data
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,30 @@
1
+ require 'redis'
2
+ class RedisClient < Redis
3
+ end
4
+
5
+ module Bipbip
6
+
7
+ class Plugin::Redis < Plugin
8
+
9
+ def metrics_schema
10
+ [
11
+ {:name => 'total_commands_processed', :type => 'ce_counter', :unit => 'Commands'},
12
+ {:name => 'used_memory', :type => 'ce_gauge', :unit => 'b'},
13
+ ]
14
+ end
15
+
16
+ def monitor(server)
17
+ redis = RedisClient.new(
18
+ :host => server['hostname'],
19
+ :port => server['port']
20
+ )
21
+ stats = redis.info
22
+
23
+ data = {}
24
+ metrics_names.each do |key|
25
+ data[key] = stats[key].to_i
26
+ end
27
+ data
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,3 @@
1
+ module Bipbip
2
+ VERSION = '0.0.7'
3
+ end
metadata ADDED
@@ -0,0 +1,126 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bipbip
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.7
5
+ platform: ruby
6
+ authors:
7
+ - Cargo Media
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-12-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: copperegg
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.6.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.6.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: memcached
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: mysql2
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: redis
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: gearman-ruby
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: Agent to collect data for common server programs and push them to CopperEgg
84
+ email: hello@cargomedia.ch
85
+ executables:
86
+ - bipbip
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - LICENSE
91
+ - README.md
92
+ - bin/bipbip
93
+ - lib/bipbip/agent.rb
94
+ - lib/bipbip/interruptible_sleep.rb
95
+ - lib/bipbip/plugin/gearman.rb
96
+ - lib/bipbip/plugin/memcached.rb
97
+ - lib/bipbip/plugin/mysql.rb
98
+ - lib/bipbip/plugin/redis.rb
99
+ - lib/bipbip/plugin.rb
100
+ - lib/bipbip/version.rb
101
+ - lib/bipbip.rb
102
+ homepage: https://github.com/cargomedia/bipbip
103
+ licenses:
104
+ - MIT
105
+ metadata: {}
106
+ post_install_message:
107
+ rdoc_options: []
108
+ require_paths:
109
+ - lib
110
+ required_ruby_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ required_rubygems_version: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ requirements: []
121
+ rubyforge_project:
122
+ rubygems_version: 2.1.11
123
+ signing_key:
124
+ specification_version: 4
125
+ summary: Gather services data and store in CopperEgg
126
+ test_files: []