-
Notifications
You must be signed in to change notification settings - Fork 515
/
query.clj
100 lines (93 loc) · 3.39 KB
/
query.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
(ns riemann.query
"The query parser. Parses strings into ASTs, and converts ASTs to functions
which match events."
(:use riemann.common)
(:use [slingshot.slingshot :only [throw+ try+]])
(:import (org.antlr.runtime ANTLRStringStream
CommonTokenStream)
(riemann QueryLexer QueryParser)))
; With many thanks to Brian Carper
; http://briancarper.net/blog/554/antlr-via-clojure
(defn parse-string
"Parse string into ANTLR tree nodes"
[s]
(try
(let [lexer (QueryLexer. (ANTLRStringStream. s))
tokens (CommonTokenStream. lexer)
parser (QueryParser. tokens)]
(.getTree (.expr parser)))
(catch Throwable e
(throw+ {:type ::parse-error
:message (.getMessage (.getCause e))}))))
(defn- make-regex
"Convert a string like \"foo%\" into /^foo.*$/"
[string]
(let [tokens (re-seq #"%|[^%]+" string)
pairs (map (fn [token]
(case token
"%" ".*"
(java.util.regex.Pattern/quote token)))
tokens)]
(re-pattern (str "^" (apply str pairs) "$"))))
(defn node-ast [node]
"The AST for a given parse node"
(let [n (.getText node)
kids (remove (fn [x] (= x :useless))
(map node-ast (.getChildren node)))]
(case n
"or" (apply list 'or kids)
"and" (apply list 'and kids)
"not" (apply list 'not kids)
"=" (apply list '= kids)
">" (list 'when (first kids) (apply list '> kids))
">=" (list 'when (first kids) (apply list '>= kids))
"<" (list 'when (first kids) (apply list '< kids))
"<=" (list 'when (first kids) (apply list '<= kids))
"=~" (list 'when (first kids) (list 're-find (make-regex (last kids))
(first kids)))
"!=" (list 'not (apply list '= kids))
"tagged" (list 'when 'tags (list 'member? (first kids) 'tags))
"(" :useless
")" :useless
"nil" nil
"null" nil
"true" true
"false" false
"host" 'host
"service" 'service
"state" 'state
"description" 'description
"metric_f" 'metric_f
"metric" 'metric
"time" 'time
"ttl" 'ttl
(when n (let [term (read-string n)]
(if (or (number? term)
(string? term))
term
(throw+ {:type ::parse-error
:message (str "invalid term \"" n "\"")})))))))
(defn ast
"The expression AST for a given string"
[string]
(node-ast (parse-string string)))
(defn fun
"Transforms an AST into a fn [event] which returns true if the query matches
that event. Example:
(def q (fun (ast \"metric > 2\")))
(q {:metric 1}) => false
(q {:metric 3}) => true"
[ast]
(eval
(list 'fn ['event]
(list 'let '[host (:host event)
service (:service event)
state (:state event)
description (:description event)
metric_f (:metric_f event)
metric (:metric event)
time (:time event)
tags (:tags event)
ttl (:ttl event)
member? riemann.common/member?]
ast))))