Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Real Time Salami

Real Time Salami

RubyConf2012 talk about real time applications, salami making, and much more.

Aaron Patterson

November 02, 2012
Tweet

More Decks by Aaron Patterson

Other Decks in Technology

Transcript

  1. Ingredients ❤ Pork ❤ Salt, nitrate, nitrite ❤ Dextose, Sugar

    ❤ White Pepper, Black Pepper ❤ Garlic, Fennel ❤ Chianti
  2. AT&T, AT&T logo and all AT&T related marks are trademarks

    of AT&T Intellectual Property and/or AT&T affiliated companies.
  3. AT&T, AT&T logo and all AT&T related marks are trademarks

    of AT&T Intellectual Property and/or AT&T affiliated companies. NO TIME
  4. Temperature 50 55 60 65 70 Day 1 Day 2

    Day 3 Day 4 Day 5 Day 6 Day 7 Temperature (F)
  5. Humidity 0 22.5 45 67.5 90 Day 1 Day 2

    Day 3 Day 4 Day 5 Day 6 Day 7 Relative Humidity
  6. 4 4.75 5.5 6.25 7 Day 1 Day 2 Day

    3 Day 4 Day 5 Day 6 Day 7 pH pH (0.3% sugar)
  7. Example class BrowserController < ApplicationController include ActionController::Live def index 100.times

    do response.stream.write "hello!\n" end response.stream.close end end
  8. Example class BrowserController < ApplicationController include ActionController::Live def index 100.times

    do response.stream.write "hello!\n" end response.stream.close end end Mix in Stream
  9. Our API def index response.status = 200 response.headers[‘X-Whatever’] = ‘<3’

    response.stream.write ‘hello’ response.stream.write ‘ world’ response.stream.close end
  10. Wrapped Response class Response attr_accessor :status attr_reader :headers, :stream def

    initialize @status = 200 @headers = {} @stream = StringIO.new end end def call(env) res = Response.new controller.response = res controller.index [res.status, res.headers, res.stream] end
  11. Threaded action def call(env) res = Response.new controller.response = res

    Thread.new { controller.index } [res.status, res.headers, res.stream] end
  12. Block until write def call(env) res = Response.new controller.response =

    res Thread.new { controller.index } res.stream.await [res.status, res.headers, res.stream] end
  13. Block until write def call(env) res = Response.new controller.response =

    res Thread.new { controller.index } res.stream.await [res.status, res.headers, res.stream] end Block
  14. Blocking Buffer class Buffer def initialize @latch = Latch.new @buffer

    = Queue.new end def await # wait for write @latch.await end def write(str) @latch.release @buffer << str end end
  15. Blocking Buffer class Buffer def initialize @latch = Latch.new @buffer

    = Queue.new end def await # wait for write @latch.await end def write(str) @latch.release @buffer << str end end `call` blocks here
  16. Blocking Buffer class Buffer def initialize @latch = Latch.new @buffer

    = Queue.new end def await # wait for write @latch.await end def write(str) @latch.release @buffer << str end end `write` unblocks
  17. Control Output class MyERB < ERB def set_eoutvar(compiler, eoutvar =

    '_erbout') compiler.put_cmd = "#{eoutvar}.write" compiler.insert_cmd = "#{eoutvar}.write" compiler.pre_cmd = [] compiler.post_cmd = [] end end doc = MyERB.new '<%= hello %> world', nil, nil, '$stdout' puts doc.src
  18. SSE Response HTTP/1.1 200 OK X-Frame-Options: SAMEORIGIN X-XSS-Protection: 1; mode=block

    X-Content-Type-Options: nosniff Content-Type: text/event-stream Transfer-Encoding: chunked event: ping data: {"ping":"2012-10-06T21:44:41-07:00"} event: reload data: {"changed":["/Users/aaron/git/lolwut/app/views/ users/"]}
  19. SSE Response HTTP/1.1 200 OK X-Frame-Options: SAMEORIGIN X-XSS-Protection: 1; mode=block

    X-Content-Type-Options: nosniff Content-Type: text/event-stream Transfer-Encoding: chunked event: ping data: {"ping":"2012-10-06T21:44:41-07:00"} event: reload data: {"changed":["/Users/aaron/git/lolwut/app/views/ users/"]}
  20. SSE Response HTTP/1.1 200 OK X-Frame-Options: SAMEORIGIN X-XSS-Protection: 1; mode=block

    X-Content-Type-Options: nosniff Content-Type: text/event-stream Transfer-Encoding: chunked event: ping data: {"ping":"2012-10-06T21:44:41-07:00"} event: reload data: {"changed":["/Users/aaron/git/lolwut/app/views/ users/"]}
  21. SSE Response HTTP/1.1 200 OK X-Frame-Options: SAMEORIGIN X-XSS-Protection: 1; mode=block

    X-Content-Type-Options: nosniff Content-Type: text/event-stream Transfer-Encoding: chunked event: ping data: {"ping":"2012-10-06T21:44:41-07:00"} event: reload data: {"changed":["/Users/aaron/git/lolwut/app/views/ users/"]}
  22. Client Side jQuery(document).ready(function() { setTimeout(function() { var source = new

    EventSource('/control'); // if we get a reload command, reload the page source.addEventListener('reload', function(e) { window.location.reload(); }); }, 1); });
  23. Client Side jQuery(document).ready(function() { setTimeout(function() { var source = new

    EventSource('/control'); // if we get a reload command, reload the page source.addEventListener('reload', function(e) { window.location.reload(); }); }, 1); });
  24. Client Side jQuery(document).ready(function() { setTimeout(function() { var source = new

    EventSource('/control'); // if we get a reload command, reload the page source.addEventListener('reload', function(e) { window.location.reload(); }); }, 1); });
  25. Client Side jQuery(document).ready(function() { setTimeout(function() { var source = new

    EventSource('/control'); // if we get a reload command, reload the page source.addEventListener('reload', function(e) { window.location.reload(); }); }, 1); });
  26. Message Bus class Bus include Observable def publish data changed

    notify_observers data end def add_observer &block o = Class.new { define_method(:update) { |data| block.call data } }.new super(o) end end
  27. Emit SSE def index queue = Queue.new observer = BUS.add_observer

    { |data| queue << data } begin while data = queue.pop begin io.write data, :event => 'measurement' rescue IOError queue.push nil io.close end end ensure BUS.delete_observer observer end end
  28. Publisher def create sig, data = params['_json'] BUS.publish({ :now =>

    Time.now, :data => JSON.load(data.unpack('m').first) }) render :nothing => true end
  29. GChart + SSE var source = new EventSource('/measurements.json'); var chart

    = document.getElementById('chart'); source.addEventListener('measurement', function(e) { var payload = JSON.parse(e.data); var data = payload.data; if(chart && table) { var count = table.getNumberOfRows(); table.addRow(['', data[0], data[1]]); if(count > 60) { table.removeRow(0); } gchart.draw(table, options(data.interface)); } }, false);
  30. Instructions 0000 trace 1 0002 putobject 2 0004 putobject 3

    0006 opt_plus <callinfo!mid:+, argc:1, ARGS_SKIP> 0008 leave
  31. Instructions 0000 trace 1 0002 putobject 2 0004 putobject 3

    0006 opt_plus <callinfo!mid:+, argc:1, ARGS_SKIP> 0008 leave Instruction names Parameters
  32. trace - trace (set_trace_func) putobject - push object on the

    stack opt_plus - optimized X + Y leave - return from this scope
  33. Ruby -> GS /trace { pop } def /putobject {

    } def /opt_plus { add } def /leave { } def
  34. Translation is = RubyVM::InstructionSequence.new '2 + 3' puts is.to_a.last.map {

    |l,i| [Hash === i ? nil : i, l].compact.join ' ' }.join "\n" 1 trace 2 putobject 3 putobject opt_plus leave Translated Instructions
  35. to_a irb> is = RubyVM::InstructionSequence.new '3 + 2' => <RubyVM::InstructionSequence>

    irb> is.to_a => ["YARVInstructionSequence/SimpleDataFormat", 2, 0, 1, {:arg_size=>0, :local_size=>1, :stack_max=>2}, "<compiled>", "<compiled>", nil, 1, :top, [], 0, [], [[:trace, 1], [:putobject, 3], [:putobject, 2], [:opt_plus, {:mid=>: +, :flag=>256, :orig_argc=>1, :blockptr=>nil}], [:leave]]]
  36. handle = DL::Handle::DEFAULT address = handle['rb_iseq_load'] func = Fiddle::Function.new(address, [DL::TYPE_VOIDP]

    * 3, DL::TYPE_VOIDP) RubyVM::InstructionSequence.class_eval do # This monkey patch allows us to load arbitrary byte code with # Ruby's VM define_singleton_method(:load) { |data, parent = nil, opt = nil| func.call(DL.dlwrap(data), parent, opt).to_value } end
  37. handle = DL::Handle::DEFAULT address = handle['rb_iseq_load'] func = Fiddle::Function.new(address, [DL::TYPE_VOIDP]

    * 3, DL::TYPE_VOIDP) RubyVM::InstructionSequence.class_eval do # This monkey patch allows us to load arbitrary byte code with # Ruby's VM define_singleton_method(:load) { |data, parent = nil, opt = nil| func.call(DL.dlwrap(data), parent, opt).to_value } end
  38. Manipulate Insns is = RubyVM::InstructionSequence.new '3 + 5' myis =

    is.to_a myis.last.map! { |ins, val| if ins == :putobject [ins, 10] else [ins, val].compact end } is2 = RubyVM::InstructionSequence.load myis p is2.eval # => 20
  39. ActiveRecord ConnectionAdapters Mysql2Adapter#execute 0 (0.0%) of 107 (21.0%) Enumerable#to_a 0

    (0.0%) of 32 (6.3%) ActiveRecord ConnectionAdapters AbstractAdapter#log 9 (1.8%) of 107 (21.0%) ActiveSupport Notifications Instrumenter#instrument 0 (0.0%) of 98 (19.3%) 98 ActiveRecord ConnectionAdapters AbstractMysqlAdapter#execute 0 (0.0%) of 107 (21.0%) 107 107 Arel Visitors ToSql#accept 0 (0.0%) of 104 (20.4%) Arel Visitors MySQL#visit_Arel_Nodes_SelectStatement 1 (0.2%) of 104 (20.4%) Arel Visitors ToSql#visit_Arel_Nodes_SelectStatement 1 (0.2%) of 103 (20.2%) 103 Arel Visitors Visitor#accept 0 (0.0%) of 104 (20.4%) 104 Arel Visitors Visitor#visit 1 (0.2%) of 104 (20.4%) 104 104 Arel Visitors ToSql#visit_Arel_Nodes_JoinSource 1 (0.2%) of 40 (7.9%) 40 Arel Visitors ToSql#visit_Arel_Nodes_InnerJoin 9 (1.8%) of 22 (4.3%) 22 Arel Visitors ToSql#visit_Arel_Nodes_Equality 8 (1.6%) of 18 (3.5%) 18 Arel Visitors ToSql#visit_Arel_Nodes_On 0 (0.0%) of 13 (2.6%) 13 Arel Visitors ToSql#visit_Arel_Nodes_And 0 (0.0%) of 11 (2.2%) 11 Arel Visitors ToSql#visit_Arel_Attributes_Attribute 10 (2.0%) 10 Array#map 3 (0.6%) of 92 (18.1%) 91 Array#join 51 (10.0%) 11 Mysql2 Client#query 82 (16.1%) of 83 (16.3%) 83 ActiveSupport Notifications Fanout#start 0 (0.0%) of 15 (2.9%) 15 41 Arel Visitors MySQL#visit_Arel_Nodes_SelectCore 0 (0.0%) of 91 (17.9%) 91 Arel Visitors ToSql#visit_Arel_Nodes_SelectCore 16 (3.1%) of 91 (17.9%) 91 41 14 18 22 17 Mysql2 Result#each 32 (6.3%) 32 Struct#hash 4 (0.8%) of 21 (4.1%) 13 Arel Table#hash 2 (0.4%) of 17 (3.3%) 17 10 15 ActiveSupport Notifications Fanout#listeners_for 9 (1.8%) 9 13 6 5 ARel mysql2
  40. ActiveRecord ConnectionAdapters Mysql2Adapter#execute 0 (0.0%) of 107 (21.0%) Enumerable#to_a 0

    (0.0%) of 32 (6.3%) ActiveRecord ConnectionAdapters AbstractAdapter#log 9 (1.8%) of 107 (21.0%) ActiveSupport Notifications Instrumenter#instrument 0 (0.0%) of 98 (19.3%) 98 ActiveRecord ConnectionAdapters AbstractMysqlAdapter#execute 0 (0.0%) of 107 (21.0%) 107 107 Arel Visitors ToSql#accept 0 (0.0%) of 104 (20.4%) Arel Visitors MySQL#visit_Arel_Nodes_SelectStatement 1 (0.2%) of 104 (20.4%) Arel Visitors ToSql#visit_Arel_Nodes_SelectStatement 1 (0.2%) of 103 (20.2%) 103 Arel Visitors Visitor#accept 0 (0.0%) of 104 (20.4%) 104 Arel Visitors Visitor#visit 1 (0.2%) of 104 (20.4%) 104 104 Arel Visitors ToSql#visit_Arel_Nodes_JoinSource 1 (0.2%) of 40 (7.9%) 40 Arel Visitors ToSql#visit_Arel_Nodes_InnerJoin 9 (1.8%) of 22 (4.3%) 22 Arel Visitors ToSql#visit_Arel_Nodes_Equality 8 (1.6%) of 18 (3.5%) 18 Arel Visitors ToSql#visit_Arel_Nodes_On 0 (0.0%) of 13 (2.6%) 13 Arel Visitors ToSql#visit_Arel_Nodes_And 0 (0.0%) of 11 (2.2%) 11 Arel Visitors ToSql#visit_Arel_Attributes_Attribute 10 (2.0%) 10 Array#map 3 (0.6%) of 92 (18.1%) 91 Array#join 51 (10.0%) 11 Mysql2 Client#query 82 (16.1%) of 83 (16.3%) 83 ActiveSupport Notifications Fanout#start 0 (0.0%) of 15 (2.9%) 15 41 Arel Visitors MySQL#visit_Arel_Nodes_SelectCore 0 (0.0%) of 91 (17.9%) 91 Arel Visitors ToSql#visit_Arel_Nodes_SelectCore 16 (3.1%) of 91 (17.9%) 91 41 14 18 22 17 Mysql2 Result#each 32 (6.3%) 32 Struct#hash 4 (0.8%) of 21 (4.1%) 13 Arel Table#hash 2 (0.4%) of 17 (3.3%) 17 10 15 ActiveSupport Notifications Fanout#listeners_for 9 (1.8%) 9 13 6 5 ARel mysql2
  41. Easy Proof irb> 10.times { puts "foo".object_id } 70166440589860 70166440589800

    70166440589740 70166440589660 70166440589580 70166440589520 70166440589420 70166440589360 70166440589260 70166440589200 => 10
  42. "FROM #{visit(o.source)} " irb> puts RubyVM::InstructionSequence.new('"FROM #{visit(o.source)} "').disasm == disasm:

    <RubyVM::InstructionSequence:<compiled>@<compiled>>========== 0000 trace 1 0002 putobject "FROM " 0004 putself 0005 putself 0006 send :o, 0, nil, 24, <ic:0> 0012 send :source, 0, nil, 0, <ic:1> 0018 send :visit, 1, nil, 8, <ic:2> 0024 tostring 0025 putstring " " 0027 concatstrings 3 0029 leave => nil
  43. "FROM #{visit(o.source)} " irb> puts RubyVM::InstructionSequence.new('"FROM #{visit(o.source)} "').disasm == disasm:

    <RubyVM::InstructionSequence:<compiled>@<compiled>>========== 0000 trace 1 0002 putobject "FROM " 0004 putself 0005 putself 0006 send :o, 0, nil, 24, <ic:0> 0012 send :source, 0, nil, 0, <ic:1> 0018 send :visit, 1, nil, 8, <ic:2> 0024 tostring 0025 putstring " " 0027 concatstrings 3 0029 leave => nil
  44. putobject /** @c put @e put some object. i.e. Fixnum,

    true, false, nil, and so on. @j ΦϒδΣΫτ val ΛελοΫʹϓογϡ͢Δɻ i.e. Fixnum, true, false, nil, and so on. */ DEFINE_INSN putobject (VALUE val) () (VALUE val) { /* */ }
  45. putobject /** @c put @e put some object. i.e. Fixnum,

    true, false, nil, and so on. @j ΦϒδΣΫτ val ΛελοΫʹϓογϡ͢Δɻ i.e. Fixnum, true, false, nil, and so on. */ DEFINE_INSN putobject (VALUE val) () (VALUE val) { /* */ } It does nothing.
  46. Use Constants irb> SELECT = "SELECT" => "SELECT" irb> 5.times

    { p SELECT.object_id } 70230907127200 70230907127200 70230907127200 70230907127200 70230907127200 => 5
  47. Use String#<< FROM = "FROM " def visit_Arel_Nodes_SelectCore o select

    = "SELECT" if o.source select << FROM select << visit(o.source) end select end
  48. String Allocations FROM = "FROM " def visit_Arel_Nodes_SelectCore o select

    = "SELECT" if o.source select << FROM select << visit(o.source) end select end
  49. String Allocations FROM = "FROM " def visit_Arel_Nodes_SelectCore o select

    = "SELECT" if o.source select << FROM select << visit(o.source) end select end
  50. Ruby Probes ruby:::function-entry; ruby:::function-return; ruby:::require-entry; ruby:::require-return; ruby:::load-entry; ruby:::load-return; ruby:::raise; ruby:::object-create;

    ruby:::array-create; ruby:::hash-create; ruby:::string-create; ruby:::parse-begin; ruby:::parse-end; ruby:::insn; ruby:::insn-operand; ruby:::gc-mark-begin; ruby:::gc-mark-end; ruby:::gc-sweep-begin; ruby:::gc-sweep-end;
  51. Output branchunless 56244 setlocal 56857 branchif 60836 dup 65032 send

    66905 putself 76301 putobject 79396 leave 141896 opt_send_simple 204095 getlocal 249000 $
  52. Samples branchif 518 putself 542 getinstancevariable 550 putobject 667 leave

    1355 getconstant 1521 invokesuper 1655 getlocal 2009 opt_send_simple 3008 send 5958 $
  53. trace trace 57 getclassvariable 64 opt_regexpmatch1 65 toregexp 66 setinlinecache

    82 jump 83 newarray 99 tostring 112 concatstrings 115 opt_regexpmatch2 120 expandarray 133 defined 179 setinstancevariable 189 putspecialobject 209 opt_eq 233 checkmatch 234 putstring 234 opt_aref 285 putnil 315 defineclass 336 getinlinecache 402 pop 433 dup 464 setlocal 471 branchunless 491 branchif 518 putself 542 getinstancevariable 550 putobject 667 leave 1355 getconstant 1521 invokesuper 1655 getlocal 2009 opt_send_simple 3008 send 5958
  54. Measure GC time ruby$target:::gc-mark-begin { self->mark = timestamp; } ruby$target:::gc-sweep-begin

    { self->sweep = timestamp; } ruby$target:::gc-mark-end { @mark_time = sum(timestamp - self->mark); } ruby$target:::gc-sweep-end { @sweep_time = sum(timestamp - self->sweep); }
  55. Track Total Time BEGIN { start = timestamp; } END

    { total = timestamp - start; printf("mark time (ns): "); printa("%@d\n", @mark_time); printf("sweep time (ns): "); printa("%@d\n", @sweep_time); printf("total time (ns): %d", total); }
  56. GC Time 76% 5% 19% Mark Sweep Rest mark time

    (ns) 276526868 sweep time (ns) 74943136 program time (ns) 1464656199
  57. D Probes ruby$target:::find-require-entry { req_search = timestamp; } ruby$target:::find-require-return {

    t = timestamp; printf("%d,%d,%s\n", t, t - req_search, copyinstr(arg0)); }
  58. Statistics Total Files 1649 Total Search Time 460096404 Total Time

    1499901879 Mean Time 278846 Min Time 13840 Max Time 1392542 Std Dev 192585 Time in Nanoseconds 30%
  59. Statistics Total Files 1649 Total Search Time 182910579 Total Time

    1141907918 Mean Time 110854 Min Time 1905 Max Time 926160 Std Dev 134429 Time in Nanoseconds 16%
  60. $ time ruby-trunk -Ilib:. -rconfig/environment -e 0 real 0m1.479s user

    0m1.280s sys 0m0.191s $ time ruby-req -Ilib:. -rconfig/environment -e 0 real 0m1.145s user 0m0.972s sys 0m0.169s
  61. rest 25% find file 30% compile 21% sweep 5% mark

    19% mark sweep compile find file rest Combined Graph
  62. rest 39% find file 16% compile 21% sweep 5% mark

    19% mark sweep compile find file rest After #7158