├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── doc ├── edoc-info ├── erlang.png ├── erlwitness.html ├── erlwitness.md ├── erlwitness_app.html ├── erlwitness_app.md ├── erlwitness_conf.html ├── erlwitness_conf.md ├── erlwitness_entity.html ├── erlwitness_entity.md ├── erlwitness_index_serv.html ├── erlwitness_index_serv.md ├── erlwitness_lobby.html ├── erlwitness_lobby.md ├── erlwitness_lookup.html ├── erlwitness_lookup.md ├── erlwitness_sup.html ├── erlwitness_sup.md ├── erlwitness_transform.html ├── erlwitness_transform.md ├── erlwitness_watcher.html ├── erlwitness_watcher.md ├── modules-frame.html ├── overview-summary.html ├── overview.edoc ├── packages-frame.html ├── stylesheet.css └── utf8 │ └── README.md ├── example ├── person_file_serv.erl └── person_watcher.erl ├── rebar.config ├── src ├── erlwitness.app.src ├── erlwitness.erl ├── erlwitness_app.erl ├── erlwitness_conf.erl ├── erlwitness_entity.erl ├── erlwitness_index_serv.erl ├── erlwitness_lobby.erl ├── erlwitness_lookup.erl ├── erlwitness_sup.erl ├── erlwitness_transform.erl └── erlwitness_watcher.erl └── test ├── erlwitness_test_entity.erl ├── erlwitness_test_watcher.erl ├── erlwitness_tests.app.config └── erlwitness_tests.erl /.gitignore: -------------------------------------------------------------------------------- 1 | all_runs.html 2 | *.beam 3 | .concrete/DEV_MODE 4 | ct_*.css 5 | ct_log* 6 | ct_run* 7 | deps 8 | .deps_plt 9 | .dialyzerignore 10 | .dialyzer_plt 11 | ebin 12 | erl_crash.dump 13 | .eunit 14 | index.html 15 | jquery* 16 | *.log 17 | log/ 18 | logs/ 19 | *.o 20 | *.plt 21 | .rebar 22 | rel/example_project 23 | *.swp 24 | test.app.config 25 | variables-ct* 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Guilherme Andrade 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | REBAR = $(shell command -v rebar || echo ./rebar) 2 | DEPS_PLT=./.deps_plt 3 | DEPS=erts kernel stdlib inets crypto mnesia public_key ssl 4 | DIALYZER = dialyzer 5 | 6 | DIALYZER_WARNINGS = -Wunmatched_returns -Werror_handling \ 7 | -Wrace_conditions -Wunderspecs 8 | 9 | .PHONY: all compile test qc clean get-deps build-plt dialyze 10 | 11 | all: compile 12 | 13 | deps: 14 | @$(REBAR) get-deps 15 | @$(REBAR) compile 16 | 17 | compile: 18 | @$(REBAR) compile 19 | 20 | test: compile 21 | @ERL_AFLAGS="-config test/erlwitness_tests.app.config" $(REBAR) eunit skip_deps=true 22 | 23 | clean: 24 | @$(REBAR) clean 25 | 26 | get-deps: 27 | @$(REBAR) get-deps 28 | 29 | $(DEPS_PLT): 30 | @echo Building $(DEPS_PLT) 31 | @$(DIALYZER) --build_plt \ 32 | -r deps \ 33 | --output_plt $(DEPS_PLT) \ 34 | --apps $(DEPS) 35 | 36 | dialyze: $(DEPS_PLT) compile 37 | @$(DIALYZER) --fullpath \ 38 | -Wunmatched_returns \ 39 | -Werror_handling \ 40 | -Wrace_conditions \ 41 | -Wunderspecs \ 42 | --plt $(DEPS_PLT) \ 43 | ebin | grep -vf .dialyzerignore 44 | 45 | xref: 46 | @$(REBAR) xref 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # erlwitness # 4 | 5 | Copyright (c) 2015 Guilherme Andrade 6 | 7 | __Version:__ 1.0.0 8 | 9 | __Authors:__ Guilherme Andrade ([`erlwitness(at)gandrade(dot)net`](mailto:erlwitness(at)gandrade(dot)net)). 10 | 11 | `erlwitness`: Semantic process groups watchtower. 12 | 13 | --------- 14 | 15 | 16 | ### What does it do? ### 17 | 18 | 19 | `erlwitness` allows one to funnel both `gen_server` events (init, calls, casts, infos, state changes) and `lager` calls specific to identified entities into arbitrary watcher processes. 20 | 21 | 22 | ### Why? ### 23 | 24 | 25 | For example: remote activity tracers for sizeable software development teams (instead of _hey, may you please check the log?_) 26 | 27 | 28 | ### Main features ### 29 | 30 | 31 | - Multiple watchers can spy on the same entity; 32 | - A single watcher can spy on multiple entities; 33 | - An entity can consist of zero or more processes that come and go; 34 | - Watching works transparently on both local and remote nodes; 35 | - Watching can be initiated both before and after entity processes have been spawned. 36 | - Lager calls are (optionally) watchable through a parse_transform. 37 | 38 | 39 | ### How do I use it? ### 40 | 41 | 42 | There are two main parts to an ordinary setup: 43 | 44 | 1) Adapt your existing entities' gen_server:start / gen_server:start_link / init calls to make use of the relevant registration flow: 45 | 46 | ```erlang 47 | 48 | % .... 49 | -behaviour(gen_server). 50 | % ... 51 | %% Uncomment to allow for lager tracing 52 | %-compile([{parse_transform, erlwitness_transform}]). 53 | 54 | start_link(Person, Files, LuckyNumber) -> 55 | {WrappedArgs, StartOptions} = erlwitness:get_start_extras({person, Person}, 56 | person_file_serv, 57 | [Files, LuckyNumber]), 58 | gen_server:start_link(?MODULE, WrappedArgs, StartOptions). 59 | 60 | init(WrappedArgs) -> 61 | [Files, LuckyNumber] = erlwitness:unwrap_init_args(WrappedArgs), 62 | InitResult = {ok, #state{files = Files, 63 | lucky_number = LuckyNumber}}, 64 | erlwitness:finalize_init(WrappedArgs, InitResult). 65 | % .... 66 | 67 | ``` 68 | 69 | 2) Code your own watcher process which will implement both `gen_server` and `erlwitness_watcher` behaviours: 70 | 71 | ```erlang 72 | 73 | % ... 74 | -behaviour(gen_server). 75 | -behaviour(erlwitness_watcher). 76 | % ... 77 | 78 | start_link(Person) -> 79 | erlwitness_watcher:start_link([{person, Person}], ?MODULE, []). 80 | 81 | % ... 82 | 83 | handle_gencall_event(Timestamp, {person, Person}, PersonPid, PersonProcType, 84 | PersonProcName, Call, From, State) -> 85 | io:format("Here's a call: ~p~n", [{Timestamp, Person, PersonPid, 86 | PersonProcType, Call, From}]), 87 | {noreply, State}. 88 | 89 | handle_gencast_event(Timestamp, {person, Person}, PersonPid, PersonProcType, 90 | PersonProcName, Cast, State) -> 91 | io:format("Here's a cast: ~p~n", [{Timestamp, Person, PersonPid, 92 | PersonProcType, Cast}]), 93 | {noreply, State}. 94 | 95 | handle_geninfo_event(Timestamp, {person, Person}, PersonPid, PersonProcType, 96 | PersonProcName, Info, State) -> 97 | io:format("Here's an info: ~p~n", [{Timestamp, Person, PersonPid, 98 | PersonProcType, Info}]), 99 | {noreply, State}. 100 | 101 | handle_newstate_event(Timestamp, {person, Person}, PersonPid, PersonProcType, 102 | PersonProcName, PersonState, State) -> 103 | io:format("Here's a new state: ~p~n", [{Timestamp, Person, PersonPid, 104 | PersonProcType, PersonState}]), 105 | {noreply, State}. 106 | 107 | handle_lager_event(Timestamp, {person, Person}, PersonPid, PersonProcType, 108 | PersonProcName, LagerMFA, LagerDebugInfo, State) -> 109 | {_LagerModule, LagerLevel, LagerArgs} = LagerMFA, 110 | {CodeModule, CodeFunction, CodeLine} = LagerDebugInfo, 111 | io:format("Here's a lager message from ~p: ~p @ ~p~n", 112 | [{Timestamp, Person, PersonPid, PersonProcType, PersonState}, 113 | {LagerLevel, LagerArgs}, {CodeModule, CodeFunction, CodeLine}]), 114 | {noreply, State}. 115 | 116 | % .. 117 | 118 | ``` 119 | 120 | .. and optionally, 3) Reuse your existing entity registration code by implementing the `erlwitness_lookup` behaviour and adjusting `erlwitness` app.config acordingly. 121 | 122 | ```erlang 123 | 124 | % ... 125 | -behaviour(erlwitness_lookup). 126 | % ... 127 | 128 | lookup_global_entity({person, Person}) -> 129 | PeopleInfo = mnesia:dirty_read(person_info, Person), 130 | [{person_file_serv, PersonInfo#person_info.file_serv_pid} || PersonInfo <- Pids]. 131 | % ... 132 | 133 | ``` 134 | 135 | Full implementations under _example/_. 136 | 137 | 138 | ### How do I configure it? ### 139 | 140 | 141 | app.config: 142 | 143 | ```erlang 144 | 145 | [{erlwitness, [ 146 | % Optional; module implementing 'erlwitness_lookup' behaviour 147 | %{entity_lookup_module, erlwitness_index_serv}, 148 | % 149 | % Optional; defaults to 10 * NumberOfSchedulers 150 | %{erlwitness_index_serv_count, N :: pos_integer()} 151 | ]} 152 | ]. 153 | 154 | ``` 155 | 156 | 157 | ### How do I globally enable the lager events parse_transform? ### 158 | 159 | Compiler flags: 160 | 161 | ```erlang 162 | 163 | {parse_transform, erlwitness_transform} 164 | 165 | ``` 166 | 167 | 168 | ### What about the overhead? ### 169 | 170 | 171 | This software is built on the premise that watchers are few, rare, and mostly limited 172 | to development environments; therefore watching is heavy and privacy is light. 173 | - Unwatched processes: basic ETS lookup just before the process starts + a single process dictionary check for every `lager` call; 174 | - Watched processes: one OTP debug fun per watcher (see [sys(3)](http://www.erlang.org/doc/man/sys.html)) + `lager` calls redirection. 175 | 176 | 177 | ### Why are there no termination events? ### 178 | 179 | 180 | This software is a building block; tracking and monitoring entity processes on a watcher is left as an exercise for the reader. 181 | 182 | 183 | ### What's the deal with entity registration? ### 184 | 185 | 186 | `erlwitness_index_serv` is bundled as an out-of-the-box solution; it might not, however, suit your needs. Please mind that it will, for each spawned entity: 187 | - 1) Write a new entry to an ETS table (on `erlwitness:finalize_init/2`); 188 | - 2) Create a monitor from a specific worker on the indexing pool; 189 | - 3) Trigger the monitor on termination in order to unregister the entity. 190 | 191 | In order to lax this potential bottleneck, the indexing pool will spawn (_10 x NumberOfSchedulers_) monitoring processes by default, with one separate ETS table per indexer, and NumberOfSchedulers generally (but not always) corresponding to the number of CPU thread queues (usually _NumOfCPUs x NumOfCoresPerCPU x (1 + HyperthreadPerCore)_). 192 | 193 | However, if you already have your own global process directory in place, it's recommended that you use it instead. 194 | 195 | 196 | ### Future plans ### 197 | 198 | - Clean up the current lager events mess; 199 | - Offline watcher scheduling with later history retrieving. 200 | 201 | 202 | ## Modules ## 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 |
erlwitness
erlwitness_app
erlwitness_conf
erlwitness_entity
erlwitness_index_serv
erlwitness_lobby
erlwitness_lookup
erlwitness_sup
erlwitness_transform
erlwitness_watcher
216 | 217 | -------------------------------------------------------------------------------- /doc/edoc-info: -------------------------------------------------------------------------------- 1 | %% encoding: UTF-8 2 | {packages,[]}. 3 | {modules,[erlwitness,erlwitness_app,erlwitness_conf,erlwitness_entity, 4 | erlwitness_index_serv,erlwitness_lobby,erlwitness_lookup, 5 | erlwitness_sup,erlwitness_transform,erlwitness_watcher]}. 6 | -------------------------------------------------------------------------------- /doc/erlang.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g-andrade/erlwitness/047ea86ea2fec9c3dab1ef091a3a1dfea23abaad/doc/erlang.png -------------------------------------------------------------------------------- /doc/erlwitness.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Module erlwitness 6 | 7 | 8 | 9 | 10 |
11 | 12 |

Module erlwitness

13 | 14 | 15 | 16 |

Data Types

17 | 18 |

entity()

19 |

entity() = any()

20 | 21 | 22 |

init_result()

23 |

init_result() = {ok, S::term()} | {ok, S::term(), timeout()} | ignore | {stop, Reason::term()}

24 | 25 | 26 |

process_type()

27 |

process_type() = any()

28 | 29 | 30 |

wrapped_init_args()

31 |

abstract datatype: wrapped_init_args()

32 | 33 | 34 |

Function Index

35 | 36 | 37 | 38 | 39 | 40 |
finalize_init/2Finalize entity's gen_server initialization.
get_start_extras/2Wrap arguments and merge erlwitness custom debug options.
get_start_extras/3Wrap arguments and merge erlwitness custom debug options.
get_start_extras/4Wrap arguments and merge erlwitness custom debug options.
unwrap_init_args/1Unwrap previously wrapped args.
41 | 42 |

Function Details

43 | 44 |

finalize_init/2

45 |
46 |

finalize_init(Wrapped_init_args::#wrapped_init_args{}, InitResult::init_result()) -> init_result()

47 |

Finalize entity's gen_server initialization

48 | 49 |

get_start_extras/2

50 |
51 |

get_start_extras(Entity::entity(), EntityProcType::process_type()) -> {WrappedInitArgs::#wrapped_init_args{}, StartOptions::[term()]}

52 |

Wrap arguments and merge erlwitness custom debug options. 53 |

54 | 55 |

get_start_extras/3

56 |
57 |

get_start_extras(Entity::entity(), EntityProcType::process_type(), Args::term()) -> {WrappedInitArgs::#wrapped_init_args{}, StartOptions::[term()]}

58 |

Wrap arguments and merge erlwitness custom debug options. 59 |

60 | 61 |

get_start_extras/4

62 |
63 |

get_start_extras(Entity::entity(), EntityProcType::process_type(), Args::term(), BaseStartOptions::[term()]) -> {WrappedInitArgs::#wrapped_init_args{}, StartOptions::[term()]}

64 |

Wrap arguments and merge erlwitness custom debug options. 65 |

66 | 67 |

unwrap_init_args/1

68 |
69 |

unwrap_init_args(WrappedInitArgs::#wrapped_init_args{}) -> term()

70 |

Unwrap previously wrapped args.

71 |
72 | 73 | 74 |

Generated by EDoc, Oct 11 2015, 19:05:56.

75 | 76 | 77 | -------------------------------------------------------------------------------- /doc/erlwitness.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module erlwitness # 4 | * [Data Types](#types) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | 9 | 10 | 11 | 12 | ## Data Types ## 13 | 14 | 15 | 16 | 17 | ### entity() ### 18 | 19 | 20 | 21 |

 22 | entity() = any()
 23 | 
24 | 25 | 26 | 27 | 28 | 29 | ### init_result() ### 30 | 31 | 32 | 33 |

 34 | init_result() = {ok, S::term()} | {ok, S::term(), timeout()} | ignore | {stop, Reason::term()}
 35 | 
36 | 37 | 38 | 39 | 40 | 41 | ### process_type() ### 42 | 43 | 44 | 45 |

 46 | process_type() = any()
 47 | 
48 | 49 | 50 | 51 | 52 | 53 | ### wrapped_init_args() ### 54 | 55 | 56 | __abstract datatype__: `wrapped_init_args()` 57 | 58 | 59 | 60 | ## Function Index ## 61 | 62 | 63 |
finalize_init/2Finalize entity's gen_server initialization.
get_start_extras/2Wrap arguments and merge erlwitness custom debug options.
get_start_extras/3Wrap arguments and merge erlwitness custom debug options.
get_start_extras/4Wrap arguments and merge erlwitness custom debug options.
unwrap_init_args/1Unwrap previously wrapped args.
64 | 65 | 66 | 67 | 68 | ## Function Details ## 69 | 70 | 71 | 72 | ### finalize_init/2 ### 73 | 74 | 75 |

 76 | finalize_init(Wrapped_init_args::#wrapped_init_args{}, InitResult::init_result()) -> init_result()
 77 | 
78 | 79 |

80 | 81 | 82 | Finalize entity's gen_server initialization 83 | 84 | 85 | ### get_start_extras/2 ### 86 | 87 | 88 |

 89 | get_start_extras(Entity::entity(), EntityProcType::process_type()) -> {WrappedInitArgs::#wrapped_init_args{}, StartOptions::[term()]}
 90 | 
91 | 92 |

93 | 94 | 95 | Wrap arguments and merge erlwitness custom debug options. 96 | 97 | 98 | 99 | ### get_start_extras/3 ### 100 | 101 | 102 |

103 | get_start_extras(Entity::entity(), EntityProcType::process_type(), Args::term()) -> {WrappedInitArgs::#wrapped_init_args{}, StartOptions::[term()]}
104 | 
105 | 106 |

107 | 108 | 109 | Wrap arguments and merge erlwitness custom debug options. 110 | 111 | 112 | 113 | ### get_start_extras/4 ### 114 | 115 | 116 |

117 | get_start_extras(Entity::entity(), EntityProcType::process_type(), Args::term(), BaseStartOptions::[term()]) -> {WrappedInitArgs::#wrapped_init_args{}, StartOptions::[term()]}
118 | 
119 | 120 |

121 | 122 | 123 | Wrap arguments and merge erlwitness custom debug options. 124 | 125 | 126 | 127 | ### unwrap_init_args/1 ### 128 | 129 | 130 |

131 | unwrap_init_args(WrappedInitArgs::#wrapped_init_args{}) -> term()
132 | 
133 | 134 |

135 | 136 | 137 | Unwrap previously wrapped args. 138 | -------------------------------------------------------------------------------- /doc/erlwitness_app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Module erlwitness_app 6 | 7 | 8 | 9 | 10 |
11 | 12 |

Module erlwitness_app

13 | 14 | 15 |

Behaviours: application.

16 | 17 |

Function Index

18 | 19 | 20 |
start/2
stop/1
21 | 22 |

Function Details

23 | 24 |

start/2

25 |
26 |

start(StartType, StartArgs) -> any()

27 |
28 | 29 |

stop/1

30 |
31 |

stop(State) -> any()

32 |
33 |
34 | 35 | 36 |

Generated by EDoc, Oct 11 2015, 19:05:56.

37 | 38 | 39 | -------------------------------------------------------------------------------- /doc/erlwitness_app.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module erlwitness_app # 4 | * [Function Index](#index) 5 | * [Function Details](#functions) 6 | 7 | __Behaviours:__ [`application`](application.md). 8 | 9 | 10 | ## Function Index ## 11 | 12 | 13 |
start/2
stop/1
14 | 15 | 16 | 17 | 18 | ## Function Details ## 19 | 20 | 21 | 22 | ### start/2 ### 23 | 24 | `start(StartType, StartArgs) -> any()` 25 | 26 | 27 | 28 | 29 | ### stop/1 ### 30 | 31 | `stop(State) -> any()` 32 | 33 | 34 | -------------------------------------------------------------------------------- /doc/erlwitness_conf.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Module erlwitness_conf 6 | 7 | 8 | 9 | 10 |
11 | 12 |

Module erlwitness_conf

13 | 14 | 15 | 16 |

Function Index

17 | 18 | 19 | 20 |
get_index_serv_count/0
get_lookup_module/0
use_internal_indexing/0
21 | 22 |

Function Details

23 | 24 |

get_index_serv_count/0

25 |
26 |

get_index_serv_count() -> pos_integer()

27 |
28 | 29 |

get_lookup_module/0

30 |
31 |

get_lookup_module() -> module()

32 |
33 | 34 |

use_internal_indexing/0

35 |
36 |

use_internal_indexing() -> boolean()

37 |
38 |
39 | 40 | 41 |

Generated by EDoc, Oct 11 2015, 19:05:56.

42 | 43 | 44 | -------------------------------------------------------------------------------- /doc/erlwitness_conf.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module erlwitness_conf # 4 | * [Function Index](#index) 5 | * [Function Details](#functions) 6 | 7 | 8 | 9 | 10 | ## Function Index ## 11 | 12 | 13 |
get_index_serv_count/0
get_lookup_module/0
use_internal_indexing/0
14 | 15 | 16 | 17 | 18 | ## Function Details ## 19 | 20 | 21 | 22 | ### get_index_serv_count/0 ### 23 | 24 | 25 |

26 | get_index_serv_count() -> pos_integer()
27 | 
28 | 29 |

30 | 31 | 32 | 33 | 34 | 35 | ### get_lookup_module/0 ### 36 | 37 | 38 |

39 | get_lookup_module() -> module()
40 | 
41 | 42 |

43 | 44 | 45 | 46 | 47 | 48 | ### use_internal_indexing/0 ### 49 | 50 | 51 |

52 | use_internal_indexing() -> boolean()
53 | 
54 | 55 |

56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /doc/erlwitness_entity.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Module erlwitness_entity 6 | 7 | 8 | 9 | 10 |
11 | 12 |

Module erlwitness_entity

13 | 14 | 15 | 16 |

Function Index

17 | 18 | 19 | 20 |
get_entity/0
report_lager_event/8
set_params/3
21 | 22 |

Function Details

23 | 24 |

get_entity/0

25 |
26 |

get_entity() -> {value, erlwitness:entity()} | undefined

27 |
28 | 29 |

report_lager_event/8

30 |
31 |

report_lager_event(Watchers::[pid()], Entity::erlwitness:entity(), LagerModule::module(), LagerFunction::atom(), LagerArgs::list(), CodeModule::module(), CodeFunction::atom(), CodeLine::pos_integer()) -> ok

32 |
33 | 34 |

set_params/3

35 |
36 |

set_params(Entity::erlwitness:entity(), EntityProcType::erlwitness:process_type(), EntityProcName::term()) -> ok

37 |
38 |
39 | 40 | 41 |

Generated by EDoc, Oct 11 2015, 19:05:56.

42 | 43 | 44 | -------------------------------------------------------------------------------- /doc/erlwitness_entity.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module erlwitness_entity # 4 | * [Function Index](#index) 5 | * [Function Details](#functions) 6 | 7 | 8 | 9 | 10 | ## Function Index ## 11 | 12 | 13 |
get_entity/0
report_lager_event/8
set_params/3
14 | 15 | 16 | 17 | 18 | ## Function Details ## 19 | 20 | 21 | 22 | ### get_entity/0 ### 23 | 24 | 25 |

26 | get_entity() -> {value, erlwitness:entity()} | undefined
27 | 
28 | 29 |

30 | 31 | 32 | 33 | 34 | 35 | ### report_lager_event/8 ### 36 | 37 | 38 |

39 | report_lager_event(Watchers::[pid()], Entity::erlwitness:entity(), LagerModule::module(), LagerFunction::atom(), LagerArgs::list(), CodeModule::module(), CodeFunction::atom(), CodeLine::pos_integer()) -> ok
40 | 
41 | 42 |

43 | 44 | 45 | 46 | 47 | 48 | ### set_params/3 ### 49 | 50 | 51 |

52 | set_params(Entity::erlwitness:entity(), EntityProcType::erlwitness:process_type(), EntityProcName::term()) -> ok
53 | 
54 | 55 |

56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /doc/erlwitness_index_serv.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Module erlwitness_index_serv 6 | 7 | 8 | 9 | 10 |
11 | 12 |

Module erlwitness_index_serv

13 | 14 | 15 |

Behaviours: erlwitness_lookup, gen_server.

16 | 17 |

Function Index

18 | 19 | 20 | 21 | 22 | 24 |
child_spec/1
lookup_entity/1
lookup_global_entity/1
register_entity/3
start_link/1 23 | Starts the server.
25 | 26 |

Function Details

27 | 28 |

child_spec/1

29 |
30 |

child_spec(Id::pos_integer()) -> supervisor:child_spec()

31 |
32 | 33 |

lookup_entity/1

34 |
35 |

lookup_entity(Entity::erlwitness:entity()) -> [erlwitness_lookup:indexed_entity_ref()]

36 |
37 | 38 |

lookup_global_entity/1

39 |
40 |

lookup_global_entity(Entity::erlwitness:entity()) -> [erlwitness_lookup:indexed_entity_ref()]

41 |
42 | 43 |

register_entity/3

44 |
45 |

register_entity(Entity::erlwitness:entity(), EntityProcType::erlwitness:process_type(), EntityPid::pid()) -> ok

46 |
47 | 48 |

start_link/1

49 |
50 |

start_link(Id::pos_integer()) -> {ok, Pid} | ignore | {error, Error}

51 |

52 | Starts the server 53 |

54 |
55 | 56 | 57 |

Generated by EDoc, Oct 11 2015, 19:05:56.

58 | 59 | 60 | -------------------------------------------------------------------------------- /doc/erlwitness_index_serv.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module erlwitness_index_serv # 4 | * [Function Index](#index) 5 | * [Function Details](#functions) 6 | 7 | __Behaviours:__ [`erlwitness_lookup`](erlwitness_lookup.md), [`gen_server`](gen_server.md). 8 | 9 | 10 | ## Function Index ## 11 | 12 | 13 |
child_spec/1
lookup_entity/1
lookup_global_entity/1
register_entity/3
start_link/1 14 | Starts the server.
15 | 16 | 17 | 18 | 19 | ## Function Details ## 20 | 21 | 22 | 23 | ### child_spec/1 ### 24 | 25 | 26 |

27 | child_spec(Id::pos_integer()) -> supervisor:child_spec()
28 | 
29 | 30 |

31 | 32 | 33 | 34 | 35 | 36 | ### lookup_entity/1 ### 37 | 38 | 39 |

40 | lookup_entity(Entity::erlwitness:entity()) -> [erlwitness_lookup:indexed_entity_ref()]
41 | 
42 | 43 |

44 | 45 | 46 | 47 | 48 | 49 | ### lookup_global_entity/1 ### 50 | 51 | 52 |

53 | lookup_global_entity(Entity::erlwitness:entity()) -> [erlwitness_lookup:indexed_entity_ref()]
54 | 
55 | 56 |

57 | 58 | 59 | 60 | 61 | 62 | ### register_entity/3 ### 63 | 64 | 65 |

66 | register_entity(Entity::erlwitness:entity(), EntityProcType::erlwitness:process_type(), EntityPid::pid()) -> ok
67 | 
68 | 69 |

70 | 71 | 72 | 73 | 74 | 75 | ### start_link/1 ### 76 | 77 | 78 |

79 | start_link(Id::pos_integer()) -> {ok, Pid} | ignore | {error, Error}
80 | 
81 | 82 |

83 | 84 | 85 | 86 | Starts the server 87 | 88 | -------------------------------------------------------------------------------- /doc/erlwitness_lobby.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Module erlwitness_lobby 6 | 7 | 8 | 9 | 10 |
11 | 12 |

Module erlwitness_lobby

13 | 14 | 15 |

Behaviours: gen_server.

16 | 17 |

Function Index

18 | 19 | 20 | 22 | 23 | 24 | 25 | 26 |
is_entity_watched/1
is_entity_watched_by/2
start_link/0 21 | Starts the server.
unwatch/2
unwatch_by_pid/1
watch/2
watchers_local_lookup/1
27 | 28 |

Function Details

29 | 30 |

is_entity_watched/1

31 |
32 |

is_entity_watched(Entity::erlwitness:entity()) -> boolean()

33 |
34 | 35 |

is_entity_watched_by/2

36 |
37 |

is_entity_watched_by(Entity::erlwitness:entity(), WatcherPid::pid()) -> boolean()

38 |
39 | 40 |

start_link/0

41 |
42 |

start_link() -> {ok, Pid} | ignore | {error, Error}

43 |

44 | Starts the server 45 |

46 | 47 |

unwatch/2

48 |
49 |

unwatch(Entity::erlwitness:entity(), WatcherPid::pid()) -> ok

50 |
51 | 52 |

unwatch_by_pid/1

53 |
54 |

unwatch_by_pid(WatcherPid::pid()) -> ok

55 |
56 | 57 |

watch/2

58 |
59 |

watch(Entity::erlwitness:entity(), WatcherPid::pid()) -> boolean()

60 |
61 | 62 |

watchers_local_lookup/1

63 |
64 |

watchers_local_lookup(Entity::erlwitness:entity()) -> [pid()]

65 |
66 |
67 | 68 | 69 |

Generated by EDoc, Oct 11 2015, 19:05:56.

70 | 71 | 72 | -------------------------------------------------------------------------------- /doc/erlwitness_lobby.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module erlwitness_lobby # 4 | * [Function Index](#index) 5 | * [Function Details](#functions) 6 | 7 | __Behaviours:__ [`gen_server`](gen_server.md). 8 | 9 | 10 | ## Function Index ## 11 | 12 | 13 |
is_entity_watched/1
is_entity_watched_by/2
start_link/0 14 | Starts the server.
unwatch/2
unwatch_by_pid/1
watch/2
watchers_local_lookup/1
15 | 16 | 17 | 18 | 19 | ## Function Details ## 20 | 21 | 22 | 23 | ### is_entity_watched/1 ### 24 | 25 | 26 |

 27 | is_entity_watched(Entity::erlwitness:entity()) -> boolean()
 28 | 
29 | 30 |

31 | 32 | 33 | 34 | 35 | 36 | ### is_entity_watched_by/2 ### 37 | 38 | 39 |

 40 | is_entity_watched_by(Entity::erlwitness:entity(), WatcherPid::pid()) -> boolean()
 41 | 
42 | 43 |

44 | 45 | 46 | 47 | 48 | 49 | ### start_link/0 ### 50 | 51 | 52 |

 53 | start_link() -> {ok, Pid} | ignore | {error, Error}
 54 | 
55 | 56 |

57 | 58 | 59 | 60 | Starts the server 61 | 62 | 63 | 64 | ### unwatch/2 ### 65 | 66 | 67 |

 68 | unwatch(Entity::erlwitness:entity(), WatcherPid::pid()) -> ok
 69 | 
70 | 71 |

72 | 73 | 74 | 75 | 76 | 77 | ### unwatch_by_pid/1 ### 78 | 79 | 80 |

 81 | unwatch_by_pid(WatcherPid::pid()) -> ok
 82 | 
83 | 84 |

85 | 86 | 87 | 88 | 89 | 90 | ### watch/2 ### 91 | 92 | 93 |

 94 | watch(Entity::erlwitness:entity(), WatcherPid::pid()) -> boolean()
 95 | 
96 | 97 |

98 | 99 | 100 | 101 | 102 | 103 | ### watchers_local_lookup/1 ### 104 | 105 | 106 |

107 | watchers_local_lookup(Entity::erlwitness:entity()) -> [pid()]
108 | 
109 | 110 |

111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /doc/erlwitness_lookup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Module erlwitness_lookup 6 | 7 | 8 | 9 | 10 |
11 | 12 |

Module erlwitness_lookup

13 | 14 | 15 |

This module defines the erlwitness_lookup behaviour.
Required callback functions: lookup_global_entity/1.

16 | 17 |

Data Types

18 | 19 |

indexed_entity_ref()

20 |

indexed_entity_ref() = {ProcType::erlwitness:process_type(), Pid::pid()}

21 | 22 |
23 | 24 | 25 |

Generated by EDoc, Oct 11 2015, 19:05:56.

26 | 27 | 28 | -------------------------------------------------------------------------------- /doc/erlwitness_lookup.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module erlwitness_lookup # 4 | * [Data Types](#types) 5 | 6 | __This module defines the `erlwitness_lookup` behaviour.__ 7 |

8 | Required callback functions: `lookup_global_entity/1`. 9 | 10 | 11 | 12 | ## Data Types ## 13 | 14 | 15 | 16 | 17 | ### indexed_entity_ref() ### 18 | 19 | 20 | 21 |

22 | indexed_entity_ref() = {ProcType::erlwitness:process_type(), Pid::pid()}
23 | 
24 | 25 | 26 | -------------------------------------------------------------------------------- /doc/erlwitness_sup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Module erlwitness_sup 6 | 7 | 8 | 9 | 10 |
11 | 12 |

Module erlwitness_sup

13 | 14 | 15 |

Behaviours: supervisor.

16 | 17 |

Function Index

18 | 19 | 20 |
init/1
start_link/0
21 | 22 |

Function Details

23 | 24 |

init/1

25 |
26 |

init(X1) -> any()

27 |
28 | 29 |

start_link/0

30 |
31 |

start_link() -> any()

32 |
33 |
34 | 35 | 36 |

Generated by EDoc, Oct 11 2015, 19:05:56.

37 | 38 | 39 | -------------------------------------------------------------------------------- /doc/erlwitness_sup.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module erlwitness_sup # 4 | * [Function Index](#index) 5 | * [Function Details](#functions) 6 | 7 | __Behaviours:__ [`supervisor`](supervisor.md). 8 | 9 | 10 | ## Function Index ## 11 | 12 | 13 |
init/1
start_link/0
14 | 15 | 16 | 17 | 18 | ## Function Details ## 19 | 20 | 21 | 22 | ### init/1 ### 23 | 24 | `init(X1) -> any()` 25 | 26 | 27 | 28 | 29 | ### start_link/0 ### 30 | 31 | `start_link() -> any()` 32 | 33 | 34 | -------------------------------------------------------------------------------- /doc/erlwitness_transform.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Module erlwitness_transform 6 | 7 | 8 | 9 | 10 |
11 | 12 |

Module erlwitness_transform

13 | 14 | 15 |
16 | 17 | 18 |

Generated by EDoc, Oct 11 2015, 19:05:56.

19 | 20 | 21 | -------------------------------------------------------------------------------- /doc/erlwitness_transform.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module erlwitness_transform # 4 | 5 | 6 | -------------------------------------------------------------------------------- /doc/erlwitness_watcher.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Module erlwitness_watcher 6 | 7 | 8 | 9 | 10 |
11 | 12 |

Module erlwitness_watcher

13 | 14 | 15 |

Behaviours: gen_server.

16 |

This module defines the erlwitness_watcher behaviour.
Required callback functions: handle_gencall_event/8, handle_gencast_event/7, handle_geninfo_event/7, handle_newstate_event/7, handle_lager_event/8.

17 | 18 |

Data Types

19 | 20 |

dbg_fun()

21 |

dbg_fun() = fun((FuncState::dbg_fun_state(), Event::any(), ProcName::any()) -> NewFuncState::dbg_fun_state())

22 | 23 | 24 |

dbg_fun_state()

25 |

dbg_fun_state() = active | done

26 | 27 | 28 |

handler_return()

29 |

handler_return() = {noreply, NewState::term()} | {noreply, NewState::term(), timeout() | hibernate} | {stop, Reason::term(), NewState::term()}

30 | 31 | 32 |

Function Index

33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 |
get_entity_dbg_options/3
install_dbg_fun/4
report_init/7
report_lager_event/12
start/3
start/4
start_link/3
start_link/4
unwatch/1
unwatch_all/0
watch/1
45 | 46 |

Function Details

47 | 48 |

get_entity_dbg_options/3

49 |
50 |

get_entity_dbg_options(Entity::erlwitness:entity(), EntityProcType::erlwitness:process_type(), Watchers::[pid()]) -> [{install, {dbg_fun(), dbg_fun_state()}}]

51 |
52 | 53 |

install_dbg_fun/4

54 |
55 |

install_dbg_fun(Entity::erlwitness:entity(), EntityProcType::erlwitness:process_type(), EntityPid::pid(), Watcher::pid()) -> any()

56 |
57 | 58 |

report_init/7

59 |
60 |

report_init(Watcher, Timestamp, Entity, EntityPid, EntityProcType, EntityProcName, EntityProcState) -> any()

61 |
62 | 63 |

report_lager_event/12

64 |
65 |

report_lager_event(Watcher, Timestamp, Entity, EntityPid, EntityProcType, EntityProcName, LagerModule, LagerFunction, LagerArgs, CodeModule, CodeFunction, CodeLine) -> any()

66 |
67 | 68 |

start/3

69 |
70 |

start(Entities, WatcherModule, WatcherArgs) -> any()

71 |
72 | 73 |

start/4

74 |
75 |

start(Name, Entities, WatcherModule, WatcherArgs) -> any()

76 |
77 | 78 |

start_link/3

79 |
80 |

start_link(Entities, WatcherModule, WatcherArgs) -> any()

81 |
82 | 83 |

start_link/4

84 |
85 |

start_link(Name, Entities, WatcherModule, WatcherArgs) -> any()

86 |
87 | 88 |

unwatch/1

89 |
90 |

unwatch(Entity::erlwitness:entity()) -> ok

91 |
92 | 93 |

unwatch_all/0

94 |
95 |

unwatch_all() -> ok

96 |
97 | 98 |

watch/1

99 |
100 |

watch(Entity::erlwitness:entity()) -> ok

101 |
102 |
103 | 104 | 105 |

Generated by EDoc, Oct 11 2015, 19:05:56.

106 | 107 | 108 | -------------------------------------------------------------------------------- /doc/erlwitness_watcher.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module erlwitness_watcher # 4 | * [Data Types](#types) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | __Behaviours:__ [`gen_server`](gen_server.md). 9 | 10 | __This module defines the `erlwitness_watcher` behaviour.__ 11 |

12 | Required callback functions: `handle_gencall_event/8`, `handle_gencast_event/7`, `handle_geninfo_event/7`, `handle_newstate_event/7`, `handle_lager_event/8`. 13 | 14 | 15 | 16 | ## Data Types ## 17 | 18 | 19 | 20 | 21 | ### dbg_fun() ### 22 | 23 | 24 | 25 |

 26 | dbg_fun() = fun((FuncState::dbg_fun_state(), Event::any(), ProcName::any()) -> NewFuncState::dbg_fun_state())
 27 | 
28 | 29 | 30 | 31 | 32 | 33 | ### dbg_fun_state() ### 34 | 35 | 36 | 37 |

 38 | dbg_fun_state() = active | done
 39 | 
40 | 41 | 42 | 43 | 44 | 45 | ### handler_return() ### 46 | 47 | 48 | 49 |

 50 | handler_return() = {noreply, NewState::term()} | {noreply, NewState::term(), timeout() | hibernate} | {stop, Reason::term(), NewState::term()}
 51 | 
52 | 53 | 54 | 55 | 56 | ## Function Index ## 57 | 58 | 59 |
get_entity_dbg_options/3
install_dbg_fun/4
report_init/7
report_lager_event/12
start/3
start/4
start_link/3
start_link/4
unwatch/1
unwatch_all/0
watch/1
60 | 61 | 62 | 63 | 64 | ## Function Details ## 65 | 66 | 67 | 68 | ### get_entity_dbg_options/3 ### 69 | 70 | 71 |

 72 | get_entity_dbg_options(Entity::erlwitness:entity(), EntityProcType::erlwitness:process_type(), Watchers::[pid()]) -> [{install, {dbg_fun(), dbg_fun_state()}}]
 73 | 
74 | 75 |

76 | 77 | 78 | 79 | 80 | 81 | ### install_dbg_fun/4 ### 82 | 83 | 84 |

 85 | install_dbg_fun(Entity::erlwitness:entity(), EntityProcType::erlwitness:process_type(), EntityPid::pid(), Watcher::pid()) -> any()
 86 | 
87 | 88 |

89 | 90 | 91 | 92 | 93 | 94 | ### report_init/7 ### 95 | 96 | `report_init(Watcher, Timestamp, Entity, EntityPid, EntityProcType, EntityProcName, EntityProcState) -> any()` 97 | 98 | 99 | 100 | 101 | ### report_lager_event/12 ### 102 | 103 | `report_lager_event(Watcher, Timestamp, Entity, EntityPid, EntityProcType, EntityProcName, LagerModule, LagerFunction, LagerArgs, CodeModule, CodeFunction, CodeLine) -> any()` 104 | 105 | 106 | 107 | 108 | ### start/3 ### 109 | 110 | `start(Entities, WatcherModule, WatcherArgs) -> any()` 111 | 112 | 113 | 114 | 115 | ### start/4 ### 116 | 117 | `start(Name, Entities, WatcherModule, WatcherArgs) -> any()` 118 | 119 | 120 | 121 | 122 | ### start_link/3 ### 123 | 124 | `start_link(Entities, WatcherModule, WatcherArgs) -> any()` 125 | 126 | 127 | 128 | 129 | ### start_link/4 ### 130 | 131 | `start_link(Name, Entities, WatcherModule, WatcherArgs) -> any()` 132 | 133 | 134 | 135 | 136 | ### unwatch/1 ### 137 | 138 | 139 |

140 | unwatch(Entity::erlwitness:entity()) -> ok
141 | 
142 | 143 |

144 | 145 | 146 | 147 | 148 | 149 | ### unwatch_all/0 ### 150 | 151 | 152 |

153 | unwatch_all() -> ok
154 | 
155 | 156 |

157 | 158 | 159 | 160 | 161 | 162 | ### watch/1 ### 163 | 164 | 165 |

166 | watch(Entity::erlwitness:entity()) -> ok
167 | 
168 | 169 |

170 | 171 | 172 | 173 | -------------------------------------------------------------------------------- /doc/modules-frame.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Overview 5 | 6 | 7 | 8 |

Modules

9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
erlwitness
erlwitness_app
erlwitness_conf
erlwitness_entity
erlwitness_index_serv
erlwitness_lobby
erlwitness_lookup
erlwitness_sup
erlwitness_transform
erlwitness_watcher
20 | 21 | -------------------------------------------------------------------------------- /doc/overview-summary.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | erlwitness 6 | 7 | 8 | 9 | 10 |

erlwitness

11 |

Copyright © 2015 Guilherme Andrade

12 |

Version: 1.0.0

13 |

Authors: Guilherme Andrade (erlwitness(at)gandrade(dot)net).

14 |

erlwitness: Semantic process groups watchtower.

15 | 16 |
17 | 18 |

What does it do?

19 | erlwitness allows one to funnel both gen_server events (init, calls, casts, infos, state changes) and lager calls specific to identified entities into arbitrary watcher processes.

20 | 21 |

Why?

22 | For example: remote activity tracers for sizeable software development teams (instead of _hey, may you please check the log?_)

23 | 24 |

Main features

25 | - Multiple watchers can spy on the same entity; 26 | - A single watcher can spy on multiple entities; 27 | - An entity can consist of zero or more processes that come and go; 28 | - Watching works transparently on both local and remote nodes; 29 | - Watching can be initiated both before and after entity processes have been spawned. 30 | - Lager calls are (optionally) watchable through a parse_transform.

31 | 32 |

How do I use it?

33 | There are two main parts to an ordinary setup:

34 | 35 |

1) Adapt your existing entities' gen_server:start / gen_server:start_link / init calls to make use of the relevant registration flow:

36 | 37 |
 38 | % ....
 39 | -behaviour(gen_server).
 40 | % ...
 41 | %% Uncomment to allow for lager tracing
 42 | %-compile([{parse_transform, erlwitness_transform}]).
 43 | 
 44 | start_link(Person, Files, LuckyNumber) ->
 45 |     {WrappedArgs, StartOptions} = erlwitness:get_start_extras({person, Person},
 46 |                                                               person_file_serv,
 47 |                                                               [Files, LuckyNumber]),
 48 |     gen_server:start_link(?MODULE, WrappedArgs, StartOptions).
 49 | 
 50 | init(WrappedArgs) ->
 51 |     [Files, LuckyNumber] = erlwitness:unwrap_init_args(WrappedArgs),
 52 |     InitResult = {ok, #state{files = Files,
 53 |                              lucky_number = LuckyNumber}},
 54 |     erlwitness:finalize_init(WrappedArgs, InitResult).
 55 | % ....
 56 | 
57 | 58 |

2) Code your own watcher process which will implement both gen_server and erlwitness_watcher behaviours:

59 | 60 |
 61 | % ...
 62 | -behaviour(gen_server).
 63 | -behaviour(erlwitness_watcher).
 64 | % ...
 65 | 
 66 | start_link(Person) ->
 67 |     erlwitness_watcher:start_link([{person, Person}], ?MODULE, []).
 68 | 
 69 | % ...
 70 | 
 71 | handle_gencall_event(Timestamp, {person, Person}, PersonPid, PersonProcType,
 72 |                      PersonProcName, Call, From, State) ->
 73 |     io:format("Here's a call: ~p~n", [{Timestamp, Person, PersonPid,
 74 |                                        PersonProcType, Call, From}]),
 75 |     {noreply, State}.
 76 | 
 77 | handle_gencast_event(Timestamp, {person, Person}, PersonPid, PersonProcType,
 78 |                      PersonProcName, Cast, State) ->
 79 |     io:format("Here's a cast: ~p~n", [{Timestamp, Person, PersonPid,
 80 |                                        PersonProcType, Cast}]),
 81 |     {noreply, State}.
 82 | 
 83 | handle_geninfo_event(Timestamp, {person, Person}, PersonPid, PersonProcType,
 84 |                      PersonProcName, Info, State) ->
 85 |     io:format("Here's an info: ~p~n", [{Timestamp, Person, PersonPid,
 86 |                                        PersonProcType, Info}]),
 87 |     {noreply, State}.
 88 | 
 89 | handle_newstate_event(Timestamp, {person, Person}, PersonPid, PersonProcType,
 90 |                       PersonProcName, PersonState, State) ->
 91 |     io:format("Here's a new state: ~p~n", [{Timestamp, Person, PersonPid,
 92 |                                             PersonProcType, PersonState}]),
 93 |     {noreply, State}.
 94 | 
 95 | handle_lager_event(Timestamp, {person, Person}, PersonPid, PersonProcType,
 96 |                    PersonProcName, LagerMFA, LagerDebugInfo, State) ->
 97 |     {_LagerModule, LagerLevel, LagerArgs} = LagerMFA,
 98 |     {CodeModule, CodeFunction, CodeLine} = LagerDebugInfo,
 99 |     io:format("Here's a lager message from ~p: ~p @ ~p~n",
100 |              [{Timestamp, Person, PersonPid, PersonProcType, PersonState},
101 |               {LagerLevel, LagerArgs}, {CodeModule, CodeFunction, CodeLine}]),
102 |     {noreply, State}.
103 | 
104 | % ..
105 | 
106 | 107 |

.. and optionally, 3) Reuse your existing entity registration code by implementing the erlwitness_lookup behaviour and adjusting erlwitness app.config acordingly.

108 | 109 |
110 | % ...
111 | -behaviour(erlwitness_lookup).
112 | % ...
113 | 
114 | lookup_global_entity({person, Person}) ->
115 |    PeopleInfo = mnesia:dirty_read(person_info, Person),
116 |    [{person_file_serv, PersonInfo#person_info.file_serv_pid} || PersonInfo <- Pids].
117 | % ...
118 | 
119 | 120 |

Full implementations under _example/_.

121 | 122 |

How do I configure it?

123 | app.config:

124 | 125 |
126 | [{erlwitness, [
127 |             % Optional; module implementing 'erlwitness_lookup' behaviour
128 |             %{entity_lookup_module, erlwitness_index_serv},
129 |             %
130 |             % Optional; defaults to 10 * NumberOfSchedulers
131 |             %{erlwitness_index_serv_count, N :: pos_integer()}
132 |             ]}
133 |     ].
134 | 
135 | 136 |

How do I globally enable the lager events parse_transform?

137 | Compiler flags: 138 |
139 | {parse_transform, erlwitness_transform}
140 | 
141 | 142 |

What about the overhead?

143 | This software is built on the premise that watchers are few, rare, and mostly limited 144 | to development environments; therefore watching is heavy and privacy is light. 145 | - Unwatched processes: basic ETS lookup just before the process starts + a single process dictionary check for every lager call; 146 | - Watched processes: one OTP debug fun per watcher (see [sys(3)](http://www.erlang.org/doc/man/sys.html)) + lager calls redirection.

147 | 148 |

Why are there no termination events?

149 | This software is a building block; tracking and monitoring entity processes on a watcher is left as an exercise for the reader.

150 | 151 |

What's the deal with entity registration?

152 | erlwitness_index_serv is bundled as an out-of-the-box solution; it might not, however, suit your needs. Please mind that it will, for each spawned entity: 153 | - 1) Write a new entry to an ETS table (on erlwitness:finalize_init/2); 154 | - 2) Create a monitor from a specific worker on the indexing pool; 155 | - 3) Trigger the monitor on termination in order to unregister the entity.

156 | 157 |

In order to lax this potential bottleneck, the indexing pool will spawn (_10 x NumberOfSchedulers_) monitoring processes by default, with one separate ETS table per indexer, and NumberOfSchedulers generally (but not always) corresponding to the number of CPU thread queues (usually _NumOfCPUs x NumOfCoresPerCPU x (1 + HyperthreadPerCore)_).

158 | 159 |

However, if you already have your own global process directory in place, it's recommended that you use it instead.

160 | 161 |

Future plans

162 | - Clean up the current lager events mess; 163 | - Offline watcher scheduling with later history retrieving. 164 | 165 |
166 | 167 |

Generated by EDoc, Oct 11 2015, 19:05:56.

168 | 169 | 170 | -------------------------------------------------------------------------------- /doc/overview.edoc: -------------------------------------------------------------------------------- 1 | @author Guilherme Andrade 2 | @copyright 2015 Guilherme Andrade 3 | @version 1.0.0 4 | @title erlwitness 5 | @doc `erlwitness': Semantic process groups watchtower. 6 | 7 |
8 | 9 | == What does it do? == 10 | `erlwitness' allows one to funnel both `gen_server' events (init, calls, casts, infos, state changes) and `lager' calls specific to identified entities into arbitrary watcher processes. 11 | 12 | == Why? == 13 | For example: remote activity tracers for sizeable software development teams (instead of _hey, may you please check the log?_) 14 | 15 | == Main features == 16 | - Multiple watchers can spy on the same entity; 17 | - A single watcher can spy on multiple entities; 18 | - An entity can consist of zero or more processes that come and go; 19 | - Watching works transparently on both local and remote nodes; 20 | - Watching can be initiated both before and after entity processes have been spawned. 21 | - Lager calls are (optionally) watchable through a parse_transform. 22 | 23 | == How do I use it? == 24 | There are two main parts to an ordinary setup: 25 | 26 | 1) Adapt your existing entities' gen_server:start / gen_server:start_link / init calls to make use of the relevant registration flow: 27 | 28 |
 29 | % ....
 30 | -behaviour(gen_server).
 31 | % ...
 32 | %% Uncomment to allow for lager tracing
 33 | %-compile([{parse_transform, erlwitness_transform}]).
 34 | 
 35 | start_link(Person, Files, LuckyNumber) ->
 36 |     {WrappedArgs, StartOptions} = erlwitness:get_start_extras({person, Person},
 37 |                                                               person_file_serv,
 38 |                                                               [Files, LuckyNumber]),
 39 |     gen_server:start_link(?MODULE, WrappedArgs, StartOptions).
 40 | 
 41 | init(WrappedArgs) ->
 42 |     [Files, LuckyNumber] = erlwitness:unwrap_init_args(WrappedArgs),
 43 |     InitResult = {ok, #state{files = Files,
 44 |                              lucky_number = LuckyNumber}},
 45 |     erlwitness:finalize_init(WrappedArgs, InitResult).
 46 | % ....
 47 | 
48 | 49 | 2) Code your own watcher process which will implement both `gen_server' and `erlwitness_watcher' behaviours: 50 | 51 |
 52 | % ...
 53 | -behaviour(gen_server).
 54 | -behaviour(erlwitness_watcher).
 55 | % ...
 56 | 
 57 | start_link(Person) ->
 58 |     erlwitness_watcher:start_link([{person, Person}], ?MODULE, []).
 59 | 
 60 | % ...
 61 | 
 62 | handle_gencall_event(Timestamp, {person, Person}, PersonPid, PersonProcType,
 63 |                      PersonProcName, Call, From, State) ->
 64 |     io:format("Here's a call: ~p~n", [{Timestamp, Person, PersonPid,
 65 |                                        PersonProcType, Call, From}]),
 66 |     {noreply, State}.
 67 | 
 68 | handle_gencast_event(Timestamp, {person, Person}, PersonPid, PersonProcType,
 69 |                      PersonProcName, Cast, State) ->
 70 |     io:format("Here's a cast: ~p~n", [{Timestamp, Person, PersonPid,
 71 |                                        PersonProcType, Cast}]),
 72 |     {noreply, State}.
 73 | 
 74 | handle_geninfo_event(Timestamp, {person, Person}, PersonPid, PersonProcType,
 75 |                      PersonProcName, Info, State) ->
 76 |     io:format("Here's an info: ~p~n", [{Timestamp, Person, PersonPid,
 77 |                                        PersonProcType, Info}]),
 78 |     {noreply, State}.
 79 | 
 80 | handle_newstate_event(Timestamp, {person, Person}, PersonPid, PersonProcType,
 81 |                       PersonProcName, PersonState, State) ->
 82 |     io:format("Here's a new state: ~p~n", [{Timestamp, Person, PersonPid,
 83 |                                             PersonProcType, PersonState}]),
 84 |     {noreply, State}.
 85 | 
 86 | handle_lager_event(Timestamp, {person, Person}, PersonPid, PersonProcType,
 87 |                    PersonProcName, LagerMFA, LagerDebugInfo, State) ->
 88 |     {_LagerModule, LagerLevel, LagerArgs} = LagerMFA,
 89 |     {CodeModule, CodeFunction, CodeLine} = LagerDebugInfo,
 90 |     io:format("Here's a lager message from ~p: ~p @ ~p~n",
 91 |              [{Timestamp, Person, PersonPid, PersonProcType, PersonState},
 92 |               {LagerLevel, LagerArgs}, {CodeModule, CodeFunction, CodeLine}]),
 93 |     {noreply, State}.
 94 | 
 95 | % ..
 96 | 
97 | 98 | .. and optionally, 3) Reuse your existing entity registration code by implementing the `erlwitness_lookup' behaviour and adjusting `erlwitness' app.config acordingly. 99 | 100 |
101 | % ...
102 | -behaviour(erlwitness_lookup).
103 | % ...
104 | 
105 | lookup_global_entity({person, Person}) ->
106 |    PeopleInfo = mnesia:dirty_read(person_info, Person),
107 |    [{person_file_serv, PersonInfo#person_info.file_serv_pid} || PersonInfo <- Pids].
108 | % ...
109 | 
110 | 111 | Full implementations under _example/_. 112 | 113 | == How do I configure it? == 114 | app.config: 115 | 116 |
117 | [{erlwitness, [
118 |             % Optional; module implementing 'erlwitness_lookup' behaviour
119 |             %{entity_lookup_module, erlwitness_index_serv},
120 |             %
121 |             % Optional; defaults to 10 * NumberOfSchedulers
122 |             %{erlwitness_index_serv_count, N :: pos_integer()}
123 |             ]}
124 |     ].
125 | 
126 | 127 | == How do I globally enable the lager events parse_transform? == 128 | Compiler flags: 129 |
130 | {parse_transform, erlwitness_transform}
131 | 
132 | 133 | == What about the overhead? == 134 | This software is built on the premise that watchers are few, rare, and mostly limited 135 | to development environments; therefore watching is heavy and privacy is light. 136 | - Unwatched processes: basic ETS lookup just before the process starts + a single process dictionary check for every `lager' call; 137 | - Watched processes: one OTP debug fun per watcher (see [sys(3)](http://www.erlang.org/doc/man/sys.html)) + `lager' calls redirection. 138 | 139 | == Why are there no termination events? == 140 | This software is a building block; tracking and monitoring entity processes on a watcher is left as an exercise for the reader. 141 | 142 | == What's the deal with entity registration? == 143 | `erlwitness_index_serv' is bundled as an out-of-the-box solution; it might not, however, suit your needs. Please mind that it will, for each spawned entity: 144 | - 1) Write a new entry to an ETS table (on `erlwitness:finalize_init/2'); 145 | - 2) Create a monitor from a specific worker on the indexing pool; 146 | - 3) Trigger the monitor on termination in order to unregister the entity. 147 | 148 | In order to lax this potential bottleneck, the indexing pool will spawn (_10 x NumberOfSchedulers_) monitoring processes by default, with one separate ETS table per indexer, and NumberOfSchedulers generally (but not always) corresponding to the number of CPU thread queues (usually _NumOfCPUs x NumOfCoresPerCPU x (1 + HyperthreadPerCore)_). 149 | 150 | However, if you already have your own global process directory in place, it's recommended that you use it instead. 151 | 152 | == Future plans == 153 | - Clean up the current lager events mess; 154 | - Offline watcher scheduling with later history retrieving. 155 | -------------------------------------------------------------------------------- /doc/packages-frame.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Overview 5 | 6 | 7 | 8 |

Packages

9 |
10 | 11 | -------------------------------------------------------------------------------- /doc/stylesheet.css: -------------------------------------------------------------------------------- 1 | /* standard EDoc style sheet */ 2 | body { 3 | font-family: Verdana, Arial, Helvetica, sans-serif; 4 | margin-left: .25in; 5 | margin-right: .2in; 6 | margin-top: 0.2in; 7 | margin-bottom: 0.2in; 8 | color: #000000; 9 | background-color: #ffffff; 10 | } 11 | h1,h2 { 12 | margin-left: -0.2in; 13 | } 14 | div.navbar { 15 | background-color: #add8e6; 16 | padding: 0.2em; 17 | } 18 | h2.indextitle { 19 | padding: 0.4em; 20 | background-color: #add8e6; 21 | } 22 | h3.function,h3.typedecl { 23 | background-color: #add8e6; 24 | padding-left: 1em; 25 | } 26 | div.spec { 27 | margin-left: 2em; 28 | background-color: #eeeeee; 29 | } 30 | a.module,a.package { 31 | text-decoration:none 32 | } 33 | a.module:hover,a.package:hover { 34 | background-color: #eeeeee; 35 | } 36 | ul.definitions { 37 | list-style-type: none; 38 | } 39 | ul.index { 40 | list-style-type: none; 41 | background-color: #eeeeee; 42 | } 43 | 44 | /* 45 | * Minor style tweaks 46 | */ 47 | ul { 48 | list-style-type: square; 49 | } 50 | table { 51 | border-collapse: collapse; 52 | } 53 | td { 54 | padding: 3 55 | } 56 | -------------------------------------------------------------------------------- /doc/utf8/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # erlwitness # 4 | 5 | Copyright (c) 2015 Guilherme Andrade 6 | 7 | __Version:__ 1.0.0 8 | 9 | __Authors:__ Guilherme Andrade ([`erlwitness(at)gandrade(dot)net`](mailto:erlwitness(at)gandrade(dot)net)). 10 | 11 | `erlwitness`: Semantic process groups watchtower. 12 | --------- 13 | 14 | 15 | ### What does it do? ### 16 | 17 | 18 | `erlwitness` allows one to funnel both `gen_server` events (init, calls, casts, infos, state changes) and `lager` calls specific to identified entities into arbitrary watcher processes. 19 | 20 | 21 | ### Why? ### 22 | 23 | 24 | For example: remote activity tracers for sizeable software development teams (instead of _hey, may you please check the log?_) 25 | 26 | 27 | ### Main features ### 28 | 29 | 30 | - Multiple watchers can spy on the same entity; 31 | - A single watcher can spy on multiple entities; 32 | - An entity can consist of zero or more processes that come and go; 33 | - Watching works transparently on both local and remote nodes; 34 | - Watching can be initiated both before and after entity processes have been spawned. 35 | - Lager calls are (optionally) watchable through a parse_transform. 36 | 37 | 38 | ### How do I use it? ### 39 | 40 | 41 | There are two main parts to an ordinary setup: 42 | 43 | 1) Adapt your existing entities' gen_server:start / gen_server:start_link / init calls to make use of the relevant registration flow: 44 | 45 | ```erlang 46 | 47 | % .... 48 | -behaviour(gen_server). 49 | % ... 50 | %% Uncomment to allow for lager tracing 51 | %-compile([{parse_transform, erlwitness_transform}]). 52 | 53 | start_link(Person, Files, LuckyNumber) -> 54 | {WrappedArgs, StartOptions} = erlwitness:get_start_extras({person, Person}, 55 | person_file_serv, 56 | [Files, LuckyNumber]), 57 | gen_server:start_link(?MODULE, WrappedArgs, StartOptions). 58 | 59 | init(WrappedArgs) -> 60 | [Files, LuckyNumber] = erlwitness:unwrap_init_args(WrappedArgs), 61 | InitResult = {ok, #state{files = Files, 62 | lucky_number = LuckyNumber}}, 63 | erlwitness:finalize_init(WrappedArgs, InitResult). 64 | % .... 65 | 66 | ``` 67 | 68 | 2) Code your own watcher process which will implement both `gen_server` and `erlwitness_watcher` behaviours: 69 | 70 | ```erlang 71 | 72 | % ... 73 | -behaviour(gen_server). 74 | -behaviour(erlwitness_watcher). 75 | % ... 76 | 77 | start_link(Person) -> 78 | erlwitness_watcher:start_link([{person, Person}], ?MODULE, []). 79 | 80 | % ... 81 | 82 | handle_gencall_event(Timestamp, {person, Person}, PersonPid, PersonProcType, 83 | PersonProcName, Call, From, State) -> 84 | io:format("Here's a call: ~p~n", [{Timestamp, Person, PersonPid, 85 | PersonProcType, Call, From}]), 86 | {noreply, State}. 87 | 88 | handle_gencast_event(Timestamp, {person, Person}, PersonPid, PersonProcType, 89 | PersonProcName, Cast, State) -> 90 | io:format("Here's a cast: ~p~n", [{Timestamp, Person, PersonPid, 91 | PersonProcType, Cast}]), 92 | {noreply, State}. 93 | 94 | handle_geninfo_event(Timestamp, {person, Person}, PersonPid, PersonProcType, 95 | PersonProcName, Info, State) -> 96 | io:format("Here's an info: ~p~n", [{Timestamp, Person, PersonPid, 97 | PersonProcType, Info}]), 98 | {noreply, State}. 99 | 100 | handle_newstate_event(Timestamp, {person, Person}, PersonPid, PersonProcType, 101 | PersonProcName, PersonState, State) -> 102 | io:format("Here's a new state: ~p~n", [{Timestamp, Person, PersonPid, 103 | PersonProcType, PersonState}]), 104 | {noreply, State}. 105 | 106 | handle_lager_event(Timestamp, {person, Person}, PersonPid, PersonProcType, 107 | PersonProcName, LagerMFA, LagerDebugInfo, State) -> 108 | {_LagerModule, LagerLevel, LagerArgs} = LagerMFA, 109 | {CodeModule, CodeFunction, CodeLine} = LagerDebugInfo, 110 | io:format("Here's a lager message from ~p: ~p @ ~p~n", 111 | [{Timestamp, Person, PersonPid, PersonProcType, PersonState}, 112 | {LagerLevel, LagerArgs}, {CodeModule, CodeFunction, CodeLine}]), 113 | {noreply, State}. 114 | 115 | % .. 116 | 117 | ``` 118 | 119 | .. and optionally, 3) Reuse your existing entity registration code by implementing the `erlwitness_lookup` behaviour and adjusting `erlwitness` app.config acordingly. 120 | 121 | ```erlang 122 | 123 | % ... 124 | -behaviour(erlwitness_lookup). 125 | % ... 126 | 127 | lookup_global_entity({person, Person}) -> 128 | PeopleInfo = mnesia:dirty_read(person_info, Person), 129 | [{person_file_serv, PersonInfo#person_info.file_serv_pid} || PersonInfo <- Pids]. 130 | % ... 131 | 132 | ``` 133 | 134 | Full implementations under _example/_. 135 | 136 | 137 | ### How do I configure it? ### 138 | 139 | 140 | app.config: 141 | 142 | ```erlang 143 | 144 | [{erlwitness, [ 145 | % Optional; module implementing 'erlwitness_lookup' behaviour 146 | %{entity_lookup_module, erlwitness_index_serv}, 147 | % 148 | % Optional; defaults to 10 * NumberOfSchedulers 149 | %{erlwitness_index_serv_count, N :: pos_integer()} 150 | ]} 151 | ]. 152 | 153 | ``` 154 | 155 | 156 | ### How do I globally enable the lager events parse_transform? ### 157 | 158 | Compiler flags: 159 | 160 | ```erlang 161 | 162 | {parse_transform, erlwitness_transform} 163 | 164 | ``` 165 | 166 | 167 | ### What about the overhead? ### 168 | 169 | 170 | This software is built on the premise that watchers are few, rare, and mostly limited 171 | to development environments; therefore watching is heavy and privacy is light. 172 | - Unwatched processes: basic ETS lookup just before the process starts + a single process dictionary check for every `lager` call; 173 | - Watched processes: one OTP debug fun per watcher (see [sys(3)](http://www.erlang.org/doc/man/sys.html)) + `lager` calls redirection. 174 | 175 | 176 | ### Why are there no termination events? ### 177 | 178 | 179 | This software is a building block; tracking and monitoring entity processes on a watcher is left as an exercise for the reader. 180 | 181 | 182 | ### What's the deal with entity registration? ### 183 | 184 | 185 | `erlwitness_index_serv` is bundled as an out-of-the-box solution; it might not, however, suit your needs. Please mind that it will, for each spawned entity: 186 | - 1) Write a new entry to an ETS table (on `erlwitness:finalize_init/2`); 187 | - 2) Create a monitor from a specific worker on the indexing pool; 188 | - 3) Trigger the monitor on termination in order to unregister the entity. 189 | 190 | In order to lax this potential bottleneck, the indexing pool will spawn (_10 x NumberOfSchedulers_) monitoring processes by default, with one separate ETS table per indexer, and NumberOfSchedulers generally (but not always) corresponding to the number of CPU thread queues (usually _NumOfCPUs x NumOfCoresPerCPU x (1 + HyperthreadPerCore)_). 191 | 192 | However, if you already have your own global process directory in place, it's recommended that you use it instead. 193 | 194 | 195 | ### Future plans ### 196 | 197 | - Clean up the current lager events mess; 198 | - Offline watcher scheduling with later history retrieving. 199 | 200 | 201 | ## Modules ## 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 |
erlwitness
erlwitness_app
erlwitness_conf
erlwitness_entity
erlwitness_index_serv
erlwitness_lobby
erlwitness_lookup
erlwitness_sup
erlwitness_transform
erlwitness_watcher
215 | 216 | -------------------------------------------------------------------------------- /example/person_file_serv.erl: -------------------------------------------------------------------------------- 1 | % vim: set expandtab softtabstop=4 shiftwidth=4: 2 | -module(person_file_serv). 3 | -author('Guilherme Andrade '). 4 | 5 | -compile([{parse_transform, erlwitness_transform}]). 6 | -compile([{parse_transform, lager_transform}]). 7 | 8 | -behaviour(gen_server). 9 | 10 | -export([start_link/3, 11 | start/3]). 12 | -export([init/1, 13 | handle_call/3, 14 | handle_cast/2, 15 | handle_info/2, 16 | terminate/2, 17 | code_change/3]). 18 | 19 | 20 | -record(state, { 21 | files :: term(), 22 | lucky_number :: number() 23 | }). 24 | 25 | 26 | start_link(Person, Files, LuckyNumber) -> 27 | {WrappedArgs, StartOptions} = erlwitness:get_start_extras({person, Person}, person_file_serv, 28 | [Files, LuckyNumber]), 29 | gen_server:start_link(?MODULE, WrappedArgs, StartOptions). 30 | 31 | start(Person, Files, LuckyNumber) -> 32 | {WrappedArgs, StartOptions} = erlwitness:get_start_extras({person, Person}, person_file_serv, 33 | [Files, LuckyNumber]), 34 | gen_server:start(?MODULE, WrappedArgs, StartOptions). 35 | 36 | 37 | init(WrappedArgs) -> 38 | [Files, LuckyNumber] = erlwitness:unwrap_init_args(WrappedArgs), 39 | InitResult = {ok, #state{files = Files, 40 | lucky_number = LuckyNumber}}, 41 | erlwitness:finalize_init(WrappedArgs, InitResult). 42 | 43 | handle_call(_Request, _From, State) -> 44 | {noreply, State}. 45 | 46 | 47 | handle_cast(log_something, State) -> 48 | lager:debug("this is a message"), 49 | {noreply, State}; 50 | 51 | handle_cast(_Msg, State) -> 52 | {noreply, State}. 53 | 54 | 55 | handle_info(_Info, State) -> 56 | {noreply, State}. 57 | 58 | terminate(_Reason, _State) -> 59 | ok. 60 | 61 | code_change(_OldVsn, State, _Extra) -> 62 | {ok, State}. 63 | 64 | 65 | -------------------------------------------------------------------------------- /example/person_watcher.erl: -------------------------------------------------------------------------------- 1 | % vim: set expandtab softtabstop=4 shiftwidth=4: 2 | -module(person_watcher). 3 | -author('Guilherme Andrade '). 4 | 5 | -behaviour(gen_server). 6 | -behaviour(erlwitness_watcher). 7 | 8 | -export([start_link/1, 9 | start/1]). 10 | 11 | -export([init/1, 12 | handle_gencall_event/8, 13 | handle_gencast_event/7, 14 | handle_geninfo_event/7, 15 | handle_newstate_event/7, 16 | handle_lager_event/8, 17 | handle_call/3, 18 | handle_cast/2, 19 | handle_info/2, 20 | terminate/2, 21 | code_change/3]). 22 | 23 | 24 | -record(state, { 25 | event_count = 0 :: non_neg_integer() 26 | }). 27 | 28 | 29 | start_link(Person) -> 30 | erlwitness_watcher:start_link([{person, Person}], ?MODULE, []). 31 | 32 | start(Person) -> 33 | erlwitness_watcher:start([{person, Person}], ?MODULE, []). 34 | 35 | 36 | init([]) -> 37 | {ok, #state{}}. 38 | 39 | 40 | handle_gencall_event(Timestamp, {person, Person}, PersonPid, person_file_serv=PersonProcType, PersonProcName, Call, From, #state{}=State) -> 41 | handle_event(Timestamp, {person, Person}, PersonPid, PersonProcType, PersonProcName, {call, Call, From}, State), 42 | {noreply, State}. 43 | 44 | handle_gencast_event(Timestamp, {person, Person}, PersonPid, person_file_serv=PersonProcType, PersonProcName, Cast, #state{}=State) -> 45 | handle_event(Timestamp, {person, Person}, PersonPid, PersonProcType, PersonProcName, {cast, Cast}, State), 46 | {noreply, State}. 47 | 48 | handle_geninfo_event(Timestamp, {person, Person}, PersonPid, person_file_serv=PersonProcType, PersonProcName, Info, #state{}=State) -> 49 | handle_event(Timestamp, {person, Person}, PersonPid, PersonProcType, PersonProcName, {info, Info}, State), 50 | {noreply, State}. 51 | 52 | handle_newstate_event(Timestamp, {person, Person}, PersonPid, person_file_serv=PersonProcType, PersonProcName, PersonProcState, #state{}=State) -> 53 | handle_event(Timestamp, {person, Person}, PersonPid, PersonProcType, PersonProcName, {new_state, PersonProcState}, State), 54 | {noreply, State}. 55 | 56 | handle_lager_event(Timestamp, {person, Person}, EntityPid, EntityProcType, EntityProcName, 57 | {lager=_LagerModule, debug=_LagerFunction, _LagerArguments}=LagerMFA, 58 | {person_file_serv=_CodeModule, handle_cast=_CodeFunction, _CodeLine}=LagerDebugInfo, 59 | #state{}=State) -> 60 | handle_event(Timestamp, {person, Person}, EntityPid, EntityProcType, EntityProcName, {lager, LagerMFA, LagerDebugInfo}, State), 61 | {noreply, State}. 62 | 63 | 64 | handle_call(_Request, _From, #state{}=State) -> 65 | {noreply, State}. 66 | 67 | 68 | handle_cast(_Msg, #state{}=State) -> 69 | {noreply, State}. 70 | 71 | 72 | handle_info(_Info, #state{}=State) -> 73 | {noreply, State}. 74 | 75 | 76 | terminate(_Reason, #state{}) -> 77 | ok. 78 | 79 | 80 | code_change(_OldVsn, State, _Extra) -> 81 | {ok, State}. 82 | 83 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 84 | handle_event(Timestamp, {person, Person}, PersonPid, person_file_serv=PersonProcType, PersonProcName, Event, #state{}=State) -> 85 | io:format("example watcher received event #~p: " 86 | "timestamp ~p, person ~p, entity_pid ~p, entity_proc_type ~p, " 87 | "entity_proc_name ~p, event ~p~n", 88 | [State#state.event_count, 89 | Timestamp, Person, PersonPid, PersonProcType, PersonProcName, Event]), 90 | State#state{ event_count = State#state.event_count + 1 }. 91 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | % vim: set ft=erlang expandtab softtabstop=4 shiftwidth=4: 2 | {erl_opts, [ 3 | %debug_info 4 | warn_export_all, 5 | warn_export_vars, 6 | %warn_missing_spec, 7 | warn_obsolete_guard, 8 | warn_shadow_vars, 9 | warn_unused_import 10 | ]}. 11 | 12 | {lib_dirs, ["deps"]}. 13 | 14 | {deps, [ 15 | %{edown, ".*", {git, "git://github.com/esl/edown.git", {tag, "0.4"}}} 16 | {lager, ".*", {git, "https://github.com/basho/lager.git", {tag, "3.0.1"}}} 17 | ]}. 18 | 19 | {edoc_opts, [ 20 | %[{doclet, edown_doclet}] 21 | ]}. 22 | 23 | {plugins, [ 24 | ]}. 25 | 26 | {xref_checks, [ 27 | undefined_function_calls, 28 | undefined_functions, 29 | locals_not_used, 30 | exports_not_used, 31 | deprecated_function_calls, 32 | deprecated_functions 33 | ]}. 34 | 35 | {eunit_opts, [verbose, {skip_deps, true}]}. 36 | {eunit_exclude_deps, true}. 37 | {cover_enabled, true}. 38 | -------------------------------------------------------------------------------- /src/erlwitness.app.src: -------------------------------------------------------------------------------- 1 | % vim: set expandtab softtabstop=4 shiftwidth=4: 2 | {application, erlwitness, 3 | [ 4 | {description, "Semantic process groups watchtower"}, 5 | {vsn, "1.0.0"}, 6 | {registered, []}, 7 | {applications, [ 8 | kernel, 9 | stdlib, 10 | lager 11 | ]}, 12 | {mod, { erlwitness_app, []}}, 13 | {env, [ 14 | %{entity_lookup_module, erlwitness_index_serv}, % Module implementing 'erlwitness_lookup' behaviour 15 | %{erlwitness_index_serv_count, N :: pos_integer()} % Optional 16 | ]} 17 | ]}. 18 | -------------------------------------------------------------------------------- /src/erlwitness.erl: -------------------------------------------------------------------------------- 1 | % vim: set expandtab softtabstop=4 shiftwidth=4: 2 | -module(erlwitness). 3 | -author('Guilherme Andrade '). 4 | 5 | -export([get_start_extras/2, 6 | get_start_extras/3, 7 | get_start_extras/4, 8 | unwrap_init_args/1, 9 | finalize_init/2 10 | ]). 11 | 12 | -export_type([entity/0, process_type/0, wrapped_init_args/0, init_result/0]). 13 | 14 | -ignore_xref([{get_start_extras, 2}, 15 | {get_start_extras, 3}, 16 | {get_start_extras, 4}, 17 | {unwrap_init_args, 1}, 18 | {finalize_init, 2}]). 19 | 20 | -type entity() :: any(). 21 | -type process_type() :: any(). 22 | 23 | -record(wrapped_init_args, { 24 | watchers :: [pid()], 25 | entity :: entity(), 26 | entity_proc_type :: process_type(), 27 | args :: term() 28 | }). 29 | 30 | -opaque wrapped_init_args() :: #wrapped_init_args{}. 31 | -type init_result() :: {ok, S::term()} | {ok, S::term(), timeout()} | ignore | {stop, Reason::term()}. 32 | 33 | 34 | %% @doc Wrap arguments and merge erlwitness custom debug options. 35 | %% 36 | -spec get_start_extras(Entity :: entity(), EntityProcType :: process_type()) 37 | -> {WrappedInitArgs :: #wrapped_init_args{}, StartOptions :: [term()]}. 38 | get_start_extras(Entity, EntityProcType) -> 39 | get_start_extras(Entity, EntityProcType, []). 40 | 41 | %% @doc Wrap arguments and merge erlwitness custom debug options. 42 | %% 43 | -spec get_start_extras(Entity :: entity(), EntityProcType :: process_type(), 44 | Args :: term()) 45 | -> {WrappedInitArgs :: #wrapped_init_args{}, StartOptions :: [term()]}. 46 | get_start_extras(Entity, EntityProcType, Args) -> 47 | get_start_extras(Entity, EntityProcType, Args, []). 48 | 49 | %% @doc Wrap arguments and merge erlwitness custom debug options. 50 | %% 51 | -spec get_start_extras(Entity :: entity(), EntityProcType :: process_type(), 52 | Args :: term(), BaseStartOptions :: [term()]) 53 | -> {WrappedInitArgs :: #wrapped_init_args{}, StartOptions :: [term()]}. 54 | get_start_extras(Entity, EntityProcType, Args, BaseStartOptions) -> 55 | {StartOptions, Watchers} = merge_dbg_options(Entity, EntityProcType, BaseStartOptions), 56 | WrappedInitArgs = #wrapped_init_args{watchers = Watchers, 57 | entity = Entity, 58 | entity_proc_type = EntityProcType, 59 | args = Args}, 60 | {WrappedInitArgs, StartOptions}. 61 | 62 | 63 | %% @doc Unwrap previously wrapped args. 64 | -spec unwrap_init_args(WrappedInitArgs :: #wrapped_init_args{}) -> term(). 65 | unwrap_init_args(#wrapped_init_args{ args=Args }) -> Args. 66 | 67 | 68 | %% @doc Finalize entity's gen_server initialization 69 | -spec finalize_init(#wrapped_init_args{}, InitResult :: init_result()) -> init_result(). 70 | finalize_init(#wrapped_init_args{}, ignore) -> 71 | ignore; 72 | finalize_init(#wrapped_init_args{}, {stop, Reason}) -> 73 | {stop, Reason}; 74 | finalize_init(#wrapped_init_args{}=WrappedInitArgs, InitResult) -> 75 | State0 = case InitResult of 76 | {ok, State} -> State; 77 | {ok, State, _Timeout} -> State 78 | end, 79 | 80 | #wrapped_init_args{ entity=Entity, 81 | entity_proc_type=EntityProcType }=WrappedInitArgs, 82 | _ = index_entity(Entity, EntityProcType, self()), 83 | 84 | case WrappedInitArgs#wrapped_init_args.watchers of 85 | [] -> InitResult; 86 | [_|_]=Watchers -> 87 | ok = erlwitness_entity:set_params(Entity, EntityProcType, self()), 88 | Timestamp = os:timestamp(), 89 | lists:foreach( 90 | fun (Watcher) -> 91 | ok = erlwitness_watcher:report_init(Watcher, Timestamp, Entity, self(), 92 | EntityProcType, self(), State0) 93 | end, 94 | Watchers), 95 | InitResult 96 | end. 97 | 98 | 99 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 100 | -spec index_entity(Entity :: entity(), EntityProcType :: process_type(), EntityPid :: pid()) 101 | -> ok | {error, not_using_internal_indexing}. 102 | index_entity(Entity, EntityProcType, EntityPid) -> 103 | case erlwitness_conf:use_internal_indexing() of 104 | false -> {error, not_using_internal_indexing}; 105 | true -> 106 | ok = erlwitness_index_serv:register_entity(Entity, EntityProcType, EntityPid) 107 | end. 108 | 109 | merge_dbg_options(Entity, EntityProcType, Options) -> 110 | Watchers = erlwitness_lobby:watchers_local_lookup(Entity), 111 | ExtraDebugOptions = erlwitness_watcher:get_entity_dbg_options(Entity, EntityProcType, Watchers), 112 | {MaybeMerged, MergeStatus} = lists:mapfoldl( 113 | fun ({debug, DebugOptions}, not_merged) -> 114 | {{debug, DebugOptions ++ ExtraDebugOptions}, 115 | merged}; 116 | (V, Acc) -> 117 | {V, Acc} 118 | end, 119 | not_merged, Options), 120 | Merged = case MergeStatus of 121 | merged -> MaybeMerged; 122 | not_merged -> [{debug, ExtraDebugOptions} | MaybeMerged] 123 | end, 124 | {Merged, Watchers}. 125 | -------------------------------------------------------------------------------- /src/erlwitness_app.erl: -------------------------------------------------------------------------------- 1 | % vim: set expandtab softtabstop=4 shiftwidth=4: 2 | -module(erlwitness_app). 3 | -author('Guilherme Andrade '). 4 | 5 | -behaviour(application). 6 | 7 | %% Application callbacks 8 | -export([start/2, stop/1]). 9 | 10 | %% =================================================================== 11 | %% Application callbacks 12 | %% =================================================================== 13 | 14 | start(_StartType, _StartArgs) -> 15 | erlwitness_sup:start_link(). 16 | 17 | stop(_State) -> 18 | ok. 19 | -------------------------------------------------------------------------------- /src/erlwitness_conf.erl: -------------------------------------------------------------------------------- 1 | % vim: set expandtab softtabstop=4 shiftwidth=4: 2 | -module(erlwitness_conf). 3 | -author('Guilherme Andrade '). 4 | 5 | -export([get_lookup_module/0, 6 | use_internal_indexing/0, 7 | get_index_serv_count/0]). 8 | 9 | -spec get_lookup_module() -> module(). 10 | get_lookup_module() -> 11 | application:get_env(erlwitness, entity_lookup_module, 12 | erlwitness_index_serv). 13 | 14 | -spec use_internal_indexing() -> boolean(). 15 | use_internal_indexing() -> 16 | get_lookup_module() == erlwitness_index_serv. 17 | 18 | -spec get_index_serv_count() -> pos_integer(). 19 | get_index_serv_count() -> 20 | application:get_env(erlwitness, erlwitness_index_serv_count, 21 | 10 * erlang:system_info(schedulers_online)). 22 | -------------------------------------------------------------------------------- /src/erlwitness_entity.erl: -------------------------------------------------------------------------------- 1 | % vim: set expandtab softtabstop=4 shiftwidth=4: 2 | -module(erlwitness_entity). 3 | -export([set_params/3, get_entity/0, report_lager_event/8]). 4 | 5 | -ignore_xref([{get_entity, 0}, {report_lager_event, 8}]). 6 | 7 | %% The following code should only be used internally / through the parse transform. 8 | 9 | -define(PROCDIC_ENTITY_IDENTIFIER, 'erlwitness/entity/identifier'). 10 | -define(PROCDIC_ENTITY_PROCTYPE, 'erlwitness/entity/proc_type'). 11 | -define(PROCDIC_ENTITY_PROCNAME, 'erlwitness/entity/proc_name'). 12 | 13 | -spec set_params(Entity :: erlwitness:entity(), 14 | EntityProcType :: erlwitness:process_type(), 15 | EntityProcName :: term()) -> ok. 16 | set_params(Entity, EntityProcType, EntityProcName) -> 17 | put(?PROCDIC_ENTITY_IDENTIFIER, {value, Entity}), 18 | put(?PROCDIC_ENTITY_PROCTYPE, EntityProcType), 19 | PrevProcName = get(?PROCDIC_ENTITY_PROCNAME), 20 | case is_pid(PrevProcName) orelse PrevProcName == undefined of 21 | false -> ok; 22 | true -> 23 | put(?PROCDIC_ENTITY_PROCNAME, EntityProcName), 24 | ok 25 | end. 26 | 27 | -spec get_entity() -> {value, erlwitness:entity()} | undefined. 28 | get_entity() -> 29 | get(?PROCDIC_ENTITY_IDENTIFIER). 30 | 31 | -spec report_lager_event(Watchers :: [pid()], Entity :: erlwitness:entity(), 32 | LagerModule :: module(), LagerFunction :: atom(), LagerArgs :: list(), 33 | CodeModule :: module(), CodeFunction :: atom(), CodeLine :: pos_integer()) -> ok. 34 | report_lager_event(Watchers, Entity, LagerModule, LagerFunction, LagerArgs, 35 | CodeModule, CodeFunction, CodeLine) -> 36 | Timestamp = os:timestamp(), 37 | EntityPid = self(), 38 | EntityProcType = get(?PROCDIC_ENTITY_PROCTYPE), 39 | EntityProcName = get(?PROCDIC_ENTITY_PROCNAME), 40 | lists:foreach( 41 | fun (Watcher) -> 42 | ok = erlwitness_watcher:report_lager_event(Watcher, Timestamp, Entity, EntityPid, 43 | EntityProcType, EntityProcName, 44 | LagerModule, LagerFunction, LagerArgs, 45 | CodeModule, CodeFunction, CodeLine) 46 | end, 47 | Watchers). 48 | -------------------------------------------------------------------------------- /src/erlwitness_index_serv.erl: -------------------------------------------------------------------------------- 1 | % vim: set expandtab softtabstop=4 shiftwidth=4: 2 | -module(erlwitness_index_serv). 3 | -author('Guilherme Andrade '). 4 | 5 | -behaviour(gen_server). 6 | -behaviour(erlwitness_lookup). 7 | 8 | %% API functions 9 | -export([child_spec/1, 10 | register_entity/3, 11 | lookup_entity/1, 12 | start_link/1]). 13 | 14 | %% erlwiness_lookup callbacks 15 | -export([lookup_global_entity/1]). 16 | 17 | %% gen_server callbacks 18 | -export([init/1, 19 | handle_call/3, 20 | handle_cast/2, 21 | handle_info/2, 22 | terminate/2, 23 | code_change/3]). 24 | 25 | -ignore_xref([{start_link, 1}, 26 | {lookup_entity, 1}]). 27 | 28 | 29 | -record(state, { 30 | table = ets:tid() 31 | }). 32 | 33 | -record(indexed_entity, { 34 | entity :: erlwitness:entity(), 35 | proc_type :: erlwitness:process_type() | '$1', 36 | pid :: pid() | '$2', 37 | monitor :: reference() | '_' 38 | }). 39 | 40 | 41 | -define(BASE_TABLE, ?MODULE). 42 | -define(BASE_SERVER, ?MODULE). 43 | 44 | %%%=================================================================== 45 | %%% API functions 46 | %%%=================================================================== 47 | -spec child_spec(Id :: pos_integer()) -> supervisor:child_spec(). 48 | child_spec(Id) -> 49 | {server_for_id(Id), {?MODULE, start_link, [Id]}, permanent, 5000, worker, [?MODULE]}. 50 | 51 | -spec register_entity(Entity :: erlwitness:entity(), EntityProcType :: erlwitness:process_type(), 52 | EntityPid :: pid()) -> ok. 53 | register_entity(Entity, EntityProcType, EntityPid) when is_pid(EntityPid), node(EntityPid) == node() -> 54 | Id = pick_id(Entity, EntityProcType, EntityPid), 55 | ok = gen_server:call(server_for_id(Id), {register, Entity, EntityProcType, EntityPid}). 56 | 57 | 58 | %%-------------------------------------------------------------------- 59 | %% @doc 60 | %% Starts the server 61 | %% 62 | %% @spec start_link(Id :: pos_integer()) -> {ok, Pid} | ignore | {error, Error} 63 | %% @end 64 | %%-------------------------------------------------------------------- 65 | start_link(Id) -> 66 | gen_server:start_link({local, server_for_id(Id)}, ?MODULE, [Id], []). 67 | 68 | 69 | %%%=================================================================== 70 | %%% erlwitness_lookup callbacks 71 | %%%=================================================================== 72 | 73 | -spec lookup_global_entity(Entity :: erlwitness:entity()) -> [erlwitness_lookup:indexed_entity_ref()]. 74 | lookup_global_entity(Entity) -> 75 | {Results, _BadNodes} = rpc:multicall(?MODULE, lookup_entity, [Entity]), 76 | sets:to_list( sets:from_list( lists:foldl(fun erlang:'++'/2, [], Results) ) ). 77 | 78 | 79 | %%%=================================================================== 80 | %%% gen_server callbacks 81 | %%%=================================================================== 82 | 83 | %%-------------------------------------------------------------------- 84 | %% @private 85 | %% @doc 86 | %% Initializes the server 87 | %% 88 | %% @spec init(Args) -> {ok, State} | 89 | %% {ok, State, Timeout} | 90 | %% ignore | 91 | %% {stop, Reason} 92 | %% @end 93 | %%-------------------------------------------------------------------- 94 | init([Id]) -> 95 | Table = table_for_id(Id), 96 | Table = ets:new(Table, [set, named_table, protected, 97 | {keypos, #indexed_entity.monitor}]), 98 | {ok, #state{ table=Table }}. 99 | 100 | %%-------------------------------------------------------------------- 101 | %% @private 102 | %% @doc 103 | %% Handling call messages 104 | %% 105 | %% @spec handle_call(Request, From, State) -> 106 | %% {reply, Reply, State} | 107 | %% {reply, Reply, State, Timeout} | 108 | %% {noreply, State} | 109 | %% {noreply, State, Timeout} | 110 | %% {stop, Reason, Reply, State} | 111 | %% {stop, Reason, State} 112 | %% @end 113 | %%-------------------------------------------------------------------- 114 | handle_call({register, Entity, EntityProcType, Pid}, _From, #state{}=State) -> 115 | IndexedEntity = #indexed_entity{entity = Entity, 116 | proc_type = EntityProcType, 117 | pid = Pid, 118 | monitor = monitor(process, Pid)}, 119 | true = ets:insert_new(State#state.table, IndexedEntity), 120 | {reply, ok, State}; 121 | 122 | handle_call(_Request, _From, State) -> 123 | {noreply, State}. 124 | 125 | %%-------------------------------------------------------------------- 126 | %% @private 127 | %% @doc 128 | %% Handling cast messages 129 | %% 130 | %% @spec handle_cast(Msg, State) -> {noreply, State} | 131 | %% {noreply, State, Timeout} | 132 | %% {stop, Reason, State} 133 | %% @end 134 | %%-------------------------------------------------------------------- 135 | handle_cast(_Msg, State) -> 136 | {noreply, State}. 137 | 138 | %%-------------------------------------------------------------------- 139 | %% @private 140 | %% @doc 141 | %% Handling all non call/cast messages 142 | %% 143 | %% @spec handle_info(Info, State) -> {noreply, State} | 144 | %% {noreply, State, Timeout} | 145 | %% {stop, Reason, State} 146 | %% @end 147 | %%-------------------------------------------------------------------- 148 | handle_info({'DOWN', Ref, process, _Pid, _Reason}, #state{}=State) -> 149 | true = ets:delete(State#state.table, Ref), 150 | {noreply, State}; 151 | 152 | handle_info(_Info, State) -> 153 | {noreply, State}. 154 | 155 | %%-------------------------------------------------------------------- 156 | %% @private 157 | %% @doc 158 | %% This function is called by a gen_server when it is about to 159 | %% terminate. It should be the opposite of Module:init/1 and do any 160 | %% necessary cleaning up. When it returns, the gen_server terminates 161 | %% with Reason. The return value is ignored. 162 | %% 163 | %% @spec terminate(Reason, State) -> void() 164 | %% @end 165 | %%-------------------------------------------------------------------- 166 | terminate(_Reason, _State) -> 167 | ok. 168 | 169 | %%-------------------------------------------------------------------- 170 | %% @private 171 | %% @doc 172 | %% Convert process state when code is changed 173 | %% 174 | %% @spec code_change(OldVsn, State, Extra) -> {ok, NewState} 175 | %% @end 176 | %%-------------------------------------------------------------------- 177 | code_change(_OldVsn, State, _Extra) -> 178 | {ok, State}. 179 | 180 | %%%=================================================================== 181 | %%% Internal functions 182 | %%%=================================================================== 183 | -spec index_serv_count() -> pos_integer(). 184 | index_serv_count() -> 185 | erlwitness_conf:get_index_serv_count(). 186 | 187 | -spec server_for_id(Id :: pos_integer()) -> atom(). 188 | server_for_id(Id) -> 189 | list_to_atom(atom_to_list(?BASE_SERVER) ++ "_" ++ integer_to_list(Id)). 190 | 191 | -spec table_for_id(Id :: pos_integer()) -> atom(). 192 | table_for_id(Id) -> 193 | list_to_atom(atom_to_list(?BASE_TABLE) ++ "_" ++ integer_to_list(Id)). 194 | 195 | -spec pick_id(Entity :: erlwitness:entity(), EntityProcType :: erlwitness:process_type(), 196 | EntityPid :: pid()) -> pos_integer(). 197 | pick_id(Entity, EntityProcType, EntityPid) -> 198 | Total = index_serv_count(), 199 | 1 + erlang:phash2({Entity, EntityProcType, EntityPid, os:timestamp()}, Total). 200 | 201 | -spec lookup_entity(Entity :: erlwitness:entity()) -> [erlwitness_lookup:indexed_entity_ref()]. 202 | lookup_entity(Entity) -> 203 | Match = #indexed_entity{entity = Entity, 204 | proc_type = '$1', 205 | pid = '$2', 206 | monitor = '_'}, 207 | MatchesById = [match_over_id(Id, Match) || Id <- lists:seq(1, index_serv_count())], 208 | Matches = lists:foldl(fun erlang:'++'/2, [], MatchesById), 209 | [{EntityProcType, Pid} || [EntityProcType, Pid] <- Matches]. 210 | 211 | -spec match_over_id(Id :: pos_integer(), 212 | Match :: #indexed_entity{proc_type :: '$1', pid :: '$2', monitor :: '_'}) 213 | -> [[any()]]. 214 | match_over_id(Id, Match) -> 215 | ets:match(table_for_id(Id), Match). 216 | -------------------------------------------------------------------------------- /src/erlwitness_lobby.erl: -------------------------------------------------------------------------------- 1 | % vim: set expandtab softtabstop=4 shiftwidth=4: 2 | -module(erlwitness_lobby). 3 | -author('Guilherme Andrade '). 4 | 5 | -behaviour(gen_server). 6 | 7 | %% API functions 8 | -export([watch/2, 9 | unwatch/2, 10 | unwatch_by_pid/1, 11 | watchers_local_lookup/1, 12 | is_entity_watched_by/2, 13 | is_entity_watched/1]). 14 | 15 | -export([start_link/0]). 16 | 17 | %% gen_server callbacks 18 | -export([init/1, 19 | handle_call/3, 20 | handle_cast/2, 21 | handle_info/2, 22 | terminate/2, 23 | code_change/3]). 24 | 25 | -ignore_xref([{start_link, 0}, 26 | {is_entity_watched, 1}]). 27 | 28 | 29 | -record(state, { 30 | monitored_watchers = [] :: [{Monitor :: reference(), WatcherPid :: pid()}] 31 | }). 32 | 33 | -record(watcher, { 34 | entity :: erlwitness:entity(), 35 | watcher_pid :: pid() 36 | }). 37 | 38 | 39 | -define(TABLE, ?MODULE). 40 | -define(SERVER, ?MODULE). 41 | 42 | %%%=================================================================== 43 | %%% API functions 44 | %%%=================================================================== 45 | -spec watch(Entity :: erlwitness:entity(), WatcherPid :: pid()) -> boolean(). 46 | watch(Entity, WatcherPid) when is_pid(WatcherPid) -> 47 | Watcher = #watcher{entity = Entity, 48 | watcher_pid = WatcherPid}, 49 | {Replies, _BadNodes} = gen_server:multi_call(?SERVER, {watch, Watcher}), 50 | length(Replies) > 0. 51 | 52 | -spec unwatch(Entity :: erlwitness:entity(), WatcherPid :: pid()) -> ok. 53 | unwatch(Entity, WatcherPid) when is_pid(WatcherPid) -> 54 | Watcher = #watcher{entity = Entity, 55 | watcher_pid = WatcherPid}, 56 | abcast = gen_server:abcast(?SERVER, {unwatch, Watcher}), 57 | ok. 58 | 59 | -spec unwatch_by_pid(WatcherPid :: pid()) -> ok. 60 | unwatch_by_pid(WatcherPid) when is_pid(WatcherPid) -> 61 | abcast = gen_server:abcast(?SERVER, {unwatch_pid, WatcherPid}), 62 | ok. 63 | 64 | -spec watchers_local_lookup(Entity :: erlwitness:entity()) -> [pid()]. 65 | watchers_local_lookup(Entity) -> 66 | Watchers = ets:lookup(?TABLE, Entity), 67 | [Watcher#watcher.watcher_pid || Watcher <- Watchers]. 68 | 69 | -spec is_entity_watched_by(Entity :: erlwitness:entity(), WatcherPid :: pid()) -> boolean(). 70 | is_entity_watched_by(Entity, WatcherPid) -> 71 | lists:member(#watcher{entity = Entity, watcher_pid=WatcherPid}, 72 | ets:lookup(?TABLE, Entity)). 73 | 74 | -spec is_entity_watched(Entity :: erlwitness:entity()) -> boolean(). 75 | is_entity_watched(Entity) -> 76 | ets:member(?TABLE, Entity). 77 | 78 | 79 | %%-------------------------------------------------------------------- 80 | %% @doc 81 | %% Starts the server 82 | %% 83 | %% @spec start_link() -> {ok, Pid} | ignore | {error, Error} 84 | %% @end 85 | %%-------------------------------------------------------------------- 86 | start_link() -> 87 | gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). 88 | 89 | %%%=================================================================== 90 | %%% gen_server callbacks 91 | %%%=================================================================== 92 | 93 | %%-------------------------------------------------------------------- 94 | %% @private 95 | %% @doc 96 | %% Initializes the server 97 | %% 98 | %% @spec init(Args) -> {ok, State} | 99 | %% {ok, State, Timeout} | 100 | %% ignore | 101 | %% {stop, Reason} 102 | %% @end 103 | %%-------------------------------------------------------------------- 104 | init([]) -> 105 | ?TABLE = ets:new(?TABLE, [bag, named_table, protected, 106 | {keypos, #watcher.entity}, 107 | {read_concurrency, true}]), 108 | ok = net_kernel:monitor_nodes(true, [{node_type, visible}]), 109 | {ok, #state{}}. 110 | 111 | %%-------------------------------------------------------------------- 112 | %% @private 113 | %% @doc 114 | %% Handling call messages 115 | %% 116 | %% @spec handle_call(Request, From, State) -> 117 | %% {reply, Reply, State} | 118 | %% {reply, Reply, State, Timeout} | 119 | %% {noreply, State} | 120 | %% {noreply, State, Timeout} | 121 | %% {stop, Reason, Reply, State} | 122 | %% {stop, Reason, State} 123 | %% @end 124 | %%-------------------------------------------------------------------- 125 | handle_call({watch, #watcher{ watcher_pid=Pid }=Watcher}, _From, State) 126 | when is_pid(Pid), Pid /= self() -> 127 | {reply, ok, handle_registration(State, Watcher)}; 128 | 129 | handle_call(_Request, _From, State) -> 130 | {noreply, State}. 131 | 132 | %%-------------------------------------------------------------------- 133 | %% @private 134 | %% @doc 135 | %% Handling cast messages 136 | %% 137 | %% @spec handle_cast(Msg, State) -> {noreply, State} | 138 | %% {noreply, State, Timeout} | 139 | %% {stop, Reason, State} 140 | %% @end 141 | %%-------------------------------------------------------------------- 142 | handle_cast({watch, #watcher{ watcher_pid=Pid }=Watcher}, State) 143 | when is_pid(Pid), Pid /= self() -> 144 | {noreply, handle_registration(State, Watcher)}; 145 | 146 | handle_cast({unwatch, #watcher{ watcher_pid=Pid }=Watcher}, State) when is_pid(Pid) -> 147 | true = ets:delete_object(?TABLE, Watcher), 148 | case ets:match(?TABLE, #watcher{ entity='$1', watcher_pid=Pid }) of 149 | [_|_] -> {noreply, State}; 150 | [] -> 151 | % Last watcher for this pid unwatched; unmonitor process 152 | {noreply, handle_pid_unregistration(State, Pid)} 153 | end; 154 | 155 | handle_cast({unwatch_pid, Pid}, State) when is_pid(Pid) -> 156 | {noreply, handle_pid_unregistration(State, Pid)}; 157 | 158 | handle_cast(_Msg, State) -> 159 | {noreply, State}. 160 | 161 | %%-------------------------------------------------------------------- 162 | %% @private 163 | %% @doc 164 | %% Handling all non call/cast messages 165 | %% 166 | %% @spec handle_info(Info, State) -> {noreply, State} | 167 | %% {noreply, State, Timeout} | 168 | %% {stop, Reason, State} 169 | %% @end 170 | %%-------------------------------------------------------------------- 171 | handle_info({nodeup, Node, _}, #state{}=State) when Node /= node() -> 172 | MatchSpec = ets:fun2ms(fun(#watcher{ watcher_pid=Pid }=Watcher) when node(Pid) == node() -> Watcher end), 173 | LocalWatchers = ets:select(?TABLE, MatchSpec), 174 | lists:foreach(fun (#watcher{}=Watcher) -> gen_server:cast({?SERVER, Node}, {watch, Watcher}) end, 175 | LocalWatchers), 176 | {noreply, State}; 177 | 178 | handle_info({nodedown, _Node, _}, #state{}=State) -> 179 | {noreply, State}; 180 | 181 | handle_info({'DOWN', _Ref, process, Pid, _Reason}, #state{}=State) -> 182 | {noreply, handle_pid_unregistration(State, Pid)}; 183 | 184 | handle_info(_Info, State) -> 185 | {noreply, State}. 186 | 187 | %%-------------------------------------------------------------------- 188 | %% @private 189 | %% @doc 190 | %% This function is called by a gen_server when it is about to 191 | %% terminate. It should be the opposite of Module:init/1 and do any 192 | %% necessary cleaning up. When it returns, the gen_server terminates 193 | %% with Reason. The return value is ignored. 194 | %% 195 | %% @spec terminate(Reason, State) -> void() 196 | %% @end 197 | %%-------------------------------------------------------------------- 198 | terminate(_Reason, _State) -> 199 | ok. 200 | 201 | %%-------------------------------------------------------------------- 202 | %% @private 203 | %% @doc 204 | %% Convert process state when code is changed 205 | %% 206 | %% @spec code_change(OldVsn, State, Extra) -> {ok, NewState} 207 | %% @end 208 | %%-------------------------------------------------------------------- 209 | code_change(_OldVsn, State, _Extra) -> 210 | {ok, State}. 211 | 212 | %%%=================================================================== 213 | %%% Internal functions 214 | %%%=================================================================== 215 | 216 | -spec handle_pid_unregistration(#state{}, pid()) -> #state{}. 217 | handle_pid_unregistration(#state{ monitored_watchers=Monitors }=State, Pid) -> 218 | NewMonitors = lists:keydelete(Pid, 2, Monitors), 219 | true = ets:match_delete(?TABLE, #watcher{ entity='_', watcher_pid=Pid }), 220 | State#state{ monitored_watchers=NewMonitors }. 221 | 222 | -spec handle_registration(#state{}, #watcher{}) -> #state{}. 223 | handle_registration(#state{ monitored_watchers=Monitors }=State, 224 | #watcher{ entity=Entity, watcher_pid=Pid }=Watcher) -> 225 | case {lists:member(Watcher, ets:lookup(?TABLE, Entity)), 226 | lists:keymember(Pid, 2, Monitors)} 227 | of 228 | {true, true} -> 229 | State; 230 | {false, true} -> 231 | % We're already monitoring this watcher 232 | true = ets:insert(?TABLE, Watcher), 233 | State; 234 | {false, false} -> 235 | Monitor = monitor(process, Pid), 236 | NewMonitors = [{Monitor, Pid} | Monitors], 237 | NewState = State#state{ monitored_watchers=NewMonitors }, 238 | true = ets:insert(?TABLE, Watcher), 239 | NewState 240 | end. 241 | -------------------------------------------------------------------------------- /src/erlwitness_lookup.erl: -------------------------------------------------------------------------------- 1 | % vim: set expandtab softtabstop=4 shiftwidth=4: 2 | -module(erlwitness_lookup). 3 | -author('Guilherme Andrade '). 4 | 5 | -export_type([indexed_entity_ref/0]). 6 | -ignore_xref([behaviour_info/1]). 7 | 8 | -type indexed_entity_ref() :: {ProcType :: erlwitness:process_type(), Pid :: pid()}. 9 | 10 | -callback lookup_global_entity(Entity :: erlwitness:entity()) -> [indexed_entity_ref()]. 11 | -------------------------------------------------------------------------------- /src/erlwitness_sup.erl: -------------------------------------------------------------------------------- 1 | % vim: set expandtab softtabstop=4 shiftwidth=4: 2 | -module(erlwitness_sup). 3 | -author('Guilherme Andrade '). 4 | 5 | -behaviour(supervisor). 6 | 7 | %% API 8 | -export([start_link/0]). 9 | 10 | %% Supervisor callbacks 11 | -export([init/1]). 12 | 13 | %% Helper macro for declaring children of supervisor 14 | -define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}). 15 | 16 | %% =================================================================== 17 | %% API functions 18 | %% =================================================================== 19 | 20 | start_link() -> 21 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 22 | 23 | %% =================================================================== 24 | %% Supervisor callbacks 25 | %% =================================================================== 26 | 27 | init([]) -> 28 | Indexers = case erlwitness_conf:use_internal_indexing() of 29 | false -> []; 30 | true -> 31 | [erlwitness_index_serv:child_spec(Id) 32 | || Id <- lists:seq(1, erlwitness_conf:get_index_serv_count())] 33 | end, 34 | AllChildren = [?CHILD(erlwitness_lobby, worker) | Indexers], 35 | {ok, {{one_for_one, 5, 10}, AllChildren}}. 36 | 37 | -------------------------------------------------------------------------------- /src/erlwitness_transform.erl: -------------------------------------------------------------------------------- 1 | % vim: set expandtab softtabstop=4 shiftwidth=4: 2 | -module(erlwitness_transform). 3 | -export([parse_transform/2]). 4 | -ignore_xref([{parse_transform, 2}]). 5 | 6 | %X @doc Derived from basho/lager's lager_transform.erl (under Apache License 2.0) 7 | %X 8 | 9 | -include_lib("lager/include/lager.hrl"). 10 | 11 | -ifndef(LEVELS_UNSAFE). 12 | -define(LEVELS_UNSAFE, []). % Not present in older lager versions 13 | -endif. 14 | 15 | 16 | %% @private 17 | parse_transform(AST, _Options) -> 18 | walk_ast([], AST). 19 | 20 | walk_ast(Acc, []) -> 21 | lists:reverse(Acc); 22 | walk_ast(Acc, [{attribute, _, module, {Module, _PmodArgs}}=H|T]) -> 23 | put(module, Module), 24 | walk_ast([H|Acc], T); 25 | walk_ast(Acc, [{attribute, _, module, Module}=H|T]) -> 26 | put(module, Module), 27 | walk_ast([H|Acc], T); 28 | walk_ast(Acc, [{function, Line, Name, Arity, Clauses}|T]) -> 29 | put(function, Name), 30 | walk_ast([{function, Line, Name, Arity, 31 | walk_clauses([], Clauses)}|Acc], T); 32 | walk_ast(Acc, [H|T]) -> 33 | walk_ast([H|Acc], T). 34 | 35 | walk_clauses(Acc, []) -> 36 | lists:reverse(Acc); 37 | walk_clauses(Acc, [{clause, Line, Arguments, Guards, Body}|T]) -> 38 | walk_clauses([{clause, Line, Arguments, Guards, walk_body([], Body)}|Acc], T). 39 | 40 | walk_body(Acc, []) -> 41 | lists:reverse(Acc); 42 | walk_body(Acc, [H|T]) -> 43 | walk_body([transform_statement(H)|Acc], T). 44 | 45 | transform_statement({call, Line, {remote, _Line1, {atom, _Line2, lager=Module}, 46 | {atom, _Line3, Function}}, Arguments0} = Stmt) 47 | -> 48 | case (lists:member(Function, ?LEVELS) orelse lists:keymember(Function, 1, ?LEVELS_UNSAFE)) of 49 | true -> 50 | blockify_stmts(Line, [do_transform(Line, Module, Function, Arguments0), 51 | Stmt]); 52 | false -> 53 | Stmt 54 | end; 55 | transform_statement(Stmt) when is_tuple(Stmt) -> 56 | list_to_tuple(transform_statement(tuple_to_list(Stmt))); 57 | transform_statement(Stmt) when is_list(Stmt) -> 58 | [transform_statement(S) || S <- Stmt]; 59 | transform_statement(Stmt) -> 60 | Stmt. 61 | 62 | do_transform(Line, LagerModule, LagerFunction, LagerArguments) -> 63 | % Let's make sure we don't end up exporting any conflicting case variables 64 | EntityVar = list_to_atom("ErlWitness_LagerWrapper_SelfEntity_" ++ integer_to_list(Line)), 65 | WatchersVar = list_to_atom("ErlWitness_LagerWrapper_Watchers_" ++ integer_to_list(Line)), 66 | 67 | %% case erlwitness_entity:get_entity() of 68 | %% undefined -> ok; 69 | %% {value, Entity} -> 70 | %% case erlwitness_lobby:watchers_local_lookup(Entity) of 71 | %% [] -> ok; 72 | %% [_|_]=Watchers -> 73 | %% erlwitness_entity:report_lager_event( 74 | %% Watchers, Entity, 75 | %% $LAGER_MODULE, $LAGER_FUNCTION, $LAGER_ARGUMENTS$, 76 | %% $MODULE, $FUNCTION, $LINE) 77 | %% end 78 | %% end 79 | 80 | {'case',Line, 81 | {call,Line, 82 | {remote,Line,{atom,Line,erlwitness_entity},{atom,Line,get_entity}}, 83 | []}, 84 | [{clause,Line,[{atom,Line,undefined}],[],[{atom,Line,ok}]}, 85 | {clause,Line, 86 | [{tuple,Line,[{atom,Line,value},{var,Line,EntityVar}]}], 87 | [], 88 | [{'case',Line, 89 | {call,Line, 90 | {remote,Line, 91 | {atom,Line,erlwitness_lobby}, 92 | {atom,Line,watchers_local_lookup}}, 93 | [{var,Line,EntityVar}]}, 94 | [{clause,Line,[{nil,Line}],[],[{atom,Line,ok}]}, 95 | {clause,Line, 96 | [{match,Line, 97 | {cons,Line,{var,Line,'_'},{var,Line,'_'}}, 98 | {var,Line,WatchersVar}}], 99 | [], 100 | [{call,Line, 101 | {remote,Line, 102 | {atom,Line,erlwitness_entity}, 103 | {atom,Line,report_lager_event}}, 104 | [{var,Line,WatchersVar}, 105 | {var,Line,EntityVar}, 106 | {atom,Line,LagerModule}, 107 | {atom,Line,LagerFunction}, 108 | tailify_list(Line, LagerArguments), 109 | {atom, Line, get(module)}, 110 | {atom, Line, get(function)}, 111 | {integer, Line, Line} 112 | ]}]}]}]}]}. 113 | 114 | blockify_stmts(Line, Stmts) -> 115 | {block, Line, Stmts}. 116 | 117 | tailify_list(Line, []) -> {nil, Line}; 118 | tailify_list(Line, [H|T]) -> 119 | {cons, Line, H, tailify_list(Line, T)}. 120 | -------------------------------------------------------------------------------- /src/erlwitness_watcher.erl: -------------------------------------------------------------------------------- 1 | % vim: set expandtab softtabstop=4 shiftwidth=4: 2 | -module(erlwitness_watcher). 3 | -author('Guilherme Andrade '). 4 | 5 | -behaviour(gen_server). 6 | 7 | %% API functions 8 | -export([start_link/3, 9 | start_link/4, 10 | start/3, 11 | start/4, 12 | get_entity_dbg_options/3, 13 | install_dbg_fun/4, 14 | watch/1, 15 | unwatch/1, 16 | unwatch_all/0, 17 | report_init/7, 18 | report_lager_event/12]). 19 | 20 | %% gen_server callbacks 21 | -export([init/1, 22 | handle_call/3, 23 | handle_cast/2, 24 | handle_info/2, 25 | terminate/2, 26 | code_change/3]). 27 | 28 | -export_type([dbg_fun/0, dbg_fun_state/0, handler_return/0]). 29 | 30 | -ignore_xref([{behaviour_info, 1}, 31 | {start, 3}, 32 | {start, 4}, 33 | {start_link, 3}, 34 | {start_link, 4}, 35 | {install_dbg_fun, 4}, 36 | {watch, 1}, 37 | {unwatch, 1}, 38 | {unwatch_all, 0}]). 39 | 40 | 41 | -type handler_return() :: 42 | {noreply, NewState :: term()} | 43 | {noreply, NewState :: term(), timeout() | hibernate} | 44 | {stop, Reason :: term(), NewState :: term()}. 45 | 46 | -type lager_mfa() :: {module(), function(), list()}. 47 | -type lager_debug_info() :: {CodeModule :: module(), CodeFunction :: atom(), CodeLine :: pos_integer()}. 48 | 49 | -callback handle_gencall_event(Timestamp :: erlang:timestamp(), 50 | Entity :: erlwitness:entity(), 51 | EntityPid :: pid(), 52 | EntityProcType :: erlwitness:process_type(), 53 | EntityProcName :: term(), 54 | Call :: term(), 55 | From :: {pid(), reference()}, 56 | State :: term()) -> handler_return(). 57 | 58 | -callback handle_gencast_event(Timestamp :: erlang:timestamp(), 59 | Entity :: erlwitness:entity(), 60 | EntityPid :: pid(), 61 | EntityProcType :: erlwitness:process_type(), 62 | EntityProcName :: term(), 63 | Cast :: term(), 64 | State :: term()) -> handler_return(). 65 | 66 | -callback handle_geninfo_event(Timestamp :: erlang:timestamp(), 67 | Entity :: erlwitness:entity(), 68 | EntityPid :: pid(), 69 | EntityProcType :: erlwitness:process_type(), 70 | EntityProcName :: term(), 71 | Info :: term(), 72 | State :: term()) -> handler_return(). 73 | 74 | -callback handle_newstate_event(Timestamp :: erlang:timestamp(), 75 | Entity :: erlwitness:entity(), 76 | EntityPid :: pid(), 77 | EntityProcType :: erlwitness:process_type(), 78 | EntityProcName :: term(), 79 | EntityProcState :: term(), 80 | State :: term()) -> handler_return(). 81 | 82 | -callback handle_lager_event(Timestamp :: erlang:timestamp(), 83 | Entity :: erlwitness:entity(), 84 | EntityPid :: pid(), 85 | EntityProcType :: erlwitness:process_type(), 86 | EntityProcName :: term(), 87 | LagerMFA :: lager_mfa(), 88 | LagerDebugInfo :: lager_debug_info(), 89 | State :: term()) -> handler_return(). 90 | 91 | 92 | -define(PROCDIC_WATCHER_MODULE, 'erlwitness/watcher_module'). 93 | -define(PROCDIC_ENTITY_STATE(Pid), {'erlwitness/entity_state', Pid}). 94 | 95 | 96 | -define(WITNESSED_EVENT(Timestamp, Entity, EntityPid, EntityProcType, EntityProcName, Event), 97 | {'WITNESS', Timestamp, {Entity, EntityPid, EntityProcType, EntityProcName}, Event}). 98 | 99 | -type dbg_fun_state() :: active | done. 100 | -type dbg_fun() :: fun ((FuncState :: dbg_fun_state(), Event :: any(), ProcName :: any()) 101 | -> NewFuncState :: dbg_fun_state()). 102 | 103 | %%%=================================================================== 104 | %%% API functions 105 | %%%=================================================================== 106 | -spec get_entity_dbg_options(Entity :: erlwitness:entity(), EntityProcType :: erlwitness:process_type(), 107 | Watchers :: [pid()]) 108 | -> [{install, {dbg_fun(), dbg_fun_state()}}]. 109 | get_entity_dbg_options(Entity, EntityProcType, Watchers) -> 110 | [{install, dbg_fun(Entity, EntityProcType, Watcher)} || Watcher <- Watchers]. 111 | 112 | 113 | -spec install_dbg_fun(Entity :: erlwitness:entity(), 114 | EntityProcType :: erlwitness:process_type(), 115 | EntityPid :: pid(), 116 | Watcher :: pid()) -> any(). 117 | install_dbg_fun(Entity, EntityProcType, EntityPid, Watcher) -> 118 | Dbgfun = dbg_fun(Entity, EntityProcType, Watcher), 119 | catch sys:install(EntityPid, Dbgfun, 20). 120 | 121 | 122 | -spec watch(Entity :: erlwitness:entity()) -> ok. 123 | watch(Entity) -> 124 | Watcher = self(), 125 | 126 | % Get ready for new procs 127 | true = erlwitness_lobby:watch(Entity, Watcher), 128 | 129 | % Inject into existing procs 130 | IndexedEntityRefs = lookup_global_entity(Entity), 131 | ok = lists:foreach( 132 | fun ({EntityProcType, EntityPid}) -> 133 | catch rpc:cast(node(EntityPid), ?MODULE, install_dbg_fun, 134 | [Entity, EntityProcType, EntityPid, Watcher]) 135 | end, 136 | IndexedEntityRefs). 137 | 138 | -spec unwatch(Entity :: erlwitness:entity()) -> ok. 139 | unwatch(Entity) -> 140 | Watcher = self(), 141 | ok = erlwitness_lobby:unwatch(Entity, Watcher). 142 | 143 | -spec unwatch_all() -> ok. 144 | unwatch_all() -> 145 | Watcher = self(), 146 | ok = erlwitness_lobby:unwatch_by_pid(Watcher). 147 | 148 | report_init(Watcher, Timestamp, Entity, EntityPid, EntityProcType, EntityProcName, EntityProcState) -> 149 | ok = gen_server:cast(Watcher, ?WITNESSED_EVENT(Timestamp, Entity, EntityPid, EntityProcType, 150 | EntityProcName, {init, EntityProcState})). 151 | 152 | report_lager_event(Watcher, Timestamp, Entity, EntityPid, EntityProcType, EntityProcName, 153 | LagerModule, LagerFunction, LagerArgs, CodeModule, CodeFunction, CodeLine) -> 154 | Event = {lager, LagerModule, LagerFunction, LagerArgs, CodeModule, CodeFunction, CodeLine}, 155 | ok = gen_server:cast(Watcher, ?WITNESSED_EVENT(Timestamp, Entity, EntityPid, EntityProcType, 156 | EntityProcName, Event)). 157 | 158 | 159 | %%% ----------------------------------------------------------------- 160 | %%% Starts an watcher server. 161 | %%% start(Entity, WatcherMod, WatcherArgs) 162 | %%% start(Name, Entity, WatcherMod, WatcherArgs) 163 | %%% start_link(Entity, WatcherMod, WatcherArgs) 164 | %%% start_link(Name, Entity, WatcherMod, WatcherArgs) where: 165 | %%% Name ::= {local, atom()} | {global, atom()} | {via, atom(), term()} 166 | %%% WatcherMod ::= atom(), callback module implementing the 'real' watcher server 167 | %%% WatcherArgs ::= term(), init arguments (to Mod:init/1) 168 | %%% Returns: {ok, Pid} | 169 | %%% {error, {already_started, Pid}} | 170 | %%% {error, Reason} 171 | %%% ----------------------------------------------------------------- 172 | start_link(Entities, WatcherModule, WatcherArgs) when is_list(Entities) -> 173 | gen_server:start_link(?MODULE, [Entities, WatcherModule, WatcherArgs], []). 174 | 175 | start_link(Name, Entities, WatcherModule, WatcherArgs) when is_list(Entities) -> 176 | gen_server:start_link(Name, ?MODULE, [Entities, WatcherModule, WatcherArgs], []). 177 | 178 | start(Entities, WatcherModule, WatcherArgs) when is_list(Entities) -> 179 | gen_server:start(?MODULE, [Entities, WatcherModule, WatcherArgs], []). 180 | 181 | start(Name, Entities, WatcherModule, WatcherArgs) when is_list(Entities) -> 182 | gen_server:start(Name, ?MODULE, [Entities, WatcherModule, WatcherArgs], []). 183 | 184 | %%%=================================================================== 185 | %%% gen_server callbacks 186 | %%%=================================================================== 187 | 188 | %%-------------------------------------------------------------------- 189 | %% @private 190 | %% @doc 191 | %% Initializes the server 192 | %% 193 | %% @spec init(Args) -> {ok, State} | 194 | %% {ok, State, Timeout} | 195 | %% ignore | 196 | %% {stop, Reason} 197 | %% @end 198 | %%-------------------------------------------------------------------- 199 | init([Entities, WatcherModule, WatcherArgs]) -> 200 | undefined = put(?PROCDIC_WATCHER_MODULE, WatcherModule), 201 | gen_server:cast(self(), {watch, Entities}), 202 | WatcherModule:init(WatcherArgs). 203 | 204 | %%-------------------------------------------------------------------- 205 | %% @private 206 | %% @doc 207 | %% Handling call messages 208 | %% 209 | %% @spec handle_call(Request, From, State) -> 210 | %% {reply, Reply, State} | 211 | %% {reply, Reply, State, Timeout} | 212 | %% {noreply, State} | 213 | %% {noreply, State, Timeout} | 214 | %% {stop, Reason, Reply, State} | 215 | %% {stop, Reason, State} 216 | %% @end 217 | %%-------------------------------------------------------------------- 218 | handle_call(Request, From, State) -> 219 | (get(?PROCDIC_WATCHER_MODULE)):handle_call(Request, From, State). 220 | 221 | %%-------------------------------------------------------------------- 222 | %% @private 223 | %% @doc 224 | %% Handling cast messages 225 | %% 226 | %% @spec handle_cast(Msg, State) -> {noreply, State} | 227 | %% {noreply, State, Timeout} | 228 | %% {stop, Reason, State} 229 | %% @end 230 | %%-------------------------------------------------------------------- 231 | handle_cast({watch, Entities}, State) -> 232 | lists:foreach(fun (Entity) -> ok = watch(Entity) end, Entities), 233 | {noreply, State}; 234 | 235 | handle_cast(?WITNESSED_EVENT(Timestamp, Entity, EntityPid, EntityProcType, EntityProcName, Event), State) -> 236 | WatcherModule = get(?PROCDIC_WATCHER_MODULE), 237 | case Event of 238 | {dbg, {in, {'$gen_call', From, Call}}} -> 239 | WatcherModule:handle_gencall_event(Timestamp, Entity, EntityPid, EntityProcType, EntityProcName, 240 | Call, From, State); 241 | {dbg, {in, {'$gen_cast', Cast}}} -> 242 | WatcherModule:handle_gencast_event(Timestamp, Entity, EntityPid, EntityProcType, EntityProcName, 243 | Cast, State); 244 | {dbg, {in, Info}} -> 245 | WatcherModule:handle_geninfo_event(Timestamp, Entity, EntityPid, EntityProcType, EntityProcName, 246 | Info, State); 247 | {dbg, {noreply, EntityProcState}} -> 248 | case get(?PROCDIC_ENTITY_STATE(EntityPid)) =:= EntityProcState of 249 | true -> {noreply, State}; 250 | false -> 251 | put(?PROCDIC_ENTITY_STATE(EntityPid), EntityProcState), 252 | WatcherModule:handle_newstate_event(Timestamp, Entity, EntityPid, EntityProcType, 253 | EntityProcName, EntityProcState, State) 254 | end; 255 | {dbg, {out, _Reply, _To, EntityProcState}} -> 256 | case get(?PROCDIC_ENTITY_STATE(EntityPid)) =:= EntityProcState of 257 | true -> {noreply, State}; 258 | false -> 259 | put(?PROCDIC_ENTITY_STATE(EntityPid), EntityProcState), 260 | WatcherModule:handle_newstate_event(Timestamp, Entity, EntityPid, EntityProcType, 261 | EntityProcName, EntityProcState, State) 262 | end; 263 | {init, EntityProcState} -> 264 | undefined = put(?PROCDIC_ENTITY_STATE(EntityPid), EntityProcState), 265 | WatcherModule:handle_newstate_event(Timestamp, Entity, EntityPid, EntityProcType, 266 | EntityProcName, EntityProcState, State); 267 | {lager, LagerModule, LagerFunction, LagerArgs, CodeModule, CodeFunction, CodeLine} -> 268 | WatcherModule:handle_lager_event(Timestamp, Entity, EntityPid, EntityProcType, EntityProcName, 269 | {LagerModule, LagerFunction, LagerArgs}, 270 | {CodeModule, CodeFunction, CodeLine}, 271 | State) 272 | end; 273 | 274 | handle_cast(Msg, State) -> 275 | (get(?PROCDIC_WATCHER_MODULE)):handle_cast(Msg, State). 276 | 277 | %%-------------------------------------------------------------------- 278 | %% @private 279 | %% @doc 280 | %% Handling all non call/cast messages 281 | %% 282 | %% @spec handle_info(Info, State) -> {noreply, State} | 283 | %% {noreply, State, Timeout} | 284 | %% {stop, Reason, State} 285 | %% @end 286 | %%-------------------------------------------------------------------- 287 | handle_info(Info, State) -> 288 | (get(?PROCDIC_WATCHER_MODULE)):handle_info(Info, State). 289 | 290 | %%-------------------------------------------------------------------- 291 | %% @private 292 | %% @doc 293 | %% This function is called by a gen_server when it is about to 294 | %% terminate. It should be the opposite of Module:init/1 and do any 295 | %% necessary cleaning up. When it returns, the gen_server terminates 296 | %% with Reason. The return value is ignored. 297 | %% 298 | %% @spec terminate(Reason, State) -> void() 299 | %% @end 300 | %%-------------------------------------------------------------------- 301 | terminate(Reason, State) -> 302 | ok = unwatch_all(), 303 | (get(?PROCDIC_WATCHER_MODULE)):terminate(Reason, State). 304 | 305 | %%-------------------------------------------------------------------- 306 | %% @private 307 | %% @doc 308 | %% Convert process state when code is changed 309 | %% 310 | %% @spec code_change(OldVsn, State, Extra) -> {ok, NewState} 311 | %% @end 312 | %%-------------------------------------------------------------------- 313 | code_change(OldVsn, State, Extra) -> 314 | (get(?PROCDIC_WATCHER_MODULE)):code_change(OldVsn, State, Extra). 315 | 316 | %%%=================================================================== 317 | %%% Internal functions 318 | %%%=================================================================== 319 | -spec lookup_global_entity(Entity :: erlwitness:entity()) -> [erlwitness_lookup:indexed_entity_ref()]. 320 | lookup_global_entity(Entity) -> 321 | Module = erlwitness_conf:get_lookup_module(), 322 | Module:lookup_global_entity(Entity). 323 | 324 | report_dbg_event(Watcher, Timestamp, Entity, EntityPid, EntityProcType, EntityProcName, Event) -> 325 | ok = gen_server:cast(Watcher, ?WITNESSED_EVENT(Timestamp, Entity, EntityPid, EntityProcType, 326 | EntityProcName, {dbg, Event})). 327 | 328 | -spec dbg_fun(Entity :: erlwitness:entity(), EntityProcType :: erlwitness:process_type(), Watcher :: pid()) 329 | -> {dbg_fun(), FuncState :: active}. 330 | dbg_fun(Entity, EntityProcType, Watcher) -> 331 | Fun = fun 332 | (active, Event, EntityProcName) -> 333 | case erlwitness_lobby:is_entity_watched_by(Entity, Watcher) of 334 | false -> done; 335 | true -> 336 | Timestamp = os:timestamp(), 337 | ok = report_dbg_event(Watcher, Timestamp, Entity, self(), 338 | EntityProcType, EntityProcName, 339 | Event), 340 | ok = erlwitness_entity:set_params(Entity, EntityProcType, EntityProcName), 341 | active 342 | end 343 | end, 344 | {Fun, active}. 345 | -------------------------------------------------------------------------------- /test/erlwitness_test_entity.erl: -------------------------------------------------------------------------------- 1 | % vim: set expandtab softtabstop=4 shiftwidth=4: 2 | -module(erlwitness_test_entity). 3 | -author('Guilherme Andrade '). 4 | -behaviour(gen_server). 5 | 6 | -compile([{parse_transform, erlwitness_transform}]). 7 | -compile([{parse_transform, lager_transform}]). 8 | 9 | -export([start_link/2, 10 | start/2]). 11 | -export([init/1, 12 | handle_call/3, 13 | handle_cast/2, 14 | handle_info/2, 15 | terminate/2, 16 | code_change/3]). 17 | 18 | -include_lib("eunit/include/eunit.hrl"). 19 | 20 | -record(state, { 21 | value :: term() 22 | }). 23 | 24 | 25 | start_link(Entity, EntityProcType) -> 26 | {WrappedArgs, StartOptions} = erlwitness:get_start_extras(Entity, EntityProcType), 27 | gen_server:start_link(?MODULE, WrappedArgs, StartOptions). 28 | 29 | start(Entity, EntityProcType) -> 30 | {WrappedArgs, StartOptions} = erlwitness:get_start_extras(Entity, EntityProcType), 31 | gen_server:start(?MODULE, WrappedArgs, StartOptions). 32 | 33 | init(WrappedArgs) -> 34 | [] = erlwitness:unwrap_init_args(WrappedArgs), 35 | erlwitness:finalize_init(WrappedArgs, {ok, #state{}}). 36 | 37 | handle_call(die, _From, State) -> 38 | {stop, normal, ok, State}; 39 | 40 | handle_call(_Request, _From, State) -> 41 | Reply = ok, 42 | {reply, Reply, State}. 43 | 44 | handle_cast(change_state, State) -> 45 | {noreply, State#state{ value=os:timestamp() }}; 46 | 47 | handle_cast({log_message, Message}, State) when is_list(Message) -> 48 | lager:debug("~p", [Message]), 49 | {noreply, State}; 50 | 51 | handle_cast(_Msg, State) -> 52 | {noreply, State}. 53 | 54 | handle_info(_Info, State) -> 55 | {noreply, State}. 56 | 57 | terminate(_Reason, _State) -> 58 | ok. 59 | 60 | code_change(_OldVsn, State, _Extra) -> 61 | {ok, State}. 62 | -------------------------------------------------------------------------------- /test/erlwitness_test_watcher.erl: -------------------------------------------------------------------------------- 1 | % vim: set expandtab softtabstop=4 shiftwidth=4: 2 | -module(erlwitness_test_watcher). 3 | -author('Guilherme Andrade '). 4 | 5 | -behaviour(gen_server). 6 | -behaviour(erlwitness_watcher). 7 | 8 | -export([start_link/2, 9 | start/2]). 10 | 11 | -export([init/1, 12 | handle_gencall_event/8, 13 | handle_gencast_event/7, 14 | handle_geninfo_event/7, 15 | handle_newstate_event/7, 16 | handle_lager_event/8, 17 | handle_call/3, 18 | handle_cast/2, 19 | handle_info/2, 20 | terminate/2, 21 | code_change/3]). 22 | 23 | -include_lib("eunit/include/eunit.hrl"). 24 | 25 | -record(state, { 26 | destination_pid :: pid() 27 | }). 28 | 29 | 30 | start_link(Entities, FeedbackTestPid) -> 31 | erlwitness_watcher:start_link(Entities, ?MODULE, [FeedbackTestPid]). 32 | 33 | start(Entities, FeedbackTestPid) -> 34 | erlwitness_watcher:start(Entities, ?MODULE, [FeedbackTestPid]). 35 | 36 | 37 | init([FeedbackTestPid]) -> 38 | {ok, #state{ destination_pid=FeedbackTestPid }}. 39 | 40 | 41 | handle_gencall_event(Timestamp, Entity, EntityPid, EntityProcType, EntityProcName, Call, From, State) -> 42 | handle_event(Timestamp, Entity, EntityPid, EntityProcType, EntityProcName, {call, Call, From}, State), 43 | {noreply, State}. 44 | 45 | handle_gencast_event(Timestamp, Entity, EntityPid, EntityProcType, EntityProcName, Cast, State) -> 46 | handle_event(Timestamp, Entity, EntityPid, EntityProcType, EntityProcName, {cast, Cast}, State), 47 | {noreply, State}. 48 | 49 | handle_geninfo_event(Timestamp, Entity, EntityPid, EntityProcType, EntityProcName, Info, State) -> 50 | handle_event(Timestamp, Entity, EntityPid, EntityProcType, EntityProcName, {info, Info}, State), 51 | {noreply, State}. 52 | 53 | handle_newstate_event(Timestamp, Entity, EntityPid, EntityProcType, EntityProcName, EntityProcState, State) -> 54 | handle_event(Timestamp, Entity, EntityPid, EntityProcType, EntityProcName, {new_state, EntityProcState}, State), 55 | {noreply, State}. 56 | 57 | handle_lager_event(Timestamp, Entity, EntityPid, EntityProcType, EntityProcName, LagerMFA, LagerDebugInfo, State) -> 58 | handle_event(Timestamp, Entity, EntityPid, EntityProcType, EntityProcName, {lager, LagerMFA, LagerDebugInfo}, State), 59 | {noreply, State}. 60 | 61 | 62 | handle_call(_Request, _From, State) -> 63 | {noreply, State}. 64 | 65 | 66 | handle_cast(unwatch_all, State) -> 67 | ok = erlwitness_watcher:unwatch_all(), 68 | {noreply, State}; 69 | 70 | handle_cast({unwatch, Entity}, State) -> 71 | ok = erlwitness_watcher:unwatch(Entity), 72 | {noreply, State}; 73 | 74 | handle_cast(_Msg, State) -> 75 | {noreply, State}. 76 | 77 | 78 | handle_info(_Info, State) -> 79 | {noreply, State}. 80 | 81 | 82 | terminate(_Reason, _State) -> 83 | ok. 84 | 85 | 86 | code_change(_OldVsn, State, _Extra) -> 87 | {ok, State}. 88 | 89 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 90 | handle_event(Timestamp, Entity, EntityPid, EntityProcType, EntityProcName, Event, State) -> 91 | %?debugFmt("got event: timestamp ~p, entity ~p, entity_pid ~p, entity_proc_type ~p, " 92 | % "entity_proc_name ~p, event ~p~n", 93 | % [Timestamp, Entity, EntityPid, EntityProcType, EntityProcName, Event]), 94 | State#state.destination_pid ! {EntityPid, EntityProcType, Event, self()}. 95 | -------------------------------------------------------------------------------- /test/erlwitness_tests.app.config: -------------------------------------------------------------------------------- 1 | % vim: set ft=erlang expandtab softtabstop=4 shiftwidth=4: 2 | [ 3 | {erlwitness, [ 4 | {entity_lookup_module, erlwitness_index_serv}, 5 | {erlwitness_index_serv_count, 2} 6 | ]}, 7 | 8 | {lager, [ 9 | {handlers, [ 10 | {lager_console_backend, emergency} 11 | ]} 12 | ]} 13 | ]. 14 | -------------------------------------------------------------------------------- /test/erlwitness_tests.erl: -------------------------------------------------------------------------------- 1 | % vim: set expandtab softtabstop=4 shiftwidth=4: 2 | -module(erlwitness_tests). 3 | -author('Guilherme Andrade '). 4 | 5 | -include_lib("eunit/include/eunit.hrl"). 6 | 7 | erlwitness_test_() -> 8 | {foreach, 9 | fun() -> 10 | error_logger:tty(false), 11 | {ok, _} = application:ensure_all_started(erlwitness) 12 | end, 13 | fun(_) -> 14 | ok = application:stop(erlwitness), 15 | ok = application:stop(lager), 16 | ok = application:stop(goldrush), 17 | error_logger:tty(true) 18 | end, 19 | [{<<"Watcher before entity">>, fun watcher_before_entity/0}, 20 | {<<"Entity before watcher">>, fun entity_before_watcher/0}, 21 | {<<"Multiple entity watchers">>, fun multiple_entity_watchers/0}, 22 | {<<"Multiple entity processes">>, fun multiple_entity_processes/0}, 23 | {<<"Multiple everything">>, fun multiple_everything/0}, 24 | {<<"Watcher death">>, fun watcher_death/0}, 25 | {<<"Unwatching">>, fun unwatching/0} 26 | ] 27 | }. 28 | 29 | watcher_before_entity() -> 30 | random:seed(erlang:now()), 31 | Self = self(), 32 | Entity = random_entity(), 33 | {ok, WatcherProc} = erlwitness_test_watcher:start_link([Entity], Self), 34 | timer:sleep(20), % Give it enough time 35 | 36 | EntityProcType = barfoo, 37 | {ok, EntityProc} = erlwitness_test_entity:start_link(Entity, EntityProcType), 38 | ?assertMatch({EntityProc, EntityProcType, {new_state, _}, WatcherProc}, recv_msg()), 39 | 40 | Cast = {log_message, "a cast"}, 41 | gen_server:cast(EntityProc, Cast), 42 | ?assertEqual({EntityProc, EntityProcType, {cast, Cast}, WatcherProc}, recv_msg()), 43 | ?assertMatch({EntityProc, EntityProcType, {lager, {lager, debug, _}, 44 | {erlwitness_test_entity, handle_cast, _}}, 45 | WatcherProc}, recv_msg()), 46 | 47 | Call = <<"a call">>, 48 | ok = gen_server:call(EntityProc, Call), 49 | ?assertMatch({EntityProc, EntityProcType, {call, Call, {Self, _Ref}}, WatcherProc}, recv_msg()), 50 | 51 | ChangeStateCast = change_state, 52 | gen_server:cast(EntityProc, ChangeStateCast), 53 | ?assertEqual({EntityProc, EntityProcType, {cast, ChangeStateCast}, WatcherProc}, recv_msg()), 54 | ?assertMatch({EntityProc, EntityProcType, {new_state, _}, WatcherProc}, recv_msg()), 55 | 56 | Info = 'an info', 57 | EntityProc ! Info, 58 | ?assertEqual({EntityProc, EntityProcType, {info, Info}, WatcherProc}, recv_msg()), 59 | 60 | StopCall = die, 61 | _StopCallReply = gen_server:call(EntityProc, StopCall), 62 | ?assertMatch({EntityProc, EntityProcType, {call, StopCall, {Self, _Ref}}, WatcherProc}, recv_msg()), 63 | ok. 64 | 65 | entity_before_watcher() -> 66 | random:seed(erlang:now()), 67 | Self = self(), 68 | Entity = random_entity(), 69 | EntityProcType = foobar, 70 | {ok, EntityProc} = erlwitness_test_entity:start_link(Entity, EntityProcType), 71 | timer:sleep(20), % Give it enough time 72 | 73 | {ok, WatcherProc} = erlwitness_test_watcher:start_link([Entity], Self), 74 | timer:sleep(10), 75 | 76 | Cast = {log_message, "a cast"}, 77 | ok = gen_server:cast(EntityProc, Cast), 78 | ?assertEqual({EntityProc, EntityProcType, {cast, Cast}, WatcherProc}, recv_msg()), 79 | ?assertMatch({EntityProc, EntityProcType, {lager, {lager, debug, _}, 80 | {erlwitness_test_entity, handle_cast, _}}, 81 | WatcherProc}, recv_msg()), 82 | ?assertMatch({EntityProc, EntityProcType, {new_state, _}, WatcherProc}, recv_msg()), 83 | 84 | Call = <<"a call">>, 85 | ok = gen_server:call(EntityProc, Call), 86 | ?assertMatch({EntityProc, EntityProcType, {call, Call, {Self, _Ref}}, WatcherProc}, recv_msg()), 87 | 88 | ChangeStateCast = change_state, 89 | ok = gen_server:cast(EntityProc, ChangeStateCast), 90 | ?assertEqual({EntityProc, EntityProcType, {cast, ChangeStateCast}, WatcherProc}, recv_msg()), 91 | ?assertMatch({EntityProc, EntityProcType, {new_state, _}, WatcherProc}, recv_msg()), 92 | 93 | Info = 'an info', 94 | EntityProc ! Info, 95 | ?assertEqual({EntityProc, EntityProcType, {info, Info}, WatcherProc}, recv_msg()), 96 | 97 | StopCall = die, 98 | _StopCallReply = gen_server:call(EntityProc, StopCall), 99 | ?assertMatch({EntityProc, EntityProcType, {call, StopCall, {Self, _Ref}}, WatcherProc}, recv_msg()), 100 | ok. 101 | 102 | multiple_entity_watchers() -> 103 | random:seed(erlang:now()), 104 | Self = self(), 105 | Entity = random_entity(), 106 | EntityProcType = {dunno, random_blob()}, 107 | 108 | WatcherCount = 5 + random:uniform(200), 109 | Watchers = [ 110 | begin 111 | {ok, WatcherProc} = erlwitness_test_watcher:start_link([Entity], Self), 112 | WatcherProc 113 | end 114 | || _ <- lists:seq(1, WatcherCount)], 115 | timer:sleep(30), 116 | 117 | {ok, EntityProc} = erlwitness_test_entity:start_link(Entity, EntityProcType), 118 | timer:sleep(10), 119 | 120 | MessageBatch1 = lists:sort( recv_msgs(WatcherCount) ), 121 | lists:foreach( 122 | fun ({WatcherProc, {MBEntityProc, MBEntityProcType, MBEvent, MBWatcherProc}}) -> 123 | ?assertMatch({EntityProc, EntityProcType, {new_state, _}, WatcherProc}, 124 | {MBEntityProc, MBEntityProcType, MBEvent, MBWatcherProc}) 125 | end, 126 | lists:zip(Watchers, MessageBatch1)), 127 | 128 | TestInfo = {woohoo, random_blob()}, 129 | EntityProc ! TestInfo, 130 | MessageBatch2 = lists:sort( recv_msgs(WatcherCount) ), 131 | lists:foreach( 132 | fun ({WatcherProc, {MBEntityProc, MBEntityProcType, MBEvent, MBWatcherProc}}) -> 133 | ?assertMatch({EntityProc, EntityProcType, {info, _}, WatcherProc}, 134 | {MBEntityProc, MBEntityProcType, MBEvent, MBWatcherProc}) 135 | end, 136 | lists:zip(Watchers, MessageBatch2)). 137 | 138 | 139 | 140 | multiple_entity_processes() -> 141 | random:seed(erlang:now()), 142 | Self = self(), 143 | Entity = random_entity(), 144 | {ok, WatcherProc} = erlwitness_test_watcher:start_link([Entity], Self), 145 | timer:sleep(10), 146 | 147 | ProcCount = 5 + random:uniform(200), 148 | BaseEntityProcRefs = [ 149 | begin 150 | EntityProcType = random_blob(), 151 | {ok, EntityProc} = erlwitness_test_entity:start_link(Entity, EntityProcType), 152 | {EntityProc, EntityProcType} 153 | end 154 | || _ <- lists:seq(1, ProcCount)], 155 | EntityProcRefs = lists:sort(BaseEntityProcRefs), 156 | timer:sleep(30), 157 | 158 | 159 | MessageBatch1 = lists:sort( recv_msgs(ProcCount) ), 160 | lists:foreach( 161 | fun ({{RefEntityProc, RefEntityProcType}, {MBEntityProc, MBEntityProcType, 162 | MBEvent, MBWatcherProc}}) -> 163 | ?assertMatch({RefEntityProc, RefEntityProcType, {new_state, _}, WatcherProc}, 164 | {MBEntityProc, MBEntityProcType, MBEvent, MBWatcherProc}) 165 | end, 166 | lists:zip(EntityProcRefs, MessageBatch1)), 167 | 168 | 169 | lists:foreach(fun ({EntityProc, _EntityProcType}) -> ok = gen_server:cast(EntityProc, change_state) end, 170 | EntityProcRefs), 171 | {BaseMessageBatch2, BaseMessageBatch3} = lists:split(ProcCount, 172 | lists:keysort(3, recv_msgs(ProcCount * 2) )), 173 | MessageBatch2 = lists:sort(BaseMessageBatch2), 174 | MessageBatch3 = lists:sort(BaseMessageBatch3), 175 | lists:foreach( 176 | fun ({{RefEntityProc, RefEntityProcType}, {MBEntityProc, MBEntityProcType, 177 | MBEvent, MBWatcherProc}}) -> 178 | ?assertEqual({RefEntityProc, RefEntityProcType, {cast, change_state}, WatcherProc}, 179 | {MBEntityProc, MBEntityProcType, MBEvent, MBWatcherProc}) 180 | end, 181 | lists:zip(EntityProcRefs, MessageBatch2)), 182 | lists:foreach( 183 | fun ({{RefEntityProc, RefEntityProcType}, {MBEntityProc, MBEntityProcType, 184 | MBEvent, MBWatcherProc}}) -> 185 | ?assertMatch({RefEntityProc, RefEntityProcType, {new_state, _}, WatcherProc}, 186 | {MBEntityProc, MBEntityProcType, MBEvent, MBWatcherProc}) 187 | end, 188 | lists:zip(EntityProcRefs, MessageBatch3)). 189 | 190 | multiple_everything() -> 191 | random:seed(erlang:now()), 192 | Self = self(), 193 | EntityCount = 2 + random:uniform(1000), 194 | Entities = [random_entity() || _ <- lists:seq(1, EntityCount)], 195 | 196 | WatcherCount = 5 + random:uniform(500), 197 | WatcherRefs = [ 198 | begin 199 | Entity1 = pick_random_from_list(Entities), 200 | Entity2 = pick_random_from_list(Entities), 201 | Entity3 = pick_random_from_list(Entities), 202 | {ok, WatcherProc} = erlwitness_test_watcher:start_link([Entity1, Entity2, Entity3], Self), 203 | {WatcherProc, [Entity1, Entity2, Entity3]} 204 | end 205 | || _ <- lists:seq(1, WatcherCount)], 206 | timer:sleep(30), 207 | 208 | ProcCount = 5 + random:uniform(1200), 209 | BaseEntityProcRefs = [ 210 | begin 211 | EntityProcType = random_blob(), 212 | Entity = pick_random_from_list(Entities), 213 | {ok, EntityProc} = erlwitness_test_entity:start_link(Entity, EntityProcType), 214 | {EntityProc, EntityProcType, Entity} 215 | end 216 | || _ <- lists:seq(1, ProcCount)], 217 | EntityProcRefs = lists:sort(BaseEntityProcRefs), 218 | timer:sleep(30), 219 | 220 | SpawnedEntities = [Entity || {_EntityProc, _EntityProcType, Entity} <- EntityProcRefs], 221 | SpawnedEntitiesSet = sets:from_list(SpawnedEntities), 222 | RelevantWatcherRefs = lists:filter( 223 | fun ({_WatcherProc, WatchedEntities}) -> 224 | not sets:is_disjoint(sets:from_list(WatchedEntities), SpawnedEntitiesSet) 225 | end, 226 | WatcherRefs), 227 | ExpectedEventCount = lists:sum([sets:size(sets:intersection(SpawnedEntitiesSet, 228 | sets:from_list(WatchedEntities))) 229 | || {_WatcherProc, WatchedEntities} <- RelevantWatcherRefs]), 230 | 231 | MessageBatch1 = recv_msgs(ExpectedEventCount), 232 | lists:foreach( 233 | fun ({MBEntityProc, MBEntityProcType, MBEvent, MBWatcherProc}) -> 234 | {WatcherProc, WatchedEntities} = lists:keyfind(MBWatcherProc, 1, RelevantWatcherRefs), 235 | {EntityProc, EntityProcType, Entity} = lists:keyfind(MBEntityProc, 1, EntityProcRefs), 236 | ?assert( lists:member(Entity, WatchedEntities) ), 237 | ?assertMatch({EntityProc, EntityProcType, {new_state, _}, WatcherProc}, 238 | {MBEntityProc, MBEntityProcType, MBEvent, MBWatcherProc}) 239 | end, 240 | MessageBatch1). 241 | 242 | watcher_death() -> 243 | random:seed(erlang:now()), 244 | Self = self(), 245 | Entity = random_entity(), 246 | 247 | WatcherCount = 10 + random:uniform(50), 248 | Watchers1 = lists:sort([ 249 | begin 250 | {ok, WatcherProc} = erlwitness_test_watcher:start([Entity], Self), 251 | WatcherProc 252 | end 253 | || _ <- lists:seq(1, WatcherCount)]), 254 | timer:sleep(20), 255 | RegisteredWatchers1 = lists:sort( erlwitness_lobby:watchers_local_lookup(Entity) ), 256 | ?assertEqual(Watchers1, RegisteredWatchers1), 257 | {ok, EntityProc} = erlwitness_test_entity:start_link(Entity, random_blob()), 258 | timer:sleep(10), 259 | _MessageBatch1 = recv_msgs(length(RegisteredWatchers1)), % Events captured by all watchers 260 | ?assertEqual({message_queue_len, 0}, process_info(Self, message_queue_len)), 261 | 262 | {DeathRowA, Watchers2} = lists:mapfoldl( 263 | fun (_, Acc) -> 264 | Watcher = pick_random_from_list(Acc), 265 | {Watcher, lists:delete(Watcher, Acc)} 266 | end, 267 | Watchers1, lists:seq(1, length(RegisteredWatchers1) div 2)), 268 | [exit(Watcher, kill) || Watcher <- DeathRowA], 269 | timer:sleep(20), 270 | RegisteredWatchers2 = lists:sort( erlwitness_lobby:watchers_local_lookup(Entity) ), 271 | ?assertEqual(Watchers2, RegisteredWatchers2), 272 | ok = gen_server:cast(EntityProc, random_blob()), 273 | timer:sleep(5), 274 | _MessageBatch2 = recv_msgs(length(RegisteredWatchers2)), % Events captured only by remaining watchers 275 | ?assertEqual({message_queue_len, 0}, process_info(Self, message_queue_len)), 276 | 277 | DeathRowB = RegisteredWatchers2, 278 | [exit(Watcher, kill) || Watcher <- DeathRowB], 279 | timer:sleep(20), 280 | ?assertEqual([], erlwitness_lobby:watchers_local_lookup(Entity)), 281 | ok = gen_server:cast(EntityProc, random_blob()), 282 | ?assertEqual({message_queue_len, 0}, process_info(Self, message_queue_len)). % No watchers, no events. 283 | 284 | unwatching() -> 285 | random:seed(erlang:now()), 286 | Self = self(), 287 | 288 | WatcherCount = 5 + random:uniform(20), 289 | WatcherRefs = lists:sort([ 290 | begin 291 | EntityCount = random:uniform(5), 292 | Entities = [random_entity() || _ <- lists:seq(1, EntityCount)], 293 | {ok, WatcherProc} = erlwitness_test_watcher:start(Entities, Self), 294 | {WatcherProc, Entities} 295 | end 296 | || _ <- lists:seq(1, WatcherCount)]), 297 | timer:sleep(20), 298 | Watchers = [Watcher || {Watcher, _Entities} <- WatcherRefs], 299 | AllEntities = sets:to_list( sets:from_list( lists:foldl(fun erlang:'++'/2, [], 300 | [Entities || {_Watcher, Entities} 301 | <- WatcherRefs]))), 302 | RegisteredWatchers1 = sets:to_list( sets:from_list( 303 | lists:foldl(fun erlang:'++'/2, [], 304 | [erlwitness_lobby:watchers_local_lookup(Entity) 305 | || Entity <- AllEntities]))), 306 | ?assertEqual(Watchers, lists:sort(RegisteredWatchers1)), 307 | 308 | {ToUnwatch, RemainingEntities} = lists:mapfoldl( 309 | fun (V, Acc) -> 310 | {pick_random_from_list(Acc), lists:delete(V, Acc)} 311 | end, 312 | AllEntities, lists:seq(1, length(AllEntities) div 3)), 313 | lists:foreach( 314 | fun (Entity) -> 315 | EntityWatchers = erlwitness_lobby:watchers_local_lookup(Entity), 316 | lists:foreach( 317 | fun (Watcher) -> 318 | ok = gen_server:cast(Watcher, {unwatch, Entity}) 319 | end, 320 | EntityWatchers), 321 | timer:sleep(20), 322 | ?assertEqual([], erlwitness_lobby:watchers_local_lookup(Entity)) 323 | end, 324 | ToUnwatch), 325 | 326 | 327 | lists:foreach(fun (Watcher) -> ok = gen_server:cast(Watcher, unwatch_all) end, 328 | Watchers), 329 | timer:sleep(20), 330 | RegisteredWatchers3 = sets:to_list( sets:from_list( 331 | lists:foldl(fun erlang:'++'/2, [], 332 | [erlwitness_lobby:watchers_local_lookup(Entity) 333 | || Entity <- RemainingEntities]))), 334 | ?assertEqual([], RegisteredWatchers3). 335 | 336 | random_entity() -> 337 | {foobar, random_blob()}. 338 | 339 | random_blob() -> 340 | random:uniform(1 bsl 25). 341 | 342 | pick_random_from_list(L) -> 343 | lists:nth(random:uniform(length(L)), L). 344 | 345 | recv_msgs(N) -> 346 | [recv_msg() || _ <- lists:seq(1, N)]. 347 | 348 | recv_msg() -> 349 | receive V -> V end. 350 | --------------------------------------------------------------------------------