1 |
|
%%%---------------------------------------------------------------------- |
2 |
|
%%% File : backend_module.erl |
3 |
|
%%% Author : Alexey Shchepin <alexey@process-one.net> |
4 |
|
%%% |
5 |
|
%%% |
6 |
|
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne |
7 |
|
%%% Copyright 2016 Erlang Solutions Ltd. |
8 |
|
%%% |
9 |
|
%%% This program is free software; you can redistribute it and/or |
10 |
|
%%% modify it under the terms of the GNU General Public License as |
11 |
|
%%% published by the Free Software Foundation; either version 2 of the |
12 |
|
%%% License, or (at your option) any later version. |
13 |
|
%%% |
14 |
|
%%% This program is distributed in the hope that it will be useful, |
15 |
|
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of |
16 |
|
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
17 |
|
%%% General Public License for more details. |
18 |
|
%%% |
19 |
|
%%% You should have received a copy of the GNU General Public License |
20 |
|
%%% along with this program; if not, write to the Free Software |
21 |
|
%%% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
22 |
|
%%% |
23 |
|
%%%---------------------------------------------------------------------- |
24 |
|
|
25 |
|
-module(backend_module). |
26 |
|
-author('alexey@process-one.net'). |
27 |
|
-author('konrad.zemek@erlang-solutions.com'). |
28 |
|
|
29 |
|
-export([create/2, create/3, backend_module/2, is_exported/3]). |
30 |
|
|
31 |
|
-ignore_xref([create/2, backend_module/2, behaviour_info/1]). |
32 |
|
|
33 |
|
%% Callback implemented by proxy modules. |
34 |
|
-callback backend() -> module(). |
35 |
|
|
36 |
|
%% API |
37 |
|
|
38 |
|
-spec create(For :: module(), Name :: atom()) -> |
39 |
|
{ok, module()} | {error, already_loaded}. |
40 |
|
create(For, Name) -> |
41 |
:-( |
create(For, Name, []). |
42 |
|
|
43 |
|
-spec create(For :: module(), Name :: atom(), TrackedFuns :: [atom()]) -> |
44 |
|
{ok, module()} | {error, already_loaded}. |
45 |
|
create(Module, Backend, TrackedFuns) -> |
46 |
289 |
ProxyModule = proxy_module(Module), |
47 |
289 |
BackendModule = backend_module(Module, Backend), |
48 |
289 |
mongoose_backend:ensure_backend_metrics(Module, TrackedFuns), |
49 |
289 |
case catch ProxyModule:backend() of |
50 |
|
BackendModule -> |
51 |
289 |
{error, already_loaded}; |
52 |
|
_ -> |
53 |
:-( |
{ProxyModuleStr, CodeString} = backend_code(Module, Backend, TrackedFuns), |
54 |
:-( |
{Mod, Code} = dynamic_compile:from_string(CodeString), |
55 |
:-( |
code:load_binary(Mod, ProxyModuleStr ++ ".erl", Code), |
56 |
:-( |
{ok, ProxyModule} |
57 |
|
end. |
58 |
|
|
59 |
|
-spec is_exported(Module :: module(), Function :: atom(), |
60 |
|
Arity :: integer()) -> boolean(). |
61 |
|
is_exported(Module, Function, Arity) -> |
62 |
13384 |
code:ensure_loaded(Module), |
63 |
13384 |
erlang:function_exported(Module, Function, Arity). |
64 |
|
|
65 |
|
%% Internal functions |
66 |
|
|
67 |
|
-spec proxy_module(Module :: module()) -> module(). |
68 |
|
proxy_module(Module) -> |
69 |
289 |
list_to_atom(atom_to_list(Module) ++ "_backend"). |
70 |
|
|
71 |
|
-spec backend_module(Module :: module(), Backend :: atom()) -> module(). |
72 |
|
backend_module(Module, Backend) -> |
73 |
289 |
list_to_atom(atom_to_list(Module) ++ "_" ++ atom_to_list(Backend)). |
74 |
|
|
75 |
|
-spec backend_code(module(), atom(), list()) -> {nonempty_string(), list()}. |
76 |
|
backend_code(Module, Backend, TrackedFuns) when is_atom(Backend) -> |
77 |
:-( |
Callbacks = Module:behaviour_info(callbacks), |
78 |
:-( |
ModuleStr = atom_to_list(Module), |
79 |
:-( |
ProxyModuleName = ModuleStr ++ "_backend", |
80 |
:-( |
RealBackendModule = ModuleStr ++ "_" ++ atom_to_list(Backend), |
81 |
:-( |
BehaviourExports = [generate_export(F, A) || {F, A} <- Callbacks], |
82 |
|
|
83 |
:-( |
BehaviourImpl = [generate_fun(Module, RealBackendModule, F, A, TrackedFuns) || |
84 |
:-( |
{F, A} <- Callbacks], |
85 |
:-( |
Code = lists:flatten( |
86 |
|
["-module(", ProxyModuleName, ").\n", |
87 |
|
"-behaviour(backend_module).\n" |
88 |
|
"-export([backend/0, backend_name/0]).\n", |
89 |
|
BehaviourExports, |
90 |
|
|
91 |
|
|
92 |
|
"-spec backend() -> atom().\n", |
93 |
|
"backend() ->", RealBackendModule, ".\n", |
94 |
|
"backend_name() ->", atom_to_list(Backend), ".\n", |
95 |
|
BehaviourImpl |
96 |
|
]), |
97 |
:-( |
{ProxyModuleName, Code}. |
98 |
|
|
99 |
|
generate_export(F, A) -> |
100 |
:-( |
"-export([" ++ atom_to_list(F) ++ "/" ++ integer_to_list(A) ++ "]).\n". |
101 |
|
|
102 |
|
generate_fun(BaseModule, RealBackendModule, F, A, TrackedFuns) -> |
103 |
:-( |
Args = string:join(["A" ++ integer_to_list(I) || I <- lists:seq(1, A)], ", "), |
104 |
:-( |
IsTracked = lists:member(F, TrackedFuns), |
105 |
:-( |
[fun_header(F, Args), " ->\n", |
106 |
|
generate_fun_body(IsTracked, BaseModule, RealBackendModule, F, Args)]. |
107 |
|
|
108 |
|
fun_header(F, Args) -> |
109 |
:-( |
[atom_to_list(F), "(", Args, ")"]. |
110 |
|
|
111 |
:-( |
time_metric(Module, Op) -> [backends, Module, Op]. |
112 |
:-( |
calls_metric(Module, Op) -> [backends, Module, calls, Op]. |
113 |
|
|
114 |
|
generate_fun_body(false, _, RealBackendModule, F, Args) -> |
115 |
:-( |
[" ", RealBackendModule, ":", fun_header(F, Args), ".\n"]; |
116 |
|
generate_fun_body(true, BaseModule, RealBackendModule, F, Args) -> |
117 |
:-( |
FS = atom_to_list(F), |
118 |
|
%% returned is the following |
119 |
|
%% mongoose_metrics:update(global, calls_metric(Backend, F), 1), |
120 |
|
%% {Time, Result} = timer:tc(Backend, F, Args), |
121 |
|
%% mongoose_metrics:update(global, time_metric(Backend, F), Time), |
122 |
|
%% Result. |
123 |
:-( |
CallsMetric = io_lib:format("~p", [calls_metric(BaseModule, F)]), |
124 |
:-( |
TimeMetric = io_lib:format("~p", [time_metric(BaseModule, F)]), |
125 |
:-( |
[" mongoose_metrics:update(global, ", CallsMetric, ", 1), \n", |
126 |
|
" {Time, Result} = timer:tc(", RealBackendModule, ", ", FS, ", [", Args, "]), \n", |
127 |
|
" mongoose_metrics:update(global, ", TimeMetric, ", Time), \n", |
128 |
|
" Result.\n"]. |