remove elixir code

This commit is contained in:
Luna Mendes 2018-07-07 19:06:32 -03:00
parent 884c9f736d
commit f8269c8aec
18 changed files with 119 additions and 711 deletions

117
.gitignore vendored
View file

@ -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

20
config.example.py Normal file
View file

@ -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
}
}

View file

@ -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,
},
]

View file

@ -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,
},
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

32
mix.exs
View file

@ -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

View file

@ -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"},
}

View file

@ -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!ufa<SB`awK,KX)iC&mv~`At"
end
environment :prod do
set include_erts: true
set include_src: false
set cookie: :"1>bm2160O|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

View file

@ -1,93 +0,0 @@
<html>
<head>
<meta http-equiv="Access-Control-Allow-Origin" content="*">
<style>
body { background-color: #ddd; }
p {
text-align: center;
font-family: sans-serif;
font-size: 13pt;
}
#pretty-box {
padding-left: 5px;
max-width: 500px;
max-height: 250px;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
background-color: #eee;
border-radius: 15px;
}
</style>
<script>
// https://stackoverflow.com/a/4033310
function httpGetAsync(theUrl, callback)
{
var xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function() {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200)
callback(xmlHttp.responseText);
}
xmlHttp.open("GET", theUrl, true); // true for asynchronous
xmlHttp.send(null);
}
function genText(data) {
base = `${data.description}`;
base += ` [${data.status ? "online" : "offline"}]`;
if('latency' in data) {
base += ` - latency: ${data.latency} ms`;
}
return base;
}
function curStatHandler(text) {
let box = document.getElementById('pretty-box');
let payload = JSON.parse(text);
for(const service in payload) {
let data = payload[service];
let elementId = `${service}-description`;
let existingElement = document.getElementById(elementId);
if (existingElement === null) {
let element = document.createElement('p');
element.id = elementId;
element.innerText = genText(data);
box.appendChild(element);
} else {
existingElement.innerText = genText(data);
}
}
}
function fetchStatus () {
httpGetAsync("http://127.0.0.1:8069/api/current_status", curStatHandler);
}
window.onload = function () {
fetchStatus();
setInterval(() => {
fetchStatus();
}, 3000)
}
</script>
</head>
<body>
<div id="pretty-box">
</div>
</body>
</html>

View file

@ -1,8 +0,0 @@
defmodule ElstatTest do
use ExUnit.Case
doctest Elstat
test "greets the world" do
assert Elstat.hello() == :world
end
end

View file

@ -1 +0,0 @@
ExUnit.start()