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