mysampler 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/.rvmrc +1 -0
- data/Gemfile +4 -0
- data/README.md +53 -0
- data/bin/mysampler +88 -0
- data/lib/mysampler/client.rb +170 -0
- data/lib/mysampler/file.rb +90 -0
- data/lib/mysampler/processctl.rb +92 -0
- data/lib/mysampler/version.rb +3 -0
- data/lib/mysampler.rb +4 -0
- data/mysampler.gemspec +24 -0
- data/spec/tests.rb +65 -0
- metadata +132 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4adc593e649aa58ffb331e5ec1ee89432e25fd9a
|
4
|
+
data.tar.gz: 7fe23cf3f0aa2206aebae8a98066b7c287dbf1b2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 34212bb64ca530d7e3dfc12dc6ce47e25b93466ca380d82ce617a13aff2aec6462ba037b498e1547ca033fca7e3741ef283c3d2b009a646f0f1b9e964b8afb26
|
7
|
+
data.tar.gz: de9470ccf9822970cbebf9e5892c1e3b02eb763d17acb98786cf953dfc3b06cf5717df9631608bc830ff334c76e0142dcc034d62099fbc329a0dc52ecb3f9496
|
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use 2.1.1@mysampler --create --install
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
MySampler
|
2
|
+
=========
|
3
|
+
|
4
|
+
What is it?
|
5
|
+
-----------
|
6
|
+
MySampler is a tool written in ruby to poll SHOW GLOBAL STATUS in MySQL and output the values to either a CSV or graphite/carbon.
|
7
|
+
The interval at which the polling occurs can be specified and the output can be either the absolute or relative values, so you can see change over time.
|
8
|
+
If logging to CSV, the a date stamp is appended to the CSV file and it is rotated hourly (to be configurable later).
|
9
|
+
|
10
|
+
Dependencies
|
11
|
+
------------
|
12
|
+
|
13
|
+
MySampler requires the following gems:
|
14
|
+
|
15
|
+
graphite
|
16
|
+
sequel
|
17
|
+
|
18
|
+
Installation
|
19
|
+
------------
|
20
|
+
To install, simply install the dependencies above, and clone the repository and run mysample.rb
|
21
|
+
|
22
|
+
gem install sequel
|
23
|
+
gem install graphite
|
24
|
+
git clone https://github.com/9minutesnooze/mysampler.git
|
25
|
+
cd mysampler
|
26
|
+
./mysample.rb -o csv -u me -p secret -H localhost -f /tmp/mysample.csv -i 10 -r -d -k start
|
27
|
+
|
28
|
+
Options
|
29
|
+
-------
|
30
|
+
Usage ./mysample.rb [OPTIONS]
|
31
|
+
-u, --user USER MySQL User
|
32
|
+
-p, --pass PASSWORD MySQL Password
|
33
|
+
-P, --port PORT MySQL port (default 3306)
|
34
|
+
--pidfile PIDFILE PID File (default: `pwd`/mysample.pid)
|
35
|
+
-H, --host HOST MySQL hostname (default: localhost)
|
36
|
+
-f, --file FILENAME output filename (will be appended with rotation timestamp)
|
37
|
+
-o, --output (csv|graphite) Output format (default: csv)
|
38
|
+
-i, --sleep SECONDS Interval between runs (default: 10)
|
39
|
+
-r, --relative Show the difference between the current and previous values (default: false)
|
40
|
+
-d, --daemonize daemonize process (default: false)
|
41
|
+
-k (start|stop|status) command to pass daemon
|
42
|
+
--command
|
43
|
+
-g, --graphite HOST:PORT Graphite server:port
|
44
|
+
-h, --help this message
|
45
|
+
|
46
|
+
If daemonized with -d, currently STDERR/STDOUT does not go anywhere, so if you are having problems, try running it without the -d flag initially.
|
47
|
+
|
48
|
+
Caveats
|
49
|
+
-------
|
50
|
+
This project, while it runs in production, is rapidly changing so the command line parameters and output are not set in stone.
|
51
|
+
I will attempt to write release notes if something changes drastically.
|
52
|
+
Currently a lot of the object structure is being revamped and I am adding more features such as SHOW MUTEX STATUS, and SHOW ENGINE INNODB STATUS.
|
53
|
+
Those features are initially available in the class_refactor branch.
|
data/bin/mysampler
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require_relative '../lib/mysampler'
|
4
|
+
require 'optparse'
|
5
|
+
require 'sequel'
|
6
|
+
require 'logger'
|
7
|
+
|
8
|
+
$options = { :dbuser => nil,
|
9
|
+
:dbpass => nil,
|
10
|
+
:dbhost => "localhost",
|
11
|
+
:dbport => 3306,
|
12
|
+
:dbsocket => nil,
|
13
|
+
:daemonize => false,
|
14
|
+
:pidfile => Dir.pwd + "/mysample.pid",
|
15
|
+
:interval => 10, #seconds
|
16
|
+
:command => MySampler::ProcessCtl::STARTCMD ,
|
17
|
+
:output => MySampler::Client::CSVOUT,
|
18
|
+
:relative => false,
|
19
|
+
:graphitehost => nil, }
|
20
|
+
|
21
|
+
opts = OptionParser.new
|
22
|
+
opts.banner = "Usage #{$0} [OPTIONS]"
|
23
|
+
opts.on("-u", "--user USER", String, "MySQL User" ) { |v| $options[:dbuser] = v }
|
24
|
+
opts.on("-p", "--pass PASSWORD", String, "MySQL Password" ) { |v| $options[:dbpass] = v }
|
25
|
+
opts.on("-P", "--port PORT", Integer, "MySQL port (default #{$options[:dbport]})" ) { |v| $options[:dbport] = v }
|
26
|
+
opts.on("--pidfile PIDFILE", String, "PID File (default: #{$options[:pidfile]})" ) { |v| $options[:pidfile] = v }
|
27
|
+
opts.on("-H", "--host HOST", String, "MySQL hostname (default: #{$options[:dbhost]})" ) { |v| $options[:dbhost] = v }
|
28
|
+
opts.on("-f", "--file FILENAME", String, "output filename (will be appended with rotation timestamp)" ) { |v| $options[:outputfn] = v }
|
29
|
+
opts.on("-o", "--output (csv|graphite)", String, "Output format (default: csv)" ) do |v|
|
30
|
+
$options[:output] = case v
|
31
|
+
when "yaml"
|
32
|
+
MySampler::Client::YAMLOUT
|
33
|
+
when "csv"
|
34
|
+
MySampler::Client::CSVOUT
|
35
|
+
when "graphite"
|
36
|
+
require 'graphite/logger'
|
37
|
+
MySampler::Client::GRAPHITEOUT
|
38
|
+
else
|
39
|
+
puts opts
|
40
|
+
exit 1
|
41
|
+
end
|
42
|
+
end
|
43
|
+
opts.on("-i", "--sleep SECONDS", Integer, "Interval between runs (default: #{$options[:interval]})" ) { |v| $options[:interval] = v }
|
44
|
+
opts.on("-r", "--relative","Show the difference between the current and previous values (default: #{$options[:relative]})" ) { |v| $options[:relative] = v }
|
45
|
+
opts.on("-d", "--daemonize", "daemonize process (default: #{$options[:daemonize]})" ) { |v| $options[:daemonize] = true }
|
46
|
+
opts.on("-k", "--command (start|stop|status)", String, "command to pass daemon") do |v|
|
47
|
+
$options[:command] = case v
|
48
|
+
when "stop"
|
49
|
+
MySampler::ProcessCtl::STOPCMD
|
50
|
+
when "status"
|
51
|
+
MySampler::ProcessCtl::STATUSCMD
|
52
|
+
when "start"
|
53
|
+
MySampler::ProcessCtl::STARTCMD
|
54
|
+
else
|
55
|
+
puts opts
|
56
|
+
exit 1
|
57
|
+
end
|
58
|
+
end
|
59
|
+
opts.on("-g", "--graphite HOST:PORT", String, "Graphite server:port") { |v| $options[:graphitehost] = v }
|
60
|
+
opts.on("-h", "--help", "this message") { puts opts; exit 1}
|
61
|
+
opts.parse!
|
62
|
+
|
63
|
+
pc = MySampler::ProcessCtl.new
|
64
|
+
pc.daemonize = $options[:daemonize]
|
65
|
+
pc.pidfile = $options[:pidfile]
|
66
|
+
|
67
|
+
|
68
|
+
ms = MySampler::Client.new
|
69
|
+
ms.user = $options[:dbuser]
|
70
|
+
ms.pass = $options[:dbpass]
|
71
|
+
ms.host = $options[:dbhost]
|
72
|
+
ms.port = $options[:dbport]
|
73
|
+
ms.socket = $options[:dbsocket]
|
74
|
+
ms.interval = $options[:interval]
|
75
|
+
ms.output = $options[:output]
|
76
|
+
ms.relative = $options[:relative]
|
77
|
+
ms.outputfn = $options[:outputfn] if $options[:outputfn]
|
78
|
+
ms.rotateinterval = MySampler::FileRotating::HOUR
|
79
|
+
ms.graphitehost = $options[:graphitehost] if ms.output == MySampler::Client::GRAPHITEOUT
|
80
|
+
|
81
|
+
case $options[:command]
|
82
|
+
when MySampler::ProcessCtl::STOPCMD
|
83
|
+
pc.stop { puts "I'm done" }
|
84
|
+
when MySampler::ProcessCtl::STATUSCMD
|
85
|
+
exit pc.status
|
86
|
+
else
|
87
|
+
exit pc.start { ms.run }
|
88
|
+
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
require 'sequel'
|
2
|
+
|
3
|
+
module MySampler
|
4
|
+
class Client
|
5
|
+
CSVOUT, YAMLOUT,GRAPHITEOUT = 0,1,2
|
6
|
+
attr_accessor :user, :pass, :port, :socket, :host, :interval, :output, :relative, :outputfn, :rotateinterval, :graphitehost
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@user = nil
|
10
|
+
@pass = nil
|
11
|
+
@port = 3306
|
12
|
+
@socket = nil
|
13
|
+
@host = "localhost"
|
14
|
+
@query = 'SHOW /*!50002 GLOBAL */ STATUS'
|
15
|
+
@interval = 10
|
16
|
+
@relative = false
|
17
|
+
@output = CSVOUT
|
18
|
+
@prev_rows = {}
|
19
|
+
@outputfn = nil
|
20
|
+
@rotateinterval = FileRotating::HOUR
|
21
|
+
@rf = nil
|
22
|
+
@graphitehost = nil
|
23
|
+
@graphite = nil
|
24
|
+
@mysql_hostname = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def run
|
28
|
+
@sequel = db_connect
|
29
|
+
if @output == GRAPHITEOUT
|
30
|
+
get_mysql_hostname
|
31
|
+
conn_to_graphite if @output == GRAPHITEOUT
|
32
|
+
else
|
33
|
+
headers = get_header_rows
|
34
|
+
open_rotating_file(:header => headers.join(","), :interval => @rotateinterval)
|
35
|
+
end
|
36
|
+
|
37
|
+
first_run = true
|
38
|
+
loop do
|
39
|
+
begin
|
40
|
+
rows = @sequel[@query].to_hash(:Variable_name,:Value)
|
41
|
+
rows = values_to_numeric(rows)
|
42
|
+
rows = calc_relative(rows) if @relative
|
43
|
+
rows = scale_values(rows)
|
44
|
+
output_query(rows) unless first_run && @relative
|
45
|
+
first_run = false
|
46
|
+
rescue Exception => e
|
47
|
+
STDERR.puts "An error occurred #{e}"
|
48
|
+
end
|
49
|
+
|
50
|
+
sleep @interval
|
51
|
+
end
|
52
|
+
@rf.close if @rf && @outputfn
|
53
|
+
end
|
54
|
+
|
55
|
+
# get the real hostname of the MySQL Server that we are connected to
|
56
|
+
def get_mysql_hostname
|
57
|
+
@mysql_hostname = @sequel["SELECT @@hostname;"].first[:@@hostname]
|
58
|
+
end
|
59
|
+
|
60
|
+
def get_header_rows
|
61
|
+
@sequel[@query].to_hash(:Variable_name,:Value).keys
|
62
|
+
end
|
63
|
+
|
64
|
+
def output_header(rows)
|
65
|
+
@rf.puts(header) if @rf && @outputfn
|
66
|
+
end
|
67
|
+
|
68
|
+
def conn_to_graphite
|
69
|
+
@graphite = Graphite::Logger.new(@graphitehost)
|
70
|
+
# @graphite.logger = Logger.new('graphite.out')
|
71
|
+
end
|
72
|
+
|
73
|
+
def open_rotating_file (params)
|
74
|
+
@rf = @outputfn ? FileRotating.new(params, @outputfn, "w") : STDOUT
|
75
|
+
end
|
76
|
+
|
77
|
+
def is_counter? (key)
|
78
|
+
# list lovingly stolen from pt-mysql-summary
|
79
|
+
!%w[ Compression Delayed_insert_threads Innodb_buffer_pool_pages_data
|
80
|
+
Innodb_buffer_pool_pages_dirty Innodb_buffer_pool_pages_free
|
81
|
+
Innodb_buffer_pool_pages_latched Innodb_buffer_pool_pages_misc
|
82
|
+
Innodb_buffer_pool_pages_total Innodb_data_pending_fsyncs
|
83
|
+
Innodb_data_pending_reads Innodb_data_pending_writes
|
84
|
+
Innodb_os_log_pending_fsyncs Innodb_os_log_pending_writes
|
85
|
+
Innodb_page_size Innodb_row_lock_current_waits Innodb_row_lock_time_avg
|
86
|
+
Innodb_row_lock_time_max Key_blocks_not_flushed Key_blocks_unused
|
87
|
+
Key_blocks_used Last_query_cost Max_used_connections Ndb_cluster_node_id
|
88
|
+
Ndb_config_from_host Ndb_config_from_port Ndb_number_of_data_nodes
|
89
|
+
Not_flushed_delayed_rows Open_files Open_streams Open_tables
|
90
|
+
Prepared_stmt_count Qcache_free_blocks Qcache_free_memory
|
91
|
+
Qcache_queries_in_cache Qcache_total_blocks Rpl_status
|
92
|
+
Slave_open_temp_tables Slave_running Ssl_cipher Ssl_cipher_list
|
93
|
+
Ssl_ctx_verify_depth Ssl_ctx_verify_mode Ssl_default_timeout
|
94
|
+
Ssl_session_cache_mode Ssl_session_cache_size Ssl_verify_depth
|
95
|
+
Ssl_verify_mode Ssl_version Tc_log_max_pages_used Tc_log_page_size
|
96
|
+
Threads_cached Threads_connected Threads_running
|
97
|
+
Uptime_since_flush_status ].include? key
|
98
|
+
end
|
99
|
+
|
100
|
+
def calc_relative(rows)
|
101
|
+
result = {}
|
102
|
+
rows.each do |k,v|
|
103
|
+
if @prev_rows[k] && numeric?(v) && is_counter?(k)
|
104
|
+
result[k] = v - @prev_rows[k]
|
105
|
+
else
|
106
|
+
result[k] = v
|
107
|
+
end
|
108
|
+
end
|
109
|
+
@prev_rows = rows
|
110
|
+
return result
|
111
|
+
end
|
112
|
+
|
113
|
+
def prefix_keys ( h, prefix )
|
114
|
+
Hash[h.map { |k,v| [ "#{prefix}#{k}", v] }]
|
115
|
+
end
|
116
|
+
|
117
|
+
def numeric? (value)
|
118
|
+
true if Float(value) rescue false
|
119
|
+
end
|
120
|
+
|
121
|
+
def to_numeric (value)
|
122
|
+
numeric?(value) ? value.to_i : value
|
123
|
+
end
|
124
|
+
|
125
|
+
# scale the value to be per @interval if recording relative values
|
126
|
+
# since it doesn't make much sense to output values that are "per 5 seconds"
|
127
|
+
def scale_value (value)
|
128
|
+
(@relative && numeric?(value)) ? (value/@interval) : value
|
129
|
+
end
|
130
|
+
|
131
|
+
def scale_values ( rows )
|
132
|
+
Hash[rows.map do |k,v|
|
133
|
+
is_counter?(k) ? [k, scale_value(v)] : [k, v]
|
134
|
+
end]
|
135
|
+
end
|
136
|
+
|
137
|
+
def values_to_numeric ( h )
|
138
|
+
Hash[h.map { |k,v| [ k, to_numeric(v)] }]
|
139
|
+
end
|
140
|
+
|
141
|
+
def output_query (rows )
|
142
|
+
case @output
|
143
|
+
when YAMLOUT then
|
144
|
+
# result = YAML::dump({time => Time.now, rows})
|
145
|
+
when GRAPHITEOUT then
|
146
|
+
graphite_rows = prefix_keys(rows, "mysql.#{@mysql_hostname.split('.').reverse.join('.')}.")
|
147
|
+
@graphite.log(Time.now.to_i, graphite_rows) if @graphite
|
148
|
+
else # CSVOUT
|
149
|
+
@rf.puts(hash_to_csv(rows)) if @rf && @outputfn
|
150
|
+
end
|
151
|
+
true
|
152
|
+
end
|
153
|
+
|
154
|
+
def hash_to_csv ( rows, header = false )
|
155
|
+
str = header ? "Time" : "#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
|
156
|
+
rows.sort.each { |v| str += header ? ",#{v[0]}" : ",#{v[1]}" }
|
157
|
+
return str
|
158
|
+
end
|
159
|
+
|
160
|
+
def db_connect
|
161
|
+
params = { :host => @host,
|
162
|
+
:user => @user,
|
163
|
+
:port => @port,
|
164
|
+
:password => @pass }
|
165
|
+
params[:socket] = @socket if @socket
|
166
|
+
@sequel = Sequel.mysql(params)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
module MySampler
|
5
|
+
|
6
|
+
class FileRotating
|
7
|
+
YEAR, MONTH, DAY, HOUR, MINUTE, SECOND = 0,1,2,3,4,5
|
8
|
+
|
9
|
+
attr_accessor :header
|
10
|
+
|
11
|
+
def initialize (params, *args)
|
12
|
+
@interval = params[:interval] || DAY
|
13
|
+
@header = params[:header] || nil # a header to put at the top of every file
|
14
|
+
@args = args
|
15
|
+
@root_fn = args[0]
|
16
|
+
@stamp = get_date_stamp
|
17
|
+
|
18
|
+
open_local do |f|
|
19
|
+
f.puts @header if @header
|
20
|
+
if block_given?
|
21
|
+
return yield f
|
22
|
+
else
|
23
|
+
return f
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def close
|
29
|
+
if @f
|
30
|
+
@f.flock(File::LOCK_UN)
|
31
|
+
@f.close
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
def open_local
|
37
|
+
fn = sprintf("%s.%s",@root_fn,@stamp)
|
38
|
+
args = @args
|
39
|
+
args[0] = fn
|
40
|
+
# puts fn
|
41
|
+
|
42
|
+
begin
|
43
|
+
@f = File.open(*args)
|
44
|
+
@f.flock(File::LOCK_EX) if @f
|
45
|
+
if block_given?
|
46
|
+
return yield self
|
47
|
+
else
|
48
|
+
return self
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
def get_date_stamp
|
55
|
+
format = case @interval
|
56
|
+
when YEAR then '%Y'
|
57
|
+
when MONTH then '%Y%m'
|
58
|
+
when DAY then '%Y%m%d'
|
59
|
+
when HOUR then '%Y%m%d%H'
|
60
|
+
when MINUTE then '%Y%m%d%H%M'
|
61
|
+
when SECOND then '%Y%m%d%H%M%S'
|
62
|
+
else raise "Invalid interval"
|
63
|
+
end
|
64
|
+
return Time.now.strftime(format)
|
65
|
+
end
|
66
|
+
|
67
|
+
def method_missing(method, *args, &block)
|
68
|
+
# check to see if we need to reopen
|
69
|
+
stamp = get_date_stamp
|
70
|
+
|
71
|
+
if @stamp != stamp
|
72
|
+
@stamp = stamp
|
73
|
+
close
|
74
|
+
open_local
|
75
|
+
@f.puts @header if @header
|
76
|
+
end
|
77
|
+
return @f.send(method, *args, &block)
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
#FileRotating.new({:interval => FileRotating::SECOND, :header => "Header!!!"}, "/tmp/foo.txt", "w") do |f|
|
83
|
+
# 5.times do
|
84
|
+
# f.puts Time.now
|
85
|
+
# f.puts "Foo!"
|
86
|
+
# sleep 1
|
87
|
+
# end
|
88
|
+
#end
|
89
|
+
|
90
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module MySampler
|
2
|
+
class ProcessCtl
|
3
|
+
STARTCMD, STOPCMD, STATUSCMD = 0,1,2
|
4
|
+
|
5
|
+
attr_accessor :pidfile, :daemonize
|
6
|
+
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@pidfile = ""
|
10
|
+
@daemonize = false
|
11
|
+
@pid = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
def start
|
16
|
+
trap(:INT) { stop }
|
17
|
+
trap(:SIGTERM) { cleanup }
|
18
|
+
|
19
|
+
size = get_running_pids.size
|
20
|
+
if size > 0
|
21
|
+
puts "Daemon is already running"
|
22
|
+
return 1
|
23
|
+
end
|
24
|
+
|
25
|
+
# Daemonize.daemonize if @daemonize
|
26
|
+
if @daemonize
|
27
|
+
#http://stackoverflow.com/questions/1740308/create-a-daemon-with-double-fork-in-ruby
|
28
|
+
raise 'First fork failed' if (pid = fork) == -1
|
29
|
+
exit unless pid.nil?
|
30
|
+
|
31
|
+
Process.setsid
|
32
|
+
raise 'Second fork failed' if (pid = fork) == -1
|
33
|
+
exit unless pid.nil?
|
34
|
+
|
35
|
+
Dir.chdir '/'
|
36
|
+
File.umask 0000
|
37
|
+
STDIN.reopen '/dev/null'
|
38
|
+
STDOUT.reopen '/dev/null', 'a'
|
39
|
+
STDERR.reopen STDOUT
|
40
|
+
end
|
41
|
+
write_pid unless pidfile == ""
|
42
|
+
yield
|
43
|
+
return 0
|
44
|
+
end
|
45
|
+
|
46
|
+
def stop
|
47
|
+
# call user code if defined
|
48
|
+
begin
|
49
|
+
yield
|
50
|
+
rescue
|
51
|
+
end
|
52
|
+
get_running_pids.each do |pid|
|
53
|
+
puts "Killing pid #{pid}"
|
54
|
+
Process.kill :SIGTERM, pid
|
55
|
+
# can't do anything below here. Process is dead
|
56
|
+
end
|
57
|
+
return 0
|
58
|
+
end
|
59
|
+
|
60
|
+
# returns the exit status (1 if not running, 0 if running)
|
61
|
+
def status
|
62
|
+
size = get_running_pids.size
|
63
|
+
puts "#{File.basename $0} is #{"not " if size < 1}running."
|
64
|
+
return (size > 0) ? 0 : 1
|
65
|
+
end
|
66
|
+
|
67
|
+
protected
|
68
|
+
def cleanup
|
69
|
+
File.delete(@pidfile) if File.file?(@pidfile)
|
70
|
+
exit 0
|
71
|
+
end
|
72
|
+
|
73
|
+
def write_pid
|
74
|
+
@pid = Process.pid
|
75
|
+
File.open(@pidfile, "w") do |f|
|
76
|
+
# f.write($$)
|
77
|
+
f.write(Process.pid)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def get_running_pids
|
82
|
+
return [@pid] if @pid
|
83
|
+
result = []
|
84
|
+
if File.file? @pidfile
|
85
|
+
pid = File.read @pidfile
|
86
|
+
# big long line I stole to kill a pid
|
87
|
+
result = `ps -p #{pid} -o pid | sed 1d`.to_a.map!{|x| x.to_i}
|
88
|
+
end
|
89
|
+
return result
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
data/lib/mysampler.rb
ADDED
data/mysampler.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'mysampler/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "mysampler"
|
8
|
+
spec.version = MySampler::VERSION
|
9
|
+
spec.authors = ["Aaron Brown"]
|
10
|
+
spec.email = ["aaron@9minutesnooze.com"]
|
11
|
+
spec.summary = %q{A utility that logs MySQL statistics to CSV or graphite.}
|
12
|
+
spec.description = %q{MySampler is a tool written in ruby to poll SHOW GLOBAL STATUS in MySQL and output the values to either a CSV or graphite/carbon. The interval at which the polling occurs can be specified and the output can be either the absolute or relative values, so you can see change over time. If logging to CSV, the a date stamp is appended to the CSV file and it is rotated hourly (to be configurable later).}
|
13
|
+
spec.homepage = "https://github.com/9minutesnooze/mysampler"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
%w{mysql sequel graphite}.each { |gem| spec.add_dependency gem }
|
24
|
+
end
|
data/spec/tests.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'timecop'
|
2
|
+
require 'mysqlsampler'
|
3
|
+
|
4
|
+
|
5
|
+
describe "database connectivity" do
|
6
|
+
before(:all) do
|
7
|
+
@ms = MySQLSampler.new
|
8
|
+
@ms.user = "aaron"
|
9
|
+
@ms.pass = "n0m0r3181"
|
10
|
+
@ms.host = "locutus.borg.lan"
|
11
|
+
@ms.port = 3306
|
12
|
+
@ms.db_connect
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should return 'locutus.borg.lan'" do
|
16
|
+
@ms.get_mysql_hostname.should == @ms.host
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should return an array that includes 'Uptime'" do
|
20
|
+
@ms.get_header_rows.should include('Uptime')
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "Row operations" do
|
26
|
+
before(:all) do
|
27
|
+
Timecop.freeze(Time.local(2011,10,17,0,0,0))
|
28
|
+
@ms = MySQLSampler.new
|
29
|
+
end
|
30
|
+
|
31
|
+
after(:all) do
|
32
|
+
Timecop.return
|
33
|
+
end
|
34
|
+
|
35
|
+
# test relative operation
|
36
|
+
it "should return a key with 2 as the value" do
|
37
|
+
@ms.calc_relative({"Foo" => 4})
|
38
|
+
h = @ms.calc_relative({"Foo" => 6})
|
39
|
+
h["Foo"].should == 2
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should prefix key with 'foo.'" do
|
43
|
+
h = @ms.prefix_keys({"bar" => 1}, "foo.")
|
44
|
+
h.keys.first.should == "foo.bar"
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should convert a string to a numeric if it is a number" do
|
48
|
+
@ms.to_numeric("5").should == 5
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should not convert a string to a numeric if it is a string" do
|
52
|
+
@ms.to_numeric("a").should == "a"
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should convert a hash of string numbers to numbers" do
|
56
|
+
h = @ms.values_to_numeric({"a" => "1"})
|
57
|
+
h["a"].should == 1
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should convert a hash into a comma separated csv prefixed by time" do
|
61
|
+
h = { "a" => 1, "b" => 2, "c" => "foo" }
|
62
|
+
@ms.hash_to_csv(h).should == "2011-10-17 00:00:00,1,2,foo"
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
metadata
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mysampler
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Aaron Brown
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-04-29 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.5'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
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: mysql
|
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: sequel
|
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: graphite
|
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: MySampler is a tool written in ruby to poll SHOW GLOBAL STATUS in MySQL
|
84
|
+
and output the values to either a CSV or graphite/carbon. The interval at which
|
85
|
+
the polling occurs can be specified and the output can be either the absolute or
|
86
|
+
relative values, so you can see change over time. If logging to CSV, the a date
|
87
|
+
stamp is appended to the CSV file and it is rotated hourly (to be configurable later).
|
88
|
+
email:
|
89
|
+
- aaron@9minutesnooze.com
|
90
|
+
executables:
|
91
|
+
- mysampler
|
92
|
+
extensions: []
|
93
|
+
extra_rdoc_files: []
|
94
|
+
files:
|
95
|
+
- ".gitignore"
|
96
|
+
- ".rvmrc"
|
97
|
+
- Gemfile
|
98
|
+
- README.md
|
99
|
+
- bin/mysampler
|
100
|
+
- lib/mysampler.rb
|
101
|
+
- lib/mysampler/client.rb
|
102
|
+
- lib/mysampler/file.rb
|
103
|
+
- lib/mysampler/processctl.rb
|
104
|
+
- lib/mysampler/version.rb
|
105
|
+
- mysampler.gemspec
|
106
|
+
- spec/tests.rb
|
107
|
+
homepage: https://github.com/9minutesnooze/mysampler
|
108
|
+
licenses:
|
109
|
+
- MIT
|
110
|
+
metadata: {}
|
111
|
+
post_install_message:
|
112
|
+
rdoc_options: []
|
113
|
+
require_paths:
|
114
|
+
- lib
|
115
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
116
|
+
requirements:
|
117
|
+
- - ">="
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
version: '0'
|
120
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
requirements: []
|
126
|
+
rubyforge_project:
|
127
|
+
rubygems_version: 2.2.2
|
128
|
+
signing_key:
|
129
|
+
specification_version: 4
|
130
|
+
summary: A utility that logs MySQL statistics to CSV or graphite.
|
131
|
+
test_files:
|
132
|
+
- spec/tests.rb
|