Top Banner
Modern Black Mages Fighting in the Real World Sep 9, 2016 in RubyKaigi 2016 @tagomoris Satoshi "Moris" Tagomori
81

Modern Black Mages Fighting in the Real World

Apr 16, 2017

Download

Software

Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Modern Black Mages Fighting in the Real World

Modern Black Mages Fighting in the Real World

Sep 9, 2016 in RubyKaigi 2016

@tagomoris Satoshi "Moris" Tagomori

Page 2: Modern Black Mages Fighting in the Real World

Satoshi "Moris" Tagomori (@tagomoris)

Fluentd, MessagePack-Ruby, Norikra, ...

Treasure Data, Inc.

Page 3: Modern Black Mages Fighting in the Real World

https://github.com/tagomoris/msgpack-inspect

Page 4: Modern Black Mages Fighting in the Real World
Page 5: Modern Black Mages Fighting in the Real World

http://docs.fluentd.org/articles/logohttp://www.fluentd.org/

Page 6: Modern Black Mages Fighting in the Real World

Fluentd

• What is Fluentd? • Open Source Log Collector • Pluggable, Reliable, Less resource usage, Ease to use

• Versions of Fluentd • v0.12: stable versions (2014/12 - Now) • v0.14: versions for next stable (2016/05 - Now)

Page 7: Modern Black Mages Fighting in the Real World

http://docs.fluentd.org/articles/logo

Page 8: Modern Black Mages Fighting in the Real World

What's about this session?

• Introduce some patterns of "Black Magic"s (a.k.a. meta programming) in Ruby

• Show you some PRAGMATIC use of Black Magics

Page 9: Modern Black Mages Fighting in the Real World

Fluentd v0.14 Release

Page 10: Modern Black Mages Fighting in the Real World

Fluentd v0.14 API Update

• Everything changed :)

• Plugin namespace • before: Fluent::* (Top level classes even for plugins!) • after: Fluent::Plugin::*

• Plugin base class for common methods • Inconsistent Output plugin hierarchy • Plugin must call `super` in common methods

http://www.slideshare.net/tagomoris/fluentd-v014-plugin-api-details

Page 11: Modern Black Mages Fighting in the Real World

Classes hierarchy (v0.12)

Fluent::Input F::Filter

F::Output

BufferedOutput

ObjectBuffered

TimeSliced Multi

Output F::BufferF::Parser

F::Formatter

3rd party plugins

Page 12: Modern Black Mages Fighting in the Real World

Classes hierarchy (v0.14)

F::P::Input F::P::Filter F::P::Output

Fluent::Plugin::Base

F::P::BufferF::P::Parser

F::P::FormatterF::P::Storage

both ofbuffered/non-buffered

F::P::BareOutput(not for 3rd party

plugins)

F::P::MultiOutput

copyroundrobin

Page 13: Modern Black Mages Fighting in the Real World

diff v0.12 v0.14

F::P::Output

Fluent::Plugin::Base

both ofbuffered/non-buffered

F::P::BareOutput(not for 3rd party

plugins)

F::P::MultiOutput

copyroundrobin

F::Output

BufferedOutput

ObjectBuffered

TimeSliced Multi

Output

Super classes byhow to buffer data

All output pluginsare just "Output"

Page 14: Modern Black Mages Fighting in the Real World

Basic Black Magics: Class and Mixin in Ruby

Page 15: Modern Black Mages Fighting in the Real World

Class and Subclass in Ruby

class A

#bar

class B

#bar

super

B.new.bar

Page 16: Modern Black Mages Fighting in the Real World

class A

#bar

class B

#bar

super

B.new.bar

module M

#bar

Introducing Methods by Mixin

Page 17: Modern Black Mages Fighting in the Real World

class A

#bar

class B

#bar

super

B.new.bar

module M

#bar

Singleton Class of Ruby

#bar

B.new.singleton_class

Page 18: Modern Black Mages Fighting in the Real World

class A

#bar

class B

#bar

super

b=B.new b.singleton_class.include M2 b.bar

module M

#bar

Adding Methods on An Instance (1)

B.new.singleton_class

#bar

M2

#bar

Page 19: Modern Black Mages Fighting in the Real World

class A

#bar

class B

#bar

super

b=B.new b.extend M2 b.bar

module M

#bar

Adding Methods on An Instance (2)

B.new.singleton_class

#bar

M2

#bar

Page 20: Modern Black Mages Fighting in the Real World

Back to Fluentd code :)

Page 21: Modern Black Mages Fighting in the Real World

diff v0.12 v0.14

F::P::Output

Fluent::Plugin::Base

both ofbuffered/non-buffered

F::P::BareOutput(not for 3rd party

plugins)

F::P::MultiOutput

copyroundrobin

F::Output

BufferedOutput

ObjectBuffered

TimeSliced Multi

Output

Super classes byhow to buffer data

All output pluginsare just "Output"

Page 22: Modern Black Mages Fighting in the Real World

Fluentd v0.12 Fluent::Output

class Fluent::Output

#emit(tag, es, chain)

MyOutput

Engine calls plugin.emit(tag, es, chain)

@buffer

Page 23: Modern Black Mages Fighting in the Real World

Fluentd v0.12 Fluent::BufferedOutput (1)

class Fluent::Outputclass BufferedOutput

#emit(tag, es, chain, key)

MyOutput

#emit(tag, es, chain)

super(tag, es, chain, any_key)

Engine calls plugin.emit(tag, es, chain)

@buffer

#emit(key, data, chain)

#format(tag,time,record)

#format_stream(tag,es)

Page 24: Modern Black Mages Fighting in the Real World

Fluentd v0.12 Fluent::BufferedOutput (2)

class Fluent::Outputclass BufferedOutput

#emit(tag, es, chain, key)

MyOutput

#emit(tag, es, chain)

super(tag, es, chain, any_key)

Engine calls plugin.emit(tag, es, chain)

@buffer

#emit(key, data, chain)

#format_stream(tag,es)

#format_stream(tag,es)

#format(tag,time,record)

Page 25: Modern Black Mages Fighting in the Real World

Fluentd v0.12 Fluent::TimeSlicedOutput

class Fluent::Outputclass BufferedOutput

#emit(tag, es, chain, key)

MyOutput#emit(tag, es, chain)

Engine calls plugin.emit(tag, es, chain)

@buffer

#emit(key, data, chain)

#emit(tag, es, chain) class TimeSlicedOutput

#format(tag,time,record)

Page 26: Modern Black Mages Fighting in the Real World

Fluentd v0.12 Fluent::ObjectBufferedOutput

class Fluent::Outputclass BufferedOutput

#emit(tag, es, chain, key)

MyOutput#emit(tag, es, chain)

Engine calls plugin.emit(tag, es, chain)

@buffer

#emit(key, data, chain)

#emit(tag, es, chain) class ObjectBufferedOutput

Page 27: Modern Black Mages Fighting in the Real World

Fluentd v0.12 Fluent::BufferedOutput

class Fluent::Outputclass BufferedOutputMyOutput

@buffer calls #write in OutputThread

@buffer

chunk#write(chunk)

OutputThread

#pop

Page 28: Modern Black Mages Fighting in the Real World

Fluentd v0.12 Fluent::TimeSlicedOutput

class Fluent::Outputclass BufferedOutput

@buffer

MyOutput

class TimeSlicedOutput

OutputThread

#write(chunk)

@buffer calls #write in OutputThread

#write calls chunk.keychunk

#pop

Page 29: Modern Black Mages Fighting in the Real World

Fluentd v0.12 Fluent::ObjectBufferedOutput

class Fluent::Outputclass BufferedOutput

@buffer

MyOutput

class ObjectBufferedOutput

OutputThread

#write(chunk)

#write(chunk)

#write_object(chunk_key, chunk)

@buffer calls #write in OutputThread

chunk#pop

Page 30: Modern Black Mages Fighting in the Real World

Fluentd v0.12 API Problems

• Entry point method is implemented by Plugin subclasses • Fluentd core cannot add any processes

• counting input events • hook arguments/return values to update API

• Fluentd core didn't show fixed API

• Plugins have different call stacks • It's not clear what should be implemented for authors • It's not clear what interfaces are supported for

arguments/return values

Page 31: Modern Black Mages Fighting in the Real World

How can we solve this problem?

Page 32: Modern Black Mages Fighting in the Real World

Fluent::Plugin::Output (v0.14)

Page 33: Modern Black Mages Fighting in the Real World

Fluentd v0.14 Fluent::Plugin::Output

class Outputclass MyOutput

#process(tag, es)

Engine calls plugin.emit_events(tag, es)

@buffer

#write

#emit_events(tag, es)

#format(tag, time, record)

#write(chunk)

#try_write(chunk)

#emit_sync(tag, es)

#emit_buffered(tag, es)

Page 34: Modern Black Mages Fighting in the Real World

Fluentd v0.14 Fluent::Plugin::Output

class Outputclass MyOutput

Output calls plugin.write (or try_write)

@buffer

chunk

#write(chunk)

#try_write(chunk)

flush thread

#process(tag, es)

#format(tag, time, record)

Page 35: Modern Black Mages Fighting in the Real World

Fluentd v0.14 Design Policy

• Separate entry points from implementations • Methods in superclass control everything

• Do NOT override these methods! • Methods in subclass do things only for themselves

• not for data flow, control flow nor others

• Plugins have simple/straightforward call stack • Easy to understand/maintain

Page 36: Modern Black Mages Fighting in the Real World

Page 37: Modern Black Mages Fighting in the Real World

How about existing v0.12 plugins?

Page 38: Modern Black Mages Fighting in the Real World

Requirement:

(Almost) All Existing Plugins SHOULD Work Well WITHOUT ANY MODIFICATION

Page 39: Modern Black Mages Fighting in the Real World

• Fluent::Compat namespace for compatibility layer

v0.14 Plugins & Compat Layer

F::P::Output

F::P::Base

v0.14 PluginsFluent::

Compat::Output

F::C::BufferedOutput

F::C::TimeSliced

Output

F::C::ObjectBuffered

Output

Fluent::OutputF::

BufferedOutput

F::TimeSliced

Output

F::ObjectBuffered

Output

v0.12 Plugins

Page 40: Modern Black Mages Fighting in the Real World

Double Decker Compat Layer?

• Existing plugins inherits Fluent::Output or others • No more codes in Fluent top level :-(

• Separate code into Fluent::Compat • and import it into Fluent top level

Page 41: Modern Black Mages Fighting in the Real World

Fluentd v0.14 Fluent::Plugin::Output

class Outputclass MyOutput

#process(tag, es)

Engine calls plugin.emit_events(tag, es)

@buffer

#write

#emit_events(tag, es)

#format(tag, time, record)

#write(chunk)

#try_write(chunk)

#emit_sync(tag, es)

#emit_buffered(tag, es)

Page 42: Modern Black Mages Fighting in the Real World

Fluentd v0.12 Fluent::BufferedOutput (2)

class Fluent::Outputclass BufferedOutput

#emit(tag, es, chain, key)

MyOutput

#emit(tag, es, chain)

super(tag, es, chain, any_key)

Engine calls plugin.emit(tag, es, chain)

@buffer

#emit(key, data, chain)

#format_stream(tag,es)

#format_stream(tag,es)

#format(tag,time,record)

Page 43: Modern Black Mages Fighting in the Real World
Page 44: Modern Black Mages Fighting in the Real World

Fluent::Plugin::Outputclass MyOutput@buffer

#write

#emit_events(tag, es)

v0.12 Plugins via Compat Layer: Best case (virtual)

Compat::BufferedOutput

#emit_buffered(tag, es)

#format(tag, time, record)

#format_stream(tag,es) #handle_stream_*

#handle_stream_simple

#emit(tag, es, chain, key) #emit(tag, es, chain, key)

#format_stream(tag,es)

#write(chunk) flush thread

Page 45: Modern Black Mages Fighting in the Real World

Fluent::Plugin::Outputclass MyOutput@buffer

#write

#emit_events(tag, es)

v0.12 Plugins via Compat Layer: Best case (real)

Compat::BufferedOutput

#emit_buffered(tag, es)

#format(tag, time, record)

#format_stream(tag,es) #handle_stream_*

#handle_stream_simple

#emit(tag, es, chain, key) #emit(tag, es, chain, key)

#format_stream(tag,es)

#write(chunk) flush thread

Page 46: Modern Black Mages Fighting in the Real World

Fluent::Plugin::Outputclass MyOutput@buffer

#write

#emit_events(tag, es)

When plugin overrides #format_stream

Compat::BufferedOutput

#emit_buffered(tag, es)

#format(tag, time, record)

#format_stream(tag,es) #handle_stream_*

#handle_stream_simple

#emit(tag, es, chain, key) #emit(tag, es, chain, key)

#format_stream(tag,es)

#write(chunk) flush thread

Page 47: Modern Black Mages Fighting in the Real World

Fluent::Plugin::Outputclass MyOutput@buffer

#write

#emit_events(tag, es)

When plugin overrides #format_stream

Compat::BufferedOutput

#emit_buffered(tag, es)

#format(tag, time, record)

#format_stream(tag,es) #handle_stream_*

#handle_stream_simple

#emit(tag, es, chain, key) #emit(tag, es, chain, key)

#format_stream(tag,es)

#write(chunk) flush thread

default implementation for calling "super"

Page 48: Modern Black Mages Fighting in the Real World

Fluent::Plugin::Outputclass MyOutput@buffer

#write

#emit_events(tag, es)

When plugin overrides #emit

Compat::BufferedOutput

#emit_buffered(tag, es)

#format(tag, time, record)

#format_stream(tag,es) #handle_stream_*

#handle_stream_simple

#emit(tag, es, chain) #emit(tag, es, chain, key)

#format_stream(tag,es)

#write(chunk) flush thread

Page 49: Modern Black Mages Fighting in the Real World

Fluent::Plugin::Outputclass MyOutput@buffer

#write

#emit_events(tag, es)

Compat::BufferedOutput

#emit_buffered(tag, es)

#format(tag, time, record)

#format_stream(tag,es) #handle_stream_*

#handle_stream_simple

#emit(tag, es, chain) #emit(tag, es, chain, key)

#format_stream(tag,es)

#write(chunk) flush thread

When plugin overrides #emit

Page 50: Modern Black Mages Fighting in the Real World

Fluent::Plugin::Outputclass MyOutput@buffer

#write

#emit_events(tag, es)

Compat::BufferedOutput

#emit_buffered(tag, es)

#format(tag, time, record)

#format_stream(tag,es) #handle_stream_*

#handle_stream_simple

#emit(tag, es, chain) #emit(tag, es, chain, key)

#format_stream(tag,es)

#write(chunk) flush thread

This call doesn't happen, in fact

#emit doesn't return values!

When plugin overrides #emit

Page 51: Modern Black Mages Fighting in the Real World

Fluent::Plugin::Outputclass MyOutput@buffer

#write

#emit_events(tag, es)

When plugin overrides #emit

Compat::BufferedOutput

#emit_buffered(tag, es)

#format(tag, time, record)

#format_stream(tag,es) #handle_stream_*

#handle_stream_simple

#emit(tag, es, chain) #emit(tag, es, chain, key)

#format_stream(tag,es)

#write(chunk) flush thread

#emit calls @buffer.emit → NoMethodError !

Page 52: Modern Black Mages Fighting in the Real World

Fluent::Plugin::Outputclass MyOutput@buffer

#write

#emit_events(tag, es)

When plugin overrides #emit

Compat::BufferedOutput

#emit_buffered(tag, es)

#format(tag, time, record)

#format_stream(tag,es) #handle_stream_*

#handle_stream_simple

#emit(tag, es, chain) #emit(tag, es, chain, key)

#format_stream(tag,es)

#write(chunk) flush thread

Page 53: Modern Black Mages Fighting in the Real World

Fluent::Plugin::Outputclass MyOutput@buffer

#write

#emit_events(tag, es)

When plugin overrides #emit

Compat::BufferedOutput

#emit_buffered(tag, es)

#format(tag, time, record)

#format_stream(tag,es) #handle_stream_*

#handle_stream_simple

#emit(tag, es, chain) #emit(tag, es, chain, key)

#format_stream(tag,es)

#write(chunk) flush thread

#emit

1. #emit calls @buffer.emit with data to be written in buffer

0. plugin calls @buffer.extend to add #emit

2. @buffer.emit stores arguments into plugin's attribute

3. get stored data

4. call @buffer.write with data

Page 54: Modern Black Mages Fighting in the Real World

Fluent::Plugin::Outputclass MyOutput@buffer

#write

#emit_events(tag, es)

When plugin overrides #emit

Compat::BufferedOutput

#emit_buffered(tag, es)

#format(tag, time, record)

#format_stream(tag,es) #handle_stream_*

#handle_stream_simple

#emit(tag, es, chain) #emit(tag, es, chain, key)

#format_stream(tag,es)

#write(chunk) flush thread

Page 55: Modern Black Mages Fighting in the Real World

Fluent::Plugin::Outputclass MyOutput@buffer

#write

#emit_events(tag, es)

Thinking about "chunk" instance ...

Compat::BufferedOutput

#emit_buffered(tag, es)

#format(tag, time, record)

#format_stream(tag,es) #handle_stream_*

#handle_stream_simple

#emit(tag, es, chain) #emit(tag, es, chain, key)

#format_stream(tag,es)

#write(chunk) flush thread

#write may call "chunk.key", but v0.14 chunk doesn't have #key !

Page 56: Modern Black Mages Fighting in the Real World

Fluent::Plugin::Outputclass MyOutput@buffer

#write

Compat::BufferedOutput

#write(chunk) flush thread

"chunk" has #metadata, and values of #key can be created via #metadata

Let's "chunk.extend" !

Where to do so?

?

Thinking about "chunk" instance ...

Page 57: Modern Black Mages Fighting in the Real World

Fluent::Plugin::OutputMyOutput@buffer

#write

C::BufferedOutput

#write(chunk) flush thread

Thinking about "chunk" instance ...

#write(chunk)

BufferedChunkMixin

plugin.extend BufferedChunkMixin in #configure

Page 58: Modern Black Mages Fighting in the Real World

Similar hacks for TimeSlicedOutput and ObjectBufferedOutput ...

Page 59: Modern Black Mages Fighting in the Real World

Controlling Plugin Lifecycle

Page 60: Modern Black Mages Fighting in the Real World

Plugin Lifecycle Updated

Methods(v0.12) • #configure • #start

• #before_shutdown • #shutdown

v0.12 Plugins often doesn't call "super"!

Methods(v0.14) • #configure • #start • #stop • #before_shutdown • #shutdown • #after_shutdown • #close • #terminate

In v0.14, these methods MUST call "super"

• #configured? • #started? • #stopped? • #before_shutdown? • #shutdown? • #after_shutdown? • #closed? • #terminated?

Page 61: Modern Black Mages Fighting in the Real World

For Example: shutdown compat plugins

Fluent::Plugin::Base

#shutdown

F::P::Output

super

#shutdown?

#shutdown

F::C::Output

#shutdown

MyOutput

#shutdown

It doesn't call "super"! We want to call this...

Page 62: Modern Black Mages Fighting in the Real World

What We Want To Do:

Fluent::Plugin::Base

#shutdown

F::P::Output

super

#shutdown?

#shutdown

F::C::Output

#shutdown

MyOutput

#shutdown

1. call #shutdown anyway

0. Fluentd core calls #shutdown

2. call #shutdown? to check "super" is called or not

3. call #shutdown of superclass forcedly!

Page 63: Modern Black Mages Fighting in the Real World

What We Want To Do:

Fluent::Plugin::Base

#shutdown

F::P::Output

super

#shutdown?

#shutdown

F::C::Output

#shutdown

MyOutput

#shutdown

How to make this point?

Page 64: Modern Black Mages Fighting in the Real World

One More Magic! Module#prepend

Page 65: Modern Black Mages Fighting in the Real World

class A

#bar

class B

#bar

super

B.new.bar

Wrapping Methods on a Class (1)

B.new.singleton_class

#bar

Page 66: Modern Black Mages Fighting in the Real World

class A

#bar

class B

#bar

super

B.new.bar

module M

Wrapping Methods on a Class (2)

B.new.singleton_class

#bar

#bar

Using extend is powerful, but it should be done for all instances

How about wrapping methods for all instances of the class?

Page 67: Modern Black Mages Fighting in the Real World

class A

#bar

class B

#bar

super

module M;def bar;super;end;end B.prepend M B.new.bar

module M

Wrapping Methods on a Class (3): Module#prepend

B.new.singleton_class

#bar

#bar

module M wraps B, and M#bar is called at first

Page 68: Modern Black Mages Fighting in the Real World

What We Want To Do:

Fluent::Plugin::Base

#shutdown

F::P::Output

super

#shutdown?

#shutdown

F::C::Output

#shutdown

MyOutput

#shutdown

THIS ONE !!!

Page 69: Modern Black Mages Fighting in the Real World
Page 70: Modern Black Mages Fighting in the Real World

What We Got :-)

Fluent::Plugin::Base

#shutdown

F::P::Output

super

#shutdown?

#shutdown

F::C::Output

#shutdown

MyOutput

#shutdown

1. call #shutdown anyway

0. prepend CallSuperMixin at first

2. call #shutdown? to check "super" is called or not

3. if not, get method of superclass, bind self with it, then call it

Thank you @unak -san!

Page 71: Modern Black Mages Fighting in the Real World

Beating Test Code

Page 72: Modern Black Mages Fighting in the Real World

Testing: Capturing return values by Test Driver

OutputMyOutput@buffer

#write

#emit_events

#format

#emit_buffered

Output Plugin Test Driver

Create plugin instances

Feed test data into plugin

Page 73: Modern Black Mages Fighting in the Real World

Testing: Capturing return values by Test Driver

OutputMyOutput@buffer

#write

#emit_events

#format

#emit_buffered

We want to assert this return value!

Output Plugin Test Driver Feed test data into plugin

Page 74: Modern Black Mages Fighting in the Real World

Using #prepend to capture return values

OutputMyOutput@buffer

#write

#emit_events

#format

#emit_buffered

Output Plugin Test Driver Feed test data into plugin

moduleM

#format

Store return value of "super"

Page 75: Modern Black Mages Fighting in the Real World

Using #prepend ... doesn't work :-(

OutputMyOutput@buffer

#write

#emit_events

#format

#emit_buffered

Output Plugin Test Driver Feed test data into plugin

#format

Test code sometimes overwrites methods for many reasons :P

singletonclass

#format

😱

moduleM

Page 76: Modern Black Mages Fighting in the Real World

One Another Magic: Stronger Than Anything

Page 77: Modern Black Mages Fighting in the Real World

class A

#bar

class B

#bar

super

b=B.new b.singleton_class.module_eval{define_method(:bar){"1"}} b.bar

Another Study: How To Wrap Singleton Method?

B.new.singleton_class

#bar

module M

#bar

Page 78: Modern Black Mages Fighting in the Real World

class A

#bar

class B

#bar

supermodule P

Another Study: How To Wrap Singleton Method?

B.new.singleton_class

#bar

b=B.new b.singleton_class.module_eval{define_method(:bar){"1"}} b.singleton_class.prepend P b.bar

#bar

module M

#bar

Singleton class is a class, so it can be prepended :)

It's actually done in Test Driver implementation...

Page 79: Modern Black Mages Fighting in the Real World

Using #prepend on singleton_class: Yay!

OutputMyOutput@buffer

#write

#emit_events

#format

#emit_buffered

Output Plugin Test Driver Feed test data into plugin

#format

singletonclass

#format😃

moduleM

Prepending modules on singleton_class overrides everything!

Page 80: Modern Black Mages Fighting in the Real World

IS BUILT ON A TOP OF BUNCH OF BLACK MAGICS :P

Page 81: Modern Black Mages Fighting in the Real World

Do Whatever You Can For Users!

It Makes Everyone Happier!

... Except for Maintainers :(