redmon 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -4
- data/.travis.yml +9 -1
- data/README.md +11 -9
- data/bin/redmon +9 -3
- data/lib/redmon.rb +15 -19
- data/lib/redmon/app.rb +14 -5
- data/lib/redmon/config.rb +4 -3
- data/lib/redmon/helpers.rb +1 -3
- data/lib/redmon/public/redmon.css +15 -1
- data/lib/redmon/public/redmon.js +88 -80
- data/lib/redmon/public/vendor/bootstrap.min.css +1 -1
- data/lib/redmon/public/vendor/d3.v3.min.js +4 -0
- data/lib/redmon/public/vendor/nv.d3.css +656 -0
- data/lib/redmon/public/vendor/nv.d3.min.js +5 -0
- data/lib/redmon/redis.rb +1 -1
- data/lib/redmon/version.rb +1 -1
- data/lib/redmon/views/app.haml +9 -3
- data/redmon.gemspec +0 -1
- data/spec/app_spec.rb +7 -1
- data/spec/config_spec.rb +3 -3
- metadata +8 -22
- data/lib/redmon/public/vendor/jquery.flot.js +0 -2651
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
# Redmon
|
2
2
|
|
3
|
-
** Work in progress in the very early stages of dev **
|
4
|
-
|
5
3
|
Simple sinatra based dashbord for redis. After seeing the [fnordmetric](https://github.com/paulasmuth/fnordmetric)
|
6
4
|
project I was inspired to write this. Some of the ideas there have be carried over here.
|
7
5
|
|
8
6
|
[ ![Build status - Travis-ci](https://secure.travis-ci.org/steelThread/redmon.png) ](http://travis-ci.org/steelThread/redmon)
|
9
7
|
|
8
|
+
[![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/steelThread/redmon)
|
9
|
+
|
10
10
|
----
|
11
11
|
|
12
12
|
Watch your redis server live.
|
@@ -38,17 +38,18 @@ gem install redmon
|
|
38
38
|
## Usage
|
39
39
|
|
40
40
|
```bash
|
41
|
-
$
|
42
|
-
|
43
|
-
Usage: bin/redmon (options)
|
41
|
+
$ redmon -h
|
42
|
+
Usage: /Users/sean/codez/steelThread/redmon/vendor/ruby/1.9.1/bin/redmon (options)
|
44
43
|
-a, --address ADDRESS The thin bind address for the app (default: 0.0.0.0)
|
45
44
|
-n, --namespace NAMESPACE The root Redis namespace (default: redmon)
|
46
45
|
-i, --interval SECS Poll interval in secs for the worker (default: 10)
|
47
46
|
-p, --port PORT The thin bind port for the app (default: 4567)
|
48
47
|
-r, --redis URL The Redis url for monitor (default: redis://127.0.0.1:6379)
|
48
|
+
-s, --secure CREDENTIALS Use basic auth. Colon separated credentials, eg admin:admin.
|
49
49
|
--no-app Do not run the web app to present stats
|
50
50
|
--no-worker Do not run a worker to collect the stats
|
51
|
-
|
51
|
+
|
52
|
+
$ redmon
|
52
53
|
>> Thin web server (v1.3.1 codename Triple Espresso)
|
53
54
|
>> Maximum connections set to 1024
|
54
55
|
>> Listening on 0.0.0.0:4567, CTRL+C to stop
|
@@ -63,7 +64,6 @@ $ ruby load_sim.rb
|
|
63
64
|
|
64
65
|
Open your browser to 0.0.0.0:4567
|
65
66
|
|
66
|
-
|
67
67
|
## Using in a Rails application
|
68
68
|
|
69
69
|
Add to Gemfile:
|
@@ -85,12 +85,14 @@ You can configure the Redmon using an initializer config/initializers/redmon.rb:
|
|
85
85
|
Redmon.configure do |config|
|
86
86
|
config.redis_url = 'redis://127.0.0.1:6379'
|
87
87
|
config.namespace = 'redmon'
|
88
|
-
config.poll_interval = 10
|
89
88
|
end
|
90
89
|
```
|
91
90
|
|
92
91
|
This will mount the Redmon application to the /redmon/ path. The trailing slash
|
93
|
-
is important.
|
92
|
+
is important. The worker that gathers the redis info stats will not be started
|
93
|
+
when Redmon is mounted. In order to get a worker running inside of your Rails
|
94
|
+
app you can try this [Railtie](https://github.com/steelThread/redmon/pull/19#issuecomment-7273659)
|
95
|
+
based approach.
|
94
96
|
|
95
97
|
## License
|
96
98
|
|
data/bin/redmon
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
+
require 'rubygems'
|
3
4
|
require 'mixlib/cli'
|
4
5
|
require 'redmon'
|
5
6
|
|
6
7
|
class RedmonCLI
|
7
8
|
include Mixlib::CLI
|
8
9
|
|
9
|
-
to_i
|
10
|
+
to_i = lambda { |s| s.to_i }
|
10
11
|
|
11
12
|
option :address,
|
12
13
|
:short => '-a ADDRESS',
|
@@ -33,6 +34,11 @@ class RedmonCLI
|
|
33
34
|
:default => 'redmon',
|
34
35
|
:description => 'The root Redis namespace (default: redmon)'
|
35
36
|
|
37
|
+
option :secure,
|
38
|
+
:short => '-s CREDENTIALS',
|
39
|
+
:long => '--secure CREDENTIALS',
|
40
|
+
:description => "Use basic auth. Colon separated credentials, eg admin:admin."
|
41
|
+
|
36
42
|
option :poll_interval,
|
37
43
|
:short => '-i SECS',
|
38
44
|
:long => '--interval SECS',
|
@@ -56,10 +62,10 @@ class RedmonCLI
|
|
56
62
|
|
57
63
|
def run
|
58
64
|
parse_options
|
59
|
-
config[:web_interface] = [config[:address], config[:port]]
|
65
|
+
config[:web_interface] = [config[:address], config[:port]] if config[:app]
|
60
66
|
config
|
61
67
|
end
|
62
68
|
|
63
69
|
end
|
64
70
|
|
65
|
-
Redmon.run RedmonCLI.new.run
|
71
|
+
Redmon.run RedmonCLI.new.run
|
data/lib/redmon.rb
CHANGED
@@ -1,10 +1,5 @@
|
|
1
|
-
require 'redmon/config'
|
2
|
-
require 'active_support/core_ext'
|
3
1
|
require 'eventmachine'
|
4
|
-
require '
|
5
|
-
require 'redis'
|
6
|
-
require 'sinatra/base'
|
7
|
-
require 'thin'
|
2
|
+
require 'active_support/core_ext'
|
8
3
|
|
9
4
|
module Redmon
|
10
5
|
extend self
|
@@ -13,8 +8,11 @@ module Redmon
|
|
13
8
|
config.apply opts
|
14
9
|
start_em
|
15
10
|
rescue Exception => e
|
16
|
-
|
17
|
-
|
11
|
+
unless e.is_a?(SystemExit)
|
12
|
+
log "!!! Redmon has shit the bed, restarting... #{e.message}"
|
13
|
+
sleep(1)
|
14
|
+
run(opts)
|
15
|
+
end
|
18
16
|
end
|
19
17
|
|
20
18
|
def start_em
|
@@ -27,8 +25,10 @@ module Redmon
|
|
27
25
|
end
|
28
26
|
|
29
27
|
def start_app
|
30
|
-
|
31
|
-
Thin::Server.start(*config.web_interface
|
28
|
+
require 'thin'
|
29
|
+
Thin::Server.start(*config.web_interface) do
|
30
|
+
run Redmon::App.new
|
31
|
+
end
|
32
32
|
log "listening on http##{config.web_interface.join(':')}"
|
33
33
|
rescue Exception => e
|
34
34
|
log "Can't start Redmon::App. port in use? Error #{e}"
|
@@ -46,14 +46,10 @@ module Redmon
|
|
46
46
|
puts "[#{Time.now.strftime('%y-%m-%d %H:%M:%S')}] #{msg}"
|
47
47
|
end
|
48
48
|
|
49
|
-
|
50
|
-
|
51
|
-
config.send :option
|
52
|
-
end
|
49
|
+
require 'redmon/config'
|
50
|
+
require 'redmon/redis'
|
53
51
|
|
54
|
-
|
52
|
+
autoload :App, 'redmon/app'
|
53
|
+
autoload :Worker, 'redmon/worker'
|
55
54
|
|
56
|
-
|
57
|
-
require 'redmon/helpers'
|
58
|
-
require 'redmon/app'
|
59
|
-
require 'redmon/worker'
|
55
|
+
end
|
data/lib/redmon/app.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
+
require 'sinatra/base'
|
1
2
|
require 'redmon/helpers'
|
3
|
+
require 'haml'
|
2
4
|
|
3
5
|
module Redmon
|
4
6
|
class App < Sinatra::Base
|
@@ -6,17 +8,24 @@ module Redmon
|
|
6
8
|
helpers Redmon::Helpers
|
7
9
|
|
8
10
|
use Rack::Static, {
|
9
|
-
urls
|
10
|
-
root
|
11
|
-
cache_control
|
11
|
+
:urls => [/\.css$/, /\.js$/],
|
12
|
+
:root => "#{root}/public",
|
13
|
+
:cache_control => 'public, max-age=3600'
|
12
14
|
}
|
13
15
|
|
16
|
+
if Redmon.config.secure
|
17
|
+
use Rack::Auth::Basic do |username, password|
|
18
|
+
[username, password] == Redmon.config.secure.split(':')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
14
22
|
get '/' do
|
15
23
|
haml :app
|
16
24
|
end
|
17
25
|
|
18
26
|
get '/cli' do
|
19
|
-
args = params[:command].split
|
27
|
+
args = params[:command].split(/ *"(.*?)" *| *'(.*?)' *| /)
|
28
|
+
args.reject!(&:blank?)
|
20
29
|
@cmd = args.shift.downcase.intern
|
21
30
|
begin
|
22
31
|
raise RuntimeError unless supported? @cmd
|
@@ -44,4 +53,4 @@ module Redmon
|
|
44
53
|
end
|
45
54
|
|
46
55
|
end
|
47
|
-
end
|
56
|
+
end
|
data/lib/redmon/config.rb
CHANGED
@@ -14,7 +14,8 @@ module Redmon
|
|
14
14
|
:app => true,
|
15
15
|
:worker => true,
|
16
16
|
:web_interface => ['0.0.0.0', 4567],
|
17
|
-
:poll_interval => 10
|
17
|
+
:poll_interval => 10,
|
18
|
+
:secure => false
|
18
19
|
}
|
19
20
|
|
20
21
|
attr_accessor(*DEFAULTS.keys)
|
@@ -24,8 +25,8 @@ module Redmon
|
|
24
25
|
end
|
25
26
|
|
26
27
|
def apply(opts)
|
27
|
-
opts.each {|k,v| send("#{k}=", v) if respond_to? k}
|
28
|
+
opts.each { |k,v| send("#{k}=", v) if respond_to? k }
|
28
29
|
end
|
29
30
|
|
30
31
|
end
|
31
|
-
end
|
32
|
+
end
|
data/lib/redmon/helpers.rb
CHANGED
@@ -108,7 +108,21 @@ body {
|
|
108
108
|
|
109
109
|
#terminal input:focus{ box-shadow:none; }
|
110
110
|
|
111
|
-
.chart {
|
111
|
+
.chart-container {
|
112
|
+
position: relative;
|
112
113
|
width:880px;
|
113
114
|
height:225px;
|
115
|
+
margin-top: 10px;
|
116
|
+
}
|
117
|
+
|
118
|
+
.label {
|
119
|
+
padding: none;
|
120
|
+
font-size: inherit;
|
121
|
+
font-weight: bold;
|
122
|
+
color: white;
|
123
|
+
text-transform: uppercase;
|
124
|
+
background-color: #404040;
|
125
|
+
-webkit-border-radius: none;
|
126
|
+
-moz-border-radius: none;
|
127
|
+
border-radius: none;
|
114
128
|
}
|
data/lib/redmon/public/redmon.js
CHANGED
@@ -52,17 +52,20 @@ var Redmon = (function() {
|
|
52
52
|
}
|
53
53
|
|
54
54
|
function formatDate(date) {
|
55
|
-
var d = new Date(parseInt(
|
55
|
+
var d = new Date(parseInt(date));
|
56
56
|
return d.getMonth()+1+'/'+d.getDate()+' '+d.getHours()+':'+d.getMinutes()+':'+d.getSeconds();
|
57
57
|
}
|
58
58
|
|
59
59
|
function formatNumber(num) {
|
60
|
-
return
|
60
|
+
return d3.format(',.0f')(d3.round(num, 2));
|
61
|
+
}
|
62
|
+
|
63
|
+
function formatMetric(num) {
|
64
|
+
return d3.format('s')(num);
|
61
65
|
}
|
62
66
|
|
63
67
|
function formatTime(time) {
|
64
|
-
|
65
|
-
return d.getHours()+':'+d.getMinutes();
|
68
|
+
return d3.time.format('%H:%S')(new Date(time));
|
66
69
|
}
|
67
70
|
|
68
71
|
/**
|
@@ -70,10 +73,10 @@ var Redmon = (function() {
|
|
70
73
|
*/
|
71
74
|
function base1024(arg) {
|
72
75
|
var y = arg;
|
73
|
-
if (y >= 1073741824) { return (y / 1073741824).toFixed(2) + "
|
74
|
-
else if (y >= 1048576) { return (y / 1048576).toFixed(1) + "
|
75
|
-
else if (y >= 1024) { return (y / 1024).toFixed(0) + "
|
76
|
-
else if (y < 1 && y > 0) { return y.toFixed(0) }
|
76
|
+
if (y >= 1073741824) { return (y / 1073741824).toFixed(2) + "Gb" }
|
77
|
+
else if (y >= 1048576) { return (y / 1048576).toFixed(1) + "Mb" }
|
78
|
+
else if (y >= 1024) { return (y / 1024).toFixed(0) + "Kb" }
|
79
|
+
else if (y < 1 && y > 0) { return y.toFixed(0) + "b"}
|
77
80
|
else { return y }
|
78
81
|
}
|
79
82
|
|
@@ -146,57 +149,57 @@ var Redmon = (function() {
|
|
146
149
|
//////////////////////////////////////////////////////////////////////
|
147
150
|
// encapsulate the keyspace chart
|
148
151
|
var memoryWidget = (function() {
|
149
|
-
var
|
150
|
-
, dataset;
|
152
|
+
var chart
|
153
|
+
, dataset = [];
|
151
154
|
|
152
155
|
function render(data) {
|
153
156
|
dataset = points(data);
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
}
|
170
|
-
}
|
171
|
-
});
|
157
|
+
chart = nv.models.lineChart()
|
158
|
+
.x(function(d) { return d.x })
|
159
|
+
.y(function(d) { return d.y })
|
160
|
+
.margin({top : 10, right : 20, bottom : 20, left : 60})
|
161
|
+
.showLegend(false);
|
162
|
+
|
163
|
+
chart.xAxis
|
164
|
+
.tickFormat(formatTime);
|
165
|
+
|
166
|
+
chart.yAxis
|
167
|
+
.tickFormat(base1024);
|
168
|
+
|
169
|
+
update();
|
170
|
+
|
171
|
+
events.bind('data', onData);
|
172
172
|
}
|
173
173
|
|
174
174
|
function points(data) {
|
175
|
-
|
175
|
+
if (data.length)
|
176
|
+
return data.map(point);
|
177
|
+
|
178
|
+
return [];
|
176
179
|
}
|
177
180
|
|
178
181
|
function point(info) {
|
179
|
-
return
|
180
|
-
parseInt(info.time),
|
181
|
-
parseInt(info.used_memory)
|
182
|
-
|
182
|
+
return !info ? {} : {
|
183
|
+
x : parseInt(info.time),
|
184
|
+
y : parseInt(info.used_memory)
|
185
|
+
}
|
183
186
|
}
|
184
187
|
|
185
188
|
function onData(ev, data) {
|
186
|
-
if (
|
187
|
-
|
188
|
-
dataset.shift()
|
189
|
-
}
|
190
|
-
|
191
|
-
dataset.push(point(data));
|
192
|
-
plot.setData([dataset]);
|
193
|
-
plot.setupGrid();
|
194
|
-
plot.draw();
|
189
|
+
if (dataset.length >= 200) {
|
190
|
+
dataset.shift()
|
195
191
|
}
|
192
|
+
|
193
|
+
dataset.push(point(data));
|
194
|
+
update();
|
196
195
|
}
|
197
196
|
|
198
|
-
|
199
|
-
|
197
|
+
function update() {
|
198
|
+
d3.select('#memory-chart svg')
|
199
|
+
.datum([{key : '', values: dataset}])
|
200
|
+
.transition()
|
201
|
+
.ease("linear").call(chart);
|
202
|
+
}
|
200
203
|
|
201
204
|
return {
|
202
205
|
render: render
|
@@ -206,47 +209,45 @@ var Redmon = (function() {
|
|
206
209
|
//////////////////////////////////////////////////////////////////////
|
207
210
|
// encapsulate the keyspace chart
|
208
211
|
var keyspaceWidget = (function() {
|
209
|
-
var
|
212
|
+
var chart
|
210
213
|
, dataset = {
|
211
|
-
hits:
|
212
|
-
misses: {
|
214
|
+
hits: {key: 'Hits' , values: []},
|
215
|
+
misses: {key: 'Misses' , values: []},
|
213
216
|
load: function(data) {
|
214
217
|
var self = this;
|
215
218
|
points(data).forEach(function(point) {self.push(point)});
|
216
|
-
return self
|
219
|
+
return self;
|
217
220
|
},
|
218
221
|
append: function(data) {
|
219
|
-
if (this.hits.length >= 100) this.shift();
|
222
|
+
if (this.hits.values.length >= 100) this.shift();
|
220
223
|
this.push(point(data));
|
221
|
-
return this;
|
222
224
|
},
|
223
225
|
push: function(point) {
|
224
|
-
this.hits.
|
225
|
-
this.misses.
|
226
|
+
this.hits.values.push(point[0]);
|
227
|
+
this.misses.values.push(point[1]);
|
226
228
|
},
|
227
229
|
shift: function() {
|
228
|
-
this.hits.
|
229
|
-
this.misses.
|
230
|
-
},
|
231
|
-
series: function() {
|
232
|
-
return [this.hits, this.misses]
|
230
|
+
this.hits.values.shift();
|
231
|
+
this.misses.values.shift();
|
233
232
|
}
|
234
233
|
};
|
235
234
|
|
236
235
|
function render(data) {
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
236
|
+
dataset.load(data);
|
237
|
+
chart = nv.models.lineChart()
|
238
|
+
.x(function(d) { return d.x })
|
239
|
+
.y(function(d) { return d.y })
|
240
|
+
.margin({top : 10, right : 20, bottom : 20, left : 60});
|
241
|
+
|
242
|
+
chart.xAxis
|
243
|
+
.tickFormat(formatTime);
|
244
|
+
|
245
|
+
chart.yAxis
|
246
|
+
.tickFormat(formatMetric);
|
247
|
+
|
248
|
+
update();
|
249
|
+
|
250
|
+
events.bind('data', onData);
|
250
251
|
}
|
251
252
|
|
252
253
|
function points(data) {
|
@@ -256,21 +257,27 @@ var Redmon = (function() {
|
|
256
257
|
function point(info) {
|
257
258
|
var time = parseInt(info.time);
|
258
259
|
return [
|
259
|
-
|
260
|
-
|
260
|
+
{x: time, y: parseInt(info.keyspace_hits)},
|
261
|
+
{x: time, y: parseInt(info.keyspace_misses)}
|
261
262
|
];
|
262
263
|
}
|
263
264
|
|
264
265
|
function onData(ev, data) {
|
265
|
-
|
266
|
-
|
267
|
-
plot.setupGrid();
|
268
|
-
plot.draw();
|
269
|
-
}
|
266
|
+
dataset.append(data);
|
267
|
+
update();
|
270
268
|
}
|
271
269
|
|
272
|
-
|
273
|
-
|
270
|
+
function update() {
|
271
|
+
var data = [
|
272
|
+
{key : dataset.hits.key, values : dataset.hits.values, color : '#0000FF'},
|
273
|
+
{key : dataset.misses.key, values : dataset.misses.values, color : '#FF0000'}
|
274
|
+
];
|
275
|
+
|
276
|
+
d3.select('#keyspace-chart svg')
|
277
|
+
.datum(data)
|
278
|
+
.transition()
|
279
|
+
.ease("linear").call(chart);
|
280
|
+
}
|
274
281
|
|
275
282
|
return {
|
276
283
|
render: render
|
@@ -281,7 +288,8 @@ var Redmon = (function() {
|
|
281
288
|
// encapsulate the info widget
|
282
289
|
var infoWidget = (function() {
|
283
290
|
function render(data) {
|
284
|
-
|
291
|
+
if (data && data.length)
|
292
|
+
updateTable(data[data.length-1]);
|
285
293
|
}
|
286
294
|
|
287
295
|
function onData(ev, data) {
|