-
Notifications
You must be signed in to change notification settings - Fork 617
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 a StringList type to be used for commandline flags. #583
Conversation
Codecov Report
@@ Coverage Diff @@
## master #583 +/- ##
==========================================
- Coverage 66.94% 66.92% -0.03%
==========================================
Files 78 78
Lines 14378 14398 +20
==========================================
+ Hits 9626 9636 +10
- Misses 3892 3902 +10
Partials 860 860
Continue to review full report at Codecov.
|
internal/stringlist/stringlist.go
Outdated
) | ||
|
||
// StringList is an alias for slice of strings. | ||
type StringList []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.
Could []string be used directly?
I'm not immediately seeing the additional utility of having a wrapper class here, though could be missing something.
Modifying the FlagSet to point an internal class probably isn't ideal (depending on an internal class as part of a public interface could be a bit complicating).
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.
The new type implements the flag.Value interface. I think we cannot retain the []string type and provide the functions required by the flag package. I am open to considering other ways to achieve this, but there does not seem to be any.
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.
Thanks for this change!
Left a few initial comments about the StringList type.
driver/driver.go
Outdated
@@ -94,7 +95,7 @@ type FlagSet interface { | |||
|
|||
// StringList is similar to String but allows multiple values for a | |||
// single flag | |||
StringList(name string, def string, usage string) *[]*string | |||
StringList(name string, def string, usage string) *stringlist.StringList |
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 is part of pprof's public interface (one passes in plugin.Options
to driver.PProf
; we use this internally to, among other things, change the profile fetcher / symbolizer for internal pprof).
It would be nice if this interface did not have to change. Since we use this interface internally, it will definitely make this PR simpler to land if there aren't changes to pprof's public 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.
Also, this public interface shouldn't use an internal type (github.com/google/pprof/internal/stringlist
) in its signature.
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 the interface needs to change. Currently there is no difference between a StringList flag and a String flag. A StringList flag can only hold a single string for now. To support a list of strings, we need to provide a type that adds strings to a list. The new type implements the flag.Value interface to achieve this.
Also, this public interface shouldn't use an internal type (github.com/google/pprof/internal/stringlist) in its signature.
I have moved the new type to a public directory.
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'd need to see a design document considering this more carefully proposed change before I'd be comfortable changing the public interface. I'm not currently persuaded that the interface change is the best or only way forward; I'd really need to be convinced of that before I'd feel comfortable with a change to the interface.
As additional context, changing the interface is a really big deal. First, we don't currently version pprof. Now that golang does support versioning a library, if we change the public interface, this becomes something we ought to look into. IMO, it'd be necessary to at least fully consider versioning pprof using go modules prior to making any breaking changes to the public interface. Next, changes to the public interface can make it really hard to sync our internal copy of pprof, since it becomes possible that code used for our internal version of pprof will break. If it is concluded that changes to the public interface are necessary, I would prefer to first see changes made to our internal copy of pprof to know that nothing internal will break (a PR would still need to be sent to change code, and any changes that need to be made to our dependencies on this library would need to be done when importing pprof from GitHub).
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 have updated the PR with a different implementation (suggested by lmilewski) that does not change the public interface. Please take another look.
flagset/flagset.go
Outdated
@@ -0,0 +1,48 @@ | |||
// Copyright 2021 Google Inc. All Rights Reserved. |
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 like pulling this interface out to avoid duplication between driver/driver.go and internal/plugin/plugin.go. Can we make this a separate PR? I think this can go in regardless of other discussions. One thing I'm not sure about is whether it's worth a separate top-level directory - seems too shallow for that. Maybe have it as driver/flagset.go? (and "package driver").
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.
Can we make this a separate PR?
Yes. We just need to decide on the package for flagset.go.
Maybe have it as driver/flagset.go?
I had tried adding to driver package before, but ran into an import cycle.
package github.com/google/pprof
imports github.com/google/pprof/driver
imports github.com/google/pprof/internal/driver
imports github.com/google/pprof/internal/binutils
imports github.com/google/pprof/internal/plugin
imports github.com/google/pprof/driver: import cycle not allowed
package github.com/google/pprof
imports github.com/google/pprof/driver
imports github.com/google/pprof/internal/driver
imports github.com/google/pprof/internal/binutils
imports github.com/google/pprof/internal/plugin
imports github.com/google/pprof/driver: import cycle not allowed
Are you OK going with a new package? I could not think of any other reasonable place for this.
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'd prefer not to have a new top-level package. Actually, reading go/go-style/decisions#interfaces I wonder if we should leave things as is with two copies of the interface for now. It's duplication, but it's rather unrelated issue to this PR, so I'd tackle it separately if we ever need to.
"testing" | ||
) | ||
|
||
func TestString(t *testing.T) { |
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.
It seems a bit different (at least to the best of my knowledge and in the context of golang) to test functions on a private type in golang. Testing the public interface would probably be preferable.
But, I know that there are no tests for flag.go right now; will leave it up to @aalexand to decide what sort of testing is sufficient.
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.
Ping to @nilayvaish and @aalexand.
I'm not sure about this testing. It may be better than not having tests, but I'm a bit skeptical about testing only a private type, rather than the publicly available functions.
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.
AFAICT, it is not possible to test the public interface without creating a separate process. Would you be OK with the _test.go file creating a new process with flags? Otherwise, I think we need to drop the tests all together.
The two copies the FlagSet interface are identical and are required to be so. It seems better to have them in place. We put this in a public directory as the interface defined in driver/driver.go is already public.
The commit adds a new type flagset.StringList that holds a list of strings provided via an input flag. The new type implements that flag.Value interface as required by the flag package. flagset.StringList flags would used for the following feature: add tags as pseudo locations.
We introduce a new stringList type that implements the flag.Var interface. flags' internal data structures would now hold a var of this type instead of a var of type flag.String. Multiple invocations of a StringList flag would therefore be able to add more values to this flag.
@nilayvaish @nolanmar511 Looking at this again, I'm now not sure why flag changes are needed to support Also, one thing to watch for with list-like flags is append vs. reset semantics. E.g. in the pprof interactive shell we need a way to specify the full set of values for these flags, without ad-infinitum append behavior. |
We are treating
This is an interesting point I had not considered before. I checked |
"tagleaf": helpText( | ||
"Adds a pseudo leaf node for samples with tags matched by regexp", | ||
"Use name=value1,value2 syntax to specify the tag and the values", | ||
"for which nodes would be added. Example: latency=1s,2s,3s."), | ||
"tagroot": helpText( | ||
"Adds a pseudo root node for samples with tags matched by regexp", | ||
"Use name=value1,value2 syntax to specify the tag and the values", | ||
"for which nodes would be added. Example: latency=1s,2s,3s."), |
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.
Couple of questions:
-
The option / flag is of type []string / multi-value. The examples are of the form "foo=val1,val2" What exactly the multi-value here? Is it the comma-separated multi-values as in example, i.e. "foo=val1" and "val2" as two values which is weird? Or is that there is ability to have multiple occurrences of each of these like "-flag=foo=val1,val2 -flag=bar=val3,val4" and the documentation needs to explain this?
-
How are numeric tags handled, including with units like memory? Can we reuse the same value format as in tagfocus / taghide etc.? I feel that we are introducing a new way to specify a set of values and I don't see why different tag* flags can't unify on a single way to do that.
StringList flags would used for developing the feature: add tags as
pseudo locations.
#558