├── .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 |
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 |
17 |
18 |
19 | entity() = any()
20 |
21 |
22 |
23 | init_result() = {ok, S::term()} | {ok, S::term(), timeout()} | ignore | {stop, Reason::term()}
24 |
25 |
26 |
27 | process_type() = any()
28 |
29 |
30 |
31 | abstract datatype : wrapped_init_args()
32 |
33 |
34 |
35 |
41 |
42 |
43 |
44 |
45 | Finalize entity's gen_server initialization
48 |
49 |
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 |
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 |
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 |
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 |
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 |
18 |
21 |
22 |
23 |
24 |
25 |
26 |
start(StartType, StartArgs) -> any()
27 |
28 |
29 |
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 |
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 |
17 |
21 |
22 |
23 |
24 |
25 |
26 |
get_index_serv_count() -> pos_integer()
27 |
28 |
29 |
30 |
31 |
get_lookup_module() -> module()
32 |
33 |
34 |
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 |
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 |
17 |
21 |
22 |
23 |
24 |
25 |
28 |
29 |
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 |
35 |
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 |
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 |
18 |
25 |
26 |
27 |
28 |
29 |
32 |
33 |
34 |
37 |
38 |
39 |
42 |
43 |
44 |
47 |
48 |
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 |
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 |
18 |
27 |
28 |
29 |
30 |
31 |
34 |
35 |
36 |
39 |
40 |
41 |
42 |
start_link() -> {ok, Pid} | ignore | {error, Error}
43 |
44 | Starts the server
45 |
46 |
47 |
48 |
51 |
52 |
53 |
54 |
unwatch_by_pid(WatcherPid::pid()) -> ok
55 |
56 |
57 |
58 |
61 |
62 |
63 |
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 |
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 |
18 |
19 |
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 |
18 |
21 |
22 |
23 |
24 |
25 |
26 |
init(X1) -> any()
27 |
28 |
29 |
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 |
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 |
19 |
20 |
21 | dbg_fun() = fun((FuncState::dbg_fun_state() , Event::any(), ProcName::any()) -> NewFuncState::dbg_fun_state() )
22 |
23 |
24 |
25 | dbg_fun_state() = active | done
26 |
27 |
28 |
29 | handler_return() = {noreply, NewState::term()} | {noreply, NewState::term(), timeout() | hibernate} | {stop, Reason::term(), NewState::term()}
30 |
31 |
32 |
33 |
45 |
46 |
47 |
48 |
49 |
52 |
53 |
54 |
57 |
58 |
59 |
60 |
report_init(Watcher, Timestamp, Entity, EntityPid, EntityProcType, EntityProcName, EntityProcState) -> any()
61 |
62 |
63 |
64 |
65 |
report_lager_event(Watcher, Timestamp, Entity, EntityPid, EntityProcType, EntityProcName, LagerModule, LagerFunction, LagerArgs, CodeModule, CodeFunction, CodeLine) -> any()
66 |
67 |
68 |
69 |
70 |
start(Entities, WatcherModule, WatcherArgs) -> any()
71 |
72 |
73 |
74 |
75 |
start(Name, Entities, WatcherModule, WatcherArgs) -> any()
76 |
77 |
78 |
79 |
80 |
start_link(Entities, WatcherModule, WatcherArgs) -> any()
81 |
82 |
83 |
84 |
85 |
start_link(Name, Entities, WatcherModule, WatcherArgs) -> any()
86 |
87 |
88 |
89 |
92 |
93 |
94 |
95 |
unwatch_all() -> ok
96 |
97 |
98 |
99 |
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 |
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 |
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 |
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 | For example: remote activity tracers for sizeable software development teams (instead of _hey, may you please check the log?_)
23 |
24 |
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 |
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 |
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 |
137 | Compiler flags:
138 |
139 | {parse_transform, erlwitness_transform}
140 |
141 |
142 |
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 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------