-
Notifications
You must be signed in to change notification settings - Fork 16
cstates: rework filtering API #163
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
base: main
Are you sure you want to change the base?
Conversation
|
I had some no-network time on a flight so I took a quick stab at this. A sort-of academic exercise :) Tell me what you think. Things could be done done in different ways, like make the filter immutable (like |
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.
@marquiz, I really like this! Yet I was worried about the loss of expressive power (negations and parameter combinations), I think this solves the problem by saying "this is the BasicFilter, and filter.go can be extended with MoreComplexFilter laterj, if needed, without breaking existing code". So it really looks future-proof, as you say.
The only question that I have would be regarding asymmetry in constructing BasicFilters and FilterInputs. While the first are defined with
NewBasicFilter().SetCPUs(...).SetCstateNames(...)....
The latter are with
NewFilterInput(WithCPU(...), WithCstateName(...), ....)
The API would be more confusing if NewBasicFilter(WithCPUs(...), ...) would be possible because then we would have WithCPU() that would work with FilterInput and WithCPUs() that would work with BasicFilter. But I don't see why it couldn't be the other way around, that is, we would do
NewFilterInput().SetCPU(...).SetCstateName(...)
Did you already consider and possibly already reject this kind of symmetry in BasicFilter and FilterInput?
Good point and I don't disagree. The primary reason for this asymmetry is that the NewFilterInput was originally not exported, only for internal use of the One gap to tackle is the case where no filtering is needed. Simplest would be allow Let's see if @klihub has some insights... |
1366e47 to
ebacf65
Compare
|
Updated. The FilterInput usage is now similar to the Filter usage (like |
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.
Suggesting some comments based on porting Balloons cstates on top of this.
With suggested changes, balloons cstates e2e test passes. This API feels very nice in real use. :)
pkg/cstates/filter.go
Outdated
| return true | ||
| } | ||
| func (f *BasicFilter) evaluateCPU(cpu *utils.ID) bool { | ||
| return cpu == nil || len(f.cpus) == 0 || f.cpus[*cpu] |
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.
If user has created NewBasicFilter().SetCPUs(), it should mean that none of the CPUs match. That is, while f.cpus == nil means we do not filter based on CPUs at all, f.cpus != nil && len(f.cpus) == 0 should mean that we filter based on CPUs, and we will not accept any CPU.
Suggestion: replace len(f.MAP) == 0 with f.MAP == nil in all evaluate...() functions.
I ran into this issue when porting current Balloons cstates draft on top of this patch. The draft takes 'disabledCstates' from user input, and creates a filtered copy like
onlyDisabled := all.Copy(NewBasicFilter().SetCstateNames(disabledCstates...))
If disabledCstates included no c-states, current filtering copied all cstates, yet it should not have copied any, and disabled all cstates as a consequence.
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.
Hmm, thanks for pointing this out. You're absolutely right and what you described was the original intention (usage of the pointer values) but I don't know why I used then length check. Will change.
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.
Fixed as suggested
pkg/cstates/filter.go
Outdated
| return !filter(cpuid, cstateName, attr, val) | ||
| } | ||
| func (f *BasicFilter) evaluateAttribute(attr *AttrID) bool { | ||
| return attr == nil || len(f.attributes) == 0 || f.attributes[*attr] != 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.
Suggestion: for clarity and robust implementation, let's have separate maps:
f.attributes map[AttrID]bool
f.attributeValues map[AttriID]map[string]bool
Trying to piggyback attribute-value filtering and attribute-only filtering into the same data structure leads into trouble. Currently we cannot distinguish between:
- NewBasicFilter().SetAttribute(AttrDisable)
- NewBasicFilter().SetAttributeValue(AttrDisable, emptySliceOfAcceptableValues...)
All disable values should pass the first filter, while no disable value should pass the second. Furthermore, the first filter should filter out all other but the disable attribute, while the second should allow reading all attributes and filter only based on observed disable values. However, current implementation creates exactly the same structure from these, that is f.attributes[AttrDisable] == map[string]bool{}.
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.
Ah, good good. I was actually struggling with this -- was back-and-forth with separate maps or single map -- as I was unsure what the preferable functionality for "filter attribute values" is. I.e. should it filter out other attributes or not. Will change 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.
second should allow reading all attributes and filter only based on observed
disablevalues
Hmm, this^ what you suggest actually breaks e.g. this test case:
goresctrl/pkg/cstates/cstates_test.go
Line 149 in 9695f5c
| c2Disabled := cs.Copy(FilterNames("C2", "C3", "C4"), FilterAttrValues(AttrDisable, "1")) |
Implementing your suggestion accepts any value for other attributes than AttrDisable -> returns all CPUs (that have the cstates specified). To make it work it would need to be like:
cs.Copy(NewBasicFilter().SetCstateNames("C2", "C3", "C4").SetAttributes(AttrDisable).SetAttributeValues(AttrDisable, "1"))
Is this logical? Maybe(?) Anyway, it would change the filtering from your original impl.
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 it is indeed more logical to fix the test as you suggest, that is:
cs.Copy(NewBasicFilter().SetCstateNames("C2", "C3", "C4").SetAttributes(AttrDisable).SetAttributeValues(AttrDisable, "1"))
Original filter implementation didn't make clear distinction between attribute and attribute-value filtering. No wonder it made you go back-and-forth in this design clean-up, too. I think it'll be easier to understand when these two filters do not interfere.
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.
Check, let's do it this way. I'll change the impl and tests and try to document it a bit better. I'll also add some unit tests for the BasicFilter itself.
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.
Done. Unit tests added
This patch is an attempt to improve the filtering api of the cstates package, make it more extensible and future proof and less kludge-ish. Changes explained: 1. Change the "filter" function type into an interface. More flexibility for future extensions and in setting up the filter. 2. Pass the input for evaluation as a struct instead of args. Makes future extensions to the "args" more flexible, without breaking existing filters. 3. Only take a single filter as an argument for the cstate functions (instead of passing multiple filters). This will remove the possible confusion on how multiple filters are combined (e.g. accidental disjoint sets like (FilterCPUs(1), FilterCPUs(10), ...). 4. Combine the separate filter functions into one "basic filter". Result of intel#3 (single filter arg).
ebacf65 to
377067c
Compare
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.
LGTM.
Thanks @marquiz! This is a very nice improvement to filtering, and it fixed unclear semantics of the original API!
This patch is an attempt to improve the filtering api of the cstates package, make it more extensible and future proof and less kludge-ish.
Changes explained: