Browse Source

Add elixir article

master
Wilfried OLLIVIER 2 months ago
parent
commit
dab639e246
1 changed files with 301 additions and 0 deletions
  1. 301
    0
      content/post/04-production-iexing.md

+ 301
- 0
content/post/04-production-iexing.md View File

@@ -0,0 +1,301 @@
1
+---
2
+title: "How to debug a GenServer like Sherlock using IEx"
3
+subtitle: "A Sherlock Holmes approved guide 🕵"
4
+date: 2019-11-08
5
+draft: false
6
+tags: [dev, programming, elixir, production, debug]
7
+---
8
+
9
+# Elixir, the path to functional programming ⚗️
10
+
11
+With my [**Rust** rediscovery](https://blog.papey.fr/post/03-bites-the-rust/)
12
+a lot of _functional programming_ concepts became less obscure. In order to
13
+go deeper into this paradigm I chose _Elixir_ a **powerful** general purpose, _functional
14
+programming_ language, compiled and executed inside the _Erlang_ virtual
15
+machine (**BEAM**).
16
+
17
+But why _Elixir_, if _Erlang_ exists ? Because :
18
+
19
+    Elixir, is Erlang with underpants - Athoune
20
+
21
+Unlike _Erlang_, the _Elixir_ syntax is clear and elegant. The language comes
22
+with everything you need, included : interactive shell, deps tooling, building tooling
23
+and more…
24
+
25
+Even if _José Valim_, the _Elixir_ creator was a core _Ruby_ dev, _Elixir_ is
26
+completely different since this is a _functional programming_ language. No
27
+mutation, no inheritance, no classes, only **pure** functions. Pray your only
28
+true god, the pipe operator `|>` operator, used to compose functions.
29
+
30
+Now, I know, the only thing you want is code and examples, so let's take a
31
+quick look at some basic _Elixir_ stuff using the interactive shell **IEx**.
32
+
33
+A simple addition :
34
+
35
+{{< highlight exs >}}
36
+iex(1)> 2 + 2
37
+4
38
+{{< /highlight >}}
39
+
40
+A simple addition, inside a list
41
+
42
+{{< highlight exs >}}
43
+iex(1)> 2 + 2
44
+iex(5)> list = [1,2,3,4][1, 2, 3, 4]
45
+iex(6)> Enum.map(list, fn e -> e + 1 end)
46
+[2, 3, 4, 5]
47
+{{< /highlight >}}
48
+
49
+List splitting (head and tail)
50
+
51
+{{< highlight exs >}}
52
+iex(7)> [head | tail] = list
53
+[1, 2, 3, 4]
54
+iex(8)> head
55
+1
56
+iex(9)> tail
57
+[2, 3, 4]
58
+{{< /highlight >}}
59
+
60
+Another important aspect of Elixir is _pattern matching_ used to match
61
+values, data structures and much more, let's try it :
62
+
63
+{{< highlight exs >}}
64
+iex(11)> x = {:this, :is, :a, :test}
65
+{:this, :is, :a, :test}
66
+iex(12)> {a, b, c, d} = x
67
+{:this, :is, :a, :test}
68
+iex(13)> a
69
+:this
70
+iex(14)> b
71
+:is
72
+iex(15)> c
73
+:a
74
+iex(16)> d
75
+:test
76
+{{< /highlight >}}
77
+
78
+Using _pattern matching_ you can assign values but also destructure data to
79
+simplify interaction with it.
80
+
81
+Feels the hype growing ? Nice !
82
+
83
+As usual, I like a real project to experiment on something. In the _Elixir_
84
+case, I started a Discord bot project called
85
+[o2m](https://github.com/papey/o2m). The main idea of this bot is to send
86
+alert messages on a specific channel when a new episode from a selected
87
+podcast is available. Today, I was struggling with a bug on a production
88
+instance of _o2m_. The last episode was not fetched correctly and the action
89
+that write a _"There is a new episode_" message was not triggered correctly.
90
+To debug and inspect the current state of the application I used IEx **in
91
+production**.
92
+
93
+# Base ingredient of a good Elixir : Mix 🧙
94
+
95
+Combined with IEx, there is _Mix_. Shipped with _Elixir_, this a build tool
96
+used for the following application related tasks :
97
+
98
+- creating
99
+- setting up needed deps
100
+- testing
101
+- compiling
102
+
103
+The combo killer here is to start IEx inside the _Mix_ project, in order to have all
104
+the dependencies imported inside the interactive shell
105
+
106
+{{< highlight exs >}}
107
+iex -S mix
108
+{{< /highlight >}}
109
+
110
+After that, there is a lot of useful commands like `recompile` to refresh and
111
+recompile new code **while your code is running**. Yes, this is something
112
+that _Elixir_ do by default, hot code reloading.
113
+
114
+When your code is ready, you want to deploy it. With _Mix_ the standard way
115
+is to used the `release` command
116
+
117
+{{< highlight sh >}}
118
+MIX_ENV=production mix release
119
+{{< /highlight >}}
120
+
121
+This will create a precompile and packaged unit, with runtime included. With
122
+this, you do not have to install _Erlang_ or _Elixir_ on your production
123
+server. Boom.
124
+
125
+If you look carefully, there is an interesting message and the end of the command output :
126
+
127
+{{< highlight sh >}}
128
+Release created at \_build/prod/rel/o2m!
129
+
130
+    # To start your system
131
+    _build/prod/rel/o2m/bin/o2m start
132
+
133
+Once the release is running:
134
+
135
+    # To connect to it remotely
136
+    _build/prod/rel/o2m/bin/o2m remote
137
+
138
+    # To stop it gracefully (you may also send SIGINT/SIGTERM)
139
+    _build/prod/rel/o2m/bin/o2m stop
140
+
141
+To list all commands:
142
+
143
+    _build/prod/rel/o2m/bin/o2m
144
+
145
+{{< /highlight >}}
146
+
147
+Particularly,
148
+
149
+    To connect to it remotely
150
+
151
+This means I can have access to an interactive shell on production while my
152
+code is running, hooray 🎆
153
+
154
+# Sherlocking a GenServer 🔎
155
+
156
+Did you read the title ? We are talking about _GenServer_ here ! So _what the fuck_ is this ?
157
+
158
+Taken from the [documentation](https://hexdocs.pm/elixir/GenServer.html) :
159
+
160
+    A GenServer is a process like any other Elixir process and it can be used
161
+    to keep state, execute code asynchronously and so on. The advantage of using
162
+    a generic server process (GenServer) implemented using this module is that it
163
+    will have a standard set of interface functions and include functionality for
164
+    tracing and error reporting. It will also fit into a supervision tree.
165
+
166
+In _Elixir_, this is the default and common module used to implement client-server behaviors.
167
+
168
+Here is the example from the documentation
169
+
170
+{{< highlight elixir >}}
171
+defmodule Stack do
172
+use GenServer
173
+
174
+    # Callbacks
175
+
176
+    @impl true
177
+    def init(stack) do
178
+    {:ok, stack}
179
+    end
180
+
181
+    @impl true
182
+    def handle_call(:pop, \_from, [head | tail]) do
183
+    {:reply, head, tail}
184
+    end
185
+
186
+    @impl true
187
+    def handle_cast({:push, element}, state) do
188
+    {:noreply, [element | state]}
189
+    end
190
+
191
+end
192
+{{< / highlight >}}
193
+
194
+Basically, this is a data structure with a state responding to triggers using
195
+handlers that update or retrieve the current state
196
+
197
+To launch and interact with the server, in IEx :
198
+
199
+{{< highlight exs >}}
200
+
201
+iex(41)> {:ok, pid} = GenServer.start_link(Stack, [:hello])
202
+
203
+iex(42)> GenServer.call(pid, :pop)
204
+:hello
205
+
206
+iex(43)> GenServer.cast(pid, {:push, :world})
207
+:ok
208
+
209
+iex(44)> GenServer.call(pid, :pop)
210
+:world
211
+{{< /highlight >}}
212
+
213
+What if, I want to access the current state, of the GenServer ?
214
+
215
+In _Erlang_, there is the `sys` module with the `get_state/2`
216
+[function](http://erlang.org/doc/man/sys.html#get_state-2) available
217
+
218
+But, this is _Erlang_, and we use _Elixir_ ! We're doomed ? No ! Because
219
+everything available in _Erlang_ is available inside _Elixir_ using the `:`
220
+operator.
221
+
222
+{{< highlight erl >}}
223
+iex(18)> :io.format("Hello World~n")
224
+Hello World
225
+:ok
226
+{{< /highlight >}}
227
+
228
+So, with our `sys` example :
229
+
230
+{{< highlight exs >}}
231
+iex(19)> :sys.get_state(pid)
232
+[:!, :world]
233
+{{< /highlight >}}
234
+
235
+Nice ! Now we can get state of a GenServer using the PID !
236
+
237
+Now, let's move from local machine to prod. In this case, processus are
238
+handled by a **Supervisor** but first things first, we want a IEx shell
239
+inside our running app. In order to illustrate this last part, I will be
240
+using one of my prod instances of _o2m_
241
+
242
+{{< highlight sh >}}
243
+o2m@16557a733b59:/opt/o2m\$ ./prod/rel/o2m/bin/o2m remote
244
+Erlang/OTP 22 [erts-10.5.3][source] [64-bit][smp:2:2] [ds:2:2:10][async-threads:1] [hipe]
245
+
246
+Interactive Elixir (1.9.2) - press Ctrl+C to exit (type h() ENTER for help)
247
+iex(o2m@16557a733b59)1>
248
+{{< /highlight >}}
249
+
250
+Ok but, what is the _pid_ of the _GenServer_ I want to inspect ? I don't know !
251
+Here the first solution could be outputting _pid_ to _stdout_ but, imagine that
252
+our application kills and restart GenServer, on demand or our application is
253
+flooding _stdout_ because of some errors, this is not a viable solution.
254
+
255
+First, we could list all processus,
256
+
257
+{{< highlight exs >}}
258
+iex(o2m@16557a733b59)3> Supervisor.which_children(O2M.Supervisor)
259
+[
260
+{O2M, #PID<0.2368.0>, :worker, [O2M]},
261
+{"jobs-https://feed.ausha.co/bj5li17QPONy", #PID<0.2365.0>, :worker, [Jobs]},
262
+{"jobs-https://feed.ausha.co/yJeEUGlLVq0o", #PID<0.2362.0>, :worker, [Jobs]},
263
+{"jobs-https://anchor.fm/s/b3b7468/podcast/rss", #PID<0.2358.0>, :worker,
264
+[Jobs]}
265
+]
266
+{{< /highlight >}}
267
+
268
+Where `O2M.Supervisor` is the dedicated _Supervisor_ of my application
269
+
270
+Now I can identify the GenServer I want to inspect, let's take the
271
+`jobs-https://anchor.fm/s/b3b7468/podcast/rss` one, with associated pid
272
+`0.2358.0` (the last element of the list, here), here comes the fun
273
+
274
+{{< highlight exs >}}
275
+iex(o2m@16557a733b59)13> {_, pid, _, \_} = Supervisor.which_children(O2M.Supervisor) |> List.last
276
+{"jobs-https://anchor.fm/s/b3b7468/podcast/rss", #PID<0.2358.0>, :worker,
277
+[Jobs]}
278
+iex(o2m@16557a733b59)14> pid
279
+#PID<0.2358.0>
280
+{{< /highlight >}}
281
+
282
+Then, I can use the pid value to get _GenServer_ state and inspect it to see
283
+if everything is ok :
284
+
285
+{{< highlight exs >}}
286
+iex(o2m@16557a733b59)15> :sys.get_state(pid)
287
+{"https://anchor.fm/s/b3b7468/podcast/rss",
288
+%{
289
+date: "Mon, 04 Nov 2019 09:00:00 GMT",
290
+show: "Harry Cover, le podcast des meilleures reprises",
291
+title: "Yes we can work it out !",
292
+url: "https://anchor.fm/leotot8/episodes/Yes-we-can-work-it-out-e8n4d3"
293
+}}
294
+{{< /highlight >}}
295
+
296
+How ! Impressive ! It was a little bit Sherlock Holmes oriented debugging but
297
+that was fun. Keep in mind that this is a really simple operation here, from
298
+IEx everything is possible from starting new GenServers to modify state of a
299
+specific one and much more.
300
+
301
+I said it at the beginning, **Elixir is powerful**.

Loading…
Cancel
Save