-
Notifications
You must be signed in to change notification settings - Fork 2.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
contrib: skeleton app structure & Dogstatsd nsqd addon #909
base: master
Are you sure you want to change the base?
contrib: skeleton app structure & Dogstatsd nsqd addon #909
Conversation
* contrib package with nsqd contrib * skeleton interfaces * skeleton datadogd contrib package * explicitly adds opts to nsqd
nsqd/options.go
Outdated
|
||
// contrib | ||
// TODO cleanly extend this in the the contrib app | ||
DogStatsdAddress string `flag:"dogstatsd-address"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure how do do this cleanest because brand new to go. Thinking of NSQDContribOptions that embeds NSQD Options, contrib options will have an option struct for each contrib addon. The goal is to not touch nsqd core when create ing new addons.
* datadog client in contrib * statsd topic/channel stats
I was thinking these things would be called something like "optional modules". Though "contrib" isn't too bad. My idea for command-line flags, and the config file, is to pass through a single multi-value (array) flag, which would be called something like
The main "module" or "contrib" system would only initialize a "module" if there are any options for that module. It passes the module just that module's options as an array like This way, everything about a module's options is only in the module, and nsqd flag and config file handling is unaffected except for a single Just my humble ideas :) |
contrib/nsqd.go
Outdated
|
||
// Starts all addons that are active | ||
func (as *NSQDAddons) Start() { | ||
logger.Println("Starting All addons") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should somehow go through NSQD.logf()
too.
|
||
// would like to expose logf to the contrib modules so that contrib can share the | ||
// configuration end user specifies on CLI | ||
func (n *NSQD) Logf(level lg.LogLevel, f string, args ...interface{}) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A possibly cleaner alternative is to pass the logf
bound-method reference to NewNSQDAddons()
or NSQDAddons.Start()
, which could take a internal.lg.AppLogFunc
type. For example see
Line 231 in 844c6a0
protocol.TCPServer(n.tcpListener, tcpServer, n.logf) |
nsq/internal/protocol/tcp_server.go
Line 15 in 844c6a0
func TCPServer(listener net.Listener, handler TCPHandler, logf lg.AppLogFunc) { |
@ploxiln with the |
* exposes single new option on nsqd * pushes option handling down to each individual optional module
Yeah, it won't be convenient to show the module options in |
@ploxiln awesome! ty |
@mreiferson - have any opinions on this, the structure it is developing? |
apps/nsqd/nsqd.go
Outdated
@@ -220,6 +225,7 @@ func (p *program) Start() error { | |||
cfg.Validate() | |||
|
|||
options.Resolve(opts, flagSet, cfg) | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
best not to add unrelated whitespace changes here
nsqd/nsqd.go
Outdated
@@ -265,6 +269,10 @@ func (n *NSQD) Main() { | |||
} | |||
} | |||
|
|||
func (n *NSQD) RegisterAddon(addonFn func()) { | |||
n.waitGroup.Wrap(func() { addonFn() }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could this possibly just be written as n.waitGroup.Wrap(addonFn)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I might name RegisterAddon()
slightly differently, like AddModuleGoroutine()
.
Here's another idea - to avoid needing to add the new public ExitChan()
method to NSQD, pass the exitChan
to the coroutine function - they'll all need it to integrate with the wait group anyway.
(That'll require going back to creating a trivial wrapper function like n.waitGroup.Wrap(func() { modFn(&n.exitChan) })
)
* stubbed out more opts related tests * removed modified whitespace from nsqd.go
apps/nsqd/nsqd.go
Outdated
@@ -232,6 +238,10 @@ func (p *program) Start() error { | |||
} | |||
nsqd.Main() | |||
|
|||
// hook into addons | |||
addons := contrib.NewEnabledNSQDAddons(opts.ModOpt, nsqd, nsqd.Logf) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Eh, well, not much point in passing the public method Logf
separately.
Imagine if something like addons.Start()
was called from within NSQD.Main()
, then it could pass n.logf
as a parameter to that, and avoid adding the new public method. But I understand that won't be very convenient or clean, so I don't have a better idea at the moment ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I moved the addon initialization to Main()
after your initial comment and passed lg.AppLogFunc
to the Addons, but then there was a circular dependency :p . The only thing I could think of was to add an interface to represent NSQD? Can you think of any easier ways?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, that makes sense ... nope, can't think of a better way at the moment.
These "contrib addons" are going to want to get information from the NSQD, and maybe some will want to make changes, e.g. to the list of nsqlookupd, or auth info for new connections. These various purposes will need some functions to be made public, which we'll consider on a case-by-case basis. An interface of these public functions which contrib modules can use sounds like it could be a pretty good idea.
contrib/nsqd.go
Outdated
|
||
// ask each addon if it should be initialize | ||
dogStats := NewNSQDDogStatsd(contribOpts, n, logf) | ||
if dogStats.Enabled() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be better to only call NewNSQDDogStatsd()
if there are any contribOpts that start with "dogstatsd=" - that way, you can be sure that any "contrib modules" which you did not pass options to did not run anything, without looking at their code.
So this function would just need a little table of name strings and init functions to loop over.
Sorry I'm late following up on this — I feel like we should investigate how we could structure this using https://golang.org/pkg/plugin/. It would be linux-only, but it would allow us to define some sort of API in |
Eh, I think it's a bit early to use Plugin |
contrib/nsqd.go
Outdated
|
||
// ask each addon if it should be initialize | ||
dogStats := NewNSQDDogStatsd(contribOpts, n) | ||
if dogStats.Enabled() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(this comment copied to a new one because the original was hidden by github)
I think it would be better to only call NewNSQDDogStatsd()
if there are any contribOpts that start with "dogstatsd=" - that way, you can be sure that any "contrib modules" which you did not pass options to did not run anything, without looking at their code.
So this function would just need a little table of name strings and init functions to loop over.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this a flexible requirement? Is there ever a case where a contrib module
flag could be specified
(ie dogstatsd=prefix) but not qualify as enabled? I feel like since the
flags are encapsulated in specific contrib module, delegating to that
module may be the surest way to determine if a module is enabled based on
the flags? What do you think? What's the table optimizing for? If contrive
have no side effects, they should just be initialized, check if enabled and
if not, shouldn't have any references after the newenablednsqaddons returns
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, I don't think it's a strict requirement. Just that it makes some sense to put this code which filters the mod-opts in one place, instead of in each module.
This also enables better "invalid option" checking - if the common code ensures that every option is for a module it knows of, and every module ensures that it understands every option it gets.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good, will update
I just re-added a comment about this approach. But you should probably not actually work on it if @mreiferson is against this. I am sorry if I've led you on a fruitless path, but in my defense, I've tried to be clear that I was only giving my opinion 😬 |
Hah! I guess my position is that, unless it's truly going to be an opt-in plugin model, then why jump through all the hoops with internal code to structure it like that? I guess the argument would be that adopting a flag API like what we've got here would allow us to migrate to a plugin model in the future? |
sorry obviously very new to nsq and am new to go too:
- would the plugin required end user to compile with plugin flag for each
plugin they wanted enabled?
- could you imagine nsq providing a binary with all plugins enabled?
- I just read about plugins for the first time last night, would it be
possible to view it as an implementation detail (Was this your last point?)
and change once a a clear pattern for how plugins are likely to work?
- is the main concern still supporting paid/proprietary services/protocols in
nsq core?
Thank you
Danny
…On Sunday, July 23, 2017, Matt Reiferson ***@***.***> wrote:
Hah!
I guess my position is that, unless it's truly going to be an opt-in
plugin model, then why jump through all the hoops with internal code to
structure it like that? I guess the argument would be that adopting a flag
API like what we've got here would allow us to migrate to a plugin model in
the future?
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
<#909 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AATpqxjGm3dQaMLaXMfME1shoutyBDy3ks5sQ7JmgaJpZM4N-afW>
.
|
I can see a path forward to plugins, from this model. nsqd would need to look in a directory (probably from a flag option) for plugin files named e.g. The point of plugins is so that they can be compiled separately from nsqd, from code in separate repositories, and then used with the released build of nsqd (I think). This repo might itself include some nsqd plugins, built along with all the apps by I don't think "paid/priority" is a thing we're thinking about. Just about trying to enable people to do what they want, without "muddying up" the core code, or needed to get agreement from all core contributors. It's really just another way to organize code to optimize for maintenance and flexibility. |
@ploxiln Could you possibly take another look? I added the initializer table to only initialize addons if one of their options are passed. |
contrib/nsqd.go
Outdated
var hasOpt bool | ||
|
||
initializers := map[string]initializer{ | ||
"dogstatsd": NewNSQDDogStatsd, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mreiferson if we used https://golang.org/pkg/plugin/ that functionality would go roughly here. We would scan for plugin files in some dir, open them and try to get two or three functions with standard names out.
The fact that the dogstatsd module is in the same "package" as this module loader makes it not match that pattern exactly - it has a unique init function name - but it's pretty close.
I have a couple minor stylistic quibbles, but I like the structure. @mreiferson are you somewhat convinced yet ;) |
contrib/nsqd.go
Outdated
|
||
for _, opt := range contribOpts { | ||
if strings.Contains(opt, k) { | ||
hasOpt = true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This match could be more exact.
This loop could append to a new array of opts (instead of breaking out), and pass that new array to the initializer function. Then, if the module initializer does not recognize any of the opts, it can warn or error about that (because it would not have to handle any opts not intended for it).
@ploxiln updated to filter valid options (and only pass those to addon initializer), and user more accurate option matching :) |
nice work! |
I'll take another look real soon, was just a bit busy at work ... |
Thank you!
…On Friday, August 18, 2017, Pierce Lopez ***@***.***> wrote:
I'll take another look real soon, was just a bit busy at work ...
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
<#909 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AATpq4l1KmPfsQT8PgPlccmxBMt9ABHGks5sZkVbgaJpZM4N-afW>
.
|
@mreiferson I think I can clean this up a bit, in terms of API, before the next release, and I think I can work on transitioning this to use go plugins (though they are still linux-only in go-1.9 I think). Thoughts? @dm03514 can you squash these commits into one? |
actually, everyone just wait for a few days, I'll open another PR (which includes and adds to this one) |
WAITING 😳 |
Is there any work I can help with? |
It's very reasonable to wonder what is going on with this PR. I happen to be on a vacation overseas, so you won't see any update from me before the end of this month. (For what it's worth, I was busy trying to wrap things up at work before the vacation.) So, I apologize for holding this up. I have not forgotten about it :) |
Thank you @ploxiln ! |
The goal of this is to add a contrib pacakge which allows for extensions/addons for NSQD. The idea was to make the contrib as separate from NSQD as possible. To do this the contrib nsqd addons are passed a reference to NSQD and can only interact with it through its exported methods.
It was also the hope that contrib addons would be completely segregated from core so that updating implementation or command line flags shouldn't require touching core at all.
DatadogStatsd was chosen to deliver the contrib package.
AddModuleGoroutine()
&& Exit channel passed toLoop
refs #904
Verified Datadogstatsd can communicate with local udp server
Started with:
Verified in local UDP echo server: