frostfs-sdk-go/netmap/filter.go
Evgenii Stratonikov d282cd094f
All checks were successful
DCO / DCO (pull_request) Successful in 23s
Code generation / Generate proto (pull_request) Successful in 1m27s
Tests and linters / Tests (pull_request) Successful in 1m27s
Tests and linters / Lint (pull_request) Successful in 2m35s
[#355] netmap: Cache price and capacity attributes
They are used by HRW sorting and it makes sense to store them separately
instead of iterating over all attributes each time we need them.
It also simplifies code: we already parse them in NodeInfo.readFromV2(),
so just save the result.

```
goos: linux
goarch: amd64
pkg: git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap
cpu: 11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz
                                                              │     old     │                 new                 │
                                                              │   sec/op    │   sec/op     vs base                │
Netmap_ContainerNodes/REP_2-8                                   5.923µ ± 0%   5.209µ ± 1%  -12.05% (p=0.000 n=10)
Netmap_ContainerNodes/REP_2_IN_X_CBF_2_SELECT_2_FROM_*_AS_X-8   5.931µ ± 7%   5.088µ ± 1%  -14.22% (p=0.000 n=10)
geomean                                                         5.927µ        5.148µ       -13.14%

                                                              │     old      │                 new                 │
                                                              │     B/op     │     B/op      vs base               │
Netmap_ContainerNodes/REP_2-8                                   7.609Ki ± 0%   8.172Ki ± 0%  +7.39% (p=0.000 n=10)
Netmap_ContainerNodes/REP_2_IN_X_CBF_2_SELECT_2_FROM_*_AS_X-8   7.031Ki ± 0%   7.469Ki ± 0%  +6.22% (p=0.000 n=10)
geomean                                                         7.315Ki        7.812Ki       +6.81%

                                                              │    old     │                 new                 │
                                                              │ allocs/op  │ allocs/op   vs base                 │
Netmap_ContainerNodes/REP_2-8                                   77.00 ± 0%   77.00 ± 0%       ~ (p=1.000 n=10) ¹
Netmap_ContainerNodes/REP_2_IN_X_CBF_2_SELECT_2_FROM_*_AS_X-8   77.00 ± 0%   77.00 ± 0%       ~ (p=1.000 n=10) ¹
geomean                                                         77.00        77.00       +0.00%
¹ all samples are equal
```

Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2025-04-07 17:47:08 +03:00

165 lines
3.9 KiB
Go

package netmap
import (
"fmt"
"strconv"
"strings"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/netmap"
)
// mainFilterName is a name of the filter
// which points to the whole netmap.
const mainFilterName = "*"
const likeWildcard = "*"
// processFilters processes filters and returns error is any of them is invalid.
func (c *context) processFilters(p PlacementPolicy) error {
for i := range p.filters {
if err := c.processFilter(p.filters[i], true); err != nil {
return fmt.Errorf("process filter #%d (%s): %w", i, p.filters[i].GetName(), err)
}
}
return nil
}
func (c *context) processFilter(f netmap.Filter, top bool) error {
fName := f.GetName()
if fName == mainFilterName {
return fmt.Errorf("%w: '%s' is reserved", errInvalidFilterName, mainFilterName)
}
if top && fName == "" {
return errUnnamedTopFilter
}
if !top && fName != "" && c.processedFilters[fName] == nil {
return errFilterNotFound
}
inner := f.GetFilters()
switch op := f.GetOp(); op {
case netmap.AND, netmap.OR, netmap.NOT:
for i := range inner {
if err := c.processFilter(inner[i], false); err != nil {
return fmt.Errorf("process inner filter #%d: %w", i, err)
}
}
default:
if len(inner) != 0 {
return errNonEmptyFilters
} else if !top && fName != "" { // named reference
return nil
}
switch op {
case netmap.EQ, netmap.NE, netmap.LIKE:
case netmap.GT, netmap.GE, netmap.LT, netmap.LE:
val := f.GetValue()
n, err := strconv.ParseUint(val, 10, 64)
if err != nil {
return fmt.Errorf("%w: '%s'", errInvalidNumber, f.GetValue())
}
c.numCache[val] = n
default:
return fmt.Errorf("%w: %s", errInvalidFilterOp, op)
}
}
if top {
c.processedFilters[fName] = &f
}
return nil
}
// match matches f against b. It returns no errors because
// filter should have been parsed during context creation
// and missing node properties are considered as a regular fail.
func (c *context) match(f *netmap.Filter, b NodeInfo) bool {
switch f.GetOp() {
case netmap.NOT:
inner := f.GetFilters()
fSub := &inner[0]
if name := inner[0].GetName(); name != "" {
fSub = c.processedFilters[name]
}
return !c.match(fSub, b)
case netmap.AND, netmap.OR:
inner := f.GetFilters()
for i := range inner {
fSub := &inner[i]
if name := inner[i].GetName(); name != "" {
fSub = c.processedFilters[name]
}
ok := c.match(fSub, b)
if ok == (f.GetOp() == netmap.OR) {
return ok
}
}
return f.GetOp() == netmap.AND
default:
return c.matchKeyValue(f, b)
}
}
func (c *context) matchKeyValue(f *netmap.Filter, b NodeInfo) bool {
switch op := f.GetOp(); op {
case netmap.EQ:
return b.Attribute(f.GetKey()) == f.GetValue()
case netmap.LIKE:
str, prefix := strings.CutPrefix(f.GetValue(), likeWildcard)
str, suffix := strings.CutSuffix(str, likeWildcard)
if prefix && suffix {
return strings.Contains(b.Attribute(f.GetKey()), str)
}
if prefix && !suffix {
return strings.HasSuffix(b.Attribute(f.GetKey()), str)
}
if !prefix && suffix {
return strings.HasPrefix(b.Attribute(f.GetKey()), str)
}
return b.Attribute(f.GetKey()) == f.GetValue()
case netmap.NE:
return b.Attribute(f.GetKey()) != f.GetValue()
default:
var attr uint64
switch f.GetKey() {
case attrPrice:
attr = b.price
case attrCapacity:
attr = b.capacity
default:
var err error
attr, err = strconv.ParseUint(b.Attribute(f.GetKey()), 10, 64)
if err != nil {
// Note: because filters are somewhat independent from nodes attributes,
// We don't report an error here, and fail filter instead.
return false
}
}
switch op {
case netmap.GT:
return attr > c.numCache[f.GetValue()]
case netmap.GE:
return attr >= c.numCache[f.GetValue()]
case netmap.LT:
return attr < c.numCache[f.GetValue()]
case netmap.LE:
return attr <= c.numCache[f.GetValue()]
default:
// do nothing and return false
}
}
// will not happen if context was created from f (maybe panic?)
return false
}