├── .gitignore
├── .travis.yml
├── LICENSE.txt
├── README.md
├── include
└── rivus_cep.hrl
├── priv
├── basho_bench_driver_rivus.erl
├── rivus.config
└── rivus_cep_event_gen.erl
├── rebar
├── rebar.config
├── rel
├── files
│ ├── erl
│ ├── install_upgrade.escript
│ ├── nodetool
│ ├── rivus_cep
│ ├── rivus_cep.cmd
│ ├── start_erl.cmd
│ ├── sys.config
│ └── vm.args
├── reltool.config
└── vars.config
├── relx
├── relx.config
├── src
├── erlydtl_custom_filters.erl
├── event_behaviour.erl
├── gen_nb_server.erl
├── result_subscriber.erl
├── rivus_cep.app.src
├── rivus_cep.erl
├── rivus_cep_app.erl
├── rivus_cep_app_srv.erl
├── rivus_cep_app_srv_sup.erl
├── rivus_cep_clock_server.erl
├── rivus_cep_clock_sup.erl
├── rivus_cep_event_creator.erl
├── rivus_cep_parser.erl
├── rivus_cep_parser.yrl
├── rivus_cep_query.erl
├── rivus_cep_query_planner.erl
├── rivus_cep_query_worker.erl
├── rivus_cep_query_worker_sup.erl
├── rivus_cep_result_eval.erl
├── rivus_cep_scanner.erl
├── rivus_cep_scanner.xrl
├── rivus_cep_server.erl
├── rivus_cep_server_sup.erl
├── rivus_cep_sup.erl
├── rivus_cep_tcp_listener.erl
├── rivus_cep_tcp_listener_sup.erl
├── rivus_cep_utils.erl
├── rivus_cep_window.erl
└── rivus_cep_window_ets.erl
└── test
├── event1.erl
├── event2.erl
├── event3.erl
├── event4.erl
├── event5.erl
├── rivus_cep_event_creator_tests.erl
├── rivus_cep_parser_tests.erl
├── rivus_cep_query_planner_tests.erl
├── rivus_cep_query_worker_tests.erl
├── rivus_cep_result_eval_tests.erl
├── rivus_cep_server_tests.erl
├── rivus_cep_tests.erl
├── rivus_cep_window_ets_tests.erl
└── rivus_cep_window_tests.erl
/.gitignore:
--------------------------------------------------------------------------------
1 | *.beam
2 | .eunit
3 | deps/*
4 | ebin
5 | *~
6 | dev/*
7 | doc/*
8 | rel/mynode
9 | logs
10 | logs/*
11 | log/*
12 | rel/logs/*
13 | rel/eepe_node
14 | *.swp
15 | *.vim
16 | tags
17 | *.log
18 | *.vim
19 | etc/*.erl
20 | .idea
21 | *.iml
22 | out
23 | data
24 | data/*
25 | .rebar
26 | _rel
27 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: erlang
2 | otp_release:
3 | - 17.1
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/vascokk/rivus_cep) [](https://gitter.im/vascokk/rivus_cep?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
2 |
3 | # Overview
4 |
5 |
6 | Rivus CEP is an Erlang application for Complex Event Processing. It uses a declarative SQL-like DSL to define operations over event streams.
7 | In the CEP-based systems the events(data) are processed as they arrive, as opposite to the DB, where data is first persisted, then fetched and processed:
8 |
9 |
10 |
11 | |------CEP Engine------|
12 | | |
13 | DataSource ------------------->| Continuous Query |-------------> Result Subscriber
14 | /Provider/ Event stream | over a time interval | Result /Consumer/
15 | |----------------------|
16 |
17 |
18 |
19 | #Queries
20 |
21 | There are two type of queries:
22 | - simple queries:
23 |
24 | ```
25 | select
26 | ev1.eventparam1, ev2.eventparam2, sum(ev2.eventparam3)
27 | from
28 | event1 as ev1, event2 as ev2
29 | where
30 | ev1.eventparam2 = ev2.eventparam2
31 | within 60 seconds
32 | ```
33 |
34 | The above query will join all the events of type `event1` and `event2` arrived whithin the last 60 seconds ("sliding window").
35 | In case of "join" queries, the events within the time window will be persisted in memory. For queries that do not require "join" - the result will be calculated immediately, wihout events persistence.
36 |
37 | - pattern matching queries:
38 |
39 | ```
40 | select
41 | ev1.eventparam1, ev2.eventparam2, ev2.eventparam3, ev2.eventparam4
42 | from
43 | event1 as ev1 -> event2 as ev2
44 | where
45 | ev1.eventparam2 = ev2.eventparam2
46 | within 60 seconds
47 | ```
48 |
49 | Here the result will be generated only in case when `event2` strictly follows `event1` within a 60 seconds window. Pattern-based queries always persist the events. Pattern matching mechanism is based on a directed graph FSM, using the `digraph` module.
50 |
51 | #Windows
52 |
53 | A "window" in Rivus actually means two things:
54 |
55 | - a "time window" - the time interval, which the query operates on, and:
56 | - the temporary storage where the events within the "time window" are being persisted;
57 |
58 | There are two types of windows currently implemented:
59 | - sliding window (default) - this is a moving length window, which contains the events from the last X second. A new Result will be generated after each received event.
60 | - batch window - contains the events from a given moment in the past (t0) up to the moment t0+X. The Result will be generated only after the amount of time X expires. Once the Result is calculated the window will be cleaned up.
61 |
62 | The underlying persistence mechanism is pluggable (see the `rivus_cep_window.erl` module). Default implementation is based on ETS tables.
63 |
64 | ##Filters
65 |
66 | Filters are used to remove events from the stream, based on certain criteria, so that to speed up the result generation and reduce the memory requirements:
67 |
68 | ```
69 | select
70 | ev1.eventparam1, ev2.eventparam2, ev2.eventparam3, ev1.eventparam2
71 | from
72 | event1(eventparam1>5, eventparam1<30) as ev1,
73 | event2(eventparam1<50) as ev2
74 | where
75 | ev1.eventparam2 = ev2.eventparam2
76 | within 60 seconds
77 | ```
78 |
79 | Multiple criteria separated by `','` could be used. The above query will filter out all the `event1` events, which do not satisfy the condition `eventparam1>5 AND eventparam1<30` and also the `event2` for which `eventparam1<50` is not true.
80 | The difference between using filters and using `where` clause is - the filters are executed before the event to be persisted in memory. In this way the user can reduce the event volume in Result calculations.
81 |
82 | #Aggregations
83 |
84 | The following aggregation functions are currently supported:
85 |
86 | - sum
87 | - count
88 | - min
89 | - max
90 |
91 | #Events representation
92 |
93 | Events are tuples in the format: `{, , ,.....,}`. The `` must be unique.
94 | For each event type there must be a module implementing the `event_behavior` with the same name as the name of the event. The important function that needs to be implemented is - `get_param_by_name(Event, ParamName)`.
95 | You can define events in runtime, using the following statement with `rivus_cep:execute/1`:
96 |
97 | ```
98 | define as (, ,.....,);
99 | ```
100 |
101 | See the following example in "Usage".
102 |
103 | #Usage
104 |
105 | Here is how to use Rivus:
106 |
107 | Clone and build:
108 |
109 | ``` bash
110 | $ git clone https://github.com/vascokk/rivus_cep.git
111 | $ ./rebar get-deps
112 | $ ./rebar compile
113 |
114 | ```
115 |
116 | Update `rel/vars.config` according to your preferences or use the default values.
117 |
118 | Create a release using `relx`:
119 |
120 | ``` sh
121 | $ ./relx
122 |
123 | ```
124 |
125 | Start the application:
126 |
127 | ``` sh
128 | $ ./_rel/rivus_cep/bin/rivus_cep console
129 |
130 | ```
131 |
132 | Try the following in the Erlang console:
133 |
134 | ``` erlang
135 |
136 | %% define the events to be recognised by the engine:
137 | EventDefStr1 = "define event1 as (eventparam1, eventparam2, eventparam3);".
138 | EventDefStr2 = "define event2 as (eventparam1, eventparam2, eventparam3);".
139 |
140 | rivus_cep:execute(EventDefStr1).
141 | rivus_cep:execute(EventDefStr2).
142 |
143 | % deploy the query
144 | QueryStr = "define correlation1 as
145 | select ev1.eventparam1, ev2.eventparam2, ev2.eventparam3, ev1.eventparam2
146 | from event1 as ev1, event2 as ev2
147 | where ev1.eventparam2 = ev2.eventparam2
148 | within 60 seconds; ".
149 |
150 | Producer = event_producer_1.
151 | {ok, SubscriberPid} = result_subscriber:start_link().
152 |
153 | {ok, QueryPid, QueryDetails} = rivus_cep:execute(QueryStr, [Producer], [SubscriberPid], [{shared_streams, true}]).
154 |
155 | %% create some evetnts
156 | Event1 = {event1, 10, b, c}.
157 | Event2 = {event1, 15, bbb, c}.
158 | Event3 = {event1, 20, b, c}.
159 | Event4 = {event2, 30, b, cc}.
160 | Event5 = {event2, 40, bb, cc}.
161 |
162 | %% send the events (if you do not care about the producers, you can use notify/1)
163 | rivus_cep:notify(Producer, Event1).
164 | rivus_cep:notify(Producer, Event2).
165 | rivus_cep:notify(Producer, Event3).
166 | rivus_cep:notify(Producer, Event4).
167 | rivus_cep:notify(Producer, Event5).
168 |
169 | %% result
170 | %% you should get:
171 | %% {ok,[{10,b,cc,b},{20,b,cc,b}]}
172 |
173 | gen_server:call(SubscriberPid, get_result).
174 |
175 | ```
176 |
177 | The query is started with `rivus_cep:execute/4` (previously `load_query/4`, which is now deprecated). It takes as arguments: query string, list of event producers, list of query result subscribers and options as a proplist.
178 |
179 | Each query worker will register itself to the [gproc](https://github.com/uwiger/gproc) process registry, for the events listed in the "from" clause.
180 |
181 | If the events are sent via `rivus_cep:notify/1`, the event will be received by any query subscribed for this event type. With `notify/2` - only the queries subscribed to the particular Producer will receive the event.
182 |
183 | For each query there must be at least one Subscriber to receive the query results.
184 |
185 | See `tests/rivus_cep_tests.erl` for more examples.
186 |
187 |
188 | #Streaming events via TCP
189 |
190 | You can stream events via TCP connection:
191 |
192 | ``` erlang
193 | {ok, {Host, Port}} = application:get_env(rivus_cep, rivus_tcp_serv),
194 | {ok, Socket} = gen_tcp:connect(Host, Port, [{active, false}, {nodelay, true}, {packet, 4}, binary]),
195 |
196 | Event1 = {event1, 10, b, c},
197 | Event2 = {event1, 15, bbb, c},
198 | Event3 = {event1, 20, b, c},
199 | Event4 = {event2, 30, b, cc, d},
200 | Event5 = {event2, 40, bb, cc, dd},
201 |
202 | gen_tcp:send(Socket, term_to_binary({event, test_query_1, Event1})),
203 | gen_tcp:send(Socket, term_to_binary({event, test_query_1, Event2})),
204 | gen_tcp:send(Socket, term_to_binary({event, test_query_1, Event3})),
205 | gen_tcp:send(Socket, term_to_binary({event, test_query_1, Event4})),
206 | gen_tcp:send(Socket, term_to_binary({event, test_query_1, Event5})),
207 | ```
208 |
209 | Currently only Erlang clients are supported, but events serialization using [BERT](http://bert-rpc.org/) is work in progress.
210 |
211 |
212 | #Shared streams
213 |
214 | For memory efficiency, `{shared_streams, true}` can be provided in the options list. In this case, the query will work with a shared event sliding window. The window's size will be equal of the maximum "within" clause of all sharing queries.
215 | Queries based on event pattern use only non-shared windows.
216 |
217 | #Benchmarking
218 |
219 | You can load-test your queries using [Basho Bench](https://github.com/basho/basho_bench). Use the Basho Bench driver `basho_bench_driver_rivus.erl` and configuration file `rivus.config` provided in the `/priv` directory. Edit `rivus.config` according to your needs.
220 | Here is the result of the single query (single process) in `rivus.config` running on GCE cloud instance (16 CPU, 15GB RAM) for 5 minutes:
221 |
222 | 
223 |
224 | #Dependencies
225 |
226 | - [gproc](https://github.com/uwiger/gproc)
227 | - [folsom](https://github.com/boundary/folsom)
228 | - [lager](https://github.com/basho/lager)
229 |
230 |
231 | #Disclaimer
232 |
233 | This is not a production ready project. You can use it on your own risk.
234 |
235 |
236 |
237 |
--------------------------------------------------------------------------------
/include/rivus_cep.hrl:
--------------------------------------------------------------------------------
1 | %%------------------------------------------------------------------------------
2 | %% Copyright (c) 2013 Vasil Kolarov
3 | %%
4 | %% Licensed under the Apache License, Version 2.0 (the "License");
5 | %% you may not use this file except in compliance with the License.
6 | %% You may obtain a copy of the License at
7 | %%
8 | %% http://www.apache.org/licenses/LICENSE-2.0
9 | %%
10 | %% Unless required by applicable law or agreed to in writing, software
11 | %% distributed under the License is distributed on an "AS IS" BASIS,
12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | %% See the License for the specific language governing permissions and
14 | %% limitations under the License.
15 | %%------------------------------------------------------------------------------
16 |
17 | -record(res_eval_state, { stmt,
18 | recno = 1,
19 | aggrno = 1,
20 | aggr_nodes = #{},
21 | result = #{}
22 | }).
23 |
24 |
25 |
26 | -record(query_plan, {
27 | join_keys = orddict:new(),
28 | fsm,
29 | has_aggregations = false,
30 | fast_aggregations = false
31 | }).
32 |
33 | -record(query_details, {
34 | clauses,
35 | producers,
36 | subscribers,
37 | options,
38 | event_window,
39 | fsm_window,
40 | window_register,
41 | event_window_pid,
42 | fsm_window_pid
43 | }).
44 | %%[QueryClauses, Producers, Subscribers, _Options, EventWindow, FsmWindow, GlobalWinReg]
45 |
46 | -record(query_state,{
47 | query_name,
48 | query_type,
49 | producers,
50 | subscribers,
51 | events = [] ,
52 | timeout = 60,
53 | query_ast,
54 | window,
55 | fsm_window,
56 | global_window_register,
57 | window_pid,
58 | fsm_window_pid,
59 | query_plan = #query_plan{},
60 | stream_filters,
61 | window_type,
62 | batch_clock_pid
63 | }).
64 |
65 | -record(query_ast,{
66 | select,
67 | from,
68 | where,
69 | within
70 | }).
71 |
72 | -record(fsm,{
73 | fsm_state,
74 | fsm_graph,
75 | fsm_events
76 | }).
77 |
78 |
79 |
--------------------------------------------------------------------------------
/priv/basho_bench_driver_rivus.erl:
--------------------------------------------------------------------------------
1 | -module(basho_bench_driver_rivus).
2 | -compile([{parse_transform, lager_transform}]).
3 |
4 | -export([new/1, run/4]).
5 |
6 | -include("basho_bench.hrl").
7 |
8 | -record(state, {
9 | host,
10 | port,
11 | socket,
12 | client_num
13 | }).
14 |
15 | new(Id) ->
16 | {Host, Port, Socket} = connect(),
17 | deploy_queries(Socket, Id),
18 |
19 | {ok, #state{
20 | host = Host,
21 | port = Port,
22 | socket = Socket,
23 | client_num = Id}}.
24 | %% new(_Id) ->
25 | %% {Host, Port, Socket} = connect(),
26 | %%
27 | %% {ok, #state{
28 | %% host = Host,
29 | %% port = Port,
30 | %% socket = Socket}}.
31 |
32 | connect() ->
33 | {Host, Port} = basho_bench_config:get(rivus_cep_tcp_serv),
34 | {ok, Socket} = gen_tcp:connect(Host, Port, [{active, false}, {nodelay, true}, {packet, 4}, binary]),
35 | {Host, Port, Socket}.
36 |
37 | deploy_queries(Socket, Id) ->
38 | Queries = basho_bench_config:get(rivus_cep_queries, []),
39 | lists:foreach(fun({Type, Query}) ->
40 | Q = re:replace(Query,"\\$",integer_to_list(Id),[global, {return,list}]),
41 | lager:info("Deploying query type: ~p, client: ~p, stmt: ~p", [Type, Id, Q]),
42 | execute(Type, Q, Socket) end, Queries).
43 |
44 | execute(query, Query, Socket) ->
45 | ok = gen_tcp:send(Socket, term_to_binary({load_query, {Query, [benchmark_test], [], []}}));
46 | execute(event, Query, Socket) ->
47 | ok = gen_tcp:send(Socket, term_to_binary({load_query, {Query, [benchmark_event], [], []}})).
48 |
49 | send_event(Event, #state{socket = Socket} = State) ->
50 | case gen_tcp:send(Socket, term_to_binary({event, benchmark_test, Event})) of
51 | ok -> {ok, State};
52 | Error -> Error
53 | end.
54 |
55 | create_event(_KeyGen, ValueGen) ->
56 | ValueGen.
57 |
58 | run(notify, KeyGen, ValueGen, #state{socket = Socket} = State) ->
59 | Event = create_event(KeyGen, ValueGen),
60 | % Send message
61 | case send_event(Event, State) of
62 | {error, E} ->
63 | {error, E, State};
64 | {ok, State} -> case gen_tcp:recv(Socket,0) of
65 | {ok, _} -> {ok, State};
66 | {error, E} -> {error, E, State}
67 | end
68 | end.
69 |
--------------------------------------------------------------------------------
/priv/rivus.config:
--------------------------------------------------------------------------------
1 | {mode, max}.
2 |
3 | {duration, 5}.
4 |
5 | {concurrent, 2}.
6 |
7 | {driver, basho_bench_driver_rivus}.
8 |
9 | {operations, [{notify, 1}]}.
10 |
11 | {key_generator, {uniform_int, 10000}}.
12 | {value_generator, {function, rivus_cep_event_gen, generate_event, ["testevent", 4]}}.
13 |
14 | {rivus_cep_queries,[{event, "define testevent$ as (attr1, attr2, attr3, attr4);"},
15 | {query, "define testquery$ as
16 | select sum(ev1.attr1)
17 | from testevent$ as ev1
18 | within 60 seconds;"}]}.
19 |
20 | {rivus_cep_tcp_serv, {"127.0.0.1", 5775}}.
--------------------------------------------------------------------------------
/priv/rivus_cep_event_gen.erl:
--------------------------------------------------------------------------------
1 |
2 | -module(rivus_cep_event_gen).
3 |
4 | %% API
5 | -export([generate_event/3]).
6 |
7 | generate_event(Id, EventName, ParamCount) when is_integer(ParamCount) andalso ParamCount>0 ->
8 | generate(ParamCount, [list_to_atom(EventName ++ integer_to_list(Id))]).
9 |
10 | generate(0, Acc) ->
11 | list_to_tuple(Acc);
12 | generate(ParamCount, Acc) ->
13 | generate(ParamCount-1, Acc ++ [random:uniform(1000)]).
14 |
15 |
--------------------------------------------------------------------------------
/rebar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vascokk/rivus_cep/e9fe6ed79201d852065f7fb2a24a880414031d27/rebar
--------------------------------------------------------------------------------
/rebar.config:
--------------------------------------------------------------------------------
1 | %% ; -*- mode: Erlang;-*-
2 |
3 | {lib_dirs, ["../../apps","../../deps","../../deps/folsom/"]}.
4 | {deps, [
5 | {folsom, ".*", {git, "git://github.com/vascokk/folsom.git", "HEAD"}},
6 | {meck, ".*", {git, "git://github.com/eproxus/meck", "HEAD"}},
7 | {gen_leader, ".*", {git, "git://github.com/abecciu/gen_leader_revival.git", "HEAD"}},
8 | {gproc, ".*", {git, "git://github.com/uwiger/gproc.git", "HEAD"}},
9 | {lager, ".*", {git, "git://github.com/basho/lager", "HEAD"}}
10 | ]}.
11 |
12 |
13 | {cover_enabled, false}.
14 | {erl_opts, [debug_info, {src_dirs, ["src"]}]}.
15 | {edoc_opts, [{dir, "doc"}]}.
16 | {lib_dirs, ["apps", "deps"]}.
17 | {eunit_opts, [verbose]}.
18 |
--------------------------------------------------------------------------------
/rel/files/erl:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # /bin/sh on Solaris is not a POSIX compatible shell, but /usr/bin/ksh is.
4 | if [ `uname -s` = 'SunOS' -a "${POSIX_SHELL}" != "true" ]; then
5 | POSIX_SHELL="true"
6 | export POSIX_SHELL
7 | exec /usr/bin/ksh $0 "$@"
8 | fi
9 |
10 | # clear it so if we invoke other scripts, they run as ksh as well
11 | unset POSIX_SHELL
12 |
13 | ## This script replaces the default "erl" in erts-VSN/bin. This is
14 | ## necessary as escript depends on erl and in turn, erl depends on
15 | ## having access to a bootscript (start.boot). Note that this script
16 | ## is ONLY invoked as a side-effect of running escript -- the embedded
17 | ## node bypasses erl and uses erlexec directly (as it should).
18 | ##
19 | ## Note that this script makes the assumption that there is a
20 | ## start_clean.boot file available in $ROOTDIR/release/VSN.
21 |
22 | # Determine the abspath of where this script is executing from.
23 | ERTS_BIN_DIR=$(cd ${0%/*} && pwd -P)
24 |
25 | # Now determine the root directory -- this script runs from erts-VSN/bin,
26 | # so we simply need to strip off two dirs from the end of the ERTS_BIN_DIR
27 | # path.
28 | ROOTDIR=${ERTS_BIN_DIR%/*/*}
29 |
30 | # Parse out release and erts info
31 | START_ERL=`cat $ROOTDIR/releases/start_erl.data`
32 | ERTS_VSN=${START_ERL% *}
33 | APP_VSN=${START_ERL#* }
34 |
35 | BINDIR=$ROOTDIR/erts-$ERTS_VSN/bin
36 | EMU=beam
37 | PROGNAME=`echo $0 | sed 's/.*\\///'`
38 | CMD="$BINDIR/erlexec"
39 | export EMU
40 | export ROOTDIR
41 | export BINDIR
42 | export PROGNAME
43 |
44 | exec $CMD -boot $ROOTDIR/releases/$APP_VSN/start_clean ${1+"$@"}
45 |
--------------------------------------------------------------------------------
/rel/files/install_upgrade.escript:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env escript
2 | %%! -noshell -noinput
3 | %% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*-
4 | %% ex: ft=erlang ts=4 sw=4 et
5 |
6 | -define(TIMEOUT, 60000).
7 | -define(INFO(Fmt,Args), io:format(Fmt,Args)).
8 |
9 | %% TODO: This script currently does NOT support slim releases.
10 | %% Necessary steps to upgrade a slim release are as follows:
11 | %% 1. unpack relup archive manually
12 | %% 2. copy releases directory and necessary libraries
13 | %% 3. using release_hander:set_unpacked/2 .
14 | %% For more details, see https://github.com/rebar/rebar/pull/52
15 | %% and https://github.com/rebar/rebar/issues/202
16 |
17 | main([NodeName, Cookie, ReleasePackage]) ->
18 | TargetNode = start_distribution(NodeName, Cookie),
19 | {ok, Vsn} = rpc:call(TargetNode, release_handler, unpack_release,
20 | [ReleasePackage], ?TIMEOUT),
21 | ?INFO("Unpacked Release ~p~n", [Vsn]),
22 | {ok, OtherVsn, Desc} = rpc:call(TargetNode, release_handler,
23 | check_install_release, [Vsn], ?TIMEOUT),
24 | {ok, OtherVsn, Desc} = rpc:call(TargetNode, release_handler,
25 | install_release, [Vsn], ?TIMEOUT),
26 | ?INFO("Installed Release ~p~n", [Vsn]),
27 | ok = rpc:call(TargetNode, release_handler, make_permanent, [Vsn], ?TIMEOUT),
28 | ?INFO("Made Release ~p Permanent~n", [Vsn]);
29 | main(_) ->
30 | init:stop(1).
31 |
32 | start_distribution(NodeName, Cookie) ->
33 | MyNode = make_script_node(NodeName),
34 | {ok, _Pid} = net_kernel:start([MyNode, shortnames]),
35 | erlang:set_cookie(node(), list_to_atom(Cookie)),
36 | TargetNode = make_target_node(NodeName),
37 | case {net_kernel:hidden_connect_node(TargetNode),
38 | net_adm:ping(TargetNode)} of
39 | {true, pong} ->
40 | ok;
41 | {_, pang} ->
42 | io:format("Node ~p not responding to pings.\n", [TargetNode]),
43 | init:stop(1)
44 | end,
45 | TargetNode.
46 |
47 | make_target_node(Node) ->
48 | [_, Host] = string:tokens(atom_to_list(node()), "@"),
49 | list_to_atom(lists:concat([Node, "@", Host])).
50 |
51 | make_script_node(Node) ->
52 | list_to_atom(lists:concat([Node, "_upgrader_", os:getpid()])).
53 |
--------------------------------------------------------------------------------
/rel/files/nodetool:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env escript
2 | %% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*-
3 | %% ex: ft=erlang ts=4 sw=4 et
4 | %% -------------------------------------------------------------------
5 | %%
6 | %% nodetool: Helper Script for interacting with live nodes
7 | %%
8 | %% -------------------------------------------------------------------
9 | main(Args) ->
10 | ok = start_epmd(),
11 | %% Extract the args
12 | {RestArgs, TargetNode} = process_args(Args, [], undefined),
13 |
14 | %% any commands that don't need a running node
15 | case RestArgs of
16 | ["chkconfig", File] ->
17 | case file:consult(File) of
18 | {ok, _} ->
19 | io:format("ok\n"),
20 | halt(0);
21 | {error, {Line, Mod, Term}} ->
22 | io:format(standard_error, ["Error on line ",
23 | file:format_error({Line, Mod, Term}), "\n"], []),
24 | halt(1);
25 | {error, R} ->
26 | io:format(standard_error, ["Error reading config file: ",
27 | file:format_error(R), "\n"], []),
28 | halt(1)
29 | end;
30 | _ ->
31 | ok
32 | end,
33 |
34 | %% See if the node is currently running -- if it's not, we'll bail
35 | case {net_kernel:hidden_connect_node(TargetNode),
36 | net_adm:ping(TargetNode)} of
37 | {true, pong} ->
38 | ok;
39 | {false,pong} ->
40 | io:format("Failed to connect to node ~p .\n", [TargetNode]),
41 | halt(1);
42 | {_, pang} ->
43 | io:format("Node ~p not responding to pings.\n", [TargetNode]),
44 | halt(1)
45 | end,
46 |
47 | case RestArgs of
48 | ["getpid"] ->
49 | io:format("~p\n",
50 | [list_to_integer(rpc:call(TargetNode, os, getpid, []))]);
51 | ["ping"] ->
52 | %% If we got this far, the node already responsed to a
53 | %% ping, so just dump a "pong"
54 | io:format("pong\n");
55 | ["stop"] ->
56 | io:format("~p\n", [rpc:call(TargetNode, init, stop, [], 60000)]);
57 | ["restart"] ->
58 | io:format("~p\n", [rpc:call(TargetNode, init, restart, [], 60000)]);
59 | ["reboot"] ->
60 | io:format("~p\n", [rpc:call(TargetNode, init, reboot, [], 60000)]);
61 | ["rpc", Module, Function | RpcArgs] ->
62 | case rpc:call(TargetNode,
63 | list_to_atom(Module),
64 | list_to_atom(Function),
65 | [RpcArgs], 60000) of
66 | ok ->
67 | ok;
68 | {badrpc, Reason} ->
69 | io:format("RPC to ~p failed: ~p\n", [TargetNode, Reason]),
70 | halt(1);
71 | _ ->
72 | halt(1)
73 | end;
74 | ["rpc_infinity", Module, Function | RpcArgs] ->
75 | case rpc:call(TargetNode,
76 | list_to_atom(Module),
77 | list_to_atom(Function),
78 | [RpcArgs], infinity) of
79 | ok ->
80 | ok;
81 | {badrpc, Reason} ->
82 | io:format("RPC to ~p failed: ~p\n", [TargetNode, Reason]),
83 | halt(1);
84 | _ ->
85 | halt(1)
86 | end;
87 | ["rpcterms", Module, Function, ArgsAsString] ->
88 | case rpc:call(TargetNode,
89 | list_to_atom(Module),
90 | list_to_atom(Function),
91 | consult(ArgsAsString), 60000) of
92 | {badrpc, Reason} ->
93 | io:format("RPC to ~p failed: ~p\n", [TargetNode, Reason]),
94 | halt(1);
95 | Other ->
96 | io:format("~p\n", [Other])
97 | end;
98 | ["eval", Str0] ->
99 | Str = string:strip(Str0, right, $.) ++ ".",
100 | Bindings = erl_eval:new_bindings(),
101 | case rpc:call(TargetNode,
102 | erl_eval,
103 | exprs,
104 | [parse(Str), Bindings],
105 | 60000) of
106 | {badrpc, Reason} ->
107 | io:format("RPC to ~p failed: ~p\n", [TargetNode, Reason]),
108 | halt(1);
109 | {value, Value, _Bindings} ->
110 | io:format("~p\n", [Value])
111 | end;
112 | Other ->
113 | io:format("Other: ~p\n", [Other]),
114 | io:format("Usage: nodetool {chkconfig|getpid|ping|stop|restart|reboot|rpc|rpc_infinity|rpcterms|eval}\n")
115 | end,
116 | net_kernel:stop().
117 |
118 | process_args([], Acc, TargetNode) ->
119 | {lists:reverse(Acc), TargetNode};
120 | process_args(["-setcookie", Cookie | Rest], Acc, TargetNode) ->
121 | erlang:set_cookie(node(), list_to_atom(Cookie)),
122 | process_args(Rest, Acc, TargetNode);
123 | process_args(["-name", TargetName | Rest], Acc, _) ->
124 | ThisNode = append_node_suffix(TargetName, "_maint_"),
125 | {ok, _} = net_kernel:start([ThisNode, longnames]),
126 | process_args(Rest, Acc, nodename(TargetName));
127 | process_args(["-sname", TargetName | Rest], Acc, _) ->
128 | ThisNode = append_node_suffix(TargetName, "_maint_"),
129 | {ok, _} = net_kernel:start([ThisNode, shortnames]),
130 | process_args(Rest, Acc, nodename(TargetName));
131 | process_args([Arg | Rest], Acc, Opts) ->
132 | process_args(Rest, [Arg | Acc], Opts).
133 |
134 |
135 | start_epmd() ->
136 | [] = os:cmd(epmd_path() ++ " -daemon"),
137 | ok.
138 |
139 | epmd_path() ->
140 | ErtsBinDir = filename:dirname(escript:script_name()),
141 | Name = "epmd",
142 | case os:find_executable(Name, ErtsBinDir) of
143 | false ->
144 | case os:find_executable(Name) of
145 | false ->
146 | io:format("Could not find epmd.~n"),
147 | halt(1);
148 | GlobalEpmd ->
149 | GlobalEpmd
150 | end;
151 | Epmd ->
152 | Epmd
153 | end.
154 |
155 |
156 | nodename(Name) ->
157 | case string:tokens(Name, "@") of
158 | [_Node, _Host] ->
159 | list_to_atom(Name);
160 | [Node] ->
161 | [_, Host] = string:tokens(atom_to_list(node()), "@"),
162 | list_to_atom(lists:concat([Node, "@", Host]))
163 | end.
164 |
165 | append_node_suffix(Name, Suffix) ->
166 | case string:tokens(Name, "@") of
167 | [Node, Host] ->
168 | list_to_atom(lists:concat([Node, Suffix, os:getpid(), "@", Host]));
169 | [Node] ->
170 | list_to_atom(lists:concat([Node, Suffix, os:getpid()]))
171 | end.
172 |
173 |
174 | %%
175 | %% Given a string or binary, parse it into a list of terms, ala file:consult/0
176 | %%
177 | consult(Str) when is_list(Str) ->
178 | consult([], Str, []);
179 | consult(Bin) when is_binary(Bin)->
180 | consult([], binary_to_list(Bin), []).
181 |
182 | consult(Cont, Str, Acc) ->
183 | case erl_scan:tokens(Cont, Str, 0) of
184 | {done, Result, Remaining} ->
185 | case Result of
186 | {ok, Tokens, _} ->
187 | {ok, Term} = erl_parse:parse_term(Tokens),
188 | consult([], Remaining, [Term | Acc]);
189 | {eof, _Other} ->
190 | lists:reverse(Acc);
191 | {error, Info, _} ->
192 | {error, Info}
193 | end;
194 | {more, Cont1} ->
195 | consult(Cont1, eof, Acc)
196 | end.
197 |
198 | parse(Str) ->
199 | {ok, Tokens, _} = erl_scan:string(Str),
200 | {ok, Exprs} = erl_parse:parse_exprs(Tokens),
201 | Exprs.
202 |
--------------------------------------------------------------------------------
/rel/files/rivus_cep.cmd:
--------------------------------------------------------------------------------
1 | @setlocal
2 |
3 | @set node_name=rivus_cep
4 |
5 | @rem Get the absolute path to the parent directory,
6 | @rem which is assumed to be the node root.
7 | @for /F "delims=" %%I in ("%~dp0..") do @set node_root=%%~fI
8 |
9 | @set releases_dir=%node_root%\releases
10 |
11 | @rem Parse ERTS version and release version from start_erl.data
12 | @for /F "usebackq tokens=1,2" %%I in ("%releases_dir%\start_erl.data") do @(
13 | @call :set_trim erts_version %%I
14 | @call :set_trim release_version %%J
15 | )
16 |
17 | @set vm_args=%releases_dir%\%release_version%\vm.args
18 | @set sys_config=%releases_dir%\%release_version%\sys.config
19 | @set node_boot_script=%releases_dir%\%release_version%\%node_name%
20 | @set clean_boot_script=%releases_dir%\%release_version%\start_clean
21 |
22 | @rem extract erlang cookie from vm.args
23 | @for /f "usebackq tokens=1-2" %%I in (`findstr /b \-setcookie "%vm_args%"`) do @set erlang_cookie=%%J
24 |
25 | @set erts_bin=%node_root%\erts-%erts_version%\bin
26 |
27 | @set service_name=%node_name%_%release_version%
28 |
29 | @set erlsrv="%erts_bin%\erlsrv.exe"
30 | @set epmd="%erts_bin%\epmd.exe"
31 | @set escript="%erts_bin%\escript.exe"
32 | @set werl="%erts_bin%\werl.exe"
33 | @set nodetool="%erts_bin%\nodetool"
34 |
35 | @if "%1"=="usage" @goto usage
36 | @if "%1"=="install" @goto install
37 | @if "%1"=="uninstall" @goto uninstall
38 | @if "%1"=="start" @goto start
39 | @if "%1"=="stop" @goto stop
40 | @if "%1"=="restart" @call :stop && @goto start
41 | @if "%1"=="console" @goto console
42 | @if "%1"=="ping" @goto ping
43 | @if "%1"=="query" @goto query
44 | @if "%1"=="attach" @goto attach
45 | @if "%1"=="upgrade" @goto upgrade
46 | @echo Unknown command: "%1"
47 |
48 | :usage
49 | @echo Usage: %~n0 [install^|uninstall^|start^|stop^|restart^|console^|ping^|query^|attach^|upgrade]
50 | @goto :EOF
51 |
52 | :install
53 | @set description=Erlang node %node_name% in %node_root%
54 | @set start_erl=%node_root%\bin\start_erl.cmd
55 | @set args= ++ %node_name% ++ %node_root%
56 | @%erlsrv% add %service_name% -c "%description%" -sname %node_name% -w "%node_root%" -m "%start_erl%" -args "%args%" -stopaction "init:stop()."
57 | @goto :EOF
58 |
59 | :uninstall
60 | @%erlsrv% remove %service_name%
61 | @%epmd% -kill
62 | @goto :EOF
63 |
64 | :start
65 | @%erlsrv% start %service_name%
66 | @goto :EOF
67 |
68 | :stop
69 | @%erlsrv% stop %service_name%
70 | @goto :EOF
71 |
72 | :console
73 | @start "%node_name% console" %werl% -boot "%node_boot_script%" -config "%sys_config%" -args_file "%vm_args%" -sname %node_name%
74 | @goto :EOF
75 |
76 | :ping
77 | @%escript% %nodetool% ping -sname "%node_name%" -setcookie "%erlang_cookie%"
78 | @exit %ERRORLEVEL%
79 | @goto :EOF
80 |
81 | :query
82 | @%erlsrv% list %service_name%
83 | @exit %ERRORLEVEL%
84 | @goto :EOF
85 |
86 | :attach
87 | @for /f "usebackq" %%I in (`hostname`) do @set hostname=%%I
88 | start "%node_name% attach" %werl% -boot "%clean_boot_script%" -remsh %node_name%@%hostname% -sname console -setcookie %erlang_cookie%
89 | @goto :EOF
90 |
91 | :upgrade
92 | @if "%2"=="" (
93 | @echo Missing upgrade package argument
94 | @echo Usage: %~n0 upgrade {package base name}
95 | @echo NOTE {package base name} MUST NOT include the .tar.gz suffix
96 | @goto :EOF
97 | )
98 | @%escript% %node_root%\bin\install_upgrade.escript %node_name% %erlang_cookie% %2
99 | @goto :EOF
100 |
101 | :set_trim
102 | @set %1=%2
103 | @goto :EOF
104 |
--------------------------------------------------------------------------------
/rel/files/start_erl.cmd:
--------------------------------------------------------------------------------
1 | @setlocal
2 |
3 | @rem Parse arguments. erlsrv.exe prepends erl arguments prior to first ++.
4 | @rem Other args are position dependent.
5 | @set args="%*"
6 | @for /F "delims=++ tokens=1,2,3" %%I in (%args%) do @(
7 | @set erl_args=%%I
8 | @call :set_trim node_name %%J
9 | @rem Trim spaces from the left of %%K (node_root), which may have spaces inside
10 | @for /f "tokens=* delims= " %%a in ("%%K") do @set node_root=%%a
11 | )
12 |
13 | @set releases_dir=%node_root%\releases
14 |
15 | @rem parse ERTS version and release version from start_erl.dat
16 | @for /F "usebackq tokens=1,2" %%I in ("%releases_dir%\start_erl.data") do @(
17 | @call :set_trim erts_version %%I
18 | @call :set_trim release_version %%J
19 | )
20 |
21 | @set erl_exe="%node_root%\erts-%erts_version%\bin\erl.exe"
22 | @set boot_file="%releases_dir%\%release_version%\%node_name%"
23 |
24 | @if exist "%releases_dir%\%release_version%\sys.config" (
25 | @set app_config="%releases_dir%\%release_version%\sys.config"
26 | ) else (
27 | @set app_config="%node_root%\etc\app.config"
28 | )
29 |
30 | @if exist "%releases_dir%\%release_version%\vm.args" (
31 | @set vm_args="%releases_dir%\%release_version%\vm.args"
32 | ) else (
33 | @set vm_args="%node_root%\etc\vm.args"
34 | )
35 |
36 | @%erl_exe% %erl_args% -boot %boot_file% -config %app_config% -args_file %vm_args%
37 |
38 | :set_trim
39 | @set %1=%2
40 | @goto :EOF
41 |
--------------------------------------------------------------------------------
/rel/files/sys.config:
--------------------------------------------------------------------------------
1 | [
2 | %% SASL config
3 | {sasl, [
4 | {sasl_error_logger, {file, "log/sasl-error.log"}},
5 | {errlog_type, error},
6 | {error_logger_mf_dir, "log/sasl"}, % Log directory
7 | {error_logger_mf_maxbytes, 10485760}, % 10 MB max file size
8 | {error_logger_mf_maxfiles, 5} % 5 files max
9 | ]},
10 | {rivus_cep, [
11 | {rivus_tcp_serv, {{rivus_tcp_serv}}},
12 | {rivus_window_provider, {{rivus_window_provider}}}
13 | ]}
14 | ].
15 |
16 |
--------------------------------------------------------------------------------
/rel/files/vm.args:
--------------------------------------------------------------------------------
1 | ## Name of the node
2 | -name {{node}}
3 |
4 | ## Cookie for distributed erlang
5 | -setcookie {{cookie}}
6 |
7 | ## Heartbeat management; auto-restarts VM if it dies or becomes unresponsive
8 | ## (Disabled by default..use with caution!)
9 | ##-heart
10 |
11 | ## Enable kernel poll and a few async threads
12 | +K true
13 | +A 5
14 |
15 | ## Increase number of concurrent ports/sockets
16 | -env ERL_MAX_PORTS 4096
17 |
18 | ## Tweak GC to run more often
19 | -env ERL_FULLSWEEP_AFTER 10
--------------------------------------------------------------------------------
/rel/reltool.config:
--------------------------------------------------------------------------------
1 | %% -*- mode: erlang -*-
2 | %% ex: ft=erlang
3 | {sys, [
4 | {lib_dirs, []},
5 | {erts, [{mod_cond, derived}, {app_file, strip}]},
6 | {app_file, strip},
7 | {rel, "rivus_cep", "1",
8 | [
9 | kernel,
10 | stdlib,
11 | sasl,
12 | rivus_cep, runtime_tools
13 | ]},
14 | {rel, "start_clean", "",
15 | [
16 | kernel,
17 | stdlib
18 | ]},
19 | {boot_rel, "rivus_cep"},
20 | {profile, embedded},
21 | {incl_cond, derived},
22 | {excl_archive_filters, [".*"]}, %% Do not archive built libs
23 | {excl_sys_filters, ["^bin/(?!start_clean.boot)",
24 | "^erts.*/bin/(dialyzer|typer)",
25 | "^erts.*/(doc|info|include|lib|man|src)"]},
26 | {excl_app_filters, ["\.gitignore"]},
27 | {app, rivus_cep, [{mod_cond, app}, {incl_cond, include}]}
28 | ]}.
29 |
30 | {target_dir, "rivus_cep"}.
31 |
32 | {overlay, [
33 | {mkdir, "log/sasl"},
34 | {copy, "files/erl", "\{\{erts_vsn\}\}/bin/erl"},
35 | {copy, "files/nodetool", "releases/\{\{rel_vsn\}\}/nodetool"},
36 | {copy, "rivus_cep/bin/start_clean.boot",
37 | "\{\{erts_vsn\}\}/bin/start_clean.boot"},
38 | {copy, "files/rivus_cep", "bin/rivus_cep"},
39 | {copy, "files/rivus_cep.cmd", "bin/rivus_cep.cmd"},
40 | {copy, "files/start_erl.cmd", "bin/start_erl.cmd"},
41 | {copy, "files/install_upgrade.escript", "bin/install_upgrade.escript"},
42 | {copy, "files/sys.config", "releases/\{\{rel_vsn\}\}/sys.config"},
43 | {copy, "files/vm.args", "releases/\{\{rel_vsn\}\}/vm.args"}
44 | ]}.
45 |
--------------------------------------------------------------------------------
/rel/vars.config:
--------------------------------------------------------------------------------
1 | %%
2 | %% etc/app.config
3 | %%
4 | {rivus_tcp_serv, {"127.0.0.1", 5775}}.
5 | {rivus_window_provider, "rivus_cep_window_ets"}.
6 | %%
7 | %% etc/vm.args
8 | %%
9 | {node, "rivus_cep@127.0.0.1"}.
10 | {cookie, "rivus_cep"}.
--------------------------------------------------------------------------------
/relx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vascokk/rivus_cep/e9fe6ed79201d852065f7fb2a24a880414031d27/relx
--------------------------------------------------------------------------------
/relx.config:
--------------------------------------------------------------------------------
1 | {release, {rivus_cep, "0.1"},
2 | [gproc,
3 | lager,
4 | folsom,
5 | gen_leader,
6 | rivus_cep,
7 | observer,
8 | wx,
9 | runtime_tools, tools]}.
10 |
11 | {extended_start_script, true}.
12 |
13 | {overlay_vars, "rel/vars.config"}.
14 |
15 | {overlay, [
16 | {mkdir, "log/sasl"},
17 | {copy, "rel/files/erl", "\{\{erts_vsn\}\}/bin/erl"},
18 | {copy, "rel/files/nodetool", "releases/\{\{rel_vsn\}\}/nodetool"},
19 | {copy, "rel/files/rivus_cep", "bin/rivus_cep"},
20 | {copy, "rel/files/rivus_cep.cmd", "bin/rivus_cep.cmd"},
21 | {copy, "rel/files/start_erl.cmd", "bin/start_erl.cmd"},
22 | {copy, "rel/files/install_upgrade.escript", "bin/install_upgrade.escript"},
23 | {template, "rel/files/sys.config", "releases/\{\{rel_vsn\}\}/sys.config"},
24 | %% {template, "rel/files/app.config", "etc/app.config"},
25 | {template, "rel/files/vm.args", "etc/vm.args"}
26 | ]}.
27 |
28 | %% {overlay, [{mkdir, "log/sasl"},
29 | %% {copy, "files/erl", "{{erts_vsn}}/bin/erl"},
30 | %% {copy, "files/nodetool", "{{erts_vsn}}/bin/nodetool"},
31 | %% {template, "files/app.config", "etc/app.config"},
32 | %% {template, "files/vm.args", "etc/vm.args"}]}.
33 |
--------------------------------------------------------------------------------
/src/erlydtl_custom_filters.erl:
--------------------------------------------------------------------------------
1 | -module(erlydtl_custom_filters).
2 |
3 | -export([next/2]).
4 |
5 | next(List, Idx) ->
6 | list_to_binary(atom_to_list(lists:nth(Idx+1, List))).
7 |
--------------------------------------------------------------------------------
/src/event_behaviour.erl:
--------------------------------------------------------------------------------
1 | -module(event_behaviour).
2 | -export([behaviour_info/1]).
3 |
4 | behaviour_info(callbacks) -> [{get_param_by_name, 2},
5 | {get_attributes, 0}];
6 | behaviour_info(_) -> undefined.
7 |
--------------------------------------------------------------------------------
/src/gen_nb_server.erl:
--------------------------------------------------------------------------------
1 | %% Copyright (c) 2009 Hypothetical Labs, Inc.
2 |
3 | %% Permission is hereby granted, free of charge, to any person obtaining a copy
4 | %% of this software and associated documentation files (the "Software"), to deal
5 | %% in the Software without restriction, including without limitation the rights
6 | %% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | %% copies of the Software, and to permit persons to whom the Software is
8 | %% furnished to do so, subject to the following conditions:
9 | %%
10 | %% The above copyright notice and this permission notice shall be included in
11 | %% all copies or substantial portions of the Software.
12 | %%
13 | %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | %% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | %% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | %% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | %% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | %% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | %% THE SOFTWARE.
20 |
21 | -module(gen_nb_server).
22 |
23 | -author('kevin@hypotheticalabs.com').
24 |
25 | -behaviour(gen_server).
26 |
27 | %% API
28 | -export([start_link/4]).
29 |
30 | %% gen_server callbacks
31 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
32 | terminate/2, code_change/3]).
33 |
34 | -define(SERVER, ?MODULE).
35 |
36 | -record(state, {cb,
37 | sock,
38 | server_state}).
39 |
40 | -callback init(InitArgs::list()) ->
41 | {ok, State::term()} |
42 | {error, Reason::term()}.
43 |
44 | -callback handle_call(Msg::term(), From::{pid(), term()}, State::term()) ->
45 | {reply, Reply::term(), State::term()} |
46 | {reply, Reply::term(), State::term(), number() | hibernate} |
47 | {noreply, State::term()} |
48 | {noreply, State::term(), number() | hibernate} |
49 | {stop, Reason::term(), State::term()}.
50 |
51 | -callback handle_cast(Msg::term(), State::term()) ->
52 | {noreply, State::term()} |
53 | {noreply, State::term(), number() | hibernate} |
54 | {stop, Reason::term(), State::term()}.
55 |
56 | -callback handle_info(Msg::term(), State::term()) ->
57 | {noreply, State::term()} |
58 | {noreply, State::term(), number() | hibernate} |
59 | {stop, Reason::term(), State::term()}.
60 |
61 | -callback terminate(Reason::term(), State::term()) ->
62 | ok.
63 |
64 | -callback sock_opts() -> [gen_tcp:listen_option()].
65 |
66 | -callback new_connection(inet:socket(), State::term()) ->
67 | {ok, NewState::term()} |
68 | {stop, Reason::term(), NewState::term()}.
69 |
70 | %% @spec start_link(CallbackModule, IpAddr, Port, InitParams) -> Result
71 | %% CallbackModule = atom()
72 | %% IpAddr = string()
73 | %% Port = integer()
74 | %% InitParams = [any()]
75 | %% Result = {ok, pid()} | {error, any()}
76 | %% @doc Start server listening on IpAddr:Port
77 | start_link(CallbackModule, IpAddr, Port, InitParams) ->
78 | gen_server:start_link(?MODULE, [CallbackModule, IpAddr, Port, InitParams], []).
79 |
80 | %% @hidden
81 | init([CallbackModule, IpAddr, Port, InitParams]) ->
82 | case CallbackModule:init(InitParams) of
83 | {ok, ServerState} ->
84 | case listen_on(CallbackModule, IpAddr, Port) of
85 | {ok, Sock} ->
86 | {ok, #state{cb=CallbackModule, sock=Sock, server_state=ServerState}};
87 | Error ->
88 | CallbackModule:terminate(Error, ServerState),
89 | Error
90 | end;
91 | Err ->
92 | Err
93 | end.
94 |
95 | %% @hidden
96 | handle_call(Request, From, #state{cb=Callback, server_state=ServerState}=State) ->
97 | case Callback:handle_call(Request, From, ServerState) of
98 | {reply, Reply, NewServerState} ->
99 | {reply, Reply, State#state{server_state=NewServerState}};
100 | {reply, Reply, NewServerState, Arg} when Arg =:= hibernate orelse is_number(Arg) ->
101 | {reply, Reply, State#state{server_state=NewServerState}, Arg};
102 | {noreply, NewServerState} ->
103 | {noreply, State#state{server_state=NewServerState}};
104 | {noreply, NewServerState, Arg} when Arg =:= hibernate orelse is_number(Arg) ->
105 | {noreply, State#state{server_state=NewServerState}, Arg};
106 | {stop, Reason, NewServerState} ->
107 | {stop, Reason, State#state{server_state=NewServerState}};
108 | {stop, Reason, Reply, NewServerState} ->
109 | {stop, Reason, Reply, State#state{server_state=NewServerState}}
110 | end.
111 |
112 | %% @hidden
113 | handle_cast(Msg, #state{cb=Callback, server_state=ServerState}=State) ->
114 | case Callback:handle_cast(Msg, ServerState) of
115 | {noreply, NewServerState} ->
116 | {noreply, State#state{server_state=NewServerState}};
117 | {noreply, NewServerState, Arg} when Arg =:= hibernate orelse is_number(Arg) ->
118 | {noreply, State#state{server_state=NewServerState}, Arg};
119 | {stop, Reason, NewServerState} ->
120 | {stop, Reason, State#state{server_state=NewServerState}}
121 | end.
122 |
123 | %% @hidden
124 | handle_info({inet_async, ListSock, _Ref, {ok, CliSocket}}, #state{cb=Callback, server_state=ServerState}=State) ->
125 | inet_db:register_socket(CliSocket, inet_tcp),
126 | case Callback:new_connection(CliSocket, ServerState) of
127 | {ok, NewServerState} ->
128 | {ok, _} = prim_inet:async_accept(ListSock, -1),
129 | {noreply, State#state{server_state=NewServerState}};
130 | {stop, Reason, NewServerState} ->
131 | {stop, Reason, State#state{server_state=NewServerState}}
132 | end;
133 |
134 | handle_info(Info, #state{cb=Callback, server_state=ServerState}=State) ->
135 | case Callback:handle_info(Info, ServerState) of
136 | {noreply, NewServerState} ->
137 | {noreply, State#state{server_state=NewServerState}};
138 | {noreply, NewServerState, Arg} when Arg =:= hibernate orelse is_number(Arg) ->
139 | {noreply, State#state{server_state=NewServerState}, Arg};
140 | {stop, Reason, NewServerState} ->
141 | {stop, Reason, State#state{server_state=NewServerState}}
142 | end.
143 |
144 | %% @hidden
145 | terminate(Reason, #state{cb=Callback, sock=Sock, server_state=ServerState}) ->
146 | gen_tcp:close(Sock),
147 | Callback:terminate(Reason, ServerState),
148 | ok.
149 |
150 | %% @hidden
151 | code_change(_OldVsn, State, _Extra) ->
152 | {ok, State}.
153 |
154 | %% Internal functions
155 |
156 | %% @hidden
157 | %% @spec listen_on(CallbackModule, IpAddr, Port) -> Result
158 | %% CallbackModule = atom()
159 | %% IpAddr = string() | tuple()
160 | %% Port = integer()
161 | %% Result = {ok, port()} | {error, any()}
162 | listen_on(CallbackModule, IpAddr, Port) when is_tuple(IpAddr) andalso
163 | (8 =:= size(IpAddr) orelse
164 | 4 =:= size(IpAddr)) ->
165 | SockOpts = [{ip, IpAddr}|CallbackModule:sock_opts()],
166 | case gen_tcp:listen(Port, SockOpts) of
167 | {ok, LSock} ->
168 | {ok, _Ref} = prim_inet:async_accept(LSock, -1),
169 | {ok, LSock};
170 | Err ->
171 | Err
172 | end;
173 | listen_on(CallbackModule, IpAddrStr, Port) ->
174 | case inet:parse_address(IpAddrStr) of
175 | {ok, IpAddr} ->
176 | listen_on(CallbackModule, IpAddr, Port);
177 | Err ->
178 | lager:critical("Cannot start listener for ~p on invalid address ~p:~p", [CallbackModule, IpAddrStr, Port]),
179 | Err
180 | end.
--------------------------------------------------------------------------------
/src/result_subscriber.erl:
--------------------------------------------------------------------------------
1 | -module(result_subscriber).
2 |
3 | -compile([debug_info, export_all]).
4 |
5 | -include_lib("eunit/include/eunit.hrl").
6 |
7 |
8 | start_link() ->
9 | gen_server:start({local, ?MODULE}, ?MODULE, [], []).
10 |
11 | init([]) ->
12 | gproc:reg({p, l, {self(), result_subscribers}}),
13 | {ok, ok}.
14 |
15 | handle_call(stop, _From, State) ->
16 | {stop, normal, ok, State};
17 | handle_call(get_result, _From, State) ->
18 | {reply, {ok, State}, State, 50000}.
19 |
20 | handle_info(timeout, State) ->
21 | {stop,normal,State};
22 |
23 | handle_info(_Info, _State) ->
24 | ?debugMsg(io_lib:format("Info: ~p~n",[_Info])),
25 | {noreply, _Info, infinity}.
26 |
27 | terminate(_Reason, _State) ->
28 | ?debugMsg(io_lib:format("Reason: ~p~n",[_Reason])),
29 | ok.
30 |
31 |
--------------------------------------------------------------------------------
/src/rivus_cep.app.src:
--------------------------------------------------------------------------------
1 | %%-*- mode: erlang -*-
2 | {application, rivus_cep,
3 | [
4 | {description, "Rivus CEP"},
5 | {vsn, "0.1"},
6 | {registered, []},
7 | {applications, [
8 | kernel,
9 | stdlib,
10 | gproc,
11 | lager
12 | ]},
13 | {mod, { rivus_cep_app, []}},
14 | {env, []}
15 | ]}.
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/rivus_cep.erl:
--------------------------------------------------------------------------------
1 | %%------------------------------------------------------------------------------
2 | %% Copyright (c) 2013 Vasil Kolarov
3 | %%
4 | %% Licensed under the Apache License, Version 2.0 (the "License");
5 | %% you may not use this file except in compliance with the License.
6 | %% You may obtain a copy of the License at
7 | %%
8 | %% http://www.apache.org/licenses/LICENSE-2.0
9 | %%
10 | %% Unless required by applicable law or agreed to in writing, software
11 | %% distributed under the License is distributed on an "AS IS" BASIS,
12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | %% See the License for the specific language governing permissions and
14 | %% limitations under the License.
15 | %%------------------------------------------------------------------------------
16 |
17 | -module(rivus_cep).
18 | -behaviour(gen_server).
19 | -compile([{parse_transform, lager_transform}]).
20 |
21 | %% API
22 | -export([start_link/0,
23 | start_link/1,
24 | execute/1,
25 | execute/2,
26 | execute/4,
27 | execute/5,
28 | load_query/4,
29 | notify/1,
30 | notify/2,
31 | notify/3,
32 | notify_sync/1,
33 | notify_sync/2,
34 | notify_sync/3,
35 | get_query_details/4]).
36 |
37 | %% gen_server API
38 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
39 | terminate/2, code_change/3]).
40 |
41 | -define(SERVER, ?MODULE).
42 |
43 | -include("rivus_cep.hrl").
44 | -include_lib("folsom/include/folsom.hrl").
45 |
46 | -record(state, {query_sup,
47 | clock_sup,
48 | win_register = dict:new()}
49 | ).
50 |
51 | %%--------------------------------------------------------------------
52 | %% API functions
53 | %%--------------------------------------------------------------------
54 | start_link() ->
55 | SrvId = erlang:phash2({node(), now()}),
56 | gen_server:start_link({local, list_to_atom(integer_to_list(SrvId))}, ?MODULE, [], []).
57 |
58 | start_link(standalone) ->
59 | gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
60 |
61 | load_query(QueryStr, Producers, Subscribers, Options) ->
62 | gen_server:call(?SERVER, {load_query, [QueryStr, Producers, Subscribers, Options]}).
63 |
64 | execute(QueryStr) ->
65 | gen_server:call(?SERVER, {execute, [QueryStr, [], [], []]}).
66 |
67 | execute(Pid, QueryStr) ->
68 | gen_server:call(Pid, {execute, [QueryStr, [], [], []]}).
69 |
70 | execute(QueryStr, Producers, Subscribers, Options) ->
71 | gen_server:call(?SERVER, {execute, [QueryStr, Producers, Subscribers, Options]}).
72 |
73 | execute(Pid, QueryStr, Producers, Subscribers, Options) ->
74 | gen_server:call(Pid, {execute, [QueryStr, Producers, Subscribers, Options]}).
75 |
76 | get_query_details(QueryStr, Producers, Subscribers, Options) ->
77 | gen_server:call(?SERVER, {get_query_details, [QueryStr, Producers, Subscribers, Options]}).
78 |
79 | notify(Event) ->
80 | notify(any, Event).
81 |
82 | notify(Pid, Event) when is_pid(Pid) ->
83 | notify(Pid, any, Event);
84 | notify(Producer, Event) ->
85 | gen_server:cast(?SERVER, {notify, Producer, Event}).
86 |
87 | notify(Pid, Producer, Event) ->
88 | gen_server:cast(Pid, {notify, Producer, Event}).
89 |
90 |
91 |
92 | notify_sync(Event) ->
93 | notify_sync(any, Event).
94 |
95 | notify_sync(Pid, Event) when is_pid(Pid) ->
96 | notify_sync(Pid, any, Event);
97 | notify_sync(Producer, Event) ->
98 | gen_server:call(?SERVER, {notify, Producer, Event}).
99 |
100 | notify_sync(Pid, Producer, Event) ->
101 | gen_server:call(Pid, {notify, Producer, Event}).
102 |
103 |
104 | init([]) ->
105 | {ok, QuerySup} = rivus_cep_app_srv:get_query_sup(),
106 | {ok, ClockSup} = rivus_cep_app_srv:get_clock_sup(),
107 | {ok, #state{query_sup = QuerySup, clock_sup = ClockSup}}.
108 |
109 |
110 | %%--------------------------------------------------------------------
111 | %% gen_server functions
112 | %%--------------------------------------------------------------------
113 | handle_cast({notify, Producer, Event}, #state{win_register = WinReg} = State) ->
114 | EventName = element(1, Event),
115 | gproc:send({p, l, {Producer, EventName}}, Event),
116 | case dict:is_key(EventName, WinReg) of
117 | true -> Window = dict:fetch(EventName, WinReg),
118 | lager:debug("Updating global window: ~p~n", [Window]),
119 | rivus_cep_window:update(Window, Event),
120 | gproc:send({p, l, {Producer, EventName, global}}, Event);
121 | false -> ok
122 | end,
123 | {noreply, State};
124 | handle_cast(_Msg, State) ->
125 | {noreply, State}.
126 |
127 |
128 | handle_call({load_query, [QueryStr, _Producers, Subscribers, Options]}, _From, State) ->
129 |
130 | WinReg = State#state.win_register,
131 | QuerySup = State#state.query_sup,
132 |
133 | QueryDetails = get_query_details([QueryStr, _Producers, Subscribers, Options], WinReg),
134 |
135 | lager:debug("Query sup, Args: ~p~n", [QueryDetails]),
136 |
137 | {ok, Pid} = supervisor:start_child(QuerySup, [QueryDetails]),
138 | {reply, {ok, Pid, QueryDetails}, State#state{win_register = QueryDetails#query_details.window_register}};
139 | handle_call({execute, [QueryStr, _Producers, Subscribers, Options]}, _From, State) ->
140 |
141 | WinReg = State#state.win_register,
142 | QuerySup = State#state.query_sup,
143 |
144 | QueryDetails = get_query_details([QueryStr, _Producers, Subscribers, Options], WinReg),
145 |
146 | case QueryDetails of
147 | {module, _} -> {reply, ok, State};
148 | _ -> lager:info("Query sup, Args: ~p~n", [QueryDetails]),
149 | {ok, Pid} = supervisor:start_child(QuerySup, [QueryDetails]),
150 | {reply, {ok, Pid, QueryDetails}, State#state{win_register = QueryDetails#query_details.window_register}}
151 | end;
152 | handle_call({get_query_details, [QueryStr, _Producers, Subscribers, Options]}, _From, #state{win_register = WinReg} = State) ->
153 | QueryDetails = get_query_details([QueryStr, _Producers, Subscribers, Options], WinReg),
154 | {reply, {ok, QueryDetails}, State#state{win_register = QueryDetails#query_details.window_register}};
155 | handle_call({notify, Producer, Event}, _From, #state{win_register = WinReg} = State) ->
156 | EventName = element(1, Event),
157 | gproc:send({p, l, {Producer, EventName}}, Event),
158 | case dict:is_key(EventName, WinReg) of
159 | true -> Window = dict:fetch(EventName, WinReg),
160 | rivus_cep_window:update(Window, Event),
161 | gproc:send({p, l, {Producer, EventName, global}}, Event);
162 | false -> ok
163 | end,
164 | {reply, ok, State};
165 | handle_call(_Msg, _From, State) ->
166 | {reply, not_handled, State}.
167 |
168 | handle_info(_Info, State) ->
169 | {noreply, State}.
170 |
171 | terminate(_Reason, _State) ->
172 | ok.
173 |
174 | code_change(_OldVsn, State, _Extra) ->
175 | {ok, State}.
176 |
177 | %%--------------------------------------------------------------------
178 | %% Internal functions
179 | %%--------------------------------------------------------------------
180 | get_query_details([QueryStr, _Producers, Subscribers, Options], WinReg) ->
181 | QueryClauses = parse_query(QueryStr),
182 |
183 | case QueryClauses of
184 | {event, EventDef} -> rivus_cep_event_creator:load_event_mod(EventDef);
185 | _ -> Producers = case _Producers of
186 | [] -> [any];
187 | _ -> _Producers
188 | end,
189 |
190 | {{EventWindow, EvWinPid}, {FsmWindow, FsmWinPid}, NewWinReg} = register_windows(QueryClauses, Options, WinReg),
191 |
192 | #query_details{
193 | clauses = QueryClauses,
194 | producers = Producers,
195 | subscribers = Subscribers,
196 | options = Options,
197 | event_window = EventWindow,
198 | fsm_window = FsmWindow,
199 | window_register = NewWinReg,
200 | event_window_pid = EvWinPid,
201 | fsm_window_pid = FsmWinPid
202 | }
203 | end.
204 |
205 | parse_query(QueryStr) ->
206 | {ok, Tokens, _} = rivus_cep_scanner:string(QueryStr, 1),
207 | {ok, QueryClauses} = rivus_cep_parser:parse(Tokens),
208 | QueryClauses.
209 |
210 | register_windows([_StmtName, _SelectClause, FromClause, _WhereClause, WithinClause, {_Filters}], Options, WinReg) ->
211 | Mod = application:get_env(rivus_cep, rivus_window_provider, rivus_cep_window_ets), %%TODO: to be passed as parameter to the func
212 | %%{ok, EvWinPid} = rivus_cep_window:start_link(Mod),
213 |
214 | {QueryType, Events} = case FromClause of
215 | {pattern, {List}} -> {pattern, List};
216 | {List} -> {simple, List}
217 | end,
218 | SharedStreams = proplists:get_value(shared_streams, Options, false),
219 | case {QueryType, SharedStreams} of
220 | {pattern, _} -> {ok, EvWinPid} = rivus_cep_window:start_link(Mod),
221 | {ok, FsmWinPid} = rivus_cep_window:start_link(Mod),
222 | {{register_local_window(WithinClause, EvWinPid), EvWinPid},
223 | {register_local_window(WithinClause, FsmWinPid), FsmWinPid},
224 | WinReg};
225 | {simple, true} -> {{global, nil}, {nil, nil}, register_global_windows(Events, WithinClause, WinReg)};
226 | _ -> {ok, EvWinPid} = rivus_cep_window:start_link(Mod),
227 | {{register_local_window(WithinClause, EvWinPid), EvWinPid}, {nil, nil}, WinReg}
228 | end.
229 |
230 | register_local_window({Within, _WindowsType}, Pid) ->
231 | rivus_cep_window:new(Pid, slide, Within).
232 |
233 | register_global_windows(Events, WithinClause, WinReg) ->
234 | NewWinReg = lists:foldl(fun(Event, Register) -> case dict:is_key(Event, Register) of
235 | true -> maybe_update_window_size(Event, Register, WithinClause);
236 | false -> create_new_global_window(Event, Register, WithinClause)
237 | end
238 | end, WinReg, Events),
239 | lager:debug("Windows Register: ~p~n", [NewWinReg]),
240 | NewWinReg.
241 |
242 | maybe_update_window_size(Event, WinReg, {Within, _WindowsType}) ->
243 | Window = dict:fetch(Event, WinReg),
244 | Size = Window#slide.window,
245 | case Size < Within of
246 | true -> NewWindow = rivus_cep_window:resize(Window, Within),
247 | dict:store(Event, NewWindow, WinReg);
248 | false -> WinReg
249 | end.
250 |
251 |
252 | create_new_global_window(Event, WinReg, {Within, _WindowType}) ->
253 | %% Mod = application:get_env(rivus_cep, rivus_window_provider, rivus_cep_window_ets), %%TODO: to be passed as parameter to the func
254 | %% {ok, EvWinPid} = rivus_cep_window:start_link(Mod),
255 | Window = rivus_cep_window:new(slide, Within),
256 | dict:store(Event, Window, WinReg).
257 |
--------------------------------------------------------------------------------
/src/rivus_cep_app.erl:
--------------------------------------------------------------------------------
1 | %%------------------------------------------------------------------------------
2 | %% Copyright (c) 2013 Vasil Kolarov
3 | %%
4 | %% Licensed under the Apache License, Version 2.0 (the "License");
5 | %% you may not use this file except in compliance with the License.
6 | %% You may obtain a copy of the License at
7 | %%
8 | %% http://www.apache.org/licenses/LICENSE-2.0
9 | %%
10 | %% Unless required by applicable law or agreed to in writing, software
11 | %% distributed under the License is distributed on an "AS IS" BASIS,
12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | %% See the License for the specific language governing permissions and
14 | %% limitations under the License.
15 | %%------------------------------------------------------------------------------
16 |
17 | -module(rivus_cep_app).
18 | -behaviour(application).
19 | -compile([{parse_transform, lager_transform}]).
20 |
21 | %% Application callbacks
22 | -export([start/0, stop/0, start/2, stop/1]).
23 |
24 | -define(CEPSUP, ).
25 |
26 | start() ->
27 | start(normal, []).
28 |
29 | stop() ->
30 | stop([]).
31 |
32 |
33 | start(_StartType, _StartArgs) ->
34 | WinMod = application:get_env(rivus_cep, rivus_window_provider, rivus_cep_window_ets),
35 | WinSup = {window__sup,
36 | {rivus_cep_window, start_link, [WinMod, global]},
37 | permanent,
38 | 10000,
39 | supervisor,
40 | [rivus_cep_window]},
41 | case rivus_cep_app_srv_sup:start_link() of
42 | {ok, AppSupPid} ->
43 | lager:debug("-------> rivus_cep_app, AppSupPid: ~p~n",[AppSupPid]),
44 | {ok, _} = supervisor:start_child(AppSupPid, WinSup),
45 | {Ip, Port} = application:get_env(rivus_cep, rivus_tcp_serv, {"127.0.0.1", 5775}),
46 | {ok, _} = rivus_cep_tcp_listener_sup:start_link(Ip, Port),
47 | {ok, _} = rivus_cep_server_sup:start_link(),
48 | {ok, CepSup} = rivus_cep_app_srv:get_cep_sup(),
49 | supervisor:start_child(CepSup, [standalone]),
50 | {ok, AppSupPid};
51 | Error ->
52 | Error
53 | end.
54 |
55 | stop(_State) ->
56 | ok.
57 |
--------------------------------------------------------------------------------
/src/rivus_cep_app_srv.erl:
--------------------------------------------------------------------------------
1 | %%%-------------------------------------------------------------------
2 | %%% @author vasco
3 | %%% @copyright (C) 2014,
4 | %%% @doc
5 | %%%
6 | %%% @end
7 | %%% Created : 13. Nov 2014 10:08 PM
8 | %%%-------------------------------------------------------------------
9 | -module(rivus_cep_app_srv).
10 | -compile([{parse_transform, lager_transform}]).
11 | -behaviour(gen_server).
12 |
13 | %% API
14 | -export([start_link/1,
15 | get_query_sup/0,
16 | get_clock_sup/0,
17 | get_cep_sup/0]).
18 |
19 | %% gen_server callbacks
20 | -export([init/1,
21 | handle_call/3,
22 | handle_cast/2,
23 | handle_info/2,
24 | terminate/2,
25 | code_change/3]).
26 |
27 | -define(SERVER, ?MODULE).
28 |
29 | -record(state, {
30 | app_srv_sup,
31 | query_sup,
32 | clock_sup,
33 | cep_sup
34 | }).
35 |
36 | %%%===================================================================
37 | %%% API
38 | %%%===================================================================
39 | get_query_sup() ->
40 | gen_server:call(?SERVER, get_query_sup).
41 |
42 | get_clock_sup() ->
43 | gen_server:call(?SERVER, get_clock_sup).
44 |
45 | get_cep_sup() ->
46 | gen_server:call(?SERVER, get_cep_sup).
47 |
48 | %%--------------------------------------------------------------------
49 | %% @doc
50 | %% Starts the server
51 | %%
52 | %% @end
53 | %%--------------------------------------------------------------------
54 | start_link(Sup) ->
55 | gen_server:start_link({local, ?SERVER}, ?MODULE, [Sup], []).
56 |
57 | %%%===================================================================
58 | %%% gen_server callbacks
59 | %%%===================================================================
60 |
61 | %%--------------------------------------------------------------------
62 | %% @private
63 | %% @doc
64 | %% Initializes the server
65 | %%
66 | %% @spec init(Args) -> {ok, State} |
67 | %% {ok, State, Timeout} |
68 | %% ignore |
69 | %% {stop, Reason}
70 | %% @end
71 | %%--------------------------------------------------------------------
72 | init([Sup]) ->
73 | QuerySupSpec = {query_worker_sup,
74 | {rivus_cep_query_worker_sup, start_link, []},
75 | permanent,
76 | 10000,
77 | supervisor,
78 | [rivus_cep_query_worker_sup]},
79 | BatchClockSupSpec = {batch_clock_sup,
80 | {rivus_cep_clock_sup, start_link, []},
81 | permanent,
82 | 10000,
83 | supervisor,
84 | [rivus_cep_clock_sup]},
85 | CepSupSpeck = {cep_worker_sup,
86 | {rivus_cep_sup, start_link, []},
87 | permanent,
88 | 10000,
89 | supervisor,
90 | [rivus_cep_sup]},
91 | self() ! {start_query_supervisor, Sup, QuerySupSpec},
92 | self() ! {start_batch_clock_supervisor, Sup, BatchClockSupSpec},
93 | self() ! {start_cep_worker_supervisor, Sup, CepSupSpeck},
94 | lager:info("--- Rivus CEP server started"),
95 | {ok, #state{app_srv_sup = Sup}}.
96 |
97 | %%--------------------------------------------------------------------
98 | %% @private
99 | %% @doc
100 | %% Handling call messages
101 | %%
102 | %% @end
103 | %%--------------------------------------------------------------------
104 | handle_call(get_query_sup, _From, #state{query_sup = Sup} = State) ->
105 | {reply, {ok, Sup}, State};
106 | handle_call(get_clock_sup, _From, #state{clock_sup = Sup} = State) ->
107 | {reply, {ok, Sup}, State};
108 | handle_call(get_cep_sup, _From, #state{cep_sup = Sup} = State) ->
109 | {reply, {ok, Sup}, State};
110 | handle_call(_Request, _From, State) ->
111 | {reply, ok, State}.
112 |
113 | %%--------------------------------------------------------------------
114 | %% @private
115 | %% @doc
116 | %% Handling cast messages
117 | %%
118 | %% @end
119 | %%--------------------------------------------------------------------
120 | handle_cast(_Request, State) ->
121 | {noreply, State}.
122 |
123 | %%--------------------------------------------------------------------
124 | %% @private
125 | %% @doc
126 | %% Handling all non call/cast messages
127 | %%
128 | %% @spec handle_info(Info, State) -> {noreply, State} |
129 | %% {noreply, State, Timeout} |
130 | %% {stop, Reason, State}
131 | %% @end
132 | %%--------------------------------------------------------------------
133 | handle_info({start_query_supervisor, Sup, QuerySupSpec}, State) ->
134 | lager:info("----> Start Query Sup, Args: ~p, ~p~n", [Sup, QuerySupSpec]),
135 | {ok, Pid} = supervisor:start_child(Sup, QuerySupSpec),
136 | link(Pid),
137 | {noreply, State#state{query_sup = Pid}};
138 | handle_info({start_batch_clock_supervisor, Sup, BatchClockSupSpec}, State) ->
139 | {ok, Pid} = supervisor:start_child(Sup, BatchClockSupSpec),
140 | link(Pid),
141 | {noreply, State#state{clock_sup = Pid}};
142 | handle_info({start_cep_worker_supervisor, Sup, CepSupSpeck}, State) ->
143 | {ok, Pid} = supervisor:start_child(Sup, CepSupSpeck),
144 | link(Pid),
145 | {noreply, State#state{cep_sup = Pid}};
146 | handle_info(_Info, State) ->
147 | {noreply, State}.
148 |
149 | %%--------------------------------------------------------------------
150 | %% @private
151 | %% @doc
152 | %% This function is called by a gen_server when it is about to
153 | %% terminate. It should be the opposite of Module:init/1 and do any
154 | %% necessary cleaning up. When it returns, the gen_server terminates
155 | %% with Reason. The return value is ignored.
156 | %%
157 | %% @spec terminate(Reason, State) -> void()
158 | %% @end
159 | %%--------------------------------------------------------------------
160 | terminate(_Reason, _State) ->
161 | ok.
162 |
163 | %%--------------------------------------------------------------------
164 | %% @private
165 | %% @doc
166 | %% Convert process state when code is changed
167 | %%
168 | %% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}
169 | %% @end
170 | %%--------------------------------------------------------------------
171 | code_change(_OldVsn, State, _Extra) ->
172 | {ok, State}.
173 |
174 | %%%===================================================================
175 | %%% Internal functions
176 | %%%===================================================================
177 |
--------------------------------------------------------------------------------
/src/rivus_cep_app_srv_sup.erl:
--------------------------------------------------------------------------------
1 | %%%-------------------------------------------------------------------
2 | %%% @author vasco
3 | %%% @copyright (C) 2014,
4 | %%% @doc
5 | %%%
6 | %%% @end
7 | %%% Created : 13. Nov 2014 10:09 PM
8 | %%%-------------------------------------------------------------------
9 | -module(rivus_cep_app_srv_sup).
10 | -compile([{parse_transform, lager_transform}]).
11 | -behaviour(supervisor).
12 |
13 | %% API
14 | -export([start_link/0]).
15 |
16 | %% Supervisor callbacks
17 | -export([init/1]).
18 |
19 | -define(SERVER, ?MODULE).
20 |
21 | start_link() ->
22 | supervisor:start_link({local, ?SERVER}, ?MODULE, []).
23 |
24 | init([]) ->
25 | lager:debug("-----> rivus_cep_app_sup, Args: ~p~n", [[]]),
26 | RestartStrategy = one_for_one,
27 | MaxRestarts = 1000,
28 | MaxSecondsBetweenRestarts = 3600,
29 |
30 | SupFlags = {RestartStrategy, MaxRestarts, MaxSecondsBetweenRestarts},
31 |
32 | Restart = permanent,
33 | Shutdown = 2000,
34 | Type = worker,
35 |
36 | AChild = {rivus_cep_app_srv, {rivus_cep_app_srv, start_link, [self()]},
37 | Restart, Shutdown, Type, [rivus_cep_app_srv]},
38 |
39 | {ok, {SupFlags, [AChild]}}.
40 |
--------------------------------------------------------------------------------
/src/rivus_cep_clock_server.erl:
--------------------------------------------------------------------------------
1 | %%------------------------------------------------------------------------------
2 | %% Copyright (c) 2013-2014 Vasil Kolarov
3 | %%
4 | %% Licensed under the Apache License, Version 2.0 (the "License");
5 | %% you may not use this file except in compliance with the License.
6 | %% You may obtain a copy of the License at
7 | %%
8 | %% http://www.apache.org/licenses/LICENSE-2.0
9 | %%
10 | %% Unless required by applicable law or agreed to in writing, software
11 | %% distributed under the License is distributed on an "AS IS" BASIS,
12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | %% See the License for the specific language governing permissions and
14 | %% limitations under the License.
15 | %%------------------------------------------------------------------------------
16 |
17 | -module(rivus_cep_clock_server).
18 | -behaviour(gen_server).
19 | -compile([{parse_transform, lager_transform}]).
20 | -define(SERVER, ?MODULE).
21 |
22 | %% ------------------------------------------------------------------
23 | %% API Function Exports
24 | %% ------------------------------------------------------------------
25 |
26 | -export([start_link/2,
27 | stop/1]).
28 |
29 | %% ------------------------------------------------------------------
30 | %% gen_server Function Exports
31 | %% ------------------------------------------------------------------
32 |
33 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
34 | terminate/2, code_change/3]).
35 |
36 | -record(state, {query_pid, interval}).
37 | %% ------------------------------------------------------------------
38 | %% API Function Definitions
39 | %% ------------------------------------------------------------------
40 |
41 | start_link(QueryPid, Interval) ->
42 | gen_server:start_link(?MODULE, [QueryPid, Interval], []).
43 |
44 |
45 | stop(Pid) ->
46 | gen_server:cast(Pid, stop).
47 |
48 | %% ------------------------------------------------------------------
49 | %% gen_server Function Definitions
50 | %% ------------------------------------------------------------------
51 |
52 | init([QueryPid, Interval]) ->
53 | lager:debug(" Clock Server STARTED ......~n",[]),
54 | {ok, #state{query_pid = QueryPid, interval = Interval}, Interval}.
55 |
56 | handle_call(_Request, _From, State) ->
57 | {reply, ok, State}.
58 |
59 | handle_cast(stop, State) ->
60 | {stop, normal, State};
61 | handle_cast(_Msg, State) ->
62 | {noreply, State}.
63 |
64 | handle_info(timeout, State=#state{query_pid = QueryPid, interval = Interval}) ->
65 | %%gen_server:cast(QueryPid, generate_result),
66 | lager:debug(" Clock Server TIMEOUT ......~n",[]),
67 | rivus_cep_query_worker:generate_result(QueryPid),
68 | {noreply, State, Interval};
69 | handle_info(_Info, State) ->
70 | {noreply, State}.
71 |
72 | terminate(_Reason, _State) ->
73 | ok.
74 |
75 | code_change(_OldVsn, State, _Extra) ->
76 | {ok, State}.
77 |
--------------------------------------------------------------------------------
/src/rivus_cep_clock_sup.erl:
--------------------------------------------------------------------------------
1 | %%------------------------------------------------------------------------------
2 | %% Copyright (c) 2013-2014 Vasil Kolarov
3 | %%
4 | %% Licensed under the Apache License, Version 2.0 (the "License");
5 | %% you may not use this file except in compliance with the License.
6 | %% You may obtain a copy of the License at
7 | %%
8 | %% http://www.apache.org/licenses/LICENSE-2.0
9 | %%
10 | %% Unless required by applicable law or agreed to in writing, software
11 | %% distributed under the License is distributed on an "AS IS" BASIS,
12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | %% See the License for the specific language governing permissions and
14 | %% limitations under the License.
15 | %%------------------------------------------------------------------------------
16 |
17 | -module(rivus_cep_clock_sup).
18 | -behaviour(supervisor).
19 | -export([start_link/0,
20 | init/1,
21 | start_clock_server/2]).
22 |
23 | start_link () ->
24 | supervisor:start_link({local,?MODULE},?MODULE,[]).
25 |
26 | start_clock_server(QueryPid, Interval) ->
27 | {ok, Pid} = supervisor:start_child(?MODULE, [QueryPid, Interval]),
28 | Pid.
29 |
30 | init ([]) ->
31 | {ok,{{simple_one_for_one, 3, 180},
32 | [
33 | {undefined, {rivus_cep_clock_server, start_link, []},
34 | transient, brutal_kill, worker, [rivus_cep_clock_server]}
35 | ]}}.
36 |
--------------------------------------------------------------------------------
/src/rivus_cep_event_creator.erl:
--------------------------------------------------------------------------------
1 | %%------------------------------------------------------------------------------
2 | %% Copyright (c) 2014 Vasil Kolarov
3 | %%
4 | %% Licensed under the Apache License, Version 2.0 (the "License");
5 | %% you may not use this file except in compliance with the License.
6 | %% You may obtain a copy of the License at
7 | %%
8 | %% http://www.apache.org/licenses/LICENSE-2.0
9 | %%
10 | %% Unless required by applicable law or agreed to in writing, software
11 | %% distributed under the License is distributed on an "AS IS" BASIS,
12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | %% See the License for the specific language governing permissions and
14 | %% limitations under the License.
15 | %%------------------------------------------------------------------------------
16 | -module(rivus_cep_event_creator).
17 | -compile([{parse_transform, lager_transform}]).
18 | -include_lib("eunit/include/eunit.hrl").
19 |
20 | %% API
21 | -export([load_event_mod/1]).
22 |
23 | load_event_mod({EventName, ParameterNames}) when is_atom(EventName) andalso is_list(ParameterNames) ->
24 | %%{event10, [attr1, attr2, attr3]}
25 | Module = erl_syntax:attribute(erl_syntax:atom(module),[erl_syntax:atom(EventName)]),
26 | ModuleForm = erl_syntax:revert(Module),
27 |
28 | ExpFunctionList = erl_syntax:list([erl_syntax:arity_qualifier(erl_syntax:atom(get_param_by_name),erl_syntax:integer(2)),
29 | erl_syntax:arity_qualifier(erl_syntax:atom(get_param_names),erl_syntax:integer(0))]),
30 | Export = erl_syntax:attribute(erl_syntax:atom(export),[ExpFunctionList]),
31 | ExportForm = erl_syntax:revert(Export),
32 |
33 | GetParamFuncForm = create_get_param_by_name(ParameterNames),
34 | GetAttrFuncForm = create_get_param_names(ParameterNames),
35 |
36 | {ok, Mod, Bin, _} = compile:forms([ModuleForm, ExportForm, GetParamFuncForm, GetAttrFuncForm], [return]),
37 | code:load_binary(Mod, [], Bin).
38 |
39 | create_get_param_by_name(ParameterNames) ->
40 | EventVar = erl_syntax:variable("Event"),
41 | ParamNameVar = erl_syntax:variable("ParamName"),
42 |
43 | %%Case = case_expr(Argument::syntaxTree(), Clauses::[syntaxTree()]) -> syntaxTree()
44 | {_, ClausesList} = lists:foldl(fun(Attr, {Idx, L}) ->
45 | {Idx + 1, L ++ [create_case_clause(EventVar, Attr, Idx)]}
46 | end,
47 | {2, [create_case_clause(EventVar, name, 1)]},
48 | ParameterNames),
49 | CaseExpr = erl_syntax:case_expr(ParamNameVar, ClausesList),
50 | FuncClause = erl_syntax:clause([EventVar, ParamNameVar], [], [CaseExpr]),
51 | Function = erl_syntax:function(erl_syntax:atom(get_param_by_name), [FuncClause]),
52 | FunctionForm = erl_syntax:revert(Function),
53 | FunctionForm.
54 |
55 | create_get_param_names(ParameterNames) ->
56 | Body = erl_syntax:list([erl_syntax:atom(ParamName) || ParamName <- ParameterNames]),
57 | FuncClause = erl_syntax:clause([], [], [Body]),
58 | Function = erl_syntax:function(erl_syntax:atom(get_param_names), [FuncClause]),
59 | FunctionForm = erl_syntax:revert(Function),
60 | FunctionForm.
61 |
62 | create_case_clause(EventVar, ParameterNames, Idx) ->
63 | %%clause(Patterns::[syntaxTree()], Guard::guard(), Body::[syntaxTree()]) -> syntaxTree()
64 | ClauseBody = erl_syntax:application(none, erl_syntax:atom(element), [erl_syntax:integer(Idx), EventVar]),
65 | erl_syntax:clause([erl_syntax:atom(ParameterNames)],[],[ClauseBody]).
--------------------------------------------------------------------------------
/src/rivus_cep_parser.yrl:
--------------------------------------------------------------------------------
1 | %% ; -*- mode: Erlang;-*-
2 |
3 | Header "%% Copyright (c)" "%% @Vasil Kolarov".
4 |
5 | Nonterminals declaration event_declaration query_declaration select_clause select_elements from_clause pattern where_clause within_clause filter filter_predicates filter_predicate name_clause name_params event events alias event_param expression predicate predicates operand uminus function literal window_type event_name event_attribute event_attributes.
6 |
7 | Terminals define as select from where within seconds and or not if foreach '(' ')' '+' '-' '*' '/' '<' '>' '=' '<=' '>=' '<>' ',' '->' atom integer float index of '.' var string char count sum min max avg sliding batch tumbling.
8 |
9 | Rootsymbol declaration.
10 |
11 | Endsymbol semicolon.
12 |
13 | Nonassoc 100 '='.
14 | Nonassoc 200 '>'.
15 | Nonassoc 300 '<'.
16 | Nonassoc 400 '>='.
17 | Nonassoc 500 '<='.
18 | Nonassoc 600 '<>'.
19 | Left 700 '+'.
20 | Left 800 '-'.
21 | Left 900 'or'.
22 | Left 1000 '*'.
23 | Left 1100 '/'.
24 | Left 1200 'and'.
25 | Unary 1300 uminus.
26 | Unary 1400 'not'.
27 |
28 | uminus -> '-' expression.
29 |
30 | declaration -> event_declaration : {event, '$1'}.
31 | declaration -> query_declaration : '$1'.
32 |
33 | event_declaration -> define name_clause as '(' event_attributes ')' : {'$2', '$5'}.
34 |
35 | event_attributes -> event_attribute: '$1'.
36 | event_attributes -> event_attribute ',' event_attributes: flatten(['$1', '$3']).
37 |
38 | event_attribute -> atom: value_of('$1').
39 |
40 |
41 | query_declaration -> define
42 | name_clause as
43 | select_clause
44 | from_clause
45 | where_clause
46 | within_clause : get_ast({'$2','$4','$5','$6','$7'}).
47 |
48 | query_declaration -> define
49 | name_clause as
50 | select_clause
51 | from_clause
52 | where_clause : get_ast({'$2','$4','$5','$6',nil}).
53 |
54 | query_declaration -> define
55 | name_clause as
56 | select_clause
57 | from_clause
58 | within_clause : get_ast({'$2','$4','$5',nil,'$6'}).
59 |
60 | query_declaration -> define
61 | name_clause as
62 | select_clause
63 | from_clause: get_ast({'$2','$4','$5',nil,nil}).
64 |
65 | name_clause -> atom: value_of('$1').
66 |
67 | select_clause -> select select_elements: flatten(['$2']).
68 |
69 | select_elements -> expression: '$1'.
70 | select_elements -> expression ',' select_elements: flatten(['$1', '$3']).
71 |
72 | event_param -> atom: {nil, value_of('$1')}.
73 | event_param -> alias '.' atom: {'$1', value_of('$3')}.
74 |
75 | from_clause -> from events: '$2'.
76 | from_clause -> from pattern: {pattern, '$2'}.
77 |
78 | pattern -> event '->' event: ['$1','$3'].
79 | pattern -> event '->' pattern: ['$1', '$3'].
80 |
81 | event -> atom: {nil, value_of('$1')}.
82 | event -> atom filter: {nil, value_of('$1'), '$2'}.
83 | event -> atom as alias: {'$3',value_of('$1')}.
84 | event -> atom filter as alias: {'$4',value_of('$1'), '$2'}.
85 | events -> event: ['$1'].
86 | events -> event ',' events: flatten(['$1','$3']).
87 |
88 | filter -> '(' filter_predicates ')': '$2'.
89 | filter_predicates -> filter_predicate: ['$1'].
90 | filter_predicates -> filter_predicate ',' filter_predicates: flatten(['$1','$3']).
91 |
92 | filter_predicate -> event_param '=' literal: {'eq', '$1', '$3'}.
93 | filter_predicate -> event_param '>' literal: {'gt', '$1', '$3'}.
94 | filter_predicate -> event_param '<' literal: {'lt', '$1', '$3'}.
95 | filter_predicate -> event_param '<=' literal: {'lte', '$1', '$3'}.
96 | filter_predicate -> event_param '>=' literal: {'gte', '$1', '$3'}.
97 | filter_predicate -> event_param '<>' literal: {'neq', '$1', '$3'}.
98 |
99 | literal -> integer: {integer, value_of('$1')}.
100 | literal -> float: {float, value_of('$1')}.
101 | literal -> string: {string, value_of('$1')}.
102 | literal -> char: {char, value_of('$1')}.
103 | literal -> atom: {atom, value_of('$1')}.
104 |
105 | alias -> atom: value_of('$1').
106 |
107 | where_clause -> where predicates: '$2'.
108 |
109 | expression -> event_param: '$1'.
110 | expression -> expression '+' expression: {'plus','$1','$3'}.
111 | expression -> expression '-' expression: {'minus','$1','$3'}.
112 | expression -> expression '*' expression: {'mult','$1','$3'}.
113 | expression -> expression '/' expression: {'div','$1','$3'}.
114 | expression -> '(' expression ')': '$2'.
115 | expression -> function '(' expression ')': {type_of('$1'), '$3'}.
116 | expression -> integer: {integer,value_of('$1')}.
117 | expression -> float: {float, value_of('$1')}.
118 |
119 |
120 | predicates -> predicates 'or' predicates: {'or','$1','$3'}.
121 | predicates -> predicates 'and' predicates: {'and','$1','$3'}.
122 | predicates -> 'not' predicates: {neg, '$2'}.
123 | predicates -> predicate: '$1'.
124 | predicate -> '(' predicate ')' : '$2'.
125 | predicate -> expression '=' expression: {'eq','$1','$3'}.
126 | predicate -> expression '<' expression: {'lt','$1','$3'}.
127 | predicate -> expression '>' expression: {'gt','$1','$3'}.
128 | predicate -> expression '<=' expression: {'lte','$1','$3'}.
129 | predicate -> expression '>=' expression: {'gte','$1','$3'}.
130 | predicate -> expression '<>' expression: {'neq','$1','$3'}.
131 |
132 | within_clause -> within integer seconds: value_of('$2').
133 | within_clause -> within integer seconds window_type: {value_of('$2'), '$4'}.
134 |
135 | window_type -> sliding : sliding.
136 | window_type -> batch : batch.
137 | %% TODO window_type -> tumbling : tumbling.
138 |
139 | function -> sum: '$1'.
140 | function -> count: '$1'.
141 | function -> min: '$1'.
142 | function -> max: '$1'.
143 | function -> avg: '$1'.
144 |
145 | Erlang code.
146 |
147 | -import(erl_syntax, []).
148 | -include_lib("eunit/include/eunit.hrl").
149 |
150 | value_of(Token) -> element(3, Token).
151 | line_of(Token) -> element(2, Token).
152 | type_of(Token) -> element(1, Token).
153 | flatten(L) -> lists:flatten(L).
154 |
155 | get_ast({Name, SelectClause, _FromClause, WhereClause, WithinClause}) ->
156 | %%?debugMsg(io_lib:format("SelectClause: ~p~n",[SelectClause])),
157 | %%?debugMsg(io_lib:format("_FromClause: ~p~n",[_FromClause])),
158 | %%?debugMsg(io_lib:format("Within clause: ~p~n",[WithinClause])),
159 |
160 | {IsPattern, FromClause, Filters} = case _FromClause of
161 | {pattern, Events} -> {true, get_events(Events), get_filters(Events)};
162 | Events when is_list(Events) -> {false, get_events(Events), get_filters(Events)}
163 | end,
164 | %%?debugMsg(io_lib:format("FromClause: ~p~n",[FromClause])),
165 | %%?debugMsg(io_lib:format("Filters: ~p~n",[Filters])),
166 |
167 | Select = replace_select_aliases(SelectClause, FromClause),
168 | Where = replace_where_aliases(WhereClause, FromClause),
169 | From = case IsPattern of
170 | true -> {pattern, {remove_from_aliases(FromClause, [])}};
171 | false -> {remove_from_aliases(FromClause, [])}
172 | end,
173 | FiltersDict = orddict:from_list(Filters),
174 | Within = case WithinClause of
175 | {D, W} -> {D, W};
176 | D when is_integer(D) -> {D, sliding};
177 | nil -> {nil}
178 | end,
179 | [{Name}, {Select}, From, {Where}, Within, {FiltersDict}].
180 |
181 |
182 | get_events(Events) ->
183 | get_events(Events, []).
184 |
185 | get_events([L | T], Acc) when is_list(L) ->
186 | Acc2 = get_events(L, []),
187 | get_events(T, Acc ++ [get_events(L, [])]);
188 | get_events([{Alias, Event} | T], Acc) ->
189 | get_events(T, Acc ++ [{Alias, Event}]);
190 | get_events([{Alias, Event, _} | T], Acc) ->
191 | get_events(T, Acc ++ [{Alias, Event}]);
192 | get_events([], Acc) ->
193 | Acc.
194 |
195 |
196 | get_filters(Events) ->
197 | get_filters(Events, []).
198 |
199 | get_filters([L | T], Acc) when is_list(L) ->
200 | get_filters(T, Acc ++ get_filters(L, []));
201 | get_filters([{_, _} | T], Acc) ->
202 | get_filters(T, Acc);
203 | get_filters([{Alias, Event, FilterPred} | T], Acc) ->
204 | get_filters(T, Acc ++ [{Event, replace_filter_aliases(Event, FilterPred)}]);
205 | get_filters([], Acc) ->
206 | Acc.
207 |
208 | replace_filter_aliases(Event, Predicates) ->
209 | lists:map(fun({Op, Left, Right}) ->
210 | {Op,
211 | replace_filter_alias(Event, Left),
212 | replace_filter_alias(Event, Right)}
213 | end, Predicates).
214 |
215 | replace_filter_alias(Event, {nil, Param}) ->
216 | {Event, Param};
217 | replace_filter_alias(_Event, {atom, Param}) ->
218 | {atom, Param};
219 | replace_filter_alias(_Event, {integer, Param}) ->
220 | {integer, Param};
221 | replace_filter_alias(_Event, {float, Param}) ->
222 | {float, Param};
223 | replace_filter_alias(_Event, {string, Param}) ->
224 | {string, Param};
225 | replace_filter_alias(Event, {_Alias, Param}) ->
226 | {Event, Param};
227 | replace_filter_alias(_Event, Predicate) ->
228 | Predicate.
229 |
230 |
231 | replace_select_aliases({nil, ParamName}, FromTuples) ->
232 | case length(FromTuples) == 1 of
233 | true -> {element(2, hd(FromTuples)), ParamName};
234 | _ -> erlang:error({error, missing_event_qualifier})
235 | end;
236 | replace_select_aliases({Alias, ParamName}, FromTuples) ->
237 | {_, Event} = lists:keyfind(Alias, 1, FromTuples),
238 | [Event, ParamName];
239 | replace_select_aliases(Tuples, FromTuples) when is_list(Tuples) ->
240 | lists:map(fun({E1, E2, E3}) when is_tuple(E2) andalso is_tuple(E3) ->
241 | list_to_tuple(replace_select_aliases([E1, E2, E3], FromTuples));
242 | ({E1, E2}) when is_tuple(E2) ->
243 | {E1, list_to_tuple(replace_select_aliases(E2, FromTuples))};
244 | ({E1, E2}) ->
245 | case E1 of
246 | integer -> {integer, E2};
247 | float -> {float, E2};
248 | atom -> {atom, E2};
249 | nil -> case find_event_name(FromTuples, E2) of
250 | ubiquitous_event_param -> erlang:error({error, ubiquitous_event_param});
251 | EventName when is_atom(EventName) -> {EventName, E2}
252 | end;
253 | _ -> {_, Event} = lists:keyfind(E1, 1, lists:flatten(FromTuples)),
254 | {Event, E2}
255 | end;
256 | ({EventParam}) ->
257 | case length(FromTuples) of
258 | 1 -> {element(1, hd(FromTuples)), EventParam};
259 | _ -> erlang:error({error, missing_event_qualifier})
260 | end;
261 | (nil) -> erlang:error({error, missing_event_qualifier});
262 | (Atom) when is_atom(Atom) -> Atom
263 | end,
264 | Tuples).
265 |
266 |
267 | replace_where_aliases(nil, _) ->
268 | nil;
269 | replace_where_aliases({Op, Left, Right}, FromTuples) ->
270 | {Op, replace_where_aliases(Left, FromTuples), replace_where_aliases(Right, FromTuples)};
271 | replace_where_aliases({E1, E2}, FromTuples) ->
272 | case E1 of
273 | integer -> {integer, E2};
274 | float -> {float, E2};
275 | atom -> {atom, E2};
276 | nil -> case find_event_name(FromTuples, E2) of
277 | ubiquitous_event_param -> erlang:error({error, ubiquitous_event_param});
278 | EventName when is_atom(EventName) -> {EventName, E2}
279 | end;
280 | _ -> {_, Event} = lists:keyfind(E1, 1, lists:flatten(FromTuples)),
281 | {Event, E2}
282 | end.
283 |
284 | remove_from_aliases([H | T], Acc) when is_tuple(H) ->
285 | remove_from_aliases(T, Acc ++ [element(2, H)]);
286 | remove_from_aliases([H | T], Acc) when is_list(H) ->
287 | remove_from_aliases(T, Acc ++ [list_to_tuple(remove_from_aliases(H, []))]);
288 | remove_from_aliases([], Acc) ->
289 | Acc.
290 |
291 | find_event_name(FromTuples, ParamName) ->
292 | ParamOccur = lists:foldl(fun({_, EventName}, Acc) ->
293 | Params = EventName:get_param_names(),
294 | case lists:member(ParamName, Params) of
295 | true -> Acc + 1;
296 | false -> Acc
297 | end
298 | end,
299 | 0,
300 | FromTuples),
301 | [{_, Event}] = case ParamOccur > 1 of
302 | true -> ubiquitous_event_param;
303 | false -> lists:filter(fun({_, EventName}) ->
304 | lists:member(ParamName, EventName:get_param_names())
305 | end,
306 | FromTuples)
307 | end,
308 | Event.
--------------------------------------------------------------------------------
/src/rivus_cep_query_planner.erl:
--------------------------------------------------------------------------------
1 | %% ; -*- mode: Erlang;-*-
2 | %%------------------------------------------------------------------------------
3 | %% Copyright (c) 2013-2014 Vasil Kolarov
4 | %%
5 | %% Licensed under the Apache License, Version 2.0 (the "License");
6 | %% you may not use this file except in compliance with the License.
7 | %% You may obtain a copy of the License at
8 | %%
9 | %% http://www.apache.org/licenses/LICENSE-2.0
10 | %%
11 | %% Unless required by applicable law or agreed to in writing, software
12 | %% distributed under the License is distributed on an "AS IS" BASIS,
13 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | %% See the License for the specific language governing permissions and
15 | %% limitations under the License.
16 | %%------------------------------------------------------------------------------
17 | -module(rivus_cep_query_planner).
18 |
19 | -include("rivus_cep.hrl").
20 | -include_lib("eunit/include/eunit.hrl").
21 |
22 | -export([analyze/1, get_join_keys/1,
23 | to_cnf/1,
24 | to_single_predicate/1,
25 | predicates_to_list/1,
26 | get_predicate_variables/1,
27 | sort_predicates/1,
28 | pattern_to_graph/2,
29 | get_start_state/1,
30 | set_predicates_on_edge/4,
31 | get_predicates_on_edge/3,
32 | get_events_on_path/2,
33 | is_first/2,
34 | is_next/3,
35 | is_last/2]).
36 |
37 |
38 | analyze([{_QueryName}, {SelectClause}, FromClause, {WhereClause}, {_Within, WindowType}, {_Filters}]) ->
39 | case FromClause of
40 | {pattern, {Pattern}} -> CNF = to_cnf(WhereClause),
41 | PL = predicates_to_list(CNF),
42 | PV = get_predicate_variables(PL),
43 | G = pattern_to_graph(PV, Pattern),
44 | #query_plan{fsm = G};
45 | {_EventsList} -> JoinKeys = get_join_keys(WhereClause),
46 | FastAggr = JoinKeys =:= [] andalso WindowType =:= batch,
47 |
48 | #query_plan{join_keys = JoinKeys, has_aggregations = is_aggregation_query(SelectClause), fast_aggregations = FastAggr}
49 | end.
50 |
51 | sort_predicates(WhereClause) ->
52 | PL = predicates_to_list(to_cnf(WhereClause)),
53 | get_predicate_variables(lists:flatten(PL)).
54 |
55 | get_predicate_variables(PL) ->
56 | [{get_predicate_variables(Predicate, ordsets:new()), Predicate} || Predicate <- PL].
57 |
58 | get_predicate_variables({_, Left, Right}, Acc) ->
59 | NewAcc = get_predicate_variables(Left, Acc),
60 | get_predicate_variables(Right, NewAcc);
61 | get_predicate_variables({neg, Predicate}, Acc) ->
62 | {neg, get_predicate_variables(Predicate, Acc)};
63 | get_predicate_variables({EventName, Value}, Acc) ->
64 | ordsets:add_element({EventName, Value}, Acc);
65 | get_predicate_variables(_, Acc) ->
66 | Acc.
67 |
68 |
69 | predicates_to_list({'and', P, Q}) ->
70 | predicates_to_list(P) ++ predicates_to_list(Q);
71 | predicates_to_list(Predicate) ->
72 | [Predicate].
73 |
74 |
75 | to_cnf(WhereClause) ->
76 | P = move_not_inwards(WhereClause),
77 | distribute_or_over_and(P).
78 |
79 | to_single_predicate(Predicates) ->
80 | lists:foldl(fun([], Acc) -> Acc;
81 | (P, Acc) when P /= [] -> {'and', Acc, P}
82 | end,
83 | hd(Predicates), tl(Predicates)).
84 |
85 | move_not_inwards({neg, {neg, Predicate}}) ->
86 | move_not_inwards(Predicate);
87 | move_not_inwards({neg, {'or', P, Q}}) ->
88 | {'and', move_not_inwards({neg, P}), move_not_inwards({neg, Q})};
89 | move_not_inwards({neg, {'and', P, Q}}) ->
90 | {'or', move_not_inwards({neg, P}), move_not_inwards({neg, Q})};
91 | move_not_inwards(Predicate) ->
92 | Predicate.
93 |
94 |
95 | distribute_or_over_and({'or', P, {'and', Q, R}}) ->
96 | {'and', distribute_or_over_and({'or', P, Q}), distribute_or_over_and({'or', P, R})};
97 | distribute_or_over_and({'or', {'and', Q, R}, P}) ->
98 | {'and', distribute_or_over_and({'or', P, Q}), distribute_or_over_and({'or', P, R})};
99 | distribute_or_over_and(Predicate) ->
100 | Predicate.
101 |
102 | pattern_to_graph(PredVars, Pattern) ->
103 | {G, _} = pattern_to_graph(start, PredVars, Pattern, digraph:new()),
104 | G.
105 |
106 | pattern_to_graph(start, PredVars, [First, Second | T], G) when is_atom(First), is_atom(Second) ->
107 | FV = digraph:add_vertex(G, First),
108 | Start = FV,
109 | SV = digraph:add_vertex(G, Second),
110 |
111 | E = digraph:add_edge(G, FV, SV, []),
112 | {NewPredVars, Labels} = set_predicates_on_edge(Start, SV, PredVars, G),
113 | digraph:add_edge(G, E, FV, SV, Labels),
114 | pattern_to_graph(Start, NewPredVars, [Second | T], G);
115 | pattern_to_graph(Start, PredVars, [First, Second | T], G) when is_atom(First), is_atom(Second) ->
116 | FV = digraph:add_vertex(G, First),
117 | SV = digraph:add_vertex(G, Second),
118 |
119 | E = digraph:add_edge(G, FV, SV, []),
120 | {NewPredVars, Labels} = set_predicates_on_edge(Start, SV, PredVars, G),
121 | digraph:add_edge(G, E, FV, SV, Labels),
122 | pattern_to_graph(Start, NewPredVars, [Second | T], G);
123 | pattern_to_graph(Start, PredVars, [First, Second | T], G) when is_atom(First), is_tuple(Second) ->
124 | FV = digraph:add_vertex(G, First),
125 | NewPV = lists:foldl(fun(V, PV) when is_atom(V) ->
126 | SV = digraph:add_vertex(G, V),
127 |
128 | E = digraph:add_edge(G, FV, SV, []),
129 | {NewPredVars, Labels} = set_predicates_on_edge(Start, SV, PV, G),
130 | digraph:add_edge(G, E, FV, SV, Labels),
131 | NewPredVars;
132 |
133 | (Tup, PV) when is_tuple(Tup) ->
134 | L = tuple_to_list(Tup),
135 | SV = digraph:add_vertex(G, hd(L)),
136 |
137 | E = digraph:add_edge(G, FV, SV, []),
138 | {NewPredVars, Labels} = set_predicates_on_edge(Start, SV, PV, G),
139 | digraph:add_edge(G, E, FV, SV, Labels),
140 |
141 | {_, NewPredVars2} = pattern_to_graph(Start, NewPredVars, L, G),
142 | NewPredVars2
143 | end, PredVars, tuple_to_list(Second)),
144 | pattern_to_graph(Start, NewPV, [Second | T], G);
145 | pattern_to_graph(_Start, _PredVars, [First, Second | _], _) when is_list(First), is_list(Second) ->
146 | erlang:error(unsupported_pattern);
147 | pattern_to_graph(_Start, [], _, G) ->
148 | {G, []};
149 | pattern_to_graph(_Start, PredVars, [_ | []], _G) ->
150 | erlang:error({unsupported_pattern, "Cannot assign some of the predicates.", PredVars}).
151 |
152 |
153 | set_predicates_on_edge(Start, Second, PredVars, G) ->
154 | CurrentPath = ordsets:from_list(digraph:get_path(G, Start, Second)),
155 | {NewPredVars, Labels} = lists:foldl(fun({Vars, Predicate}, {PVSet, Acc}) ->
156 | case check_path(CurrentPath, Vars) of
157 | true -> {remove_predicate({Vars, Predicate}, PVSet),
158 | Acc ++ [Predicate]};
159 | false -> {PVSet, Acc}
160 | end
161 | end, {ordsets:from_list(PredVars), []}, PredVars),
162 | {ordsets:to_list(NewPredVars), Labels}.
163 |
164 |
165 | %% check if all the events in the current predicate (Vars) are part of the current path
166 | check_path(CurrentPath, Vars) ->
167 | lists:all(fun({Event, _}) -> ordsets:is_element(Event, CurrentPath) end, Vars).
168 |
169 | %% current predicate will be assigned to edge. remove it from the list ov available predicates
170 | remove_predicate(Key, PVSet) ->
171 | ordsets:del_element(Key, PVSet).
172 |
173 | get_predicates_on_edge(G, V1, V2) ->
174 | [Edge] = lists:filter(fun(E) ->
175 | {_E_tmp, _V1_tmp, V2_tmp, _} = digraph:edge(G, E),
176 | V2_tmp == V2
177 | end, digraph:out_edges(G, V1)),
178 | {_, _, _, Label} = digraph:edge(G, Edge),
179 | Label.
180 |
181 |
182 | get_join_keys(Predicate) ->
183 | {L, R, Dict} = get_join_keys(start, Predicate, {false, false, orddict:new()}),
184 | case {L, R} of
185 | {true, true} -> Set = lists:foldl(fun({Name, _}, Acc) -> sets:add_element(Name, Acc) end,
186 | sets:new(),
187 | orddict:to_list(Dict)),
188 | case length(sets:to_list(Set)) < 2 of
189 | true -> [];
190 | false -> Dict
191 | end;
192 | _ -> []
193 | end.
194 |
195 | get_join_keys(_Side, {_, Left, Right}, {L, R, Acc}) ->
196 | {L1, R1, NewAcc1} = get_join_keys(left, Left, {L, R, Acc}),
197 | {L2, R2, NewAcc2} = get_join_keys(right, Right, {L1, R1, Acc}),
198 | List = orddict:to_list(NewAcc1) ++ orddict:to_list(NewAcc2),
199 | NewAcc = lists:foldl(fun({Name, Val}, FAcc) ->
200 | case orddict:is_key(Name, FAcc) of
201 | true -> orddict:update(Name, fun(Old) ->
202 | sets:to_list(sets:union(sets:from_list(Val), sets:from_list(Old)))
203 | end, hd(Val), FAcc);
204 | false -> orddict:store(Name, lists:flatten(Val), FAcc)
205 | end
206 | end,
207 | orddict:new(),
208 | lists:flatten(List)
209 | ),
210 | {L2, R2, NewAcc};
211 | get_join_keys(Side, {neg, Predicate}, {L, R, Acc}) ->
212 | get_join_keys(Side, Predicate, {L, R, Acc});
213 | get_join_keys(_, {integer, _Value}, {L, R, Acc}) ->
214 | {L, R, Acc};
215 | get_join_keys(_, {float, _Value}, {L, R, Acc}) ->
216 | {L, R, Acc};
217 | get_join_keys(_, {string, _Value}, {L, R, Acc}) ->
218 | {L, R, Acc};
219 | get_join_keys(Side, {EventName, Value}, {L, R, Acc}) ->
220 | List = ordsets:to_list(ordsets:add_element({EventName, Value}, ordsets:from_list(orddict:to_list(Acc)))),
221 |
222 | NewAcc = lists:foldl(fun({Name, Val}, FAcc) ->
223 | case orddict:is_key(Name, FAcc) of
224 | true -> orddict:append(Name, Val, FAcc);
225 | false -> orddict:store(Name, [Val], FAcc)
226 | end
227 | end,
228 | orddict:new(),
229 | lists:flatten(List)
230 | ),
231 | case Side of
232 | left -> {true, R, NewAcc};
233 | right -> {L, true, NewAcc}
234 | end;
235 | get_join_keys(_, nil, _) ->
236 | {false, false, []}.
237 |
238 |
239 | %% {[{event1,eventparam1},
240 | %% {sum,{event2,eventparam2}},
241 | %% {minus,{plus,{plus,{plus,{event1,eventparam1},
242 | %% {mult,{event2,eventparam2},{integer,5}}},
243 | %% {integer,6}},
244 | %% {event2,eventparam4}},
245 | %% {event1,eventparam1}},
246 | %% {count,{event2,eventparam3}}]}
247 | is_aggregation_query(sum) ->
248 | true;
249 | is_aggregation_query(count) ->
250 | true;
251 | is_aggregation_query(min) ->
252 | tru;
253 | is_aggregation_query(max) ->
254 | true;
255 | is_aggregation_query({L, R}) ->
256 | is_aggregation_query(L) or is_aggregation_query(R);
257 | is_aggregation_query({Op, L, R}) ->
258 | is_aggregation_query(Op) or is_aggregation_query(L) or is_aggregation_query(R);
259 | is_aggregation_query([H | []]) ->
260 | is_aggregation_query(H);
261 | is_aggregation_query([H | L]) ->
262 | is_aggregation_query(H) or is_aggregation_query(L);
263 | is_aggregation_query(_) ->
264 | false.
265 |
266 |
267 |
268 | is_first(G, StateName) ->
269 | hd(digraph_utils:topsort(G)) == StateName.
270 |
271 | is_next(G, CurrentState, StateName) ->
272 | case digraph:get_path(G, CurrentState, StateName) of
273 | false -> false;
274 | _ -> true
275 | end.
276 |
277 | is_last(G, StateName) ->
278 | lists:last(digraph_utils:topsort(G)) == StateName.
279 |
280 |
281 | get_start_state(Pattern) when is_list(Pattern) andalso is_atom(hd(Pattern)) ->
282 | hd(Pattern);
283 | get_start_state(G) when is_tuple(G) ->
284 | case digraph_utils:topsort(G) of
285 | false -> erlang:error({badpattern, "Unsupported pattern."});
286 | L when is_list(L) -> hd(L)
287 | end;
288 | get_start_state(_) ->
289 | erlang:error({badpattern, "Unsupported pattern."}).
290 |
291 | get_events_on_path(G, EventName) ->
292 | digraph:get_path(G, get_start_state(G), EventName).
293 |
--------------------------------------------------------------------------------
/src/rivus_cep_query_worker.erl:
--------------------------------------------------------------------------------
1 | %%------------------------------------------------------------------------------
2 | %% Copyright (c) 2013-2014 Vasil Kolarov
3 | %%
4 | %% Licensed under the Apache License, Version 2.0 (the "License");
5 | %% you may not use this file except in compliance with the License.
6 | %% You may obtain a copy of the License at
7 | %%
8 | %% http://www.apache.org/licenses/LICENSE-2.0
9 | %%
10 | %% Unless required by applicable law or agreed to in writing, software
11 | %% distributed under the License is distributed on an "AS IS" BASIS,
12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | %% See the License for the specific language governing permissions and
14 | %% limitations under the License.
15 | %%------------------------------------------------------------------------------
16 |
17 | -module(rivus_cep_query_worker).
18 | -behaviour(gen_server).
19 | -compile([{parse_transform, lager_transform}]).
20 | -include_lib("stdlib/include/ms_transform.hrl").
21 | -include_lib("stdlib/include/qlc.hrl").
22 | -include("rivus_cep.hrl").
23 |
24 | -export([init/1, handle_cast/2, handle_call/3, handle_info/2, code_change/3, terminate/2]).
25 | -export([start_link/1, generate_result/1]).
26 |
27 |
28 | %%% API functions
29 |
30 | start_link(QueryDetails) ->
31 | {QueryName} = hd(QueryDetails#query_details.clauses),
32 | gen_server:start_link( {local, QueryName}, ?MODULE, [QueryDetails], []).
33 |
34 | init([QueryDetails]) ->
35 | {ok, State} = rivus_cep_query:init(QueryDetails),
36 | Timeout = State#query_state.query_ast#query_ast.within,
37 | WindowType = State#query_state.window_type,
38 | case WindowType of
39 | batch -> %%ClockPid = rivus_cep_clock_sup:start_clock_server(self(), Timeout),
40 | self() ! {start_clock_server, Timeout},
41 | {ok, State};
42 | _ -> {ok, State}
43 | end.
44 |
45 |
46 | generate_result(Pid) ->
47 | gen_server:cast(Pid, generate_result).
48 |
49 |
50 |
51 | %%----------------------------------------------------------------------------------------------
52 | %% gen_server functions
53 | %%----------------------------------------------------------------------------------------------
54 | handle_call(stop, _From, State) ->
55 | {stop, normal, ok, State};
56 | handle_call(_Request, _From, State) ->
57 | Reply = {ok, notsupported} ,
58 | {reply, Reply, State}.
59 |
60 | handle_cast(generate_result, #query_state{query_plan = QP} = State) when not QP#query_plan.fast_aggregations->
61 | lager:debug("Statement: ~p, handle_cast got event: generate_result. ~n",[State#query_state.query_name]),
62 | Result = rivus_cep_query:get_result_set(State),
63 | case Result of
64 | [] -> [];
65 | _ -> [gproc:send({p, l, {Subscriber, result_subscribers}}, Result) || Subscriber<-State#query_state.subscribers]
66 | end,
67 | {noreply, State};
68 | handle_cast(generate_result, #query_state{query_plan = QP} = State) when QP#query_plan.fast_aggregations->
69 | lager:debug("Statement: ~p, handle_cast got event: generate_result FASTAGGR. ~n",[State#query_state.query_name]),
70 | Result = rivus_cep_query:get_fast_aggr_result_set(State),
71 | case Result of
72 | [] -> [];
73 | _ -> [gproc:send({p, l, {Subscriber, result_subscribers}}, Result) || Subscriber<-State#query_state.subscribers]
74 | end,
75 | {noreply, State};
76 | handle_cast(_Msg, State) ->
77 | {noreply, State}.
78 |
79 | handle_info({start_clock_server, Timeout}, State) ->
80 | lager:debug("Statement: ~p, handle_info got event: ~p. Will do nothing ...",[ State#query_state.query_name ,start_clock_server]),
81 | ClockPid = rivus_cep_clock_sup:start_clock_server(self(), Timeout * 1000),
82 | {noreply, State#query_state{batch_clock_pid = ClockPid}};
83 | handle_info(Event, #query_state{query_type = QueryType, window_type = WindowType} = State) when WindowType == batch ->
84 | lager:debug("handle_info, query_type: ~p (batch), Event: ~p",[QueryType, Event]),
85 | rivus_cep_query:process_event(Event, State),
86 | {noreply, State};
87 | handle_info(Event, #query_state{query_type = QueryType} = State) ->
88 | lager:debug("handle_info, query_type: ~p, Event: ~p",[QueryType, Event]),
89 | Result = rivus_cep_query:process_event(Event, State),
90 | case Result of
91 | [] -> [];
92 | _ -> [gproc:send({p, l, {Subscriber, result_subscribers}}, Result) || Subscriber<-State#query_state.subscribers]
93 | end,
94 | {noreply, State};
95 | handle_info(Info, State) ->
96 | lager:debug("Statement: ~p, handle_info got event: ~p. Will do nothing ...",[ State#query_state.query_name ,Info]),
97 | {noreply, State}.
98 |
99 | terminate(_Reason, _State) ->
100 | lager:debug("Worker stopped. Reason: ~p~n",[_Reason]),
101 | ok.
102 |
103 | code_change(_OldVsn, _State, _Extra) ->
104 | {ok, _State}.
105 |
106 |
107 |
108 |
--------------------------------------------------------------------------------
/src/rivus_cep_query_worker_sup.erl:
--------------------------------------------------------------------------------
1 | %%------------------------------------------------------------------------------
2 | %% Copyright (c) 2013 Vasil Kolarov
3 | %%
4 | %% Licensed under the Apache License, Version 2.0 (the "License");
5 | %% you may not use this file except in compliance with the License.
6 | %% You may obtain a copy of the License at
7 | %%
8 | %% http://www.apache.org/licenses/LICENSE-2.0
9 | %%
10 | %% Unless required by applicable law or agreed to in writing, software
11 | %% distributed under the License is distributed on an "AS IS" BASIS,
12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | %% See the License for the specific language governing permissions and
14 | %% limitations under the License.
15 | %%------------------------------------------------------------------------------
16 |
17 | -module(rivus_cep_query_worker_sup).
18 | -behaviour(supervisor).
19 |
20 | -export([start_link/0, init/1]).
21 |
22 | -compile([{parse_transform, lager_transform}]).
23 |
24 | start_link() ->
25 | supervisor:start_link(?MODULE, []).
26 |
27 | init(Args) ->
28 | lager:debug("query_worker_sup, Args: ~p~n",[Args]),
29 | MaxRestart = 5,
30 | MaxTime = 3600,
31 | {ok, {{simple_one_for_one, MaxRestart, MaxTime},
32 | [{query_worker,
33 | {rivus_cep_query_worker, start_link, []},
34 | transient, brutal_kill, worker, [rivus_cep_query_worker]}]}}.
35 |
--------------------------------------------------------------------------------
/src/rivus_cep_result_eval.erl:
--------------------------------------------------------------------------------
1 | %%------------------------------------------------------------------------------
2 | %% Copyright (c) 2013 Vasil Kolarov
3 | %%
4 | %% Licensed under the Apache License, Version 2.0 (the "License");
5 | %% you may not use this file except in compliance with the License.
6 | %% You may obtain a copy of the License at
7 | %%
8 | %% http://www.apache.org/licenses/LICENSE-2.0
9 | %%
10 | %% Unless required by applicable law or agreed to in writing, software
11 | %% distributed under the License is distributed on an "AS IS" BASIS,
12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | %% See the License for the specific language governing permissions and
14 | %% limitations under the License.
15 | %%------------------------------------------------------------------------------
16 |
17 | -module(rivus_cep_result_eval).
18 |
19 | -export([eval_resultset/4, get_group_key/2, new_state/0, eval_resultset_fast_aggr/1]).
20 |
21 | -include_lib("eunit/include/eunit.hrl").
22 | -include("rivus_cep.hrl").
23 |
24 | eval_resultset_fast_aggr(QueryState) ->
25 | Window = QueryState#query_state.window,
26 | Pid = QueryState#query_state.window_pid,
27 | [[AggrState]] = rivus_cep_window:get_aggr_state(Pid, local, Window),
28 | [Value || {_, Value} <- maps:to_list(AggrState#res_eval_state.result)].
29 |
30 | eval_resultset(_, [], AggrState, #query_state{query_plan = QP}) when not QP#query_plan.fast_aggregations ->
31 | [Value || {_, Value} <- maps:to_list(AggrState#res_eval_state.result)];
32 | eval_resultset(_, [], AggrState, #query_state{query_plan = QP} = QueryState) when QP#query_plan.fast_aggregations ->
33 | Window = QueryState#query_state.window,
34 | Pid = QueryState#query_state.window_pid,
35 | rivus_cep_window:trim(Pid, Window),
36 | rivus_cep_window:update(Pid, Window, AggrState),
37 | [Value || {_, Value} <- maps:to_list(AggrState#res_eval_state.result)];
38 | eval_resultset(Stmt, [H | T], #res_eval_state{recno = RecNo, result = CurrentRes} = AggrState, QueryState) ->
39 | SelectClause = QueryState#query_state.query_ast#query_ast.select,
40 | Key = get_group_key(H, SelectClause),
41 | {ResultRecord, NewAggrState} = eval_result_record(Stmt, H, Key, SelectClause, AggrState#res_eval_state{aggrno = 1}),
42 | NewRes = maps:put(Key, ResultRecord, CurrentRes),
43 | %%?debugMsg(io_lib:format("----->NewRes: ~p~n",[NewRes])),
44 | eval_resultset(Stmt, T, NewAggrState#res_eval_state{recno = RecNo + 1, result = NewRes}, QueryState).
45 |
46 |
47 | eval_result_record(Stmt, ResultRecord, Key, SelectClause, State) when is_tuple(ResultRecord) ->
48 | eval_result_record(Stmt, [ResultRecord], Key, SelectClause, State);
49 | eval_result_record(Stmt, ResultRecord, Key, SelectClause, State) ->
50 | {ResNodes, NewState} = lists:mapfoldl(fun({EventName, EventParam}, Acc) when is_atom(EventName) andalso is_atom(EventParam) ->
51 | Event = lists:keyfind(EventName, 1, ResultRecord),
52 | ParamValue = (EventName):get_param_by_name(Event, EventParam),
53 | {ParamValue, Acc};
54 | ({Op, Left, Right}, Acc) ->
55 | eval_node(Stmt, {Op, Left, Right}, Key, ResultRecord, Acc); (Node, Acc) when is_tuple(Node) ->
56 | eval_node(Stmt, Node, Key, ResultRecord, Acc);
57 | (Node, Acc) -> {Node, Acc}
58 | end, State, SelectClause),
59 |
60 | {list_to_tuple(ResNodes), NewState}.
61 |
62 |
63 | eval_node(Stmt, Node, Key, ResultRecord, State) ->
64 | case Node of
65 | {EventName, EventParam} when is_atom(EventName) andalso is_atom(EventParam) ->
66 | Event = lists:keyfind(EventName, 1, ResultRecord),
67 | {(EventName):get_param_by_name(Event, EventParam), State};
68 | Value when not is_tuple(Value) andalso not is_list(Value) -> {Value, State};
69 | {Value} when not is_tuple(Value) andalso not is_list(Value) -> {Value, State};
70 | {Op, LeftNode, RightNode} -> {LeftValue, NewState1} = eval_node(Stmt, LeftNode, Key, ResultRecord, State),
71 | {RightValue, NewState2} = eval_node(Stmt, RightNode, Key, ResultRecord, NewState1),
72 | {eval_op({Op, LeftValue, RightValue}, ResultRecord), NewState2}; %%!!!!!
73 | {Aggr, Param} when is_tuple(Param) -> {Value, NewState} = eval_node(Stmt, Param, Key, ResultRecord, State),
74 | eval_aggregation(Aggr, Stmt, Value, Key, NewState)
75 | end.
76 |
77 | eval_aggregation(sum, Stmt, Value, Key, #res_eval_state{aggrno = AggrNo, aggr_nodes = AggrNodes} = State) ->
78 | AggKey = {Stmt, Key, AggrNo},
79 | NewValue = maps:get(AggKey, AggrNodes, 0) + Value,
80 | NewAggrNodes = maps:put(AggKey, NewValue, AggrNodes),
81 | {NewValue, State#res_eval_state{aggrno = AggrNo + 1, aggr_nodes = NewAggrNodes}};
82 | eval_aggregation(count, Stmt, _, Key, #res_eval_state{aggrno = AggrNo, aggr_nodes = Aggregations} = State) ->
83 | AggKey = {Stmt, Key, AggrNo},
84 | NewValue = maps:get(AggKey, Aggregations, 0) + 1,
85 | NewAggr = maps:put(AggKey, NewValue, Aggregations),
86 | {NewValue, State#res_eval_state{aggrno = AggrNo + 1, aggr_nodes = NewAggr}};
87 | eval_aggregation(min, Stmt, Value, Key, #res_eval_state{aggrno = AggrNo, aggr_nodes = AggrNodes} = State) ->
88 | AggKey = {Stmt, Key, AggrNo},
89 | Old = maps:get(AggKey, AggrNodes, Value),
90 | NewValue = case Old < Value of
91 | true -> Old;
92 | false -> Value
93 | end,
94 |
95 | NewAggrNodes = maps:put(AggKey, NewValue, AggrNodes),
96 | {NewValue, State#res_eval_state{aggrno = AggrNo + 1, aggr_nodes = NewAggrNodes}};
97 | eval_aggregation(max, Stmt, Value, Key, #res_eval_state{aggrno = AggrNo, aggr_nodes = AggrNodes} = State) ->
98 | AggKey = {Stmt, Key, AggrNo},
99 | Old = maps:get(AggKey, AggrNodes, Value),
100 | NewValue = case Old > Value of
101 | true -> Old;
102 | false -> Value
103 | end,
104 |
105 | NewAggrNodes = maps:put(AggKey, NewValue, AggrNodes),
106 | {NewValue, State#res_eval_state{aggrno = AggrNo + 1, aggr_nodes = NewAggrNodes}}.
107 |
108 | get_group_key(Event, SelectClause) when is_tuple(Event) ->
109 | Keys = lists:foldl(fun({EventName, EventParam}, Acc) when is_atom(EventName) andalso is_atom(EventParam) ->
110 | ParamValue = (EventName):get_param_by_name(Event, EventParam),
111 | Acc ++ [ParamValue];
112 | ({Op, Left, Right}, Acc) -> Acc ++ lists:flatten([eval_op({Op, Left, Right}, Event)]);
113 | (_, Acc) -> Acc
114 | end, [], SelectClause),
115 | list_to_tuple(Keys);
116 | get_group_key(ResultRecord, SelectClause) when is_list(ResultRecord) ->
117 | Keys = lists:foldl(fun({EventName, EventParam}, Acc) when is_atom(EventName) andalso is_atom(EventParam) ->
118 | Event = lists:keyfind(EventName, 1, ResultRecord),
119 | ParamValue = (EventName):get_param_by_name(Event, EventParam),
120 | Acc ++ [ParamValue];
121 | ({Op, Left, Right}, Acc) -> Acc ++ lists:flatten([eval_op({Op, Left, Right}, ResultRecord)]);
122 | (_, Acc) -> Acc
123 | end, [], SelectClause),
124 | list_to_tuple(Keys).
125 |
126 |
127 | eval_op({EventName, ParamName}, ResultRecord) when is_atom(EventName) andalso is_atom(ParamName) andalso is_list(ResultRecord) ->
128 | Event = lists:keyfind(EventName, 1, ResultRecord),
129 | EventName:get_param_by_name(Event, ParamName);
130 | eval_op({EventName, ParamName}, Event) when is_atom(EventName) andalso is_atom(ParamName) ->
131 | EventName:get_param_by_name(Event, ParamName);
132 | eval_op({integer, Value}, _) ->
133 | Value;
134 | eval_op({float, Value}, _) ->
135 | Value;
136 | eval_op({atom, Value}, _) ->
137 | Value;
138 | eval_op({_, T}, _) when is_tuple(T) -> %this is an aggregation operation
139 | [];
140 | eval_op({Op, Left, Right}, ResultRecord) when Left =/= [] andalso Right =/= [] ->
141 | L = eval_op(Left, ResultRecord),
142 | R = eval_op(Right, ResultRecord),
143 | case L =/= [] andalso R =/= [] of
144 | true -> case Op of
145 | plus -> L + R;
146 | minus -> L - R;
147 | mult -> L * R;
148 | 'div' -> L / R;
149 | _ -> undefined_op
150 | end;
151 | false -> []
152 | end;
153 | eval_op({_, _, _}, _) ->
154 | [blah].
155 |
156 | new_state() ->
157 | #res_eval_state{}.
158 |
--------------------------------------------------------------------------------
/src/rivus_cep_scanner.xrl:
--------------------------------------------------------------------------------
1 | %% ; -*- mode: Erlang;-*-
2 |
3 | Definitions.
4 | D = [0-9]
5 | H = [0-9a-fA-F]
6 | U = [A-Z]
7 | L = [a-z]
8 | A = ({U}|{L}|{D}|_)
9 | WS = ([\000-\s]|%.*)
10 |
11 | Rules.
12 |
13 | {D}+ : {token,{integer,TokenLine,list_to_integer(TokenChars)}}.
14 |
15 | {L}{A}* : Atom = list_to_atom(TokenChars),
16 | {token,case reserved_word(Atom) of
17 | true -> case Atom of
18 | 'end' -> {end_token,{'end',TokenLine}};
19 | _ -> {Atom, TokenLine}
20 | end;
21 | false -> {atom,TokenLine,Atom}
22 | end}.
23 |
24 | ({U}|_){A}* : {token,{var,TokenLine,list_to_atom(TokenChars)}}.
25 |
26 | "(\\\^.|\\.|[^"])*" :
27 | %% Strip quotes.
28 | S = lists:sublist(TokenChars, 2, TokenLen - 2),
29 | {token,{string,TokenLine,S}}.
30 | \'(\\.|\\\n|[^'\n])*\' :
31 | %% Strip quotes.
32 | S = lists:sublist(TokenChars, 2, TokenLen - 2),
33 | {token,{string,TokenLine,S}}.
34 |
35 | \$(\\{O}{O}{O}|\\\^.|\\.|.) :
36 | {token,{char,TokenLine,cc_convert(TokenChars)}}.
37 | \+ : {token,{'+',TokenLine}}.
38 | \- : {token,{'-',TokenLine}}.
39 | \* : {token,{'*',TokenLine}}.
40 | \/ : {token,{'/',TokenLine}}.
41 | \( : {token,{'(',TokenLine}}.
42 | \) : {token,{')',TokenLine}}.
43 | \= : {token,{'=',TokenLine}}.
44 | \< : {token,{'<',TokenLine}}.
45 | \> : {token,{'>',TokenLine}}.
46 | >= : {token,{'>=',TokenLine}}.
47 | =< : {token,{'<=',TokenLine}}.
48 | <> : {token,{'<>',TokenLine}}.
49 | -> : {token,{'->',TokenLine}}.
50 |
51 | []()[}{|!?/:,.*+#<>=-] :
52 | {token,{list_to_atom(TokenChars),TokenLine}}.
53 | \; : {end_token,{semicolon,TokenLine}}.
54 | {WS}+ : skip_token.
55 |
56 | Erlang code.
57 |
58 | -export([reserved_word/1]).
59 |
60 | %% reserved_word(Atom) -> Bool
61 | %% return 'true' if Atom is an Erlang reserved word, else 'false'.
62 |
63 | reserved_word('define') -> true;
64 | reserved_word('as') -> true;
65 | reserved_word('select') -> true;
66 | reserved_word('from') -> true;
67 | reserved_word('where') -> true;
68 | reserved_word('within') -> true;
69 | reserved_word('seconds') -> true;
70 | reserved_word('and') -> true;
71 | reserved_word('or') -> true;
72 | reserved_word('not') -> true;
73 | reserved_word('if') -> true;
74 | reserved_word('foreach') -> true;
75 | reserved_word('index') -> true;
76 | reserved_word('of') -> true;
77 | reserved_word('end') -> true;
78 | reserved_word('sum') -> true;
79 | reserved_word('count') -> true;
80 | reserved_word('avg') -> true;
81 | reserved_word('min') -> true;
82 | reserved_word('max') -> true;
83 | reserved_word('sliding') -> true;
84 | reserved_word('batch') -> true;
85 | reserved_word('tumbling') -> true;
86 | reserved_word(_) -> false.
87 |
88 | cc_convert([$$,$\\|Cs]) ->
89 | hd(string_escape(Cs));
90 | cc_convert([$$,C]) -> C.
91 |
92 | string_gen([$\\|Cs]) ->
93 | string_escape(Cs);
94 | string_gen([C|Cs]) ->
95 | [C|string_gen(Cs)];
96 | string_gen([]) -> [].
97 |
98 | string_escape([O1,O2,O3|S]) when
99 | O1 >= $0, O1 =< $7, O2 >= $0, O2 =< $7, O3 >= $0, O3 =< $7 ->
100 | [(O1*8 + O2)*8 + O3 - 73*$0|string_gen(S)];
101 | string_escape([$^,C|Cs]) ->
102 | [C band 31|string_gen(Cs)];
103 | string_escape([C|Cs]) when C >= $\000, C =< $\s ->
104 | string_gen(Cs);
105 | string_escape([C|Cs]) ->
106 | [escape_char(C)|string_gen(Cs)].
107 |
108 | escape_char($n) -> $\n; %\n = LF
109 | escape_char($r) -> $\r; %\r = CR
110 | escape_char($t) -> $\t; %\t = TAB
111 | escape_char($v) -> $\v; %\v = VT
112 | escape_char($b) -> $\b; %\b = BS
113 | escape_char($f) -> $\f; %\f = FF
114 | escape_char($e) -> $\e; %\e = ESC
115 | escape_char($s) -> $\s; %\s = SPC
116 | escape_char($d) -> $\d; %\d = DEL
117 | escape_char(C) -> C.
118 |
--------------------------------------------------------------------------------
/src/rivus_cep_server.erl:
--------------------------------------------------------------------------------
1 | %%------------------------------------------------------------------------------
2 | %% Copyright (c) 2014 Vasil Kolarov
3 | %%
4 | %% Licensed under the Apache License, Version 2.0 (the "License");
5 | %% you may not use this file except in compliance with the License.
6 | %% You may obtain a copy of the License at
7 | %%
8 | %% http://www.apache.org/licenses/LICENSE-2.0
9 | %%
10 | %% Unless required by applicable law or agreed to in writing, software
11 | %% distributed under the License is distributed on an "AS IS" BASIS,
12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | %% See the License for the specific language governing permissions and
14 | %% limitations under the License.
15 | %%------------------------------------------------------------------------------
16 | -module(rivus_cep_server).
17 | -behaviour(gen_fsm).
18 | -compile([{parse_transform, lager_transform}]).
19 | -include_lib("eunit/include/eunit.hrl").
20 |
21 | -export([start_link/0, set_socket/2]).
22 |
23 | %% gen_fsm callbacks
24 | -export([init/1, handle_event/3, handle_sync_event/4, handle_info/3,
25 | terminate/3, code_change/4]).
26 |
27 | %% FSM States
28 | -export([wait_for_socket/2, wait_for_data/2]).
29 |
30 | -record(state, {
31 | socket,
32 | peername,
33 | cep_work_sup,
34 | cep_work_pid
35 | }).
36 |
37 | -define(SERVER, ?MODULE).
38 | -define(TIMEOUT, 120000).
39 | -include_lib("eunit/include/eunit.hrl").
40 |
41 | start_link() ->
42 | gen_fsm:start_link(?MODULE, [], []).
43 |
44 | set_socket(Pid, Socket) ->
45 | gen_fsm:send_event(Pid, {socket_ready, Socket}).
46 |
47 | init([]) ->
48 | lager:debug("------> rivus_cep_server init() : ~p",[]),
49 | {ok, CepSup} = rivus_cep_app_srv:get_cep_sup(),
50 | {ok, wait_for_socket, #state{cep_work_sup = CepSup}}.
51 |
52 | wait_for_socket({socket_ready, Socket}, #state{cep_work_sup = CepSup} = State) when is_port(Socket) ->
53 | case inet:peername(Socket) of
54 | {ok, PeerInfo} ->
55 | case supervisor:start_child(CepSup, []) of
56 | {ok, Pid} -> inet:setopts(Socket, [{active, once}, {packet, 4}, binary]),
57 | lager:debug("------> Peer connected !"),
58 | {next_state, wait_for_data, State#state{socket = Socket, peername = PeerInfo, cep_work_pid = Pid}};
59 | Error -> {stop, Error, State}
60 | end;
61 | {error, Reason} ->
62 | lager:debug("Could not get peername: ~p", [Reason]),
63 | {stop, ok, State}
64 | end;
65 | wait_for_socket(Other, State) ->
66 | lager:info("State: wait_for_socket. Unexpected message: ~p\n", [Other]),
67 | {next_state, wait_for_socket, State}.
68 |
69 |
70 | %% Notification event coming from client
71 | wait_for_data({data, Packet}, State) ->
72 | process_packet(Packet, State),
73 | {next_state, wait_for_data, State, ?TIMEOUT};
74 | wait_for_data(timeout, State) ->
75 | lager:error("--->Client connection timeout - closing: ~p", [self()]),
76 | {stop, normal, State};
77 |
78 | wait_for_data(Data, State) ->
79 | lager:info("--->Ignoring data: ~p, ~p", [self(), Data]),
80 | {next_state, wait_for_data, State, ?TIMEOUT}.
81 |
82 | handle_event(Event, StateName, StateData) ->
83 | {stop, {StateName, undefined_event, Event}, StateData}.
84 | handle_sync_event(Event, _From, StateName, StateData) ->
85 | {stop, {StateName, undefined_event, Event}, StateData}.
86 |
87 |
88 | handle_info({tcp, Socket, Packet}, StateName, State) ->
89 | inet:setopts(Socket, [{active, once}]),
90 | ?MODULE:StateName({data, Packet}, State);
91 | handle_info({tcp_closed, Socket}, _StateName, State = #state{socket = Socket}) ->
92 | lager:debug("-----> Got tcp_closed!!!"),
93 | {stop, normal, State};
94 | handle_info({tcp_error, Socket, Reason}, StateName, State = #state{socket = Socket}) ->
95 | lager:debug("-----> Got tcp_eror. Reason: ~p!!!", [Reason]),
96 | {next_state, StateName, State}.
97 |
98 | terminate(_Reason, _StateName, _State) ->
99 | ok.
100 |
101 | code_change(_OldVsn, StateName, State, _Extra) ->
102 | {ok, StateName, State}.
103 |
104 | %%--------------------------------------------------------------------
105 | %% Internal functions
106 | %%--------------------------------------------------------------------
107 | process_packet(Packet, State) ->
108 | case decode_packet(Packet) of
109 | {event, Event} -> process_event(Event, State);
110 | {event, Provider, Event} -> process_event(Provider, Event, State);
111 | {load_query, Query} -> load_query(Query, State);
112 | P -> lager:error("Cannot decode packet ~p", [P])
113 | end.
114 |
115 | decode_packet(Packet) ->
116 | Term = binary_to_term(Packet),
117 | lager:debug("Got term: ~p", [Term]),
118 | Term.
119 |
120 | process_event(Event, #state{socket = Socket, cep_work_pid = Pid}) ->
121 | lager:debug("-----> Sending event to CEP: ~p", [Event]),
122 | rivus_cep:notify_sync(Pid, Event),
123 | send_resp(ok, Socket).
124 |
125 | process_event(Provider, Event, #state{socket = Socket, cep_work_pid = Pid}) ->
126 | lager:debug("-----> Sending event to CEP: ~p", [Event]),
127 | rivus_cep:notify_sync(Pid, Provider, Event),
128 | send_resp(ok, Socket).
129 |
130 | send_resp(Resp, Socket) ->
131 | ok = gen_tcp:send(Socket, term_to_binary(Resp)).
132 |
133 | load_query(Query, #state{socket = Socket, cep_work_pid = Pid}) ->
134 | {QueryStr, Providers, UpdateListners, Options} = Query,
135 |
136 | case rivus_cep:execute(Pid, QueryStr, Providers, UpdateListners, Options) of
137 | {ok, QueryPid, _} -> ok = gen_tcp:send(Socket, term_to_binary(QueryPid));
138 | ok -> send_resp(ok, Socket);
139 | _ -> lager:error("Cannot execute statement:: ~p", [Query]),
140 | send_resp({error, "Cannot execute statement:: ~p"}, Socket)
141 | end.
142 |
143 |
--------------------------------------------------------------------------------
/src/rivus_cep_server_sup.erl:
--------------------------------------------------------------------------------
1 | %%------------------------------------------------------------------------------
2 | %% Copyright (c) 2014 Vasil Kolarov
3 | %%
4 | %% Licensed under the Apache License, Version 2.0 (the "License");
5 | %% you may not use this file except in compliance with the License.
6 | %% You may obtain a copy of the License at
7 | %%
8 | %% http://www.apache.org/licenses/LICENSE-2.0
9 | %%
10 | %% Unless required by applicable law or agreed to in writing, software
11 | %% distributed under the License is distributed on an "AS IS" BASIS,
12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | %% See the License for the specific language governing permissions and
14 | %% limitations under the License.
15 | %%------------------------------------------------------------------------------
16 | -module(rivus_cep_server_sup).
17 | -behaviour(supervisor).
18 | -export([start_link/0, init/1, stop/1]).
19 | -export([start_socket/0]).
20 |
21 | -compile([{parse_transform, lager_transform}]).
22 |
23 | start_socket() ->
24 | supervisor:start_child(?MODULE, []).
25 |
26 | start_link() ->
27 | supervisor:start_link({local, ?MODULE}, ?MODULE, []).
28 |
29 | stop(_S) -> ok.
30 |
31 | init([]) ->
32 | lager:debug("-----> rivus_cep_server_sup, Args: ~p~n",[]),
33 | {ok,
34 | {{simple_one_for_one, 10, 10},
35 | [{rivus_cep_server,
36 | {rivus_cep_server, start_link, []},
37 | temporary, brutal_kill, worker, [rivus_cep_server]}]}}.
--------------------------------------------------------------------------------
/src/rivus_cep_sup.erl:
--------------------------------------------------------------------------------
1 | %%------------------------------------------------------------------------------
2 | %% Copyright (c) 2013 Vasil Kolarov
3 | %%
4 | %% Licensed under the Apache License, Version 2.0 (the "License");
5 | %% you may not use this file except in compliance with the License.
6 | %% You may obtain a copy of the License at
7 | %%
8 | %% http://www.apache.org/licenses/LICENSE-2.0
9 | %%
10 | %% Unless required by applicable law or agreed to in writing, software
11 | %% distributed under the License is distributed on an "AS IS" BASIS,
12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | %% See the License for the specific language governing permissions and
14 | %% limitations under the License.
15 | %%------------------------------------------------------------------------------
16 |
17 | -module(rivus_cep_sup).
18 | -behaviour(supervisor).
19 | -compile([{parse_transform, lager_transform}]).
20 |
21 | -export([start_link/0]).
22 |
23 | -export([init/1]).
24 |
25 | %%
26 | %% start_link() ->
27 | %% supervisor:start_link({local, ?MODULE}, ?MODULE, []).
28 |
29 | start_link() ->
30 | supervisor:start_link(?MODULE, []).
31 |
32 | init(Args) ->
33 | lager:debug("rivus_cep_sup, Args: ~p~n",[Args]),
34 | CepServ = { rivus_cep,
35 | {rivus_cep, start_link, []},
36 | transient, brutal_kill, worker, [rivus_cep]},
37 |
38 | { ok,
39 | { {simple_one_for_one, 5, 3600},
40 | [CepServ]}}.
41 |
42 |
43 |
--------------------------------------------------------------------------------
/src/rivus_cep_tcp_listener.erl:
--------------------------------------------------------------------------------
1 | %%------------------------------------------------------------------------------
2 | %% Copyright (c) 2014 Vasil Kolarov
3 | %%
4 | %% Licensed under the Apache License, Version 2.0 (the "License");
5 | %% you may not use this file except in compliance with the License.
6 | %% You may obtain a copy of the License at
7 | %%
8 | %% http://www.apache.org/licenses/LICENSE-2.0
9 | %%
10 | %% Unless required by applicable law or agreed to in writing, software
11 | %% distributed under the License is distributed on an "AS IS" BASIS,
12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | %% See the License for the specific language governing permissions and
14 | %% limitations under the License.
15 | %%------------------------------------------------------------------------------
16 | -module(rivus_cep_tcp_listener).
17 | -behaviour(gen_nb_server).
18 | -compile([{parse_transform, lager_transform}]).
19 | -export([start_link/2]).
20 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
21 | terminate/2, code_change/3]).
22 | -export([sock_opts/0, new_connection/2]).
23 |
24 | -record(state, {portnum}).
25 |
26 | start_link(IpAddr, PortNum) ->
27 | gen_nb_server:start_link(?MODULE, IpAddr, PortNum, [PortNum]).
28 |
29 | init([PortNum]) ->
30 | {ok, #state{portnum=PortNum}}.
31 |
32 | sock_opts() ->
33 | [binary, {packet, 4}, {reuseaddr, true}, {keepalive, true}].
34 |
35 | handle_call(_Req, _From, State) ->
36 | {reply, not_implemented, State}.
37 |
38 | handle_cast(_Msg, State) ->
39 | {noreply, State}.
40 |
41 | handle_info(_Info, State) ->
42 | {noreply, State}.
43 |
44 | terminate(_Reason, _State) ->
45 | ok.
46 |
47 | code_change(_OldVsn, State, _Extra) -> {ok, State}.
48 |
49 | new_connection(Socket, State) ->
50 | lager:debug("----------> New Connection"),
51 | {ok, Pid} = rivus_cep_server_sup:start_socket(),
52 | ok = gen_tcp:controlling_process(Socket, Pid),
53 | ok = rivus_cep_server:set_socket(Pid, Socket),
54 | {ok, State}.
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/src/rivus_cep_tcp_listener_sup.erl:
--------------------------------------------------------------------------------
1 | %%------------------------------------------------------------------------------
2 | %% Copyright (c) 2014 Vasil Kolarov
3 | %%
4 | %% Licensed under the Apache License, Version 2.0 (the "License");
5 | %% you may not use this file except in compliance with the License.
6 | %% You may obtain a copy of the License at
7 | %%
8 | %% http://www.apache.org/licenses/LICENSE-2.0
9 | %%
10 | %% Unless required by applicable law or agreed to in writing, software
11 | %% distributed under the License is distributed on an "AS IS" BASIS,
12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | %% See the License for the specific language governing permissions and
14 | %% limitations under the License.
15 | %%------------------------------------------------------------------------------
16 | -module(rivus_cep_tcp_listener_sup).
17 |
18 | -behaviour(supervisor).
19 |
20 | %% API
21 | -export([start_link/2]).
22 |
23 | %% Supervisor callbacks
24 | -export([init/1]).
25 |
26 | -define(SERVER, ?MODULE).
27 |
28 | %%%===================================================================
29 | %%% API functions
30 | %%%===================================================================
31 |
32 | %%--------------------------------------------------------------------
33 | %% @doc
34 | %% Starts the supervisor
35 | %%
36 | %% @end
37 | %%--------------------------------------------------------------------
38 | start_link(Host, Port) ->
39 | supervisor:start_link({local, ?SERVER}, ?MODULE, [Host, Port]).
40 |
41 | %%%===================================================================
42 | %%% Supervisor callbacks
43 | %%%===================================================================
44 |
45 | %%--------------------------------------------------------------------
46 | %% @private
47 | %% @doc
48 | %% Whenever a supervisor is started using supervisor:start_link/[2,3],
49 | %% this function is called by the new process to find out about
50 | %% restart strategy, maximum restart frequency and child
51 | %% specifications.
52 | %%
53 | %% @end
54 | %%--------------------------------------------------------------------
55 | -spec(init(Args :: term()) ->
56 | {ok, {SupFlags :: {RestartStrategy :: supervisor:strategy(),
57 | MaxR :: non_neg_integer(), MaxT :: non_neg_integer()},
58 | [ChildSpec :: supervisor:child_spec()]
59 | }} |
60 | ignore |
61 | {error, Reason :: term()}).
62 | init([Host, Port]) ->
63 | RestartStrategy = one_for_one,
64 | MaxRestarts = 10,
65 | MaxSecondsBetweenRestarts = 10,
66 |
67 | SupFlags = {RestartStrategy, MaxRestarts, MaxSecondsBetweenRestarts},
68 |
69 | Restart = permanent,
70 | Shutdown = 5000,
71 | Type = worker,
72 |
73 | AChild = {rivus_cep_tcp_listener, {rivus_cep_tcp_listener, start_link, [Host, Port]},
74 | Restart, Shutdown, Type, [rivus_cep_tcp_listener]},
75 |
76 | {ok, {SupFlags, [AChild]}}.
77 |
78 | %%%===================================================================
79 | %%% Internal functions
80 | %%%===================================================================
81 |
--------------------------------------------------------------------------------
/src/rivus_cep_utils.erl:
--------------------------------------------------------------------------------
1 | -module(rivus_cep_utils).
2 |
3 | -export([timestamp/0]).
4 |
5 | timestamp() ->
6 | folsom_utils:now_epoch().
7 |
--------------------------------------------------------------------------------
/src/rivus_cep_window.erl:
--------------------------------------------------------------------------------
1 | %%------------------------------------------------------------------------------
2 | %% Copyright (c) 2013 Vasil Kolarov
3 | %%
4 | %% Licensed under the Apache License, Version 2.0 (the "License");
5 | %% you may not use this file except in compliance with the License.
6 | %% You may obtain a copy of the License at
7 | %%
8 | %% http://www.apache.org/licenses/LICENSE-2.0
9 | %%
10 | %% Unless required by applicable law or agreed to in writing, software
11 | %% distributed under the License is distributed on an "AS IS" BASIS,
12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | %% See the License for the specific language governing permissions and
14 | %% limitations under the License.
15 | %%------------------------------------------------------------------------------
16 |
17 | -module(rivus_cep_window).
18 | -behaviour(gen_server).
19 |
20 | -compile([{parse_transform, lager_transform}]).
21 |
22 | -include_lib("stdlib/include/ms_transform.hrl").
23 | -include_lib("stdlib/include/qlc.hrl").
24 | -include("rivus_cep.hrl").
25 |
26 | -export([new/1,
27 | new/2,
28 | new/3,
29 | update/2,
30 | update/3,
31 | resize/2,
32 | resize/3,
33 | trim/1,
34 | trim/2,
35 | get_values/1,
36 | get_values/2,
37 | get_window/1,
38 | get_window/2,
39 | update_fsm/4,
40 | delete_fsm/3,
41 | get_fsms/2,
42 | start_link/1,
43 | start_link/2,
44 | get_pre_result/3,
45 | get_pre_result/4,
46 | get_aggr_state/3]).
47 |
48 | -record(state,{provider, window, size, mod_details}).
49 |
50 | -define(SERVER, ?MODULE).
51 |
52 |
53 | %% gen_server API
54 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
55 | terminate/2, code_change/3]).
56 |
57 | start_link(WinModule) ->
58 | gen_server:start_link(?MODULE, [WinModule], []).
59 |
60 | start_link(WinModule, Args) when is_atom(Args) andalso Args == global-> %%start server for global windows
61 | gen_server:start_link({local, ?SERVER}, ?MODULE, [WinModule], []);
62 | start_link(WinModule, Args) when is_list(Args) ->
63 | gen_server:start_link(?MODULE, [WinModule, Args], []).
64 |
65 |
66 |
67 | init([WinModule]) ->
68 | lager:info("--- rivus_cep: Window server started. Provider:~p~n",[WinModule]),
69 | {ok, MD} = WinModule:initialize([]),
70 | {ok, #state{provider = WinModule, mod_details = MD}};
71 | init([WinModule, Args]) ->
72 | lager:info("--- rivus_cep: Window server started. Provider:~p, Args:~p~n",[WinModule,Args]),
73 | {ok, MD} = WinModule:initialize(Args),
74 | {ok, #state{provider = WinModule, mod_details = MD}}.
75 |
76 | new(Size) ->
77 | gen_server:call(?SERVER, {new, Size}).
78 |
79 | new(slide, Size) ->
80 | gen_server:call(?SERVER, {new, slide, Size});
81 | new(Pid, Size) when Pid /= slide->
82 | gen_server:call(Pid, {new, Size}).
83 |
84 | new(Pid, slide, Size) ->
85 | gen_server:call(Pid, {new, slide, Size}).
86 |
87 | update(Window, Value) ->
88 | gen_server:call(?SERVER, {update, Window, Value}).
89 |
90 | update(Pid, Window, Value) ->
91 | gen_server:call(Pid, {update, Window, Value}).
92 |
93 | trim(Window)->
94 | gen_server:call(?SERVER, {trim, Window}).
95 | trim(Pid, Window)->
96 | gen_server:call(Pid, {trim, Window}).
97 |
98 | get_values(Window) ->
99 | gen_server:call(?SERVER, {get_value, Window}).
100 |
101 | get_values(Pid, Window) ->
102 | gen_server:call(Pid, {get_value, Window}).
103 |
104 | resize(Window, NewSize) ->
105 | gen_server:call(?SERVER, {resize, Window, NewSize}).
106 |
107 | resize(Pid, Window, NewSize) ->
108 | gen_server:call(Pid, {resize, Window, NewSize}).
109 |
110 | get_window(Window) ->
111 | gen_server:call(?SERVER, {get_window, Window}).
112 |
113 | get_window(Pid, Window) ->
114 | gen_server:call(Pid, {get_window, Window}).
115 |
116 | update_fsm(Pid, Window, Key, Value) ->
117 | gen_server:call(Pid, {update_fsm, Window, Key, Value}).
118 |
119 | get_fsms(Pid, Window) ->
120 | gen_server:call(Pid, {get_fsm, Window}).
121 |
122 | delete_fsm(Pid, Window, Key) ->
123 | gen_server:call(Pid, {delete_fsm, Window, Key}).
124 |
125 | get_pre_result(Pid, local, Window, Events) ->
126 | gen_server:call(Pid, {get_result, local, Window, Events}).
127 |
128 | get_pre_result(global, WinReg, Events) ->
129 | gen_server:call(?SERVER, {get_result, global, WinReg, Events}).
130 |
131 | get_aggr_state(Pid, local, Window) ->
132 | gen_server:call(Pid, {get_aggr_state, local, Window}).
133 |
134 |
135 | handle_cast(_Msg, State) ->
136 | {noreply, State}.
137 |
138 | handle_call({new, Size}, _From, #state{provider=Mod, mod_details = MD} = State) ->
139 | Res = Mod:new(Size, MD),
140 | {reply, Res, State#state{window=Res, size=Size}};
141 | handle_call({new, slide, Size}, _From, #state{provider=Mod, mod_details = MD} = State) ->
142 | Res = Mod:new(Size, MD),
143 | {reply, Res, State#state{window=Res, size=Size, mod_details = MD}, timeout(Size)};
144 | handle_call({update, Window, Value}, _From, #state{provider=Mod, mod_details = MD} = State) ->
145 | lager:debug("~nUpdate window:~p, Value: ~p~n",[Window, Value]),
146 | Res = Mod:update(Window, Value, MD),
147 | {reply, Res, State};
148 | handle_call({get_value, Window}, _From, #state{provider=Mod, mod_details = MD} = State) ->
149 | Res = Mod:get_values(Window, MD),
150 | {reply, Res, State};
151 | handle_call({resize, Window, NewSize}, _From, #state{provider=Mod, mod_details = MD} = State) ->
152 | Res = Mod:resize(Window, NewSize, MD),
153 | {reply, Res, State#state{window=Res, size=NewSize},timeout(NewSize)};
154 | handle_call({get_window, Window}, _From, #state{provider=Mod, mod_details = MD} = State) ->
155 | Res = Mod:get_window(Window, MD),
156 | {reply, Res, State};
157 | handle_call({update_fsm, Window, Key, Value}, _From, #state{provider=Mod, mod_details = MD} = State) ->
158 | Res = Mod:update_fsm(Window, Key, Value, MD),
159 | {reply, Res, State};
160 | handle_call({get_fsm, Window}, _From, #state{provider=Mod, mod_details = MD} = State) ->
161 | Res = Mod:get_fsms(Window, MD),
162 | {reply, Res, State};
163 | handle_call({delete_fsm, Window, Key}, _From, #state{provider=Mod, mod_details = MD} = State) ->
164 | Res = Mod:delete_fsm(Window, Key, MD),
165 | {reply, Res, State};
166 | handle_call({get_result, local, Window, Events}, _From, #state{provider=Mod, mod_details = MD} = State) ->
167 | Res = Mod:get_result(local, Window, Events, MD),
168 | {reply, Res, State};
169 | handle_call({get_result, global, WinReg, Events}, _From, #state{provider=Mod, mod_details = MD} = State) ->
170 | Res = Mod:get_result(global, WinReg, Events, MD),
171 | {reply, Res, State};
172 | handle_call({get_aggr_state, local, Window}, _From, #state{provider=Mod, mod_details = MD} = State) ->
173 | Res = Mod:get_aggr_state(local, Window, MD),
174 | {reply, Res, State};
175 | handle_call({trim, Window}, _From, #state{provider=Mod, mod_details = MD} = State) ->
176 | Res = Mod:trim(Window, MD),
177 | {reply, Res, State}.
178 |
179 | handle_info(timeout, State=#state{window = Window, provider=Mod, mod_details = MD, size=Size}) ->
180 | Mod:trim(Window, MD),
181 | {noreply, State, timeout(Size)};
182 | handle_info(_Info, State) ->
183 | {noreply, State}.
184 |
185 | terminate(_Reason, _State) ->
186 | ok.
187 |
188 | code_change(_OldVsn, State, _Extra) ->
189 | {ok, State}.
190 |
191 | timeout(Window) ->
192 | timer:seconds(Window) div 2.
193 |
--------------------------------------------------------------------------------
/src/rivus_cep_window_ets.erl:
--------------------------------------------------------------------------------
1 | %%------------------------------------------------------------------------------
2 | %% Copyright (c) 2013-2014 Vasil Kolarov
3 | %%
4 | %% Licensed under the Apache License, Version 2.0 (the "License");
5 | %% you may not use this file except in compliance with the License.
6 | %% You may obtain a copy of the License at
7 | %%
8 | %% http://www.apache.org/licenses/LICENSE-2.0
9 | %%
10 | %% Unless required by applicable law or agreed to in writing, software
11 | %% distributed under the License is distributed on an "AS IS" BASIS,
12 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | %% See the License for the specific language governing permissions and
14 | %% limitations under the License.
15 | %%------------------------------------------------------------------------------
16 |
17 | -module(rivus_cep_window_ets).
18 |
19 | -compile([{parse_transform, lager_transform}]).
20 |
21 | -export([new/2,
22 | resize/3,
23 | trim/1,
24 | trim/2,
25 | update/3,
26 | get_values/2,
27 | get_fsms/2,
28 | update_fsm/4,
29 | delete_fsm/3,
30 | get_window/2,
31 | get_result/4,
32 | get_aggr_state/3,
33 | initialize/1]).
34 |
35 | -include("rivus_cep.hrl").
36 | -include_lib("folsom/include/folsom.hrl").
37 | -include_lib("stdlib/include/ms_transform.hrl").
38 | -include_lib("stdlib/include/qlc.hrl").
39 |
40 | -define(WIDTH, 16).
41 |
42 | initialize([]) ->
43 | {ok, []}.
44 |
45 | new(Size, _MD) ->
46 | folsom_sample_slide:new(Size).
47 |
48 | resize(Window, NewSize, _MD) ->
49 | folsom_sample_slide:resize(Window, NewSize).
50 |
51 | get_window(Window, _MD) ->
52 | Size = Window#slide.window,
53 | Reservoir = Window#slide.reservoir,
54 | Oldest = rivus_cep_utils:timestamp() - Size,
55 | {Reservoir, Oldest}.
56 |
57 | update(Window, Value, _MD) ->
58 | folsom_sample_slide:update(Window, Value).
59 |
60 | update_fsm(#slide{reservoir = Reservoir} = Window, Key, Value, _MD) ->
61 | ets:update_element(Reservoir, Key, {1, Value}),
62 | Window.
63 |
64 | get_values(Window, _MD) ->
65 | folsom_sample_slide:get_values(Window).
66 |
67 | get_fsms(#slide{reservoir = Reservoir, window = Size}, _MD) ->
68 | Oldest = rivus_cep_utils:timestamp() - Size,
69 | ets:select(Reservoir, [{{{'$1','$2'},'$3'},[{'>=', '$1', Oldest}],['$_']}]).
70 |
71 | delete_fsm(#slide{reservoir = Reservoir}, {Ts,Rnd}, _MD) ->
72 | ets:select_delete(Reservoir, [{{{'$1','$2'},'$3'},[{'andalso',{'==', '$1', Ts}, {'==', '$2',Rnd}}],['true']}]).
73 |
74 | get_result(local, Window, Events, _MD) ->
75 | {Reservoir, Oldest} = get_window(Window, []),
76 | MatchSpecs = [create_match_spec(Event, Oldest) || Event<- Events],
77 | QueryHandlers = [create_qh(MS, Reservoir) || MS <- MatchSpecs],
78 | [qlc:e(QH) || QH <- QueryHandlers ];
79 | get_result(global, WinReg, Events, _MD) ->
80 | QueryHandlers = lists:map(fun(Event) -> create_qh_shared_window(Event, WinReg) end, Events),
81 | [qlc:e(QH) || QH <- QueryHandlers ].
82 |
83 | get_aggr_state(local, Window, _MD) ->
84 | {Reservoir, Oldest} = get_window(Window, []),
85 | MatchSpecs = [ets:fun2ms(fun({ {Time,'_'},Value}) when Time >= Oldest -> Value end)],
86 | QueryHandlers = [create_qh(MS, Reservoir) || MS <- MatchSpecs],
87 | [qlc:e(QH) || QH <- QueryHandlers ].
88 |
89 | trim(Window, _MD) ->
90 | Reservoir = Window#slide.reservoir,
91 | ets:delete_all_objects(Reservoir).
92 |
93 | trim(Window) ->
94 | Size = Window#slide.window,
95 | Reservoir = Window#slide.reservoir,
96 | Oldest = rivus_cep_utils:timestamp() - Size,
97 | ets:select_delete(Reservoir, [{{{'$1','_'},'_'},[{'<', '$1', Oldest}],['true']}]).
98 |
99 | create_qh_shared_window(Event, WinReg) ->
100 | Window = dict:fetch(Event, WinReg),
101 | {Reservoir, Oldest} = get_window(Window, []),
102 | MatchSpec = create_match_spec(Event, Oldest),
103 | create_qh(MatchSpec, Reservoir).
104 |
105 | create_match_spec(Event, Oldest) ->
106 | ets:fun2ms(fun({ {Time,'_'},Value}) when Time >= Oldest andalso element(1,Value)==Event -> Value end).
107 |
108 | create_qh(MatchSpec, Reservoir) ->
109 | ets:table(Reservoir, [{traverse, {select, MatchSpec}}]).
110 |
--------------------------------------------------------------------------------
/test/event1.erl:
--------------------------------------------------------------------------------
1 | -module(event1).
2 | -behaviour(event_behaviour).
3 | -export([get_param_by_name/2, get_param_names/0]).
4 |
5 | get_param_by_name(Event, ParamName) ->
6 | case ParamName of
7 | name -> element(1, Event);
8 | eventparam1 -> element(2, Event);
9 | eventparam2 -> element(3, Event);
10 | eventparam3 -> element(4, Event);
11 | eventparam4 -> element(5, Event)
12 | end.
13 |
14 | get_param_names() ->
15 | [eventparam1, eventparam2, eventparam3, eventparam4].
16 |
--------------------------------------------------------------------------------
/test/event2.erl:
--------------------------------------------------------------------------------
1 | -module(event2).
2 | -behaviour(event_behaviour).
3 | -export([get_param_by_name/2, get_param_names/0]).
4 |
5 | get_param_by_name(Event, ParamName) ->
6 | case ParamName of
7 | name -> element(1, Event);
8 | eventparam1 -> element(2, Event);
9 | eventparam2 -> element(3, Event);
10 | eventparam3 -> element(4, Event);
11 | eventparam4 -> element(5, Event)
12 | end.
13 |
14 | get_param_names() ->
15 | [eventparam1, eventparam2, eventparam3, eventparam4].
16 |
--------------------------------------------------------------------------------
/test/event3.erl:
--------------------------------------------------------------------------------
1 | -module(event3).
2 | -behaviour(event_behaviour).
3 | -export([get_param_by_name/2, get_param_names/0]).
4 |
5 | get_param_by_name(Event, ParamName) ->
6 | case ParamName of
7 | name -> element(1, Event);
8 | eventparam1 -> element(2, Event);
9 | eventparam2 -> element(3, Event);
10 | eventparam3 -> element(4, Event);
11 | eventparam4 -> element(5, Event)
12 | end.
13 |
14 | get_param_names() ->
15 | [eventparam1, eventparam2, eventparam3, eventparam4].
--------------------------------------------------------------------------------
/test/event4.erl:
--------------------------------------------------------------------------------
1 | -module(event4).
2 | -behaviour(event_behaviour).
3 | -export([get_param_by_name/2, get_param_names/0]).
4 |
5 | get_param_by_name(Event, ParamName) ->
6 | case ParamName of
7 | name -> element(1, Event);
8 | attr1 -> element(2, Event);
9 | attr2 -> element(3, Event);
10 | qttr3 -> element(4, Event);
11 | attr4 -> element(5, Event)
12 | end.
13 |
14 | get_param_names() ->
15 | [attr1, attr2, attr3, attr4].
--------------------------------------------------------------------------------
/test/event5.erl:
--------------------------------------------------------------------------------
1 | -module(event5).
2 | -behaviour(event_behaviour).
3 | -export([get_param_by_name/2, get_param_names/0]).
4 |
5 | get_param_by_name(Event, ParamName) ->
6 | case ParamName of
7 | name -> element(1, Event);
8 | attr11 -> element(2, Event);
9 | attr12 -> element(3, Event);
10 | qttr13 -> element(4, Event);
11 | attr4 -> element(5, Event)
12 | end.
13 |
14 | get_param_names() ->
15 | [attr11, attr12, attr13, attr4].
--------------------------------------------------------------------------------
/test/rivus_cep_event_creator_tests.erl:
--------------------------------------------------------------------------------
1 | -module(rivus_cep_event_creator_tests).
2 |
3 | -include_lib("eunit/include/eunit.hrl").
4 |
5 |
6 |
7 | create_event_1_test() ->
8 | {ok, Tokens, _Endline} = rivus_cep_scanner:string("define event10 as (attr1, attr2, attr3);"),
9 | {ok, ParseRes} = rivus_cep_parser:parse(Tokens),
10 | ?assertEqual({event, {event10, [attr1, attr2, attr3]}}, ParseRes),
11 | {event, EventDef} = ParseRes,
12 | ?assertEqual({module, event10}, rivus_cep_event_creator:load_event_mod(EventDef)),
13 | Event = {event10, a, b, c},
14 | ?assertEqual(event10, event10:get_param_by_name(Event, name)),
15 | ?assertEqual(a, event10:get_param_by_name(Event, attr1)),
16 | ?assertEqual(b, event10:get_param_by_name(Event, attr2)),
17 | ?assertEqual(c, event10:get_param_by_name(Event, attr3)),
18 | ?assertError({case_clause,attr4}, event10:get_param_by_name(Event, attr4)),
19 | ?assertEqual([attr1, attr2, attr3], event10:get_param_names()).
20 |
21 |
--------------------------------------------------------------------------------
/test/rivus_cep_parser_tests.erl:
--------------------------------------------------------------------------------
1 | -module(rivus_cep_parser_tests).
2 |
3 | -compile([debug_info, export_all]).
4 | -compile([{parse_transform, lager_transform}]).
5 |
6 | -include_lib("eunit/include/eunit.hrl").
7 | -include_lib("stdlib/include/ms_transform.hrl").
8 | -include_lib("stdlib/include/qlc.hrl").
9 |
10 | -record(event, {id,
11 | name,
12 | param1,
13 | param2,
14 | ts}).
15 |
16 | parse_query_1_test() ->
17 | {ok, Tokens, _Endline} = rivus_cep_scanner:string("define correlation1 as
18 | select eventparam1
19 | from event1; ", 1),
20 | ?assertEqual({ok, [{correlation1},
21 | {[{event1, eventparam1}]}, {[event1]}, {nil}, {nil}, {[]}]},
22 | rivus_cep_parser:parse(Tokens)).
23 |
24 | parse_query_2_test() ->
25 | {ok, Tokens, _Endline} = rivus_cep_scanner:string("define correlation1 as
26 | select eventparam1, eventparam2
27 | from event1, event2; ", 1),
28 |
29 | ?assertError({badmatch,ubiquitous_event_param}, rivus_cep_parser:parse(Tokens)).
30 |
31 | parse_query_3_test() ->
32 | {ok, Tokens, _Endline} = rivus_cep_scanner:string("define correlation1 as
33 | select eventparam1
34 | from event1
35 | where eventparam1 = 20
36 | within 60 seconds; ", 1),
37 |
38 | ?assertEqual({ok, [{correlation1},
39 | {[{event1, eventparam1}]},
40 | {[event1]},
41 | {{eq, {event1, eventparam1}, {integer, 20}}},
42 | {60, sliding}, {[]}]},
43 | rivus_cep_parser:parse(Tokens)).
44 |
45 |
46 |
47 | parse_query_4_test() ->
48 | {ok, Tokens, _Endline} = rivus_cep_scanner:string("define correlation1 as select ev1.eventparam1, ev2.eventparam2, ev2.eventparam3, ev1.eventparam2
49 | from event1 as ev1, event2 as ev2
50 | where ev1.eventparam1 = ev2.eventparam2 and ev1.eventparam1 > ev2.eventparam2
51 | within 60 seconds; ", 1),
52 | ?assertEqual({ok, [{correlation1},
53 | {[{event1, eventparam1},
54 | {event2, eventparam2},
55 | {event2, eventparam3},
56 | {event1, eventparam2}]},
57 | {[event1, event2]},
58 | {{'and', {eq, {event1, eventparam1}, {event2, eventparam2}},
59 | {gt, {event1, eventparam1}, {event2, eventparam2}}}},
60 | {60, sliding}, {[]}]},
61 | rivus_cep_parser:parse(Tokens)).
62 |
63 | parse_query_5_test() ->
64 | {ok, Tokens, _Endline} = rivus_cep_scanner:string(
65 | "define correlation1 as
66 | select ev1.eventparam1, ev2.eventparam2,
67 | ((ev1.eventparam1 + ev2.eventparam2 * 5 + 6) + ev2.eventparam4) - ev1.eventparam1, ev2.eventparam3
68 | from event1 as ev1, event2 as ev2
69 | where ( ev1.eventparam1 * ev2.eventparam2 + 4 > ev2.eventparam4)
70 | or ev1.eventparam1 = ev2.eventparam2
71 | and ev1.eventparam1 > ev2.eventparam2
72 | within 60 seconds; ", 1),
73 |
74 | ?assertEqual({ok, [{correlation1},
75 | {[{event1, eventparam1},
76 | {event2, eventparam2},
77 | {minus, {plus, {plus, {plus, {event1, eventparam1},
78 | {mult, {event2, eventparam2}, {integer, 5}}},
79 | {integer, 6}},
80 | {event2, eventparam4}},
81 | {event1, eventparam1}},
82 | {event2, eventparam3}]},
83 | {[event1, event2]},
84 | {{'or', {gt, {plus, {mult, {event1, eventparam1}, {event2, eventparam2}}, {integer, 4}},
85 | {event2, eventparam4}},
86 | {'and', {eq, {event1, eventparam1}, {event2, eventparam2}},
87 | {gt, {event1, eventparam1}, {event2, eventparam2}}}}},
88 | {60, sliding}, {[]}]},
89 | rivus_cep_parser:parse(Tokens)).
90 | parse_query_6_test() ->
91 | {ok, Tokens, _Endline} = rivus_cep_scanner:string(
92 | "define correlation1 as
93 | select ev1.eventparam1, sum(ev2.eventparam2),
94 | ((ev1.eventparam1 + ev2.eventparam2 * 5 + 6) + ev2.eventparam4) - ev1.eventparam1, count(ev2.eventparam3)
95 | from event1 as ev1, event2 as ev2
96 | where ( ev1.eventparam1 * ev2.eventparam2 + 4 > ev2.eventparam4)
97 | or ev1.eventparam1 = ev2.eventparam2
98 | and ev1.eventparam1 > ev2.eventparam2
99 | within 60 seconds; ", 1),
100 |
101 | ?assertEqual({ok, [{correlation1},
102 | {[{event1, eventparam1},
103 | {sum, {event2, eventparam2}},
104 | {minus, {plus, {plus, {plus, {event1, eventparam1},
105 | {mult, {event2, eventparam2}, {integer, 5}}},
106 | {integer, 6}},
107 | {event2, eventparam4}},
108 | {event1, eventparam1}},
109 | {count, {event2, eventparam3}}]},
110 | {[event1, event2]},
111 | {{'or', {gt, {plus, {mult, {event1, eventparam1}, {event2, eventparam2}}, {integer, 4}},
112 | {event2, eventparam4}},
113 | {'and', {eq, {event1, eventparam1}, {event2, eventparam2}},
114 | {gt, {event1, eventparam1}, {event2, eventparam2}}}}},
115 | {60, sliding}, {[]}]},
116 | rivus_cep_parser:parse(Tokens)).
117 |
118 | parse_pattern_test() ->
119 | {ok, Tokens, _Endline} = rivus_cep_scanner:string("define pattern1 as
120 | select ev1.eventparam1, ev2.eventparam2, ev2.eventparam3, ev1.eventparam2
121 | from event1 as ev1 -> event2 as ev2
122 | where ev1.eventparam1 = ev2.eventparam2 and ev1.eventparam1 > ev2.eventparam2
123 | within 60 seconds; ", 1),
124 | ?assertEqual({ok, [{pattern1},
125 | {[{event1, eventparam1},
126 | {event2, eventparam2},
127 | {event2, eventparam3},
128 | {event1, eventparam2}]},
129 | {pattern, {[event1, event2]}},
130 | {{'and', {eq, {event1, eventparam1}, {event2, eventparam2}},
131 | {gt, {event1, eventparam1}, {event2, eventparam2}}}},
132 | {60, sliding}, {[]}]},
133 | rivus_cep_parser:parse(Tokens)).
134 |
135 |
136 | parse_pattern_2_test() ->
137 | {ok, Tokens, _Endline} = rivus_cep_scanner:string("define pattern1 as
138 | select ev1.eventparam1, ev2.eventparam2, ev2.eventparam3, ev1.eventparam2
139 | from event1 as ev1 -> event2 as ev2 -> event3 as ev3
140 | where ev1.eventparam1 = ev2.eventparam2 and ev2.eventparam1 = ev3.eventparam2
141 | within 60 seconds; ", 1),
142 |
143 | ?assertEqual({ok, [{pattern1},
144 | {[{event1, eventparam1},
145 | {event2, eventparam2},
146 | {event2, eventparam3},
147 | {event1, eventparam2}]},
148 | {pattern, {[event1, {event2, event3}]}},
149 | {{'and', {eq, {event1, eventparam1}, {event2, eventparam2}},
150 | {eq, {event2, eventparam1}, {event3, eventparam2}}}},
151 | {60, sliding}, {[]}]},
152 | rivus_cep_parser:parse(Tokens)).
153 |
154 | parse_query_filter_1_test() ->
155 | {ok, Tokens, _Endline} = rivus_cep_scanner:string("define query1 as
156 | select eventparam1
157 | from event1(eventparam3=100, eventparam4=\"APPL\"); ", 1),
158 | ?assertEqual({ok, [{query1},
159 | {[{event1, eventparam1}]}, {[event1]}, {nil}, {nil},
160 | {orddict:from_list([{event1, [{eq, {event1, eventparam3}, {integer, 100}},
161 | {eq, {event1, eventparam4}, {string, "APPL"}}]}])}
162 | ]},
163 | rivus_cep_parser:parse(Tokens)).
164 |
165 |
166 | parse_query_filter_2_test() ->
167 | {ok, Tokens, _Endline} = rivus_cep_scanner:string("define query1 as
168 | select ev1.eventparam1, ev2.eventparam2
169 | from event1(eventparam3=100, eventparam4=\"APPL\") as ev1,
170 | event2(eventpam1=300) as ev2; ", 1),
171 | ?assertEqual({ok, [{query1},
172 | {[{event1, eventparam1}, {event2, eventparam2}]}, {[event1, event2]}, {nil}, {nil},
173 | {orddict:from_list([{event1, [{eq, {event1, eventparam3}, {integer, 100}},
174 | {eq, {event1, eventparam4}, {string, "APPL"}}]},
175 | {event2, [{eq, {event2, eventpam1}, {integer, 300}}]}])}
176 | ]},
177 | rivus_cep_parser:parse(Tokens)).
178 |
179 | parse_batch_window_test() ->
180 | {ok, Tokens, _Endline} = rivus_cep_scanner:string("define correlation1 as
181 | select ev1.eventparam1, ev2.eventparam2, ev2.eventparam3, ev1.eventparam2
182 | from event1 as ev1, event2 as ev2
183 | where ev1.eventparam1 = ev2.eventparam2 and ev1.eventparam1 > ev2.eventparam2
184 | within 60 seconds batch ; ", 1),
185 | ?assertEqual({ok, [{correlation1},
186 | {[{event1, eventparam1},
187 | {event2, eventparam2},
188 | {event2, eventparam3},
189 | {event1, eventparam2}]},
190 | {[event1, event2]},
191 | {{'and', {eq, {event1, eventparam1}, {event2, eventparam2}},
192 | {gt, {event1, eventparam1}, {event2, eventparam2}}}},
193 | {60, batch}, {[]}]},
194 | rivus_cep_parser:parse(Tokens)).
195 |
196 | parse_query_filter_batch_aggr_test() ->
197 | {ok, Tokens, _Endline} = rivus_cep_scanner:string("define aggr_query as
198 | select ev1.eventparam1, ev2.eventparam2, sum(ev2.eventparam3)
199 | from event1(eventparam2=b) as ev1, event2(eventparam2=b) as ev2
200 | within 5 seconds batch; ", 1),
201 | ?assertEqual({ok, [{aggr_query},
202 | {[{event1, eventparam1}, {event2, eventparam2}, {sum, {event2, eventparam3}}]},
203 | {[event1, event2]}, {nil}, {5, batch},
204 | {orddict:from_list([{event1, [{eq, {event1, eventparam2}, {atom, b}}]},
205 | {event2, [{eq, {event2, eventparam2}, {atom, b}}]}])}
206 | ]},
207 | rivus_cep_parser:parse(Tokens)).
208 |
209 |
210 | parse_event_1_test() ->
211 | {ok, Tokens, _Endline} = rivus_cep_scanner:string("define event10 as (attr1, attr2, attr3);"),
212 |
213 | ?assertEqual({ok, {event, {event10, [attr1, attr2, attr3]}}},
214 | rivus_cep_parser:parse(Tokens)).
215 |
216 | parse_query_no_alias_test() ->
217 | {ok, Tokens, _Endline} = rivus_cep_scanner:string("define query1 as
218 | select eventparam1, attr2
219 | from event1, event4
220 | where eventparam1 = 20
221 | within 60 seconds; ", 1),
222 |
223 | ?assertEqual({ok, [{query1},
224 | {[{event1, eventparam1}, {event4,attr2}]},
225 | {[event1, event4]},
226 | {{eq, {event1, eventparam1}, {integer, 20}}},
227 | {60, sliding}, {[]}]},
228 | rivus_cep_parser:parse(Tokens)).
229 |
230 | parse_query_no_alias_ubiquitous_1_test() ->
231 | {ok, Tokens, _Endline} = rivus_cep_scanner:string("define query1 as
232 | select eventparam1, eventparam2
233 | from event1, event2
234 | where eventparam1 = 20
235 | within 60 seconds; ", 1),
236 | ?assertError({badmatch,ubiquitous_event_param}, rivus_cep_parser:parse(Tokens)).
237 |
238 | parse_query_no_alias_2_test() ->
239 | {ok, Tokens, _Endline} = rivus_cep_scanner:string("define query1 as
240 | select attr1, attr11
241 | from event4, event5
242 | where attr1 = 20
243 | within 60 seconds; ", 1),
244 |
245 | ?assertEqual({ok, [{query1},
246 | {[{event4, attr1}, {event5,attr11}]},
247 | {[event4, event5]},
248 | {{eq, {event4, attr1}, {integer, 20}}},
249 | {60, sliding}, {[]}]},
250 | rivus_cep_parser:parse(Tokens)).
251 |
252 | parse_query_no_alias_ubiquitous_2_test() ->
253 | {ok, Tokens, _Endline} = rivus_cep_scanner:string("define query1 as
254 | select attr1, attr11
255 | from event4, event5
256 | where attr4 = 20
257 | within 60 seconds; ", 1),
258 | ?assertError({badmatch,ubiquitous_event_param}, rivus_cep_parser:parse(Tokens)).
259 |
260 |
--------------------------------------------------------------------------------
/test/rivus_cep_query_planner_tests.erl:
--------------------------------------------------------------------------------
1 | -module(rivus_cep_query_planner_tests).
2 | -compiler([export_all]).
3 | -include_lib("eunit/include/eunit.hrl").
4 |
5 |
6 | to_cnf_1_test() ->
7 | Predicate = {neg,{'or', p,q}},
8 | ?assertEqual({'and',{neg,p}, {neg,q}}, rivus_cep_query_planner:to_cnf(Predicate)).
9 |
10 | to_cnf_2_test() ->
11 | Predicate = {neg,{'or',
12 | {neg,{'or',p1,q1}},
13 | {neg, {'and',p2,q2}}}},
14 | ?assertEqual({'and',{'or',p1,q1},{'and',p2,q2}}, rivus_cep_query_planner:to_cnf(Predicate)).
15 |
16 | to_cnf_3_test() ->
17 | Predicate = {'or', p, {'and', q, r}},
18 | ?assertEqual({'and', {'or', p, q}, {'or', p, r}}, rivus_cep_query_planner:to_cnf(Predicate)).
19 |
20 | to_cnf_4_test() ->
21 | Predicate = {neg,{'or', p, {'and', q, r}}},
22 | ?assertEqual({'and',{neg,p},{'or',{neg,q},{neg,r}}}, rivus_cep_query_planner:to_cnf(Predicate)).
23 |
24 | to_single_predicate_test() ->
25 | ?assertEqual({eq,{event1,eventparam2},{event2,eventparam2}}, rivus_cep_query_planner:to_single_predicate(
26 | [{eq,{event1,eventparam2},{event2,eventparam2}}])),
27 | ?assertEqual({'and',
28 | {eq,{event1,eventparam2},{event2,eventparam2}},
29 | {eq,{event3,eventparam2},{event4,eventparam2}}},
30 | rivus_cep_query_planner:to_single_predicate(
31 | [{eq,{event1,eventparam2},{event2,eventparam2}},
32 | {eq,{event3,eventparam2},{event4,eventparam2}}])),
33 | ?assertEqual({'and',
34 | {'and',
35 | {eq,{event1,eventparam2},{event2,eventparam2}},
36 | {eq,{event3,eventparam2},{event4,eventparam2}}},
37 | {eq,{event5,eventparam2},{event6,eventparam2}}
38 | },
39 | rivus_cep_query_planner:to_single_predicate(
40 | [{eq,{event1,eventparam2},{event2,eventparam2}},
41 | {eq,{event3,eventparam2},{event4,eventparam2}},
42 | {eq,{event5,eventparam2},{event6,eventparam2}}])).
43 |
44 |
45 | predicates_to_list_1_test()->
46 | Predicate = {'and', p, q},
47 | CNF = rivus_cep_query_planner:to_cnf(Predicate),
48 | ?assertEqual([p,q], rivus_cep_query_planner:predicates_to_list(CNF)).
49 |
50 | predicates_to_list_2_test()->
51 | Predicate = {'and', p, {'and', q, r}},
52 | CNF = rivus_cep_query_planner:to_cnf(Predicate),
53 | ?assertEqual([p, q, r], rivus_cep_query_planner:predicates_to_list(CNF)).
54 |
55 | predicates_to_list_3_test()->
56 | Predicate = {'and', {'or', p1,p2}, {'and', q, r}},
57 | CNF = rivus_cep_query_planner:to_cnf(Predicate),
58 | ?assertEqual([{'or',p1,p2}, q, r], rivus_cep_query_planner:predicates_to_list(CNF)).
59 |
60 |
61 | get_predicate_variables_test() ->
62 | Predicate = {'or',{gt,{plus,{mult,{event1,eventparam1},{event2,eventparam2}},{integer,4}},
63 | {event2,eventparam4}},
64 | {'and',{eq,{event1,eventparam1},{event2,eventparam2}},
65 | {gt,{event1,eventparam1},{event2,eventparam2}}}},
66 | %% Transform Where Clause in Conjunctive Normal Form
67 | CNF = rivus_cep_query_planner:to_cnf(Predicate),
68 |
69 | %% Get the list of predicates (list of conjuncts)
70 | PL = rivus_cep_query_planner:predicates_to_list(CNF),
71 | ?assertEqual([{'or',{gt,{plus,{mult,{event1,eventparam1},{event2,eventparam2}},{integer,4}},
72 | {event2,eventparam4}},
73 | {eq,{event1,eventparam1},{event2,eventparam2}}},
74 | {'or',{gt,{plus,{mult,{event1,eventparam1},{event2,eventparam2}},{integer,4}},
75 | {event2,eventparam4}},
76 | {gt,{event1,eventparam1},{event2,eventparam2}}}]
77 | , PL),
78 | %% find the variables in each predicate(conjunct) and return a list of pairs: variables-predicate:
79 | %% [ {[variables], predicate}, {[variables],predicate}, {[variables],predicate}, ..... ]
80 | ?assertEqual([{[{event1,eventparam1},
81 | {event2,eventparam2},
82 | {event2,eventparam4},
83 | {integer,4}],
84 | {'or',{gt,{plus,{mult,{event1,eventparam1},{event2,eventparam2}},{integer,4}},
85 | {event2,eventparam4}},
86 | {eq,{event1,eventparam1},{event2,eventparam2}}}
87 | },
88 | {[{event1,eventparam1},
89 | {event2,eventparam2},
90 | {event2,eventparam4},
91 | {integer,4}],
92 | {'or',{gt,{plus,{mult,{event1,eventparam1},{event2,eventparam2}},{integer,4}},
93 | {event2,eventparam4}},
94 | {gt,{event1,eventparam1},{event2,eventparam2}}}
95 | }],
96 | rivus_cep_query_planner:get_predicate_variables(PL)).
97 |
98 | set_get_predicates_on_edge_test() ->
99 | Predicate = {'and',{eq,{event1,eventparam1},{event2,eventparam2}},
100 | {eq,{event2,eventparam1},{event3,eventparam2}}},
101 | Pattern = [event1,event2,event3], % event1 -> event2 ->event3
102 | CNF = rivus_cep_query_planner:to_cnf(Predicate),
103 | PL = rivus_cep_query_planner:predicates_to_list(CNF),
104 | PV = rivus_cep_query_planner:get_predicate_variables(PL),
105 |
106 | G = digraph:new(),
107 | V1 = digraph:add_vertex(G, event1),
108 | V2 = digraph:add_vertex(G, event2),
109 | V3 = digraph:add_vertex(G, event3),
110 |
111 | Start = rivus_cep_query_planner:get_start_state(Pattern),
112 | ?assertEqual(event1, Start),
113 | E1 = digraph:add_edge(G, V1, V2, []),
114 | {NewPV, Label1} = rivus_cep_query_planner:set_predicates_on_edge(Start, V2, PV, G),
115 | digraph:add_edge(G, E1, V1, V2, Label1),
116 |
117 | E2 = digraph:add_edge(G, V2, V3, []),
118 | {_, Label2} = rivus_cep_query_planner:set_predicates_on_edge(Start, V3, NewPV, G),
119 | digraph:add_edge(G, E2, V2, V3, Label2),
120 |
121 | ?assertEqual(Label1, rivus_cep_query_planner:get_predicates_on_edge(G,V1,V2)),
122 | ?assertEqual(Label2, rivus_cep_query_planner:get_predicates_on_edge(G,V2,V3)).
123 |
124 | pattern_to_graph_test() ->
125 | Predicate = {'and',{eq,{b,eventparam1},{c,eventparam2}},
126 | {eq,{d,eventparam1},{c,eventparam2}}},
127 |
128 | CNF = rivus_cep_query_planner:to_cnf(Predicate),
129 | PL = rivus_cep_query_planner:predicates_to_list(CNF),
130 | PV = rivus_cep_query_planner:get_predicate_variables(PL),
131 |
132 | %{a,b} - choice "a or b"
133 | %[a,b] - sequence "a then b"
134 | Pattern = [a,b,{{c, {c,d}}}], %% a->b->(c->(c or d))
135 |
136 | G = rivus_cep_query_planner:pattern_to_graph(PV, Pattern),
137 |
138 | ?assertEqual([], rivus_cep_query_planner:get_predicates_on_edge(G,a,b)),
139 | ?assertEqual([{eq,{b,eventparam1},{c,eventparam2}}], rivus_cep_query_planner:get_predicates_on_edge(G,b,c)),
140 | ?assertEqual([], rivus_cep_query_planner:get_predicates_on_edge(G,c,c)),
141 | ?assertEqual([{eq,{d,eventparam1},{c,eventparam2}}], rivus_cep_query_planner:get_predicates_on_edge(G,c,d)),
142 |
143 | ?assertEqual([a,b],digraph:get_path(G,a,b)),
144 | ?assertEqual([b,c],digraph:get_path(G,b,c)),
145 |
146 | ?assertEqual([a,b,c],digraph:get_path(G,a,c)),
147 |
148 | ?assertEqual([c,c],digraph:get_path(G,c,c)),
149 | ?assertEqual([c,d],digraph:get_path(G,c,d)),
150 |
151 | ?assertNot(digraph:get_path(G,d,a)),
152 |
153 | ?assertNot(digraph:get_path(G,c,a)),
154 | ?assertNot(digraph:get_path(G,b,a)),
155 | ?assertNot(digraph:get_path(G,c,b)),
156 |
157 | ?assertNot(digraph:get_path(G,d,b)),
158 | ?assertNot(digraph:get_path(G,d,c)),
159 |
160 | ?assertEqual([c], digraph_utils:loop_vertices(G)),
161 | ?assertEqual([a,b,c], digraph:get_path(G, a, c)),
162 |
163 | ?assertEqual(1, digraph:out_degree(G, a)),
164 | ?assertEqual(1, digraph:out_degree(G, b)),
165 | ?assertEqual(2, digraph:out_degree(G, c)),
166 |
167 | ?assertEqual(0, digraph:out_degree(G, d)),
168 | ?assertEqual(1, digraph:in_degree(G, d)),
169 | ?assertEqual(2, digraph:in_degree(G, c)),
170 |
171 | ?assertEqual(0, digraph:in_degree(G, a)),
172 | ?assertEqual(1, digraph:in_degree(G, b)),
173 | ?assertEqual([c,d], digraph_utils:reachable_neighbours([c],G)),
174 |
175 | ?assertNot(digraph:get_cycle(G, a)),
176 | ?assertNot(digraph:get_cycle(G, b)),
177 | ?assertNot(digraph:get_cycle(G, d)),
178 | ?assertEqual([c], digraph:get_cycle(G, c)).
179 |
180 | pattern_to_graph_2_test() ->
181 | Predicate = {'or',
182 | {'and',{eq,{b,eventparam1},{c,eventparam2}},
183 | {eq,{d,eventparam1},{c,eventparam2}}},
184 | {eq,{x,param1},{d,param1}}},
185 |
186 | CNF = rivus_cep_query_planner:to_cnf(Predicate),
187 | PL = rivus_cep_query_planner:predicates_to_list(CNF),
188 | PV = rivus_cep_query_planner:get_predicate_variables(PL),
189 |
190 | %{a,b} - choice "a or b"
191 | %[a,b] - sequence "a then b"
192 | Pattern = [a,b,{{c, {c,d}},{x, {x,d}}}], %% a->b->((c->(c or d)) or (x->(x or d))) - unsupported pattern. cannot assign predicates
193 | ?assertError({unsupported_pattern, _, _}, rivus_cep_query_planner:pattern_to_graph(PV, Pattern)).
194 |
195 |
196 | get_join_keys_test() ->
197 | Predicate = {eq,{event1,eventparam1},{event2, eventparam1}},
198 | ?assertEqual([{event1,[eventparam1]}, {event2, [eventparam1]}], orddict:to_list(rivus_cep_query_planner:get_join_keys(Predicate))).
199 |
200 | get_join_keys_2_test() ->
201 | Predicate = {'and',{eq,{event1,eventparam1},{event2,eventparam2}},
202 | {gt,{event1,eventparam1},{event2,eventparam2}}},
203 | ?assertEqual([{event1,[eventparam1]}, {event2, [eventparam2]}], orddict:to_list(rivus_cep_query_planner:get_join_keys(Predicate))).
204 |
205 | get_join_keys_3_test() ->
206 | Predicate = {'or',{gt,{plus,{mult,{event1,eventparam1},{event2,eventparam2}},{integer,4}},
207 | {event2,eventparam4}
208 | },
209 | {'and',{eq,{event1,eventparam1},{event2,eventparam2}},
210 | {gt,{event1,eventparam1},{event2,eventparam2}}}},
211 | ?assertEqual([{event1,[eventparam1]}, {event2, [eventparam2,eventparam4]}], orddict:to_list(rivus_cep_query_planner:get_join_keys(Predicate))).
212 |
213 | get_join_keys_4_test() ->
214 | Predicate = {eq,{event1,eventparam1},{integer, 10}},
215 | ?assertEqual([], orddict:to_list(rivus_cep_query_planner:get_join_keys(Predicate))).
216 |
217 | get_join_keys_5_test() ->
218 | Predicate = {'and', {eq,{event1,eventparam1},{integer, 10}},
219 | {eq,{event1,eventparam2},{integer, 200}}
220 | },
221 | ?assertEqual([], orddict:to_list(rivus_cep_query_planner:get_join_keys(Predicate))).
222 |
223 | get_join_keys_6_test() ->
224 | Predicate = {eq, {mult,{event1,eventparam1}, {integer,4}},
225 | {event2,eventparam4}
226 | },
227 | ?assertEqual([{event1,[eventparam1]}, {event2, [eventparam4]}], orddict:to_list(rivus_cep_query_planner:get_join_keys(Predicate))).
228 |
229 | get_start_state_test() ->
230 | ?assertEqual(a, rivus_cep_query_planner:get_start_state([a,b,c])),
231 | ?assertError({badpattern,_}, rivus_cep_query_planner:get_start_state([[a,b],c,d])),
232 | ?assertError({badpattern,_}, rivus_cep_query_planner:get_start_state([{a,b},c,d])).
233 |
234 |
235 |
236 |
237 |
238 |
239 |
--------------------------------------------------------------------------------
/test/rivus_cep_query_worker_tests.erl:
--------------------------------------------------------------------------------
1 | -module(rivus_cep_query_worker_tests).
2 |
3 | -compile([debug_info, export_all]).
4 | -compile([{parse_transform, lager_transform}]).
5 |
6 | -include_lib("eunit/include/eunit.hrl").
7 | -include("rivus_cep.hrl").
8 |
9 | query_worker_test_() ->
10 | {setup,
11 | fun () ->
12 | folsom:start(),
13 | lager:start(),
14 | application:start(gproc),
15 | lager:set_loglevel(lager_console_backend, debug),
16 | application:set_env(rivus_cep, rivus_window_provider, rivus_cep_window_ets),
17 | ok = application:start(rivus_cep)
18 | end,
19 | fun (_) ->
20 | folsom:stop(),
21 | application:stop(lager),
22 | application:stop(gproc),
23 | ok = application:stop(rivus_cep)
24 | end,
25 |
26 | [{"Test query without aggregations",
27 | fun query_1/0},
28 | {"Test an aggregation query",
29 | fun query_2/0},
30 | {"Test query on event sequence (event pattern matching)",
31 | fun pattern/0}
32 | ]
33 | }.
34 |
35 | query_1() ->
36 | {ok,SubPid} = result_subscriber:start_link(),
37 |
38 | QueryStr = "define correlation1 as
39 | select ev1.eventparam1, ev2.eventparam2, ev2.eventparam3, ev1.eventparam2
40 | from event1 as ev1, event2 as ev2
41 | where ev1.eventparam2 = ev2.eventparam2
42 | within 60 seconds; ",
43 | Mod = application:get_env(rivus_cep, rivus_window_provider, rivus_cep_window_ets),
44 | {ok, Pid} = rivus_cep_window:start_link(Mod),
45 |
46 | Window = rivus_cep_window:new(Pid, slide, 60),
47 | {ok, Tokens, Endline} = rivus_cep_scanner:string(QueryStr, 1),
48 | {ok, QueryClauses} = rivus_cep_parser:parse(Tokens),
49 | {ok, QueryPid} = rivus_cep_query_worker:start_link(#query_details{
50 | clauses = QueryClauses,
51 | producers = [test_query_1],
52 | subscribers = [SubPid],
53 | options = [],
54 | event_window = Window,
55 | event_window_pid = Pid,
56 | fsm_window = nil,
57 | window_register = nil}),
58 |
59 | Event1 = {event1, 10,b,c},
60 | Event2 = {event1, 15,bbb,c},
61 | Event3 = {event1, 20,b,c},
62 | Event4 = {event2, 30,b,cc,d},
63 | Event5 = {event2, 40,bb,cc,dd},
64 |
65 | gproc:send({p, l, {test_query_1, element(1, Event1)}}, Event1),
66 | gproc:send({p, l, {test_query_1, element(1, Event2)}}, Event2),
67 | gproc:send({p, l, {test_query_1, element(1, Event3)}}, Event3),
68 | gproc:send({p, l, {test_query_1, element(1, Event4)}}, Event4),
69 | gproc:send({p, l, {test_query_1, element(1, Event5)}}, Event5),
70 |
71 | timer:sleep(2000),
72 |
73 | {ok,Values} = gen_server:call(SubPid, get_result),
74 | %% ?debugMsg(io_lib:format("Values: ~p~n",[Values])),
75 | %%?assertEqual([{10,b,cc,b},{20,b,cc,b}], Values),
76 | ?assertEqual(2, length(Values)),
77 | ?assert(lists:any(fun(T) -> T == {10,b,cc,b} end, Values)),
78 | ?assert(lists:any(fun(T) -> T == {20,b,cc,b} end, Values)),
79 |
80 | gen_server:call(QueryPid,stop),
81 | gen_server:call(SubPid,stop).
82 |
83 | query_2()->
84 | {ok, SubPid} = result_subscriber:start_link(),
85 |
86 | QueryStr = "define correlation2 as
87 | select ev1.eventparam1, ev2.eventparam2, sum(ev2.eventparam3)
88 | from event1 as ev1, event2 as ev2
89 | where ev1.eventparam2 = ev2.eventparam2
90 | within 60 seconds; ",
91 |
92 | Mod = application:get_env(rivus_cep, rivus_window_provider, rivus_cep_window_ets),
93 | {ok, Pid} = rivus_cep_window:start_link(Mod),
94 |
95 | Window = rivus_cep_window:new(Pid, slide, 60),
96 |
97 | {ok, Tokens, Endline} = rivus_cep_scanner:string(QueryStr, 1),
98 | {ok, QueryClauses} = rivus_cep_parser:parse(Tokens),
99 | {ok, QueryPid} = rivus_cep_query_worker:start_link(#query_details{
100 | clauses = QueryClauses,
101 | producers = [test_query_2],
102 | subscribers = [SubPid],
103 | options = [],
104 | event_window = Window,
105 | event_window_pid = Pid,
106 | fsm_window = nil,
107 | window_register = nil}),
108 |
109 | %% send some events
110 | Event1 = {event1, gr1,b,10},
111 | Event2 = {event1, gr2,bbb,20},
112 | Event3 = {event1, gr3,b,30},
113 | Event4 = {event2, gr1,b,40,d},
114 | Event5 = {event2, gr2,bb,50,dd},
115 | Event6 = {event2, gr3,b,40,d},
116 |
117 | gproc:send({p, l, {test_query_2, element(1, Event1)}}, Event1),
118 | gproc:send({p, l, {test_query_2, element(1, Event2)}}, Event2),
119 | gproc:send({p, l, {test_query_2, element(1, Event3)}}, Event3),
120 | gproc:send({p, l, {test_query_2, element(1, Event4)}}, Event4),
121 | gproc:send({p, l, {test_query_2, element(1, Event5)}}, Event5),
122 | gproc:send({p, l, {test_query_2, element(1, Event6)}}, Event6),
123 |
124 | timer:sleep(2000),
125 | {ok,Values} = gen_server:call(SubPid, get_result),
126 | %% ?debugMsg(io_lib:format("Values: ~p~n",[Values])),
127 | %%?assertEqual([{gr1,b,80},{gr3,b,80}], Values),
128 | ?assertEqual(2, length(Values)),
129 | ?assert(lists:any(fun(T) -> T == {gr1,b,80} end, Values)),
130 | ?assert(lists:any(fun(T) -> T == {gr3,b,80} end, Values)),
131 |
132 | gen_server:call(QueryPid,stop),
133 | gen_server:call(SubPid,stop).
134 |
135 |
136 | pattern() ->
137 | {ok, SubPid} = result_subscriber:start_link(),
138 |
139 | QueryStr = "define pattern1 as
140 | select ev1.eventparam1, ev2.eventparam2, ev2.eventparam3, ev2.eventparam4
141 | from event1 as ev1 -> event2 as ev2
142 | where ev1.eventparam2 = ev2.eventparam2
143 | within 60 seconds; ",
144 | Mod = application:get_env(rivus_cep, rivus_window_provider, rivus_cep_window_ets),
145 | {ok, Pid1} = rivus_cep_window:start_link(Mod),
146 | {ok, Pid2} = rivus_cep_window:start_link(Mod),
147 |
148 | Window = rivus_cep_window:new(Pid1, slide, 60),
149 | FsmWindow = rivus_cep_window:new(Pid2, 60),
150 |
151 | {ok, Tokens, _Endline} = rivus_cep_scanner:string(QueryStr, 1),
152 | {ok, QueryClauses} = rivus_cep_parser:parse(Tokens),
153 | {ok, QueryPid} = rivus_cep_query_worker:start_link(#query_details{
154 | clauses = QueryClauses,
155 | producers = [test_pattern_1],
156 | subscribers = [SubPid],
157 | options = [],
158 | event_window = Window,
159 | event_window_pid = Pid1,
160 | fsm_window = FsmWindow,
161 | fsm_window_pid = Pid2,
162 | window_register = nil}),
163 |
164 | Event1 = {event1, 10,b,10},
165 | Event2 = {event1, 15,bbb,20},
166 | Event3 = {event1, 20,b,10},
167 | Event4 = {event2, 30,b,100,20},
168 | Event5 = {event2, 40,bb,200,30},
169 |
170 | gproc:send({p, l, {test_pattern_1, element(1, Event1)}}, Event1),
171 | gproc:send({p, l, {test_pattern_1, element(1, Event2)}}, Event2),
172 | gproc:send({p, l, {test_pattern_1, element(1, Event3)}}, Event3),
173 | gproc:send({p, l, {test_pattern_1, element(1, Event4)}}, Event4),
174 | gproc:send({p, l, {test_pattern_1, element(1, Event5)}}, Event5),
175 |
176 | timer:sleep(2000),
177 |
178 | {ok,Values} = gen_server:call(SubPid, get_result),
179 | ?debugMsg(io_lib:format("Values: ~p~n",[Values])),
180 | ?assertEqual([{10,b,100,20},{20,b,100,20}], Values),
181 | gen_server:call(QueryPid,stop),
182 | gen_server:call(SubPid,stop).
183 |
--------------------------------------------------------------------------------
/test/rivus_cep_result_eval_tests.erl:
--------------------------------------------------------------------------------
1 | -module(rivus_cep_result_eval_tests).
2 | -compile([debug_info, export_all]).
3 | -compile([{parse_transform, lager_transform}]).
4 |
5 | -include_lib("eunit/include/eunit.hrl").
6 | -include("rivus_cep.hrl").
7 |
8 | group_key_1_test() ->
9 | QueryStr = "define aggr_query as
10 | select ev1.eventparam1, ev1.eventparam2, sum(ev1.eventparam3)
11 | from event1(eventparam2=b) as ev1
12 | within 5 seconds batch; ",
13 | {ok, Tokens, _Endline} = rivus_cep_scanner:string(QueryStr, 1),
14 |
15 | ?debugMsg(io_lib:format("Select: ~p~n",[ rivus_cep_parser:parse(Tokens)])),
16 | SelectClause = [{event1,eventparam1},
17 | {event1,eventparam2},
18 | {sum,{event1,eventparam3}}],
19 | Key1 = rivus_cep_result_eval:get_group_key({event1,gr1,b,30}, SelectClause),
20 | Key2 = rivus_cep_result_eval:get_group_key({event1,gr3,b,40,d}, SelectClause),
21 |
22 | ?assertEqual([{gr1,b}],[Key1]),
23 | ?assertEqual([{gr3,b}],[Key2]).
24 |
25 | group_key_2_test() ->
26 | QueryStr = "define aggr_query as
27 | select ev1.eventparam1, ev1.eventparam2, ev1.eventparam3 + ev1.eventparam4, sum(ev1.eventparam3)
28 | from event1(eventparam2=b) as ev1
29 | within 5 seconds batch; ",
30 | {ok, Tokens, _Endline} = rivus_cep_scanner:string(QueryStr, 1),
31 |
32 | ?debugMsg(io_lib:format("Select: ~p~n",[ rivus_cep_parser:parse(Tokens)])),
33 | SelectClause = [{event1,eventparam1},
34 | {event1,eventparam2},
35 | {plus,{event1,eventparam3},{event1,eventparam4}},
36 | {sum,{event1,eventparam3}}],
37 | Key1 = rivus_cep_result_eval:get_group_key({event1,gr1,b,30,10}, SelectClause),
38 | Key2 = rivus_cep_result_eval:get_group_key({event1,gr3,b,40,10,d}, SelectClause),
39 |
40 | ?assertEqual([{gr1,b,40}],[Key1]),
41 | ?assertEqual([{gr3,b,50}],[Key2]).
42 |
43 |
44 |
--------------------------------------------------------------------------------
/test/rivus_cep_server_tests.erl:
--------------------------------------------------------------------------------
1 | -module(rivus_cep_server_tests).
2 |
3 | -compile([debug_info, export_all]).
4 | -compile([{parse_transform, lager_transform}]).
5 |
6 | -include_lib("eunit/include/eunit.hrl").
7 |
8 |
9 | query_worker_test_() ->
10 | {setup,
11 | fun() ->
12 | folsom:start(),
13 | lager:start(),
14 | application:start(gproc),
15 | lager:set_loglevel(lager_console_backend, debug),
16 | application:set_env(rivus_cep, rivus_window_provider, rivus_cep_window_ets),
17 | application:set_env(rivus_cep, rivus_tcp_serv, {"127.0.0.1", 5775}),
18 | ok = application:start(rivus_cep)
19 | end,
20 | fun(_) ->
21 | folsom:stop(),
22 | application:stop(lager),
23 | application:stop(gproc),
24 | application:stop(rivus_cep)
25 | end,
26 | [
27 | {"Test query via TCP connection",
28 | timeout, 60 * 60,
29 | fun load_query_tcp/0},
30 | {"Test event module load via TCP connection",
31 | timeout, 60 * 60,
32 | fun load_event_tcp/0},
33 | {"Test query and event module load via TCP connection",
34 | timeout, 60 * 60,
35 | fun load_query_and_event/0}
36 | ]
37 | }.
38 |
39 |
40 | load_query_tcp() ->
41 |
42 | {ok, Pid} = result_subscriber:start_link(),
43 | {ok, {Host, Port}} = application:get_env(rivus_cep, rivus_tcp_serv),
44 | {ok, Socket} = gen_tcp:connect(Host, Port, [{active, false}, {nodelay, true}, {packet, 4}, binary]),
45 |
46 | QueryStr = "define correlation1 as
47 | select ev1.eventparam1, ev2.eventparam2, ev2.eventparam3, ev1.eventparam2
48 | from event1 as ev1, event2 as ev2
49 | where ev1.eventparam2 = ev2.eventparam2
50 | within 60 seconds; ",
51 |
52 | ?assertEqual(ok, gen_tcp:send(Socket, term_to_binary({load_query, {QueryStr, [test_query_1], [Pid], []}}))),
53 | Result = gen_tcp:recv(Socket, 0),
54 | ?assertMatch({ok, _}, Result),
55 | QueryPid = binary_to_term(element(2, Result)),
56 |
57 | Event1 = {event1, 10, b, c},
58 | Event2 = {event1, 15, bbb, c},
59 | Event3 = {event1, 20, b, c},
60 | Event4 = {event2, 30, b, cc, d},
61 | Event5 = {event2, 40, bb, cc, dd},
62 |
63 | gen_tcp:send(Socket, term_to_binary({event, test_query_1, Event1})),
64 | gen_tcp:send(Socket, term_to_binary({event, test_query_1, Event2})),
65 | gen_tcp:send(Socket, term_to_binary({event, test_query_1, Event3})),
66 | gen_tcp:send(Socket, term_to_binary({event, test_query_1, Event4})),
67 | gen_tcp:send(Socket, term_to_binary({event, test_query_1, Event5})),
68 |
69 | timer:sleep(3000),
70 |
71 | {ok, Values} = gen_server:call(Pid, get_result),
72 | ?debugMsg(io_lib:format("Values: ~p~n", [Values])),
73 | %%?assertEqual([{10, b, cc, b}, {20, b, cc, b}], Values),
74 | ?assertEqual(2, length(Values)),
75 | ?assert(lists:any(fun(T) -> T == {10, b, cc, b} end, Values)),
76 | ?assert(lists:any(fun(T) -> T == {20, b, cc, b} end, Values)),
77 |
78 | gen_server:call(QueryPid, stop),
79 | gen_server:call(Pid, stop).
80 |
81 | load_event_tcp() ->
82 | EventDefStr = "define event11 as (attr1, attr2, attr3, attr4);",
83 | {ok, {Host, Port}} = application:get_env(rivus_cep, rivus_tcp_serv),
84 | {ok, Socket} = gen_tcp:connect(Host, Port, [{active, false}, {nodelay, true}, {packet, 4}, binary]),
85 | ?assertEqual(ok, gen_tcp:send(Socket, term_to_binary({load_query, {EventDefStr, [], [], []}}))),
86 | Result = gen_tcp:recv(Socket, 0),
87 | ?assertMatch({ok, _}, Result),
88 | QueryPid = binary_to_term(element(2, Result)).
89 |
90 |
91 |
92 | load_query_and_event() ->
93 | EventDefStr = "define event11 as (attr1, attr2, attr3, attr4);",
94 | Query = "define correlation1 as
95 | select sum(ev1.attr1)
96 | from event11 as ev1
97 | within 60 seconds; ",
98 | {ok, {Host, Port}} = application:get_env(rivus_cep, rivus_tcp_serv),
99 | {ok, Socket} = gen_tcp:connect(Host, Port, [{active, false}, {nodelay, true}, {packet, 4}, binary]),
100 | ok = gen_tcp:send(Socket, term_to_binary({load_query, {EventDefStr, [benchmark_event], [], []}})),
101 | ?assertMatch({ok, _}, gen_tcp:recv(Socket, 0)),
102 |
103 |
104 | ok = gen_tcp:send(Socket, term_to_binary({load_query, {Query, [benchmark_test], [], []}})),
105 |
106 | ?assertMatch({ok, _}, gen_tcp:recv(Socket, 0)).
107 |
108 | %% {ok, {Host, Port}} = application:get_env(rivus_cep, rivus_tcp_serv).
109 | %% {ok, Socket} = gen_tcp:connect(Host, Port, [{active, false}, {nodelay, true}, {packet, 4}, binary]).
110 | %%
111 | %%
112 | %% Event1 = {event11, 10,b,c}.
113 | %% Event2 = {event11, 15,bbb,c}.
114 | %% Event3 = {event11, 20,b,c}.
115 | %% Event4 = {event11, 30,b,cc,d}.
116 | %% Event5 = {event11, 40,bb,cc,dd}.
117 | %%
118 | %% rivus_cep:notify(test1, Event1).
119 | %% rivus_cep:notify(test1, Event2).
120 | %% rivus_cep:notify(test1, Event3).
121 | %% rivus_cep:notify(test1, Event4).
122 | %% rivus_cep:notify(test1, Event5).
123 | %%
124 | %%
125 | %% {ok,Pid} = result_subscriber:start_link().
126 | %% EventDefStr = "define event11 as (attr1, attr2, attr3, attr4);".
127 | %% QueryStr = "define correlation1 as
128 | %% select sum(ev1.attr1)
129 | %% from event11 as ev1
130 | %% within 60 seconds; ".
131 | %% ok = rivus_cep:execute(EventDefStr).
132 | %% {ok, QueryPid, _} = rivus_cep:execute(QueryStr, [test1], [Pid], []).
133 | %%
134 | %%
135 | %%
136 | %% gen_tcp:send(Socket, term_to_binary({event, test1, Event1})).
137 | %% gen_tcp:send(Socket, term_to_binary({event, test1, Event2})).
138 | %% gen_tcp:send(Socket, term_to_binary({event, test1, Event3})).
139 | %% gen_tcp:send(Socket, term_to_binary({event, test1, Event4})).
140 | %% gen_tcp:send(Socket, term_to_binary({event, test1, Event5})).
141 |
--------------------------------------------------------------------------------
/test/rivus_cep_window_ets_tests.erl:
--------------------------------------------------------------------------------
1 | %%%
2 | %%% Copyright 2012 - Basho Technologies, Inc. All Rights Reserved.
3 | %%%
4 | %%% Licensed under the Apache License, Version 2.0 (the "License");
5 | %%% you may not use this file except in compliance with the License.
6 | %%% You may obtain a copy of the License at
7 | %%%
8 | %%% http://www.apache.org/licenses/LICENSE-2.0
9 | %%%
10 | %%% Unless required by applicable law or agreed to in writing, software
11 | %%% distributed under the License is distributed on an "AS IS" BASIS,
12 | %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | %%% See the License for the specific language governing permissions and
14 | %%% limitations under the License.
15 | %%%
16 |
17 | -module(rivus_cep_window_ets_tests).
18 | -include_lib("eunit/include/eunit.hrl").
19 | -include("rivus_cep.hrl").
20 | -include_lib("../deps/folsom/include/folsom.hrl").
21 |
22 | -define(SIZE, 30).
23 | -define(DOUBLE_SIZE, 60).
24 | -define(RUNTIME, 90).
25 | -define(READINGS, 10).
26 |
27 | slide_test_() ->
28 | {setup,
29 | fun () ->
30 | folsom:start(),
31 | meck:new(folsom_utils)
32 | end,
33 | fun (_) -> meck:unload(folsom_utils),
34 | folsom:stop()
35 | end,
36 | [{"Create sliding window",
37 | fun create/0},
38 | {"test sliding window",
39 | {timeout, 30, fun slide/0}},
40 | {"resize sliding window (expand)",
41 | {timeout, 30, fun expand_window/0}},
42 | {"resize sliding window (shrink)",
43 | {timeout, 30, fun shrink_window/0}}
44 |
45 | ]}.
46 |
47 | create() ->
48 | Window = rivus_cep_window_ets:new(?SIZE, []),
49 | ?assert(is_pid(Window#slide.server)),
50 | ?assertEqual(?SIZE, Window#slide.window),
51 | ?assertEqual(0, ets:info(Window#slide.reservoir, size)).
52 |
53 | slide() ->
54 | %% don't want a trim to happen
55 | %% unless we call trim
56 | %% so kill the trim server process
57 | Window = rivus_cep_window_ets:new(?SIZE, []),
58 | ok = folsom_sample_slide_server:stop(Window#slide.server),
59 | Moments = lists:seq(1, ?RUNTIME),
60 | %% pump in 90 seconds worth of readings
61 | Moment = lists:foldl(fun(_X, Tick) ->
62 | Tock = tick(Tick),
63 | [rivus_cep_window_ets:update(Window, N, []) ||
64 | N <- lists:duplicate(?READINGS, Tock)],
65 | Tock end,
66 | 0,
67 | Moments),
68 | %% are all readings in the table?
69 | check_table(Window, Moments),
70 | %% get values only returns last ?WINDOW seconds
71 | ExpectedValues = lists:sort(lists:flatten([lists:duplicate(?READINGS, N) ||
72 | N <- lists:seq(?RUNTIME - ?SIZE, ?RUNTIME)])),
73 | Values = lists:sort(rivus_cep_window_ets:get_values(Window, [])),
74 | ?assertEqual(ExpectedValues, Values),
75 | %% trim the table
76 | Trimmed = rivus_cep_window_ets:trim(Window),
77 | ?assertEqual((?RUNTIME - ?SIZE - 1) * ?READINGS, Trimmed),
78 | check_table(Window, lists:seq(?RUNTIME - ?SIZE, ?RUNTIME)),
79 | %% increment the clock past the window
80 | tick(Moment, ?SIZE * 2),
81 | %% get values should be empty
82 | ?assertEqual([], rivus_cep_window_ets:get_values(Window, [])),
83 | %% trim, and table should be empty
84 | Trimmed2 = rivus_cep_window_ets:trim(Window),
85 | ?assertEqual((?RUNTIME * ?READINGS) - ((?RUNTIME - ?SIZE - 1) * ?READINGS), Trimmed2),
86 | check_table(Window, []),
87 | ok.
88 |
89 | expand_window() ->
90 | %% create a new histogram
91 | %% will leave the trim server running, as resize() needs it
92 | Window = rivus_cep_window_ets:new(?SIZE, []),
93 | Moments = lists:seq(1, ?RUNTIME ),
94 | %% pump in 90 seconds worth of readings
95 | Moment = lists:foldl(fun(_X, Tick) ->
96 | Tock = tick(Tick),
97 | [rivus_cep_window_ets:update(Window, N, []) ||
98 | N <- lists:duplicate(?READINGS, Tock)],
99 | Tock end,
100 | 0,
101 | Moments),
102 | %% are all readings in the table?
103 | check_table(Window, Moments),
104 |
105 | %% get values only returns last ?WINDOW seconds
106 | ExpectedValues = lists:sort(lists:flatten([lists:duplicate(?READINGS, N) ||
107 | N <- lists:seq(?RUNTIME - ?SIZE, ?RUNTIME)])),
108 | Values = lists:sort(rivus_cep_window_ets:get_values(Window, [])),
109 | ?assertEqual(ExpectedValues, Values),
110 |
111 | %%expand the sliding window
112 | NewWindow = rivus_cep_window_ets:resize(Window, ?DOUBLE_SIZE, []),
113 |
114 | %% get values only returns last ?WINDOW*2 seconds
115 | NewExpectedValues = lists:sort(lists:flatten([lists:duplicate(?READINGS, N) ||
116 | N <- lists:seq(?RUNTIME - ?DOUBLE_SIZE, ?RUNTIME)])),
117 | NewValues = lists:sort(rivus_cep_window_ets:get_values(NewWindow, [])),
118 | ?assertEqual(NewExpectedValues, NewValues),
119 |
120 | %% trim the table
121 | Trimmed = rivus_cep_window_ets:trim(NewWindow),
122 | ?assertEqual((?RUNTIME - ?DOUBLE_SIZE - 1) * ?READINGS, Trimmed),
123 | check_table(NewWindow, lists:seq(?RUNTIME - ?DOUBLE_SIZE, ?RUNTIME)),
124 | %% increment the clock past the window
125 | tick(Moment, ?DOUBLE_SIZE*2),
126 | %% get values should be empty
127 | ?assertEqual([], rivus_cep_window_ets:get_values(NewWindow, [])),
128 | %% trim, and table should be empty
129 | Trimmed2 = rivus_cep_window_ets:trim(NewWindow),
130 | ?assertEqual((?RUNTIME * ?READINGS) - ((?RUNTIME - ?DOUBLE_SIZE - 1) * ?READINGS), Trimmed2),
131 | check_table(NewWindow, []),
132 | ok.
133 | %% ok = folsom_metrics:delete_metric(?HISTO2).
134 |
135 |
136 | shrink_window() ->
137 | %% create a new histogram
138 | %% will leave the trim server running, as resize() needs it
139 | Window = rivus_cep_window_ets:new(?DOUBLE_SIZE, []),
140 | Moments = lists:seq(1, ?RUNTIME ),
141 | %% pump in 90 seconds worth of readings
142 | Moment = lists:foldl(fun(_X, Tick) ->
143 | Tock = tick(Tick),
144 | [rivus_cep_window_ets:update(Window, N, []) ||
145 | N <- lists:duplicate(?READINGS, Tock)],
146 | Tock end,
147 | 0,
148 | Moments),
149 | %% are all readings in the table?
150 | check_table(Window, Moments),
151 |
152 | %% get values only returns last ?DOUBLE_WINDOW seconds
153 | ExpectedValues = lists:sort(lists:flatten([lists:duplicate(?READINGS, N) ||
154 | N <- lists:seq(?RUNTIME - ?DOUBLE_SIZE, ?RUNTIME)])),
155 | Values = lists:sort(rivus_cep_window_ets:get_values(Window, [])),
156 | ?assertEqual(ExpectedValues, Values),
157 |
158 | %%shrink the sliding window
159 | NewWindow = rivus_cep_window_ets:resize(Window, ?SIZE, []),
160 |
161 | %% get values only returns last ?SIZE seconds
162 | NewExpectedValues = lists:sort(lists:flatten([lists:duplicate(?READINGS, N) ||
163 | N <- lists:seq(?RUNTIME - ?SIZE, ?RUNTIME)])),
164 | NewValues = lists:sort(rivus_cep_window_ets:get_values(NewWindow, [])),
165 | ?assertEqual(NewExpectedValues, NewValues),
166 |
167 |
168 | %% trim the table
169 | Trimmed = rivus_cep_window_ets:trim(NewWindow),
170 | ?assertEqual((?RUNTIME - ?SIZE - 1) * ?READINGS, Trimmed),
171 | check_table(NewWindow, lists:seq(?RUNTIME - ?SIZE, ?RUNTIME)),
172 | %% increment the clock past the window
173 | tick(Moment, ?SIZE*2),
174 | %% get values should be empty
175 | ?assertEqual([], rivus_cep_window_ets:get_values(NewWindow, [])),
176 | %% trim, and table should be empty
177 | Trimmed2 = rivus_cep_window_ets:trim(NewWindow),
178 | ?assertEqual((?RUNTIME * ?READINGS) - ((?RUNTIME - ?SIZE - 1) * ?READINGS), Trimmed2),
179 | check_table(NewWindow, []),
180 | ok.
181 |
182 | tick(Moment0, IncrBy) ->
183 | Moment = Moment0 + IncrBy,
184 | meck:expect(folsom_utils, now_epoch, fun() ->
185 | Moment end),
186 | Moment.
187 |
188 | tick(Moment) ->
189 | tick(Moment, 1).
190 |
191 | check_table(Window, Moments) ->
192 | Tab = lists:sort(ets:tab2list(Window#slide.reservoir)),
193 | {Ks, Vs} = lists:unzip(Tab),
194 | ExpectedVs = lists:sort(lists:flatten([lists:duplicate(10, N) || N <- Moments])),
195 | StrippedKeys = lists:usort([X || {X, _} <- Ks]),
196 | ?assertEqual(Moments, StrippedKeys),
197 | ?assertEqual(ExpectedVs, lists:sort(Vs)).
198 |
--------------------------------------------------------------------------------
/test/rivus_cep_window_tests.erl:
--------------------------------------------------------------------------------
1 | -module(rivus_cep_window_tests).
2 |
3 | -compile([debug_info, export_all]).
4 |
5 | -include_lib("eunit/include/eunit.hrl").
6 |
7 | -include_lib("stdlib/include/ms_transform.hrl").
8 | -include_lib("stdlib/include/qlc.hrl").
9 |
10 | -include("rivus_cep.hrl").
11 | -include_lib("../deps/folsom/include/folsom.hrl").
12 |
13 | window_test_() ->
14 | {setup,
15 | fun () ->
16 | folsom:start(),
17 | lager:start(),
18 | application:start(gproc),
19 | lager:set_loglevel(lager_console_backend, debug),
20 | meck:new(folsom_utils),
21 | application:set_env(rivus_cep, rivus_window_provider, rivus_cep_window_ets),
22 | ok = application:start(rivus_cep),
23 | tick(0, 0)
24 | end,
25 | fun (_) ->
26 | application:stop(lager),
27 | application:stop(gproc),
28 | application:stop(rivus_cep),
29 | meck:unload(folsom_utils) end,
30 |
31 | [{"Create new window & insert event",
32 | fun new/0},
33 | {"Create sliding window, insert, remove after 2 second",
34 | fun new_sliding/0},
35 | {"Test select from window using qlc",
36 | fun select_using_qlc/0},
37 | {"Select events where event1.param2 = event2.param2",
38 | fun select_where_op_equal/0},
39 | {"Select Count(event)",
40 | fun select_count/0},
41 | {"Select Count(event) where event1.param2 = event2.param2",
42 | fun select_count_where_op_equal/0},
43 | {"Select Sum(event1.param1) where event1.param2 = event2.param2",
44 | fun select_sum_where_op_equal/0},
45 | {"Dynamic MatchSpec and QueryHandler",
46 | fun dynamic_qh/0}]
47 | }.
48 |
49 | new() ->
50 | Mod = application:get_env(rivus_cep, rivus_window_provider, rivus_cep_window_ets),
51 | {ok, Pid} = rivus_cep_window:start_link(Mod),
52 | Window = rivus_cep_window:new(Pid, slide, 2),
53 | tick(0,0),
54 | rivus_cep_window:update(Pid, Window, <<"event1">>),
55 | rivus_cep_window:update(Pid, Window, <<"event2">>),
56 | rivus_cep_window:update(Pid, Window, <<"event3">>),
57 | tick(0,1),
58 | %%?assertEqual([<<"event1">>, <<"event2">>,<<"event3">>],rivus_cep_window:get_values(Pid, Window)).
59 | Values = rivus_cep_window:get_values(Pid, Window),
60 | ?assertEqual(3, length(Values)),
61 | ?assert(lists:member(<<"event1">>, Values)),
62 | ?assert(lists:member(<<"event2">>, Values)),
63 | ?assert(lists:member(<<"event3">>, Values)).
64 |
65 |
66 | new_sliding() ->
67 | Mod = application:get_env(rivus_cep, rivus_window_provider, rivus_cep_window_ets),
68 | {ok, Pid} = rivus_cep_window:start_link(Mod),
69 | Window = rivus_cep_window:new(Pid, slide, 2), %% 2 seconds sliding-window
70 | tick(0,0),
71 | rivus_cep_window:update(Pid, Window, <<"event1">>),
72 | rivus_cep_window:update(Pid, Window, <<"event2">>),
73 | rivus_cep_window:update(Pid, Window, <<"event3">>),
74 | %%?assertEqual([<<"event1">>, <<"event2">>,<<"event3">>],rivus_cep_window:get_values(Pid, Window)),
75 | Values = rivus_cep_window:get_values(Pid, Window),
76 | ?assertEqual(3, length(Values)),
77 | ?assert(lists:member(<<"event1">>, Values)),
78 | ?assert(lists:member(<<"event2">>, Values)),
79 | ?assert(lists:member(<<"event3">>, Values)),
80 |
81 | %%timer:sleep(4000),
82 | tick(0, 5),
83 | ?assertEqual([],rivus_cep_window:get_values(Pid, Window)).
84 |
85 |
86 | select_using_qlc() ->
87 | Mod = application:get_env(rivus_cep, rivus_window_provider, rivus_cep_window_ets),
88 | {ok, Pid} = rivus_cep_window:start_link(Mod),
89 | Window = rivus_cep_window:new(Pid, slide, 60),
90 | tick(0,0),
91 | rivus_cep_window:update(Pid, Window, {event1, a,b,c}),
92 | rivus_cep_window:update(Pid, Window, {event1, aa,b,c}),
93 | rivus_cep_window:update(Pid, Window, {event1, a,bbb,c}),
94 | rivus_cep_window:update(Pid, Window, {event2, a,bb,cc,d}),
95 | rivus_cep_window:update(Pid, Window, {event2, a,bb,cc,dd}),
96 |
97 | Size = Window#slide.window,
98 | Reservoir = Window#slide.reservoir,
99 | Oldest = tick(0,60) - Size,
100 |
101 | Ms1 = ets:fun2ms(fun({{Time,'_'},Value}) when Time >= Oldest andalso element(1,Value)==event1 -> Value end),
102 | Ms2 = ets:fun2ms(fun({{Time,'_'},Value}) when Time >= Oldest andalso element(1,Value)==event2 -> Value end),
103 |
104 | QH1 = ets:table(Reservoir, [{traverse, {select, Ms1}}]),
105 | QH2 = ets:table(Reservoir, [{traverse, {select, Ms2}}]),
106 |
107 | QH = qlc:q([ {X,Y} || X <- QH1, Y <- QH2, element(2,X) == element(2,Y)]),
108 | ResSet = lists:foldl(fun({X,Y}, Acc) -> sets:add_element(Y, sets:add_element(X,Acc)) end, sets:new(), qlc:e(QH)),
109 | Res = sets:to_list(ResSet),
110 | %%?assertEqual([ {event1, a,b,c}, {event1, a, bbb, c}, {event2, a,bb,cc,d}, {event2, a,bb,cc,dd}], Res).
111 | ?assertEqual(4, length(Res)),
112 | ?assert(lists:member({event1, a,b,c}, Res)),
113 | ?assert(lists:member({event1, a, bbb, c}, Res)),
114 | ?assert(lists:member({event2, a,bb,cc,d}, Res)),
115 | ?assert(lists:member({event2, a,bb,cc,dd}, Res)).
116 |
117 |
118 | select_where_op_equal() ->
119 | Mod = application:get_env(rivus_cep, rivus_window_provider, rivus_cep_window_ets),
120 | {ok, Pid} = rivus_cep_window:start_link(Mod),
121 | Window = rivus_cep_window:new(Pid, slide, 60),
122 | rivus_cep_window:update(Pid, Window, {event1, a,b,c}),
123 | rivus_cep_window:update(Pid, Window, {event1, aa,b,c}),
124 | rivus_cep_window:update(Pid, Window, {event1, a,bbb,c}),
125 | rivus_cep_window:update(Window, {event2, a,bb,cc,d}),
126 | rivus_cep_window:update(Window, {event2, a,bb,cc,dd}),
127 |
128 | {Reservoir, Oldest} = rivus_cep_window:get_window(Pid, Window),
129 |
130 | Ms1 = ets:fun2ms(fun({{Time,'_'},Value}) when Time >= Oldest andalso element(1,Value)==event1 -> Value end),
131 | Ms2 = ets:fun2ms(fun({{Time,'_'},Value}) when Time >= Oldest andalso element(1,Value)==event2 -> Value end),
132 |
133 | QH1 = ets:table(Reservoir, [{traverse, {select, Ms1}}]),
134 | QH2 = ets:table(Reservoir, [{traverse, {select, Ms2}}]),
135 |
136 | QH = qlc:q([ {X,Y} || X <- QH1, Y <- QH2, element(2,X) == element(2,Y)]),
137 | ResSet = lists:foldl(fun({X,Y}, Acc) -> sets:add_element(Y, sets:add_element(X,Acc)) end, sets:new(), qlc:e(QH)),
138 | Res = sets:to_list(ResSet),
139 | %%?assertEqual([ {event1, a,b,c}, {event1, a, bbb, c}, {event2, a,bb,cc,d}, {event2, a,bb,cc,dd}], Res).
140 | ?assertEqual(4, length(Res)),
141 | ?assert(lists:member({event1, a,b,c}, Res)),
142 | ?assert(lists:member({event1, a, bbb, c}, Res)),
143 | ?assert(lists:member({event2, a,bb,cc,d}, Res)),
144 | ?assert(lists:member({event2, a,bb,cc,dd}, Res)).
145 |
146 | select_count() ->
147 | Mod = application:get_env(rivus_cep, rivus_window_provider, rivus_cep_window_ets),
148 | {ok, Pid} = rivus_cep_window:start_link(Mod),
149 | Window = rivus_cep_window:new(Pid, slide, 60),
150 | rivus_cep_window:update(Pid, Window, {event1, a,b,c}),
151 | rivus_cep_window:update(Pid, Window, {event1, aa,b,c}),
152 | rivus_cep_window:update(Pid, Window, {event1, a,bbb,c}),
153 | rivus_cep_window:update(Pid, Window, {event2, a,bb,cc,d}),
154 | rivus_cep_window:update(Pid, Window, {event2, a,bb,cc,dd}),
155 |
156 | {Reservoir, Oldest} = rivus_cep_window:get_window(Pid, Window),
157 |
158 | Ms1 = ets:fun2ms(fun({{Time,'_'},Value}) when Time >= Oldest, (element(1,Value)==event1 orelse element(1,Value)==event2) -> Value end),
159 | QH1 = ets:table(Reservoir, [{traverse, {select, Ms1}}]),
160 | QH = qlc:q([ X || X <- QH1]),
161 | Count = length(qlc:e(QH)),
162 | %%?assertEqual(5, lists:foldl(fun(_, Count) -> Count + 1 end, 0, qlc:e(QH)) ).
163 | ?assertEqual(5, Count ).
164 |
165 | select_count_where_op_equal() ->
166 | Mod = application:get_env(rivus_cep, rivus_window_provider, rivus_cep_window_ets),
167 | {ok, Pid} = rivus_cep_window:start_link(Mod),
168 | Window = rivus_cep_window:new(Pid, slide, 60),
169 | rivus_cep_window:update(Pid, Window, {event1, a,b,c}),
170 | rivus_cep_window:update(Pid, Window, {event1, aa,b,c}),
171 | rivus_cep_window:update(Pid, Window, {event1, a,bbb,c}),
172 | rivus_cep_window:update(Pid, Window, {event2, a,bb,cc,d}),
173 | rivus_cep_window:update(Pid, Window, {event2, a,bb,cc,dd}),
174 |
175 | %% WITHIN clause
176 | {Reservoir, Oldest} = rivus_cep_window:get_window(Pid, Window),
177 | Ms1 = ets:fun2ms(fun({{Time,'_'},Value}) when Time >= Oldest andalso element(1,Value)==event1 -> Value end),
178 | Ms2 = ets:fun2ms(fun({{Time,'_'},Value}) when Time >= Oldest andalso element(1,Value)==event2 -> Value end),
179 |
180 | %% FROM clause
181 | QH1 = ets:table(Reservoir, [{traverse, {select, Ms1}}]),
182 | QH2 = ets:table(Reservoir, [{traverse, {select, Ms2}}]),
183 |
184 | %% WHERE clause
185 | QH = qlc:q([ {X,Y} || X <- QH1, Y <- QH2, element(2,X) == element(2,Y)]),
186 |
187 | %% SELECT clause
188 | ResSet = lists:foldl(fun({X,Y}, Acc) -> sets:add_element(Y, sets:add_element(X,Acc)) end, sets:new(), qlc:e(QH)),
189 | Count = sets:size(ResSet),
190 |
191 | ?assertEqual(4, Count ).
192 |
193 |
194 | select_sum_where_op_equal() ->
195 | Mod = application:get_env(rivus_cep, rivus_window_provider, rivus_cep_window_ets),
196 | {ok, Pid} = rivus_cep_window:start_link(Mod),
197 | Window = rivus_cep_window:new(Pid, slide, 60),
198 | rivus_cep_window:update(Pid, Window, {event1, 10,b,c}), % *
199 | rivus_cep_window:update(Pid, Window, {event1, 15,bbb,c}),
200 | rivus_cep_window:update(Pid, Window, {event1, 20,b,c}), % *
201 | rivus_cep_window:update(Pid, Window, {event2, 30,b,cc,d}),
202 | rivus_cep_window:update(Pid, Window, {event2, 40,bb,cc,dd}),
203 |
204 | %% WITHIN clause
205 | {Reservoir, Oldest} = rivus_cep_window:get_window(Pid, Window),
206 | Ms1 = ets:fun2ms(fun({{Time,'_'},Value}) when Time >= Oldest andalso element(1,Value)==event1 -> Value end),
207 | Ms2 = ets:fun2ms(fun({{Time,'_'},Value}) when Time >= Oldest andalso element(1,Value)==event2 -> Value end),
208 |
209 | %% FROM clause
210 | QH1 = ets:table(Reservoir, [{traverse, {select, Ms1}}]),
211 | QH2 = ets:table(Reservoir, [{traverse, {select, Ms2}}]),
212 |
213 | %% WHERE clause
214 | QH = qlc:q([ X || X <- QH1, Y <- QH2, element(3,X) == element(3,Y)]),
215 |
216 | %% SELECT clause
217 | ResSet = lists:foldl(fun(X, Acc) -> sets:add_element(X,Acc) end, sets:new(), qlc:e(QH)),
218 | Sum = lists:foldl(fun(E, Acc) -> element(2, E) + Acc end, 0, sets:to_list(ResSet)),
219 |
220 | ?assertEqual(30, Sum ).
221 |
222 | create_match_spec(Event, Oldest) ->
223 | ets:fun2ms(fun({ {Time,'_'},Value}) when Time >= Oldest andalso element(1,Value)==Event -> Value end).
224 |
225 |
226 | create_from_qh(MatchSpec, Reservoir) ->
227 | ets:table(Reservoir, [{traverse, {select, MatchSpec}}]).
228 |
229 | dynamic_qh() ->
230 | Mod = application:get_env(rivus_cep, rivus_window_provider, rivus_cep_window_ets),
231 | {ok, Pid} = rivus_cep_window:start_link(Mod),
232 | Window = rivus_cep_window:new(Pid, slide, 60),
233 | rivus_cep_window:update(Pid, Window, {event1, 10,b,c}), % *
234 | rivus_cep_window:update(Pid, Window, {event1, 15,bbb,c}),
235 | rivus_cep_window:update(Pid, Window, {event1, 20,b,c}), % *
236 | rivus_cep_window:update(Pid, Window, {event2, 30,b,cc,d}),
237 | rivus_cep_window:update(Pid, Window, {event2, 40,bb,cc,dd}),
238 |
239 | %% WITHIN clause
240 | {Reservoir, Oldest} = rivus_cep_window:get_window(Pid, Window),
241 |
242 | MatchSpecs =
243 | [
244 | create_match_spec(Event, Oldest) ||
245 | Event <- [event1, event2]
246 | ],
247 | FromQueryHandlers =
248 | [
249 | create_from_qh(MS, Reservoir) ||
250 | MS <- MatchSpecs
251 | ],
252 | QH =
253 | qlc:q([
254 | E1 ||
255 | E1 <- hd(FromQueryHandlers),
256 | E2 <- hd(tl(FromQueryHandlers)),
257 | element(3,E1) == element(3,E2)
258 | ]),
259 |
260 | %% SELECT clause
261 | ResSet = lists:foldl(fun(X, Acc) -> sets:add_element(X,Acc) end, sets:new(), qlc:e(QH)),
262 | Sum = lists:foldl(fun(E, Acc) -> element(2, E) + Acc end, 0, sets:to_list(ResSet)),
263 |
264 | ?assertEqual(30, Sum ).
265 |
266 | tick(Moment0, IncrBy) ->
267 | Moment = Moment0 + IncrBy,
268 | meck:expect(folsom_utils, now_epoch, fun() ->
269 | Moment end),
270 | Moment.
271 |
272 | tick(Moment) ->
273 | tick(Moment, 1).
274 |
--------------------------------------------------------------------------------