├── .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 | [![Build Status](https://travis-ci.org/vascokk/rivus_cep.png)](https://travis-ci.org/vascokk/rivus_cep) [![Join the chat at https://gitter.im/vascokk/rivus_cep](https://badges.gitter.im/Join%20Chat.svg)](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 | ![Benchmark result](https://cloud.githubusercontent.com/assets/172945/4927648/6176fd88-653d-11e4-9d2b-ddfeb6254654.png) 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 | --------------------------------------------------------------------------------