diff --git a/.gitignore b/.gitignore index ed636be..552e997 100644 --- a/.gitignore +++ b/.gitignore @@ -10,30 +10,111 @@ thumbs.db /priv/yarn-debug.log* /priv/yarn-error.log* -# The directory Mix will write compiled artifacts to. -/_build/ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class -# If you run "mix test --cover", coverage assets end up here. -/cover/ +# C extensions +*.so -# The directory Mix downloads your dependencies sources to. -/deps/ +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST -# Where 3rd-party dependencies like ExDoc output generated docs. -/doc/ +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec -# Ignore .fetch files in case you like to edit your project deps locally. -/.fetch +# Installer logs +pip-log.txt +pip-delete-this-directory.txt -# If the VM crashes, it generates a dump, let's ignore it too. -erl_crash.dump +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ -# Also ignore archive artifacts (built via "mix archive.build"). -*.ez +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ -# Ignore package tarball (built via "mix hex.build"). -elstat-*.tar *.db -config/config.exs - +config.py diff --git a/config.example.py b/config.example.py new file mode 100644 index 0000000..aaf17dd --- /dev/null +++ b/config.example.py @@ -0,0 +1,20 @@ +PORT = 8069 + +SERVICES = { + 'elixire': { + 'description': "elixi.re's backend", + 'adapter': 'elixire', + 'adapter_args': { + 'base_url': 'https://elixi.re' + }, + 'poll': 10 + }, + 'dabian': { + 'description': 'elixi.re main server', + 'adapter': 'ping', + 'adapter_args': { + 'address': '192.168.1.1' + }, + 'poll': 15 + } +} diff --git a/config/config.example.exs b/config/config.example.exs deleted file mode 100644 index 6603a02..0000000 --- a/config/config.example.exs +++ /dev/null @@ -1,31 +0,0 @@ -use Mix.Config - -config :elstat, - # where will elstat (backend and frontend) - # be served - port: 8069, - - # which services will be managed by elstat? - # this follows a service spec - # the example here is for elixire - services: [ - %{ - id: :elixire, - description: "elixi.re", - adapter: Elstat.Adapter.Elixire, - adapter_opts: %{ - base_url: "https://elixi.re", - }, - # every 10 seconds - poll: 10, - }, - %{ - id: :genserver, - description: "genserver, elixire main server", - adapter: Elstat.Adapter.Ping, - adapter_opts: %{ - address: "192.168.1.1", - }, - poll: 15, - }, - ] diff --git a/config/config.exs b/config/config.exs deleted file mode 100644 index a3458ec..0000000 --- a/config/config.exs +++ /dev/null @@ -1,27 +0,0 @@ -use Mix.Config - -config :elstat, - port: 8069, - - # which services will be managed by elstat? - # this follows a service spec - # the example here is for elixire - services: %{ - elixire: %{ - description: "the backend of elixi.re", - adapter: Elstat.Adapter.Elixire, - adapter_opts: %{ - base_url: "https://elixi.re", - }, - # every 10 seconds - poll: 10, - }, - genserver: %{ - description: "genserver, elixire main server", - adapter: Elstat.Adapter.Ping, - adapter_opts: %{ - address: "192.168.1.1", - }, - poll: 15, - }, - } diff --git a/lib/adapters/adapter.ex b/lib/adapters/adapter.ex deleted file mode 100644 index d7e3764..0000000 --- a/lib/adapters/adapter.ex +++ /dev/null @@ -1,7 +0,0 @@ -defmodule Elstat.Adapter do - @type adapter_check_res :: {:ok, any()} | {:error, any()} - - @type adp_args :: Map.t - - @callback check(adp_args) :: adapter_check_res -end diff --git a/lib/adapters/elixire.ex b/lib/adapters/elixire.ex deleted file mode 100644 index af54a10..0000000 --- a/lib/adapters/elixire.ex +++ /dev/null @@ -1,39 +0,0 @@ -defmodule Elstat.Adapter.Elixire do - @behaviour Elstat.Adapter - - def adapter_spec do - %{ - db_columns: [:timestamp, :status, :latency] - } - end - - def check(args) do - addr = args.base_url - - req_start = :erlang.monotonic_time(:millisecond) - status = HTTPoison.get("#{addr}/api/hello") - req_end = :erlang.monotonic_time(:millisecond) - - delta = req_end - req_start - - case status do - {:ok, resp} -> - {:ok, {:map, %{ - status: resp.status_code == 200, - latency: delta - }}} - {:error, reason} -> - {:ok, {:map, %{ - status: false, - - # use drops to 0 as failure on conn - latency: 0 - }}} - end - end - - def transform_val({:map, %{status: status, latency: latency}}) do - [status, latency] - end - -end diff --git a/lib/adapters/ping.ex b/lib/adapters/ping.ex deleted file mode 100644 index 047ebf1..0000000 --- a/lib/adapters/ping.ex +++ /dev/null @@ -1,20 +0,0 @@ -defmodule Elstat.Adapter.Ping do - @behaviour Elstat.Adapter - - def adapter_spec do - %{ - db_columns: [:timestamp, :status] - } - end - - def check(args) do - {cmd_output, _} = System.cmd("ping", ["-c", "1", args.address]) - alive? = not Regex.match?(~r/100(\.0)?% packet loss/, cmd_output) - {:ok, {:bool, alive?}} - end - - def transform_val({:bool, alive?}) do - [alive?] - end - -end diff --git a/lib/api.ex b/lib/api.ex deleted file mode 100644 index 07279d2..0000000 --- a/lib/api.ex +++ /dev/null @@ -1,33 +0,0 @@ -defmodule Elstat.API.CurrentStatus do - require Logger - - def init(req0, state) do - data = Elstat.Manager.get_current_state() - - req = :cowboy_req.reply(200, - %{"content-type" => "text/json", "Access-Control-Allow-Origin" => "*"}, - Poison.encode!(data), - req0 - ) - - {:ok, req, state} - end -end - -defmodule Elstat.API.Status do - def init(req0, state) do - status = Elstat.Manager.get_current_state() - graph = Elstat.Manager.get_graph_state() - - req = :cowboy_req.reply(200, - %{"content-type" => "text/json", "Access-Control-Allow-Origin" => "*"}, - Poison.encode!(%{ - status: status, - graph: graph, - }), - req0 - ) - - {:ok, req, state} - end -end diff --git a/lib/cowboy.ex b/lib/cowboy.ex deleted file mode 100644 index fc54025..0000000 --- a/lib/cowboy.ex +++ /dev/null @@ -1,41 +0,0 @@ -defmodule Elstat.Cowboy.DefaultHandler do - def init(req0, state) do - - # TODO: find a way to send a html file - # instead of hewwo owo - req = :cowboy_req.reply(200, - %{"content-type" => "text/plain"}, - "hewwo", - req0 - ) - - {:ok, req, state} - end -end - - -defmodule Elstat.Cowboy do - def start_link do - dispatch_config = build_dispatch_config() - - port = Application.fetch_env!(:elstat, :port) - - {:ok, _} = :cowboy.start_clear( - :elstat, - [{:port, port}], - %{env: %{dispatch: dispatch_config}} - ) - end - - def build_dispatch_config do - :cowboy_router.compile([ - {:_, [ - {"/hewwo", Elstat.Cowboy.DefaultHandler, []}, - {"/api/current_status", Elstat.API.CurrentStatus, []}, - {"/api/status", Elstat.API.Status, []}, - {"/", :cowboy_static, {:priv_file, :elstat, "frontend/build/index.html"}}, - {"/[...]", :cowboy_static, {:priv_dir, :elstat, "frontend/build"}}, - ]} - ]) - end -end diff --git a/lib/elstat.ex b/lib/elstat.ex deleted file mode 100644 index 04acb86..0000000 --- a/lib/elstat.ex +++ /dev/null @@ -1,15 +0,0 @@ -defmodule Elstat do - use Application - - require Logger - - def start(_type, _args) do - Logger.info "starting app" - Supervisor.start_link( - [ - Elstat.Supervisor - ], - strategy: :one_for_one - ) - end -end diff --git a/lib/manager.ex b/lib/manager.ex deleted file mode 100644 index 92c808d..0000000 --- a/lib/manager.ex +++ /dev/null @@ -1,244 +0,0 @@ -defmodule Elstat.Error.Service do - defexception message: "default message" -end - -defmodule Elstat.ServiceWorker do - require Logger - - def start_link(service) do - Logger.info "spawning worker for #{inspect service.id}" - - worker_pid = spawn(fn -> - worker_func(service) - end) - - {:ok, worker_pid} - end - - def worker_func(service) do - adapter = service.adapter - - result = adapter.check(service.adapter_opts) - Logger.info "result from #{inspect adapter} #{inspect service.id}: #{inspect result}" - - man_pid = :global.whereis_name Elstat.Manager - send man_pid, {:service_info, {service.id, result}} - - Process.sleep(service.poll * 1000) - worker_func(service) - end -end - -defmodule Elstat.Manager do - @moduledoc """ - Elstat's service manager. - """ - use GenServer - require Logger - - def start_link(opts) do - GenServer.start_link(__MODULE__, :ok, [name: {:global, Elstat.Manager}] ++ opts) - end - - def build_services() do - services = Application.fetch_env!(:elstat, :services) - - workers = services - |> Map.keys - |> Enum.map(fn service_id -> - service = Map.put(services[service_id], :id, service_id) - - # each service worker will enter an infinite loop - # polling the service (via an adapter) and giving - # information back to the manager, so it can - # process and show those via its API. - %{ - id: service_id, - start: {Elstat.ServiceWorker, :start_link, [service]} - } - end) - - # spawn the managere alongside service workers - [ Elstat.Manager | workers] - end - - def get_current_state() do - man_pid = :global.whereis_name Elstat.Manager - GenServer.call(man_pid, {:get_current}) - end - - def get_graph_state() do - man_pid = :global.whereis_name Elstat.Manager - GenServer.call(man_pid, {:get_graph}) - end - - # server callbacks - - def get_columns_def do - %{ - timestamp: "timestamp bigint", - status: "status bool", - latency: "latency bigint", - } - end - - def get_columns_name do - %{ - timestamp: "timestamp", - status: "status", - latency: "latency", - } - end - - def init(:ok) do - services = Application.fetch_env!(:elstat, :services) - - Sqlitex.with_db('elstat.db', fn(db) -> - services - |> Map.keys - |> Enum.each(fn service_id -> - service = services[service_id] - adapter = service.adapter - - spec = adapter.adapter_spec - - columns = get_columns_def() - - column_res = spec.db_columns - |> Enum.map(fn column -> - columns[column] - end) - |> Enum.join(",\n") - - query = """ - CREATE TABLE IF NOT EXISTS #{Atom.to_string service_id} ( - #{column_res} - ); - """ - - Logger.debug "query for #{inspect service_id}: #{query}" - - case Sqlitex.query(db, query) do - {:ok, _} -> - Logger.info "created table for #{inspect service_id}" - {:error, _err} -> - Logger.error "error making table for #{inspect service_id}" - end - end) - end) - - {:ok, %{ - services: services, - serv_state: %{}, - }} - end - - def handle_call({:get_current}, _from, state) do - reply = state.serv_state - |> Map.keys - |> Enum.map(fn key -> - data = state.serv_state[key] - desc = state.services[key].description - - case data do - {:map, data_map} -> - {key, Map.put(data_map, :description, desc)} - {:bool, data_bool} -> - {key, %{ - status: data_bool, - description: desc, - }} - end - end) - |> Map.new - - {:reply, reply, state} - end - - def handle_call({:get_graph}, _from, state) do - graph_reply = state.serv_state - |> Map.keys - |> Enum.map(fn key -> - spec = state.services[key].adapter.adapter_spec - - if Enum.member?(spec.db_columns, :latency) do - {:ok, result} = Sqlitex.with_db('elstat.db', fn db -> - query = """ - SELECT timestamp, latency FROM #{Atom.to_string key} - ORDER BY timestamp DESC - LIMIT 50 - """ - - Logger.debug "query for latency: #{query}" - - Sqlitex.query(db, query) - end) - - act = result - |> Enum.map(fn field -> - [Keyword.get(field, :timestamp), Keyword.get(field, :latency)] - end) - - {key, act} - else - nil - end - end) - |> Enum.filter(fn d -> d != nil end) - |> Map.new - - Logger.debug "graph reply: #{inspect graph_reply}" - - {:reply, graph_reply, state} - end - - def build_insert_query(service_id, state) do - services = state.services - service = services[service_id] - spec = service.adapter.adapter_spec - - columns_str = spec.db_columns - |> Enum.map(fn atom -> - Atom.to_string atom - end) - |> Enum.join(",") - - query_args_str = 1..Enum.count(spec.db_columns) - |> Enum.map(fn num -> - "$#{num}" - end) - |> Enum.join(",") - - """ - INSERT INTO #{Atom.to_string service_id} (#{columns_str}) - VALUES (#{query_args_str}) - """ - end - - def handle_info({:service_info, {sid, sdata}}, state) do - case sdata do - {:ok, actual_data} -> - new_serv_state = Map.put(state.serv_state, sid, actual_data) - - query = build_insert_query(sid, state) - - adapter = state.services[sid].adapter - adp_args = adapter.transform_val(actual_data) - - timestamp = :erlang.system_time(:millisecond) - - Logger.debug "query: #{query}, timestamp: #{timestamp}, adp args: #{inspect adp_args}" - - Sqlitex.with_db('elstat.db', fn(db) -> - res = Sqlitex.query(db, query, bind: [timestamp | adp_args]) - IO.puts "#{inspect res}" - end) - - {:noreply, %{state | serv_state: new_serv_state}} - {:error, err} -> - Logger.warn "error on #{inspect sid}: #{inspect err}" - end - - end - -end diff --git a/lib/supervisor.ex b/lib/supervisor.ex deleted file mode 100644 index 1e3b50b..0000000 --- a/lib/supervisor.ex +++ /dev/null @@ -1,30 +0,0 @@ -defmodule Elstat.Supervisor do - use Supervisor - require Logger - - def start_link(opts) do - Supervisor.start_link(__MODULE__, :ok, opts) - end - - def init(:ok) do - Logger.info "starting sup" - - base_children = [ - %{ - id: Elstat.Cowboy, - start: {Elstat.Cowboy, :start_link, []}, - }, - %{ - id: Sqlitex.Server, - start: {Sqlitex.Server, :start_link, ['elstat.db', [name: Sqlitex.Server]]} - } - ] - - service_children = Elstat.Manager.build_services() - - children = service_children ++ base_children - Logger.debug "final children #{inspect children}" - Supervisor.init(children, strategy: :one_for_one) - end - -end diff --git a/mix.exs b/mix.exs deleted file mode 100644 index 22653ab..0000000 --- a/mix.exs +++ /dev/null @@ -1,32 +0,0 @@ -defmodule Elstat.MixProject do - use Mix.Project - - def project do - [ - app: :elstat, - version: "0.1.0", - elixir: "~> 1.6", - start_permanent: Mix.env() == :prod, - deps: deps() - ] - end - - # Run "mix help compile.app" to learn about applications. - def application do - [ - mod: {Elstat, []}, - extra_applications: [:logger] - ] - end - - # Run "mix help deps" to learn about dependencies. - defp deps do - [ - {:httpoison, "~> 1.1.1"}, - {:poison, "~> 3.1"}, - {:sqlitex, "~> 1.4.2"}, - {:cowboy, "~> 2.4.0"}, - {:distillery, "~> 1.5", runtime: false} - ] - end -end diff --git a/mix.lock b/mix.lock deleted file mode 100644 index 913d7ea..0000000 --- a/mix.lock +++ /dev/null @@ -1,19 +0,0 @@ -%{ - "certifi": {:hex, :certifi, "2.3.1", "d0f424232390bf47d82da8478022301c561cf6445b5b5fb6a84d49a9e76d2639", [:rebar3], [{:parse_trans, "3.2.0", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"}, - "cowboy": {:hex, :cowboy, "2.4.0", "f1b72fabe9c8a5fc64ac5ac85fb65474d64733d1df52a26fad5d4ba3d9f70a9f", [:rebar3], [{:cowlib, "~> 2.3.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.5.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"}, - "cowlib": {:hex, :cowlib, "2.3.0", "bbd58ef537904e4f7c1dd62e6aa8bc831c8183ce4efa9bd1150164fe15be4caa", [:rebar3], [], "hexpm"}, - "decimal": {:hex, :decimal, "1.5.0", "b0433a36d0e2430e3d50291b1c65f53c37d56f83665b43d79963684865beab68", [:mix], [], "hexpm"}, - "distillery": {:hex, :distillery, "1.5.2", "eec18b2d37b55b0bcb670cf2bcf64228ed38ce8b046bb30a9b636a6f5a4c0080", [:mix], [], "hexpm"}, - "esqlite": {:hex, :esqlite, "0.2.4", "3a8a352c190afe2d6b828b252a6fbff65b5cc1124647f38b15bdab3bf6fd4b3e", [:rebar3], [], "hexpm"}, - "hackney": {:hex, :hackney, "1.12.1", "8bf2d0e11e722e533903fe126e14d6e7e94d9b7983ced595b75f532e04b7fdc7", [:rebar3], [{:certifi, "2.3.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.1", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, - "httpoison": {:hex, :httpoison, "1.1.1", "96ed7ab79f78a31081bb523eefec205fd2900a02cda6dbc2300e7a1226219566", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, - "idna": {:hex, :idna, "5.1.1", "cbc3b2fa1645113267cc59c760bafa64b2ea0334635ef06dbac8801e42f7279c", [:rebar3], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, - "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"}, - "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"}, - "parse_trans": {:hex, :parse_trans, "3.2.0", "2adfa4daf80c14dc36f522cf190eb5c4ee3e28008fc6394397c16f62a26258c2", [:rebar3], [], "hexpm"}, - "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, - "ranch": {:hex, :ranch, "1.5.0", "f04166f456790fee2ac1aa05a02745cc75783c2bfb26d39faf6aefc9a3d3a58a", [:rebar3], [], "hexpm"}, - "sqlitex": {:hex, :sqlitex, "1.4.2", "b18f2b53cefbc9cca0bd17d51386f9caa7cf341144cb314e5cd9fd2a1f9b0845", [:mix], [{:decimal, "~> 1.1", [hex: :decimal, repo: "hexpm", optional: false]}, {:esqlite, "~> 0.2.4", [hex: :esqlite, repo: "hexpm", optional: false]}], "hexpm"}, - "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], [], "hexpm"}, - "unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [:rebar3], [], "hexpm"}, -} diff --git a/rel/config.exs b/rel/config.exs deleted file mode 100644 index 96359ed..0000000 --- a/rel/config.exs +++ /dev/null @@ -1,53 +0,0 @@ -# Import all plugins from `rel/plugins` -# They can then be used by adding `plugin MyPlugin` to -# either an environment, or release definition, where -# `MyPlugin` is the name of the plugin module. -Path.join(["rel", "plugins", "*.exs"]) -|> Path.wildcard() -|> Enum.map(&Code.eval_file(&1)) - -use Mix.Releases.Config, - # This sets the default release built by `mix release` - default_release: :default, - # This sets the default environment used by `mix release` - default_environment: Mix.env() - -# For a full list of config options for both releases -# and environments, visit https://hexdocs.pm/distillery/configuration.html - - -# You may define one or more environments in this file, -# an environment's settings will override those of a release -# when building in that environment, this combination of release -# and environment configuration is called a profile - -environment :dev do - # If you are running Phoenix, you should make sure that - # server: true is set and the code reloader is disabled, - # even in dev mode. - # It is recommended that you build with MIX_ENV=prod and pass - # the --env flag to Distillery explicitly if you want to use - # dev mode. - set dev_mode: true - set include_erts: false - set cookie: :"fn5>S.FMYz7!Ri/U.Svz5hXw,kKO=S,2kvDr;~8I!ufabm2160O|df!iH=}h2j6tAG4X8L(v/}S<8oS>!UZ!oI0@`si;=lmA1Wf=Ns=^?d" -end - -# You may define one or more releases in this file. -# If you have not set a default release, or selected one -# when running `mix release`, the first release in the file -# will be used by default - -release :elstat do - set version: current_version(:elstat) - set applications: [ - :runtime_tools - ] -end - diff --git a/static/index.html b/static/index.html deleted file mode 100644 index 0ca1d1e..0000000 --- a/static/index.html +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - - - -
-
- - - diff --git a/test/elstat_test.exs b/test/elstat_test.exs deleted file mode 100644 index b21623a..0000000 --- a/test/elstat_test.exs +++ /dev/null @@ -1,8 +0,0 @@ -defmodule ElstatTest do - use ExUnit.Case - doctest Elstat - - test "greets the world" do - assert Elstat.hello() == :world - end -end diff --git a/test/test_helper.exs b/test/test_helper.exs deleted file mode 100644 index 869559e..0000000 --- a/test/test_helper.exs +++ /dev/null @@ -1 +0,0 @@ -ExUnit.start()