diff --git a/.gitignore b/.gitignore index a19d9f4..51c78d2 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ erl_crash.dump *.ez .tool-versions +.elixir_ls +priv/monotonic_nif.so diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..fb32b75 --- /dev/null +++ b/Makefile @@ -0,0 +1,30 @@ +MIX = mix +CFLAGS = -g -std=gnu99 -O3 -pedantic -Wall -Wextra -Wno-unused-parameter + +ERLANG_PATH = $(shell erl -eval 'io:format("~s", [lists:concat([code:root_dir(), "/erts-", erlang:system_info(version), "/include"])])' -s init stop -noshell) +CFLAGS += -I$(ERLANG_PATH) + +CFLAGS += -I$(HOEDOWN_PATH)/c_src + +ifneq ($(OS),Windows_NT) + CFLAGS += -fPIC + + ifeq ($(shell uname),Darwin) + LDFLAGS += -dynamiclib -undefined dynamic_lookup + endif +endif + +.PHONY: all monotonic clean + +all: monotonic + +monotonic: priv priv/monotonic_nif.so + +priv: + mkdir priv + +priv/monotonic_nif.so: c_src/monotonic_nif.c + $(CC) $(CFLAGS) -shared $(LDFLAGS) -o $@ c_src/monotonic_nif.c + +clean: + $(RM) priv/monotonic_nif.so diff --git a/c_src/monotonic_nif.c b/c_src/monotonic_nif.c new file mode 100644 index 0000000..1748116 --- /dev/null +++ b/c_src/monotonic_nif.c @@ -0,0 +1,175 @@ + +#include +#include +#include +#include + +#include "erl_nif.h" + +/* http://web.archive.org/web/20100501115556/http://le-depotoir.googlecode.com/svn/trunk/misc/clock_gettime_stub.c + */ +#ifdef __APPLE__ +/* + * Copyright (c), MM Weiss + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the MM Weiss nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * clock_gettime_stub.c + * gcc -Wall -c clock_gettime_stub.c + * posix realtime functions; MacOS user space glue + */ + +/* @comment + * other possible implementation using intel builtin rdtsc + * rdtsc-workaround: http://www.mcs.anl.gov/~kazutomo/rdtsc.html + * + * we could get the ticks by doing this + * + * __asm __volatile("mov %%ebx, %%esi\n\t" + * "cpuid\n\t" + * "xchg %%esi, %%ebx\n\t" + * "rdtsc" + * : "=a" (a), + * "=d" (d) + * ); + + * we could even replace our tricky sched_yield call by assembly code to get a + better accurency, + * anyway the following C stub will satisfy 99% of apps using posix + clock_gettime call, + * moreover, the setter version (clock_settime) could be easly written using + mach primitives: + * http://www.opensource.apple.com/source/xnu/xnu-${VERSION}/osfmk/man/ + (clock_[set|get]_time) + * + * hackers don't be crackers, don't you use a flush toilet? + * + * + * @see draft: ./posix-realtime-stub/posix-realtime-stub.c + * + */ + +#pragma weak clock_gettime + +#include +#include +#include +#include +#include +#include +#include +#include + +int clock_gettime(clockid_t clk_id, struct timespec *tp); + +#ifndef CLOCK_REALTIME +// latest macos now has a CLOCK_REALTIME defined +typedef enum { + CLOCK_REALTIME, + CLOCK_MONOTONIC, + CLOCK_PROCESS_CPUTIME_ID, + CLOCK_THREAD_CPUTIME_ID +} clockid_t; +#endif + +static mach_timebase_info_data_t __clock_gettime_inf; + +int clock_gettime(clockid_t clk_id, struct timespec *tp) { + kern_return_t ret; + clock_serv_t clk; + clock_id_t clk_serv_id; + mach_timespec_t tm; + + uint64_t start, end, delta, nano; + + int retval = -1; + switch (clk_id) { + case CLOCK_REALTIME: + case CLOCK_MONOTONIC: + clk_serv_id = clk_id == CLOCK_REALTIME ? CALENDAR_CLOCK : SYSTEM_CLOCK; + if (KERN_SUCCESS == + (ret = host_get_clock_service(mach_host_self(), clk_serv_id, &clk))) { + if (KERN_SUCCESS == (ret = clock_get_time(clk, &tm))) { + tp->tv_sec = tm.tv_sec; + tp->tv_nsec = tm.tv_nsec; + retval = 0; + } + } + if (KERN_SUCCESS != ret) { + errno = EINVAL; + retval = -1; + } + break; + case CLOCK_PROCESS_CPUTIME_ID: + case CLOCK_THREAD_CPUTIME_ID: + start = mach_absolute_time(); + if (clk_id == CLOCK_PROCESS_CPUTIME_ID) { + getpid(); + } else { + sched_yield(); + } + end = mach_absolute_time(); + delta = end - start; + if (0 == __clock_gettime_inf.denom) { + mach_timebase_info(&__clock_gettime_inf); + } + nano = delta * __clock_gettime_inf.numer / __clock_gettime_inf.denom; + tp->tv_sec = nano * 1e-9; + tp->tv_nsec = nano - (tp->tv_sec * 1e9); + retval = 0; + break; + default: + errno = EINVAL; + retval = -1; + } + return retval; +} +#endif /* __APPLE__ */ + +struct timespec ts; + +static ERL_NIF_TERM microseconds(ErlNifEnv *env, int argc, + const ERL_NIF_TERM argv[]) { + clock_gettime(CLOCK_MONOTONIC, &ts); + return enif_make_uint64(env, (ts.tv_sec * (uint64_t)1000000) + + (uint64_t)(ts.tv_nsec / 1000.0)); +} + +static ERL_NIF_TERM milliseconds(ErlNifEnv *env, int argc, + const ERL_NIF_TERM argv[]) { + clock_gettime(CLOCK_MONOTONIC, &ts); + return enif_make_uint64(env, (ts.tv_sec * (uint64_t)1000) + + (uint64_t)(ts.tv_nsec / 1000000.0)); +} + +static ErlNifFunc nif_funcs[] = {{"milliseconds", 0, milliseconds}, + {"microseconds", 0, microseconds}}; + +ERL_NIF_INIT(Elixir.Monotonic.NIF, nif_funcs, NULL, NULL, NULL, NULL) diff --git a/config/config.exs b/config/config.exs deleted file mode 100644 index 6dfa82f..0000000 --- a/config/config.exs +++ /dev/null @@ -1,24 +0,0 @@ -# This file is responsible for configuring your application -# and its dependencies with the aid of the Mix.Config module. -use Mix.Config - -# This configuration is loaded before any dependency and is restricted -# to this project. If another project depends on this project, this -# file won't be loaded nor affect the parent project. For this reason, -# if you want to provide default values for your application for third- -# party users, it should be done in your mix.exs file. - -# Sample configuration: -# -# config :logger, :console, -# level: :info, -# format: "$date $time [$level] $metadata$message\n", -# metadata: [:user_id] - -# It is also possible to import configuration files, relative to this -# directory. For example, you can emulate configuration per environment -# by uncommenting the line below and defining dev.exs, test.exs and such. -# Configuration from the imported file will override the ones defined -# here (which is why it is important to import them last). -# -# import_config "#{Mix.env}.exs" diff --git a/lib/monotonic.ex b/lib/monotonic.ex index 1a4e4e3..7949e12 100644 --- a/lib/monotonic.ex +++ b/lib/monotonic.ex @@ -2,7 +2,7 @@ defmodule Monotonic do defmacro __using__(_opts) do quote do require Monotonic - import Monotonic, only: [monotonic_microseconds: 0, monotonic_milliseconds: 0] + import Monotonic, only: [monotonic_microseconds: 0, monotonic_milliseconds: 0] end end @@ -14,20 +14,7 @@ defmodule Monotonic do """ defmacro monotonic_microseconds do quote do - :c_monotonic_time.microseconds() - end - end - - @doc """ - Returns the current OS monotonic time in microseconds. - - This macro is not imported so should be called as - - Monotonic.microseconds - """ - defmacro microseconds do - quote do - :c_monotonic_time.microseconds() + Monotonic.NIF.microseconds() end end @@ -39,20 +26,21 @@ defmodule Monotonic do """ defmacro monotonic_milliseconds do quote do - :c_monotonic_time.milliseconds() + Monotonic.NIF.milliseconds() end end @doc """ - Returns the current OS monotonic time in milliseconds. - - This macro is not imported so should be called as + Returns the current OS monotonic time in microseconds. + """ + def microseconds do + Monotonic.NIF.microseconds() + end - Monotonic.milliseconds + @doc """ + Returns the current OS monotonic time in milliseconds. """ - defmacro milliseconds do - quote do - :c_monotonic_time.milliseconds() - end + def milliseconds do + Monotonic.NIF.milliseconds() end end diff --git a/lib/monotonic/nif.ex b/lib/monotonic/nif.ex new file mode 100644 index 0000000..9cfedd9 --- /dev/null +++ b/lib/monotonic/nif.ex @@ -0,0 +1,17 @@ +defmodule Monotonic.NIF do + @on_load {:load_nifs, 0} + + def load_nifs do + path = :filename.join(:code.priv_dir(:monotonic), 'monotonic_nif') + + :erlang.load_nif(path, 0) + end + + def milliseconds do + :erlang.nif_error("NIF library not loaded") + end + + def microseconds do + :erlang.nif_error("NIF library not loaded") + end +end diff --git a/mix.exs b/mix.exs index 4f842ac..758c7de 100644 --- a/mix.exs +++ b/mix.exs @@ -2,33 +2,24 @@ defmodule Monotonic.Mixfile do use Mix.Project def project do - [app: :monotonic, - version: "0.0.1", - elixir: "~> 1.0", - build_embedded: Mix.env == :prod, - start_permanent: Mix.env == :prod, - deps: deps()] + [ + app: :monotonic, + version: "0.0.1", + elixir: "~> 1.0", + build_embedded: Mix.env() == :prod, + start_permanent: Mix.env() == :prod, + compilers: [:elixir_make] ++ Mix.compilers(), + deps: deps() + ] end - # Configuration for the OTP application - # - # Type `mix help compile.app` for more information def application do - [applications: [:logger, :c_monotonic_time]] + [applications: [:logger]] end - # Dependencies can be Hex packages: - # - # {:mydep, "~> 0.3.0"} - # - # Or git/path repositories: - # - # {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"} - # - # Type `mix help deps` for more examples and options defp deps do - [ {:c_monotonic_time, git: "https://github.com/magnetised/c_monotonic_time.git", compile: "./rebar compile"}, - # {:c_monotonic_time, path: "/Users/garry/Seafile/Peep/c_monotonic_time", compile: "./rebar compile"} + [ + {:elixir_make, "~> 0.6", runtime: false} ] end end diff --git a/mix.lock b/mix.lock index a3cf2ff..4ac2510 100644 --- a/mix.lock +++ b/mix.lock @@ -1 +1,3 @@ -%{"c_monotonic_time": {:git, "https://github.com/magnetised/c_monotonic_time.git", "88afba0ef780bb7fc8f200121d3c1908375e62cd", []}} +%{ + "elixir_make": {:hex, :elixir_make, "0.6.3", "bc07d53221216838d79e03a8019d0839786703129599e9619f4ab74c8c096eac", [:mix], [], "hexpm", "f5cbd651c5678bcaabdbb7857658ee106b12509cd976c2c2fca99688e1daf716"}, +}