memdash 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/.gitignore +18 -0
  2. data/.rspec +2 -0
  3. data/.travis.yml +5 -0
  4. data/Gemfile +3 -0
  5. data/LICENSE +22 -0
  6. data/README.md +58 -0
  7. data/Rakefile +22 -0
  8. data/config.ru +7 -0
  9. data/lib/generators/memdash/active_record_generator.rb +17 -0
  10. data/lib/generators/memdash/templates/migration.rb +12 -0
  11. data/lib/memdash.rb +9 -0
  12. data/lib/memdash/active_record.rb +2 -0
  13. data/lib/memdash/active_record/client.rb +13 -0
  14. data/lib/memdash/active_record/report.rb +13 -0
  15. data/lib/memdash/client.rb +26 -0
  16. data/lib/memdash/configuration.rb +11 -0
  17. data/lib/memdash/server.rb +50 -0
  18. data/lib/memdash/server/public/charts.js +136 -0
  19. data/lib/memdash/server/public/cross_scratches.png +0 -0
  20. data/lib/memdash/server/public/jqplot.canvasAxisTickRenderer.js +243 -0
  21. data/lib/memdash/server/public/jqplot.canvasAxisTickRenderer.min.js +57 -0
  22. data/lib/memdash/server/public/jqplot.canvasTextRenderer.min.js +57 -0
  23. data/lib/memdash/server/public/jqplot.dateAxisRenderer.min.js +57 -0
  24. data/lib/memdash/server/public/jquery.jqplot.min.css +1 -0
  25. data/lib/memdash/server/public/jquery.jqplot.min.js +57 -0
  26. data/lib/memdash/server/public/reset.css +260 -0
  27. data/lib/memdash/server/public/style.css +66 -0
  28. data/lib/memdash/server/views/application.erb +24 -0
  29. data/lib/memdash/server/views/overview.erb +19 -0
  30. data/memdash.gemspec +25 -0
  31. data/screenshot.png +0 -0
  32. data/spec/memdash_spec.rb +31 -0
  33. data/spec/spec_helper.rb +3 -0
  34. data/spec/support/adapters/active_record.rb +11 -0
  35. data/spec/support/dalli.rb +15 -0
  36. data/spec/support/database_cleaner.rb +16 -0
  37. data/spec/support/memcached.rb +6 -0
  38. data/spec/support/schema.rb +12 -0
  39. metadata +204 -0
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .rvmrc
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
@@ -0,0 +1,5 @@
1
+ rvm:
2
+ - 1.8.7
3
+ - 1.9.2
4
+ - 1.9.3
5
+ - ruby-head
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
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.
@@ -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
@@ -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
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ require 'logger'
3
+
4
+ require 'memdash/server'
5
+
6
+ use Rack::ShowExceptions
7
+ run Memdash::Server.new
@@ -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
@@ -0,0 +1,12 @@
1
+ class CreateMemdashActiveRecordReports < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :memdash_reports do |table|
4
+ table.text :stats
5
+ table.datetime :created_at
6
+ end
7
+ end
8
+
9
+ def self.down
10
+ drop_table :memdash_reports
11
+ end
12
+ end
@@ -0,0 +1,9 @@
1
+ require 'memdash/client'
2
+ require 'memdash/configuration'
3
+ require 'dalli'
4
+
5
+ module Memdash
6
+ extend Memdash::Configuration
7
+ end
8
+
9
+ Dalli::Client.send(:include, Memdash::Client)
@@ -0,0 +1,2 @@
1
+ require 'memdash'
2
+ require 'memdash/active_record/client'
@@ -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,11 @@
1
+ module Memdash
2
+ module Configuration
3
+ DEFAULT_TTL = 60
4
+
5
+ attr_writer :ttl
6
+
7
+ def ttl
8
+ @ttl ||= DEFAULT_TTL
9
+ end
10
+ end
11
+ 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
+ });