Current section
2 Versions
Jump to
Current section
2 Versions
Compare versions
4
files changed
+74
additions
-11
deletions
| @@ -14,10 +14,11 @@ end | |
| 14 14 | |
| 15 15 | ## Usage |
| 16 16 | |
| 17 | - ### !!!WARNING!!! Check amount of processes on your production node! |
| 18 | - ### this tool uses Process.list() and Enum, so IT CAN SIGNIFICANTLY SLOW DOWN YOUR APPLICATION |
| 19 | - ### if you have a lot of processes, 10k of shouldn't create a problem |
| 20 | - ### but if you have more - run it on your own risk. |
| 17 | + ### !!!WARNING!!! |
| 18 | + Check amount of processes on your production node! |
| 19 | + this tool uses Process.list() and Enum, so IT CAN SIGNIFICANTLY SLOW DOWN YOUR APPLICATION |
| 20 | + if you have a lot of processes, 10k of shouldn't create a problem |
| 21 | + but if you have more - run it on your own risk. |
| 21 22 | |
| 22 23 | To get list of unlinked processes: |
| 23 24 | ```elixir |
| @@ -10,4 +10,4 @@ | |
| 10 10 | {<<"links">>,[{<<"GitHub">>,<<"https://github.com/elixir-ecto/postgrex">>}]}. |
| 11 11 | {<<"name">>,<<"ghostbuster">>}. |
| 12 12 | {<<"requirements">>,[]}. |
| 13 | - {<<"version">>,<<"0.1.0">>}. |
| 13 | + {<<"version">>,<<"0.1.1">>}. |
| @@ -9,7 +9,7 @@ defmodule Ghostbuster do | |
| 9 9 | @unlinked_info_keys [:links, :monitors] |
| 10 10 | |
| 11 11 | @doc """ |
| 12 | - `get_top_init_calls` returns sorted list of initial call functions with number of processes |
| 12 | + `get_top_init_calls/0` returns sorted list of initial call functions with number of processes |
| 13 13 | |
| 14 14 | ## Example: |
| 15 15 | |
| @@ -25,14 +25,36 @@ defmodule Ghostbuster do | |
| 25 25 | @spec get_top_init_calls() :: list() |
| 26 26 | def get_top_init_calls do |
| 27 27 | Process.list() |
| 28 | + |> get_top_init_calls() |
| 29 | + end |
| 30 | + |
| 31 | + @doc """ |
| 32 | + `get_top_init_calls/1` returns sorted list of initial call functions with number of processes |
| 33 | + for given list of processes |
| 34 | + |
| 35 | + ## Example: |
| 36 | + |
| 37 | + iex> Ghostbuster.get_top_init_calls(Process.list()) |
| 38 | + [ |
| 39 | + {{:application_master, :init, 4}, 5}, |
| 40 | + {{:application_master, :start_it, 4}, 5}, |
| 41 | + {{:supervisor, Supervisor.Default, 1}, 4}, |
| 42 | + {{:gen_event, :init_it, 6}, 3}, |
| 43 | + {{:erts_dirty_process_signal_handler, :start, 0}, 3} |
| 44 | + ] |
| 45 | + """ |
| 46 | + @spec get_top_init_calls(list(pid)) :: list() |
| 47 | + def get_top_init_calls(process_list) do |
| 48 | + process_list |
| 28 49 | |> Enum.map(&get_info(&1, @initial_call_info_keys)) |
| 50 | + |> Enum.filter(&is_alive?/1) |
| 29 51 | |> Enum.reduce(%{}, &count_initial_calls/2) |
| 30 52 | |> Enum.sort(fn {_k1, c1}, {_k2, c2} -> c1 > c2 end) |
| 31 53 | |> Enum.take(5) |
| 32 54 | end |
| 33 55 | |
| 34 56 | @doc """ |
| 35 | - `get_pids_by_init_call` returns list of pids by given {module, function, args} tuple |
| 57 | + `get_pids_by_init_call/1` returns list of pids by given {module, function, args} tuple |
| 36 58 | |
| 37 59 | ## Example: |
| 38 60 | |
| @@ -42,13 +64,29 @@ defmodule Ghostbuster do | |
| 42 64 | @spec get_pids_by_init_call({atom, atom, integer}) :: list(pid) |
| 43 65 | def get_pids_by_init_call(function) do |
| 44 66 | Process.list() |
| 67 | + |> get_pids_by_init_call(function) |
| 68 | + end |
| 69 | + |
| 70 | + @doc """ |
| 71 | + `get_pids_by_init_call/2` returns list of pids by given {module, function, args} tuple |
| 72 | + for given list of processes |
| 73 | + |
| 74 | + ## Example: |
| 75 | + |
| 76 | + iex> Ghostbuster.get_pids_by_init_call(Process.list(), {:application_master, :init, 4}) |
| 77 | + [:erlang.list_to_pid('<0.45.0>')] |
| 78 | + """ |
| 79 | + @spec get_pids_by_init_call(list(pid), {atom, atom, integer}) :: list(pid) |
| 80 | + def get_pids_by_init_call(process_list, function) do |
| 81 | + process_list |
| 45 82 | |> Enum.map(&get_info(&1, @initial_call_info_keys)) |
| 83 | + |> Enum.filter(&is_alive?/1) |
| 46 84 | |> Enum.filter(&initial_call_eq?(&1, function)) |
| 47 85 | |> Enum.map(& &1[:pid]) |
| 48 86 | end |
| 49 87 | |
| 50 88 | @doc """ |
| 51 | - `get_unlinked_pids` returns list of pids which don't have links or monitors |
| 89 | + `get_unlinked_pids/0` returns list of pids which don't have links or monitors |
| 52 90 | |
| 53 91 | ## Example: |
| 54 92 | |
| @@ -58,13 +96,29 @@ defmodule Ghostbuster do | |
| 58 96 | @spec get_unlinked_pids() :: list(pid) |
| 59 97 | def get_unlinked_pids do |
| 60 98 | Process.list() |
| 99 | + |> get_unlinked_pids() |
| 100 | + end |
| 101 | + |
| 102 | + @doc """ |
| 103 | + `get_unlinked_pids/1` returns list of pids which don't have links or monitors |
| 104 | + for given list of processes |
| 105 | + |
| 106 | + ## Example: |
| 107 | + |
| 108 | + iex> Ghostbuster.get_unlinked_pids(Process.list()) |
| 109 | + [:erlang.list_to_pid('<0.45.0>')] |
| 110 | + """ |
| 111 | + @spec get_unlinked_pids(list(pid)) :: list(pid) |
| 112 | + def get_unlinked_pids(process_list) do |
| 113 | + process_list |
| 61 114 | |> Enum.map(&get_info(&1, @unlinked_info_keys)) |
| 115 | + |> Enum.filter(&is_alive?/1) |
| 62 116 | |> Enum.filter(&no_links?/1) |
| 63 117 | |> Enum.map(& &1[:pid]) |
| 64 118 | end |
| 65 119 | |
| 66 120 | @doc """ |
| 67 | - `get_init_call` returns {module, function, args} tuple for given pid |
| 121 | + `get_init_call/1` returns {module, function, args} tuple for given pid |
| 68 122 | |
| 69 123 | ## Example: |
| 70 124 | |
| @@ -81,9 +135,17 @@ defmodule Ghostbuster do | |
| 81 135 | defp get_info(pid, keys) do |
| 82 136 | pid |
| 83 137 | |> Process.info(keys) |
| 138 | + # in case if process died before the info request Proces.info/2 would return nil value |
| 139 | + # and this can happen because there is short time gap between calling Process.list/0 and Process.info/2 |
| 140 | + # so for that reason we don't want to break the whole pipeline with error, instead we can set |
| 141 | + # default key "died" and filter those processes later |
| 142 | + |> Kernel.||(died: true) |
| 84 143 | |> put_in([:pid], pid) |
| 85 144 | end |
| 86 145 | |
| 146 | + defp is_alive?(pid: _, died: true), do: false |
| 147 | + defp is_alive?(_), do: true |
| 148 | + |
| 87 149 | defp count_initial_calls(info, acc) do |
| 88 150 | ic = get_initial_call(info) |
| 89 151 | Map.update(acc, ic, 1, &(&1 + 1)) |
| @@ -1,7 +1,7 @@ | |
| 1 1 | defmodule Ghostbuster.MixProject do |
| 2 2 | use Mix.Project |
| 3 3 | |
| 4 | - @version "0.1.0" |
| 4 | + @version "0.1.1" |
| 5 5 | @description "Set of useful functions to catch leaked processes in runtime" |
| 6 6 | |
| 7 7 | def project do |
| @@ -14,7 +14,7 @@ defmodule Ghostbuster.MixProject do | |
| 14 14 | package: package(), |
| 15 15 | docs: [ |
| 16 16 | extras: [ |
| 17 | - "README.md", |
| 17 | + "README.md" |
| 18 18 | ], |
| 19 19 | main: "readme", |
| 20 20 | source_ref: "v#{@version}", |