├── .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 | [](https://travis-ci.org/lpgauth/foil)
6 | [](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 |
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 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------