Skip to content

Commit 4689852

Browse files
authored
core: add support for constant math (#1763)
Computes constants as the expression is being parsed and allows them to be used as a parameter.
1 parent 240269a commit 4689852

File tree

8 files changed

+155
-101
lines changed

8 files changed

+155
-101
lines changed

atlas-core/src/main/scala/com/netflix/atlas/core/model/MathExpr.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,14 @@ object MathExpr {
102102
val ts = TimeSeries(Map("name" -> v.toString), v.toString, seq)
103103
ResultSet(this, List(ts), context.state)
104104
}
105+
106+
/** Overridden to allow for NaN value to be equivalent. */
107+
override def equals(that: Any): Boolean = {
108+
that match {
109+
case other: Constant => java.lang.Double.compare(v, other.v) == 0
110+
case _ => false
111+
}
112+
}
105113
}
106114

107115
/**

atlas-core/src/main/scala/com/netflix/atlas/core/model/MathVocabulary.scala

Lines changed: 52 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -695,11 +695,23 @@ object MathVocabulary extends Vocabulary {
695695
case (_: StyleExpr) :: _ => true
696696
}
697697

698-
def newInstance(t: TimeSeriesExpr): TimeSeriesExpr
698+
def newInstance(t: TimeSeriesExpr): MathExpr.UnaryMathExpr
699+
700+
protected def execute(t: TimeSeriesExpr, stack: List[Any]): List[Any] = {
701+
t match {
702+
case c: MathExpr.Constant =>
703+
// Precompute math on constants
704+
MathExpr.Constant(newInstance(c)(c.v)) :: stack
705+
case _ =>
706+
newInstance(t) :: stack
707+
}
708+
}
699709

700710
protected def executor: PartialFunction[List[Any], List[Any]] = {
701-
case TimeSeriesType(t) :: stack => newInstance(t) :: stack
702-
case (t: StyleExpr) :: stack => t.copy(expr = newInstance(t.expr)) :: stack
711+
case TimeSeriesType(t) :: stack =>
712+
execute(t, stack)
713+
case (t: StyleExpr) :: stack =>
714+
t.copy(expr = newInstance(t.expr)) :: stack
703715
}
704716

705717
override def signature: String = "TimeSeriesExpr -- TimeSeriesExpr"
@@ -711,7 +723,7 @@ object MathVocabulary extends Vocabulary {
711723

712724
override def name: String = "abs"
713725

714-
def newInstance(t: TimeSeriesExpr): TimeSeriesExpr = MathExpr.Abs(t)
726+
def newInstance(t: TimeSeriesExpr): MathExpr.UnaryMathExpr = MathExpr.Abs(t)
715727

716728
override def summary: String =
717729
"""
@@ -724,7 +736,7 @@ object MathVocabulary extends Vocabulary {
724736

725737
override def name: String = "neg"
726738

727-
def newInstance(t: TimeSeriesExpr): TimeSeriesExpr = MathExpr.Negate(t)
739+
def newInstance(t: TimeSeriesExpr): MathExpr.UnaryMathExpr = MathExpr.Negate(t)
728740

729741
override def summary: String =
730742
"""
@@ -737,7 +749,7 @@ object MathVocabulary extends Vocabulary {
737749

738750
override def name: String = "sin"
739751

740-
def newInstance(t: TimeSeriesExpr): TimeSeriesExpr = MathExpr.Sine(t)
752+
def newInstance(t: TimeSeriesExpr): MathExpr.UnaryMathExpr = MathExpr.Sine(t)
741753

742754
override def summary: String =
743755
"""
@@ -750,7 +762,7 @@ object MathVocabulary extends Vocabulary {
750762

751763
override def name: String = "sqrt"
752764

753-
def newInstance(t: TimeSeriesExpr): TimeSeriesExpr = MathExpr.Sqrt(t)
765+
def newInstance(t: TimeSeriesExpr): MathExpr.UnaryMathExpr = MathExpr.Sqrt(t)
754766

755767
override def summary: String =
756768
"""
@@ -763,7 +775,12 @@ object MathVocabulary extends Vocabulary {
763775

764776
override def name: String = "per-step"
765777

766-
def newInstance(t: TimeSeriesExpr): TimeSeriesExpr = MathExpr.PerStep(t)
778+
def newInstance(t: TimeSeriesExpr): MathExpr.UnaryMathExpr = MathExpr.PerStep(t)
779+
780+
override protected def execute(t: TimeSeriesExpr, stack: List[Any]): List[Any] = {
781+
// Cannot be precomputed as the step size depends on the execution context
782+
newInstance(t) :: stack
783+
}
767784

768785
override def summary: String =
769786
"""
@@ -782,11 +799,21 @@ object MathVocabulary extends Vocabulary {
782799
case (_: StyleExpr) :: (_: StyleExpr) :: _ => true
783800
}
784801

785-
def newInstance(t1: TimeSeriesExpr, t2: TimeSeriesExpr): TimeSeriesExpr
802+
def newInstance(t1: TimeSeriesExpr, t2: TimeSeriesExpr): MathExpr.BinaryMathExpr
803+
804+
private def execute(t1: TimeSeriesExpr, t2: TimeSeriesExpr, stack: List[Any]): List[Any] = {
805+
(t1, t2) match {
806+
case (c1: MathExpr.Constant, c2: MathExpr.Constant) =>
807+
// Precompute math on constants
808+
MathExpr.Constant(newInstance(c1, c2)(c1.v, c2.v)) :: stack
809+
case _ =>
810+
newInstance(t1, t2) :: stack
811+
}
812+
}
786813

787814
protected def executor: PartialFunction[List[Any], List[Any]] = {
788815
case TimeSeriesType(t2) :: TimeSeriesType(t1) :: stack =>
789-
newInstance(t1, t2) :: stack
816+
execute(t1, t2, stack)
790817
case (t2: StyleExpr) :: TimeSeriesType(t1) :: stack =>
791818
t2.copy(expr = newInstance(t1, t2.expr)) :: stack
792819
case TimeSeriesType(t2) :: (t1: StyleExpr) :: stack =>
@@ -807,7 +834,7 @@ object MathVocabulary extends Vocabulary {
807834

808835
override def name: String = "add"
809836

810-
def newInstance(t1: TimeSeriesExpr, t2: TimeSeriesExpr): TimeSeriesExpr = {
837+
def newInstance(t1: TimeSeriesExpr, t2: TimeSeriesExpr): MathExpr.BinaryMathExpr = {
811838
MathExpr.Add(t1, t2)
812839
}
813840

@@ -829,7 +856,7 @@ object MathVocabulary extends Vocabulary {
829856

830857
override def name: String = "sub"
831858

832-
def newInstance(t1: TimeSeriesExpr, t2: TimeSeriesExpr): TimeSeriesExpr = {
859+
def newInstance(t1: TimeSeriesExpr, t2: TimeSeriesExpr): MathExpr.BinaryMathExpr = {
833860
MathExpr.Subtract(t1, t2)
834861
}
835862

@@ -851,7 +878,7 @@ object MathVocabulary extends Vocabulary {
851878

852879
override def name: String = "mul"
853880

854-
def newInstance(t1: TimeSeriesExpr, t2: TimeSeriesExpr): TimeSeriesExpr = {
881+
def newInstance(t1: TimeSeriesExpr, t2: TimeSeriesExpr): MathExpr.BinaryMathExpr = {
855882
MathExpr.Multiply(t1, t2)
856883
}
857884

@@ -866,7 +893,7 @@ object MathVocabulary extends Vocabulary {
866893

867894
override def name: String = "div"
868895

869-
def newInstance(t1: TimeSeriesExpr, t2: TimeSeriesExpr): TimeSeriesExpr = {
896+
def newInstance(t1: TimeSeriesExpr, t2: TimeSeriesExpr): MathExpr.BinaryMathExpr = {
870897
MathExpr.Divide(t1, t2)
871898
}
872899

@@ -890,7 +917,7 @@ object MathVocabulary extends Vocabulary {
890917

891918
override def name: String = "pow"
892919

893-
def newInstance(t1: TimeSeriesExpr, t2: TimeSeriesExpr): TimeSeriesExpr = {
920+
def newInstance(t1: TimeSeriesExpr, t2: TimeSeriesExpr): MathExpr.BinaryMathExpr = {
894921
MathExpr.Power(t1, t2)
895922
}
896923

@@ -905,7 +932,7 @@ object MathVocabulary extends Vocabulary {
905932

906933
override def name: String = "gt"
907934

908-
def newInstance(t1: TimeSeriesExpr, t2: TimeSeriesExpr): TimeSeriesExpr = {
935+
def newInstance(t1: TimeSeriesExpr, t2: TimeSeriesExpr): MathExpr.BinaryMathExpr = {
909936
MathExpr.GreaterThan(t1, t2)
910937
}
911938

@@ -920,7 +947,7 @@ object MathVocabulary extends Vocabulary {
920947

921948
override def name: String = "ge"
922949

923-
def newInstance(t1: TimeSeriesExpr, t2: TimeSeriesExpr): TimeSeriesExpr = {
950+
def newInstance(t1: TimeSeriesExpr, t2: TimeSeriesExpr): MathExpr.BinaryMathExpr = {
924951
MathExpr.GreaterThanEqual(t1, t2)
925952
}
926953

@@ -935,7 +962,7 @@ object MathVocabulary extends Vocabulary {
935962

936963
override def name: String = "lt"
937964

938-
def newInstance(t1: TimeSeriesExpr, t2: TimeSeriesExpr): TimeSeriesExpr = {
965+
def newInstance(t1: TimeSeriesExpr, t2: TimeSeriesExpr): MathExpr.BinaryMathExpr = {
939966
MathExpr.LessThan(t1, t2)
940967
}
941968

@@ -950,7 +977,7 @@ object MathVocabulary extends Vocabulary {
950977

951978
override def name: String = "le"
952979

953-
def newInstance(t1: TimeSeriesExpr, t2: TimeSeriesExpr): TimeSeriesExpr = {
980+
def newInstance(t1: TimeSeriesExpr, t2: TimeSeriesExpr): MathExpr.BinaryMathExpr = {
954981
MathExpr.LessThanEqual(t1, t2)
955982
}
956983

@@ -965,7 +992,7 @@ object MathVocabulary extends Vocabulary {
965992

966993
override def name: String = "fadd"
967994

968-
def newInstance(t1: TimeSeriesExpr, t2: TimeSeriesExpr): TimeSeriesExpr = {
995+
def newInstance(t1: TimeSeriesExpr, t2: TimeSeriesExpr): MathExpr.BinaryMathExpr = {
969996
MathExpr.FAdd(t1, t2)
970997
}
971998

@@ -992,7 +1019,7 @@ object MathVocabulary extends Vocabulary {
9921019

9931020
override def name: String = "fsub"
9941021

995-
def newInstance(t1: TimeSeriesExpr, t2: TimeSeriesExpr): TimeSeriesExpr = {
1022+
def newInstance(t1: TimeSeriesExpr, t2: TimeSeriesExpr): MathExpr.BinaryMathExpr = {
9961023
MathExpr.FSubtract(t1, t2)
9971024
}
9981025

@@ -1019,7 +1046,7 @@ object MathVocabulary extends Vocabulary {
10191046

10201047
override def name: String = "fmul"
10211048

1022-
def newInstance(t1: TimeSeriesExpr, t2: TimeSeriesExpr): TimeSeriesExpr = {
1049+
def newInstance(t1: TimeSeriesExpr, t2: TimeSeriesExpr): MathExpr.BinaryMathExpr = {
10231050
MathExpr.FMultiply(t1, t2)
10241051
}
10251052

@@ -1034,7 +1061,7 @@ object MathVocabulary extends Vocabulary {
10341061

10351062
override def name: String = "fdiv"
10361063

1037-
def newInstance(t1: TimeSeriesExpr, t2: TimeSeriesExpr): TimeSeriesExpr = {
1064+
def newInstance(t1: TimeSeriesExpr, t2: TimeSeriesExpr): MathExpr.BinaryMathExpr = {
10381065
MathExpr.FDivide(t1, t2)
10391066
}
10401067

@@ -1061,7 +1088,7 @@ object MathVocabulary extends Vocabulary {
10611088

10621089
override def name: String = "and"
10631090

1064-
def newInstance(t1: TimeSeriesExpr, t2: TimeSeriesExpr): TimeSeriesExpr = {
1091+
def newInstance(t1: TimeSeriesExpr, t2: TimeSeriesExpr): MathExpr.BinaryMathExpr = {
10651092
MathExpr.And(t1, t2)
10661093
}
10671094

@@ -1076,7 +1103,7 @@ object MathVocabulary extends Vocabulary {
10761103

10771104
override def name: String = "or"
10781105

1079-
def newInstance(t1: TimeSeriesExpr, t2: TimeSeriesExpr): TimeSeriesExpr = {
1106+
def newInstance(t1: TimeSeriesExpr, t2: TimeSeriesExpr): MathExpr.BinaryMathExpr = {
10801107
MathExpr.Or(t1, t2)
10811108
}
10821109

atlas-core/src/main/scala/com/netflix/atlas/core/stacklang/Extractors.scala

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,25 +15,29 @@
1515
*/
1616
package com.netflix.atlas.core.stacklang
1717

18+
import com.netflix.atlas.core.model.MathExpr
19+
1820
import scala.util.Try
1921

2022
object Extractors {
2123

2224
case object IntType {
2325

2426
def unapply(value: Any): Option[Int] = value match {
25-
case v: String => Try(v.toInt).toOption
26-
case v: Int => Some(v)
27-
case _ => None
27+
case v: String => Try(v.toInt).toOption
28+
case v: MathExpr.Constant => Some(v.v.toInt)
29+
case v: Int => Some(v)
30+
case _ => None
2831
}
2932
}
3033

3134
case object DoubleType {
3235

3336
def unapply(value: Any): Option[Double] = value match {
34-
case v: String => Try(v.toDouble).toOption
35-
case v: Double => Some(v)
36-
case _ => None
37+
case v: String => Try(v.toDouble).toOption
38+
case v: MathExpr.Constant => Some(v.v)
39+
case v: Double => Some(v)
40+
case _ => None
3741
}
3842
}
3943
}

atlas-core/src/test/scala/com/netflix/atlas/core/model/MathAcrossStyleSuite.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ class MathAcrossStyleSuite extends FunSuite {
4242
.filter(_.isInstanceOf[MathVocabulary.UnaryWord])
4343
.foreach { w =>
4444
test(s"${w.name}, StyleExpr op") {
45-
val expected = eval(s"1,:${w.name},abc,:legend")
46-
val actual = eval(s"1,abc,:legend,:${w.name}")
45+
val expected = eval(s"a,:has,:sum,:${w.name},abc,:legend")
46+
val actual = eval(s"a,:has,:sum,abc,:legend,:${w.name}")
4747
assertEquals(actual, expected)
4848
}
4949
}

atlas-core/src/test/scala/com/netflix/atlas/core/model/MathExamplesSuite.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,14 @@ class MathExamplesSuite extends BaseExamplesSuite {
4949
"name,test,:eq,app,foo,:eq,:and,:dist-avg,PT1H,:offset,(,cluster,),:by"
5050
)
5151
}
52+
53+
test("constant math: unary expressions") {
54+
val expr = eval("name,test,:eq,:sum,5,:neg,:abs,:mul")
55+
assertEquals(expr.toString, "name,test,:eq,:sum,5.0,:const,:mul")
56+
}
57+
58+
test("constant math: binary expressions") {
59+
val expr = eval("name,test,:eq,:sum,5,4,:mul,2,:add,:div")
60+
assertEquals(expr.toString, "name,test,:eq,:sum,22.0,:const,:div")
61+
}
5262
}

atlas-core/src/test/scala/com/netflix/atlas/core/model/StatefulExamplesSuite.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,9 @@ class StatefulExamplesSuite extends BaseExamplesSuite {
2626
val expr = eval("name,test,:eq,:sum,:des-fast,app,foo,:eq,:cq")
2727
assertEquals(expr.toString, "name,test,:eq,app,foo,:eq,:and,:sum,:des-fast")
2828
}
29+
30+
test("constant math: use as parameter") {
31+
val expr = eval("name,test,:eq,:sum,3600,60,:div,:rolling-count")
32+
assertEquals(expr.toString, "name,test,:eq,:sum,60,:rolling-count")
33+
}
2934
}

0 commit comments

Comments
 (0)