redmon 0.0.1
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/.gitignore +8 -0
- data/.rspec +2 -0
- data/.travis.yml +1 -0
- data/Gemfile +3 -0
- data/README.md +71 -0
- data/Rakefile +11 -0
- data/bin/redmon +71 -0
- data/lib/redmon.rb +67 -0
- data/lib/redmon/app.rb +44 -0
- data/lib/redmon/helpers.rb +18 -0
- data/lib/redmon/public/redmon.css +114 -0
- data/lib/redmon/public/redmon.js +556 -0
- data/lib/redmon/public/vendor/bootstrap-modal.js +260 -0
- data/lib/redmon/public/vendor/bootstrap.min.css +356 -0
- data/lib/redmon/public/vendor/jquery-1.7.1.min.js +4 -0
- data/lib/redmon/public/vendor/jquery-effects-core.min.js +20 -0
- data/lib/redmon/public/vendor/jquery.editinplace.js +646 -0
- data/lib/redmon/public/vendor/jquery.flot.js +2651 -0
- data/lib/redmon/public/vendor/terminal.js +135 -0
- data/lib/redmon/redis.rb +63 -0
- data/lib/redmon/version.rb +3 -0
- data/lib/redmon/views/app.haml +178 -0
- data/lib/redmon/views/cli.haml +15 -0
- data/lib/redmon/worker.rb +41 -0
- data/load_sim.rb +20 -0
- data/redmon.gemspec +38 -0
- data/spec/app_spec.rb +95 -0
- data/spec/helpers_spec.rb +86 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/worker_spec.rb +62 -0
- metadata +262 -0
@@ -0,0 +1,135 @@
|
|
1
|
+
// TryMongo
|
2
|
+
//
|
3
|
+
// Copyright (c) 2009 Kyle Banker
|
4
|
+
// Licensed under the MIT Licence.
|
5
|
+
// http://www.opensource.org/licenses/mit-license.php
|
6
|
+
|
7
|
+
// Readline class to handle line input.
|
8
|
+
var ReadLine = function(options) {
|
9
|
+
this.options = options || {};
|
10
|
+
this.htmlForInput = this.options.htmlForInput;
|
11
|
+
this.inputHandler = this.options.handler;
|
12
|
+
this.terminal = $(this.options.terminalId || "#terminal");
|
13
|
+
this.lineClass = this.options.lineClass || '.readLine';
|
14
|
+
this.history = [];
|
15
|
+
this.historyPtr = 0;
|
16
|
+
|
17
|
+
this.initialize();
|
18
|
+
};
|
19
|
+
|
20
|
+
ReadLine.prototype = {
|
21
|
+
|
22
|
+
initialize: function() {
|
23
|
+
this.addInputLine();
|
24
|
+
},
|
25
|
+
|
26
|
+
// Enter a new input line with proper behavior.
|
27
|
+
addInputLine: function(stackLevel) {
|
28
|
+
stackLevel = stackLevel || 0;
|
29
|
+
this.terminal.append(this.htmlForInput(stackLevel));
|
30
|
+
var ctx = this;
|
31
|
+
ctx.activeLine = $(this.lineClass + '.active');
|
32
|
+
|
33
|
+
// Bind key events for entering and navigting history.
|
34
|
+
ctx.activeLine.bind("keydown", function(ev) {
|
35
|
+
switch (ev.keyCode) {
|
36
|
+
case EnterKeyCode:
|
37
|
+
ctx.processInput(this.value);
|
38
|
+
break;
|
39
|
+
case UpArrowKeyCode:
|
40
|
+
ctx.getCommand('previous');
|
41
|
+
break;
|
42
|
+
case DownArrowKeyCode:
|
43
|
+
ctx.getCommand('next');
|
44
|
+
break;
|
45
|
+
}
|
46
|
+
});
|
47
|
+
|
48
|
+
this.activeLine.focus();
|
49
|
+
},
|
50
|
+
|
51
|
+
// Returns the 'next' or 'previous' command in this history.
|
52
|
+
getCommand: function(direction) {
|
53
|
+
if(this.history.length === 0) {
|
54
|
+
return;
|
55
|
+
}
|
56
|
+
this.adjustHistoryPointer(direction);
|
57
|
+
this.activeLine[0].value = this.history[this.historyPtr];
|
58
|
+
$(this.activeLine[0]).focus();
|
59
|
+
//this.activeLine[0].value = this.activeLine[0].value;
|
60
|
+
},
|
61
|
+
|
62
|
+
// Moves the history pointer to the 'next' or 'previous' position.
|
63
|
+
adjustHistoryPointer: function(direction) {
|
64
|
+
if(direction == 'previous') {
|
65
|
+
if(this.historyPtr - 1 >= 0) {
|
66
|
+
this.historyPtr -= 1;
|
67
|
+
}
|
68
|
+
}
|
69
|
+
else {
|
70
|
+
if(this.historyPtr + 1 < this.history.length) {
|
71
|
+
this.historyPtr += 1;
|
72
|
+
}
|
73
|
+
}
|
74
|
+
},
|
75
|
+
|
76
|
+
// Return the handler's response.
|
77
|
+
processInput: function(value) {
|
78
|
+
value = value.trim();
|
79
|
+
if (!value) {
|
80
|
+
// deactivate the line...
|
81
|
+
this.activeLine.value = "";
|
82
|
+
this.activeLine.attr({disabled: true});
|
83
|
+
this.activeLine.removeClass('active');
|
84
|
+
|
85
|
+
// and add add a new command line.
|
86
|
+
this.addInputLine();
|
87
|
+
return
|
88
|
+
|
89
|
+
} else if (value == 'clear') {
|
90
|
+
this.clear();
|
91
|
+
return
|
92
|
+
}
|
93
|
+
|
94
|
+
var me = this;
|
95
|
+
me.inputHandler(value, function(response) {
|
96
|
+
me.insertResponse(response);
|
97
|
+
|
98
|
+
// Save to the command history...
|
99
|
+
if((lineValue = value.trim()) !== "") {
|
100
|
+
me.history.push(lineValue);
|
101
|
+
me.historyPtr = me.history.length;
|
102
|
+
}
|
103
|
+
|
104
|
+
// deactivate the line...
|
105
|
+
me.activeLine.value = "";
|
106
|
+
me.activeLine.attr({disabled: true});
|
107
|
+
me.activeLine.removeClass('active');
|
108
|
+
|
109
|
+
// and add add a new command line.
|
110
|
+
me.addInputLine();
|
111
|
+
});
|
112
|
+
},
|
113
|
+
|
114
|
+
insertResponse: function(response) {
|
115
|
+
if(response.length < 3) {
|
116
|
+
this.activeLine.parent().append("<p class='response'></p>");
|
117
|
+
}
|
118
|
+
else {
|
119
|
+
this.activeLine.parent().append("<p class='response'>" + response + "</p>");
|
120
|
+
}
|
121
|
+
},
|
122
|
+
|
123
|
+
clear: function() {
|
124
|
+
this.terminal.empty();
|
125
|
+
this.addInputLine();
|
126
|
+
},
|
127
|
+
|
128
|
+
focus: function() {
|
129
|
+
this.activeLine.focus();
|
130
|
+
}
|
131
|
+
};
|
132
|
+
|
133
|
+
var EnterKeyCode = 13;
|
134
|
+
var UpArrowKeyCode = 38;
|
135
|
+
var DownArrowKeyCode = 40;
|
data/lib/redmon/redis.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
module Redmon
|
2
|
+
module Redis
|
3
|
+
extend self
|
4
|
+
|
5
|
+
UNSUPPORTED = [
|
6
|
+
:eval,
|
7
|
+
:psubscribe,
|
8
|
+
:punsubscribe,
|
9
|
+
:subscribe,
|
10
|
+
:unsubscribe,
|
11
|
+
:unwatch,
|
12
|
+
:watch
|
13
|
+
]
|
14
|
+
|
15
|
+
def redis
|
16
|
+
@redis ||= ::Redis.connect(:url => Redmon[:redis_url])
|
17
|
+
end
|
18
|
+
|
19
|
+
def ns
|
20
|
+
Redmon[:namespace]
|
21
|
+
end
|
22
|
+
|
23
|
+
def redis_url
|
24
|
+
@redis_url ||= Redmon[:redis_url].gsub(/\w*:\w*@/, '')
|
25
|
+
end
|
26
|
+
|
27
|
+
def redis_host
|
28
|
+
redis_url.gsub('redis://', '')
|
29
|
+
end
|
30
|
+
|
31
|
+
def config
|
32
|
+
redis.config :get, '*' rescue {}
|
33
|
+
end
|
34
|
+
|
35
|
+
def unquoted
|
36
|
+
%w{string OK} << '(empty list or set)'
|
37
|
+
end
|
38
|
+
|
39
|
+
def supported?(cmd)
|
40
|
+
!UNSUPPORTED.include? cmd
|
41
|
+
end
|
42
|
+
|
43
|
+
def empty_result
|
44
|
+
'(empty list or set)'
|
45
|
+
end
|
46
|
+
|
47
|
+
def unknown(cmd)
|
48
|
+
"(error) ERR unknown command '#{cmd}'"
|
49
|
+
end
|
50
|
+
|
51
|
+
def wrong_number_of_arguments_for(cmd)
|
52
|
+
"(error) ERR wrong number of arguments for '#{cmd}' command"
|
53
|
+
end
|
54
|
+
|
55
|
+
def connection_refused
|
56
|
+
"Could not connect to Redis at #{redis_url.gsub(/\w*:\/\//, '')}: Connection refused"
|
57
|
+
end
|
58
|
+
|
59
|
+
def stats_key
|
60
|
+
"#{ns}:redis:#{redis_host}:stats"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
!!!
|
2
|
+
%html
|
3
|
+
%head
|
4
|
+
%title Redmon
|
5
|
+
|
6
|
+
%script(type="text/javascript" src="vendor/jquery-1.7.1.min.js")
|
7
|
+
%script(type="text/javascript" src="vendor/jquery-effects-core.min.js")
|
8
|
+
%script(type="text/javascript" src="vendor/jquery.editinplace.js")
|
9
|
+
%script(type="text/javascript" src="vendor/jquery.flot.js")
|
10
|
+
%script(type="text/javascript" src="vendor/bootstrap-modal.js")
|
11
|
+
%script(type="text/javascript" src="vendor/terminal.js")
|
12
|
+
%script(type="text/javascript" src="redmon.js")
|
13
|
+
|
14
|
+
%link(type="text/css" rel="stylesheet" href="vendor/bootstrap.min.css")
|
15
|
+
%link(type="text/css" rel="stylesheet" href="redmon.css")
|
16
|
+
|
17
|
+
%body
|
18
|
+
.topbar
|
19
|
+
.fill
|
20
|
+
.container
|
21
|
+
%a.brand Redmon
|
22
|
+
%ul.nav
|
23
|
+
%li.active{:id => 'dashboard'}
|
24
|
+
%a Dashboard
|
25
|
+
%li{:id => 'keys'}
|
26
|
+
%a Keys
|
27
|
+
%li{:id => 'cli'}
|
28
|
+
%a CLI
|
29
|
+
%li{:id => 'config'}
|
30
|
+
%a Configuration
|
31
|
+
.pull-right
|
32
|
+
%a.brand #{redis_url}
|
33
|
+
.btns
|
34
|
+
%button.btn.primary{:id => 'flush-btn'} Flush DB
|
35
|
+
%button.btn.primary{:id => 'reset-btn'} Reset Statistics
|
36
|
+
|
37
|
+
.container.viewport
|
38
|
+
.dashboard
|
39
|
+
.content
|
40
|
+
.page-header
|
41
|
+
%h1 Dashboard
|
42
|
+
.row
|
43
|
+
.span15
|
44
|
+
.row
|
45
|
+
.span15
|
46
|
+
.widget
|
47
|
+
.headbar
|
48
|
+
%h2 Memory Usage
|
49
|
+
#memory-container.chart
|
50
|
+
.row
|
51
|
+
.span15
|
52
|
+
.widget
|
53
|
+
.headbar
|
54
|
+
%h2 Keyspace
|
55
|
+
#keyspace-container.chart
|
56
|
+
.row
|
57
|
+
.span15
|
58
|
+
.widget
|
59
|
+
.headbar
|
60
|
+
%h2 Slow Log
|
61
|
+
%div{:style => "height:225px;overflow-y:scroll;"}
|
62
|
+
%table.condensed-table{:id => "slow-tbl"}
|
63
|
+
%tbody
|
64
|
+
|
65
|
+
.span6
|
66
|
+
.widget
|
67
|
+
.headbar
|
68
|
+
%h2 Info
|
69
|
+
%table.condensed-table{:id => "info-tbl"}
|
70
|
+
%tbody
|
71
|
+
%tr
|
72
|
+
%td{:style => "width: 65%"} Version
|
73
|
+
%td{:id => "redis_version"}
|
74
|
+
%tr
|
75
|
+
%td Role
|
76
|
+
%td{:id => "role"}
|
77
|
+
%tr
|
78
|
+
%td Uptime Days
|
79
|
+
%td{:id => "uptime_in_days"}
|
80
|
+
%tr
|
81
|
+
%td Memory Used
|
82
|
+
%td{:id => "used_memory_human"}
|
83
|
+
%tr
|
84
|
+
%td Memory Peak
|
85
|
+
%td{:id => "used_memory_peak_human"}
|
86
|
+
%tr
|
87
|
+
%td Memory Fragmentation Ratio
|
88
|
+
%td{:id => "mem_fragmentation_ratio"}
|
89
|
+
%tr
|
90
|
+
%td DB Size (Keys)
|
91
|
+
%td{:id => "dbsize", :type => 'number'}
|
92
|
+
%tr
|
93
|
+
%td Keyspace Hits
|
94
|
+
%td{:id => "keyspace_hits", :type => 'number'}
|
95
|
+
%tr
|
96
|
+
%td Keyspace Misses
|
97
|
+
%td{:id => "keyspace_misses", :type => 'number'}
|
98
|
+
%tr
|
99
|
+
%td Pub/Sub Channels
|
100
|
+
%td{:id => "pubsub_channels", :type => 'number'}
|
101
|
+
%tr
|
102
|
+
%td Pub/Sub Patterns
|
103
|
+
%td{:id => "pubsub_patterns", :type => 'number'}
|
104
|
+
%tr
|
105
|
+
%td Total Connections Received
|
106
|
+
%td{:id => "total_connections_received", :type => 'number'}
|
107
|
+
%tr
|
108
|
+
%td Total Commands Processed
|
109
|
+
%td{:id => "total_commands_processed", :type => 'number'}
|
110
|
+
%tr
|
111
|
+
%td Blocked Clients
|
112
|
+
%td{:id => "blocked_clients", :type => 'number'}
|
113
|
+
%tr
|
114
|
+
%td Connected Slaves
|
115
|
+
%td{:id => "connected_slaves", :type => 'number'}
|
116
|
+
%tr
|
117
|
+
%td Last Save
|
118
|
+
%td{:id => "last_save_time", :type => 'date'}
|
119
|
+
%tr
|
120
|
+
%td Changes Since Last Save
|
121
|
+
%td{:id => "changes_since_last_save", :type => 'number'}
|
122
|
+
%tr
|
123
|
+
%td AOF Enabled
|
124
|
+
%td{:id => "aof_enabled"}
|
125
|
+
%tr
|
126
|
+
%td VM Enabled
|
127
|
+
%td{:id => "vm_enabled"}
|
128
|
+
.keys.hidden
|
129
|
+
.content
|
130
|
+
.page-header
|
131
|
+
%h1 Keys - Coming soon!
|
132
|
+
.row
|
133
|
+
.span6
|
134
|
+
.input-prepend
|
135
|
+
%span.add-on pattern
|
136
|
+
%input.span5
|
137
|
+
.span15
|
138
|
+
|
139
|
+
.cli.hidden
|
140
|
+
.content
|
141
|
+
.page-header
|
142
|
+
%h1 Command Line Interface
|
143
|
+
.row
|
144
|
+
.span21{:style => "height:620px"}
|
145
|
+
#terminal
|
146
|
+
|
147
|
+
.config.hidden
|
148
|
+
.content
|
149
|
+
.page-header
|
150
|
+
%h1 Configuration
|
151
|
+
.widget
|
152
|
+
.headbar
|
153
|
+
%h2 Parameters
|
154
|
+
%table.condensed-table{:id => "config-table"}
|
155
|
+
%tbody
|
156
|
+
- config.each do |k,v|
|
157
|
+
%tr
|
158
|
+
%td{:style => "width: 20%"}= k
|
159
|
+
%td
|
160
|
+
.editable{:id => "#{k}"}= v
|
161
|
+
|
162
|
+
#flush-confirm.modal.hide
|
163
|
+
.modal-header
|
164
|
+
%a.close x
|
165
|
+
%h3 Flush DB
|
166
|
+
.modal-body
|
167
|
+
%p This action will, without a doubt, delete all the keys of the redis server.
|
168
|
+
.modal-footer
|
169
|
+
%button.btn.secondary{:id => 'flush-cancel-btn'} Well On Second Thought
|
170
|
+
%button.btn.danger{:id => 'flush-confirm-btn'} Just Do It Already!
|
171
|
+
|
172
|
+
:javascript
|
173
|
+
$(document).ready(function() {
|
174
|
+
Redmon.init({
|
175
|
+
pollInterval : #{poll_interval},
|
176
|
+
cliPrompt : "#{prompt}"
|
177
|
+
});
|
178
|
+
});
|
@@ -0,0 +1,15 @@
|
|
1
|
+
- if @cmd == :info
|
2
|
+
- @result.each do |item|
|
3
|
+
%span= item.join ':'
|
4
|
+
%br
|
5
|
+
- elsif @result.respond_to? :each_with_index
|
6
|
+
- @result.each_with_index do |item, index|
|
7
|
+
%span= "#{index+1}) \"#{item}\""
|
8
|
+
%br
|
9
|
+
- elsif @result.is_a? Fixnum
|
10
|
+
%span= "(integer) #{@result}"
|
11
|
+
%br
|
12
|
+
- else
|
13
|
+
- @result = "\"#{@result}\"" unless unquoted.include? @result
|
14
|
+
%span= @result
|
15
|
+
%br
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Redmon
|
2
|
+
class Worker
|
3
|
+
include Redmon::Redis
|
4
|
+
|
5
|
+
def run!
|
6
|
+
EM::PeriodicTimer.new(interval) {record_stats}
|
7
|
+
end
|
8
|
+
|
9
|
+
def record_stats
|
10
|
+
redis.zadd stats_key, *stats
|
11
|
+
end
|
12
|
+
|
13
|
+
def stats
|
14
|
+
stats = redis.info.merge! \
|
15
|
+
:dbsize => redis.dbsize,
|
16
|
+
:time => ts = Time.now.to_i * 1000,
|
17
|
+
:slowlog => entries(redis.slowlog :get)
|
18
|
+
[ts, stats.to_json]
|
19
|
+
end
|
20
|
+
|
21
|
+
def entries(slowlog)
|
22
|
+
sort(slowlog).map do |entry|
|
23
|
+
{
|
24
|
+
:id => entry.shift,
|
25
|
+
:timestamp => entry.shift * 1000,
|
26
|
+
:process_time => entry.shift,
|
27
|
+
:command => entry.shift.join(' ')
|
28
|
+
}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def sort(slowlog)
|
33
|
+
slowlog.sort_by{|a| a[2]}.reverse!
|
34
|
+
end
|
35
|
+
|
36
|
+
def interval
|
37
|
+
Redmon[:poll_interval]
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
data/load_sim.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
$: << ::File.expand_path("../lib/", __FILE__)
|
2
|
+
require "rubygems"
|
3
|
+
require "bundler/setup"
|
4
|
+
require 'redmon'
|
5
|
+
require 'redis'
|
6
|
+
|
7
|
+
redis = Redis.connect
|
8
|
+
|
9
|
+
loop do
|
10
|
+
start = rand(100000)
|
11
|
+
multi = rand(5)
|
12
|
+
start.upto(multi * start) do |i|
|
13
|
+
redis.set("key-#{i}", "abcdedghijklmnopqrstuvwxyz")
|
14
|
+
end
|
15
|
+
|
16
|
+
start.upto(multi * start) do |i|
|
17
|
+
redis.get("key-#{i}")
|
18
|
+
redis.del("key-#{i}")
|
19
|
+
end
|
20
|
+
end
|