summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLoïc Hoguin <[email protected]>2013-11-05 20:06:22 +0100
committerLoïc Hoguin <[email protected]>2013-11-05 21:08:18 +0100
commitd030ee255323fdab740d324654548888c70f23f6 (patch)
tree04005e0c196e239e51745627e3c8c585fb1f185c
downloadhorse-d030ee255323fdab740d324654548888c70f23f6.tar.gz
horse-d030ee255323fdab740d324654548888c70f23f6.tar.bz2
horse-d030ee255323fdab740d324654548888c70f23f6.zip
Initial commit
-rw-r--r--LICENSE13
-rw-r--r--Makefile5
-rw-r--r--README.md107
-rw-r--r--erlang.mk277
-rw-r--r--src/horse.app.src24
-rw-r--r--src/horse.erl49
-rw-r--r--src/horse_autoexport.erl60
7 files changed, 535 insertions, 0 deletions
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..6bc2b91
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,13 @@
+Copyright (c) 2011-2013, Loïc Hoguin <[email protected]>
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..d331c67
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,5 @@
+# See LICENSE for licensing information.
+
+PROJECT = horse
+
+include erlang.mk
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..9222ddb
--- /dev/null
+++ b/README.md
@@ -0,0 +1,107 @@
+Horse
+=====
+
+Integrated performance testing.
+
+Goals
+-----
+
+Horse is designed to provide quick feedback on the performance
+of units of code, for example a function or a group of functions.
+
+Horse works in a manner similar to the `eunit` application: it
+will export automatically all the performance test functions,
+run them one after another and give you a convenient report.
+
+There are two main use cases for Horse.
+
+You are optimizing your application and found a function or
+group of functions to be too slow or using too much CPU. You
+can write Horse tests, measure the time it takes to perform
+that operation, and then modify your code until you get an
+improvement in performance.
+
+You are modifying a critical section of your code. You do not
+want to inadvertently kill the performance of your application
+if you make the wrong modification. You can write Horse tests
+and ensure any modification will keep the performance at least
+on par to what it was before.
+
+Disclaimer
+----------
+
+Horse may tell you your code is faster, despite the code performing
+slower when in production under heavy load. If that happens it
+generally means you are using NIFs but not always. It's meant to
+give you a quick indication of how your code performs and is by
+no means a definitive proof of the performance of your code.
+
+Horse may run a test slower than it should, at times. This is
+because there are many factors coming in, including your hardware,
+the other processes running on your machine, or even the other
+processes running in the Erlang VM. Repeating tests helps get
+a better view but it is by no means a perfect solution.
+
+Use with caution.
+
+Usage
+-----
+
+You can add the following rule to your Makefile to run Horse
+on your application every time you run `make perfs`.
+
+``` Makefile
+deps/horse:
+ git clone -n -- https://github.com/extend/horse $(DEPS_DIR)/horse
+ cd $(DEPS_DIR)/horse ; git checkout -q master
+ $(MAKE) -C $(DEPS_DIR)/horse
+
+perfs: ERLC_OPTS += -DPERF=1 +'{parse_transform, horse_autoexport}'
+perfs: clean deps deps/horse app
+ $(gen_verbose) erl -noshell -pa ebin deps/horse/ebin \
+ -eval 'horse:app_perf($(PROJECT)), init:stop().'
+```
+
+In your source files, you should put your test in an `ifdef` block
+like this:
+
+``` erlang
+-ifdef(PERF).
+%% Your tests here.
+-endif.
+```
+
+All functions that begin with `horse_` will be ran as performance
+tests. You can put anything inside the function, measurements will
+be made for the whole call.
+
+``` erlang
+-ifdef(PERF).
+horse_do_nothing() ->
+ ok.
+-endif.
+```
+
+When the code you need to test is very fast, you may want to
+execute it many times to get a more interesting output. Horse
+provides the special function `horse:repeat/2`. This function
+takes an integer as first argument, which is the number of times
+the expression in the second argument should be executed.
+
+``` erlang
+-ifdef(PERF).
+horse_rfc2019() ->
+ horse:repeat(100000,
+ doit()
+ ).
+-endif.
+```
+
+You should repeat the test until you get a time between 0.1s
+and 1s to get a better overview.
+
+Support
+-------
+
+ * Official IRC Channel: #ninenines on irc.freenode.net
+ * [Mailing Lists](http://lists.ninenines.eu)
diff --git a/erlang.mk b/erlang.mk
new file mode 100644
index 0000000..d5fa2aa
--- /dev/null
+++ b/erlang.mk
@@ -0,0 +1,277 @@
+# Copyright (c) 2013, Loïc Hoguin <[email protected]>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+# Project.
+
+PROJECT ?= $(notdir $(CURDIR))
+
+# Packages database file.
+
+PKG_FILE ?= $(CURDIR)/.erlang.mk.packages.v1
+export PKG_FILE
+
+PKG_FILE_URL ?= https://raw.github.com/extend/erlang.mk/master/packages.v1.tsv
+
+define get_pkg_file
+ wget -O $(PKG_FILE) $(PKG_FILE_URL) || rm $(PKG_FILE)
+endef
+
+# Verbosity and tweaks.
+
+V ?= 0
+
+appsrc_verbose_0 = @echo " APP " $(PROJECT).app.src;
+appsrc_verbose = $(appsrc_verbose_$(V))
+
+erlc_verbose_0 = @echo " ERLC " $(filter %.erl %.core,$(?F));
+erlc_verbose = $(erlc_verbose_$(V))
+
+xyrl_verbose_0 = @echo " XYRL " $(filter %.xrl %.yrl,$(?F));
+xyrl_verbose = $(xyrl_verbose_$(V))
+
+dtl_verbose_0 = @echo " DTL " $(filter %.dtl,$(?F));
+dtl_verbose = $(dtl_verbose_$(V))
+
+gen_verbose_0 = @echo " GEN " $@;
+gen_verbose = $(gen_verbose_$(V))
+
+.PHONY: rel clean-rel all clean-all app clean deps clean-deps \
+ docs clean-docs build-tests tests build-plt dialyze
+
+# Release.
+
+RELX_CONFIG ?= $(CURDIR)/relx.config
+
+ifneq ($(wildcard $(RELX_CONFIG)),)
+
+RELX ?= $(CURDIR)/relx
+export RELX
+
+RELX_URL ?= https://github.com/erlware/relx/releases/download/0.4.0/relx
+
+define get_relx
+ wget -O $(RELX) $(RELX_URL) || rm $(RELX)
+ chmod +x $(RELX)
+endef
+
+rel: clean-rel all $(RELX)
+ @$(RELX)
+
+$(RELX):
+ @$(call get_relx)
+
+clean-rel:
+ @rm -rf _rel
+
+endif
+
+# Deps directory.
+
+DEPS_DIR ?= $(CURDIR)/deps
+export DEPS_DIR
+
+REBAR_DEPS_DIR = $(DEPS_DIR)
+export REBAR_DEPS_DIR
+
+ALL_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(DEPS))
+ALL_TEST_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(TEST_DEPS))
+
+# Application.
+
+ERL_LIBS ?= $(DEPS_DIR)
+export ERL_LIBS
+
+ERLC_OPTS ?= -Werror +debug_info +warn_export_all +warn_export_vars \
+ +warn_shadow_vars +warn_obsolete_guard # +bin_opt_info +warn_missing_spec
+COMPILE_FIRST ?=
+COMPILE_FIRST_PATHS = $(addprefix src/,$(addsuffix .erl,$(COMPILE_FIRST)))
+
+all: deps app
+
+clean-all: clean clean-deps clean-docs
+ $(gen_verbose) rm -rf .$(PROJECT).plt $(DEPS_DIR) logs
+
+app: ebin/$(PROJECT).app
+ $(eval MODULES := $(shell find ebin -type f -name \*.beam \
+ | sed 's/ebin\///;s/\.beam/,/' | sed '$$s/.$$//'))
+ $(appsrc_verbose) cat src/$(PROJECT).app.src \
+ | sed 's/{modules,\s*\[\]}/{modules, \[$(MODULES)\]}/' \
+ > ebin/$(PROJECT).app
+
+define compile_erl
+ $(erlc_verbose) erlc -v $(ERLC_OPTS) -o ebin/ \
+ -pa ebin/ -I include/ $(COMPILE_FIRST_PATHS) $(1)
+endef
+
+define compile_xyrl
+ $(xyrl_verbose) erlc -v -o ebin/ $(1)
+ $(xyrl_verbose) erlc $(ERLC_OPTS) -o ebin/ ebin/*.erl
+ @rm ebin/*.erl
+endef
+
+define compile_dtl
+ $(dtl_verbose) erl -noshell -pa ebin/ $(DEPS_DIR)/erlydtl/ebin/ -eval ' \
+ Compile = fun(F) -> \
+ Module = list_to_atom( \
+ string:to_lower(filename:basename(F, ".dtl")) ++ "_dtl"), \
+ erlydtl_compiler:compile(F, Module, [{out_dir, "ebin/"}]) \
+ end, \
+ _ = [Compile(F) || F <- string:tokens("$(1)", " ")], \
+ init:stop()'
+endef
+
+ebin/$(PROJECT).app: $(shell find src -type f -name \*.erl) \
+ $(shell find src -type f -name \*.core) \
+ $(shell find src -type f -name \*.xrl) \
+ $(shell find src -type f -name \*.yrl) \
+ $(shell find templates -type f -name \*.dtl 2>/dev/null)
+ @mkdir -p ebin/
+ $(if $(strip $(filter %.erl %.core,$?)), \
+ $(call compile_erl,$(filter %.erl %.core,$?)))
+ $(if $(strip $(filter %.xrl %.yrl,$?)), \
+ $(call compile_xyrl,$(filter %.xrl %.yrl,$?)))
+ $(if $(strip $(filter %.dtl,$?)), \
+ $(call compile_dtl,$(filter %.dtl,$?)))
+
+clean:
+ $(gen_verbose) rm -rf ebin/ test/*.beam erl_crash.dump
+
+# Dependencies.
+
+define get_dep
+ @mkdir -p $(DEPS_DIR)
+ifeq (,$(findstring pkg://,$(word 1,$(dep_$(1)))))
+ git clone -n -- $(word 1,$(dep_$(1))) $(DEPS_DIR)/$(1)
+else
+ @if [ ! -f $(PKG_FILE) ]; then $(call get_pkg_file); fi
+ git clone -n -- `awk 'BEGIN { FS = "\t" }; \
+ $$$$1 == "$(subst pkg://,,$(word 1,$(dep_$(1))))" { print $$$$2 }' \
+ $(PKG_FILE)` $(DEPS_DIR)/$(1)
+endif
+ cd $(DEPS_DIR)/$(1) ; git checkout -q $(word 2,$(dep_$(1)))
+endef
+
+define dep_target
+$(DEPS_DIR)/$(1):
+ $(call get_dep,$(1))
+endef
+
+$(foreach dep,$(DEPS),$(eval $(call dep_target,$(dep))))
+
+deps: $(ALL_DEPS_DIRS)
+ @for dep in $(ALL_DEPS_DIRS) ; do \
+ if [ -f $$dep/Makefile ] ; then \
+ $(MAKE) -C $$dep ; \
+ else \
+ echo "include $(CURDIR)/erlang.mk" | $(MAKE) -f - -C $$dep ; \
+ fi ; \
+ done
+
+clean-deps:
+ @for dep in $(ALL_DEPS_DIRS) ; do \
+ if [ -f $$dep/Makefile ] ; then \
+ $(MAKE) -C $$dep clean ; \
+ else \
+ echo "include $(CURDIR)/erlang.mk" | $(MAKE) -f - -C $$dep clean ; \
+ fi ; \
+ done
+
+# Documentation.
+
+docs: clean-docs
+ $(gen_verbose) erl -noshell \
+ -eval 'edoc:application($(PROJECT), ".", []), init:stop().'
+
+clean-docs:
+ $(gen_verbose) rm -f doc/*.css doc/*.html doc/*.png doc/edoc-info
+
+# Tests.
+
+$(foreach dep,$(TEST_DEPS),$(eval $(call dep_target,$(dep))))
+
+build-test-deps: $(ALL_TEST_DEPS_DIRS)
+ @for dep in $(ALL_TEST_DEPS_DIRS) ; do $(MAKE) -C $$dep; done
+
+build-tests: build-test-deps
+ $(gen_verbose) erlc -v $(ERLC_OPTS) -o test/ \
+ $(wildcard test/*.erl test/*/*.erl) -pa ebin/
+
+CT_RUN = ct_run \
+ -no_auto_compile \
+ -noshell \
+ -pa $(realpath ebin) $(DEPS_DIR)/*/ebin \
+ -dir test \
+ -logdir logs
+# -cover test/cover.spec
+
+CT_SUITES ?=
+
+define test_target
+test_$(1): ERLC_OPTS += -DTEST=1 +'{parse_transform, eunit_autoexport}'
+test_$(1): clean deps app build-tests
+ @if [ -d "test" ] ; \
+ then \
+ mkdir -p logs/ ; \
+ $(CT_RUN) -suite $(addsuffix _SUITE,$(1)) ; \
+ fi
+ $(gen_verbose) rm -f test/*.beam
+endef
+
+$(foreach test,$(CT_SUITES),$(eval $(call test_target,$(test))))
+
+tests: ERLC_OPTS += -DTEST=1 +'{parse_transform, eunit_autoexport}'
+tests: clean deps app build-tests
+ @if [ -d "test" ] ; \
+ then \
+ mkdir -p logs/ ; \
+ $(CT_RUN) -suite $(addsuffix _SUITE,$(CT_SUITES)) ; \
+ fi
+ $(gen_verbose) rm -f test/*.beam
+
+# Dialyzer.
+
+PLT_APPS ?=
+DIALYZER_OPTS ?= -Werror_handling -Wrace_conditions \
+ -Wunmatched_returns # -Wunderspecs
+
+build-plt: deps app
+ @dialyzer --build_plt --output_plt .$(PROJECT).plt \
+ --apps erts kernel stdlib $(PLT_APPS) $(ALL_DEPS_DIRS)
+
+dialyze:
+ @dialyzer --src src --plt .$(PROJECT).plt --no_native $(DIALYZER_OPTS)
+
+# Packages.
+
+$(PKG_FILE):
+ @$(call get_pkg_file)
+
+pkg-list: $(PKG_FILE)
+ @cat $(PKG_FILE) | awk 'BEGIN { FS = "\t" }; { print \
+ "Name:\t\t" $$1 "\n" \
+ "Repository:\t" $$2 "\n" \
+ "Website:\t" $$3 "\n" \
+ "Description:\t" $$4 "\n" }'
+
+ifdef q
+pkg-search: $(PKG_FILE)
+ @cat $(PKG_FILE) | grep -i ${q} | awk 'BEGIN { FS = "\t" }; { print \
+ "Name:\t\t" $$1 "\n" \
+ "Repository:\t" $$2 "\n" \
+ "Website:\t" $$3 "\n" \
+ "Description:\t" $$4 "\n" }'
+else
+pkg-search:
+ @echo "Usage: make pkg-search q=STRING"
+endif
diff --git a/src/horse.app.src b/src/horse.app.src
new file mode 100644
index 0000000..6ab0339
--- /dev/null
+++ b/src/horse.app.src
@@ -0,0 +1,24 @@
+%% Copyright (c) 2013, Loïc Hoguin <[email protected]>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+{application, horse, [
+ {description, "Integrated performance testing."},
+ {vsn, "0.1.0"},
+ {modules, []},
+ {registered, []},
+ {applications, [
+ kernel,
+ stdlib
+ ]}
+]}.
diff --git a/src/horse.erl b/src/horse.erl
new file mode 100644
index 0000000..b03a05b
--- /dev/null
+++ b/src/horse.erl
@@ -0,0 +1,49 @@
+%% Copyright (c) 2013, Loïc Hoguin <[email protected]>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(horse).
+
+-export([app_perf/1]).
+-export([mod_perf/1]).
+
+%% These might be interesting later on.
+%% @todo horse_init, horse_end
+%% @todo horse_init_per_test, horse_end_per_test
+
+app_perf(App) when is_atom(App) ->
+ io:format("Running horse on application ~s~n", [App]),
+ ok = application:load(App),
+ {ok, Modules} = application:get_key(App, modules),
+ _ = [mod_perf(M) || M <- lists:sort(Modules)],
+ ok.
+
+mod_perf(Mod) when is_atom(Mod) ->
+ Perfs = [F || {F, 0} <- Mod:module_info(exports),
+ "horse_" =:= string:substr(atom_to_list(F), 1, 6)],
+ _ = [fun_perf(Mod, Fun) || Fun <- Perfs],
+ ok.
+
+fun_perf(Mod, Fun) when is_atom(Mod), is_atom(Fun) ->
+ %% Dry run.
+ _ = Mod:Fun(),
+ %% Proper run.
+ Before = os:timestamp(),
+ _Val = Mod:Fun(),
+ After = os:timestamp(),
+ %% Results.
+ Time = timer:now_diff(After, Before),
+ "horse_" ++ Name = atom_to_list(Fun),
+ io:format("~s:~s in ~b.~6.10.0bs~n",
+ [Mod, Name, Time div 1000000, Time rem 1000000]),
+ ok.
diff --git a/src/horse_autoexport.erl b/src/horse_autoexport.erl
new file mode 100644
index 0000000..6e4a49a
--- /dev/null
+++ b/src/horse_autoexport.erl
@@ -0,0 +1,60 @@
+%% Copyright (c) 2013, Loïc Hoguin <[email protected]>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(horse_autoexport).
+
+-export([parse_transform/2]).
+
+parse_transform([File, Module|Forms], _) ->
+ Exports = [F || {attribute, _, export, [{F, 0}]} <- Forms],
+ AutoExports = [{attribute, 0, export, [{F, 0}]}
+ || {function, _, F, 0, _} <- Forms,
+ "horse_" =:= string:substr(atom_to_list(F), 1, 6),
+ false =:= lists:member(F, Exports)],
+ replace_calls([File, Module|AutoExports ++ Forms]).
+
+replace_calls(Forms) ->
+ lists:flatten([replace_call(Form) || Form <- Forms]).
+
+replace_call(
+ {function, Fu, Name, 0, [
+ {clause, Cl, [], [], [
+ {call, Ca, {remote, _, {atom, _, horse}, {atom, _, repeat}}, [
+ Repeat,
+ Expr
+ ]}
+ ]}
+ ]}
+) when Repeat > 0 ->
+ GenName = list_to_atom("generated_" ++ atom_to_list(Name)),
+ [
+ {function, Fu, Name, 0, [
+ {clause, Cl, [], [], [
+ {call, Ca, {atom, Ca, GenName}, [Repeat]}
+ ]}
+ ]},
+ {function, Ca, GenName, 1, [
+ {clause, Ca, [{integer, Ca, 0}], [], [
+ {atom, Ca, ok}
+ ]},
+ {clause, Ca, [{var, Ca, 'N'}], [], [
+ Expr,
+ {call, Ca, {atom, Ca, GenName}, [
+ {op, Ca, '-', {var, Ca, 'N'}, {integer, Ca, 1}}
+ ]}
+ ]}
+ ]}
+ ];
+replace_call(Form) ->
+ Form.