Skip to content

Commit

Permalink
Add :contains predicate for compatibility with log queries (Netflix…
Browse files Browse the repository at this point in the history
…#1470)

Adds support for `:contains` which is frequently used with some
log queries internally. Bump Spectator to 1.3.8
  • Loading branch information
manolama committed May 22, 2024
1 parent 02ece6c commit bcd5d79
Show file tree
Hide file tree
Showing 7 changed files with 92 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,49 @@
/*
* Copyright 2014-2022 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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 bcd5d79

Please sign in to comment.