-
Notifications
You must be signed in to change notification settings - Fork 25
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 summary at the end of the test #39
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.
Whether it's -json
or -format=json
or -batch
, I think we should make sure that the documentation is in sync with the accepted command flags. (That said, I find -format=json
rather cool.)
Ah, and also, I would be much more pleased if we could keep the coverage to 100%.
ndt7.go
Outdated
@@ -53,6 +53,14 @@ type testFn = func( | |||
// by NewClient in the Client.Dialer.HandshakeTimeout field. | |||
const DefaultWebSocketHandshakeTimeout = 7 * time.Second | |||
|
|||
// TestData contains the latest Measurement send by the server and the client, | |||
// plus the latest ConnectionInfo sent by the server. | |||
type TestData struct { |
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 quite like this name because I find it too generic. How about LatestMeasurement? (My understanding is that this data structure is that and, after we fix server, we wont need separate ConnectionInfo, do I understand it correctly?)
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.
Your understanding is correct: if ndt-server started sending ConnectionInfo
with every measurement, rather than just on the first one, then we could theoretically remove the ConnectionInfo field here and just keep the latest Measurement.
However, I feel like we should not rely on the ndt-server specific implementation but rather follow the protocol specification, which at the moment goes in the opposite direction:
Servers MUST send this message exactly once.
Clients SHOULD cache the first received instance of this message, and discard any subsequently received instance of this message.
To clarify: I like the idea of sending ConnectionInfo every time, I just think we should also update the specification.
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.
Yeah, what you say is sensible.
flagNoVerify = flag.Bool("no-verify", false, "skip TLS certificate verification") | ||
flagHostname = flag.String("hostname", "", "optional ndt7 server hostname") | ||
flagTimeout = flag.Duration( | ||
"timeout", defaultTimeout, "time after which the test is aborted") | ||
flagQuiet = flag.Bool("quiet", false, "emit summary and errors only") |
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 quite liked the suggestion by @pboothe to have -format=json|human|brief
because it seems to me this kinda solves the annoying issue we had with composing emitters in libndt. I am also not so happy with breaking existing scripts that may exist, but, after discussing this with @robertodauria, I understand the potential of this -format=
design and I am fully on board regarding it. However, please, @robertodauria:
-
if you don't change to
-format=json
, there's a comment above mentioning it, can you fix the comment? -
if you change the command line arguments, can you also change the docstring and, if needed, the README?
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.
Following a suggestion you gave me in a private chat, I've kept -batch
as an alias to -format=json
but marked it as deprecated, so it shouldn't disrupt anyone's workflow. Also, I've updated the documentation to reflect these changes.
I would argue, however, that it's hard to provide any kind of API stability guarantee when the client is relatively new, and people directly using master
should expect breaking changes, especially at this stage.
Using semantic versioning + advising users to depend on tagged releases would make this a non-issue. Maybe it's something it would be good to discuss with the team?
P.S.: I'll leave it to you to decide if this is solved by the changes I've made and click on "Resolve", the documentation changes look good to me but I might've missed something :)
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.
Before providing a more comprehensive response, let me say that the issue we're dealing with here is mostly theoretical, since just me and @mattmathis probably use this client.
Speaking from a theoretical point of view, there is clearly a tension here between moving quickly and breaking things that people may be using. When there is a single repository for all the source code, you can move more quickly because you know that you are not breaking expectations. When there are multiple repositories this is more complex and I believe, from the theoretical point of view, that deleting immediately old code is bad.
Speaking of good practices here, I believe we should consider what the abseil.io team do in terms of compatibility: "We will support language versions, compilers, platforms, and workarounds as needed for 5 years after their replacement is available, when possible". My suggestion in this regard would be to more modestly strive for keeping the previous public interface for three months. In INTP fashion, of course, here I can also argue that three months equal zero months, because people do not read deprecation notices. Trying to find a synthesis between those two points of view, I'd say we either remove the old interface immediately, or we make it such that people notices the deprecation warning.
(You may also notice that my response here is much more comprehensive than the one I gave you in private as I am making an effort towards not using my system 1 brain when providing feedback. I think specifically the "three months equal zero months" argument is system 2 thinking.)
[...]
Using semantic versioning + advising users to depend on tagged releases would make this a non-issue.
I disagree that it is a non-issue. Assuming semantic versioning is done correctly, a semantic bump only tells you that you are going to have some kind of trouble. (In this regard, I believe API stability is impossible, and for this reason it probably makes sense to never provide strong API stability, i.e., never release v.1.0.0.)
Maybe it's something it would be good to discuss with the team?
This discussion is dangerous material, because certainly it has the potential to go down to a rabbit hole. At the same time, it's also a topic that has to do with engineering, and I may end up learning something, so, yes please!
This broke main_test.go with relied on being able to create a custom emitter.JSON with a non-standard writer. I've added NewJSONWithWriter() to still allow that from external packages.
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 another pass and provided some (I hope) useful feedback. There is just one-two places in which I asked you whether you could apply minimal changes, hence using "Request Changes".
cmd/ndt7-client/internal/summary.go
Outdated
Server string | ||
|
||
// IP address of the client. | ||
Client 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.
Would you mind naming this variable ClientIP
?
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.
At that point I would also need to rename Server
to ServerFQDN
, for the same reason.
I originally had ServerFQDN
and ClientIP
as field names there, which translated to server
and client
in the JSON and you advised me to avoid using the json:"fieldname"
annotation but rather re-use the same field names in the source code.
Did you change your mind, or you're suggesting to have {"ServerFQDN": ..., "ClientIP": ...}
in the summary JSON, too?
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.
When I mentioned to avoid using JSON annotations, I said this because we applied a similar change to the ndt-server in the past, so I wanted to help to enforce consistency in this respect. I don't like the fact that in a JSON variables have upper case names, but I understand how the annotation complicates maintainability.
I believe my comment regarding changing the name of the variable was caused by the documentation string, which starts with "IP", which led me to think in terms of the ClientIP, I guess.
Anyway, variable names can be changed at any time and hence it does not matter.
cmd/ndt7-client/internal/summary.go
Outdated
} | ||
} | ||
if dl.Server.TCPInfo != nil { | ||
if dl.Server.TCPInfo.BytesSent > 0 { |
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 appreciate that there are checks for the value being not-zero. Optionally we can write tests for such error condition?
flagNoVerify = flag.Bool("no-verify", false, "skip TLS certificate verification") | ||
flagHostname = flag.String("hostname", "", "optional ndt7 server hostname") | ||
flagTimeout = flag.Duration( | ||
"timeout", defaultTimeout, "time after which the test is aborted") | ||
flagQuiet = flag.Bool("quiet", false, "emit summary and errors only") |
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.
Before providing a more comprehensive response, let me say that the issue we're dealing with here is mostly theoretical, since just me and @mattmathis probably use this client.
Speaking from a theoretical point of view, there is clearly a tension here between moving quickly and breaking things that people may be using. When there is a single repository for all the source code, you can move more quickly because you know that you are not breaking expectations. When there are multiple repositories this is more complex and I believe, from the theoretical point of view, that deleting immediately old code is bad.
Speaking of good practices here, I believe we should consider what the abseil.io team do in terms of compatibility: "We will support language versions, compilers, platforms, and workarounds as needed for 5 years after their replacement is available, when possible". My suggestion in this regard would be to more modestly strive for keeping the previous public interface for three months. In INTP fashion, of course, here I can also argue that three months equal zero months, because people do not read deprecation notices. Trying to find a synthesis between those two points of view, I'd say we either remove the old interface immediately, or we make it such that people notices the deprecation warning.
(You may also notice that my response here is much more comprehensive than the one I gave you in private as I am making an effort towards not using my system 1 brain when providing feedback. I think specifically the "three months equal zero months" argument is system 2 thinking.)
[...]
Using semantic versioning + advising users to depend on tagged releases would make this a non-issue.
I disagree that it is a non-issue. Assuming semantic versioning is done correctly, a semantic bump only tells you that you are going to have some kind of trouble. (In this regard, I believe API stability is impossible, and for this reason it probably makes sense to never provide strong API stability, i.e., never release v.1.0.0.)
Maybe it's something it would be good to discuss with the team?
This discussion is dangerous material, because certainly it has the potential to go down to a rabbit hole. At the same time, it's also a topic that has to do with engineering, and I may end up learning something, so, yes please!
} | ||
|
||
s := internal.NewSummary(r.client.FQDN, r.client.Results()) | ||
r.emitter.OnSummary(s) |
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 agree that it's tricky to decide whether to show a summary if there was an error
return ch, nil | ||
} | ||
|
||
func (c *Client) collectData(ctx context.Context, f testFn, conn websocketx.Conn, outch chan<- spec.Measurement) { |
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.
When we were discussing in private this file's implementation, you mentioned that you'd rather see this piece of code written using interfaces rather than in this function-oriented JavaScript-like fashion. Maybe open an issue?
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 like to revisit my opinion about this function-oriented approach after writing the same summary for the ndt5-client (which, AFAIK, is more OO-like?). At the moment I don't feel strongly enough about this to suggest writing it differently.
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.
Sure. I should actually learn to mind my business more. It's natural that the coding style conventions change when the maintainer of the code changes, and this has already happened. I hope you find ndt5 less functional.
case spec.OriginServer: | ||
// The server only sends ConnectionInfo once at the beginning of | ||
// the test, thus if we want to know the client IP and test UUID | ||
// we need to store it separately. |
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.
Edited m-lab/ndt-server#194 (comment) such that we remember to change this code here.
We noticed how annoying is not doing that with @robertodauria during the review of m-lab/ndt7-client-go#39. As part of such work, we also double checked that every TCPInfo field described by the spec is a meaningful ndt7 summary. As a result we can now close #194.
There should only be one reviewer, removing myself from the line
We noticed how annoying is not doing that with @robertodauria during the review of m-lab/ndt7-client-go#39. As part of such work, we also double checked that every TCPInfo field described by the spec is a meaningful ndt7 summary. As a result we can now close #194.
…ype. Also, add NewHumanReadableWithWriter(io.Writer).
I apologise for stepping into the review, which I was not asked. |
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.
Dismissed @bassosimone and @robertodauria from 9 discussions.
Reviewable status: complete! 1 of 1 approvals obtained (waiting on @pboothe and @robertodauria)
cmd/ndt7-client/main.go, line 107 at r2 (raw file):
Previously, bassosimone (Simone Basso) wrote…
Before providing a more comprehensive response, let me say that the issue we're dealing with here is mostly theoretical, since just me and @mattmathis probably use this client.
Speaking from a theoretical point of view, there is clearly a tension here between moving quickly and breaking things that people may be using. When there is a single repository for all the source code, you can move more quickly because you know that you are not breaking expectations. When there are multiple repositories this is more complex and I believe, from the theoretical point of view, that deleting immediately old code is bad.
Speaking of good practices here, I believe we should consider what the abseil.io team do in terms of compatibility: "We will support language versions, compilers, platforms, and workarounds as needed for 5 years after their replacement is available, when possible". My suggestion in this regard would be to more modestly strive for keeping the previous public interface for three months. In INTP fashion, of course, here I can also argue that three months equal zero months, because people do not read deprecation notices. Trying to find a synthesis between those two points of view, I'd say we either remove the old interface immediately, or we make it such that people notices the deprecation warning.
(You may also notice that my response here is much more comprehensive than the one I gave you in private as I am making an effort towards not using my system 1 brain when providing feedback. I think specifically the "three months equal zero months" argument is system 2 thinking.)
[...]
Using semantic versioning + advising users to depend on tagged releases would make this a non-issue.
I disagree that it is a non-issue. Assuming semantic versioning is done correctly, a semantic bump only tells you that you are going to have some kind of trouble. (In this regard, I believe API stability is impossible, and for this reason it probably makes sense to never provide strong API stability, i.e., never release v.1.0.0.)
Maybe it's something it would be good to discuss with the team?
This discussion is dangerous material, because certainly it has the potential to go down to a rabbit hole. At the same time, it's also a topic that has to do with engineering, and I may end up learning something, so, yes please!
For this client, this discussion is immaterial. Go change it to the right way and don't worry. When a tool is used outside of m-lab, we should think more deeply. Feel free to delete the -batch
flag now or later.
cmd/ndt7-client/internal/emitter/humanreadable.go, line 86 at r7 (raw file):
%15s: %7.1f %s %15s: %7.1f %s %15s: %7.2f %s`
nitpick - end the quote character on the next line to avoid having to add "\n"
later
cmd/ndt7-client/internal/emitter/humanreadable_test.go, line 223 at r7 (raw file):
func TestHumanReadableOnSummary(t *testing.T) { expected := " Server: test\n" +
Another great opportunity for the "`"-style string
cmd/ndt7-client/internal/emitter/json_test.go, line 15 at r7 (raw file):
func TestJSONOnStarting(t *testing.T) { sw := &mocks.SavingWriter{} j := jsonEmitter{sw}
Use the NewJSONWithWriter method, please
cmd/ndt7-client/internal/emitter/json_test.go, line 280 at r7 (raw file):
func TestNewJSONConstructor(t *testing.T) { j := NewJSON()
Does any code still use this? Should NewJSON just take a writer and then you can delete NewJSONWithWriter?
This looks good to me with minor questions. I have faith that, upon considering the questions, you will come to a good conclusion one way or the other, so this PR Looks Good To Me. |
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.
Reviewable status: complete! 1 of 1 approvals obtained (waiting on @pboothe and @robertodauria)
cmd/ndt7-client/internal/emitter/humanreadable_test.go, line 223 at r7 (raw file):
Previously, pboothe (Peter Boothe) wrote…
Another great opportunity for the "`"-style string
Done. I'm not sure why it did not work the first time, vscode seemed to treat multiline strings as code and replaced groups of 4 spaces with a \t
. What was here was a workaround, but it's not needed anymore.
cmd/ndt7-client/internal/emitter/json_test.go, line 15 at r7 (raw file):
Previously, pboothe (Peter Boothe) wrote…
Use the NewJSONWithWriter method, please
Done (here and in many other places in this file.)
cmd/ndt7-client/internal/emitter/json_test.go, line 280 at r7 (raw file):
Previously, pboothe (Peter Boothe) wrote…
Does any code still use this? Should NewJSON just take a writer and then you can delete NewJSONWithWriter?
Not in the emitter package, but it's still used in main.go
:
if *flagBatch || flagFormat.Value == "json" {
e = emitter.NewJSON()
We noticed how annoying is not doing that with @robertodauria during the review of m-lab/ndt7-client-go#39. As part of such work, we also double checked that every TCPInfo field described by the spec is a meaningful ndt7 summary. As a result we can now close #194.
This change adds a summary at the end of the ndt7 test.
To achieve that, the ndt7 Client now saves the results of each test in a
TestData
struct containing:These fields are filled while the test is running, since tests can be interrupted at any time and we do not know which Measurement will be the last one.
Here is how the summary looks like, in interactive mode:
And in
-batch
modeAlso, to allow for summary-only output, a
-quiet
flag has been introduced to hide everything but the summary and/or errors.This change is