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 summary at the end of the test #39

Merged
merged 26 commits into from
Feb 13, 2020
Merged

Conversation

robertodauria
Copy link
Contributor

@robertodauria robertodauria commented Feb 12, 2020

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:

  1. The latest Measurement sent by the server
  2. The latest Measurement sent by the client
  3. The latest ConnectionInfo sent by the server

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:

$ ./ndt7-client
[...]
         Server: ndt-iupui-mlab3-mil02.measurement-lab.org
         Client: 93.188.101.116
        Latency:    19.8 milliseconds
       Download:   351.9 Mbit/s
         Upload:    55.2 Mbit/s
 Retransmission:    7.98 %

And in -batch mode

$ ./ndt7-client -batch
[...]
{
  "Server": "ndt-iupui-mlab2-mil05.measurement-lab.org",
  "Client": "93.188.101.116",
  "Download": {
    "value": 364.0964194856322,
    "unit": "Mbit/s"
  },
  "upload": {
    "value": 50.754748581668935,
    "unit": "Mbit/s"
  },
  "downloadRetrans": {
    "value": 6.206705982788881,
    "unit": "%"
  },
  "rtt": {
    "value": 18.643,
    "unit": "milliseconds"
  }
}

Also, to allow for summary-only output, a -quiet flag has been introduced to hide everything but the summary and/or errors.


This change is Reviewable

cmd/ndt7-client/main.go Outdated Show resolved Hide resolved
cmd/ndt7-client/internal/emitter/interactive.go Outdated Show resolved Hide resolved
cmd/ndt7-client/main.go Outdated Show resolved Hide resolved
Copy link
Contributor

@bassosimone bassosimone left a 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%.

cmd/ndt7-client/internal/summary.go Outdated Show resolved Hide resolved
cmd/ndt7-client/internal/summary.go Outdated Show resolved Hide resolved
ndt7.go Outdated Show resolved Hide resolved
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 {
Copy link
Contributor

@bassosimone bassosimone Feb 12, 2020

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?)

Copy link
Contributor Author

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.

Copy link
Contributor

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")
Copy link
Contributor

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:

  1. if you don't change to -format=json, there's a comment above mentioning it, can you fix the comment?

  2. if you change the command line arguments, can you also change the docstring and, if needed, the README?

Copy link
Contributor Author

@robertodauria robertodauria Feb 13, 2020

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 :)

Copy link
Contributor

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!

Copy link
Contributor

@bassosimone bassosimone left a 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/emitter/humanreadable.go Outdated Show resolved Hide resolved
cmd/ndt7-client/internal/emitter/quiet.go Show resolved Hide resolved
Server string

// IP address of the client.
Client string
Copy link
Contributor

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?

Copy link
Contributor Author

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?

Copy link
Contributor

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 Show resolved Hide resolved
}
}
if dl.Server.TCPInfo != nil {
if dl.Server.TCPInfo.BytesSent > 0 {
Copy link
Contributor

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")
Copy link
Contributor

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!

cmd/ndt7-client/main.go Outdated Show resolved Hide resolved
}

s := internal.NewSummary(r.client.FQDN, r.client.Results())
r.emitter.OnSummary(s)
Copy link
Contributor

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) {
Copy link
Contributor

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?

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 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.

Copy link
Contributor

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.
Copy link
Contributor

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.

bassosimone added a commit to m-lab/ndt-server that referenced this pull request Feb 13, 2020
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.
@bassosimone bassosimone dismissed their stale review February 13, 2020 15:57

There should only be one reviewer, removing myself from the line

bassosimone added a commit to m-lab/ndt-server that referenced this pull request Feb 13, 2020
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.
@bassosimone
Copy link
Contributor

I apologise for stepping into the review, which I was not asked.

Copy link
Contributor

@pboothe pboothe left a 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: :shipit: 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?

@pboothe
Copy link
Contributor

pboothe commented Feb 13, 2020

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.

Copy link
Contributor Author

@robertodauria robertodauria left a comment

Choose a reason for hiding this comment

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

Reviewable status: :shipit: 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()

@robertodauria robertodauria merged commit c4da0e9 into master Feb 13, 2020
@robertodauria robertodauria deleted the sandbox-roberto-summary branch February 13, 2020 22:44
satomiie pushed a commit to satomiie/performance-ndt-server that referenced this pull request Apr 16, 2020
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.
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.

3 participants