Pārlūkot izejas kodu

Add todo metrics

master
Piotr Orzechowski pirms 5 mēnešiem
vecāks
revīzija
191a51c898

+ 4
- 0
ch10/todo_metrics/.formatter.exs Parādīt failu

@@ -0,0 +1,4 @@
# Used by "mix format"
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]

+ 24
- 0
ch10/todo_metrics/.gitignore Parādīt failu

@@ -0,0 +1,24 @@
# The directory Mix will write compiled artifacts to.
/_build/

# If you run "mix test --cover", coverage assets end up here.
/cover/

# The directory Mix downloads your dependencies sources to.
/deps/

# Where third-party dependencies like ExDoc output generated docs.
/doc/

# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch

# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump

# Also ignore archive artifacts (built via "mix archive.build").
*.ez

# Ignore package tarball (built via "mix hex.build").
todo-*.tar


+ 21
- 0
ch10/todo_metrics/README.md Parādīt failu

@@ -0,0 +1,21 @@
# Todo

**TODO: Add description**

## Installation

If [available in Hex](https://hex.pm/docs/publish), the package can be installed
by adding `todo` to your list of dependencies in `mix.exs`:

```elixir
def deps do
[
{:todo, "~> 0.1.0"}
]
end
```

Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
be found at [https://hexdocs.pm/todo](https://hexdocs.pm/todo).


+ 30
- 0
ch10/todo_metrics/config/config.exs Parādīt failu

@@ -0,0 +1,30 @@
# 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.

# You can configure your application as:
#
# config :todo, key: :value
#
# and access this configuration in your application as:
#
# Application.get_env(:todo, :key)
#
# You can also configure a third-party app:
#
# config :logger, level: :info
#

# 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"

+ 20
- 0
ch10/todo_metrics/lib/todo/cache.ex Parādīt failu

@@ -0,0 +1,20 @@
defmodule Todo.Cache do

def start_link() do
IO.puts("Starting to-do cache")
DynamicSupervisor.start_link(name: __MODULE__, strategy: :one_for_one)
end

def server_process(todo_list_name) do
case start_child(todo_list_name) do
{:ok, pid} -> pid
{:error, {:already_started, pid}} -> pid
end
end

def child_spec(_), do: %{id: __MODULE__, start: {__MODULE__, :start_link, []}, type: :supervisor}

defp start_child(todo_list_name) do
DynamicSupervisor.start_child(__MODULE__, {Todo.Server, todo_list_name})
end
end

+ 32
- 0
ch10/todo_metrics/lib/todo/database.ex Parādīt failu

@@ -0,0 +1,32 @@
defmodule Todo.Database do

@pool_size 3
@db_folder "./persist"

def start_link() do
File.mkdir_p!(@db_folder)
children = Enum.map(1..@pool_size, &worker_spec/1)
Supervisor.start_link(children, strategy: :one_for_one)
end

def child_spec(_), do: %{id: __MODULE__, start: {__MODULE__, :start_link, []}, type: :supervisor}

def store(key, data) do
key
|> choose_worker()
|> Todo.DatabaseWorker.store(key, data)
end

def get(key) do
key
|> choose_worker()
|> Todo.DatabaseWorker.get(key)
end

defp worker_spec(worker_id) do
default_worker_spec = {Todo.DatabaseWorker, {@db_folder, worker_id}}
Supervisor.child_spec(default_worker_spec, id: worker_id)
end

defp choose_worker(key), do: :erlang.phash2(key, @pool_size) + 1
end

+ 39
- 0
ch10/todo_metrics/lib/todo/database_worker.ex Parādīt failu

@@ -0,0 +1,39 @@
defmodule Todo.DatabaseWorker do
use GenServer

def start_link({db_folder, worker_id}) do
IO.puts("Starting database worker #{worker_id}")
GenServer.start_link(__MODULE__, db_folder, name: via_tuple(worker_id))
end

def store(worker_id, key, data), do: GenServer.cast(via_tuple(worker_id), {:store, key, data})
def get(worker_id, key), do: GenServer.call(via_tuple(worker_id), {:get, key})

@impl GenServer
def init(db_folder) do
File.mkdir_p!(db_folder)
{:ok, db_folder}
end

@impl GenServer
def handle_cast({:store, key, data}, state) do
file_name(state, key)
|> File.write!(:erlang.term_to_binary(data))

{:noreply, state}
end

@impl GenServer
def handle_call({:get, key}, _, state) do
data =
case File.read(file_name(state, key)) do
{:ok, contents} -> :erlang.binary_to_term(contents)
_ -> nil
end

{:reply, data, state}
end

defp via_tuple(worker_id), do: Todo.ProcessRegistry.via_tuple({__MODULE__, worker_id})
defp file_name(db_folder, key), do: Path.join(db_folder, to_string(key))
end

+ 83
- 0
ch10/todo_metrics/lib/todo/list.ex Parādīt failu

@@ -0,0 +1,83 @@
defmodule Todo.List do

defstruct auto_id: 1, entries: %{}

def new(entries \\ []), do: add_entries(%Todo.List{}, entries)

def add_entries(todo_list, entries) do
Enum.reduce(entries, todo_list, &add_entry(&2, &1))
end

def add_entry(todo_list, entry) do
entry = Map.put(entry, :id, todo_list.auto_id)
new_entries = Map.put(todo_list.entries, todo_list.auto_id, entry)
%Todo.List{todo_list |
entries: new_entries,
auto_id: todo_list.auto_id + 1
}
end

def entries(todo_list, date) do
todo_list.entries
|> Stream.filter(fn {_, entry} -> entry.date == date end)
|> Enum.map(fn {_, entry} -> entry end)
end

def entries(todo_list) do
Map.values(todo_list.entries)
end

def update_entry(todo_list, entry_id, updater_fun) do
case Map.fetch(todo_list.entries, entry_id) do
:error ->
todo_list

{:ok, old_entry} ->
old_entry_id = old_entry.id
new_entry = %{id: ^old_entry_id} = updater_fun.(old_entry)
new_entries = Map.put(todo_list.entries, new_entry.id, new_entry)
%Todo.List{todo_list | entries: new_entries}
end
end

def update_entry(todo_list, %{} = new_entry) do
update_entry(todo_list, new_entry.id, fn _ -> new_entry end)
end

def delete_entry(todo_list, %{id: entry_id} = _entry) do
delete_entry(todo_list, entry_id)
end

def delete_entry(todo_list, entry_id) do
new_entries = Map.delete(todo_list.entries, entry_id)
%Todo.List{todo_list | entries: new_entries}
end
end

defmodule Todo.List.CsvImporter do

def import!(file_path) do
file_path
|> File.stream!()
|> Stream.map(&String.replace(&1, "\n", ""))
|> Stream.map(&parse_entry_fields/1)
|> Stream.map(&create_entry/1)
|> Todo.List.new()
end

defp parse_entry_fields(entry_line) do
[date_line, title] = String.split(entry_line, ",")

date = date_line
|> String.split("/")
|> Enum.map(&String.to_integer/1)
|> List.to_tuple()

{date, title}
end

defp create_entry({date, title} = _entry_tuple) do
{:ok, date} = Date.from_erl(date)
%{date: date, title: title}
end
end

+ 21
- 0
ch10/todo_metrics/lib/todo/metrics.ex Parādīt failu

@@ -0,0 +1,21 @@
defmodule Todo.Metrics do
use Task

def start_link(_) do
IO.puts("Starting metrics")
Task.start_link(&loop/0)
end

defp loop() do
Process.sleep(:timer.seconds(10))
IO.inspect(collect_metrics())
loop()
end

defp collect_metrics() do
[
memory_usage: :erlang.memory(:total),
process_count: :erlang.system_info(:process_count)
]
end
end

+ 14
- 0
ch10/todo_metrics/lib/todo/process_registry.ex Parādīt failu

@@ -0,0 +1,14 @@
defmodule Todo.ProcessRegistry do

def start_link(), do: Registry.start_link(keys: :unique, name: __MODULE__)

def via_tuple(key), do: {:via, Registry, {__MODULE__, key}}

def child_spec(_) do
Supervisor.child_spec(
Registry,
id: __MODULE__,
start: {__MODULE__, :start_link, []}
)
end
end

+ 93
- 0
ch10/todo_metrics/lib/todo/server.ex Parādīt failu

@@ -0,0 +1,93 @@
defmodule Todo.Server do
use GenServer, restart: :temporary

def start_link(name, entries \\ []) do
IO.puts("Starting to-do server for #{name}")
GenServer.start(__MODULE__, {name, entries}, name: via_tuple(name))
end

def entries(todo_server, date), do: GenServer.call(todo_server, {:entries, date})

def entries(todo_server), do: GenServer.call(todo_server, :entries)

def add_entries(todo_server, new_entries) do
GenServer.cast(todo_server, {:add_entries, new_entries})
end

def add_entry(todo_server, new_entry) do
GenServer.cast(todo_server, {:add_entry, new_entry})
end

def update_entry(todo_server, entry_id, updater_fun) do
GenServer.cast(todo_server, {:update_entry, entry_id, updater_fun})
end

def update_entry(todo_server, %{} = new_entry) do
GenServer.cast(todo_server, {:update_entry, new_entry})
end

def delete_entry(todo_server, %{id: _} = entry) do
GenServer.cast(todo_server, {:delete_entry, entry})
end

def delete_entry(todo_server, entry_id) do
GenServer.cast(todo_server, {:delete_entry, entry_id})
end

@impl GenServer
def init({name, entries}), do: {:ok, {name, Todo.Database.get(name) || Todo.List.new(entries)}}

@impl GenServer
def handle_call({:entries, date}, _from, {_, todo_list} = state) do
{:reply, Todo.List.entries(todo_list, date), state}
end

@impl GenServer
def handle_call(:entries, _from, {_, todo_list} = state) do
{:reply, Todo.List.entries(todo_list), state}
end

@impl GenServer
def handle_cast({:add_entries, new_entries}, {name, todo_list}) do
new_list = Todo.List.add_entries(todo_list, new_entries)
Todo.Database.store(name, new_list)
{:noreply, {name, new_list}}
end

@impl GenServer
def handle_cast({:add_entry, new_entry}, {name, todo_list}) do
new_list = Todo.List.add_entry(todo_list, new_entry)
Todo.Database.store(name, new_list)
{:noreply, {name, new_list}}
end

@impl GenServer
def handle_cast({:update_entry, entry_id, updater_fun}, {name, todo_list}) do
new_list = Todo.List.update_entry(todo_list, entry_id, updater_fun)
Todo.Database.store(name, new_list)
{:noreply, {name, new_list}}
end

@impl GenServer
def handle_cast({:update_entry, %{} = new_entry}, {name, todo_list}) do
new_list = Todo.List.update_entry(todo_list, new_entry)
Todo.Database.store(name, new_list)
{:noreply, {name, new_list}}
end

@impl GenServer
def handle_cast({:delete_entry, %{} = entry}, {name, todo_list}) do
new_list = Todo.List.delete_entry(todo_list, entry)
Todo.Database.store(name, new_list)
{:noreply, {name, new_list}}
end

@impl GenServer
def handle_cast({:delete_entry, entry_id}, {name, todo_list}) do
new_list = Todo.List.delete_entry(todo_list, entry_id)
Todo.Database.store(name, new_list)
{:noreply, {name, new_list}}
end

defp via_tuple(name), do: Todo.ProcessRegistry.via_tuple({__MODULE__, name})
end

+ 14
- 0
ch10/todo_metrics/lib/todo/system.ex Parādīt failu

@@ -0,0 +1,14 @@
defmodule Todo.System do

def start_link() do
Supervisor.start_link(
[
Todo.Metrics,
Todo.ProcessRegistry,
Todo.Database,
Todo.Cache
],
strategy: :one_for_one
)
end
end

+ 28
- 0
ch10/todo_metrics/mix.exs Parādīt failu

@@ -0,0 +1,28 @@
defmodule Todo.MixProject do
use Mix.Project

def project do
[
app: :todo,
version: "0.1.0",
elixir: "~> 1.8",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end

# Run "mix help compile.app" to learn about applications.
def application do
[
extra_applications: [:logger]
]
end

# Run "mix help deps" to learn about dependencies.
defp deps do
[
# {:dep_from_hexpm, "~> 0.3.0"},
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
]
end
end

+ 1
- 0
ch10/todo_metrics/test/test_helper.exs Parādīt failu

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

+ 20
- 0
ch10/todo_metrics/test/todo_cache_test.exs Parādīt failu

@@ -0,0 +1,20 @@
defmodule TodoCacheTest do
use ExUnit.Case

test "server_process" do
{:ok, cache} = Todo.Cache.start()
jessie_pid = Todo.Cache.server_process(cache, "jessie")

assert jessie_pid != Todo.Cache.server_process(cache, "daria")
assert jessie_pid == Todo.Cache.server_process(cache, "jessie")
end

test "to-do operations" do
{:ok, cache} = Todo.Cache.start()
daria = Todo.Cache.server_process(cache, "daria")
Todo.Server.add_entry(daria, %{date: ~D[2019-01-01], title: "Dentist"})
entries = Todo.Server.entries(daria, ~D[2019-01-01])

assert [%{date: ~D[2019-01-01], title: "Dentist"}] = entries
end
end

Notiek ielāde…
Atcelt
Saglabāt