From 93cc91462171ee74577e76fb30a16a49f85ba865 Mon Sep 17 00:00:00 2001 From: keynslug Date: Mon, 24 Oct 2011 18:43:57 +0400 Subject: [PATCH] first commit --- LICENSE | 177 +++++++++++++++ Makefile | 21 ++ README | 2 + rebar | Bin 0 -> 105392 bytes rebar.config | 12 + src/reloader.erl | 161 +++++++++++++ src/websinema.app.src | 23 ++ src/websinema.erl | 58 +++++ src/websinema_aggregator.erl | 426 +++++++++++++++++++++++++++++++++++ src/websinema_app.erl | 30 +++ src/websinema_bindings.erl | 129 +++++++++++ src/websinema_endpoint.erl | 50 ++++ src/websinema_frontend.erl | 122 ++++++++++ src/websinema_observer.erl | 174 ++++++++++++++ src/websinema_sup.erl | 55 +++++ src/websinema_utilities.erl | 306 +++++++++++++++++++++++++ src/websinema_ws.erl | 231 +++++++++++++++++++ start-app.sh | 5 + 18 files changed, 1982 insertions(+) create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README create mode 100755 rebar create mode 100644 rebar.config create mode 100644 src/reloader.erl create mode 100644 src/websinema.app.src create mode 100644 src/websinema.erl create mode 100644 src/websinema_aggregator.erl create mode 100644 src/websinema_app.erl create mode 100644 src/websinema_bindings.erl create mode 100644 src/websinema_endpoint.erl create mode 100644 src/websinema_frontend.erl create mode 100644 src/websinema_observer.erl create mode 100644 src/websinema_sup.erl create mode 100644 src/websinema_utilities.erl create mode 100644 src/websinema_ws.erl create mode 100755 start-app.sh diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f433b1a --- /dev/null +++ b/LICENSE @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7dc68ef --- /dev/null +++ b/Makefile @@ -0,0 +1,21 @@ +REBAR=./rebar + +.PHONY: all compile get-deps clean distclean config + +all: clean compile + +compile: get-deps + ${REBAR} compile + +get-deps: + ${REBAR} get-deps + +clean: + ${REBAR} clean + rm -rfv erl_crash.dump + +distclean: clean + rm -rfv ebin deps + +config: + cp -fv default.config.example default.config diff --git a/README b/README new file mode 100644 index 0000000..269a49b --- /dev/null +++ b/README @@ -0,0 +1,2 @@ +Websinema - A simple SNMP metrics realtime visualizer. + diff --git a/rebar b/rebar new file mode 100755 index 0000000000000000000000000000000000000000..a715f1fd5cb5cb515f0ad0dc3213d7aec05e5042 GIT binary patch literal 105392 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 T@Y9K+-hh8!!o$HNh$!lRCqH~~ literal 0 HcmV?d00001 diff --git a/rebar.config b/rebar.config new file mode 100644 index 0000000..37f714e --- /dev/null +++ b/rebar.config @@ -0,0 +1,12 @@ +%% Rebar configuration directives. + +{cover_enabled, false}. +{erl_opts, [{parse_transform, lager_transform}]}. + +{deps, [ + {cowboy, ".*", {git, "git://github.com/extend/cowboy.git", "master"}}, + {lager, ".*", {git, "git://github.com/basho/lager.git", "master"}}, + {json, ".*", {git, "git://github.com/davisp/eep0018.git", "master"}} +]}. + +{lib_dirs, ["deps"]}. diff --git a/src/reloader.erl b/src/reloader.erl new file mode 100644 index 0000000..c0f5de8 --- /dev/null +++ b/src/reloader.erl @@ -0,0 +1,161 @@ +%% @copyright 2007 Mochi Media, Inc. +%% @author Matthew Dempsky +%% +%% @doc Erlang module for automatically reloading modified modules +%% during development. + +-module(reloader). +-author("Matthew Dempsky "). + +-include_lib("kernel/include/file.hrl"). + +-behaviour(gen_server). +-export([start/0, start_link/0]). +-export([stop/0]). +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). +-export([all_changed/0]). +-export([is_changed/1]). +-export([reload_modules/1]). +-record(state, {last, tref}). + +%% External API + +%% @spec start() -> ServerRet +%% @doc Start the reloader. +start() -> + gen_server:start({local, ?MODULE}, ?MODULE, [], []). + +%% @spec start_link() -> ServerRet +%% @doc Start the reloader. +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). + +%% @spec stop() -> ok +%% @doc Stop the reloader. +stop() -> + gen_server:call(?MODULE, stop). + +%% gen_server callbacks + +%% @spec init([]) -> {ok, State} +%% @doc gen_server init, opens the server in an initial state. +init([]) -> + {ok, TRef} = timer:send_interval(timer:seconds(1), doit), + {ok, #state{last = stamp(), tref = TRef}}. + +%% @spec handle_call(Args, From, State) -> tuple() +%% @doc gen_server callback. +handle_call(stop, _From, State) -> + {stop, shutdown, stopped, State}; +handle_call(_Req, _From, State) -> + {reply, {error, badrequest}, State}. + +%% @spec handle_cast(Cast, State) -> tuple() +%% @doc gen_server callback. +handle_cast(_Req, State) -> + {noreply, State}. + +%% @spec handle_info(Info, State) -> tuple() +%% @doc gen_server callback. +handle_info(doit, State) -> + Now = stamp(), + doit(State#state.last, Now), + {noreply, State#state{last = Now}}; +handle_info(_Info, State) -> + {noreply, State}. + +%% @spec terminate(Reason, State) -> ok +%% @doc gen_server termination callback. +terminate(_Reason, State) -> + {ok, cancel} = timer:cancel(State#state.tref), + ok. + + +%% @spec code_change(_OldVsn, State, _Extra) -> State +%% @doc gen_server code_change callback (trivial). +code_change(_Vsn, State, _Extra) -> + {ok, State}. + +%% @spec reload_modules([atom()]) -> [{module, atom()} | {error, term()}] +%% @doc code:purge/1 and code:load_file/1 the given list of modules in order, +%% return the results of code:load_file/1. +reload_modules(Modules) -> + [begin code:purge(M), code:load_file(M) end || M <- Modules]. + +%% @spec all_changed() -> [atom()] +%% @doc Return a list of beam modules that have changed. +all_changed() -> + [M || {M, Fn} <- code:all_loaded(), is_list(Fn), is_changed(M)]. + +%% @spec is_changed(atom()) -> boolean() +%% @doc true if the loaded module is a beam with a vsn attribute +%% and does not match the on-disk beam file, returns false otherwise. +is_changed(M) -> + try + module_vsn(M:module_info()) =/= module_vsn(code:get_object_code(M)) + catch _:_ -> + false + end. + +%% Internal API + +module_vsn({M, Beam, _Fn}) -> + {ok, {M, Vsn}} = beam_lib:version(Beam), + Vsn; +module_vsn(L) when is_list(L) -> + {_, Attrs} = lists:keyfind(attributes, 1, L), + {_, Vsn} = lists:keyfind(vsn, 1, Attrs), + Vsn. + +doit(From, To) -> + [case file:read_file_info(Filename) of + {ok, #file_info{mtime = Mtime}} when Mtime >= From, Mtime < To -> + reload(Module); + {ok, _} -> + unmodified; + {error, enoent} -> + %% The Erlang compiler deletes existing .beam files if + %% recompiling fails. Maybe it's worth spitting out a + %% warning here, but I'd want to limit it to just once. + gone; + {error, Reason} -> + io:format("Error reading ~s's file info: ~p~n", + [Filename, Reason]), + error + end || {Module, Filename} <- code:all_loaded(), is_list(Filename)]. + +reload(Module) -> + io:format("Reloading ~p ...", [Module]), + code:purge(Module), + case code:load_file(Module) of + {module, Module} -> + io:format(" ok.~n"), + case erlang:function_exported(Module, test, 0) of + true -> + io:format(" - Calling ~p:test() ...", [Module]), + case catch Module:test() of + ok -> + io:format(" ok.~n"), + reload; + Reason -> + io:format(" fail: ~p.~n", [Reason]), + reload_but_test_failed + end; + false -> + reload + end; + {error, Reason} -> + io:format(" fail: ~p.~n", [Reason]), + error + end. + + +stamp() -> + erlang:localtime(). + +%% +%% Tests +%% +-include_lib("eunit/include/eunit.hrl"). +-ifdef(TEST). +-endif. diff --git a/src/websinema.app.src b/src/websinema.app.src new file mode 100644 index 0000000..a7d3551 --- /dev/null +++ b/src/websinema.app.src @@ -0,0 +1,23 @@ +{application, websinema, [ + + {description, "WebSiNeMa - An SNMP metrics visualization web frontend"}, + {mod, {websinema_app, []}}, + {registered, [websinema]}, + {vsn, "0.0"}, + + {applications, [ + kernel, + stdlib, + sasl, + lager, + inets, + crypto, + public_key, + ssl, + cowboy, + snmp + ]}, + + {env, []} + +]}. diff --git a/src/websinema.erl b/src/websinema.erl new file mode 100644 index 0000000..27b64b4 --- /dev/null +++ b/src/websinema.erl @@ -0,0 +1,58 @@ +%% Copyright (c) 2011 Drimmi, Inc. +%% All rights reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. + +-module(websinema). + +-include_lib("kernel/include/file.hrl"). + +-export([start/0, stop/0]). +-export([priv/0, priv/1, option/1, option/2]). + +start() -> + application:load(?MODULE), + ensure_deps_started(), + application:start(?MODULE). + +stop() -> + application:stop(?MODULE). + +priv() -> + case code:priv_dir(?MODULE) of + {error, bad_name} -> + {ok, #file_info{type=directory}} = file:read_file_info([priv]), + "./priv/"; + Value -> + Value ++ "/" + end. + +priv([$/ | Rest]) -> + priv() ++ Rest; + +priv(Sub) -> + priv() ++ Sub. + +option(Path) -> + option(Path, undefined). + +option(Path, Default) when is_list(Path) -> + websinema_utilities:propget(Path, application:get_all_env(), Default); + +option(Atom, Default) -> + option([Atom], Default). + +ensure_deps_started() -> + {ok, DepsList} = application:get_key(?MODULE, applications), + [application:start(App) || App <- DepsList], + ok. diff --git a/src/websinema_aggregator.erl b/src/websinema_aggregator.erl new file mode 100644 index 0000000..b995b52 --- /dev/null +++ b/src/websinema_aggregator.erl @@ -0,0 +1,426 @@ +%% Copyright (c) 2011 Drimmi, Inc. +%% All rights reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. + +-module(websinema_aggregator). +-behaviour(gen_server). + +%% Exports + +-export([ + start_link/1, + start_link/2, + stop/1, + stop/2, + + agents/1, + enlist/3, + dismiss/2, + discover/2, + discover/3, + examine/3, + examine/4, + examine/5 +]). + +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). + +-record(state, { + user_id, + agents, + options +}). + +%% Public API + +-define(DEFAULT_PRESENTATION, [{tree, preserve}, aliases, {ignore, [[snmpModules]]}]). + +start_link(Options) -> + gen_server:start_link(?MODULE, Options, []). + +start_link(Name, Options) -> + gen_server:start_link(Name, ?MODULE, Options, []). + +stop(Name) -> + stop(Name, shutdown). + +stop(Name, Reason) -> + gen_server:call(Name, {shutdown, Reason}, infinity). + +agents(Name) -> + async(Name, get_agents_list). + +enlist(Name, AgentName, AgentOptions) -> + sync(Name, {enlist_agent, {AgentName, AgentOptions}}). + +dismiss(Name, AgentName) -> + sync(Name, {dismiss_agent, AgentName}). + +discover(Name, AgentName) -> + discover(Name, AgentName, ?DEFAULT_PRESENTATION). + +discover(Name, AgentName, Options) -> + async(Name, {discover_agent, AgentName, Options}). + +examine(Name, View, Scope) -> + examine(Name, View, Scope, average). + +examine(Name, View, Scope, Aggregate) -> + examine(Name, View, Scope, Aggregate, ?DEFAULT_PRESENTATION). + +examine(Name, {agent, AgentName}, Scope, Aggregate, Options) -> + examine(Name, [{AgentName, [[]]}], Scope, Aggregate, Options); + +examine(Name, {object, AgentName, ID}, Scope, Aggregate, Options) -> + examine(Name, [{AgentName, [ID]}], Scope, Aggregate, Options); + +examine(Name, View, Scope, Aggregate, Options) -> + async(Name, {examine_view, View, Scope, Aggregate, Options}). + +%% Definitions + +-define(DEFAULT_DIR, "manager"). +-define(DEFAULT_CONFIG, [{address, [0, 0, 0, 0]}, {port, 5000}, {engine_id, "mgrEngine"}, {max_message_size, 484}]). +-define(DEFAULT_OPTIONS(Dir, V), [ + {mibs, []}, + {versions, [v1, v2]}, + {server, [{verbosity, V}]}, + {config, [{dir, Dir}, {db_dir, Dir}, {verbosity, V}]}, + {observation, [ + {interval, 1000}, + {backlog, 600}, + {rules, [ + {integer, {watch, buffer}}, + {ticks, {watch, actual}}, + {timestamp, {watch, actual}} + ]} + ]} +]). + +-define(MIBS, [ + {any, ["SNMP-NOTIFICATION-MIB", "SNMP-COMMUNITY-MIB"]}, + {v1, ["STANDARD-MIB"]}, + {v2, ["SNMPv2-MIB", "SNMPv2-TM", "SNMP-VIEW-BASED-ACM-MIB"]}, + {v3, ["SNMP-USM-AES-MIB"]} +]). + +%% Callbacks + +init(Options) -> + lager:info("Initializing websinema aggregator..."), + process_flag(trap_exit, true), + + Sites = [manager, mibs, user, agents], + Starter = fun + (Site, {ok, L}) -> + try start(Site, Options, L) of + ok -> {ok, L}; + {ok, N} -> {ok, L ++ N}; + Error -> throw(Error) + catch + _:Reason -> + lager:error("Error starting ~p because of ~p", [Site, Reason]), + {stop, {init_failed, Site}} + end; + (_, Error) -> Error + end, + Result = lists:foldl(Starter, {ok, []}, Sites), + construct_state(Result). + +terminate(Reason, #state{agents = Agents, user_id = User}) -> + lager:info("Terminating with reason ~p", [Reason]), + [ finish(agent, A, User) || {A, _} <- Agents ], + finish(user, User), + ok. + +code_change(_WasVersion, State, _Extra) -> + {ok, State}. + +handle_call({shutdown, Reason}, _From, State) -> + {stop, Reason, ok, State}; + +handle_call({sync, Request}, _, State) -> + {Reply, NewState} = handle_sync_request(Request, State), + {reply, Reply, NewState}; + +handle_call({async, Request}, From, State) -> + spawn(fun () -> Reply = handle_async_request(Request, State), gen_server:reply(From, Reply) end), + {noreply, State}; + +handle_call(Request, From, State) -> + lager:error("Unexpected call ~p received from ~p", [Request, From]), + {noreply, State}. + +handle_cast(Request, State) -> + lager:error("Unexpected cast ~p received", [Request]), + {noreply, State}. + +handle_info({'EXIT', Observer, Error}, State = #state{agents = InitialAgents}) -> + Agents = lists:map( + fun (E = {Name, Props}) -> + Observers = websinema_utilities:propget([observers], Props), + case lists:keyfind(Observer, 2, Observers) of + false -> E; + {Oid, Observer} -> + lager:error("Observer on ~p::~p exited with error ~p", [Name, websinema_bindings:varalias(Oid), Error]), + NewProps = websinema_utilities:propset([observers, Oid], not_accessible, Props), + {Name, NewProps} + end + end, + InitialAgents), + {noreply, State#state{agents = Agents}}; + +handle_info(Message, State) -> + lager:error("Unexpected message received ~p", [Message]), + {noreply, State}. + +%% Initialization + +start(manager, Options, _) -> + Verbose = proplists:get_value(verbose, Options, silence), + ManagerDir = websinema:priv(proplists:get_value(manager_dir, Options, ?DEFAULT_DIR)), + InitialConfig = proplists:get_value(manager_config, Options), + InitialOptions = proplists:get_value(manager_options, Options, []), + + ManagerOptions = websinema_utilities:propdefaults(?DEFAULT_OPTIONS(ManagerDir, Verbose), InitialOptions), + ManagerConfig = websinema_utilities:propdefaults(?DEFAULT_CONFIG, InitialConfig), + + ok = snmpm_conf:write_manager_config(ManagerDir, ManagerConfig), + ok = snmpm:start_link(ManagerOptions), + {ok, [{options, ManagerOptions}]}; + +start(mibs, Options, State) -> + Fixed = proplists:get_value(mibs, Options, []), + Versions = proplists:get_value(versions, proplists:get_value(options, State), []), + Relevant = [ E || {any, L} <- ?MIBS, E <- L ] ++ [ E || {V, L} <- ?MIBS, E <- L, V0 <- Versions, V =:= V0 ], + Files = expand_mib_filenames(local, Fixed) ++ expand_mib_filenames(global, Relevant), + Result = [ {E, snmpm:load_mib(E)} || E <- Files ], + Loaded = [ E || {E, ok} <- Result ], + [ lager:error("Mib binary ~p failed to load properly because of ~p", [E, Reason]) || {E, {error, Reason}} <- Result ], + {ok, [{mibs, Loaded}]}; + +start(user, Options, _) -> + UserID = proplists:get_value(user_id, Options, ?MODULE), + Endpoint = proplists:get_value(endpoint, Options, websinema_endpoint), + ok = snmpm:register_user_monitor(UserID, Endpoint, self()), + {ok, [{user_id, UserID}]}; + +start(agents, Options, State) -> + UserID = proplists:get_value(user_id, Options, ?MODULE), + Agents = proplists:get_value(agents, Options, []), + Ready = lists:foldl( + fun (Agent = {Name, Opts}, Ready) -> + case catch start(agent, {Agent, UserID}, State) of + {ok, Discovery, Observers} -> + Props = {Name, [ {remote, Opts}, {bindings, Discovery}, {observers, Observers} ]}, + [Props | Ready]; + Error -> + lager:error("The agent ~p has not been registered because of ~p", [Agent, Error]) + end + end, + [], Agents + ), + {ok, [{agents, Ready}]}; + +start(agent, {Agent, User}, State) -> + {Name, Opts} = Agent, + ok = snmpm:register_agent(User, Name, config(agent, Name, Opts)), + case websinema_bindings:discover(User, Name) of + {ok, Bindings} -> + Observers = start(observers, {Name, User, Bindings}, State), + {ok, Bindings, Observers}; + Error -> + throw(Error) + end; + +start(observers, {Name, User, Bindings}, State) -> + ObserveOptions = [ + {agent, Name}, {user, User} | + websinema_utilities:propget([options, observation], State) + ], + [ {Oid, websinema_observer:start_link(Oid, Value, ObserveOptions)} || {Oid, Value} <- Bindings ]. + +finish(agent, Name, User) -> + catch snmpm:unregister_agent(User, Name). + +finish(user, User) -> + catch snmpm:unregister_user(User). + +%% Calls dispatching + +sync(Name, Request) -> gen_server:call(Name, {sync, Request}). +async(Name, Request) -> gen_server:call(Name, {async, Request}). + +handle_sync_request(Request, State) -> + try request(Request, State) of + {ok, Result, NewState} -> {Result, NewState}; + Error = {error, _} -> {Error, State}; + AnotherError -> {{error, AnotherError}, State} + catch _:Reason -> {{error, Reason}, State} + end. + +handle_async_request(Request, State) -> + try request(Request, State) of + {ok, Result} -> Result; + Error -> {error, Error} + catch _:Reason -> {error, Reason} + end. + +%% Operations + +request(get_agents_list, #state{agents = Agents}) -> + {ok, [ Name || {Name, _} <- Agents ]}; + +request({has_agent, Name}, #state{agents = Agents}) -> + {ok, proplists:is_defined(Name, Agents)}; + +request({enlist_agent, Agent = {Name, Remote}}, State = #state{agents = Agents, user_id = User}) -> + assert_no_agent(Name, Agents), + case start(agent, {Agent, User}, none) of + ok -> + NewAgents = websinema_utilities:propset([Name, endpoint], Remote, Agents), + {ok, ok, State#state{agents = NewAgents}}; + Error -> + Error + end; + +request({dismiss_agent, Name}, State = #state{agents = Agents, user_id = User}) -> + assert_agent(Name, Agents), + Result = finish(agent, Name, User), + NewAgents = proplists:delete(Name, Agents), + {ok, Result, State#state{agents = NewAgents}}; + +request({discover_agent, Name, Options}, #state{agents = Agents}) -> + Agent = assert_agent(Name, Agents), + Bindings = proplists:get_value(bindings, Agent, undefined), + {ok, {ok, xform_view(Bindings, Options)}}; + +request({examine_view, View, Scope, Aggregate, Options}, #state{agents = Agents}) -> + Ignored = websinema_utilities:propget([ignore], Options, []), + Expanded = expand_view(View, Ignored, Agents, []), + Examiner = fun ({Name, Ids}) -> + Observers = websinema_utilities:propget([Name, observers], Agents), + Bindings = websinema_utilities:propget([Name, bindings], Agents), + Values = lists:map(fun (Id) -> {Id, examine_one(Id, Scope, Aggregate, Bindings, Observers)} end, Ids), + {Name, xform_view(Values, Options)} + end, + {ok, {ok, lists:map(Examiner, Expanded)}}; + +%% Debug purpose + +request(state, State) -> + {ok, State}. + +%% Utilities + +config(agent, Name, {Target, Port}) -> + config(agent, Name, {Target, Port, "public"}); + +config(agent, Name, {Target, Port, Community}) -> + [{engine_id, Name}, {version, v2}, {address, Target}, {port, Port}, {community, Community}]. + +construct_state({ok, Options}) -> + {ok, #state{ + agents = proplists:get_value(agents, Options, []), + user_id = proplists:get_value(user_id, Options) + }}; + +construct_state(Another) -> + Another. + +assert_no_agent(Name, Agents) -> + [ throw({agent_exists, Name}) || proplists:is_defined(Name, Agents) ]. + +assert_agent(Name, Agents) -> + case proplists:lookup(Name, Agents) of + none -> throw({no_agent, Name}); + {_, Entry} -> Entry + end. + +xform_view(List, Options) -> + OrderedOpts = [aliases, ignore, tree], + lists:foldl(fun (Option, L) -> do_xform_view(proplists:lookup(Option, Options), L) end, List, OrderedOpts). + +do_xform_view({tree, true}, List) -> + websinema_utilities:prefix_tree(List); + +do_xform_view({tree, preserve}, List) -> + websinema_utilities:prefix_tree(List, [preserve]); + +do_xform_view({aliases, true}, List) -> + websinema_bindings:varaliases(List); + +do_xform_view({ignore, Ignored}, List) -> + Filter = fun (Ignore, L) -> + FilterOut = websinema_utilities:with_prefix(Ignore, List), + lists:filter(fun ({Key, _}) -> not proplists:is_defined(Key, FilterOut) end, L) + end, + lists:foldl(Filter, List, Ignored); + +do_xform_view(_, List) -> + List. + +expand_view([{any, Ids} | Rest], Ignored, Agents, Acc) -> + Expanded = [ {N, Ids} || {N, _} <- Agents ], + expand_view(Expanded ++ Rest, Ignored, Agents, Acc); + +expand_view([{Name, Ids} | Rest], Ignored, Agents, Acc) -> + Agent = assert_agent(Name, Agents), + Bindings = assert_bindings(Name, Agent), + expand_view(Rest, Ignored, Agents, [{Name, expand_ids(Ids, Bindings, Ignored)} | Acc]); + +expand_view([], _, _, Acc) -> + Acc. + +expand_ids(Ids, Bindings, Ignored) -> + IgnoreList = do_expand_ids(Ignored, Bindings), + [ Id || Id <- do_expand_ids(Ids, Bindings), not lists:member(Id, IgnoreList) ]. + +do_expand_ids([[]], Bindings) -> + [ Id || {Id, _} <- Bindings ]; + +do_expand_ids(Ids, Bindings) -> + Expanded = [ Id || Prefix <- Ids, {Id, _} <- + begin + Oid = websinema_bindings:varoid(Prefix), + With = websinema_utilities:with_prefix(Oid, Bindings), + case With of + [] -> throw({no_objects, Prefix}); + _ -> With + end + end + ], + lists:usort(Expanded). + +assert_bindings(Name, Agent) -> + case proplists:get_value(bindings, Agent) of + undefined -> throw({no_agent_discovery, Name}); + Bindings -> Bindings + end. + +expand_mib_filenames(Where, Mibs) -> + Path = expand_mib_path(Where), + [ Path ++ "/mibs/" ++ E ++ ".bin" || E <- Mibs ]. + +expand_mib_path(local) -> websinema:priv(); +expand_mib_path(global) -> code:priv_dir(snmp). + +examine_one(Id, Scope, Aggregation, Bindings, Observers) -> + case websinema_utilities:propget([Id], Observers) of + Pid when is_pid(Pid) -> + {Type, _} = websinema_utilities:propget([Id], Bindings), + {value, Type, websinema_observer:metrics(Pid, Scope, Aggregation)}; + Error -> + Error + end. diff --git a/src/websinema_app.erl b/src/websinema_app.erl new file mode 100644 index 0000000..9062a08 --- /dev/null +++ b/src/websinema_app.erl @@ -0,0 +1,30 @@ +%% Copyright (c) 2011 Drimmi, Inc. +%% All rights reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. + +-module(websinema_app). +-behaviour(application). + +-export([start/2, stop/1]). +-export([start/0]). + +start() -> + application:start(websinema). + +start(_StartType, _StartArgs) -> + Supervisor = websinema_sup, + supervisor:start_link({local, Supervisor}, Supervisor, []). + +stop(_) -> + ok. diff --git a/src/websinema_bindings.erl b/src/websinema_bindings.erl new file mode 100644 index 0000000..e223d07 --- /dev/null +++ b/src/websinema_bindings.erl @@ -0,0 +1,129 @@ +%% Copyright (c) 2011 Drimmi, Inc. +%% All rights reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. + +-module(websinema_bindings). + +%% Exports + +-export([discover/2, examine/3, varalias/1, varaliases/1, varname/1, varoid/1]). + +%% Public API + +-define(NEXT_ANY_OID, [0, 0, 0]). +-define(SUBTREE_TID, 'OBJECT IDENTIFIER'). + +discover(User, Agent) -> + discover(User, Agent, []). + +discover(User, Agent, Options) -> + try + {ok, discover({User, Agent}, ?NEXT_ANY_OID, [], Options)} + catch + _:Reason -> {error, {discovery_failed, Reason}} + end. + +examine(User, Agent, Oids) -> + case snmpm:sync_get(User, Agent, Oids) of + {ok, Pdu, _} -> objects(Pdu); + {error, Error} -> throw(Error) + end. + +varaliases(List) -> + [ {varalias(ID), Value} || {ID, Value} <- List ]. + +varalias(ID = [I|_]) when is_integer(I) -> + List = lists:zip(lists:seq(1, length(ID)), ID), + PrefixSet = [ [ E1 || {N1, E1} <- List, N1 =< N0 ] || {N0, _} <- List ], + case varalias(PrefixSet, {[], []}) of + {Got, [0]} -> Got; + {Got, Rest} -> Got ++ Rest + end; + +varalias(Invalid) -> + throw({invalid_oid, Invalid}). + +varoid(L = [H | _]) when is_integer(H) -> + L; + +varoid(L = [H | _]) when is_atom(H) -> + case snmpm:name_to_oid(lists:last(L)) of + {ok, [Id]} -> Id; + _Error -> L + end; + +varoid(Invalid) -> + throw({invalid_name, Invalid}). + +varname(List) when is_list(List) -> + string:join([ utilities:to_list(E) || E <- List ], "."). + +%% Internals + +discover(A = {User, Agent}, OID, Tree, Options) -> + case snmpm:sync_get_next(User, Agent, [OID]) of + {ok, Pdu, _} -> + case object(Pdu) of + {NextOID, ignore} -> + discover(A, NextOID, Tree, Options); + {NextOID, Object} -> + discover(A, NextOID, [{NextOID, Object} | Tree], Options); + _ -> + Tree + end; + Error -> throw(Error) + end. + +objects({noError, _, Objects}) when is_list(Objects) -> + [ Object || Object = {_, Value} <- [ object(O) || O <- Objects ], Value =/= ignore ]; +objects(Error) -> + throw(Error). + +object({noError, _, [Object]}) -> + object(Object); +object({varbind, OID, _, noSuchObject, _}) -> + throw({no_object, varalias(OID)}); +object({varbind, OID, _, noSuchInstance, _}) -> + throw({no_instance, varalias(OID)}); +object({varbind, _, _, endOfMibView, _}) -> + finish; +object({varbind, OID, Type, Value, _}) -> + case vartype(Type) of + ignore -> {OID, ignore}; + NativeType -> {OID, {NativeType, Value}} + end; +object(Error) -> + throw(Error). + +varalias([H|T], Acc) -> + {L, R} = varalias(T, Acc), + case snmpm:oid_to_name(H) of + {ok, Name} -> {[Name | L], R}; + _ when L =:= [] -> {L, [lists:last(H) | R]}; + _ -> {L, R} + end; + +varalias([], Acc) -> + Acc. + +vartype('OCTET STRING') -> string; +vartype('OBJECT IDENTIFIER') -> object; +vartype('DisplayString') -> string; +vartype('TimeTicks') -> ticks; +vartype('TimeStamp') -> timestamp; +vartype('INTEGER') -> integer; +vartype('Integer32') -> integer; +vartype('Counter32') -> integer; + +vartype(_Another) -> ignore. diff --git a/src/websinema_endpoint.erl b/src/websinema_endpoint.erl new file mode 100644 index 0000000..2f49075 --- /dev/null +++ b/src/websinema_endpoint.erl @@ -0,0 +1,50 @@ +%% Copyright (c) 2011 Drimmi, Inc. +%% All rights reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. + +-module(websinema_endpoint). +-behaviour(snmpm_user). + +%% Exports + +-export([handle_agent/5, handle_error/3, handle_inform/3, handle_pdu/4, handle_report/3, handle_trap/3]). + +%% Public API + +handle_error(ReqId, Reason, Server) -> + dispatch(Server, {agent, error}, {ReqId, Reason}), + ignore. + +handle_agent(Addr, Port, Type, SnmpInfo, Server) -> + dispatch(Server, {agent, unknown}, {Addr, Port, Type, SnmpInfo}), + ignore. + +handle_pdu(TargetName, ReqId, SnmpResponse, Server) -> + dispatch(Server, {agent, pdu}, {TargetName, ReqId, SnmpResponse}), + ignore. + +handle_trap(TargetName, SnmpTrap, Server) -> + dispatch(Server, {agent, trap}, {TargetName, SnmpTrap}), + ignore. + +handle_inform(TargetName, SnmpInform, Server) -> + dispatch(Server, {agent, inform}, {TargetName, SnmpInform}), + ignore. + +handle_report(TargetName, SnmpReport, Server) -> + dispatch(Server, {agent, report}, {TargetName, SnmpReport}), + ignore. + +dispatch(Ref, Tag, Info) -> + Ref ! {Tag, Info}. diff --git a/src/websinema_frontend.erl b/src/websinema_frontend.erl new file mode 100644 index 0000000..9c17cd9 --- /dev/null +++ b/src/websinema_frontend.erl @@ -0,0 +1,122 @@ +%% Copyright (c) 2011 Drimmi, Inc. +%% All rights reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. + +-module(websinema_frontend). +-behaviour(supervisor). +-behaviour(cowboy_http_handler). + +-export([start_link/0, start_link/1]). + +-export([init/1]). +-export([init/3, handle/2, terminate/2]). + +-define(DEFAULTS, [ + {port, 8801}, + {docroot, "client/websinema"} +]). + +%% Public API + +start_link() -> + start_link(?DEFAULTS). + +start_link(InitialOptions) -> + lager:info("Initializing websinema server frontend..."), + + Options = websinema_utilities:propdefaults(?DEFAULTS, InitialOptions), + TransportOptions = websinema_utilities:proplist([ip, port, backlog], Options), + ProtoOptions = websinema_utilities:proplist([timeout], Options), + + DocRoot = docroot(websinema_utilities:propget([docroot], Options)), + DocRootBinary = list_to_binary(DocRoot), + + Dispatch = [ + {'_', [ + {[<<"endpoint">>], websinema_ws, {websocket, []}}, + {'_', ?MODULE, {static, DocRootBinary}} + ]} + ], + + Transport = cowboy_tcp_transport, + Proto = cowboy_http_protocol, + {ok, _} = cowboy:start_listener(http, 8, Transport, TransportOptions, Proto, [{dispatch, Dispatch} | ProtoOptions]), + + supervisor:start_link(?MODULE, []). + +%% Supervisor callback + +init([]) -> + {ok, {{one_for_one, 10, 10}, []}}. + +%% Http handler behaviour + +init({_Any, http}, Request, {static, DocRoot}) -> + [Peer, Method, RawPath] = websinema_utilities:examine_request(Request, [peer, method, raw_path]), + lager:debug("New ~p request on ~p was received from ~s", [Method, RawPath, websinema_utilities:peername(Peer)]), + case Method of + Method when Method =:= 'GET' orelse Method =:= 'HEAD' -> + {ok, Request, {DocRoot, RawPath}}; + _Other -> + {shutdown, Request, undefined} + end. + +handle(Request, {_, <<"/favicon.ico">>}) -> + {ok, Request, undefined}; + +handle(Request, {DocRoot, <<"/">>}) -> + handle(Request, {DocRoot, <<"/index.html">>}); + +handle(Request, {DocRoot, RawPath}) -> + {ok, NewRequest} = case serve_file(DocRoot, RawPath) of + {error, Error} -> + cowboy_http_req:reply(404, [], websinema_utilities:to_binary(Error), Request); + {Format, Data} -> + cowboy_http_req:reply(200, [{'Content-Type', Format}], Data, Request) + end, + {ok, NewRequest, undefined}. + +terminate(_Request, _State) -> + ok. + +serve_file(DocRoot, Path) -> + case file:read_file(<>) of + {ok, Binary} -> {extract_format(Path), Binary}; + Error -> Error + end. + +extract_format(Path) -> + case re:run(Path, "[^.]+$") of + {match, [Slice]} -> format(binary:part(Path, Slice)); + _ -> format(undefined) + end. + +format(<<"xhtml">>) -> <<"application/xhtml+xml">>; +format(<<"html">>) -> <<"text/html">>; +format(<<"htm">>) -> <<"text/html">>; +format(<<"js">>) -> <<"text/javascript">>; +format(<<"css">>) -> <<"text/css">>; +format(<<"xml">>) -> <<"application/xml">>; +format(<<"json">>) -> <<"application/json">>; +format(_) -> <<"text/plain">>. + +%% Utilities + +docroot(DocRoot) -> + Reversed = lists:reverse(DocRoot), + case Reversed of + [$/ | Rest] -> websinema:priv(lists:reverse(Rest)); + [$\ | Rest] -> websinema:priv(lists:reverse(Rest)); + _ -> websinema:priv(DocRoot) + end. diff --git a/src/websinema_observer.erl b/src/websinema_observer.erl new file mode 100644 index 0000000..8e7b48f --- /dev/null +++ b/src/websinema_observer.erl @@ -0,0 +1,174 @@ +%% Copyright (c) 2011 Drimmi, Inc. +%% All rights reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. + +-module(websinema_observer). +-behaviour(gen_server). + +-export([start_link/3, stop/1, metrics/3]). +-export([init/1, terminate/2, code_change/3, handle_call/3, handle_cast/2, handle_info/2]). + +-record(state, { + user, + agent, + oid, + rule, + interval, + backlog, + buffer, + timer, + worker +}). + +%% Public API + +start_link(Oid, Value, Options) -> + {ok, Pid} = gen_server:start_link(?MODULE, {Oid, Value, Options}, []), + Pid. + +stop(Pid) -> + gen_server:call(Pid, {shutdown, normal}, infinity). + +metrics(Pid, Scope, Aggregation) -> + gen_server:call(Pid, {metrics, Scope, Aggregation}). + +%% Callbacks + +init({Oid, {Type, Value}, Options}) -> + lager:info("Initializing new websinema observer on ~p...", [websinema_bindings:varalias(Oid)]), + process_flag(trap_exit, true), + + [User, Agent, Rules, Interval, Backlog] = + websinema_utilities:propvalues([user, agent, rules, interval, backlog], Options), + Rule = websinema_utilities:propget([Type], Rules, {poll, actual}), + State = #state{ + user = User, + agent = Agent, + oid = Oid, + rule = Rule, + interval = Interval, + backlog = Backlog, + buffer = [do_store(Value)] + }, + + Timer = do_watchtime(self(), State), + Worker = do_observe(State), + {ok, State#state{worker = Worker, timer = Timer}, hibernate}. + +terminate(Reason, #state{timer = Timer}) -> + lager:info("Shutting down (~p) a websinema observer...", [Reason]), + timer:cancel(Timer), + ok. + +code_change(_WasVersion, State, _Extra) -> + {ok, State, hibernate}. + +handle_call({shutdown, Reason}, _From, State) -> + {stop, Reason, ok, State}; + +handle_call({metrics, Scope, Agg}, _, State = #state{buffer = Buffer}) -> + {reply, fetch(Buffer, Scope, Agg), State, hibernate}; + +handle_call(Request, From, State) -> + lager:error("Unexpected call ~p received from ~p", [Request, From]), + {noreply, State, hibernate}. + +handle_cast(Request, State) -> + lager:error("Unexpected cast ~p received", [Request]), + {noreply, State, hibernate}. + +handle_info(watchtime, State = #state{buffer = Buffer, worker = Worker, oid = Oid}) -> + case Worker(Buffer) of + {ok, FreshBuffer} -> + {noreply, State#state{buffer = FreshBuffer}, hibernate}; + Error -> + lager:error("Error arised while observing ~p: ~p", [websinema_bindings:varalias(Oid), Error]), + {stop, {error, not_accessible}, State} + end; + +handle_info(Message, State) -> + lager:error("Unexpected message received ~p", [Message]), + {noreply, State, hibernate}. + +%% Implementation + +do_observe(State = #state{oid = Oid}) -> + fun (Buffer) -> + case do_examine(State) of + [{Oid, {_, Value}}] -> + FreshBuffer = do_store(Value, Buffer, State), + {ok, FreshBuffer}; + [] -> + {error, not_accessible}; + Error -> + Error + end + end. + +do_examine(#state{user = User, agent = Agent, oid = Oid}) -> + catch websinema_bindings:examine(User, Agent, [Oid]). + +do_watchtime(Pid, #state{interval = Interval, rule = {watch, _}}) -> + timer:send_interval(Interval, Pid, watchtime); +do_watchtime(_, _) -> + undefined. + +do_store(Value, Buffer, #state{backlog = Backlog, rule = {_, buffer}}) -> + [do_store(Value) | lists:sublist(Buffer, Backlog - 1)]; +do_store(Value, _, _) -> + [do_store(Value)]. + +do_store(Value) -> + {Megas, Secs, _} = erlang:now(), + {Megas * 1000000 + Secs, Value}. + +fetch([Value | _], last, _) -> + Value; + +fetch(List, every, Agg) -> + aggregate(Agg, List); + +fetch(List, {last, N}, Agg) -> + fetch(lists:sublist(List, N), every, Agg); + +fetch(List = [{Ts, _} | _], {until, Seconds}, Agg) -> + fetch(List, {since, Ts - Seconds + 1}, Agg); + +fetch(List, {since, When}, Agg) -> + fetch(lists:takewhile(fun ({Ts, _}) -> Ts >= When end, List), every, Agg); + +fetch(List, _, Agg) -> + fetch(List, last, Agg). + +aggregate(sum, List) -> + aggregate(sum, List, initial); +aggregate(product, List) -> + aggregate(product, List, initial); +aggregate(average, List) -> + {Ts, Sum} = aggregate(sum, List, initial), + {Ts, operate(average, Sum, length(List))}; +aggregate(_, List) -> + List. + +aggregate(Op, [E | Rest], initial) -> + aggregate(Op, Rest, E); +aggregate(Op, [{_, Value} | Rest], {Ts, Intermediate}) -> + aggregate(Op, Rest, {Ts, operate(Op, Value, Intermediate)}); +aggregate(_, [], Result) -> + Result. + +operate(sum, A, B) when is_integer(A), is_integer(B) -> A + B; +operate(product, A, B) when is_integer(A), is_integer(B) -> A * B; +operate(average, A, B) when is_integer(A), is_integer(B) -> A / B; +operate(_, A, _) -> A. diff --git a/src/websinema_sup.erl b/src/websinema_sup.erl new file mode 100644 index 0000000..a2c80c2 --- /dev/null +++ b/src/websinema_sup.erl @@ -0,0 +1,55 @@ +%% Copyright (c) 2011 Drimmi, Inc. +%% All rights reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. + +-module(websinema_sup). +-behaviour(supervisor). + +-export([init/1]). + +%% Callbacks + +init([]) -> + Strategy = {one_for_one, 10, 10}, + ChildSpecs = [ + specify(websinema_aggregator, local, {options, inherit}), + specify(websinema_frontend, {options, inherit}) + ], + {ok, {Strategy, ChildSpecs}}. + +%% Utilities + +specify(Spec = {Name, _, _}, Reg, {options, Mode}) -> + specify(Spec, Reg, options(Mode, Name)); + +specify({Name, Module, Entry}, none, Args) -> + {Name, {Module, Entry, [Args]}, permanent, 5000, worker, [Module]}; + +specify({Name, Module, Entry}, Tag, Args) -> + {Name, {Module, Entry, [{Tag, Name}, Args]}, permanent, 5000, worker, [Module]}; + +specify({Name, Module}, Reg, Args) -> + specify({Name, Module, start_link}, Reg, Args); + +specify(Module, Reg, Args) -> + specify({Module, Module, start_link}, Reg, Args). + +specify(Spec, Args) -> + specify(Spec, none, Args). + +options(inherit, Name) -> + websinema:option(Name, []); + +options(_, _) -> + []. \ No newline at end of file diff --git a/src/websinema_utilities.erl b/src/websinema_utilities.erl new file mode 100644 index 0000000..d7f0831 --- /dev/null +++ b/src/websinema_utilities.erl @@ -0,0 +1,306 @@ +%% Copyright (c) 2011 Drimmi, Inc. +%% All rights reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. + +-module(websinema_utilities). + +-export([ + to_list/1, + to_binary/1, + + propget/2, + propget/3, + propset/3, + propappend/3, + propextract/2, + proplist/2, + propvalues/2, + propdefaults/2, + + prefix_tree/1, + prefix_tree/2, + + with_prefix/2, + without_prefix/2, + + examine_request/2, + peername/1 +]). + +-export([test/0]). + +%% Routines + +to_binary(X) when is_binary(X) -> X; +to_binary(X) -> list_to_binary(to_list(X)). + +to_list(A) when is_list(A) -> A; +to_list(A) when is_atom(A) -> atom_to_list(A); +to_list(A) when is_integer(A) -> integer_to_list(A); +to_list(A) when is_float(A) -> float_to_list(A); +to_list(A) when is_binary(A) -> binary_to_list(A). + +%% Prefix tree construction + +prefix_tree(List) -> + prefix_tree(List, []). + +prefix_tree(List, Options) -> + Result = do_prefix_tree(List), + lists:foldl(fun process_prefix_tree/2, Result, Options). + +do_prefix_tree([With = {[], _} | Without]) -> + [With | do_prefix_tree(Without)]; + +do_prefix_tree([E | Rest]) -> + {[First | Last], Value} = E, + With = [ {Last, Value} | [ {L, V} || {[F | L], V} <- Rest, F =:= First ] ], + Without = [ E1 || E1 = {[F | _], _} <- Rest, F =/= First ], + do_partition(First, do_prefix_tree(With), do_prefix_tree(Without)); + +do_prefix_tree(Any) -> + Any. + +do_partition(First, [{With, Value}], Without) -> + [{[First | With], Value} | Without]; + +do_partition(First, With, Without) -> + [{[First], With} | Without]. + +process_prefix_tree(preserve, List) -> + preserve_prefix(List, []); + +process_prefix_tree({preserve, true}, List) -> + preserve_prefix(List, []); + +process_prefix_tree({transform, Xform}, List) -> + transform_prefix(List, Xform); + +process_prefix_tree(_, List) -> + List. + +preserve_prefix(List = [{_, _} | _], Prefix) -> + [ {Prefix ++ Pre, preserve_prefix(Value, Prefix ++ Pre)} || {Pre, Value} <- List ]; + +preserve_prefix(Value, _) -> + Value. + +transform_prefix(List = [{_, _} | _], Xform) -> + [ {Xform(Prefix), transform_prefix(Value, Xform)} || {Prefix, Value} <- List ]; + +transform_prefix(Value, _) -> + Value. + +%% Properties deep access + +propget(Path, Proplist) -> + propget(Path, Proplist, undefined). + +propget([], Value, _) -> + Value; +propget([Key | _], [Key | _], Default) -> + Default; +propget([Key | Rest], [{Key, Value} | _], Default) -> + propget(Rest, Value, Default); +propget(Path, [_ | Left], Default) -> + propget(Path, Left, Default); +propget(_, _, Default) -> + Default. + +propset([], Entry, _) -> + Entry; +propset([Key | Rest], Entry, Proplist = [{_, _} | _]) -> + With = [ {K, propset(Rest, Entry, V)} || P = {K, V} <- Proplist, keymatch(Key, P) ], + Without = [ P || P <- Proplist, not keymatch(Key, P) ], + case With of + [E|_] -> [E | Without]; + [] -> [{Key, propset(Rest, Entry, [])} | Without] + end; +propset([Key | Rest], Entry, Value) -> + [{Key, propset(Rest, Entry, Value)}]. + +propappend([], Entry, Acc) -> + [Entry | Acc]; +propappend([Key | Rest], Entry, Proplist = [{_, _} | _]) -> + With = [ {K, propappend(Rest, Entry, V)} || P = {K, V} <- Proplist, keymatch(Key, P) ], + Without = [ P || P <- Proplist, not keymatch(Key, P) ], + case With of + [E|_] -> [E | Without]; + [] -> [{Key, propappend(Rest, Entry, [])} | Without] + end; +propappend([Key | Rest], Entry, Value) -> + [{Key, propappend(Rest, Entry, Value)}]. + +propextract(Key, Proplist) -> + propextract(Key, Proplist, []). + +propextract(Key, [Key | Rest], Acc) -> + {{Key, true}, Acc ++ Rest}; +propextract(Key, [E = {Key, _} | Rest], Acc) -> + {E, Acc ++ Rest}; +propextract(Key, [H | Rest], Acc) -> + propextract(Key, Rest, [H | Acc]). + +keymatch(Key, Key) -> true; +keymatch(Key, {Key, _}) -> true; +keymatch(_, _) -> false. + +%% Properties multiple access + +propvalues(Keys, Proplist) -> + [ propget(keynormalize(Key), Proplist) || Key <- Keys ]. + +proplist(Keys, Proplist) -> + proplist(Keys, Proplist, []). + +proplist([Key | Rest], Proplist, Acc) -> + Norm = keynormalize(Key), + case propget(Norm, Proplist) of + undefined -> proplist(Rest, Proplist, Acc); + Value -> proplist(Rest, Proplist, [{Key, Value} | Acc]) + end; + +proplist([], _, Acc) -> + Acc. + +keynormalize(Key) when is_list(Key) -> Key; +keynormalize(Key) -> [Key]. + +propdefaults([E = {Head, _} | Defaults], Proplist) -> + case proplists:is_defined(Head, Proplist) of + true -> propdefaults(Defaults, Proplist); + _ -> propdefaults(Defaults, [E | Proplist]) + end; + +propdefaults([Head | Defaults], Proplist) -> + propdefaults([{Head, true} | Defaults], Proplist); + +propdefaults([], Proplist) -> + Proplist. + +%% Has prefix + +with_prefix(Prefix, Proplist) -> + has_prefix(true, Prefix, Proplist, []). + +without_prefix(Prefix, Proplist) -> + has_prefix(false, Prefix, Proplist, []). + +has_prefix(Match, Prefix, [Entry | Rest], Acc) -> + case prefixmatch(Prefix, Entry) of + Match -> has_prefix(Match, Prefix, Rest, [Entry | Acc]); + _ -> has_prefix(Match, Prefix, Rest, Acc) + end; + +has_prefix(_, _, [], Acc) -> + Acc. + +prefixmatch(Prefix, {Key, _}) -> + prefixmatch(Prefix, Key); +prefixmatch(Prefix, Key) -> + lists:prefix(Prefix, Key). + +%% Web + +examine_request(Request, What) -> + [ begin {Value, _} = cowboy_http_req:Ask(Request), Value end || Ask <- What ]. + +peername({Ip, Port}) -> + io_lib:format("~s:~p", [peername(Ip), Port]); +peername({A, B, C, D}) -> + io_lib:format("~B.~B.~B.~B", [A, B, C, D]); +peername({A, B, C, D, E, F, G, H}) -> + io_lib:format("[~16B:~16B:~16B:~16B:~16B:~16B:~16B:~16B]", [A, B, C, D, E, F, G, H]); +peername(_) -> + "undefined". + +%% Test cases + +-include_lib("eunit/include/eunit.hrl"). + +test() -> + TestsCount = 14, + [ ok = test(X) || X <- lists:seq(1, TestsCount) ], + ok. + +test(1) -> + L = [ {"something", 1}, {"somewhere", 2} ], + R = [ {"some", [ {"thing", 1}, {"where", 2} ]} ], + ?assertEqual(R, prefix_tree(L)); + +test(2) -> + L = [ {"some", 1}, {"something", 2}, {"somewhere", 3}, {"stupid", 4}, {"there", 5} ], + R = [ {"s", [ {"ome", [ {[], 1}, {"thing", 2}, {"where", 3} ]}, {"tupid", 4} ]}, {"there", 5} ], + ?assertEqual(R, prefix_tree(L)); + +test(9) -> + L = [ {"some", 1}, {"something", 2}, {"somewhere", 3}, {"stupid", 4}, {"there", 5} ], + R = [ {"s", [ {"some", [ {"some", 1}, {"something", 2}, {"somewhere", 3} ]}, {"stupid", 4} ]}, {"there", 5} ], + ?assertEqual(R, prefix_tree(L, [preserve])); + +test(10) -> + L = [ {"some", 1}, {"something", 2}, {"somewhere", 3}, {"stupid", 4}, {"there", 5} ], + R = [ {"s", [ {"emos", [ {"emos", 1}, {"gnihtemos", 2}, {"erehwemos", 3} ]}, {"diputs", 4} ]}, {"ereht", 5} ], + ?assertEqual(R, prefix_tree(L, [preserve, {transform, fun lists:reverse/1}])); + +test(3) -> + L = [ {top, [ {level, [ {thing, 2}, {where, 3} ]}, {middle, 4} ]}, {last, 5} ], + R = [ {top, [ {level, [ {thing, new}, {where, 3} ]}, {middle, 4} ]}, {last, 5} ], + ?assertEqual(R, propset([top, level, thing], new, L)); + +test(5) -> + L = [ {top, [ {level, [ {thing, 2}, {where, 3} ]}, {middle, 4} ]}, {last, 5} ], + R = [ {top, [ {level, [ {thing2, [{thing3, new}]}, {thing, 2}, {where, 3} ]}, {middle, 4} ]}, {last, 5} ], + ?assertEqual(R, propset([top, level, thing2, thing3], new, L)); + +test(6) -> + L = [ {top, [ {level, [ {thing2, [{thing3, new}]}, {thing, 2}, {where, 3} ]}, {middle, 4} ]}, {last, 5} ], + R = [{thing3, new}], + ?assertEqual(R, propget([top, level, thing2], L)); + +test(7) -> + L = [ {top, [ {level, [ {thing, 2}, {where, 3} ]}, {middle, 4} ]}, {last, 5} ], + R = 4, + ?assertEqual(R, propget([top, middle], L)); + +test(14) -> + L = [ {top, [ {level, [ {thing, 2}, {where, [3]} ]}, {middle, [4]} ]}, {last, 5} ], + R = [ {top, [ {middle, [new, 4]}, {level, [ {thing, 2}, {where, [3]} ]} ]}, {last, 5} ], + ?assertEqual(R, propappend([top, middle], new, L)); + +test(8) -> + L = [ {top, [ {level, [ {thing, 2}, {where, 3} ]}, {middle, 4} ]}, {last, 5} ], + R = undefined, + ?assertEqual(R, propget([top, middle, further], L)); + +test(4) -> + L = [ {top, [ {level, [ {thing, 2}, {where, 3} ]} ]}, {last, 5} ], + R = new, + ?assertEqual(R, propset([], new, L)); + +test(11) -> + L = [ {level, 1}, {thing, 2}, {where, 3}, {last, 5} ], + R = { {where, 3}, [ {thing, 2}, {level, 1}, {last, 5} ] }, + ?assertEqual(R, propextract(where, L)); + +test(12) -> + L = [ {"level", 1}, {"something", 2}, {"somewhere", 3}, {"last", 5} ], + R = [ {"somewhere", 3}, {"something", 2} ], + ?assertEqual(R, with_prefix("some", L)); + +test(13) -> + L = [ {level, 1}, {something, 2}, {somewhere, 3}, {last, 5} ], + R = [ {someone, there}, {level, 1}, {something, 2}, {somewhere, 3}, {last, 5} ], + Defs = [ {somewhere, none}, {someone, there} ], + ?assertEqual(R, propdefaults(Defs, L)). diff --git a/src/websinema_ws.erl b/src/websinema_ws.erl new file mode 100644 index 0000000..f2a4b63 --- /dev/null +++ b/src/websinema_ws.erl @@ -0,0 +1,231 @@ +%% Copyright (c) 2011 Drimmi, Inc. +%% All rights reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. + +-module(websinema_ws). +-behaviour(cowboy_http_websocket_handler). + +-export([init/3]). +-export([websocket_init/3, websocket_handle/3, websocket_info/3, websocket_terminate/3]). + +-export([encode/2]). + +-record(ws_state, { + aggregator, + interval = 0, + timer = undefined, + subscription = [] +}). + +%% Behavoiur + +init({_Any, http}, Request, {websocket, _}) -> + case cowboy_http_req:header('Upgrade', Request) of + {undefined, _} -> {shutdown, Request, undefined}; + {<<"websocket">>, _} -> {upgrade, protocol, cowboy_http_websocket}; + {<<"WebSocket">>, _} -> {upgrade, protocol, cowboy_http_websocket} + end. + +websocket_init(_Any, Request, {websocket, _PassedOptions}) -> + [Peer, RawPath] = websinema_utilities:examine_request(Request, [peer, raw_path]), + lager:info("Websocket client at ~p accepted on ~p", [websinema_utilities:peername(Peer), RawPath]), + State = #ws_state{aggregator = websinema_aggregator}, + {ok, Request, State, hibernate}. + +websocket_handle({ping, _}, Request, State) -> + lager:debug("Websocket instance was poked"), + {ok, Request, State}; + +websocket_handle({text, Message}, Request, State) -> + lager:debug("Websocket instance received a message: ~p", [Message]), + {RawReply, NewState} = + case request(Message) of + {ok, {Action, Object}} -> + case catch handle_request(Action, Object, State) of + {ok, {ok, Reply}, AState} -> {respond({ok, Reply}), AState}; + {ok, Reply, AState} -> {respond({ok, Reply}), AState}; + {ok, {ok, Reply}} -> {respond({ok, Reply}), State}; + {ok, Reply} -> {respond({ok, Reply}), State}; + Error -> {respond(Error), State} + end; + Error -> + {respond(Error), State} + end, + {reply, {text, RawReply}, Request, NewState, hibernate}; + +websocket_handle(Unexpected, Request, State) -> + lager:error("Websocket instance got an unexpected message from client: ~p", [Unexpected]), + {ok, Request, State, hibernate}. + +websocket_info({timeout, resend}, Request, State = #ws_state{subscription = Subscription, aggregator = Aggregator, interval = Interval}) -> + Response = do_examine(Aggregator, Subscription, {until, Interval div 1000}, average), + {reply, {text, respond(Response)}, Request, State, hibernate}; + +websocket_info(Unexpected, Request, State) -> + lager:error("Websocket instance received unexpected message: ~p", [Unexpected]), + {ok, Request, State, hibernate}. + +websocket_terminate(Reason, _Request, #ws_state{interval = Interval}) -> + lager:info("Websocket client released because of ~p", [Reason]), + timer:cancel(Interval), + ok. + +%% Handlers + +handle_request(<<"agents_list">>, _, #ws_state{aggregator = Aggregator}) -> + List = websinema_aggregator:agents(Aggregator), + {ok, [ websinema_utilities:to_binary(A) || A <- List ]}; + +handle_request(<<"discover">>, Args, #ws_state{aggregator = Aggregator}) -> + Name = proplists:get_value(<<"name">>, Args), + NameList = binary_to_list(Name), + case websinema_aggregator:discover(Aggregator, NameList) of + {ok, Discovery} -> + {ok, [{name, Name}, {metrics, encode({tree, bindings}, Discovery)}]}; + Error -> + Error + end; + +handle_request(<<"subscribe">>, Args, State = #ws_state{aggregator = Aggregator, subscription = WasSubscription, interval = WasInterval, timer = WasTimer}) -> + Ids = proplists:get_value(<<"sids">>, Args), + Interval = proplists:get_value(<<"i">>, Args), + Timer = alter_timer(WasTimer, WasInterval, Interval, {self(), resend}), + {Fresh, Subscription} = lists:foldl(fun alter_subscription/2, {[], WasSubscription}, Ids), + Response = do_examine(Aggregator, Fresh, every, list), + {ok, Response, State#ws_state{interval = Interval, subscription = Subscription, timer = Timer}}; + +handle_request(Request, _, _) -> + lager:error("Websocket instance received invalid request: ~p", [Request]), + {error, {invalid_request, Request}}. + +%% Transport + +encode(T = {tree, _}, Values = [{_, _}|_]) -> + [ encode(T, E) || E <- Values ]; +encode(T = {tree, bindings}, {Prefix, List}) when is_list(List) -> + {[ {name, encode(oid, Prefix)}, {category, group}, {children, encode(T, List)} ]}; +encode(T = {tree, values}, {Name, List}) when is_list(List) -> + {[ {name, encode(binary, Name)}, {metrics, encode(T, List)} ]}; + +encode({tree, T}, {Name, Value}) -> + {[ {sid, Name}, {name, encode(oid, Name)} | encode(T, Value) ]}; + +encode(bindings, {Type, Value}) -> + [ {category, metric}, {type, Type}, {value, encode(metric, {Type, Value})} ]; + +encode(values, {value, Type, L = [{_, _} | _]}) -> + [ {values, [ {encode(values, {value, Type, E})} || E <- L ]} ]; +encode(values, {value, Type, {Ts, Value}}) -> + [ {ts, Ts}, {value, encode(metric, {Type, Value})} ]; +encode(values, Error) -> + [ {error, do_respond(Error)} ]; + +encode(metrics, []) -> + []; +encode(metrics, Metrics) -> + {[ {tag, metrics}, {value, encode({tree, values}, Metrics)} ]}; + + +encode(oid, Name = [_|_]) -> + lists:foldl( + fun + (E, Acc) when Acc =:= <<>> -> <<(websinema_utilities:to_binary(E))/binary>>; + (E, Acc) -> <> + end, <<>>, Name + ); + +encode(metric, {string, Value}) -> + iolist_to_binary(Value); +encode(metric, {_, Value}) -> + Value; + +encode(binary, Name) -> + websinema_utilities:to_binary(Name); + +encode(_, []) -> + []. + +%% Utilities + +request(Request) -> + case json:decode(Request) of + {ok, {Object}} -> + Action = proplists:get_value(<<"a">>, Object), + {ok, {Action, Object}}; + Error -> + Error + end. + +respond(Reply) -> + try + lager:debug("Reply: ~p", [Reply]), + {ok, Json} = json:encode(do_respond(Reply)), + Json + catch + _:_ -> + {ok, Error} = json:encode(do_respond({error, malformed_response})), + Error + end. + +do_respond(Status) when is_atom(Status) -> {[{status, Status =:= ok}]}; + +do_respond({ok, Data = {_, _}}) -> do_respond({ok, {[Data]}}); +do_respond({ok, Data = [{_, _} | _]}) -> do_respond({ok, {Data}}); +do_respond({ok, Data}) -> {[{status, true}, {message, Data}]}; + +do_respond({_Error, Reason}) -> {[{status, false}, {reason, do_reason(Reason)}]}; + +do_respond(Arbitrary) -> Arbitrary. + +do_reason({Why, What}) when is_binary(What) -> do_reason({Why, binary_to_list(What)}); +do_reason({Why, What}) -> iolist_to_binary(io_lib:format("~p: ~p", [Why, What])); +do_reason(Why) -> iolist_to_binary(io_lib:format("~p", [Why])). + +do_examine(Aggregator, Bindings, Scope, Aggregation) -> + Metrics = websinema_aggregator:examine(Aggregator, Bindings, Scope, Aggregation, [aliases]), + case Metrics of + {ok, Values} -> {ok, encode(metrics, Values)}; + Error -> Error + end. + +alter_subscription({Entry}, Subscription) -> + [NameBin, IdBin, On] = websinema_utilities:propvalues([<<"a">>, <<"sid">>, <<"on">>], Entry), + Name = binary_to_list(NameBin), + Id = [ binary_to_atom(E, utf8) || E <- IdBin ], + alter_subscription(Name, Id, On, Subscription). + +alter_subscription(Name, Id, On, {Acc0, Subscription}) -> + InitialSubs = lists:delete(Id, websinema_utilities:propget([Name], Subscription, [])), + {Acc, Subs} = case On of + false -> {Acc0, InitialSubs}; + true -> + A = websinema_utilities:propappend([Name], Id, Acc0), + {A, [Id | InitialSubs]} + end, + {Acc, websinema_utilities:propset([Name], Subs, Subscription)}. + +alter_timer(Was, WasInterval, WasInterval, _) -> + Was; +alter_timer(Was, _, Interval, What) -> + cancel_timer(Was), + start_timer(Interval, What). + +cancel_timer(undefined) -> ok; +cancel_timer(TimerRef) -> + {ok, cancel} = timer:cancel(TimerRef). + +start_timer(0, _) -> undefined; +start_timer(Interval, {Pid, Message}) -> + {ok, Timer} = timer:send_interval(Interval, Pid, {timeout, Message}), + Timer. diff --git a/start-app.sh b/start-app.sh new file mode 100755 index 0000000..fde7b96 --- /dev/null +++ b/start-app.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +export ERL_LIBS="deps/:${ERL_LIBS}" +erl -pa ebin -boot start_sasl -s reloader -s websinema -config default +