Skip to content
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 TCP support #79

Merged
merged 51 commits into from
Jul 18, 2018
Merged

Add TCP support #79

merged 51 commits into from
Jul 18, 2018

Conversation

michalholasek
Copy link
Contributor

This PR builds upon @remie's great work at sivy/node-statsd#67, with couple shortcomings:

  • I was not able to resolve problems with global tags and should properly send a and b with the same value test cases (skipped with xit). @bdeitte, could you please point me to correct direction to what is missing / wrong?
  • TCP test cases are pretty much copy/paste from the PR mentioned above - since there are lot of changes to this codebase since, it is possible that some test cases are missing

/cc @honzajavorek

@bdeitte
Copy link
Collaborator

bdeitte commented Jul 10, 2018

Thanks for this, great to see! A few things:

  • I see there's a conflicting file- looks like a merge from master resolve needs to happen here?
  • I don't like the the idea of having so much duplication in tests. This is very likely to get out of sync and be problematic for further updates. Is there a way this can be done where both ways to test share with each other?
  • Could you tell me how you are seeing some of these tests fail? I can take a look at another time in more depth but can't do that at the moment, but if you can let me know what you're seeing now I can see what I can do to help out.
    Thanks again!

Copy link

@honzajavorek honzajavorek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have just a few nitpicks, but I didn't find anything obvious to cause problems in the test. I'll spend some time playing with it locally and let's see.

  • As mentioned, there is a conflict needed to be resolved.
  • I agree with @bdeitte we should think about the duplication of tests, but I suggest we first get them passing this way, isolated and separated. I believe debugging problems in the initial TCP implementation while having the tests tangled together with the UPC implementation could be quite hard. Maybe we could make the tests more DRY as a next step when everything is passing?

lib/statsd.js Outdated
};
}

function createSocket(args) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd use options instead of args, but both are saying nothing about the contents, so it's just a convention nitpick.

lib/statsd.js Outdated
@@ -91,6 +102,10 @@ var Client = function (host, port, prefix, suffix, globalize, cacheDns, mock,
global.statsd = this;
}

if (options.protocol === 'tcp') {
this.socket.setKeepAlive(true);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be a part of the createSocket "method"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 69de8cb.

@honzajavorek
Copy link

Also, would you mind removing the xit so we can see (after the conflict is resolved) what exactly fails here in the CI? The Travis is testing ~4 versions of Node here, so maybe we can factor out whether the issue isn't specific to a particular Node version?

@honzajavorek
Copy link

honzajavorek commented Jul 10, 2018

When running locally on Node 8, I'm getting following failures randomly (flakily) even when I keep the xit marks intact:

1) StatsD [TCP] #globalTags should not add global tags if they are not specified:
     Uncaught Error: connect ECONNREFUSED 127.0.0.1:8125
      at Object._errnoException (util.js:992:11)
      at _exceptionWithHostPort (util.js:1014:20)
      at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1186:14)

  2) StatsD [TCP] #globalTags Uncaught error outside test suite:
     Uncaught TypeError: Cannot read property 'currentRetry' of undefined

Also

1) StatsD [TCP] #globalTags should not add global tags if they are not specified:
     Uncaught Error: getaddrinfo ENOTFOUND host host:1234
      at errnoException (dns.js:50:10)
      at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:92:26)

  2) StatsD [TCP] #globalTags Uncaught error outside test suite:
     Uncaught TypeError: Cannot read property 'currentRetry' of undefined

They occur especially when running the whole test suite repeatedly. Looks like there is a flawed tear down somewhere.

@michalholasek
Copy link
Contributor Author

michalholasek commented Jul 10, 2018

The conflict was probably caused by test_statsd.js -> test_statsd_udp.js rename - I removed the commits with changes in test_statsd.js for now. xit test cases are also unskipped.

Regarding tests:

  • I think that one cause for the flakiness could be old version of mocha. Another could be race conditions in create server -> assert -> close server logic.
  • We discussed the DRYness / refactoring with @honzajavorek, but decided not do it for the sake of PR review complexity (a lot of changes) and time constraints. I agree that current implementation is suboptimal, but I would rather go with working current suite and then do the refactor as next step / PR

@bdeitte In #globalTags suite, there is should combine global tags and metric tags test case - it expects the result to be test:1337|c|#foo,gtag, but the actual value is test:1337|c|#gtag,foo (position of the tags is inverted). I guess it has something to do with overrideTags in helpers.js file, but I am not able to track why it works for UDP, but does not for TCP. Another is should properly send a and b with the same value test case in every suite, be it #gauge, #histogram, ... - expects 0, but actual value is NaN.

@honzajavorek
Copy link

honzajavorek commented Jul 10, 2018

race conditions in create server -> assert -> close server logic

I suspect that as well. We should hunt those down. Do you experience similar flakiness locally or is it just me?

@michalholasek
Copy link
Contributor Author

@honzajavorek I do. I am not sure it will be possible without the rewrite 😞

@honzajavorek
Copy link

My hunch is the tcpTest function does something incorrectly and makes all the tests non-deterministic.

@michalholasek
Copy link
Contributor Author

@honzajavorek Changes made in b571d8c ensure that the tests pass/fail consistently now. The solution is not ideal, but at least we can now focus on making them pass.

Copy link

@honzajavorek honzajavorek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Running npm test on top of the branch fails due to some linter errors. I marked them with comments.

metrics.forEach(function (metric) {
test(metric, server);
});
var metrics = parts.filter(part => part !== '');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test/test_statsd_tcp.js: line 37, col 41, 'arrow function syntax (=>)' is only available in ES6 (use 'esversion: 6').

protocol: 'tcp'
});
assert.ok(statsd.socket instanceof net.Socket);
done();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test/test_statsd_tcp.js: line 72, col 9, 'done' is not defined.

@honzajavorek
Copy link

BTW I can still see #increment being strangely nested inside #gauge in the tests output, which is rather suspicious.

#gauge
      ✓ should send proper gauge format without prefix, suffix, sampling and callback
      ✓ should send proper gauge format with tags
      ✓ should send proper gauge format with prefix, suffix, sampling and callback
      4) should properly send a and b with the same value
      5) "after each" hook
      #increment

I'll try to help with debugging this. Maybe I can figure something out. @bdeitte if you don't have any immediate ideas on what could be wrong with either the TCP implementation or the tests, then feel free to ignore it here until we get the thing passing. And sorry for the noise.

@michalholasek
Copy link
Contributor Author

@bdeitte The build is green now, could you please take another look? Thank you.

@bdeitte
Copy link
Collaborator

bdeitte commented Jul 12, 2018

@michalholasek Thanks for getting the tests working and doing all this work. Unfortunately I still have the same feeling I have before- the duplication of tests in the way they are isn't something I am comfortable with. The tests are already drifting apart from what I see, and this is only going to increase without careful diligence over time. There needs to be something done to get them closer in line, even if it's not exactly right. I know that you mention this could be done in a followup PR, but that is not a great place for this codebase to be in. If you need to have this right away and can't make those changes, I would suggest using your branch for now until that can be done. (Although I hope you will make them now, would be great to have this in!)

I thought about this some more, to try to think of something that isn't too painful to do but which gets more at what I'm worried about here. Here's my suggestion: https://gist.github.com/bdeitte/4ae7fd92edb490e2adcd22e38d62a5a6

What I did in the above is take your new tests, rip out everything other than globalTags tests, and then try to add in the UDP tests. I ran this in your branch and it passes there.

If you want to try to add this way of doing things, here's you would need to do:

  1. Start with test_statsd_udp.js, and rename createServer to createTCPServer. Change all references inline. Also add in createUDPServer as shown above.
  2. Copy each "describe" section you've made and paste a new one below it. Name one UDP and one TCP. For the UDP one, switch all TCP references to UDP
  3. Run it all and hopefully see it all pass
  4. Look back in test_statsd.js to see if anything is missing in there- I know there's some tests that were just in there. Copy them in and switch to createTCPServer()
  5. Rename test_statsd_udp.js to test_statsd.js

In the future, it would be good to split these tests into multiple files (while still keeping UDP/TCP tests for related functionality together) as this file is really big. It'd also probably be good to think more about ways to shorten some of the boilerplate in the tests. But I started to think about that and realized it's not what I'm asking here and definitely don't want to make this something more than it is, which is just a request to have similar tests live by each other for easier maintenance. Thanks!

lib/statsd.js Outdated

if (this.protocol === 'tcp') {
message += '\n';
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be in the sendMessage method as that one deals with the quirks of the individual transmission protocols. This is an implementation details of sending the message over TCP and IMHO should be encapsulated by sendMessage.

lib/statsd.js Outdated
// hidden global_tags option for backwards compatibility
options.globalTags = options.globalTags || options.global_tags;

this.protocol = options.protocol;
Copy link

@honzajavorek honzajavorek Jul 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: Although it's against various code styles, I'd stick to the code style already in place and add the alignment whitespaces:

- this.protocol = options.protocol;
+ this.protocol    = options.protocol;

@honzajavorek
Copy link

Just for the record, once this is done, it closes #76 (I miss the connection when browsing the issues/PRs here 😄 )

@michalholasek
Copy link
Contributor Author

@bdeitte Test refactor is finished now, PR is ready for re-review:

  • UDP / TCP tests are now 1/1
  • Almost every public method has its own file, with the exception of most methods from statsFunctions.js

lib/statsd.js Outdated
}

return socket;
}.bind(this);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doing it this way means we have two ways to have functions in here. Could you make this like the existing "_send" method? Alternatively, if you feel strongly about it, it's not that I'm a big fan of "_send"- you could also make that method like this one.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added the bind to appease jshint, which is not good enough reason, I agree. I changed the createSocket function signature to accept instance argument instead (dee23b3).

@@ -42,26 +43,55 @@ var Client = function (host, port, prefix, suffix, globalize, cacheDns, mock,
maxBufferSize : maxBufferSize,
bufferFlushInterval: bufferFlushInterval,
telegraf : telegraf,
sampleRate : sampleRate
sampleRate : sampleRate,
protocol : protocol
};
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Someone is going to do "TCP" at some point and get confused. How about adding:

if (options.protocol) {
  options.protocol = options.toLowerCase();
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in fe2d446.

package.json Outdated
"test": "mocha -R spec",
"lint": "jshint lib/**.js test/**.js",
"test": "mocha -R spec --timeout 5000 test/index.js",
"lint": "jshint lib/**.js",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not lint the tests anymore? If you had an issue, can we just exclude that with a jshint ignore comment?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 34215ff.


var createStatsdClient = require('./helpers').createStatsdClient;
var createTCPServer = require('./helpers').createTCPServer;
var createUDPServer = require('./helpers').createUDPServer;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests and the abstractions in them look great- thanks!

@bdeitte
Copy link
Collaborator

bdeitte commented Jul 16, 2018

Wow this is a lot of work, thank you! I reviewed everything and just had a few small comments. (I took awhile with the tests a bit earlier and think I reviewed it all but lots to cover.) . Once you can answer/fix the few comments, I can review/answer that and merge in with a minor release.

I will still make it minor in this case for anyone curious- I have made a major before with major code changes, but the changes just look at statsd.js itself look small enough (and not backwards breaking of course) with whitespace ignored to be a minor release.

Copy link
Collaborator

@bdeitte bdeitte left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good- merging and releasing.

@bdeitte bdeitte merged commit 306996f into brightcove:master Jul 18, 2018
@remie
Copy link

remie commented Jul 18, 2018

Thanks @michalholasek for all the hard work you put into this!

@michalholasek
Copy link
Contributor Author

@bdeitte Thank you for your time you've put into feedback/reviewing! @remie Thank you for the original work, it helped a lot 🙂

@michalholasek michalholasek deleted the michalholasek/add-tcp-support branch July 20, 2018 13:02
@bdeitte bdeitte mentioned this pull request Jul 27, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants