-
Notifications
You must be signed in to change notification settings - Fork 5.6k
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
Add input plugin for McRouter #4077
Conversation
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 know this is based heavily on the memcached input, but this is one of the oldest plugins we have and it could stand to be brought up to the current style/standards. Would you be interested in doing some of the updates on this plugin and then we can bring memcached up to match afterwards?
plugins/inputs/mcrouter/README.md
Outdated
# with optional port. ie localhost, 10.0.0.1:11211, etc. | ||
servers = ["localhost:11211"] | ||
# An array of unix mcrouter sockets to gather stats about. | ||
# unix_sockets = ["/var/run/mcrouter.sock"] |
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.
Let's use a single list of this style:
servers = ["tcp://localhost:11211", "unix:///var/run/mcrouter.sock", "udp://localhost:11211"]
We don't need to add support for UDP at first, assuming it is even available with mcrouter, but this will give us a nice way to add it later.
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've updated to use a single array of servers and pull the protocol from the connection string.
(mcrouter does not support UDP connections)
plugins/inputs/mcrouter/mcrouter.go
Outdated
var defaultTimeout = 5 * time.Second | ||
|
||
// The list of metrics that should be sent | ||
var sendMetrics = []string{ |
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 would love to have these locked to specific types, since it would be a problem if a field changed values. Maybe we can turn this into a map[string]statType
where statType is an enum being int64, float64 or string? Then when we parse we try the expected value and log an error if it doesn't match.
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.
switched to map with types and only attempt a convert for the expected type
plugins/inputs/mcrouter/mcrouter.go
Outdated
# unix_sockets = ["/var/run/mcrouter.sock"] | ||
` | ||
|
||
var defaultTimeout = 5 * time.Second |
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.
Let's make this configurable and have it apply to all servers.
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 followed the zookeeper example here
plugins/inputs/mcrouter/mcrouter.go
Outdated
address = address + ":11211" | ||
} | ||
|
||
conn, err = net.DialTimeout("tcp", address, defaultTimeout) |
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.
Would be a nice touch to create a context.Context at the beginning of Gather, and use DialContext, I recently updated the zookeeper input to do something like this:
https://github.com/influxdata/telegraf/blob/master/plugins/inputs/zookeeper/zookeeper.go#L81
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 I followed the zookeeper example here
plugins/inputs/mcrouter/mcrouter.go
Outdated
conn.SetDeadline(time.Now().Add(defaultTimeout)) | ||
|
||
// Read and write buffer | ||
rw := bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn)) |
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 don't think this is necessary, just use conn.Write(
and you won't need to flush.
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 followed the zookeeper example and just do fmt.Fprint(conn
plugins/inputs/mcrouter/mcrouter.go
Outdated
return nil | ||
} | ||
|
||
func parseResponse(r *bufio.Reader) (map[string]string, error) { |
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.
We should use a bufio.Scanner
to read lines, the ReadLine() function can return partial lines.
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.
Using bufio.Scanner
now
plugins/inputs/mcrouter/mcrouter.go
Outdated
// Read values | ||
s := bytes.SplitN(line, []byte(" "), 3) | ||
if len(s) != 3 || !bytes.Equal(s[0], []byte("STAT")) { | ||
return values, fmt.Errorf("unexpected line in stats response: %q", line) |
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.
Return nil as the map.
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.
Made the change. I assume that the standard is to return no stats on error rather than partial stats?
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.
Yes, returning an error is probably best here.
2801c28
to
d79b823
Compare
I think I could update the memcached plugin afterwards. It seems like a breaking change for the memcached plugin to remove the |
plugins/inputs/mcrouter/mcrouter.go
Outdated
var protocol string | ||
var dialer net.Dialer | ||
|
||
s := strings.SplitN(address, "://", 2) |
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.
Use url.Parse
here, I think it will help simplify things and prevent errors.
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 updated the code to use url.Parse
I feel like it makes it a bit stricter on the server connection string format, but I think that's ok.
I make some attempt to tease partial connection strings into a proper format, but ultimately default to tcp://localhost:11211
if the server connection string does not parse properly.
Yeah, for memcached we will need to keep backwards compatibility. I usually try to translate the old format to the new one on the first call to Gather. |
plugins/inputs/mcrouter/mcrouter.go
Outdated
} | ||
|
||
if u.Scheme != "tcp" && u.Scheme != "unix" { | ||
protocol = defaultServerURL.Scheme |
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.
return error
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.
returning error
plugins/inputs/mcrouter/mcrouter.go
Outdated
address = u.Path | ||
} else { | ||
if u.Host == "" { | ||
u.Host = defaultServerURL.Host |
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.
return error
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.
returning error
plugins/inputs/mcrouter/mcrouter.go
Outdated
|
||
host, port, err = net.SplitHostPort(u.Host) | ||
|
||
if err != nil { |
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 did a few quick tests and unfortunately I think we will need to handle the case when either host or port are empty strings:
func Check(input string) {
host, port, err := net.SplitHostPort(input)
fmt.Printf("%q %q %q: %v\n", input, host, port, err)
}
func TestSplitHostPort(t *testing.T) {
Check("localhost:8086")
Check("localhost") # we should accept this and use default port
Check("localhost:") # use default port
Check(":8086") # use default hostname
Check(":") # default host:port or reject
Check("") # reject, but we already do that above
}
"localhost:8086" "localhost" "8086": <nil>
"localhost" "" "": address localhost: missing port in address
"localhost:" "localhost" "": <nil>
":8086" "" "8086": <nil>
":" "" "": <nil>
"" "" "": missing port in 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.
I added a test for parsing addresses and separated the address parsing into its own function.
I'm just using the url.URL
functions to get the hostname and port parts and not using net.SplitHostPort
any more.
McRouter is a smart proxy for Memcached https://github.com/facebook/mcrouter
Great job, merged for 1.7.0 |
Thanks! |
McRouter is a smart proxy for Memcached
https://github.com/facebook/mcrouter
Code is based on the memcached input plugin
Required for all PRs: