Skip to content

Commit

Permalink
Add a :contains predicate filter for compatibility with internal lo…
Browse files Browse the repository at this point in the history
…g queries.

Not meant to be used for metrics as it executes a full scan.
Bump Spectator to 1.3.8
  • Loading branch information
manolama committed Sep 27, 2022
1 parent 07cb339 commit 12abae5
Show file tree
Hide file tree
Showing 7 changed files with 77 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import com.netflix.atlas.core.stacklang.SimpleWord
import com.netflix.atlas.core.stacklang.StandardVocabulary
import com.netflix.atlas.core.stacklang.Vocabulary
import com.netflix.atlas.core.stacklang.Word
import com.netflix.spectator.impl.matcher.PatternUtils

object QueryVocabulary extends Vocabulary {

Expand All @@ -39,6 +40,7 @@ object QueryVocabulary extends Vocabulary {
GreaterThanEqual,
Regex,
RegexIgnoreCase,
Contains,
In,
And,
Or,
Expand Down Expand Up @@ -329,6 +331,38 @@ object QueryVocabulary extends Vocabulary {
List("name,DiscoveryStatus_(UP|DOWN)", "name,discoverystatus_(Up|Down)", "ERROR:name")
}

case object Contains extends KeyValueWord {

override def name: String = "contains"

def newInstance(k: String, v: String): Query = Query.Regex(k, s".*${PatternUtils.escape(v)}")

override def summary: String =
"""
|Query expression that matches time series with a value that contains the given
|sequence of characters. This version is case sensitive.
|
|> :warning: This operation always requires a full scan and should be avoided if at all
|possible. Queries using this operation may be de-priortized.
|
|Suppose you have four time series:
|
|* `name=http.requests, status=200, nf.app=server`
|* `name=sys.cpu, type=user, nf.app=foo`
|* `name=sys.cpu, type=user, nf.app=bar`
|* `name=sys.cpu, type=user, nf.app=foobar`
|
|The query `nf.app,bar,:contains` would match series with "bar" anywhere in
|the string:
|
|* `name=sys.cpu, type=user, nf.app=bar`
|* `name=sys.cpu, type=user, nf.app=foobar`
""".stripMargin.trim

override def examples: List[String] =
List("name,request", "result,error")
}

case object In extends SimpleWord {

override def name: String = "in"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ class MemoryDatabaseSuite extends FunSuite {
assertEquals(exec("name,[ab]$,:re"), List(ts("sum(name~/^[ab]$/)", 1, 4.0, 4.0, 4.0)))
}

test(":contains query") {
assertEquals(exec("name,a,:contains"), List(ts("sum(name~/^.*a/)", 1, 1.0, 2.0, 3.0)))
}

test(":has query") {
assertEquals(exec("name,:has"), List(ts("sum(has(name))", 1, 19.0, 22.0, 25.0)))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class ModelExtractorsSuite extends FunSuite {
}

completionTest("name", 8)
completionTest("name,sps", 19)
completionTest("name,sps", 20)
completionTest("name,sps,:eq", 20)
completionTest("name,sps,:eq,app,foo,:eq", 41)
completionTest("name,sps,:eq,app,foo,:eq,:and,(,asg,)", 12)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ class QuerySuite extends FunSuite {
}

test("matchesAny re with key match") {
val q = GreaterThan("foo", "b")
val q = Regex("foo", "b")
assert(matchesAny(q, Map("foo" -> List("bar"), "bar" -> List("foo"))))
assert(matchesAny(q, Map("foo" -> List("foo", "bar"), "bar" -> List("foo"))))
assert(matchesAny(q, Map("foo" -> List("bar", "baz"), "bar" -> List("foo"))))
Expand Down Expand Up @@ -650,4 +650,5 @@ class QuerySuite extends FunSuite {
val q = Or(Equal("a", "1"), In("b", List("1", "2")))
assertEquals(Query.expandInClauses(q, 1), List(q))
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.netflix.atlas.core.model

import com.netflix.atlas.core.model.Query.Regex
import com.netflix.atlas.core.stacklang.Interpreter
import munit.FunSuite

class QueryVocabularySuite extends FunSuite {

val interpreter = new Interpreter(QueryVocabulary.allWords)

test("contains, escape") {
var exp = interpreter.execute("a,^$.?*+[](){}\\#&!%,:contains").stack(0)
assertEquals(
exp.asInstanceOf[Regex].pattern.toString,
".*\\^\\$\\.\\?\\*\\+\\[\\]\\(\\)\\{\\}\\\\#&!%"
)
exp = interpreter.execute("a,space and ~,:contains").stack(0)
assertEquals(
exp.asInstanceOf[Regex].pattern.toString,
".*space\\u0020and\\u0020~"
)
}

test("contains, matches escaped") {
val q = interpreter
.execute("foo,my $var. [work-in-progress],:contains")
.stack(0)
.asInstanceOf[Regex]
assert(q.matches(Map("foo" -> "my $var. [work-in-progress]")))
assert(q.matches(Map("foo" -> "initialize my $var. [work-in-progress], not a range")))
assert(!q.matches(Map("foo" -> "my $var. [work-in progress]")))
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class TimeSeriesExprSuite extends FunSuite {
"name,1,:eq" -> const(ts(Map("name" -> "1"), 1)),
"name,1,:re" -> const(ts(unknownTag, 11)),
"name,2,:re" -> const(ts(unknownTag, 2)),
"name,2,:contains" -> const(ts(unknownTag, 2)),
"name,(,1,10,),:in" -> const(ts(unknownTag, 11)),
"name,1,:eq,name,10,:eq,:or" -> const(ts(unknownTag, 11)),
":true,:abs" -> const(ts(unknownTag, "abs(name=unknown)", 55.0)),
Expand Down
2 changes: 1 addition & 1 deletion project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ object Dependencies {
val log4j = "2.18.0"
val scala = "2.13.8"
val slf4j = "1.7.36"
val spectator = "1.3.7"
val spectator = "1.3.8"
val spring = "5.3.22"

val crossScala = Seq(scala)
Expand Down

0 comments on commit 12abae5

Please sign in to comment.