├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── bin ├── bench.sh ├── elvis └── rebar3 ├── doc ├── edoc-info ├── foil.md ├── foil_app.md ├── foil_compiler.md ├── foil_server.md └── foil_sup.md ├── elvis.config ├── include └── foil.hrl ├── rebar.config ├── rebar.config.script ├── rebar.lock ├── src ├── foil.app.src ├── foil.erl ├── foil_app.erl ├── foil_compiler.erl ├── foil_server.erl └── foil_sup.erl └── test ├── foil_bench.erl └── foil_tests.erl /.gitignore: -------------------------------------------------------------------------------- 1 | doc/README.md 2 | erl_crash.dump 3 | _build 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | cache: 2 | directories: 3 | - $HOME/.cache/rebar3 4 | - $TRAVIS_BUILD_DIR/_build 5 | install: true 6 | language: erlang 7 | notifications: 8 | email: false 9 | otp_release: 10 | - 19.3 11 | script: "make travis" 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Louis-Philippe Gauthier 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ELVIS=./bin/elvis 2 | REBAR3=./bin/rebar3 3 | 4 | all: compile 5 | 6 | bench: 7 | @echo "Running bin/bench.sh..." 8 | @$(REBAR3) as test compile 9 | @./bin/bench.sh 10 | 11 | clean: 12 | @echo "Running rebar3 clean..." 13 | @$(REBAR3) clean -a 14 | 15 | compile: 16 | @echo "Running rebar3 compile..." 17 | @$(REBAR3) as compile compile 18 | 19 | coveralls: 20 | @echo "Running rebar3 coveralls send..." 21 | @$(REBAR3) as test coveralls send 22 | 23 | dialyzer: 24 | @echo "Running rebar3 dialyze..." 25 | @$(REBAR3) dialyzer 26 | 27 | edoc: 28 | @echo "Running rebar3 edoc..." 29 | @$(REBAR3) as edoc edoc 30 | 31 | elvis: 32 | @echo "Running elvis rock..." 33 | @$(ELVIS) rock 34 | 35 | eunit: 36 | @echo "Running rebar3 eunit..." 37 | @$(REBAR3) do eunit -cv, cover -v 38 | 39 | test: elvis xref eunit dialyzer 40 | 41 | travis: test coveralls 42 | 43 | xref: 44 | @echo "Running rebar3 xref..." 45 | @$(REBAR3) xref 46 | 47 | .PHONY: bench clean compile coveralls dialyzer edoc elvis eunit xref 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # foil 2 | 3 | High-Performance Erlang Cache Compiler 4 | 5 | [![Build Status](https://travis-ci.org/lpgauth/foil.svg?branch=master)](https://travis-ci.org/lpgauth/foil) 6 | [![Coverage Status](https://coveralls.io/repos/github/lpgauth/foil/badge.svg?branch=master)](https://coveralls.io/github/lpgauth/foil?branch=master) 7 | 8 | ## About 9 | 10 | Foil is a cache that compiles key-values into Erlang modules. Key-values can be namespaced and are backed by an ETS table for easy re-compilation. 11 | 12 | ## API 13 | 14 | Function Index 15 | 16 | ## Benchmarks 17 | 18 | ``` 19 | make bench 20 | Running bin/bench.sh... 21 | ===> Verifying dependencies... 22 | ===> Compiling foil 23 | name mean p99 p999 24 | ets_atom 0.311 0.374 0.685 25 | foil_indirect_atom 0.190 0.320 1.940 26 | foil_direct_atom 0.085 0.106 0.280 27 | ets_binary 0.489 0.617 0.866 28 | foil_indirect_binary 0.253 0.311 0.749 29 | foil_direct_binary 0.135 0.209 0.637 30 | ets_complex 0.646 0.839 1.190 31 | foil_indirect_complex 0.276 0.295 0.542 32 | foil_direct_complex 0.166 0.196 0.451 33 | ets_list 0.427 0.544 0.823 34 | foil_indirect_list 0.253 1.730 2.000 35 | foil_direct_list 0.109 0.129 0.305 36 | ets_tuple 0.427 0.660 0.965 37 | foil_indirect_tuple 0.206 0.289 2.030 38 | foil_direct_tuple 0.115 0.139 0.314 39 | ``` 40 | 41 | ## Examples 42 | 43 | ```erlang 44 | 1> foil_app:start(). 45 | {ok, [metal, foil]} 46 | 47 | 2> foil:new(test). 48 | ok 49 | 50 | 3> foil:insert(test, key, value). 51 | ok 52 | 53 | 4> foil:insert(test, key2, <<"foo">>). 54 | ok 55 | 56 | 5> foil:load(test). 57 | ok 58 | 59 | 6> test_foil:lookup(key). 60 | {ok, value} 61 | 62 | 7> foil:lookup(test, key2). 63 | {ok, <<"foo">>} 64 | 65 | 8> foil:lookup(test, key3). 66 | {error, key_not_found} 67 | ``` 68 | 69 | ## Tests 70 | 71 | ```makefile 72 | make dialyzer 73 | make elvis 74 | make eunit 75 | make xref 76 | ``` 77 | ## License 78 | ```license 79 | The MIT License (MIT) 80 | 81 | Copyright (c) 2017 Louis-Philippe Gauthier 82 | 83 | Permission is hereby granted, free of charge, to any person obtaining a copy 84 | of this software and associated documentation files (the "Software"), to deal 85 | in the Software without restriction, including without limitation the rights 86 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 87 | copies of the Software, and to permit persons to whom the Software is 88 | furnished to do so, subject to the following conditions: 89 | 90 | The above copyright notice and this permission notice shall be included in all 91 | copies or substantial portions of the Software. 92 | 93 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 94 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 95 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 96 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 97 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 98 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 99 | SOFTWARE. 100 | ``` 101 | -------------------------------------------------------------------------------- /bin/bench.sh: -------------------------------------------------------------------------------- 1 | erl -pa _build/test/lib/*/ebin \ 2 | -pa _build/test/lib/*/test \ 3 | -noshell \ 4 | -eval 'foil_bench:run()' \ 5 | -eval 'init:stop()' 6 | -------------------------------------------------------------------------------- /bin/elvis: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lpgauth/foil/e462829855a745d8f20fb0508ae3eb8e323b03bd/bin/elvis -------------------------------------------------------------------------------- /bin/rebar3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lpgauth/foil/e462829855a745d8f20fb0508ae3eb8e323b03bd/bin/rebar3 -------------------------------------------------------------------------------- /doc/edoc-info: -------------------------------------------------------------------------------- 1 | %% encoding: UTF-8 2 | {application,foil}. 3 | {modules,[foil,foil_app,foil_compiler,foil_server,foil_sup]}. 4 | -------------------------------------------------------------------------------- /doc/foil.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module foil # 4 | * [Data Types](#types) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | 9 | 10 | ## Data Types ## 11 | 12 | 13 | 14 | 15 | ### error() ### 16 | 17 | 18 |

 19 | error() = {error, atom()}
 20 | 
21 | 22 | 23 | 24 | 25 | ### key() ### 26 | 27 | 28 |

 29 | key() = term()
 30 | 
31 | 32 | 33 | 34 | 35 | ### namespace() ### 36 | 37 | 38 |

 39 | namespace() = atom()
 40 | 
41 | 42 | 43 | 44 | 45 | ### value() ### 46 | 47 | 48 |

 49 | value() = term()
 50 | 
51 | 52 | 53 | 54 | ## Function Index ## 55 | 56 | 57 |
delete/1
delete/2
insert/3
load/1
lookup/2
new/1
58 | 59 | 60 | 61 | 62 | ## Function Details ## 63 | 64 | 65 | 66 | ### delete/1 ### 67 | 68 |

 69 | delete(Namespace::namespace()) -> ok | error()
 70 | 
71 |
72 | 73 | 74 | 75 | ### delete/2 ### 76 | 77 |

 78 | delete(Namespace::namespace(), Key::key()) -> ok | error()
 79 | 
80 |
81 | 82 | 83 | 84 | ### insert/3 ### 85 | 86 |

 87 | insert(Namespace::namespace(), Key::key(), Value::value()) -> ok | error()
 88 | 
89 |
90 | 91 | 92 | 93 | ### load/1 ### 94 | 95 |

 96 | load(Namespace::namespace()) -> ok | error()
 97 | 
98 |
99 | 100 | 101 | 102 | ### lookup/2 ### 103 | 104 |

105 | lookup(Namespace::namespace(), Key::key()) -> {ok, value()} | error()
106 | 
107 |
108 | 109 | 110 | 111 | ### new/1 ### 112 | 113 |

114 | new(Namespace::namespace()) -> ok | error()
115 | 
116 |
117 | 118 | -------------------------------------------------------------------------------- /doc/foil_app.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module foil_app # 4 | * [Function Index](#index) 5 | * [Function Details](#functions) 6 | 7 | __Behaviours:__ [`application`](application.md). 8 | 9 | 10 | 11 | ## Function Index ## 12 | 13 | 14 |
start/0
start/2
stop/0
stop/1
15 | 16 | 17 | 18 | 19 | ## Function Details ## 20 | 21 | 22 | 23 | ### start/0 ### 24 | 25 |

26 | start() -> {ok, [atom()]}
27 | 
28 |
29 | 30 | 31 | 32 | ### start/2 ### 33 | 34 |

35 | start(StartType::application:start_type(), StartArgs::term()) -> {ok, pid()}
36 | 
37 |
38 | 39 | 40 | 41 | ### stop/0 ### 42 | 43 |

44 | stop() -> ok | {error, {not_started, foil}}
45 | 
46 |
47 | 48 | 49 | 50 | ### stop/1 ### 51 | 52 |

53 | stop(State::term()) -> ok
54 | 
55 |
56 | 57 | -------------------------------------------------------------------------------- /doc/foil_compiler.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module foil_compiler # 4 | * [Data Types](#types) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | 9 | 10 | ## Data Types ## 11 | 12 | 13 | 14 | 15 | ### key() ### 16 | 17 | 18 |

19 | key() = term()
20 | 
21 | 22 | 23 | 24 | 25 | ### namespace() ### 26 | 27 | 28 |

29 | namespace() = atom()
30 | 
31 | 32 | 33 | 34 | 35 | ### value() ### 36 | 37 | 38 |

39 | value() = term()
40 | 
41 | 42 | 43 | 44 | ## Function Index ## 45 | 46 | 47 |
load/2
48 | 49 | 50 | 51 | 52 | ## Function Details ## 53 | 54 | 55 | 56 | ### load/2 ### 57 | 58 |

59 | load(Module::namespace(), KVs::[{key(), value()}]) -> ok
60 | 
61 |
62 | 63 | -------------------------------------------------------------------------------- /doc/foil_server.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module foil_server # 4 | * [Function Index](#index) 5 | * [Function Details](#functions) 6 | 7 | __Behaviours:__ [`metal`](metal.md). 8 | 9 | 10 | 11 | ## Function Index ## 12 | 13 | 14 |
handle_msg/2
init/3
start_link/0
terminate/2
15 | 16 | 17 | 18 | 19 | ## Function Details ## 20 | 21 | 22 | 23 | ### handle_msg/2 ### 24 | 25 |

26 | handle_msg(X1::term(), State::term()) -> {ok, term()}
27 | 
28 |
29 | 30 | 31 | 32 | ### init/3 ### 33 | 34 |

35 | init(Name::atom(), Parent::pid(), X3::term()) -> {ok, term()}
36 | 
37 |
38 | 39 | 40 | 41 | ### start_link/0 ### 42 | 43 |

44 | start_link() -> {ok, pid()}
45 | 
46 |
47 | 48 | 49 | 50 | ### terminate/2 ### 51 | 52 |

53 | terminate(Reason::term(), State::term()) -> ok
54 | 
55 |
56 | 57 | -------------------------------------------------------------------------------- /doc/foil_sup.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module foil_sup # 4 | * [Function Index](#index) 5 | * [Function Details](#functions) 6 | 7 | __Behaviours:__ [`supervisor`](supervisor.md). 8 | 9 | 10 | 11 | ## Function Index ## 12 | 13 | 14 |
init/1
start_link/0
15 | 16 | 17 | 18 | 19 | ## Function Details ## 20 | 21 | 22 | 23 | ### init/1 ### 24 | 25 |

26 | init(X1::[]) -> {ok, {{one_for_one, 5, 10}, [supervisor:child_spec()]}}
27 | 
28 |
29 | 30 | 31 | 32 | ### start_link/0 ### 33 | 34 |

35 | start_link() -> {ok, pid()}
36 | 
37 |
38 | 39 | -------------------------------------------------------------------------------- /elvis.config: -------------------------------------------------------------------------------- 1 | [{elvis, [ 2 | {config, [ 3 | #{dirs => ["src", "test"], 4 | filter => "*.erl", 5 | rules => [ 6 | {elvis_style, dont_repeat_yourself, #{min_complexity => 10}}, 7 | {elvis_style, god_modules, #{limit => 25}}, 8 | {elvis_style, line_length, #{limit => 80, skip_comments => false}}, 9 | {elvis_style, macro_module_names}, 10 | {elvis_style, macro_names}, 11 | {elvis_style, module_naming_convention, #{regex => "^([a-z][a-z0-9]*_?)*(_SUITE)?$", ignore => []}}, 12 | {elvis_style, nesting_level, #{level => 3}}, 13 | {elvis_style, no_behavior_info}, 14 | {elvis_style, no_if_expression}, 15 | {elvis_style, no_spec_with_records}, 16 | {elvis_style, no_tabs}, 17 | {elvis_style, no_trailing_whitespace}, 18 | {elvis_style, operator_spaces, #{rules => [{right, ","}, {right, "++"}, {left, "++"}]}}, 19 | {elvis_style, state_record_and_type}, 20 | {elvis_style, used_ignored_variable} 21 | ]}, 22 | #{dirs => ["."], 23 | filter => "elvis.config", 24 | rules => [ 25 | {elvis_project, old_configuration_format} 26 | ]} 27 | ]} 28 | ]}]. 29 | -------------------------------------------------------------------------------- /include/foil.hrl: -------------------------------------------------------------------------------- 1 | %% macros 2 | -define(APP, foil). 3 | -define(CHILD(Mod), {Mod, {Mod, start_link, []}, permanent, 5000, worker, [Mod]}). 4 | -define(SERVER, foil_server). 5 | 6 | %% tables 7 | -define(FOIL_TABLE, foil_internal). 8 | 9 | %% types 10 | -type error() :: {error, atom()}. 11 | -type key() :: term(). 12 | -type namespace() :: atom(). 13 | -type value() :: term(). 14 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {cover_export_enabled, true}. 2 | {coveralls_coverdata, "_build/test/cover/eunit.coverdata"}. 3 | {coveralls_service_name, "travis-ci"}. 4 | 5 | {edoc_opts, [ 6 | {app_default, "http://www.erlang.org/doc/man"}, 7 | {doclet, edown_doclet}, 8 | {image, ""}, 9 | {includes, ["include"]}, 10 | {preprocess, true}, 11 | {stylesheet, ""}, 12 | {title, "foil"} 13 | ]}. 14 | 15 | {erl_opts, [ 16 | debug_info 17 | ]}. 18 | 19 | {deps, [ 20 | {metal, "0.1.1"} 21 | ]}. 22 | 23 | {profiles, [ 24 | {compile, [ 25 | {erl_opts, [ 26 | warnings_as_errors, 27 | warn_export_all, 28 | warn_export_vars, 29 | warn_missing_spec, 30 | warn_obsolete_guard, 31 | warn_shadow_vars, 32 | warn_untyped_record, 33 | warn_unused_import, 34 | warn_unused_vars 35 | ]} 36 | ]}, 37 | {edoc, [ 38 | {deps, [ 39 | {edown, 40 | {git, "https://github.com/uwiger/edown.git", {tag, "0.7"}}} 41 | ]} 42 | ]}, 43 | {test, [ 44 | {deps, [ 45 | {timing, 46 | {git, "https://github.com/lpgauth/timing.git", {tag, "0.1.3"}}} 47 | ]}, 48 | {plugins, [ 49 | {coveralls, 50 | {git, "https://github.com/markusn/coveralls-erl", {branch, "master"}}} 51 | ]} 52 | ]} 53 | ]}. 54 | 55 | {xref_checks, [ 56 | deprecated_functions, 57 | deprecated_function_calls, 58 | locals_not_used, 59 | undefined_function_calls 60 | ]}. 61 | -------------------------------------------------------------------------------- /rebar.config.script: -------------------------------------------------------------------------------- 1 | case os:getenv("TRAVIS") of 2 | "true" -> 3 | JobId = os:getenv("TRAVIS_JOB_ID"), 4 | lists:keystore(coveralls_service_job_id, 1, CONFIG, {coveralls_service_job_id, JobId}); 5 | _ -> 6 | CONFIG 7 | end. 8 | -------------------------------------------------------------------------------- /rebar.lock: -------------------------------------------------------------------------------- 1 | {"1.1.0", 2 | [{<<"metal">>,{pkg,<<"metal">>,<<"0.1.1">>},0}]}. 3 | [ 4 | {pkg_hash,[ 5 | {<<"metal">>, <<"5D3D1322DA7BCD34B94FED5486F577973685298883954F7A3E517EF5EF6953F5">>}]} 6 | ]. 7 | -------------------------------------------------------------------------------- /src/foil.app.src: -------------------------------------------------------------------------------- 1 | {application, foil, [ 2 | {applications, [kernel, stdlib, metal, compiler, syntax_tools]}, 3 | {description, "High-Performance Erlang Cache Compiler"}, 4 | {env, []}, 5 | {licenses, ["MIT"]}, 6 | {links, [{"GitHub", "https://github.com/lpgauth/foil"}]}, 7 | {maintainers, ["Louis-Philippe Gauthier"]}, 8 | {mod, {foil_app, []}}, 9 | {registered, []}, 10 | {vsn, "0.1.3"} 11 | ]}. 12 | -------------------------------------------------------------------------------- /src/foil.erl: -------------------------------------------------------------------------------- 1 | -module(foil). 2 | -include("foil.hrl"). 3 | 4 | -compile(inline). 5 | -compile({inline_size, 512}). 6 | 7 | -ignore_xref([ 8 | {foil_modules, lookup, 1} 9 | ]). 10 | 11 | 12 | -export([ 13 | all/1, 14 | delete/1, 15 | delete/2, 16 | insert/3, 17 | load/1, 18 | lookup/2, 19 | new/1 20 | ]). 21 | 22 | %% public 23 | -spec all(namespace()) -> 24 | {ok, #{key() := value()}} | error(). 25 | 26 | all(Namespace) -> 27 | try foil_modules:lookup(Namespace) of 28 | {ok, Module} -> 29 | Module:all(); 30 | {error, key_not_found} -> 31 | {error, module_not_found} 32 | catch 33 | error:undef -> 34 | {error, foil_not_started} 35 | end. 36 | 37 | -spec delete(namespace()) -> 38 | ok | error(). 39 | 40 | delete(Namespace) -> 41 | try foil_modules:lookup(Namespace) of 42 | {ok, Module} -> 43 | ets:delete(Module), 44 | ets:delete(?FOIL_TABLE, Namespace), 45 | KVs = ets:tab2list(?FOIL_TABLE), 46 | foil_compiler:load(foil_modules, KVs), 47 | ok; 48 | {error, key_not_found} -> 49 | {error, module_not_found} 50 | catch 51 | error:undef -> 52 | {error, foil_not_started} 53 | end. 54 | 55 | -spec delete(namespace(), key()) -> 56 | ok | error(). 57 | 58 | delete(Namespace, Key) -> 59 | try foil_modules:lookup(Namespace) of 60 | {ok, Module} -> 61 | ets:delete(Module, Key), 62 | ok; 63 | {error, key_not_found} -> 64 | {error, module_not_found} 65 | catch 66 | error:undef -> 67 | {error, foil_not_started} 68 | end. 69 | 70 | -spec insert(namespace(), key(), value()) -> 71 | ok | error(). 72 | 73 | insert(Namespace, Key, Value) -> 74 | try foil_modules:lookup(Namespace) of 75 | {ok, Module} -> 76 | ets:insert(Module, {Key, Value}), 77 | ok; 78 | {error, key_not_found} -> 79 | {error, module_not_found} 80 | catch 81 | error:undef -> 82 | {error, foil_not_started} 83 | end. 84 | 85 | -spec load(namespace()) -> 86 | ok | error(). 87 | 88 | load(Namespace) -> 89 | try foil_modules:lookup(Namespace) of 90 | {ok, Module} -> 91 | KVs = ets:tab2list(Module), 92 | foil_compiler:load(Module, KVs); 93 | {error, key_not_found} -> 94 | {error, module_not_found} 95 | catch 96 | error:undef -> 97 | {error, foil_not_started} 98 | end. 99 | 100 | -spec lookup(namespace(), key()) -> 101 | {ok, value()} | error(). 102 | 103 | lookup(Namespace, Key) -> 104 | try foil_modules:lookup(Namespace) of 105 | {ok, Module} -> 106 | Module:lookup(Key); 107 | {error, key_not_found} -> 108 | {error, module_not_found} 109 | catch 110 | error:undef -> 111 | {error, foil_not_started} 112 | end. 113 | 114 | -spec new(namespace()) -> 115 | ok | error(). 116 | 117 | new(Namespace) -> 118 | try foil_modules:lookup(Namespace) of 119 | {ok, _Module} -> 120 | {error, module_exists}; 121 | {error, key_not_found} -> 122 | Module = module(Namespace), 123 | ets:new(Module, [named_table, public]), 124 | Server = whereis(foil_server), 125 | ets:give_away(Module, Server, undefined), 126 | ets:insert(?FOIL_TABLE, {Namespace, Module}), 127 | KVs = ets:tab2list(?FOIL_TABLE), 128 | foil_compiler:load(foil_modules, KVs) 129 | catch 130 | error:undef -> 131 | {error, foil_not_started} 132 | end. 133 | %% private 134 | module(Namespace) -> 135 | list_to_atom(atom_to_list(Namespace) ++ "_foil"). 136 | -------------------------------------------------------------------------------- /src/foil_app.erl: -------------------------------------------------------------------------------- 1 | -module(foil_app). 2 | -include("foil.hrl"). 3 | 4 | -export([ 5 | start/0, 6 | stop/0 7 | ]). 8 | 9 | -behaviour(application). 10 | -export([ 11 | start/2, 12 | stop/1 13 | ]). 14 | 15 | %% public 16 | -spec start() -> 17 | {ok, [atom()]}. 18 | 19 | start() -> 20 | application:ensure_all_started(?APP). 21 | 22 | -spec stop() -> 23 | ok | {error, {not_started, ?APP}}. 24 | 25 | stop() -> 26 | application:stop(?APP). 27 | 28 | %% application callbacks 29 | -spec start(application:start_type(), term()) -> 30 | {ok, pid()}. 31 | 32 | start(_StartType, _StartArgs) -> 33 | foil_sup:start_link(). 34 | 35 | -spec stop(term()) -> 36 | ok. 37 | 38 | stop(_State) -> 39 | ok. 40 | -------------------------------------------------------------------------------- /src/foil_compiler.erl: -------------------------------------------------------------------------------- 1 | -module(foil_compiler). 2 | -include("foil.hrl"). 3 | 4 | -export([ 5 | load/2 6 | ]). 7 | 8 | %% public 9 | -spec load(namespace(), [{key(), value()}]) -> 10 | ok. 11 | 12 | load(Module, KVs) -> 13 | Forms = forms(Module, KVs), 14 | {ok, Module, Bin} = compile:forms(Forms, [debug_info]), 15 | code:soft_purge(Module), 16 | Filename = atom_to_list(Module) ++ ".erl", 17 | {module, Module} = code:load_binary(Module, Filename, Bin), 18 | ok. 19 | 20 | %% private 21 | forms(Module, KVs) -> 22 | Mod = erl_syntax:attribute(erl_syntax:atom(module), 23 | [erl_syntax:atom(Module)]), 24 | ExportList = [ 25 | erl_syntax:arity_qualifier(erl_syntax:atom(lookup), 26 | erl_syntax:integer(1)), 27 | erl_syntax:arity_qualifier(erl_syntax:atom(all), 28 | erl_syntax:integer(0))], 29 | Export = erl_syntax:attribute(erl_syntax:atom(export), 30 | [erl_syntax:list(ExportList)]), 31 | Lookup = erl_syntax:function(erl_syntax:atom(lookup), 32 | lookup_clauses(KVs)), 33 | All = erl_syntax:function(erl_syntax:atom(all), 34 | all(lists:sort(KVs))), 35 | [erl_syntax:revert(X) || X <- [Mod, Export, Lookup, All]]. 36 | 37 | all(KVs) -> 38 | Pairs = [erl_syntax:map_field_assoc(to_syntax(K), to_syntax(V)) 39 | || {K, V} <- KVs], 40 | Body = erl_syntax:tuple([erl_syntax:atom(ok), erl_syntax:map_expr(Pairs)]), 41 | [erl_syntax:clause([], [Body])]. 42 | 43 | lookup_clause(Key, Value) -> 44 | Var = to_syntax(Key), 45 | Body = erl_syntax:tuple([erl_syntax:atom(ok), 46 | to_syntax(Value)]), 47 | erl_syntax:clause([Var], [], [Body]). 48 | 49 | lookup_clause_anon() -> 50 | Var = erl_syntax:variable("_"), 51 | Body = erl_syntax:tuple([erl_syntax:atom(error), 52 | erl_syntax:atom(key_not_found)]), 53 | erl_syntax:clause([Var], [], [Body]). 54 | 55 | lookup_clauses(KVs) -> 56 | lookup_clauses(KVs, []). 57 | 58 | lookup_clauses([], Acc) -> 59 | lists:reverse(lists:flatten([lookup_clause_anon() | Acc])); 60 | lookup_clauses([{Key, Value} | T], Acc) -> 61 | lookup_clauses(T, [lookup_clause(Key, Value) | Acc]). 62 | 63 | to_syntax(Atom) when is_atom(Atom) -> 64 | erl_syntax:atom(Atom); 65 | to_syntax(Binary) when is_binary(Binary) -> 66 | String = erl_syntax:string(binary_to_list(Binary)), 67 | erl_syntax:binary([erl_syntax:binary_field(String)]); 68 | to_syntax(Float) when is_float(Float) -> 69 | erl_syntax:float(Float); 70 | to_syntax(Integer) when is_integer(Integer) -> 71 | erl_syntax:integer(Integer); 72 | to_syntax(List) when is_list(List) -> 73 | erl_syntax:list([to_syntax(X) || X <- List]); 74 | to_syntax(Tuple) when is_tuple(Tuple) -> 75 | erl_syntax:tuple([to_syntax(X) || X <- tuple_to_list(Tuple)]). 76 | -------------------------------------------------------------------------------- /src/foil_server.erl: -------------------------------------------------------------------------------- 1 | -module(foil_server). 2 | -include("foil.hrl"). 3 | 4 | -export([ 5 | start_link/0 6 | ]). 7 | 8 | -behaviour(metal). 9 | -export([ 10 | init/3, 11 | handle_msg/2, 12 | terminate/2 13 | ]). 14 | 15 | %% public 16 | -spec start_link() -> 17 | {ok, pid()}. 18 | 19 | start_link() -> 20 | metal:start_link(?SERVER, ?SERVER, undefined). 21 | 22 | %% metal callbacks 23 | -spec init(atom(), pid(), term()) -> 24 | {ok, term()}. 25 | 26 | init(_Name, _Parent, undefined) -> 27 | ets:new(?FOIL_TABLE, [public, named_table]), 28 | KVs = ets:tab2list(?FOIL_TABLE), 29 | ok = foil_compiler:load(foil_modules, KVs), 30 | {ok, undefined}. 31 | 32 | -spec handle_msg(term(), term()) -> 33 | {ok, term()}. 34 | 35 | handle_msg({'ETS-TRANSFER', _, _, _}, State) -> 36 | {ok, State}. 37 | 38 | -spec terminate(term(), term()) -> 39 | ok. 40 | 41 | terminate(_Reason, _State) -> 42 | ok. 43 | -------------------------------------------------------------------------------- /src/foil_sup.erl: -------------------------------------------------------------------------------- 1 | -module(foil_sup). 2 | -include("foil.hrl"). 3 | 4 | -export([ 5 | start_link/0 6 | ]). 7 | 8 | -behaviour(supervisor). 9 | -export([ 10 | init/1 11 | ]). 12 | 13 | %% public 14 | -spec start_link() -> 15 | {ok, pid()}. 16 | 17 | start_link() -> 18 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 19 | 20 | %% supervisor callbacks 21 | -spec init([]) -> 22 | {ok, {{one_for_one, 5, 10}, [supervisor:child_spec()]}}. 23 | 24 | init([]) -> 25 | {ok, {{one_for_one, 5, 10}, [?CHILD(?SERVER)]}}. 26 | -------------------------------------------------------------------------------- /test/foil_bench.erl: -------------------------------------------------------------------------------- 1 | -module(foil_bench). 2 | 3 | -export([ 4 | run/0 5 | ]). 6 | 7 | -define(B, 1000). % batch size 8 | -define(C, 2048). % concurrency 9 | -define(N, 2048). % iterations 10 | -define(T, [ 11 | {atom, test}, 12 | {binary, <<0:1024>>}, 13 | {complex, {test, [<<"test">>, "test"]}}, 14 | {list, "test"}, 15 | {tuple, {test, test2}} 16 | ]). 17 | 18 | %% public 19 | -spec run() -> 20 | ok. 21 | 22 | run() -> 23 | error_logger:tty(false), 24 | foil_app:start(), 25 | io:format("~23s ~6s ~6s ~6s~n", [name, mean, p99, p999]), 26 | run(?T, ?C, ?N), 27 | foil_app:stop(). 28 | 29 | %% private 30 | lookup(Key, List) -> 31 | case lists:keyfind(Key, 1, List) of 32 | false -> undefined; 33 | {_, Value} -> Value 34 | end. 35 | 36 | name(Name, Type) -> 37 | list_to_atom(atom_to_list(Name) ++ "_" ++ atom_to_list(Type)). 38 | 39 | run([], _C, _N) -> 40 | ok; 41 | run([{Type, Value} | T], C, N) -> 42 | run_ets(Type, Value, C, N), 43 | run_foil_indirect(Type, Value, C, N), 44 | run_foil_direct(Type, Value, C, N), 45 | run(T, C, N). 46 | 47 | run(Name, Fun, C, N) -> 48 | [Fun() || _ <- lists:seq(1, 10000)], 49 | Results = timing_hdr:run(Fun, [ 50 | {name, Name}, 51 | {concurrency, C}, 52 | {iterations, N}, 53 | {output, "output/" ++ atom_to_list(Name)} 54 | ]), 55 | Mean = lookup(mean, Results) / ?B, 56 | P99 = lookup(p99, Results) / ?B, 57 | P999 = lookup(p999, Results) / ?B, 58 | 59 | io:format("~23s ~6.3f ~6.3f ~6.3f~n", [Name, Mean, P99, P999]). 60 | 61 | run_ets(Type, Value, C, N) -> 62 | ets:new(?MODULE, [named_table, public, {read_concurrency, true}]), 63 | [ets:insert(?MODULE, {X, <<"foo">>}) || X <- lists:seq(0, 100)], 64 | ets:insert(?MODULE, {test, Value}), 65 | Fun = fun() -> 66 | [Value = ets:lookup_element(?MODULE, test, 2) || _ <- lists:seq(0, ?B)], 67 | ok 68 | end, 69 | run(name(ets, Type), Fun, C, N), 70 | ets:delete(?MODULE). 71 | 72 | run_foil_direct(Type, Value, C, N) -> 73 | foil:new(?MODULE), 74 | [foil:insert(?MODULE, X, <<"foo">>) || X <- lists:seq(0, 100)], 75 | foil:insert(?MODULE, test, Value), 76 | foil:load(?MODULE), 77 | timer:sleep(500), 78 | Fun = fun() -> 79 | [{ok, Value} = foil_bench_foil:lookup(test) || _ <- lists:seq(0, ?B)], 80 | ok 81 | end, 82 | run(name(foil_direct, Type), Fun, C, N), 83 | foil:delete(?MODULE). 84 | 85 | run_foil_indirect(Type, Value, C, N) -> 86 | foil:new(?MODULE), 87 | [foil:insert(?MODULE, X, <<"foo">>) || X <- lists:seq(0, 100)], 88 | foil:insert(?MODULE, test, Value), 89 | foil:load(?MODULE), 90 | timer:sleep(500), 91 | Fun = fun() -> 92 | [{ok, Value} = foil:lookup(?MODULE, test) || _ <- lists:seq(0, ?B)], 93 | ok 94 | end, 95 | run(name(foil_indirect, Type), Fun, C, N), 96 | foil:delete(?MODULE). 97 | -------------------------------------------------------------------------------- /test/foil_tests.erl: -------------------------------------------------------------------------------- 1 | -module(foil_tests). 2 | -include("foil.hrl"). 3 | -include_lib("eunit/include/eunit.hrl"). 4 | 5 | foil_test() -> 6 | error_logger:tty(false), 7 | 8 | {error, foil_not_started} = foil:new(test), 9 | {error, foil_not_started} = foil:insert(test, key, value), 10 | {error, foil_not_started} = foil:load(test), 11 | {error, foil_not_started} = foil:lookup(test, key), 12 | {error, foil_not_started} = foil:all(test), 13 | {error, foil_not_started} = foil:delete(test, key), 14 | {error, foil_not_started} = foil:delete(test), 15 | 16 | foil_app:start(), 17 | 18 | ok = foil:new(test), 19 | {error, module_exists} = foil:new(test), 20 | 21 | ok = foil:insert(test, key, value), 22 | ok = foil:insert(test, key2, [<<"foo">>, <<"bar">>]), 23 | ok = foil:insert(test, key3, {1, 1.234}), 24 | ok = foil:insert(test, key4, "test"), 25 | {error, module_not_found} = foil:insert(test2, key2, value), 26 | 27 | ok = foil:delete(test, key4), 28 | {error, module_not_found} = foil:delete(tes2, key), 29 | 30 | ok = foil:load(test), 31 | {error, module_not_found} = foil:load(test2), 32 | 33 | {ok, value} = test_foil:lookup(key), 34 | {ok, [<<"foo">>, <<"bar">>]} = foil:lookup(test, key2), 35 | {ok, {1, 1.234}} = foil:lookup(test, key3), 36 | {ok, #{ 37 | key := value, 38 | key2 := [<<"foo">>, <<"bar">>], 39 | key3 := {1, 1.234} 40 | }} = foil:all(test), 41 | {error, module_not_found} = foil:lookup(test2, key), 42 | {error, module_not_found} = foil:all(test2), 43 | {error, key_not_found} = foil:lookup(test, key4), 44 | 45 | ok = foil:delete(test), 46 | {error, module_not_found} = foil:delete(test), 47 | 48 | foil_app:stop(). 49 | --------------------------------------------------------------------------------