pax_global_header00006660000000000000000000000064130133703600014506gustar00rootroot0000000000000052 comment=4dc81d47c3116b38af673481402f34ce03f8936b erlang-horse-0+git20161117.0.4dc81d4/000077500000000000000000000000001301337036000164075ustar00rootroot00000000000000erlang-horse-0+git20161117.0.4dc81d4/LICENSE000066400000000000000000000013651301337036000174210ustar00rootroot00000000000000Copyright (c) 2011-2013, Loïc Hoguin 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. erlang-horse-0+git20161117.0.4dc81d4/Makefile000066400000000000000000000001151301337036000200440ustar00rootroot00000000000000# See LICENSE for licensing information. PROJECT = horse include erlang.mk erlang-horse-0+git20161117.0.4dc81d4/README.md000066400000000000000000000061151301337036000176710ustar00rootroot00000000000000Horse ===== 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 run 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) erlang-horse-0+git20161117.0.4dc81d4/erlang.mk000066400000000000000000000163351301337036000202200ustar00rootroot00000000000000# Copyright (c) 2013, Loïc Hoguin # # 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 --no-check-certificate -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/v0.5.2/relx RELX_OPTS ?= define get_relx wget -O $(RELX) $(RELX_URL) || rm $(RELX) chmod +x $(RELX) endef rel: clean-rel all $(RELX) @$(RELX) -c $(RELX_CONFIG) $(RELX_OPTS) $(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,[[:space:]]*\[\]}/{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. EDOC_OPTS ?= docs: clean-docs $(gen_verbose) erl -noshell \ -eval 'edoc:application($(PROJECT), ".", [$(EDOC_OPTS)]), 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 erlang-horse-0+git20161117.0.4dc81d4/src/000077500000000000000000000000001301337036000171765ustar00rootroot00000000000000erlang-horse-0+git20161117.0.4dc81d4/src/horse.app.src000066400000000000000000000017011301337036000216050ustar00rootroot00000000000000%% Copyright (c) 2013, Loïc Hoguin %% %% 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 ]} ]}. erlang-horse-0+git20161117.0.4dc81d4/src/horse.erl000066400000000000000000000033211301337036000210210ustar00rootroot00000000000000%% Copyright (c) 2013, Loïc Hoguin %% %% 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. erlang-horse-0+git20161117.0.4dc81d4/src/horse_autoexport.erl000066400000000000000000000036051301337036000233200ustar00rootroot00000000000000%% Copyright (c) 2013, Loïc Hoguin %% %% 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.