memdash 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +3 -0
- data/LICENSE +22 -0
- data/README.md +58 -0
- data/Rakefile +22 -0
- data/config.ru +7 -0
- data/lib/generators/memdash/active_record_generator.rb +17 -0
- data/lib/generators/memdash/templates/migration.rb +12 -0
- data/lib/memdash.rb +9 -0
- data/lib/memdash/active_record.rb +2 -0
- data/lib/memdash/active_record/client.rb +13 -0
- data/lib/memdash/active_record/report.rb +13 -0
- data/lib/memdash/client.rb +26 -0
- data/lib/memdash/configuration.rb +11 -0
- data/lib/memdash/server.rb +50 -0
- data/lib/memdash/server/public/charts.js +136 -0
- data/lib/memdash/server/public/cross_scratches.png +0 -0
- data/lib/memdash/server/public/jqplot.canvasAxisTickRenderer.js +243 -0
- data/lib/memdash/server/public/jqplot.canvasAxisTickRenderer.min.js +57 -0
- data/lib/memdash/server/public/jqplot.canvasTextRenderer.min.js +57 -0
- data/lib/memdash/server/public/jqplot.dateAxisRenderer.min.js +57 -0
- data/lib/memdash/server/public/jquery.jqplot.min.css +1 -0
- data/lib/memdash/server/public/jquery.jqplot.min.js +57 -0
- data/lib/memdash/server/public/reset.css +260 -0
- data/lib/memdash/server/public/style.css +66 -0
- data/lib/memdash/server/views/application.erb +24 -0
- data/lib/memdash/server/views/overview.erb +19 -0
- data/memdash.gemspec +25 -0
- data/screenshot.png +0 -0
- data/spec/memdash_spec.rb +31 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/support/adapters/active_record.rb +11 -0
- data/spec/support/dalli.rb +15 -0
- data/spec/support/database_cleaner.rb +16 -0
- data/spec/support/memcached.rb +6 -0
- data/spec/support/schema.rb +12 -0
- metadata +204 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Brian Ryckbost
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
# Memdash [![Build Status](https://secure.travis-ci.org/bryckbost/memdash.png)](http://travis-ci.org/bryckbost/memdash) [![Dependency Status](https://gemnasium.com/bryckbost/memdash.png)](https://gemnasium.com/bryckbost/memdash)
|
2
|
+
|
3
|
+
A dashboard for your memcache. **This is a work in progress,** but aims to provide a little insight into your application's memcached servers.
|
4
|
+
|
5
|
+
![Screenshot of memdash](https://github.com/bryckbost/memdash/raw/front-end/screenshot.png)
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 'memdash'
|
12
|
+
|
13
|
+
And then from the console, run:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install memdash
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
To begin using Memdash, run the following:
|
24
|
+
|
25
|
+
$ rails g memdash:active_record
|
26
|
+
$ rake db:migrate
|
27
|
+
|
28
|
+
The generator will create a table (`memdash_reports`) to store a serialized column of statistics from your cache servers.
|
29
|
+
|
30
|
+
From this point onward, any calls to the cache should generate statistics and shove them into that table. But don't worry, Memdash won't actually write to the database on every call. Instead, it caches the stats within memcache for a minute and writes when it needs to.
|
31
|
+
|
32
|
+
To view the dashboard, you'll need to add `require 'memdash/server'` to `config/routes.rb` and mount a server at an endpoint. It might look something like this:
|
33
|
+
|
34
|
+
require 'memdash/server'
|
35
|
+
|
36
|
+
MyRailsApp::Application.routes.draw do
|
37
|
+
…other routes…
|
38
|
+
|
39
|
+
mount Memdash::Server.new, :at => "/memdash"
|
40
|
+
end
|
41
|
+
|
42
|
+
## Why Did I Build This?
|
43
|
+
|
44
|
+
Memdash is meant to give you insight into your memcached setup without adding overhead to your application. I found it useful when deploying apps to Heroku where the memcached add-on is a bit of a black box. Stuff goes in, stuff comes out. Hopefully, it's being used effectively.
|
45
|
+
|
46
|
+
## How Does it Work???
|
47
|
+
|
48
|
+
Building on top of [Dalli](https://github.com/mperham/dalli), Memdash hooks into Dalli's chokepoint method to generate statistics.
|
49
|
+
|
50
|
+
When a call to the cache is triggered, `memdash` performs that operation and does a `get` for the key `memdash`. If the returned value is not found, Memdash then writes the cache statistics to the database and `add`s the `memdash` key with a default time to live of 60 seconds.
|
51
|
+
|
52
|
+
## Contributing
|
53
|
+
|
54
|
+
1. Fork it
|
55
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
56
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
57
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
58
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
|
6
|
+
ADAPTERS = %w(active_record)
|
7
|
+
|
8
|
+
task :default => :test
|
9
|
+
|
10
|
+
desc 'Run tests'
|
11
|
+
task :test => ADAPTERS.map{|a| "#{a}:test" }
|
12
|
+
|
13
|
+
ADAPTERS.each do |adapter|
|
14
|
+
namespace adapter do
|
15
|
+
desc "Run tests against #{adapter}"
|
16
|
+
RSpec::Core::RakeTask.new(:test => :env)
|
17
|
+
|
18
|
+
task :env do
|
19
|
+
ENV['ADAPTER'] = adapter
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/config.ru
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'rails/generators/migration'
|
2
|
+
require 'rails/generators/active_record/migration'
|
3
|
+
|
4
|
+
module Memdash
|
5
|
+
module Generators
|
6
|
+
class ActiveRecordGenerator < Rails::Generators::Base
|
7
|
+
include Rails::Generators::Migration
|
8
|
+
extend ActiveRecord::Generators::Migration
|
9
|
+
|
10
|
+
self.source_paths << File.join(File.dirname(__FILE__), 'templates')
|
11
|
+
|
12
|
+
def create_migration_file
|
13
|
+
migration_template 'migration.rb', 'db/migrate/create_memdash_active_record_reports.rb'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/memdash.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'memdash/active_record/report'
|
2
|
+
|
3
|
+
module Memdash
|
4
|
+
module ActiveRecord
|
5
|
+
module Client
|
6
|
+
def generate_stats
|
7
|
+
Memdash::ActiveRecord::Report.create!(:stats => stats)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
Dalli::Client.send(:include, Memdash::ActiveRecord::Client)
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
module Memdash
|
4
|
+
module ActiveRecord
|
5
|
+
class Report < ::ActiveRecord::Base
|
6
|
+
self.table_name = :memdash_reports
|
7
|
+
|
8
|
+
serialize :stats
|
9
|
+
|
10
|
+
scope :past_day, where('created_at >= :time', :time => Time.current - 1.day)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Memdash
|
2
|
+
module Client
|
3
|
+
def self.included(base)
|
4
|
+
base.class_eval do
|
5
|
+
alias_method :perform_without_stats, :perform
|
6
|
+
alias_method :perform, :perform_with_stats
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def perform_with_stats(op, key, *args)
|
11
|
+
ret = perform_without_stats(op, key, *args)
|
12
|
+
resp = perform_without_stats(:get, 'memdash')
|
13
|
+
if resp.nil? || resp == 'Not found'
|
14
|
+
generate_stats
|
15
|
+
perform_without_stats(:add, 'memdash', true, Memdash.ttl, nil)
|
16
|
+
end
|
17
|
+
ret
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def generate_stats
|
23
|
+
raise NotImplementedError
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'erb'
|
3
|
+
require 'memdash/active_record'
|
4
|
+
|
5
|
+
module Memdash
|
6
|
+
class Server < Sinatra::Base
|
7
|
+
dir = File.dirname(File.expand_path(__FILE__))
|
8
|
+
set :views, "#{dir}/server/views"
|
9
|
+
set :public_folder, "#{dir}/server/public"
|
10
|
+
|
11
|
+
helpers do
|
12
|
+
include Rack::Utils
|
13
|
+
alias_method :h, :escape_html
|
14
|
+
|
15
|
+
def url_path(*path_parts)
|
16
|
+
[path_prefix, path_parts].join("/").squeeze('/')
|
17
|
+
end
|
18
|
+
alias_method :u, :url_path
|
19
|
+
|
20
|
+
def path_prefix
|
21
|
+
request.env['SCRIPT_NAME']
|
22
|
+
end
|
23
|
+
|
24
|
+
def hit_ratio(hits, misses)
|
25
|
+
hits / (hits + misses)
|
26
|
+
end
|
27
|
+
|
28
|
+
def miss_ratio(hits, misses)
|
29
|
+
1 - hit_ratio(hits, misses)
|
30
|
+
end
|
31
|
+
|
32
|
+
def miss_ratio_percentage(hits, misses)
|
33
|
+
miss_ratio(hits, misses) * 100
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
get "/" do
|
38
|
+
@last_report = Memdash::ActiveRecord::Report.last
|
39
|
+
past_day = Memdash::ActiveRecord::Report.past_day
|
40
|
+
@gets = past_day.flat_map{|report| report.stats.map{|_, v| [report.created_at.strftime("%m-%d-%Y %I:%M%p"), v["cmd_get"].to_i]}}
|
41
|
+
@sets = past_day.flat_map{|report| report.stats.map{|_, v| [report.created_at.strftime("%m-%d-%Y %I:%M%p"), v["cmd_set"].to_i]}}
|
42
|
+
@hits = past_day.flat_map{|report| report.stats.map{|_, v| [report.created_at.strftime("%m-%d-%Y %I:%M%p"), v["get_hits"].to_i]}}
|
43
|
+
@misses = past_day.flat_map{|report| report.stats.map{|_, v| [report.created_at.strftime("%m-%d-%Y %I:%M%p"), v["get_misses"].to_i]}}
|
44
|
+
@limit_maxbytes = past_day.flat_map{|report| report.stats.map{|_, v| [report.created_at.strftime("%m-%d-%Y %I:%M%p"), (v["limit_maxbytes"].to_i / 1024.0 / 1024.0).round(2)]}}
|
45
|
+
@bytes = past_day.flat_map{|report| report.stats.map{|_, v| [report.created_at.strftime("%m-%d-%Y %I:%M%p"), (v["bytes"].to_i / 1024.0 / 1024.0).round(2)]}}
|
46
|
+
@current_items = past_day.flat_map{|report| report.stats.map{|_, v| [report.created_at.strftime("%m-%d-%Y %I:%M%p"), v["curr_items"].to_i]}}
|
47
|
+
erb :overview, :layout => :application
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
$(document).ready(function(){
|
2
|
+
$.jqplot('gets-sets', [$("#gets-sets").data("gets"), $("#gets-sets").data("sets")], {
|
3
|
+
title: 'Gets & Sets for the Past Day',
|
4
|
+
grid: {
|
5
|
+
drawBorder: false,
|
6
|
+
shadow: false,
|
7
|
+
background: '#fefefe'
|
8
|
+
},
|
9
|
+
axes:{
|
10
|
+
xaxis:{
|
11
|
+
renderer: $.jqplot.DateAxisRenderer,
|
12
|
+
tickOptions: {
|
13
|
+
formatString:'%#I %p'
|
14
|
+
},
|
15
|
+
min: $("#gets-sets").data("gets")[0][0],
|
16
|
+
max: $("#gets-sets").data("gets")[$("#gets-sets").data("gets").length - 1][0],
|
17
|
+
tickInterval: "2 hours",
|
18
|
+
}
|
19
|
+
},
|
20
|
+
seriesDefaults: {
|
21
|
+
rendererOptions: {
|
22
|
+
smooth: true
|
23
|
+
}
|
24
|
+
},
|
25
|
+
series: [
|
26
|
+
{label: 'Gets', showMarker: false},
|
27
|
+
{label: 'Sets', showMarker: false},
|
28
|
+
],
|
29
|
+
legend: {
|
30
|
+
show: true
|
31
|
+
},
|
32
|
+
seriesColors: ["rgb(36, 173, 227)", "rgb(227, 36, 132)"]
|
33
|
+
});
|
34
|
+
$.jqplot('hits-misses', [$("#hits-misses").data("hits"), $("#hits-misses").data("misses")], {
|
35
|
+
title: 'Hits & Misses for the Past Day',
|
36
|
+
grid: {
|
37
|
+
drawBorder: false,
|
38
|
+
shadow: false,
|
39
|
+
background: '#fefefe'
|
40
|
+
},
|
41
|
+
axes:{
|
42
|
+
xaxis:{
|
43
|
+
renderer: $.jqplot.DateAxisRenderer,
|
44
|
+
tickOptions: {
|
45
|
+
formatString:'%#I %p'
|
46
|
+
},
|
47
|
+
min: $("#hits-misses").data("hits")[0][0],
|
48
|
+
max: $("#hits-misses").data("hits")[$("#hits-misses").data("hits").length - 1][0],
|
49
|
+
tickInterval: "2 hours",
|
50
|
+
},
|
51
|
+
yaxis: {
|
52
|
+
rendererOptions: { forceTickAt0: true, forceTickAt100: true }
|
53
|
+
}
|
54
|
+
|
55
|
+
},
|
56
|
+
seriesDefaults: {
|
57
|
+
rendererOptions: {
|
58
|
+
smooth: true
|
59
|
+
}
|
60
|
+
},
|
61
|
+
series: [
|
62
|
+
{label: 'Hits', showMarker: false},
|
63
|
+
{label: 'Misses', showMarker: false}
|
64
|
+
],
|
65
|
+
legend: {
|
66
|
+
show: true
|
67
|
+
},
|
68
|
+
seriesColors: ["rgb(227, 36, 132)", "rgb(227, 193, 36)"]
|
69
|
+
});
|
70
|
+
$.jqplot('memory-usage', [$("#memory-usage").data("limit-maxbytes"), $("#memory-usage").data("bytes")], {
|
71
|
+
title: 'Cache Memory for the Past Day',
|
72
|
+
grid: {
|
73
|
+
drawBorder: false,
|
74
|
+
shadow: false,
|
75
|
+
background: '#fefefe'
|
76
|
+
},
|
77
|
+
axes:{
|
78
|
+
xaxis:{
|
79
|
+
renderer: $.jqplot.DateAxisRenderer,
|
80
|
+
tickOptions: {
|
81
|
+
formatString:'%#I %p'
|
82
|
+
},
|
83
|
+
min: $("#memory-usage").data("limit-maxbytes")[0][0],
|
84
|
+
max: $("#memory-usage").data("limit-maxbytes")[$("#memory-usage").data("limit-maxbytes").length - 1][0],
|
85
|
+
tickInterval: "2 hours",
|
86
|
+
}
|
87
|
+
},
|
88
|
+
seriesDefaults: {
|
89
|
+
rendererOptions: {
|
90
|
+
smooth: true
|
91
|
+
}
|
92
|
+
},
|
93
|
+
series: [
|
94
|
+
{label: 'Max Size (MB)', showMarker: false},
|
95
|
+
{label: 'Used (MB)', showMarker: false}
|
96
|
+
],
|
97
|
+
legend: {
|
98
|
+
show: true
|
99
|
+
},
|
100
|
+
seriesColors: ["rgb(227, 193, 36)", "rgb(36, 173, 227)"]
|
101
|
+
});
|
102
|
+
$.jqplot('current-items', [$("#current-items").data("current-items")], {
|
103
|
+
title: 'Items over the Past Day',
|
104
|
+
grid: {
|
105
|
+
drawBorder: false,
|
106
|
+
shadow: false,
|
107
|
+
background: '#fefefe'
|
108
|
+
},
|
109
|
+
axes:{
|
110
|
+
xaxis:{
|
111
|
+
renderer: $.jqplot.DateAxisRenderer,
|
112
|
+
tickOptions: {
|
113
|
+
formatString:'%#I %p'
|
114
|
+
},
|
115
|
+
min: $("#current-items").data("current-items")[0][0],
|
116
|
+
max: $("#current-items").data("current-items")[$("#current-items").data("current-items").length - 1][0],
|
117
|
+
tickInterval: "2 hours",
|
118
|
+
},
|
119
|
+
yaxis: {
|
120
|
+
rendererOptions: { forceTickAt0: true, forceTickAt100: true }
|
121
|
+
}
|
122
|
+
},
|
123
|
+
seriesDefaults: {
|
124
|
+
rendererOptions: {
|
125
|
+
smooth: true,
|
126
|
+
}
|
127
|
+
},
|
128
|
+
series: [
|
129
|
+
{label: 'Items', showMarker: false},
|
130
|
+
],
|
131
|
+
legend: {
|
132
|
+
show: true
|
133
|
+
},
|
134
|
+
seriesColors: ["rgb(227, 193, 36)"]
|
135
|
+
});
|
136
|
+
});
|