-
Notifications
You must be signed in to change notification settings - Fork 453
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 scalar block and binary functions #822
Conversation
TODOs: - comparison functions - matching for non-scalar lhs and rhs sides - unit tests - consolidate with logical funcs
@@ -0,0 +1,150 @@ | |||
// Copyright (c) 2018 Uber Technologies, Inc. |
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.
call it scalar.go since its already in block package?
}, nil | ||
} | ||
|
||
func (b *scalarBlock) SeriesIter() (SeriesIter, error) { return nil, 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.
dont you want to return scalarSeriesIter?
// StepIter returns a StepIterator | ||
func (b *scalarBlock) StepIter() (StepIter, error) { | ||
bounds := b.meta.Bounds | ||
steps := bounds.End.Sub(bounds.Start) / bounds.StepSize |
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 you define Steps() on Bounds struct itself ? A multiple places in code will need it
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.
Turns out Bounds.Steps()
exists already :) using that instead
@@ -0,0 +1,232 @@ | |||
// Copyright (c) 2018 Uber Technologies, Inc. |
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.
node.go?
Codecov Report
@@ Coverage Diff @@
## master #822 +/- ##
=========================================
- Coverage 78.42% 78.33% -0.1%
=========================================
Files 375 379 +4
Lines 32247 32595 +348
=========================================
+ Hits 25289 25532 +243
- Misses 5239 5319 +80
- Partials 1719 1744 +25
Continue to review full report at Codecov.
|
src/query/block/scalar.go
Outdated
|
||
// ScalarBlock is a block containing a single value | ||
// over a certain bound | ||
type ScalarBlock 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.
call it Scalar since its already in block package. Also do you really need to expose it outside ? Seems like you outside functions should just work on the Block 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.
I cast to Scalar block sometimes as an optimization to pull out the value for the entire block, rather than having to step through each value one by one. Will rename
src/query/block/scalar.go
Outdated
|
||
// NewScalarBlockResult creates a Result consisting | ||
// of scalar blocks over the bound | ||
func NewScalarBlockResult(val float64, bounds Bounds) *Result { |
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.
why does result need to know about scalar block ?
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 was mirroring how fetch performed executions in NewScalarOp; changed it a bit to only build a single block and use that instead.
src/query/block/scalar.go
Outdated
"github.com/m3db/m3/src/query/models" | ||
) | ||
|
||
// ScalarBlock is a block containing a single value |
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 you add a comment as to when this would be useful ?
} | ||
|
||
func (it *scalarStepIter) Current() (Step, error) { | ||
if it.idx >= it.numVals || it.idx < 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.
we haven't traditionally been checking this in Current() usually rely on people to call Next() before Current
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 in general, but since this doesn't do any real "bounds" checking and just returns a value, the alternative is that this panics
in this check, as it really shouldn't be returning anything if Next() is false.
I prefer to error than to panic here, but if you have particular preference towards panicing I can change to that
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.
other places have panics instead of errors since its a way to avoid more codepaths :( I guess we can do either here since its returning error but a lot of times Current() only returns data and panic is the only option.
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, if it weren't already able to return an error I would've paniced, but figured might as well make use of the capability here so we don't kill the node :)
} | ||
|
||
func (it *scalarSeriesIter) Current() (Series, error) { | ||
if it.idx != 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.
we haven't traditionally been checking this in Current() usually rely on people to call Next() before Current
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 in general, but since this doesn't do any real "bounds" checking and just returns a value, the alternative is that this panics
in this check, as it really shouldn't be returning anything if Next() is false.
I prefer to error than to panic here, but if you have particular preference towards panicing I can change to that
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.
same reply as above
} | ||
|
||
// computeOrCache figures out if both lhs and rhs are available, if not then it caches the incoming block | ||
func (n *binaryNode) computeOrCache(ID parser.NodeID, b block.Block) (block.Block, block.Block, error) { |
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.
is this the same as the other place ? If yes, then think about refactor
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, this falls under:
TODO: consolidate binary node with logical node, as a lot of behaviour
is shared.
Might do that in a separate commit once this is in. No other binary functions are planned in the near future so shouldn't affect any upcoming commits
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.
cool, was just checking
src/query/functions/binary/node.go
Outdated
|
||
// intersect returns the slice of lhs indices that are shared with rhs, | ||
// the indices of the corresponding rhs values, and the metas for taken indices | ||
func intersect( |
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.
is this the same as the other place ? If yes, then think about refactor
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's slightly different in that this one is designed to be able to drop series which do not match, consistent with prom behaviour here.
src/query/functions/binary/node.go
Outdated
|
||
// If returnBool is false and op is a comparison function, | ||
// the function should return the scalar value rather than 1 or 0 | ||
func actualScalarFunc(fn singleScalarFunc, op string, returnBool bool) singleScalarFunc { |
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.
naming :(
src/query/functions/binary/node.go
Outdated
// If returnBool is false and op is a comparison function, | ||
// the function should return the scalar value of lhs if the comparison | ||
// is true; otherwise, it should return 1 if true, and 0 if false | ||
func actualFunc(fn mathFunc, op string, returnBool bool) mathFunc { |
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.
naming :(
src/query/functions/binary/node.go
Outdated
} | ||
|
||
if info.RIsScalar { | ||
scalarR, ok := rhs.(*block.ScalarBlock) |
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.
some duplication here:
maybe create a function which takes two block.Block, assumes first can be casted into scalar and other can't be. Then you can use the same function for this case and one of the previous cases
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'm not sure that would reduce duplication much, and may make it a bit more confusing since I'd need to use some kind of scalarLeft bool
; it also introduces a couple of extra bool checks.
All I'd be saving by using a function for this would be the cast to *block.ScalarBlock
, since the singleScalarFunction would be different for either branch
src/query/block/scalar.go
Outdated
) | ||
|
||
// Scalar is a block containing a single value | ||
// over a certain bound |
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.
nit: add this to end of first line
src/query/block/scalar.go
Outdated
type scalarStepIter struct { | ||
meta Metadata | ||
step scalarStep | ||
numVals int |
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.
nit: numVals, idx int
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.
Looks a bit uglier but ok :p
src/query/block/scalar.go
Outdated
var _ SeriesIter = (*scalarSeriesIter)(nil) | ||
|
||
func (it *scalarSeriesIter) Close() { /* No-op*/ } | ||
func (it *scalarSeriesIter) SeriesCount() int { return 1 } |
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 kind of weird- wouldn't this always be 1
in all SeriesIters
?
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 a SeriesIter
is created from a block with multiple series, SeriesCount
would be the number of the series in that block; in the case of a scalar block, there's only one "series" which has val
as the value at each step.
|
||
var ( | ||
start = time.Time{} | ||
val = 13.37 |
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 like 100
or something simple?
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.
Wanted to make it explicitly a float with decimal places
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.
math.Pi is also an option
|
||
const ( | ||
// ******************************************* | ||
// * Arithmetic 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.
What's going on with the *
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.
Just visual delineation
return n.processSingleBlock( | ||
rhs, | ||
func(x float64) float64 { | ||
return n.op.fn(lVal, x) |
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 you add a comment here?
lIter, rIter block.StepIter, | ||
) (block.Block, error) { | ||
lMeta, rMeta := lIter.Meta(), rIter.Meta() | ||
|
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 to make sure bounds are equal
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 we're doing the check, should that be in process() ?
return nil, err | ||
} | ||
|
||
lValues := lStep.Values() |
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.
Get the values right before you use them below instead of here
src/query/functions/binary/node.go
Outdated
return nil, err | ||
} | ||
|
||
rValues := rStep.Values() |
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.
Same here
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 one's already right before it's used
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.
Oh yeah lol
src/query/parser/promql/parse.go
Outdated
@@ -176,6 +179,18 @@ func (p *parseState) walk(node pql.Node) error { | |||
p.transforms = append(p.transforms, opTransform) | |||
return nil | |||
|
|||
case *pql.NumberLiteral: | |||
op := NewScalarOperator(n) | |||
|
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.
nit: remove newline
func TestDAGWithAbsentOp(t *testing.T) { | ||
q := "absent(http_requests_total{method=\"GET\"})" | ||
p, err := Parse(q) | ||
var linearParseTests = []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.
Nice!!
@@ -178,6 +239,10 @@ func promVectorCardinalityToM3(card promql.VectorMatchCardinality) logical.Vecto | |||
} | |||
|
|||
func promMatchingToM3(vectorMatching *promql.VectorMatching) *logical.VectorMatching { | |||
// vectorMatching can be nil iff at least one of the sides is a scalar |
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.
nit: s/iff/if
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's intentional
src/query/block/scalar.go
Outdated
} | ||
|
||
// assert that Scalar implements Block | ||
var _ Block = (*Scalar)(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.
you don't need it since NewScalar returns Block
src/query/block/scalar.go
Outdated
} | ||
|
||
// assert that scalarStepIter implements StepIter | ||
var _ StepIter = (*scalarStepIter)(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.
you don't need it since StepIter() returns & scalarStepIter
} | ||
|
||
func (it *scalarStepIter) Current() (Step, error) { | ||
if it.idx >= it.numVals || it.idx < 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.
other places have panics instead of errors since its a way to avoid more codepaths :( I guess we can do either here since its returning error but a lot of times Current() only returns data and panic is the only option.
src/query/block/scalar.go
Outdated
} | ||
|
||
// assert that scalarStepIter implements StepIter | ||
var _ Step = (*scalarStep)(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.
don't think you need this
src/query/block/scalar.go
Outdated
} | ||
|
||
// assert that scalarStepIter implements StepIter | ||
var _ SeriesIter = (*scalarSeriesIter)(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.
same as above
src/query/executor/state.go
Outdated
options transform.Options, | ||
) (parser.Source, *transform.Controller) { | ||
controller := &transform.Controller{ID: ID} | ||
|
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.
extra line ?
@@ -47,6 +47,15 @@ type TimeSpec struct { | |||
Step time.Duration | |||
} | |||
|
|||
// ToBounds transforms a timespec to bounds | |||
func (ts TimeSpec) ToBounds() block.Bounds { |
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.
Bounds() ?
lIter, rIter block.StepIter, | ||
) (block.Block, error) { | ||
lMeta, rMeta := lIter.Meta(), rIter.Meta() | ||
|
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 we're doing the check, should that be in process() ?
} | ||
|
||
// computeOrCache figures out if both lhs and rhs are available, if not then it caches the incoming block | ||
func (n *binaryNode) computeOrCache(ID parser.NodeID, b block.Block) (block.Block, block.Block, error) { |
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.
cool, was just checking
src/query/functions/binary/node.go
Outdated
n.cache.Remove(n.op.params.RNode) | ||
} | ||
|
||
const initIndexSliceLength = 10 |
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.
makes sense to move it to the top ? We might want to put some of these things in the config later.
Added scalar block type for parsing prom constant values
Arithmetic functions added (+, -, *, /, ^, %) for scalar-scalar,
scalar-series, series-scalar, series-series left and right node values
Comparison functions added (==, !=, >, <, >=, <=) for scalar-scalar,
scalar-series, series-scalar, series-series left and right node values �
Added unit tests for all permutations of comparison and arithmetic functions
Still TODO: consolidate binary node with logical node, as a lot of behaviour
is shared.