1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 1997-2016. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20
21%%
22%% Getting started:
23%%
24%% 1 Start one or more distributed Erlang nodes
25%% 2a Connect the nodes, e.g. with net_adm:ping/1
26%% 3a Run mnesia_meter:go()
27%% 3b Run mnesia_meter:go(ReplicaType)
28%% 3c Run mnesia_meter:go(ReplicaType, Nodes)
29
30-module(mnesia_meter).
31-author('hakan@erix.ericsson.se').
32-export([
33         go/0,
34         go/1,
35         go/2,
36         repeat_meter/2
37        ]).
38
39-record(person, {name,        %% atomic, unique key
40                 data,        %% compound structure
41                 married_to,  %% name of partner or undefined
42                 children}).  %% list of children
43
44-record(meter, {desc, init, meter, micros}).
45
46-record(result, {desc, list}).
47
48-define(TIMES, 1000).
49
50go() ->
51    go(ram_copies).
52
53go(ReplicaType) ->
54    go(ReplicaType, [node() | nodes()]).
55
56go(ReplicaType, Nodes) ->
57    {ok, FunOverhead} = tc(fun(_) -> {atomic, ok} end, ?TIMES),
58    Size = size(term_to_binary(#person{})),
59    io:format("A fun apply costs ~p micro seconds. Record size is ~p bytes.~n",
60              [FunOverhead, Size]),
61    Res = go(ReplicaType, Nodes, [], FunOverhead, []),
62    NewRes = rearrange(Res, []),
63    DescHeader = lists:flatten(io_lib:format("~w on ~w", [ReplicaType, Nodes])),
64    ItemHeader = lists:seq(1, length(Nodes)),
65    Header = #result{desc = DescHeader, list = ItemHeader},
66    SepList = ['--------' || _ <- Nodes],
67    Separator = #result{desc = "", list = SepList},
68    display([Separator, Header, Separator | NewRes] ++ [Separator]).
69
70go(_ReplicaType, [], _Config, _FunOverhead, Acc) ->
71    Acc;
72go(ReplicaType, [H | T], OldNodes, FunOverhead, Acc) ->
73    Nodes = [H | OldNodes],
74    Config = [{ReplicaType, Nodes}],
75    Res = run(Nodes, Config, FunOverhead),
76    go(ReplicaType, T, Nodes, FunOverhead, [{ReplicaType, Nodes, Res} | Acc]).
77
78rearrange([{_ReplicaType, _Nodes, Meters} | Tail], Acc) ->
79    Acc2 = [add_meter(M, Acc) || M <- Meters],
80    rearrange(Tail, Acc2);
81rearrange([], Acc) ->
82    Acc.
83
84add_meter(M, Acc) ->
85    case lists:keysearch(M#meter.desc, #result.desc, Acc) of
86        {value, R} ->
87            R#result{list = [M#meter.micros | R#result.list]};
88        false ->
89            #result{desc = M#meter.desc, list = [M#meter.micros]}
90    end.
91
92display(Res) ->
93    MaxDesc = lists:max([length(R#result.desc) || R <- Res]),
94    Format = lists:concat(["! ~-", MaxDesc, "s"]),
95    display(Res, Format, MaxDesc).
96
97display([R | Res], Format, MaxDesc) ->
98    case R#result.desc of
99	"" ->
100	    io:format(Format, [lists:duplicate(MaxDesc, "-")]);
101	Desc ->
102	    io:format(Format, [Desc])
103    end,
104    display_items(R#result.list, R#result.desc),
105    io:format(" !~n", []),
106    display(Res, Format, MaxDesc);
107display([], _Format, _MaxDesc) ->
108    ok.
109
110display_items([_Item | Items], "") ->
111    io:format(" ! ~s", [lists:duplicate(10, $-)]),
112    display_items(Items, "");
113display_items([Micros | Items], Desc) ->
114    io:format(" ! ~10w", [Micros]),
115    display_items(Items, Desc);
116display_items([], _Desc) ->
117    ok.
118
119%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
120
121meters() ->
122    [#meter{desc = "transaction update two records with read and write",
123            init = fun write_records/2,
124            meter = fun update_records/1},
125     #meter{desc = "transaction update two records with wread and write",
126            init = fun write_records/2,
127            meter = fun w_update_records/1},
128     #meter{desc = "transaction update two records with read and s_write",
129            init = fun s_write_records/2,
130            meter = fun s_update_records/1},
131     #meter{desc = "sync_dirty  update two records with read and write",
132            init = fun sync_dirty_write_records/2,
133            meter = fun sync_dirty_update_records/1},
134     #meter{desc = "async_dirty update two records with read and write",
135            init = fun async_dirty_write_records/2,
136            meter = fun async_dirty_update_records/1},
137     #meter{desc = "plain fun   update two records with dirty_read and dirty_write",
138            init = fun dirty_write_records/2,
139            meter = fun dirty_update_records/1},
140     #meter{desc = "ets         update two records with read and write (local only)",
141            init = fun ets_opt_write_records/2,
142            meter = fun ets_update_records/1},
143     #meter{desc = "plain fun   update two records with ets:lookup and ets:insert (local only)",
144            init = fun bif_opt_write_records/2,
145            meter = fun bif_update_records/1},
146     #meter{desc = "plain fun   update two records with dets:lookup and dets:insert (local only)",
147            init = fun dets_opt_write_records/2,
148            meter = fun dets_update_records/1},
149
150     #meter{desc = "transaction write two records with write",
151            init = fun write_records/2,
152            meter = fun(X) -> write_records(X, 0-X) end},
153     #meter{desc = "transaction write two records with s_write",
154            init = fun s_write_records/2,
155            meter = fun(X) -> s_write_records(X, 0-X) end},
156     #meter{desc = "sync_dirty  write two records with write",
157            init = fun sync_dirty_write_records/2,
158            meter = fun(X) -> sync_dirty_write_records(X, 0-X) end},
159     #meter{desc = "async_dirty write two records with write",
160            init = fun async_dirty_write_records/2,
161            meter = fun(X) -> async_dirty_write_records(X, 0-X) end},
162     #meter{desc = "plain fun   write two records with dirty_write",
163            init = fun dirty_write_records/2,
164            meter = fun(X) -> dirty_write_records(X, 0-X) end},
165     #meter{desc = "ets         write two records with write (local only)",
166            init = fun ets_opt_write_records/2,
167            meter = fun(X) -> ets_write_records(X, 0-X) end},
168     #meter{desc = "plain fun   write two records with ets:insert (local only)",
169            init = fun bif_opt_write_records/2,
170            meter = fun(X) -> bif_write_records(X, 0-X) end},
171     #meter{desc = "plain fun   write two records with dets:insert (local only)",
172            init = fun dets_opt_write_records/2,
173            meter = fun(X) -> dets_write_records(X, 0-X) end},
174
175     #meter{desc = "transaction read two records with read",
176            init = fun write_records/2,
177            meter = fun(X) -> read_records(X, 0-X) end},
178     #meter{desc = "sync_dirty  read two records with read",
179            init = fun sync_dirty_write_records/2,
180            meter = fun(X) -> sync_dirty_read_records(X, 0-X) end},
181     #meter{desc = "async_dirty read two records with read",
182            init = fun async_dirty_write_records/2,
183            meter = fun(X) -> async_dirty_read_records(X, 0-X) end},
184     #meter{desc = "plain fun   read two records with dirty_read",
185            init = fun dirty_write_records/2,
186            meter = fun(X) -> dirty_read_records(X, 0-X) end},
187     #meter{desc = "ets         read two records with read",
188            init = fun ets_opt_write_records/2,
189            meter = fun(X) -> ets_read_records(X, 0-X) end},
190     #meter{desc = "plain fun   read two records with ets:lookup",
191            init = fun bif_opt_write_records/2,
192            meter = fun(X) -> bif_read_records(X, 0-X) end},
193     #meter{desc = "plain fun   read two records with dets:lookup",
194            init = fun dets_opt_write_records/2,
195            meter = fun(X) -> dets_read_records(X, 0-X) end}
196    ].
197
198update_fun(Name) ->
199    fun() ->
200            case mnesia:read({person, Name}) of
201                [] ->
202                    mnesia:abort(no_such_person);
203                [Pers] ->
204                    [Partner] = mnesia:read({person, Pers#person.married_to}),
205                    mnesia:write(Pers#person{married_to = undefined}),
206                    mnesia:write(Partner#person{married_to = undefined})
207            end
208    end.
209
210update_records(Name) ->
211  mnesia:transaction(update_fun(Name)).
212
213sync_dirty_update_records(Name) ->
214  {atomic, mnesia:sync_dirty(update_fun(Name))}.
215
216async_dirty_update_records(Name) ->
217  {atomic, mnesia:async_dirty(update_fun(Name))}.
218
219ets_update_records(Name) ->
220  {atomic, mnesia:ets(update_fun(Name))}.
221
222w_update_records(Name) ->
223    F = fun() ->
224                case mnesia:wread({person, Name}) of
225                    [] ->
226                        mnesia:abort(no_such_person);
227                    [Pers] ->
228                        [Partner] = mnesia:wread({person, Pers#person.married_to}),
229                        mnesia:write(Pers#person{married_to = undefined}),
230                        mnesia:write(Partner#person{married_to = undefined})
231                end
232        end,
233    mnesia:transaction(F).
234
235s_update_records(Name) ->
236    F = fun() ->
237                case mnesia:read({person, Name}) of
238                    [] ->
239                        mnesia:abort(no_such_person);
240                    [Pers] ->
241                        [Partner] = mnesia:read({person, Pers#person.married_to}),
242                        mnesia:s_write(Pers#person{married_to = undefined}),
243                        mnesia:s_write(Partner#person{married_to = undefined})
244                end
245        end,
246    mnesia:transaction(F).
247
248dirty_update_records(Name) ->
249    case mnesia:dirty_read({person, Name}) of
250        [] ->
251            mnesia:abort(no_such_person);
252        [Pers] ->
253            [Partner] = mnesia:dirty_read({person, Pers#person.married_to}),
254            mnesia:dirty_write(Pers#person{married_to = undefined}),
255            mnesia:dirty_write(Partner#person{married_to = undefined})
256    end,
257    {atomic, ok}.
258
259bif_update_records(Name) ->
260    case ets:lookup(person, Name) of
261        [] ->
262            mnesia:abort(no_such_person);
263        [Pers] ->
264            [Partner] = ets:lookup(person, Pers#person.married_to),
265            ets:insert(person, Pers#person{married_to = undefined}),
266            ets:insert(person, Partner#person{married_to = undefined})
267    end,
268    {atomic, ok}.
269
270dets_update_records(Name) ->
271    case dets:lookup(person, Name) of
272        [] ->
273            mnesia:abort(no_such_person);
274        [Pers] ->
275            [Partner] = dets:lookup(person, Pers#person.married_to),
276            dets:insert(person, Pers#person{married_to = undefined}),
277            dets:insert(person, Partner#person{married_to = undefined})
278    end,
279    {atomic, ok}.
280
281write_records_fun(Pers, Partner) ->
282    fun() ->
283            P = #person{children = [ulla, bella]},
284            mnesia:write(P#person{name = Pers, married_to = Partner}),
285            mnesia:write(P#person{name = Partner, married_to = Pers})
286    end.
287
288write_records(Pers, Partner) ->
289    mnesia:transaction(write_records_fun(Pers, Partner)).
290
291sync_dirty_write_records(Pers, Partner) ->
292    {atomic, mnesia:sync_dirty(write_records_fun(Pers, Partner))}.
293
294async_dirty_write_records(Pers, Partner) ->
295    {atomic, mnesia:async_dirty(write_records_fun(Pers, Partner))}.
296
297ets_write_records(Pers, Partner) ->
298    {atomic, mnesia:ets(write_records_fun(Pers, Partner))}.
299
300s_write_records(Pers, Partner) ->
301    F = fun() ->
302                P = #person{children = [ulla, bella]},
303                mnesia:s_write(P#person{name = Pers, married_to = Partner}),
304                mnesia:s_write(P#person{name = Partner, married_to = Pers})
305        end,
306    mnesia:transaction(F).
307
308dirty_write_records(Pers, Partner) ->
309    P = #person{children = [ulla, bella]},
310    mnesia:dirty_write(P#person{name = Pers, married_to = Partner}),
311    mnesia:dirty_write(P#person{name = Partner, married_to = Pers}),
312    {atomic, ok}.
313
314ets_opt_write_records(Pers, Partner) ->
315    case mnesia:table_info(person, where_to_commit) of
316        [{N, ram_copies}] when N == node() ->
317            ets_write_records(Pers, Partner);
318        _ ->
319            throw(skipped)
320    end.
321
322bif_opt_write_records(Pers, Partner) ->
323    case mnesia:table_info(person, where_to_commit) of
324        [{N, ram_copies}] when N == node() ->
325            bif_write_records(Pers, Partner);
326        _ ->
327            throw(skipped)
328    end.
329
330bif_write_records(Pers, Partner) ->
331    P = #person{children = [ulla, bella]},
332    ets:insert(person, P#person{name = Pers, married_to = Partner}),
333    ets:insert(person, P#person{name = Partner, married_to = Pers}),
334    {atomic, ok}.
335
336dets_opt_write_records(Pers, Partner) ->
337    case mnesia:table_info(person, where_to_commit) of
338        [{N, disc_only_copies}] when N == node() ->
339            dets_write_records(Pers, Partner);
340        _ ->
341            throw(skipped)
342    end.
343
344dets_write_records(Pers, Partner) ->
345    P = #person{children = [ulla, bella]},
346    dets:insert(person, P#person{name = Pers, married_to = Partner}),
347    dets:insert(person, P#person{name = Partner, married_to = Pers}),
348    {atomic, ok}.
349
350read_records_fun(Pers, Partner) ->
351    fun() ->
352            case {mnesia:read({person, Pers}),
353                  mnesia:read({person, Partner})} of
354                {[_], [_]} ->
355                    ok;
356                _ ->
357                    mnesia:abort(no_such_person)
358            end
359    end.
360
361read_records(Pers, Partner) ->
362    mnesia:transaction(read_records_fun(Pers, Partner)).
363
364sync_dirty_read_records(Pers, Partner) ->
365    {atomic, mnesia:sync_dirty(read_records_fun(Pers, Partner))}.
366
367async_dirty_read_records(Pers, Partner) ->
368    {atomic, mnesia:async_dirty(read_records_fun(Pers, Partner))}.
369
370ets_read_records(Pers, Partner) ->
371    {atomic, mnesia:ets(read_records_fun(Pers, Partner))}.
372
373dirty_read_records(Pers, Partner) ->
374    case {mnesia:dirty_read({person, Pers}),
375          mnesia:dirty_read({person, Partner})} of
376        {[_], [_]} ->
377            {atomic, ok};
378        _ ->
379            mnesia:abort(no_such_person)
380    end.
381
382bif_read_records(Pers, Partner) ->
383    case {ets:lookup(person, Pers),
384          ets:lookup(person, Partner)} of
385        {[_], [_]} ->
386            {atomic, ok};
387        _ ->
388            mnesia:abort(no_such_person)
389    end.
390
391dets_read_records(Pers, Partner) ->
392    case {dets:lookup(person, Pers),
393          dets:lookup(person, Partner)} of
394        {[_], [_]} ->
395            {atomic, ok};
396        _ ->
397            mnesia:abort(no_such_person)
398    end.
399
400%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
401
402run(Nodes, Config, FunOverhead) ->
403    Meters = meters(),
404    io:format("Run ~w meters with table config: ~w~n", [length(Meters), Config]),
405    rpc:multicall(Nodes, mnesia, lkill, []),
406    start(Nodes, Config),
407    Res = [run_meter(Data, Nodes, FunOverhead) || Data <- Meters],
408    stop(Nodes),
409    Res.
410
411run_meter(M, Nodes, FunOverhead) when is_record(M, meter) ->
412    io:format(".", []),
413    case catch init_records(M#meter.init, ?TIMES) of
414        {atomic, ok} ->
415            rpc:multicall(Nodes, mnesia, dump_log, []),
416            case tc(M#meter.meter, ?TIMES) of
417                {ok, Micros} ->
418                    M#meter{micros = lists:max([0, Micros - FunOverhead])};
419                {error, Reason} ->
420                    M#meter{micros = Reason}
421            end;
422        Res ->
423            M#meter{micros = Res}
424    end.
425
426start(Nodes, Config) ->
427    mnesia:delete_schema(Nodes),
428    ok = mnesia:create_schema(Nodes),
429    Args = [[{dump_log_write_threshold, ?TIMES div 2},
430             {dump_log_time_threshold, timer:hours(10)}]],
431    lists:foreach(fun(Node) -> rpc:call(Node, mnesia, start, Args) end, Nodes),
432    Attrs = record_info(fields, person),
433    TabDef = [{attributes, Attrs} | Config],
434    {atomic, _} = mnesia:create_table(person, TabDef).
435
436stop(Nodes) ->
437    rpc:multicall(Nodes, mnesia, stop, []).
438
439%% Generate some dummy persons
440init_records(_Fun, 0) ->
441    {atomic, ok};
442init_records(Fun, Times) ->
443    {atomic, ok} = Fun(Times, 0 - Times),
444    init_records(Fun, Times - 1).
445
446tc(Fun, Times) ->
447    case catch timer:tc(?MODULE, repeat_meter, [Fun, Times]) of
448        {Micros, ok} ->
449            {ok, Micros div Times};
450        {_Micros, {error, Reason}} ->
451            {error, Reason};
452        {'EXIT', Reason} ->
453            {error, Reason}
454    end.
455
456%% The meter must return {atomic, ok}
457repeat_meter(Meter, Times) ->
458    repeat_meter(Meter, {atomic, ok}, Times).
459
460repeat_meter(_, {atomic, ok}, 0) ->
461    ok;
462repeat_meter(Meter, {atomic, _Result}, Times) when Times > 0 ->
463    repeat_meter(Meter, Meter(Times), Times - 1);
464repeat_meter(_Meter, Reason, _Times) ->
465    {error, Reason}.
466
467