$30 off During Our Annual Pro Sale. View Details »

Big Ruby Conf Bonus tracks

Big Ruby Conf Bonus tracks

A whirlwind tour of Tony Arcieri's Celluloid and Eric Lindvall's Metriks

Adam Keys

March 01, 2013
Tweet

More Decks by Adam Keys

Other Decks in Programming

Transcript

  1. You should know Celluloid and Metriks
    Adam Keys
    http://therealadam.com
    @therealadam
    March 2013

    View Slide

  2. Concurrent objects in Ruby
    http://celluloid.io

    View Slide

  3. What’s an actor model?
    Actor "daryl"
    Object ralph
    ...
    ...
    frob
    frob
    grok
    Thread 1
    Actor "larry"
    Object B
    ...
    frob
    frob
    grok
    grok
    Thread 2

    View Slide

  4. Celluloid actors
    require 'redis'
    require 'celluloid'
    class RedisAdapter
    include Celluloid
    def initialize(redis)
    @redis = redis
    end
    def record(path)
    redis.incr("stats:#{path}")
    end
    def fetch(path)
    redis.get("stats:#{path}")
    end
    protected
    attr_reader :redis
    end

    View Slide

  5. Celluloid actors
    require 'celluloid'
    require 'json'
    class Stats
    include Celluloid
    def handle(msg)
    log msg
    case msg
    when %r{^VISIT (.*)$}
    path = $1 # regex globals :(
    redis.async.record(path)
    "OK\n"
    when %r{^STATS (.*)$}
    path = $1 # regex globals :(
    stats = redis.fetch(path)
    JSON.dump(stats: {path => stats})
    else
    "ERR\n"
    end
    end
    def log(msg)
    Celluloid.logger.info(msg)
    end
    def redis
    Celluloid::Actor[:redis]
    end
    end

    View Slide

  6. Reliability and Supervision Groups
    class Collector < Celluloid::SupervisionGroup
    supervise Stats, as: :stats
    supervise RedisAdapter, as: :redis, args: [Redis.new]
    supervise Listener, as: :listener, args: ["0.0.0.0", 4000]
    end
    if __FILE__ == $0
    collector = Collector.run!
    trap("INT") { collector.terminate; exit }
    sleep
    end

    View Slide

  7. I/O actors
    require 'celluloid/io'
    class Listener
    include Celluloid::IO
    def initialize(host, port)
    log "Starting on #{host}:#{port}"
    @server = TCPServer.new(host, port)
    async.run
    end
    def finalize
    return unless @server
    @server.close
    end
    def run
    loop {
    async.handle_connection(@server.accept)
    }
    end
    def handle_connection(socket)
    _, host, port = socket.peeraddr
    log "#{host}: #{port} connected"
    msg = socket.readpartial(4096)
    response = stats.handle(msg)
    socket.write(response)
    socket.close
    rescue EOFError
    log "#{host}: #{port} disconnected"
    socket.close
    end
    def log(msg)
    puts msg
    end
    def stats
    Celluloid::Actor[:stats]
    end
    end

    View Slide

  8. More reading
    •Erlang - http://erlang.org
    •D-Cell - https://github.com/celluloid/dcell
    •On Ruby concurrency:
    •http://therealadam.com/2012/06/19/getting-
    started-with-ruby-concurrency-using-two-
    simple-classes/
    •http://therealadam.com/2012/07/05/protect-
    that-state-locks-monitors-and-atomics/
    •http://therealadam.com/2012/09/10/designing-
    for-concurrency/
    •Java Concurrency in Practice

    View Slide

  9. Knowing your production systems
    https://github.com/eric/metriks

    View Slide

  10. All arguments are shallow given numbers

    View Slide

  11. Counters, stats, et. al.
    require 'metrics'
    $api_hits = Metriks.counter('api_hits')
    $api_hits.increment
    require 'metriks/reporter/logger'
    reporter = Metriks::Reporter::Logger(logger: Logger.new('log/metrics.log'))
    reporter.start
    $api_stats = Metriks.timer('api_stats')
    $api_stats.time do
    # … work …
    end

    View Slide

  12. 1. Logfiles
    I, [2013-02-18T07:00:00.006093 #50406] INFO -- : metriks: time=1361192400 name=stats type=timer count=92440
    one_minute_rate=434.4299373735234 five_minute_rate=
    220.682764284654 fifteen_minute_rate=90.15828364936169 mean_rate=423.37823163275544 min=0.000362023 max=0.070315041
    mean=0.0007417273702509757 stddev=2.4841137753254027e-06 median=0.000614221 95th_percentile=0.0011458962499999996
    I, [2013-02-18T07:00:00.009590 #50406] INFO -- : metriks: time=1361192400 name=api_stats type=timer count=92440
    one_minute_rate=434.4299373735234 five_minute_rate=220.682764284654 fifteen_minute_rate=90.15828364936169
    mean_rate=423.3715240226875 min=0.000300078 max=0.070015888 mean=0.0006167874175681445 stddev=2.4013694294628166e-06
    median=0.000505336 95th_percentile=0.0009915237499999996

    View Slide

  13. 2. Convert to CSV
    #!/usr/bin/env ruby
    require 'csv'
    columns = %w{name time mean_rate 95th_percentile}
    out = CSV.generate do |csv|
    csv << columns.to_a
    ARGF.each_line do |l|
    fields = l.split
    next if fields[0] != 'I,'
    metrics = fields.slice(7..-1).inject({}) do |hsh, str|
    name, value = str.split('=')
    hsh.update(name => value)
    end
    csv << columns.inject([]) { |row, name| row << metrics[name] }
    end
    end
    puts out

    View Slide

  14. 3. Spreadsheets!

    View Slide

  15. Dashboard services

    View Slide

  16. More reading
    •Metrics, the original library - http://
    metrics.codahale.com
    •Metrics everywhere, the slides - http://
    codahale.com/codeconf-2011-04-09-metrics-
    metrics-everywhere.pdf
    •Metrics everywhere, the talk - http://
    www.youtube.com/watch?v=czes-oa0yik

    View Slide

  17. Thanks!

    View Slide