Commit 739fd5e2 authored by Paul Asmuth's avatar Paul Asmuth
Browse files

made fnordmetric::web independent

parent a0ed4955
Loading
Loading
Loading
Loading
+56 −143
Original line number Diff line number Diff line
@@ -10,88 +10,20 @@ require 'rack/server'
require "fnordmetric/ext"
require "fnordmetric/version"


# TODO
#  -> timeline-widget: tick button active states
#  -> port numbers style from scala-experiment
#  -> gauge api: render with add. offset
#  -> toplist-widget: tick+range selector
#  -> toplist-widget: display trends
#  -> timeline-widget: compare with yesterday
#  -> numeric-gauge view: add numbers
#  -> store per-session-data
#  -> callback on session-flush
#  -> backport sidebar css from 0.7?
#  -> minimize/pack js
#  -> put images into one sprite
#  -> remove params from log
#  -> toplist_widget per_keyword: show multiple keywords
#  -> proper garbage collection
#  -> gauge overview page (a'la github graphs landing)
#  -> gauge online vs offline status
#
# numeric_gauge
#   -> time-distribution/punchcard
#   -> moving avg. etc / bezier fu
#   -> realtime view
#   -> formatter: num, time, currency
#   -> opts: average, average_by, unique_by, enable_histogram, enable_punchard, enable_stddeviation
#
# toplist_gauge
#   -> trending keys
#
# timeline_widget
#   -> show last interval (cmp. w/ yesterday)
#
# toplist_widget
#   -> multi-interval + range-selector
#
# geo_distribution_gauge
#
#  distribution gauge
#
#
# wiki
#
#  -> getting started
#  -> gagues pages
#  -> sending data pages
#    -> tcp, udp, http, apis
#    -> events containing user data
#    -> pre-defined fields (_session etc)
#    -> pre-defined events (_incr, _observe etc)
#  -> configurin fnordmetric
#    -> configuration options
#    -> running embedded
#    -> running standalone
#  -> event handler pages
#    -> pre-defined event-fields
#    -> incrementing multiple gauges per event
#    -> storing data per session
#    -> end-of-session callback
#  -> building custom dashboards

module FnordMetric

  @@namespaces = {}
  @@server_configuration = nil

  @@firehose    = EM::Channel.new
  @@options = nil

  def self.backend
    FnordMetric::RedisBackend.new(options)
  end

  def self.namespace(key=nil, &block)
    @@namespaces[key] = block
  end

  def self.server_configuration=(configuration)
    @@server_configuration = configuration
  def self.options(opts = {})
    default_options(@@options || {}).merge(opts)
  end

  def self.options=(configuration)
    @@server_configuration = configuration
  def self.options=(opts)
    @@options = opts
  end

  def self.default_options(opts = {})
@@ -110,10 +42,6 @@ module FnordMetric
    }.merge(opts)
  end

  def self.options(opts = {})
    default_options(@@server_configuration || {}).merge(opts)
  end

  def self.log(msg)
    puts "[#{Time.now.strftime("%y-%m-%d %H:%M:%S")}] #{msg}"
  end
@@ -146,31 +74,6 @@ module FnordMetric
        ($fnordmetric || []).map(&:initialized)
      end

      # opts = options(opts)
      # app = embedded(opts)

      # if opts[:web_interface]
      #   server = opts[:web_interface_server].downcase

      #   unless ["thin", "hatetepe"].include? server
      #     raise "Need an EventMachine webserver, but #{server} isn't"
      #   end

      #   host, port = *opts[:web_interface]

      #   Rack::Server.start(
      #     :app => app,
      #     :server => server,
      #     :Host => host, 
      #     :Port => port
      #   ) && log("listening on http://#{host}:#{port}")
        
      #   FnordMetric::WebSocket.new(
      #     :host => host, 
      #     :port => (port.to_i+1)
      #   ) && log("listening on ws://#{host}:#{port.to_i+1}")

      # end
    end
  end

@@ -188,6 +91,21 @@ module FnordMetric
    end
  end




  # LEGACY / BACKWARDS COMPATBILE STUFF

  def self.server_configuration=(configuration)
    self.options=(configuration)
  end

  def self.namespace(*args, &block)
    FnordMetric::Web.namespace(*args, &block)
  end



  def self.standalone
    require "fnordmetric/standalone"
  end
@@ -200,10 +118,6 @@ module FnordMetric
    opts = options(opts)
    app  = nil

    if opts[:rack_app] or opts[:web_interface]
      app = FnordMetric::App.new(@@namespaces.clone, opts)
    end

    EM.next_tick do

      # FIXPAUL: this is re-instantiating all gauges. why?
@@ -212,22 +126,13 @@ module FnordMetric
      #  worker.ready!
      #end

      if opts[:inbound_stream]
        inbound_class = opts[:inbound_protocol] == :udp ? InboundDatagram : InboundStream
        begin
          inbound_stream = inbound_class.start(opts)
          log "listening on #{opts[:inbound_protocol]}://#{opts[:inbound_stream][0..1].join(":")}"
        rescue
          log "cant start #{inbound_class.name}. port in use?"
        end
      end

      if opts[:print_stats]
        redis = connect_redis(opts[:redis_url])
        EM::PeriodicTimer.new(opts[:print_stats]) do
          print_stats(opts, redis)
        end
      end

    end

    app
@@ -239,35 +144,43 @@ end
require "fnordmetric/backends/redis_backend"
require "fnordmetric/backends/memory_backend"

require "fnordmetric/acceptors/acceptor"
require "fnordmetric/acceptors/tcp_acceptor"
require "fnordmetric/acceptors/udp_acceptor"

require "fnordmetric/acceptor"
require "fnordmetric/web/web"
require "fnordmetric/web/namespace"
require "fnordmetric/web/app"
require "fnordmetric/web/websocket"
require "fnordmetric/web/event"
require "fnordmetric/web/dashboard"
require "fnordmetric/web/session"

require "fnordmetric/logger"

require "fnordmetric/api"
require "fnordmetric/udp_client"
require "fnordmetric/inbound_stream"
require "fnordmetric/inbound_datagram"
require "fnordmetric/worker"
require "fnordmetric/widget"
require "fnordmetric/timeline_widget"
require "fnordmetric/numbers_widget"
require "fnordmetric/bars_widget"
require "fnordmetric/toplist_widget"
require "fnordmetric/pie_widget"
require "fnordmetric/html_widget"
require "fnordmetric/namespace"
require "fnordmetric/gauge_modifiers"
require "fnordmetric/gauge_calculations"
require "fnordmetric/context"
require "fnordmetric/gauge"
require "fnordmetric/remote_gauge"
require "fnordmetric/multi_gauge"
require "fnordmetric/numeric_gauge"
require "fnordmetric/toplist_gauge"
require "fnordmetric/session"
require "fnordmetric/app"
require "fnordmetric/websocket"
require "fnordmetric/dashboard"
require "fnordmetric/event"




# require "fnordmetric/api"
# require "fnordmetric/udp_client"
# require "fnordmetric/worker"

# require "fnordmetric/widget"
# require "fnordmetric/timeline_widget"
# require "fnordmetric/numbers_widget"
# require "fnordmetric/bars_widget"
# require "fnordmetric/toplist_widget"
# require "fnordmetric/pie_widget"
# require "fnordmetric/html_widget"

# require "fnordmetric/namespace"
# require "fnordmetric/gauge_modifiers"
# require "fnordmetric/gauge_calculations"
# require "fnordmetric/context"
# require "fnordmetric/gauge"

# require "fnordmetric/remote_gauge"
# require "fnordmetric/multi_gauge"
# require "fnordmetric/numeric_gauge"
# require "fnordmetric/toplist_gauge"
+30 −0
Original line number Diff line number Diff line
class FnordMetric::Acceptor

  def initialize(opts)
    @opts = opts

    $fnordmetric ||= []
    $fnordmetric << self
  end

  def initialized   
    inbound_class = if @opts[:protocol] == :udp 
      FnordMetric::UDPAcceptor
    else
      FnordMetric::TCPAcceptor
    end

    @opts[:listen] = [
      @opts[:host] || "0.0.0.0",
      @opts[:port] || 2323
    ]

    begin
      inbound_stream = inbound_class.start(@opts)
      FnordMetric.log "listening on #{@opts[:protocol]}://#{@opts[:listen][0..1].join(":")}"
    #rescue
    #  FnordMetric.log "cant start #{inbound_class.name}. port in use?"
    end
  end

end
 No newline at end of file
+0 −2
Original line number Diff line number Diff line
@@ -31,8 +31,6 @@ class FnordMetric::Logger
    end

    FnordMetric.log "logging to #{logfile_path}"
    
    listener.join
  end

  def self.import(logfile_path)
+194 −0
Original line number Diff line number Diff line
# encoding: utf-8

class FnordMetric::App < Sinatra::Base

  @@sessions = Hash.new
  @@public_files = {
    "fnordmetric.css" => "text/css",
    "fnordmetric.js" => "application/x-javascript",
    "fnordmetric.ui.js" => "application/x-javascript",
    "fnordmetric.util.js" => "application/x-javascript",
    "fnordmetric.dashboard_view.js" => "application/x-javascript",
    "fnordmetric.gauge_view.js" => "application/x-javascript",
    "fnordmetric.overview_view.js" => "application/x-javascript",
    "fnordmetric.session_view.js" => "application/x-javascript",
    "fnordmetric.timeline_widget.js" => "application/x-javascript",
    "fnordmetric.numbers_widget.js" => "application/x-javascript",
    "fnordmetric.bars_widget.js" => "application/x-javascript",
    "fnordmetric.toplist_widget.js" => "application/x-javascript",
    "fnordmetric.pie_widget.js" => "application/x-javascript",
    "fnordmetric.html_widget.js" => "application/x-javascript",
    "vendor/jquery-1.6.1.min.js" => "application/x-javascript",
    "vendor/highcharts.js" => "application/x-javascript",
    "vendor/raphael.min.js" => "application/x-javascript",
    "vendor/raphael.util.js" => "application/x-javascript",
    "img/list.png" => "image/png",
    "img/list_active.png" => "image/png",
    "img/list_hover.png" => "image/png",
    "img/picto_gauge.png" => "image/png",
    "img/head.png" => "image/png",
    "img/navbar.png" => "image/png",
    "img/navbar_btn.png" => "image/png"
  }

  if RUBY_VERSION =~ /1.9.\d/
    Encoding.default_external = Encoding::UTF_8
  end

  enable :session

  set :haml, :format => :html5
  set :views, ::File.expand_path('../../../../haml', __FILE__)

  def initialize(namespaces, opts)
    @namespaces = {}
    @redis = Redis.connect(:url => opts[:redis_url])
    @opts = opts
    namespaces.each do |key, block|
      @namespaces[key] = FnordMetric::Namespace.new(key, opts.clone)
      @namespaces[key].instance_eval(&block)
      @namespaces[key].ready!(@redis.clone)
    end
    super(nil)
  end

  helpers do
    include Rack::Utils
    alias_method :h, :escape_html

    def path_prefix
      request.env["SCRIPT_NAME"]
    end

    def namespaces
      @namespaces
    end

    def current_namespace
      @namespaces[@namespaces.keys.detect{ |k|
        k.to_s == params[:namespace]
      }.try(:intern)]
    end

  end

  if ENV['RACK_ENV'] == "test"
    set :raise_errors, true
  end

  get '/' do
  	redirect "#{path_prefix}/#{@namespaces.keys.first}"
  end

  get '/:namespace' do
    pass unless current_namespace
    haml :app
  end

  get '/favicon.ico' do
    ""
  end

  get '/:namespace/gauge/:name' do

    gauge = if params[:name].include?("++")
      parts = params[:name].split("++")
      current_namespace.gauges[parts.first.to_sym].fetch_gauge(parts.last, params[:tick].to_i)
    else
      current_namespace.gauges[params[:name].to_sym]
    end

    if !gauge
      status 404
      return ""
    end

    data = if gauge.three_dimensional?
      _t = (params[:at] || Time.now).to_i
      { :count => gauge.field_values_total(_t), :values => gauge.field_values_at(_t) }
    elsif params[:at] && params[:at] =~ /^[0-9]+$/
      { (_t = gauge.tick_at(params[:at].to_i)) => gauge.value_at(_t) }
    elsif params[:at] && params[:at] =~ /^([0-9]+)-([0-9]+)$/
      _range = params[:at].split("-").map(&:to_i)
      _values = gauge.values_in(_range.first.._range.last)
      params[:sum] ? { :sum => _values.values.compact.map(&:to_i).sum } : _values
    else
      { (_t = gauge.tick_at(Time.now.to_i-gauge.tick)) => gauge.value_at(_t) }
    end

    data.to_json
  end

  get '/:namespace/sessions' do

    sessions = current_namespace.sessions(:all, :limit => 100).map do |session|
      session.fetch_data!
      session.to_json
    end

    { :sessions => sessions }.to_json
  end

  get '/:namespace/events' do

    events = if params[:type]
      current_namespace.events(:by_type, :type => params[:type])
    elsif params[:session_key]
      current_namespace.events(:by_session_key, :session_key => params[:session_key])
    else 
      find_opts = { :limit => 100 }
      find_opts.merge!(:since => params[:since].to_i+1) if params[:since]
      current_namespace.events(:all, find_opts)
    end

    { :events => events.map(&:to_json) }.to_json
  end

  get '/:namespace/event_types' do
    types_key = current_namespace.key_prefix("type-")
    keys = @redis.keys("#{types_key}*").map{ |k| k.gsub(types_key,'') }

    { :types => keys }.to_json
  end

  get '/:namespace/dashboard/:dashboard' do
    dashboard = current_namespace.dashboards.fetch(params[:dashboard])

    dashboard.to_json
  end

  post '/events' do
    halt 400, 'please specify the event_type (_type)' unless params["_type"]
    track_event((8**32).to_s(36), parse_params(params))
  end

  @@public_files.each do |public_file, public_file_type|
    get "/#{public_file}" do
      content_type(public_file_type)
      ::File.open(::File.expand_path("../../../../pub/#{public_file}", __FILE__)).read
    end
  end
private

  def parse_params(hash)
    hash.tap do |h|
      h.keys.each{ |k| h[k] = parse_param(h[k]) }
    end
  end

  def parse_param(object)
    return object unless object.is_a?(String)
    return object.to_f if object.match(/^[0-9]+[,\.][0-9]+$/)
    return object.to_i if object.match(/^[0-9]+$/)
    object
  end

  def track_event(event_id, event_data)
    @redis.hincrby "#{@opts[:redis_prefix]}-stats",             "events_received", 1
    @redis.set     "#{@opts[:redis_prefix]}-event-#{event_id}", event_data.to_json
    @redis.lpush   "#{@opts[:redis_prefix]}-queue",             event_id
    @redis.expire  "#{@opts[:redis_prefix]}-event-#{event_id}", @opts[:event_queue_ttl]
  end

end
+36 −0
Original line number Diff line number Diff line
class FnordMetric::Dashboard

  attr_accessor :widgets

  def initialize(options={})    
    raise "please provide a :title" unless options[:title]        
    @widgets = Array.new
    @options = options
  end

  def add_widget(w)
    @widgets << w
  end

  def title
    @options[:title]
  end

  def token
    token = title.to_s.gsub(/[\W]/, '')
    token = Digest::SHA1.hexdigest(title.to_s) if token.empty?
    token
  end

  def to_json
    {
      :title => title,
      :widgets => {}.tap { |wids|
        @widgets.each do |w|
          wids[w.token] = w.render
        end
      }
    }.to_json
  end
  
end
 No newline at end of file
Loading