fighting api compatibility on fluentd using "black magic"

71
Fighting API Compatibility On Fluentd Using "Black Magic" Jul 2 2016 in YAPC::Asia Hachioji #yapc8oji Satoshi "Moris" Tagomori (@tagomoris)

Upload: satoshi-tagomori

Post on 09-Jan-2017

5.507 views

Category:

Software


0 download

TRANSCRIPT

Page 1: Fighting API Compatibility On Fluentd Using "Black Magic"

Fighting API Compatibility On Fluentd Using "Black Magic"

Jul 2 2016 in YAPC::Asia Hachioji #yapc8oji

Satoshi "Moris" Tagomori (@tagomoris)

Page 2: Fighting API Compatibility On Fluentd Using "Black Magic"

Satoshi "Moris" Tagomori (@tagomoris)

Fluentd, MessagePack-Ruby, Norikra, ...

Treasure Data, Inc.

Page 3: Fighting API Compatibility On Fluentd Using "Black Magic"
Page 4: Fighting API Compatibility On Fluentd Using "Black Magic"

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

Page 5: Fighting API Compatibility On Fluentd Using "Black Magic"

Fluentd v0.14 Release

Page 6: Fighting API Compatibility On Fluentd Using "Black Magic"

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 7: Fighting API Compatibility On Fluentd Using "Black Magic"

Classes hierarchy (v0.12)

Fluent::Input F::Filter

F::Output

BufferedOutput

ObjectBuffered

TimeSliced Multi

Output F::BufferF::Parser

F::Formatter

3rd party plugins

Page 8: Fighting API Compatibility On Fluentd Using "Black Magic"

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 9: Fighting API Compatibility On Fluentd Using "Black Magic"

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 10: Fighting API Compatibility On Fluentd Using "Black Magic"

Basic Weapons: Class and Mixin in Ruby

Page 11: Fighting API Compatibility On Fluentd Using "Black Magic"

Class and Subclass in Ruby

class A

#bar

class B

#bar

super

B.new.bar

Page 12: Fighting API Compatibility On Fluentd Using "Black Magic"

class A

#bar

class B

#bar

super

B.new.bar

module M

#bar

Introducing Methods by Mixin

Page 13: Fighting API Compatibility On Fluentd Using "Black Magic"

class A

#bar

class B

#bar

super

B.new.bar

module M

#bar

Singleton Class of Ruby

#bar

B.new.singleton_class

Page 14: Fighting API Compatibility On Fluentd Using "Black Magic"

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 15: Fighting API Compatibility On Fluentd Using "Black Magic"

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 16: Fighting API Compatibility On Fluentd Using "Black Magic"

Back to Fluentd code :)

Page 17: Fighting API Compatibility On Fluentd Using "Black Magic"

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 18: Fighting API Compatibility On Fluentd Using "Black Magic"

Fluentd v0.12 Fluent::Output

class Fluent::Output

#emit(tag, es, chain)

MyOutput

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

@buffer

Page 19: Fighting API Compatibility On Fluentd Using "Black Magic"

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 20: Fighting API Compatibility On Fluentd Using "Black Magic"

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 21: Fighting API Compatibility On Fluentd Using "Black Magic"

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 22: Fighting API Compatibility On Fluentd Using "Black Magic"

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 23: Fighting API Compatibility On Fluentd Using "Black Magic"

Fluentd v0.12 Fluent::BufferedOutput

class Fluent::Outputclass BufferedOutputMyOutput

@buffer calls #write in OutputThread

@buffer

chunk#write(chunk)

OutputThread

#pop

Page 24: Fighting API Compatibility On Fluentd Using "Black Magic"

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 25: Fighting API Compatibility On Fluentd Using "Black Magic"

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 26: Fighting API Compatibility On Fluentd Using "Black Magic"

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 27: Fighting API Compatibility On Fluentd Using "Black Magic"

How can we solve this problem?

Page 28: Fighting API Compatibility On Fluentd Using "Black Magic"

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

Page 29: Fighting API Compatibility On Fluentd Using "Black Magic"

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 30: Fighting API Compatibility On Fluentd Using "Black Magic"

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 31: Fighting API Compatibility On Fluentd Using "Black Magic"

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 32: Fighting API Compatibility On Fluentd Using "Black Magic"

Page 33: Fighting API Compatibility On Fluentd Using "Black Magic"

How about existing v0.12 plugins?

Page 34: Fighting API Compatibility On Fluentd Using "Black Magic"

Requirement:

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

Page 35: Fighting API Compatibility On Fluentd Using "Black Magic"

• 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 36: Fighting API Compatibility On Fluentd Using "Black Magic"

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 37: Fighting API Compatibility On Fluentd Using "Black Magic"

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 38: Fighting API Compatibility On Fluentd Using "Black Magic"

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 39: Fighting API Compatibility On Fluentd Using "Black Magic"
Page 40: Fighting API Compatibility On Fluentd Using "Black Magic"

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 41: Fighting API Compatibility On Fluentd Using "Black Magic"

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 42: Fighting API Compatibility On Fluentd Using "Black Magic"

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 43: Fighting API Compatibility On Fluentd Using "Black Magic"

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 44: Fighting API Compatibility On Fluentd Using "Black Magic"

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 45: Fighting API Compatibility On Fluentd Using "Black Magic"

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 46: Fighting API Compatibility On Fluentd Using "Black Magic"

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 47: Fighting API Compatibility On Fluentd Using "Black Magic"

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 48: Fighting API Compatibility On Fluentd Using "Black Magic"

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: Fighting API Compatibility On Fluentd Using "Black Magic"

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 50: Fighting API Compatibility On Fluentd Using "Black Magic"

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 51: Fighting API Compatibility On Fluentd Using "Black Magic"

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 52: Fighting API Compatibility On Fluentd Using "Black Magic"

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 53: Fighting API Compatibility On Fluentd Using "Black Magic"

Fluent::Plugin::OutputMyOutput@buffer

#write

C::BufferedOutput

#write(chunk) flush thread

Thinking about "chunk" instance ...

#write(chunk)

BufferedChunkMixin

plugin.extend BufferedChunkMixin in #configure

Page 54: Fighting API Compatibility On Fluentd Using "Black Magic"

Similar hacks for TimeSlicedOutput and ObjectBufferedOutput ...

Page 55: Fighting API Compatibility On Fluentd Using "Black Magic"

Controlling Plugin Lifecycle

Page 56: Fighting API Compatibility On Fluentd Using "Black Magic"

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 57: Fighting API Compatibility On Fluentd Using "Black Magic"

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 58: Fighting API Compatibility On Fluentd Using "Black Magic"

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 59: Fighting API Compatibility On Fluentd Using "Black Magic"

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 60: Fighting API Compatibility On Fluentd Using "Black Magic"

More Weapon! Module#prepend

Page 61: Fighting API Compatibility On Fluentd Using "Black Magic"

class A

#bar

class B

#bar

super

B.new.bar

Wrapping Methods on a Class (1)

B.new.singleton_class

#bar

Page 62: Fighting API Compatibility On Fluentd Using "Black Magic"

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 63: Fighting API Compatibility On Fluentd Using "Black Magic"

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 64: Fighting API Compatibility On Fluentd Using "Black Magic"

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

Page 65: Fighting API Compatibility On Fluentd Using "Black Magic"

class A

#bar

class B

#bar

supermodule M

Another Study: How To Wrap Singleton Method?

B.new.singleton_class

#bar

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

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

#bar

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

Page 66: Fighting API Compatibility On Fluentd Using "Black Magic"

What We Want To Do:

Fluent::Plugin::Base

#shutdown

F::P::Output

super

#shutdown?

#shutdown

F::C::Output

#shutdown

MyOutput

#shutdown

THIS ONE !!!

Page 67: Fighting API Compatibility On Fluentd Using "Black Magic"
Page 68: Fighting API Compatibility On Fluentd Using "Black Magic"

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 69: Fighting API Compatibility On Fluentd Using "Black Magic"
Page 70: Fighting API Compatibility On Fluentd Using "Black Magic"

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

Page 71: Fighting API Compatibility On Fluentd Using "Black Magic"

Do Whatever You Can For Users!

It Makes Everyone Happier

... Except for Maintainers :(