Packages

Set of useful functions to catch leaked processes in runtime

Current section

2 Versions

Jump to

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