casseo 0.1.0
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/README.md +77 -0
- data/Rakefile +2 -0
- data/bin/casseo +54 -0
- data/lib/casseo.rb +4 -0
- data/lib/casseo/config.rb +49 -0
- data/lib/casseo/dashboard.rb +166 -0
- data/lib/casseo/index.rb +27 -0
- data/lib/casseo/version.rb +3 -0
- metadata +55 -0
    
        data/README.md
    ADDED
    
    | @@ -0,0 +1,77 @@ | |
| 1 | 
            +
            Casseo
         | 
| 2 | 
            +
            ======
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            A Graphite dashboard viewable without ever leaving the command line. Configuration and concept very similar to [Tasseo](tasseo).
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            Install via Rubygems:
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                gem install casseo
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            Or if you're really concerned about Rubygems' speed, clone the reposistory and create a standalone version (Ruby 1.9 satisfies all of Casseo's dependencies):
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                git clone https://github.com/brandur/casseo.git
         | 
| 13 | 
            +
                cd casseo
         | 
| 14 | 
            +
                rake standalone
         | 
| 15 | 
            +
                mv casseo ~/bin/casseo
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            Configuration
         | 
| 18 | 
            +
            -------------
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            Casseo expects to be able to find your Graphite credentials at `~/.casseorc`:
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                echo '{ graphite_auth: "graphite:my_secret_api_key", graphite_url: "https://graphite.example.com:8080" }' > ~/.casseorc
         | 
| 23 | 
            +
                chmod 600 ~/.casseorc
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            Other allowed configuration options are:
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                * `compressed_chart:` whether to include a space between chart symbols
         | 
| 28 | 
            +
                * `dashboard_default:` name of the dashboard to load if none is specified
         | 
| 29 | 
            +
                * `interval:` Graphite update interval in seconds
         | 
| 30 | 
            +
             | 
| 31 | 
            +
            Dashboards
         | 
| 32 | 
            +
            ----------
         | 
| 33 | 
            +
             | 
| 34 | 
            +
            Dashboards are configured via simple Ruby in a manner reminiscent of Tasseo. All `*.rb` files in `~/.casseo/dashboards` or in any of its subdirectories are loaded automatically at startup. Dashboards are assigned names so that they can be referenced and opened like so:
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                casseo home
         | 
| 37 | 
            +
             | 
| 38 | 
            +
            An example dashboard that can be saved to `~/.casseo/dashboards/home.rb`:
         | 
| 39 | 
            +
             | 
| 40 | 
            +
            ``` ruby
         | 
| 41 | 
            +
            Casseo::Dashboard.define(:api) do |d|
         | 
| 42 | 
            +
              d.metric "custom.api.production.requests.per-sec", display: "req/sec"
         | 
| 43 | 
            +
              d.blank
         | 
| 44 | 
            +
              d.metric "custom.api.production.requests.500.per-min", display: "req 500/min"
         | 
| 45 | 
            +
              d.metric "custom.api.production.requests.502.per-min", display: "req 502/min"
         | 
| 46 | 
            +
              d.metric "custom.api.production.requests.503.per-min", display: "req 503/min"
         | 
| 47 | 
            +
              d.metric "custom.api.production.requests.504.per-min", display: "req 504/min"
         | 
| 48 | 
            +
              d.blank
         | 
| 49 | 
            +
              d.metric "custom.api.production.requests.user-errors.per-min", display: "req user err/min"
         | 
| 50 | 
            +
              d.blank
         | 
| 51 | 
            +
              d.metric "custom.api.production.requests.latency.avg", display: "req latency"
         | 
| 52 | 
            +
            end
         | 
| 53 | 
            +
            ```
         | 
| 54 | 
            +
             | 
| 55 | 
            +
            Get a list of all known dashboards:
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                casseo --list
         | 
| 58 | 
            +
             | 
| 59 | 
            +
            Casseo also takes a file as its first parameter:
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                casseo ~/.casseo/dashboards/home.rb
         | 
| 62 | 
            +
             | 
| 63 | 
            +
            Key Bindings
         | 
| 64 | 
            +
            ------------
         | 
| 65 | 
            +
             | 
| 66 | 
            +
            For now, there are no options on key bindings. Here's what you get:
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                * `j` page down
         | 
| 69 | 
            +
                * `k` page up
         | 
| 70 | 
            +
                * `q` quit
         | 
| 71 | 
            +
                * `1` 5 minute range
         | 
| 72 | 
            +
                * `2` 60 minute range
         | 
| 73 | 
            +
                * `3` 3 hour range
         | 
| 74 | 
            +
                * `4` 24 hour range
         | 
| 75 | 
            +
                * `5` 7 day range
         | 
| 76 | 
            +
             | 
| 77 | 
            +
            [tasseo]: https://github.com/obfuscurity/tasseo
         | 
    
        data/Rakefile
    ADDED
    
    
    
        data/bin/casseo
    ADDED
    
    | @@ -0,0 +1,54 @@ | |
| 1 | 
            +
            #!/usr/bin/env ruby
         | 
| 2 | 
            +
            # encoding: utf-8
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            require "curses"
         | 
| 5 | 
            +
            require "json"
         | 
| 6 | 
            +
            require "net/https"
         | 
| 7 | 
            +
            require "timeout"
         | 
| 8 | 
            +
            require "uri"
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            require_relative "../lib/casseo"
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            # load all user dashboards
         | 
| 13 | 
            +
            dir = "#{File.expand_path("~")}/.casseo/dashboards"
         | 
| 14 | 
            +
            if File.exists?(dir)
         | 
| 15 | 
            +
              # clever hack to get symlinks working properly
         | 
| 16 | 
            +
              Dir["#{dir}/**{,/*/**}/*.rb"].each do |d|
         | 
| 17 | 
            +
                require d
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
            end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            $exit_message = nil
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            ["TERM", "INT"].each do |s|
         | 
| 24 | 
            +
              Signal.trap(s) do
         | 
| 25 | 
            +
                $exit_message = "Caught deadly signal"
         | 
| 26 | 
            +
                Kernel.exit(0)
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
            end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
            begin
         | 
| 31 | 
            +
              Curses.cbreak      # no need for a newline to get type chars
         | 
| 32 | 
            +
              Curses.curs_set(1) # invisible
         | 
| 33 | 
            +
              Curses.noecho      # don't echo character on a getch
         | 
| 34 | 
            +
             | 
| 35 | 
            +
              Curses.init_screen
         | 
| 36 | 
            +
             | 
| 37 | 
            +
              # fail fast on missing required config
         | 
| 38 | 
            +
              Casseo::Config.required
         | 
| 39 | 
            +
             | 
| 40 | 
            +
              if ARGV.count > 0 && File.exists?(ARGV.first)
         | 
| 41 | 
            +
                require ARGV.first
         | 
| 42 | 
            +
              elsif ARGV.count > 0 && ["-l", "--list"].include?(ARGV.first)
         | 
| 43 | 
            +
                $exit_message = Casseo::Dashboard.index.sort.join("\n")
         | 
| 44 | 
            +
              elsif ARGV.first
         | 
| 45 | 
            +
                Casseo::Dashboard.run(ARGV.first.to_sym)
         | 
| 46 | 
            +
              else
         | 
| 47 | 
            +
                Casseo::Dashboard.run(Casseo::Config.dashboard_default)
         | 
| 48 | 
            +
              end
         | 
| 49 | 
            +
            rescue Casseo::ConfigError, Casseo::DashboardNotFound => e
         | 
| 50 | 
            +
              $exit_message = e.message
         | 
| 51 | 
            +
            ensure
         | 
| 52 | 
            +
              Curses.close_screen
         | 
| 53 | 
            +
              puts $exit_message if $exit_message
         | 
| 54 | 
            +
            end
         | 
    
        data/lib/casseo.rb
    ADDED
    
    
| @@ -0,0 +1,49 @@ | |
| 1 | 
            +
            module Casseo
         | 
| 2 | 
            +
              module Config
         | 
| 3 | 
            +
                extend self
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                # whether an extra space is inserted between chart characters
         | 
| 6 | 
            +
                def compressed_chart
         | 
| 7 | 
            +
                  config(:compressed_chart)
         | 
| 8 | 
            +
                end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                def dashboard_default
         | 
| 11 | 
            +
                  config(:dashboard_default) || :home
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                def graphite_auth
         | 
| 15 | 
            +
                  config!(:graphite_auth)
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                def graphite_url
         | 
| 19 | 
            +
                  config!(:graphite_url)
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                # seconds
         | 
| 23 | 
            +
                def interval
         | 
| 24 | 
            +
                  config(:interval) || 2.0
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                def required
         | 
| 28 | 
            +
                  [ Casseo::Config.graphite_auth,
         | 
| 29 | 
            +
                    Casseo::Config.graphite_url ]
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                private
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                def casseorc
         | 
| 35 | 
            +
                  @@casseorc ||= eval(File.read(File.expand_path("~/.casseorc")))
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                def config(sym)
         | 
| 39 | 
            +
                  casseorc[sym]
         | 
| 40 | 
            +
                end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                def config!(sym)
         | 
| 43 | 
            +
                  casseorc[sym] or raise ConfigError.new(":#{sym} not found in ~/.casseorc")
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
              end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
              class ConfigError < StandardError
         | 
| 48 | 
            +
              end
         | 
| 49 | 
            +
            end
         | 
| @@ -0,0 +1,166 @@ | |
| 1 | 
            +
            # encoding: utf-8
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Casseo
         | 
| 4 | 
            +
              class Dashboard
         | 
| 5 | 
            +
                CHART_CHARS = [" "] + %w(▁ ▂ ▃ ▄ ▅ ▆ ▇)
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                extend Index
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def initialize
         | 
| 10 | 
            +
                  @confs = []
         | 
| 11 | 
            +
                  @data = nil
         | 
| 12 | 
            +
                  @page = 0
         | 
| 13 | 
            +
                  @period = 5 # minutes
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                def blank
         | 
| 17 | 
            +
                  @confs << nil
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                def metric(metric, conf = {})
         | 
| 21 | 
            +
                  @confs << conf.merge(metric: metric)
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                def run
         | 
| 25 | 
            +
                  @longest_display = @confs.compact.
         | 
| 26 | 
            +
                    map { |c| c[:display] || c[:metric] }.map { |c| c.length }.max
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  # no data yet, but force drawing of stats to the screen
         | 
| 29 | 
            +
                  show(true)
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  Thread.new do
         | 
| 32 | 
            +
                    # one initial fetch where we don't suppress errors so that the user can
         | 
| 33 | 
            +
                    # verify that their credentials are right
         | 
| 34 | 
            +
                    fetch(false)
         | 
| 35 | 
            +
                    sleep(Config.interval)
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                    loop do
         | 
| 38 | 
            +
                      fetch
         | 
| 39 | 
            +
                      sleep(Config.interval)
         | 
| 40 | 
            +
                    end
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  loop do
         | 
| 44 | 
            +
                    show
         | 
| 45 | 
            +
                    begin
         | 
| 46 | 
            +
                      Timeout::timeout(Config.interval) do
         | 
| 47 | 
            +
                        handle_key_presses
         | 
| 48 | 
            +
                      end
         | 
| 49 | 
            +
                    rescue Timeout::Error
         | 
| 50 | 
            +
                    end
         | 
| 51 | 
            +
                  end
         | 
| 52 | 
            +
                end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                private
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                def clamp(n, min, max)
         | 
| 57 | 
            +
                  if n < min
         | 
| 58 | 
            +
                    min
         | 
| 59 | 
            +
                  elsif n > max
         | 
| 60 | 
            +
                    max
         | 
| 61 | 
            +
                  else
         | 
| 62 | 
            +
                    n
         | 
| 63 | 
            +
                  end
         | 
| 64 | 
            +
                end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                def fetch(suppress_errors=true)
         | 
| 67 | 
            +
                  metrics = @confs.compact.map { |c| c[:metric] }
         | 
| 68 | 
            +
                  targets = metrics.map { |m| "target=#{URI.encode(m)}" }.join("&")
         | 
| 69 | 
            +
                  uri = URI.parse("#{Config.graphite_url}/render/?#{targets}&from=-#{@period}minutes&format=json")
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                  http = Net::HTTP.new(uri.host, uri.port)
         | 
| 72 | 
            +
                  http.use_ssl = true
         | 
| 73 | 
            +
                  request = Net::HTTP::Get.new(uri.request_uri)
         | 
| 74 | 
            +
                  request.basic_auth(*Config.graphite_auth.split(":"))
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                  @data = begin
         | 
| 77 | 
            +
                    response = http.request(request)
         | 
| 78 | 
            +
                    JSON.parse(response.body)
         | 
| 79 | 
            +
                  rescue
         | 
| 80 | 
            +
                    raise unless suppress_errors
         | 
| 81 | 
            +
                    nil
         | 
| 82 | 
            +
                  end
         | 
| 83 | 
            +
                end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                def handle_key_presses
         | 
| 86 | 
            +
                  loop do
         | 
| 87 | 
            +
                    new_page   = nil
         | 
| 88 | 
            +
                    new_period = nil
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                    case Curses.getch
         | 
| 91 | 
            +
                    when Curses::KEY_RESIZE then show
         | 
| 92 | 
            +
                    when ?j then new_page = clamp(@page + 1, 0, num_pages)
         | 
| 93 | 
            +
                    when ?k then new_page = clamp(@page - 1, 0, num_pages)
         | 
| 94 | 
            +
                    when ?q then Kernel.exit(0)
         | 
| 95 | 
            +
                    when ?1 then new_period = 5
         | 
| 96 | 
            +
                    when ?2 then new_period = 60
         | 
| 97 | 
            +
                    when ?3 then new_period = 60 * 3
         | 
| 98 | 
            +
                    when ?4 then new_period = 60 * 24
         | 
| 99 | 
            +
                    when ?5 then new_period = 60 * 24 * 7
         | 
| 100 | 
            +
                    end
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                    if new_page && new_page != @page
         | 
| 103 | 
            +
                      @page = new_page
         | 
| 104 | 
            +
                      Curses.clear
         | 
| 105 | 
            +
                      show
         | 
| 106 | 
            +
                    end
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                    if new_period && new_period != @period
         | 
| 109 | 
            +
                      @period = new_period
         | 
| 110 | 
            +
                      # will update the next time the fetch loop runs
         | 
| 111 | 
            +
                    end
         | 
| 112 | 
            +
                  end
         | 
| 113 | 
            +
                end
         | 
| 114 | 
            +
             | 
| 115 | 
            +
                def num_pages
         | 
| 116 | 
            +
                  (@confs.count / Curses.lines).ceil
         | 
| 117 | 
            +
                end
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                def show(force_draw=false)
         | 
| 120 | 
            +
                  # force us through the method
         | 
| 121 | 
            +
                  @data = @data || [] if force_draw
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                  # failed to fetch on this cycle
         | 
| 124 | 
            +
                  return unless @data
         | 
| 125 | 
            +
             | 
| 126 | 
            +
                  @confs.each_with_index do |conf, i|
         | 
| 127 | 
            +
                    next unless conf
         | 
| 128 | 
            +
                    next unless i >= @page * Curses.lines && i < (@page + 1) * Curses.lines
         | 
| 129 | 
            +
             | 
| 130 | 
            +
                    data_points = @data.detect { |d| d["target"] == conf[:metric] }
         | 
| 131 | 
            +
                    data_points = data_points ? data_points["datapoints"] : []
         | 
| 132 | 
            +
             | 
| 133 | 
            +
                    # show left to right latest to oldest
         | 
| 134 | 
            +
                    data_points.reverse!
         | 
| 135 | 
            +
             | 
| 136 | 
            +
                    max = data_points.
         | 
| 137 | 
            +
                      select { |p| p[0] != nil }.
         | 
| 138 | 
            +
                      max { |p1, p2| p1[0] <=> p2[0] }
         | 
| 139 | 
            +
                    max = max ? max[0] : nil
         | 
| 140 | 
            +
             | 
| 141 | 
            +
                    latest = data_points.detect { |p| p[0] != nil }
         | 
| 142 | 
            +
                    latest = latest ? latest[0] : 0.0
         | 
| 143 | 
            +
             | 
| 144 | 
            +
                    current = nil
         | 
| 145 | 
            +
                    chart = ""
         | 
| 146 | 
            +
                    if max && max > 0
         | 
| 147 | 
            +
                      data_points.each do |p|
         | 
| 148 | 
            +
                        current = p[0] || current
         | 
| 149 | 
            +
                        next unless current
         | 
| 150 | 
            +
                        index = (current.to_f / max * CHART_CHARS.count).to_i - 1
         | 
| 151 | 
            +
                        chart += CHART_CHARS[index]
         | 
| 152 | 
            +
                        chart += " " unless Config.compressed_chart
         | 
| 153 | 
            +
                      end
         | 
| 154 | 
            +
                    end
         | 
| 155 | 
            +
             | 
| 156 | 
            +
                    unit = conf[:unit] || " "
         | 
| 157 | 
            +
                    str = "%-#{@longest_display}s %8.1f%s %s" %
         | 
| 158 | 
            +
                      [conf[:display] || conf[:metric], latest, unit, chart]
         | 
| 159 | 
            +
                    str = str[0...Curses.cols]
         | 
| 160 | 
            +
                    Curses.setpos(i % Curses.lines, 0)
         | 
| 161 | 
            +
                    Curses.addstr(str)
         | 
| 162 | 
            +
                  end
         | 
| 163 | 
            +
                  Curses.refresh
         | 
| 164 | 
            +
                end
         | 
| 165 | 
            +
              end
         | 
| 166 | 
            +
            end
         | 
    
        data/lib/casseo/index.rb
    ADDED
    
    | @@ -0,0 +1,27 @@ | |
| 1 | 
            +
            module Casseo
         | 
| 2 | 
            +
              module Index
         | 
| 3 | 
            +
                @@index = {}
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                def define(name)
         | 
| 6 | 
            +
                  dashboard = Dashboard.new
         | 
| 7 | 
            +
                  yield(dashboard)
         | 
| 8 | 
            +
                  @@index[name] = dashboard
         | 
| 9 | 
            +
                  dashboard
         | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                def index
         | 
| 13 | 
            +
                  @@index.keys
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                def run(name)
         | 
| 17 | 
            +
                  if @@index.key?(name)
         | 
| 18 | 
            +
                    @@index[name].run
         | 
| 19 | 
            +
                  else
         | 
| 20 | 
            +
                    raise DashboardNotFound.new("#{name} is not a known dashboard")
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
              class DashboardNotFound < StandardError
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
            end
         | 
    
        metadata
    ADDED
    
    | @@ -0,0 +1,55 @@ | |
| 1 | 
            +
            --- !ruby/object:Gem::Specification
         | 
| 2 | 
            +
            name: casseo
         | 
| 3 | 
            +
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            +
              version: 0.1.0
         | 
| 5 | 
            +
              prerelease: 
         | 
| 6 | 
            +
            platform: ruby
         | 
| 7 | 
            +
            authors:
         | 
| 8 | 
            +
            - Brandur
         | 
| 9 | 
            +
            autorequire: 
         | 
| 10 | 
            +
            bindir: bin
         | 
| 11 | 
            +
            cert_chain: []
         | 
| 12 | 
            +
            date: 2012-07-01 00:00:00.000000000 Z
         | 
| 13 | 
            +
            dependencies: []
         | 
| 14 | 
            +
            description: 
         | 
| 15 | 
            +
            email: brandur@mutelight.org
         | 
| 16 | 
            +
            executables:
         | 
| 17 | 
            +
            - casseo
         | 
| 18 | 
            +
            extensions: []
         | 
| 19 | 
            +
            extra_rdoc_files: []
         | 
| 20 | 
            +
            files:
         | 
| 21 | 
            +
            - README.md
         | 
| 22 | 
            +
            - Rakefile
         | 
| 23 | 
            +
            - !binary |-
         | 
| 24 | 
            +
              YmluL2Nhc3Nlbw==
         | 
| 25 | 
            +
            - lib/casseo/config.rb
         | 
| 26 | 
            +
            - lib/casseo/dashboard.rb
         | 
| 27 | 
            +
            - lib/casseo/index.rb
         | 
| 28 | 
            +
            - lib/casseo/version.rb
         | 
| 29 | 
            +
            - lib/casseo.rb
         | 
| 30 | 
            +
            homepage: https://github.com/brandur/casseo
         | 
| 31 | 
            +
            licenses:
         | 
| 32 | 
            +
            - MIT
         | 
| 33 | 
            +
            post_install_message: 
         | 
| 34 | 
            +
            rdoc_options: []
         | 
| 35 | 
            +
            require_paths:
         | 
| 36 | 
            +
            - lib
         | 
| 37 | 
            +
            required_ruby_version: !ruby/object:Gem::Requirement
         | 
| 38 | 
            +
              none: false
         | 
| 39 | 
            +
              requirements:
         | 
| 40 | 
            +
              - - ! '>='
         | 
| 41 | 
            +
                - !ruby/object:Gem::Version
         | 
| 42 | 
            +
                  version: '0'
         | 
| 43 | 
            +
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 44 | 
            +
              none: false
         | 
| 45 | 
            +
              requirements:
         | 
| 46 | 
            +
              - - ! '>='
         | 
| 47 | 
            +
                - !ruby/object:Gem::Version
         | 
| 48 | 
            +
                  version: '0'
         | 
| 49 | 
            +
            requirements: []
         | 
| 50 | 
            +
            rubyforge_project: 
         | 
| 51 | 
            +
            rubygems_version: 1.8.24
         | 
| 52 | 
            +
            signing_key: 
         | 
| 53 | 
            +
            specification_version: 3
         | 
| 54 | 
            +
            summary: A Graphite dashboard for the command line.
         | 
| 55 | 
            +
            test_files: []
         |