<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[p3ld3v blog]]></title><description><![CDATA[p3ld3v blog]]></description><link>https://blog.p3ld3v.dev</link><generator>RSS for Node</generator><lastBuildDate>Thu, 15 Oct 2026 13:28:10 GMT</lastBuildDate><atom:link href="https://blog.p3ld3v.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Binary Patching with Radar2]]></title><description><![CDATA[Abstract
Binary patching is a crucial tool in software analysis and reverse engineering. This article provides an overview of the binary patching process using the Radare2 utility. The article begins ]]></description><link>https://blog.p3ld3v.dev/binary-patching-with-radar2</link><guid isPermaLink="true">https://blog.p3ld3v.dev/binary-patching-with-radar2</guid><dc:creator><![CDATA[Pavel Durov]]></dc:creator><pubDate>Thu, 02 Jul 2026 16:10:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/63440fc03c49cd0552fb1067/83597d8c-4cb7-42da-9695-186d383ede16.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Abstract</h2>
<p>Binary patching is a crucial tool in software analysis and reverse engineering. This article provides an overview of the binary patching process using the Radare2 utility. The article begins by introducing the concept of binary patching.</p>
<p>Key concepts covered in the article include:</p>
<ul>
<li><p>Understanding binary analysis fundamentals</p>
</li>
<li><p>Navigating and seeking specific memory addresses within binaries</p>
</li>
<li><p>Writing assembly instructions to modify binary behaviour</p>
</li>
</ul>
<p>Throughout the article, step-by-step examples and commands are provided to guide readers through the process of binary patching using Radare2. By the end of the article, readers will gain a fundamental understanding of how to navigate Radare2 tool and how to perform basic binary patching.</p>
<h2>Introduction</h2>
<p>Binary patching is a process of modifying compiled binary executable files to change their behaviour or fix issues without access to the source code.</p>
<p>This technique is commonly used in software development, reverse engineering, and cybersecurity for various purposes such as bug fixing, feature enhancement, and vulnerability exploitation or mitigation.</p>
<p>In our examples, we will use a simple C application, compile it, run it and later on change its binary using the Radar2 utility to modify its behaviour.</p>
<p>Note: Assembly language and instruction codes are specific to CPU architecture. In the examples provided, we will be focusing on the Intel x86 architecture.</p>
<h2>Binary Analysis</h2>
<p>Binary analysis is a process of examining and understanding compiled binary code (machine code) without access to the original source code. This analysis includes disassembling, decompiling, and analyzing the binary behaviour. In our case, we will know exactly what the program intends to do, since we're going to be the ones who write it. So the binary analysis will be straightforward.</p>
<h2>Wallet application</h2>
<p>We're going to implement a simple wallet sample app in C where the wallet is unlocked only if the user enters the correct numeric password (1234 in our case).</p>
<pre><code class="language-cpp">// file: wallet.c
#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;

int main(int argc, char *argv[]) {
  int num = atoi(argv[1]);
  if (num == 1234) {
    printf("Unlocked!\n");
    return 0;
  }
  printf("Wrong passcode! This incident will be reported!\n");
  return 1;
}
</code></pre>
<p>Le's compile it:</p>
<pre><code class="language-bash">$ clang wallet.c -o ./wallet
</code></pre>
<p>Lets see what it does.</p>
<p>If we give it any number other than 1234 we get an error message and error status code:</p>
<pre><code class="language-shell">$ ./wallet 1
Wrong! This incident will be reported!
$ echo $?
1
</code></pre>
<p>If we guess the secret number we get success exit code and a message printed:</p>
<pre><code class="language-shell">$ ./wallet 1234
Unlocked!
$ echo $?
0
</code></pre>
<h2>Patching the binary</h2>
<p>Next, we're going to use Radar2 tool to do the binary patching.</p>
<p>Open it with Radar2:</p>
<pre><code class="language-shell">$ r2 -w ./wallet # -w stands for write mode
[0x00001060]&gt; aa
[0x00001060]&gt; s main # seek main function
[0x00001150]&gt; pdf
            ; DATA XREF from entry0 @ 0x1078(r)
┌ 107: int main (int argc, char **argv);
│           ; arg int argc @ rdi
│           ; arg char **argv @ rsi
│           ; var int64_t var_4h @ rbp-0x4
│           ; var int64_t var_8h @ rbp-0x8
│           ; var int64_t var_10h @ rbp-0x10
│           ; var int64_t var_14h @ rbp-0x14
│           0x00001150      55             push rbp
│           0x00001151      4889e5         mov rbp, rsp
│           0x00001154      4883ec20       sub rsp, 0x20
│           0x00001158      c745fc0000..   mov dword [var_4h], 0
│           0x0000115f      897df8         mov dword [var_8h], edi     ; argc
│           0x00001162      488975f0       mov qword [var_10h], rsi    ; argv
│           0x00001166      488b45f0       mov rax, qword [var_10h]
│           0x0000116a      488b7808       mov rdi, qword [rax + 8]
│           0x0000116e      e8cdfeffff     call sym.imp.atoi           ; int atoi(const char *str)
│           0x00001173      8945ec         mov dword [var_14h], eax
│           0x00001176      817decd204..   cmp dword [var_14h], 0x4d2
│       ┌─&lt; 0x0000117d      0f851a000000   jne 0x119d
│       │   0x00001183      488d3d7a0e..   lea rdi, str.Unlocked__n    ; 0x2004 ; "Unlocked!\n"
│       │   0x0000118a      b000           mov al, 0
│       │   0x0000118c      e89ffeffff     call sym.imp.printf         ; int printf(const char *format)
│       │   0x00001191      c745fc0000..   mov dword [var_4h], 0
│      ┌──&lt; 0x00001198      e915000000     jmp 0x11b2
│      │└─&gt; 0x0000119d      488d3d6b0e..   lea rdi, str.Wrong_passcode__This_incident_will_be_reported__n ; 0x200f ; "Wrong passcode! This incident will be reported!\n"
│      │    0x000011a4      b000           mov al, 0
│      │    0x000011a6      e885feffff     call sym.imp.printf         ; int printf(const char *format)
│      │    0x000011ab      c745fc0100..   mov dword [var_4h], 1
│      │    ; CODE XREF from main @ 0x1198(x)
│      └──&gt; 0x000011b2      8b45fc         mov eax, dword [var_4h]
│           0x000011b5      4883c420       add rsp, 0x20
│           0x000011b9      5d             pop rbp
└           0x000011ba      c3             ret
</code></pre>
<p>Let's break down the provided assembly code and understand its relation to C code:</p>
<p>There's a bunch of instructions related to args and string convertion to int value. But we actually care about the if statement here.</p>
<p>In our code:</p>
<pre><code class="language-c"> if (num == 1234) {
  ...
 }
</code></pre>
<p>Is mached to:</p>
<pre><code class="language-asm">cmp dword [var_14h], 0x4d2
jne 0x119d
</code></pre>
<p>The instruction <code>jne</code> will jump to <code>0x119d</code> which is <code>0x0000119d</code> if the <code>cmp</code> returns false, which will print "Wrong passcode...". What we want is to remove this condition or change it to be always true. One way of achieving this is by overriding it with a noop (no operation) instruction, and that's exactly what we're going to do next.</p>
<p>0x90 is a NOP instruction on Intel x86 CPU family. <a href="https://en.wikipedia.org/wiki/NOP%5C_(code)">https://en.wikipedia.org/wiki/NOP\_(code)</a>.</p>
<pre><code class="language-shell">[0x00001150]&gt; s 0x0000117d # navigate to jne
[0x0000117d]&gt; px # print instruction as this location
- offset -  7D7E 7F80 8182 8384 8586 8788 898A 8B8C  DEF0123456789ABC
0x0000117d  0f85 1a00 0000 488d 3d7a 0e00 00b0 00e8  ......H.=z......
...
[0x000011de]&gt; wx 909090909090 # override 6 bytes with noop instructions
</code></pre>
<p>Let's review the amended asm again:</p>
<pre><code class="language-shell">[0x00001150]&gt; pdf
            ; DATA XREF from entry0 @ 0x1078(r)
┌ 86: int main (int argc, char **argv);
│           ; arg int argc @ rdi
│           ; arg char **argv @ rsi
│           ; var int64_t var_4h @ rbp-0x4
│           ; var int64_t var_8h @ rbp-0x8
│           ; var char **var_10h @ rbp-0x10
│           ; var uint32_t var_14h @ rbp-0x14
│           0x00001150      55             push rbp
│           0x00001151      4889e5         mov rbp, rsp
│           0x00001154      4883ec20       sub rsp, 0x20
│           0x00001158      c745fc0000..   mov dword [var_4h], 0
│           0x0000115f      897df8         mov dword [var_8h], edi     ; argc
│           0x00001162      488975f0       mov qword [var_10h], rsi    ; argv
│           0x00001166      488b45f0       mov rax, qword [var_10h]
│           0x0000116a      488b7808       mov rdi, qword [rax + 8]    ; const char *str
│           0x0000116e      e8cdfeffff     call sym.imp.atoi           ; int atoi(const char *str)
│           0x00001173      8945ec         mov dword [var_14h], eax
│           0x00001176      817decd204..   cmp dword [var_14h], 0x4d2
│           0x0000117d      90             nop
│           0x0000117e      90             nop
│           0x0000117f      90             nop
│           0x00001180      90             nop
│           0x00001181      90             nop
│           0x00001182      90             nop
│           0x00001183      488d3d7a0e..   lea rdi, str.Unlocked__n    ; 0x2004 ; "Unlocked!\n" ; const char *format
│           0x0000118a      b000           mov al, 0
│           0x0000118c      e89ffeffff     call sym.imp.printf         ; int printf(const char *format)
│           0x00001191      c745fc0000..   mov dword [var_4h], 0
│       ┌─&lt; 0x00001198      e915000000     jmp 0x11b2
..
│       │   ; CODE XREF from main @ 0x1198(x)
│       └─&gt; 0x000011b2      8b45fc         mov eax, dword [var_4h]
│           0x000011b5      4883c420       add rsp, 0x20
│           0x000011b9      5d             pop rbp
└           0x000011ba      c3             ret
</code></pre>
<p>So why did we uesd <code>wx 909090909090</code> you might ask.</p>
<p>As we already mentioned, 90 is an opcode for nop and the jump instruction <code>0f851a000000</code> is a 6 bytes long:</p>
<p><code>0F</code> - Primary jump instruction opcode. <code>85</code> - Secondary jump instruction opcode - jump near if not zero/not equal (ZF=0) <code>1a000000</code> - The relative address to jump to.</p>
<p>So we had to override all these bits inorder to maintain the correct flow of the program.</p>
<p>Now we can exit the radar2 and run the changed binary.</p>
<pre><code class="language-shell">$ ./wallet 0
Correct!
$ echo $?
0
</code></pre>
<p>That's it!</p>
<p>We successfully patched the binary and we'll get a bypass no matter what integer we input to our wallet binary.</p>
<p>If we want to revert the changes, we can follow the same steps in reverse order. It's all about setting the right bits at the right place (which sounds easier than it is in practice).</p>
<h2>Summary</h2>
<p>Binary patching is a powerful technique for modifying software behaviour without access to the original source code.</p>
<p>In this article, we demonstrated how to navigate, find, read and modify the behaviour of a simple binary using the Radare2 tool.</p>
<p>The main thing is to understand the binary itself, in our example we changed the program that we wrote, so obviously it was easier than modifying an external binary because we don't have access to its source.</p>
<p>I did this work as part of my Ph.D. research project where I intend to change interpreter behaviour at runtime by patching function instructions.</p>
<p>This article serves as a documentation of my own understanding and the organization of my thoughts, aiming to share knowledge with others.</p>
<p>I hope you find it valuable! If you have any further questions or wish to delve into more details, feel free to reach out.</p>
]]></content:encoded></item><item><title><![CDATA[Writing interpreter in Rust - Variables & Builtins (part-5)]]></title><description><![CDATA[Abstract
We're continuing our journey of implementing our interpreter called Coconut.
We're going to build on top of the previous functionality of our interpreter. We will extend our Lexer and Parser ]]></description><link>https://blog.p3ld3v.dev/writing-interpreter-in-rust-variables-builtins-part-5</link><guid isPermaLink="true">https://blog.p3ld3v.dev/writing-interpreter-in-rust-variables-builtins-part-5</guid><dc:creator><![CDATA[Pavel Durov]]></dc:creator><pubDate>Thu, 02 Jul 2026 16:08:22 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/63440fc03c49cd0552fb1067/1dc79d9b-2db1-4d59-9d83-c9db9cad204a.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1>Abstract</h1>
<p>We're continuing our journey of implementing our interpreter called Coconut.</p>
<p>We're going to build on top of the previous functionality of our interpreter. We will extend our Lexer and Parser and implement variable declaration and evaluation as well as a simple built-in function support.</p>
<p>If you haven’t already, I recommend checking out my previous article: <a href="https://hashnode.com/edit/cmr3p6afa00000aku9zvgc1n8">Writing Interpreter in Rust — Repl, Files, and Comments (Part 4)</a></p>
<h1>Introduction</h1>
<p>In order to add variables to the Coconut interpreter we will need to add new tokens to our Lexer. The same goes for built-in functions. We will also implement multi-statement evaluation support, with a ";" termination token - it will be handy when working with variables.</p>
<h2>Lexer</h2>
<pre><code class="language-yacc">%%
[0-9]+ "INTEGER"
\+ "ADD"
\* "MUL"
\( "LPAR"
\) "RPAR"
; ";" 
= "ASSIGN"
let "LET" 
println "PRINT_LN" 
[a-zA-Z0-9_]+ "IDENTIFIER"
[\t\n ]+ ;
//[^\n]*?$ ;
</code></pre>
<p><code>;</code> - Statment termination token (same as in any C-like language).</p>
<p><code>ASSIGN</code> - Assignment operator. It will assign values to variables.</p>
<p><code>LET</code> - Variable declaration token.</p>
<p><code>IDENTIFIER</code> - Identity token, will be used for variable names.</p>
<p><code>PRINT_LN</code> - Buildin function that will print expression evaluation results to the stdout.</p>
<h1>Parser</h1>
<p>Here's what our parser will look like:</p>
<pre><code class="language-rust">%start StatementList
%%

StatementList -&gt; Result&lt;Vec&lt;Node&gt;, ()&gt;:
    StatementList Statement { append($1.map_err(|_| ())?, $2.map_err(|_| ())?)  }
    | { Ok(vec![]) }
    ;

Statement -&gt; Result&lt;Node, ()&gt;:
   ';' { Ok(Node::Empty{}) }
    | Expression ';' { $1 }
    | Builtins { $1 }
    ;
    
Expression -&gt; Result&lt;Node, ()&gt;:
    AdditiveExpression { $1 }
    | PrimaryExpression 'ASSIGN' Expression {
        match $1.map_err(|_| ())? {
            Node::Id { value } =&gt; {
                Ok(Node::Assign { id: value, rhs: Box::new($3?) })
            },
            _ =&gt; Err(())
        }
    }
    | 'LET' PrimaryExpression 'ASSIGN' Expression {
        match $2.map_err(|_| ())? {
            Node::Id { value } =&gt; {
                Ok(Node::Declare { id: value, rhs: Some(Box::new($4?)) })
            },
            _ =&gt; Err(())
        }
    } 
    ;

AdditiveExpression -&gt; Result&lt;Node, ()&gt;:
    MultiplicativeExpression { $1 }
    | AdditiveExpression 'ADD' MultiplicativeExpression { 
        Ok(Node::Add{ lhs: Box::new($1?), rhs: Box::new($3?) })
    }
    ;

MultiplicativeExpression -&gt; Result&lt;Node, ()&gt;: 
    PrimaryExpression { $1 }
    | MultiplicativeExpression 'MUL' PrimaryExpression { 
      Ok(Node::Mul{ lhs: Box::new($1?), rhs: Box::new($3?) })
    }
    ;

PrimaryExpression -&gt; Result&lt;Node, ()&gt;:
    'IDENTIFIER' { Ok(Node::Id { value: $lexer.span_str(($1.map_err(|_| ())?).span()).to_string() }) }
    |  'LPAR' Expression 'RPAR' { $2 }
    | 'INTEGER' { parse_int($lexer.span_str(($1.map_err(|_| ())?).span())) }
    ;

Builtins -&gt; Result&lt;Node, ()&gt;:
    'PRINT_LN' 'LPAR' Expression 'RPAR' { Ok(Node::PrintLn{ rhs: Box::new($3?) }) };

%%
use crate::ast::Node;

fn append(mut lhs: Vec&lt;Node&gt;, rhs: Node ) -&gt; Result&lt;Vec&lt;Node&gt;, ()&gt;{
    lhs.push(rhs);
    Ok(lhs)
}

fn parse_int(s: &amp;str) -&gt; Result&lt;Node, ()&gt; {
    match s.parse::&lt;u64&gt;() {
        Ok(n_val) =&gt; Ok(Node::Number{ value: n_val }),
        Err(_) =&gt; {
            eprintln!("{} cannot be represented as a u64", s);
            Err(())
        }
    }
}
</code></pre>
<p>Ast Node enum:</p>
<pre><code class="language-rust">#[derive(Debug, Clone, PartialEq)]
pub enum Node {
    Add { lhs: Box&lt;Node&gt;, rhs: Box&lt;Node&gt; },
    Mul { lhs: Box&lt;Node&gt;, rhs: Box&lt;Node&gt; },
    Number { value: u64 },
    Id { value: String },
    PrintLn { rhs: Box&lt;Node&gt; },
    Assign { id: String, rhs: Box&lt;Node&gt; },
    Declare { id: String, rhs: Option&lt;Box&lt;Node&gt;&gt; },
    Empty,
}
</code></pre>
<p>It's a bit longer and more complex than what we had before.</p>
<p>The main changes are:</p>
<ol>
<li><p>Added <code>Builtins</code> rule where built-in functions like <code>println</code> can be defined.</p>
</li>
<li><p>Added PrimaryExpression rule for variable declaration and assignment such as: <code>let a = 6;</code></p>
</li>
<li><p>Added support for multiple statements with <code>;</code> termination token. Now we can do something like this: <code>let a = 1; let b = 3; println(a+b);</code></p>
</li>
</ol>
<p>And it's structured in a way that we can extend easily later on. We can add new data types like booleans, add new built-in functions etc...</p>
<h2>Evaluation</h2>
<p>Generally, the evaluation is done in the same way as before. We added new Ast nodes to represent our new functionality and we added a notion of a Scope. Now when we support variables, these variables' identities (aka names) and their values need to be stored somewhere and that somewhere is this Scope.</p>
<p>Our Scope is a simple struct:</p>
<pre><code class="language-rust">use std::collections::HashMap;

pub struct Scope {
    store: HashMap&lt;String, u64&gt;,
}

impl Scope {
    pub fn new() -&gt; Self {
        Scope {
            store: HashMap::new(),
        }
    }

    pub fn dec_var(&amp;mut self, id: String, val: u64) -&gt; Option&lt;u64&gt; {
        self.store.insert(id, val)
    }

    pub fn set_var(&amp;mut self, id: String, val: u64) -&gt; Option&lt;u64&gt; {
        self.store.insert(id, val)
    }

    pub fn get_var(&amp;self, id: String) -&gt; Option&lt;&amp;u64&gt; {
        self.store.get(&amp;id.clone())
    }
}
</code></pre>
<p>Basically, it's a wrapper around the HashMap data structure, so we could've used HashMap directly but I think it creates a nice separation with not much overhead. It will also allow us to extend it in the future when we deal with functions, closures and different scopes.</p>
<p>With the scop in place, we can evaluate source code line by line and declare, assign and reassign variables.</p>
<p>For example:</p>
<pre><code class="language-plaintext">let a = 1;
let b = 2;
a = 3;
print(a+b); // prints 5
</code></pre>
<h3>Evaluation</h3>
<pre><code class="language-rust">#[derive(Debug, PartialEq, Clone)]
pub enum BinaryOp {
    Add,
    Mul,
    Assign { name: String },
    Declare { name: String },
}

#[derive(Debug, PartialEq, Clone)]
pub enum Op {
    Add,                 // Addition operation
    Mull,                // Multiplication operation
    Push { value: u64 }, // Load numeric value onto stack
    Assign { name: String },
    Declare { name: String },
    PrintLn,
    Load { id: String },
}

pub fn eval(ast: Vec&lt;Node&gt;, scope: &amp;mut Scope) -&gt; Result&lt;Option&lt;u64&gt;, String&gt; {
    let ops = &amp;mut vec![];
    for a in ast {
        ast_to_bytecode(a, ops);
    }
    let mut stack: Vec&lt;u64&gt; = vec![];

    for instruction in ops {
        match instruction {
            Op::Push { value } =&gt; stack.push(*value),
            Op::Add =&gt; {
                let rhs = stack.pop().unwrap();
                let lhs = stack.pop().unwrap();
                stack.push(lhs + rhs);
            }
            Op::Mull {} =&gt; {
                let rhs = stack.pop().unwrap();
                let lhs = stack.pop().unwrap();
                stack.push(lhs * rhs);
            }
            Op::Assign { name } =&gt; {
                let val = stack.pop().unwrap();
                scope.set_var(name.clone(), val);
            }
            Op::Declare { name } =&gt; {
                let val = stack.pop().unwrap();
                scope.dec_var(name.clone(), val);
            }
            Op::PrintLn =&gt; {
                println!("{}", stack.pop().unwrap());
            }
            Op::Load { id } =&gt; {
                if let Some(value) = scope.get_var(id.clone()) {
                    stack.push(value.clone());
                } else {
                    return Err(format!("Variable '{}' not found", id.clone()));
                }
            }
        }
    }
    return Ok(stack.pop());
}
</code></pre>
<p>Except for the additional scope variable where we can declare and retrieve variables and their values, it should have the same structure as before.</p>
<p>We still using our Stack for evaluation.</p>
<h3>Conversion from AST to Bytecode</h3>
<p>Here's our <code>ast_to_bytecode</code> function:</p>
<pre><code class="language-rust">pub fn ast_to_bytecode(node: Node, ops: &amp;mut Vec&lt;Op&gt;) {
    match node {
        Node::Add { lhs, rhs } =&gt; {
            ast_to_bytecode(*lhs, ops);
            ast_to_bytecode(*rhs, ops);
            ops.push(Op::Add {})
        }
        Node::Mul { lhs, rhs } =&gt; {
            ast_to_bytecode(*lhs, ops);
            ast_to_bytecode(*rhs, ops);
            ops.push(Op::Mull {})
        }
        Node::Number { value } =&gt; ops.push(Op::Push { value }),
        Node::Declare { id, rhs } =&gt; {
            if let Some(val) = rhs {
                ast_to_bytecode(*val, ops);
            }
            ops.push(Op::Declare { name: id.clone() });
        }
        Node::Assign { id, rhs } =&gt; {
            ast_to_bytecode(*rhs, ops);
            ops.push(Op::Declare { name: id.clone() });
        }
        Node::Id { value } =&gt; ops.push(Op::Load { id: value }),
        Node::PrintLn { rhs } =&gt; {
            ast_to_bytecode(*rhs, ops);
            ops.push(Op::PrintLn {})
        }
        Node::Empty {} =&gt; {}
    }
}
</code></pre>
<p>This change also has no big surprises, this function extended to accompany the new AST nodes and appropriate bytecodes.</p>
<h2>Tests</h2>
<p>And of course, let's not forget about our tests.</p>
<p>We added some simple tests to test that our evaluation still works as expected - with and without variables (keeping previous tests in place).</p>
<pre><code class="language-rust">#[cfg(test)]
#[test]
fn vars_declare_match() {
    assert_eq!(
        eval_str(&amp;"let x = 1; let y = 2; y + x;".to_string()).unwrap(),
        Some(3)
    );
}
#[test]
fn vars_reassign_math() {
    assert_eq!(
        eval_str(&amp;"let x = 1; let y = 2; x = 3; x + y;".to_string()).unwrap(),
        Some(5)
    );
}
#[test]
fn vars_undeclared_variable() {
    assert_eq!(
        eval_str(&amp;"a + 1;".to_string()),
        Err("Variable 'a' not found".to_string())
    );
}
</code></pre>
<p>We didn't add a test for the <code>println</code> functionality, and that's intentional cause I didn't want to add more complexity here with stdout testing - we can explore this in the following posts.</p>
<p>Full source code can be found here: TODO Link.</p>
<h1>Summary</h1>
<p>In this article, we've expanded the capabilities of the Coconut interpreter by introducing variable declaration and assignment. Additionally, we've incorporated a simple <code>println</code> function, enabling us to output evaluated results to <code>stdout</code>.</p>
<p>To facilitate these changes, we introduced a new concept called <code>Scope</code>. This allows us to store variable declarations and their corresponding values, with potential extensions planned for the future.</p>
<p>While much of the heavy lifting was handled by the <code>grmtools</code> Lexer and Parser, the rest of the implementation followed a straightforward path.</p>
<p>This article serves as a documentation of my own understanding and the organization of my thoughts, aiming to share knowledge with others.</p>
<p>I hope you find it valuable! If you have any further questions or wish to delve into more details, feel free to reach out.</p>
]]></content:encoded></item><item><title><![CDATA[Writing interpreter in Rust - Repl, Files and Comments (part-4)]]></title><description><![CDATA[Abstract
We're continuing our journey of implementing our interpreter called Coconut.
In this article, we're going to implement the REPL (Read-Eval-Print Loop), re-organise the project and add file an]]></description><link>https://blog.p3ld3v.dev/writing-interpreter-in-rust-repl-files-and-comments-part-4</link><guid isPermaLink="true">https://blog.p3ld3v.dev/writing-interpreter-in-rust-repl-files-and-comments-part-4</guid><dc:creator><![CDATA[Pavel Durov]]></dc:creator><pubDate>Thu, 02 Jul 2026 16:06:55 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/63440fc03c49cd0552fb1067/b31c4dcc-d033-497c-9afa-8ad7a18c3f39.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1>Abstract</h1>
<p>We're continuing our journey of implementing our interpreter called Coconut.</p>
<p>In this article, we're going to implement the REPL (Read-Eval-Print Loop), re-organise the project and add file and comments support. It will be a collection of small changes that will extend the usability of our interpreter.</p>
<p>If you haven’t already, I recommend checking out my previous article: <a href="https://hashnode.com/edit/cmr3p4dfm00000bji9y2w6x92">Writing an Interpreter in Rust: Bytecode and Stack-Based VM (Part 3)</a></p>
<h1>Introduction</h1>
<p>A REPL (Read-Eval-Print Loop) allows the user to interactively execute code and see the results immediately.</p>
<p>Not all interpreters have REPL, but having REPL allows them to prototype fast, and test ideas and language features quickly.</p>
<p>I personally find REPL very handy especially when I am not 100% familiar with the language interface.</p>
<h1>Project Structure</h1>
<p>I have restructured the code a bit, but nothing fundamentally changed from the last implementation, I just moved the code form the main to separate modules in order to have a clear separation of concern.</p>
<p>Our project content:</p>
<pre><code class="language-shell">$ ls -l ./src

ast.rs - AST logic
bytecode.rs - Bytecode logic
coconut.l - Lexer logic
coconut.y - Parser logic
lib.rs - Main library crate
main.rs - Main program
parser.rs - Parsing logic
</code></pre>
<p>Our <code>main.rs</code> file is very simple:</p>
<pre><code class="language-rust">use std::env;

use coconut::eval_str;

fn main() {
    println!("Writing Interpreter With Rust Part 4");
    let args: Vec&lt;String&gt; = env::args().collect();
    if args.len() &gt; 1 {
        let input = &amp;args[1];
        match eval_str(input) {
            Ok(Some(result)) =&gt; {
                println!("{}", result);
            }
            _ =&gt; eprintln!("Unable to evaluate expression."),
        }
    } else {
        println!("Please provide at least one cli argument!")
    }
}
</code></pre>
<p>The main responsibility of <code>main.rs</code> is to get the program input, call the appropriate functions of the <code>lib.rs</code> crate, and display results or errors to the console.</p>
<p>High-level program evaluation flow:</p>
<p>We get program input as CLI argument, then we call <code>eval_str</code> which will parse the string to an AST, then AST will be translated to Bytecode and then this Bytecode will be evaluated.</p>
<p>Let's implement the REPL.</p>
<h2>Implementing REPL</h2>
<p>So far we were running our Coconut programs as CLI arguments, as:</p>
<pre><code class="language-shell">$ cargo run '2+2'
4
</code></pre>
<p>We're about to change that!</p>
<p>The main component of REPL is (surprisingly) the loop, so let's start with that.</p>
<p>Here we go:</p>
<pre><code class="language-rust">use std::{
    env,
    io::{stdin, stdout, Write},
};

use coconut::eval_str;

fn main() {
    println!("Writing Interpreter With Rust Part 4");
    let args: Vec&lt;String&gt; = env::args().collect();
    if args.len() &gt; 1 {
        eval(&amp;args[1])
    } else {
        repl()
    }
}

fn repl() {
    loop {
        print!("&gt; ");
        stdout().flush().unwrap();
        match stdin().lines().next() {
            Some(Ok(input)) =&gt; {
                if input.trim() == "exit" {
                    break;
                }
                if input.trim().is_empty() {
                    continue;
                }
                eval(&amp;input);
            }
            _ =&gt; {}
        }
    }
}

fn eval(input: &amp;String) {
    match eval_str(input) {
        Ok(Some(result)) =&gt; {
            println!("{}", result);
        }
        _ =&gt; eprintln!("Unable to evaluate expression."),
    }
}
</code></pre>
<p>Try it out:</p>
<pre><code class="language-shell">$ cargo run
&gt; 2+2
4
&gt; 5
5
&gt; exit
</code></pre>
<p>Works as expected, each line will be evaluated seperatly and the process will repeat until we type <code>exit</code>.</p>
<p>That's it, we have a REPL!</p>
<h2>Addding file support</h2>
<p>So now we have two ways of executing our Coconut programs; with CLI arguments or as REPL. Let's add another way - files.</p>
<p>It will allow us to save programs into files and pass them to the interpreter that will evaluate the source code line by line, just as if we were typing the lines in the REPL.</p>
<p>Let's add file support:</p>
<pre><code class="language-rust">use std::{
    env, fs,
    io::{stdin, stdout, Write},
};

fn main() {
    println!("Writing Interpreter With Rust Part 4");
    let args: Vec&lt;String&gt; = env::args().collect();
    if args.len() &gt; 1 {
        if args[1].ends_with(".cnt".clone()) {
            eval_file(args[1].clone())
        } else {
            eval(&amp;args[1])
        }
    } else {
        repl()
    }
}

fn eval_file(file_name: String) {
    match fs::read_to_string(file_name) {
        Ok(content) =&gt; {
            eval(&amp;content);
        }
        Err(_) =&gt; eprintln!("Unable to evaluate expression."),
    }
}
</code></pre>
<p>Here, before running the interpreter we check whether the CLI argument contains the extension ".cnt" (short for Coconut). If it is, we are reading the file and then evaluate it in the same fashion - line by line.</p>
<p>That's about it.</p>
<h2>Adding comments support</h2>
<p>That might feel like a feature creep, but I thought that we could add a small feature to our interpreter anyway, as you will witness it will be a very tiny change. We're going to add comment support.</p>
<p>For simplicity reasons, we're going to support only single-line comments. So if we have a file named <code>math.cnt</code> with the following content:</p>
<pre><code class="language-plaintext">// 1+2
2+2
</code></pre>
<p>We would expect our interpreter to evaluate only the <code>2+2</code>.</p>
<p>Luckily, the only thing we need to do for that is to add the following line to our Lexer:</p>
<pre><code class="language-yacc">//[^\n]*?$ ;
</code></pre>
<p>You can check the Regex yourself. Basically, it will match anything between the <code>//</code> characters and the end of the line and remove it from parsing.</p>
<p>Our Lexer should look something like this:</p>
<pre><code class="language-yacc">%%
[0-9]+ "INTEGER"
\+ "ADD"
\* "MUL"
\( "LPAR"
\) "RPAR"
//[^\n]*?$ ;
[\t\n ]+ ;
</code></pre>
<p>Run it:</p>
<pre><code class="language-rust">$ cargo run math.cnt
4
</code></pre>
<p>And don't forget about tests:</p>
<pre><code class="language-rust">#[test]
fn comments() {
    assert_eq!(
        eval_str(&amp;"// 2+2\n 1+1".to_string()).unwrap(),
        Some(2),
        "expected 1+1=2"
    );
    assert_eq!(
        eval_str(&amp;"// 2+2".to_string()).unwrap(),
        None,
        "expected 1+1=2"
    );
}
</code></pre>
<p>And we're done.</p>
<h1>Summary</h1>
<p>This time we didn't introduce any new concepts or components into the Coconut interpreter, we just refactored the project and added REPL, file evaluation and comment support.</p>
<p>REPL will give us the ability to try out our interpreter's functionality without relaunching the interpreter every time, and program files will allow us to store our Coconut programs for later evaluation. And we can add comments to our source code. What more do we need :)?</p>
<p>This article was written for my own sake of understanding and the organisation of my thoughts as it was about knowledge sharing.</p>
<p>I trust that it proved valuable!</p>
]]></content:encoded></item><item><title><![CDATA[Writing an Interpreter in Rust: Bytecode and Stack-based VM (part 3)]]></title><description><![CDATA[Abstract
We’re continuing our journey of implementing an interpreter called Coconut in Rust!
In this article, you're diving into the concept of Bytecode and its implementation within the Coconut inter]]></description><link>https://blog.p3ld3v.dev/writing-an-interpreter-in-rust-bytecode-and-stack-based-vm-part-3</link><guid isPermaLink="true">https://blog.p3ld3v.dev/writing-an-interpreter-in-rust-bytecode-and-stack-based-vm-part-3</guid><dc:creator><![CDATA[Pavel Durov]]></dc:creator><pubDate>Thu, 02 Jul 2026 16:05:26 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/63440fc03c49cd0552fb1067/6971630d-1b1c-4c26-b3c1-cd1f071c2f77.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1>Abstract</h1>
<p>We’re continuing our journey of implementing an interpreter called Coconut in Rust!</p>
<p>In this article, you're diving into the concept of Bytecode and its implementation within the Coconut interpreter. We will also discuss bytecode evaluation in stack-based virtual machines and compare them to other alternatives.</p>
<p>If you haven’t already, I recommend checking out my previous article: <a href="https://hashnode.com/edit/cmr3p06m100000ajg5uivhf9a">Writing an Interpreter in Rust: AST (part 2)</a></p>
<p>Let's go.</p>
<h2>What's Bytecode?</h2>
<p>Bytecode is a crucial component in many interpreters and VMs (Virtual Machines). In our context, VM is a software-based environment that simulates the program execution. Our Coconut language is not running directly on the hardware, it is simulated by our VM (aka interpreter).</p>
<p>Here we're going to use the terms Virtual Machine and Interpreter interchangeably.</p>
<p>Bytecode is a low-level representation of a program's source code that is designed to be executed by the VM. It acts as an intermediary step between the high-level human-readable source code and the machine code executed by the VM.</p>
<h2>How is it different from AST?</h2>
<p>It might be confusing, but it's different from bytecode in its essence. AST is a structural representation of the program while Bytecode is just an instruction set that is executed by the VM.</p>
<p>I think it will make more sense once we'll see an actual practical example.</p>
<h3>Advantages of Bytecode</h3>
<p>You might wonder why do we need so many components in our interpreter: Lexer, Parser, AST, Bytecode. And its hard to justrify when we're hacing our toy Coconut example that all it does is adding to numbers. But I will try :)</p>
<p>Here are a few advantages of bytecode:</p>
<h3>Portability</h3>
<p>Bytecode can be generated once and executed on multiple target platforms. Think about it as a compilation of the source code.</p>
<h3>Performance and optimisations</h3>
<p>Interpreting and making optimisations on the Bytecode level is often faster than interpreting or compiling from the source code.</p>
<p>Bytecode can serve as an intermediary phase for JIT compilation. Some VMs, like the JVM (Java Virtual Machine), can compile bytecode into native machine code at runtime. JIT can lead to significant performance gains. But it's definitely out of this article's scope.</p>
<h4>Separation of Concerns</h4>
<p>As with AST, we separated the parsing and evaluation phases. With Bytecode we're separating the code structural model (how the program is organized) from the execution model (how the program is run). It makes our code more modular and easier to maintain.</p>
<h2>Stack-based VM</h2>
<p>Why are we talking about stacks suddenly?</p>
<p>There are different types of VMs, the two most common are:</p>
<p>Stack-based VM - stack machines use an operand stack to push and pop results to.</p>
<p>Stack-based VM - register machines use a number of registers to store values or pass arguments.</p>
<p>I chose to implement a stack-based VM cause it just simpler. With stack-based machines, there's no need to keep track of multiple register usage when compared to Register-based VMs.</p>
<p>Our stack will be very simple, just a vector, an array with two functions used, <code>pop</code> and <code>push</code>.</p>
<h2>Defining Bytecode</h2>
<p>Bytecode instructions encapsulate the operations and actions that the VM can perform. Let's define it based on the interpreter functionality we have so far:</p>
<pre><code class="language-rust">#[derive(Debug, PartialEq, Clone)]
pub enum Op {
    Add, 
    Mull,
    Push { value: u64 }, 
}
</code></pre>
<p><code>Op</code> is short for operation.</p>
<p>Here's a breakdown:</p>
<p>Add - This instruction represents the addition operation. It will pop the top two values from the stack, perform addition, and push the result back onto the stack.</p>
<p>Mull - This instruction represents the multiplication operation. Similar to the Add instruction, it will pop the top two values from the stack, perform multiplication, and push the result back onto the stack.</p>
<p>Push - This instruction is used to load a numeric value onto the stack.</p>
<h2>Implementing stack-based VM</h2>
<p>To implement a stack-based VM, we will need to have a Stack (surprisingly enough). But we will also need to convert our AST to Bytecode since want to move away from AST-time evaluation.</p>
<p>And that's what we're going to do next.</p>
<pre><code class="language-rust">pub fn ast_to_bytecode(node: Node, ops: &amp;mut Vec&lt;Op&gt;) {
    match node {
        Node::Add { lhs, rhs } =&gt; {
            ast_to_bytecode(*lhs, ops);
            ast_to_bytecode(*rhs, ops);
            ops.push(Op::Add {})
        }
        Node::Mul { lhs, rhs } =&gt; {
            ast_to_bytecode(*lhs, ops);
            ast_to_bytecode(*rhs, ops);
            ops.push(Op::Mull {})
        }
        Node::Number { value } =&gt; ops.push(Op::Push { value }),
    }
}
</code></pre>
<p>Note that <code>ast_to_bytecode</code> is recursive, since we're dealing with a tree here.</p>
<p>An expression such as <code>3+2*(2+1)</code> would be parsed as AST:</p>
<pre><code class="language-shell">Add { 
        lhs: Number { value: 3 }, 
        rhs: Mul { 
            lhs: Number { value: 2 }, 
            rhs: Add { 
                lhs: Number { value: 2 }, 
                rhs: Number { value: 1 } 
        } 
    } 
}
</code></pre>
<p>And Bytcode:</p>
<pre><code class="language-shell">Push { value: 3 }, 
Push { value: 2 }, 
Push { value: 2 }, 
Push { value: 1 }, 
Add, 
Mull, 
Add
</code></pre>
<p>Let's break down the actual evaluation of this Bytcode iterms of Add, Mul and Stack operations:</p>
<pre><code class="language-h">                                        # stack = []
Push 3                                  # stack = [3]
Push 2                                  # stack = [3,2]
Push 2                                  # stack = [3,2,2]
Push 1                                  # stack = [3,2,2,1]
Push (Add(pop(), pop()) = 1 + 2 = 3)    # stack = [3,2,3]
Push (Mul(pop(), pop()) = 2 * 3 = 6)    # stack = [3,6]
Push (Add(pop(), pop()) = 6 + 3 = 9)    # stack = [9]
</code></pre>
<p>The result is 9. As expected!</p>
<p>Next, we're going to evaluate these operations one by one:</p>
<pre><code class="language-rust">pub fn eval(ast: Vec&lt;Node&gt;) -&gt; Option&lt;u64&gt; {
    let ops = &amp;mut vec![];
    for a in ast {
        ast_to_bytecode(a, ops);
    }
    
    let mut stack: Vec&lt;u64&gt; = vec![]; // Our stack
    
    for instruction in ops {
        match instruction {
            Op::Push { value } =&gt; stack.push(*value),
            Op::Add =&gt; {
                let rhs = stack.pop().unwrap();
                let lhs = stack.pop().unwrap();
                stack.push(lhs + rhs);
            }
            Op::Mull {} =&gt; {
                let rhs = stack.pop().unwrap();
                let lhs = stack.pop().unwrap();
                stack.push(lhs * rhs);
            }
        }
    }
    return stack.pop();
}
</code></pre>
<p>That's it. We have an interpreter that parses our syntax into AST and then generates Bytecode which is evaluated as a Stack-based VM.</p>
<p>Now we have basic components that will allow us to extend our interpreter functionality more easily in the future.</p>
<h2>Tests</h2>
<p>We did change our interpreter internals but nothing really changed from the end user perspective, so our tests we've implemented before should be still good to go.</p>
<p>Tests are great, make more tests please!</p>
<p>Full source code:</p>
<p><code>instruction.rs</code> - <a href="https://gist.github.com/Pavel-Durov/44dd21c246cb199ecb35a2cf31aa816f">https://gist.github.com/Pavel-Durov/44dd21c246cb199ecb35a2cf31aa816f</a></p>
<p><a href="http://ast.rs"><code>ast.rs</code></a> - <a href="https://gist.github.com/Pavel-Durov/44dd21c246cb199ecb35a2cf31aa816f">https://gist.github.com/Pavel-Durov/44dd21c246cb199ecb35a2cf31aa816f</a></p>
<p><code>main.rs</code> - <a href="https://gist.github.com/Pavel-Durov/85e206411b870107d7033d047fe59a8c">https://gist.github.com/Pavel-Durov/85e206411b870107d7033d047fe59a8c</a></p>
<h2>Summary</h2>
<p>We’ve implemented Bytecodes in our Coconut interpreter, and we moved away from AST-time evaluation.</p>
<p>We discussed briefly different VM implementations and the advantages of Stack-based VMs accompanied by practical Rust examples.</p>
<p>All the changes were interpreter internal only so no changes in Parsing, Lexing or Testing were needed.</p>
<p>This change will allow us to extend the Coconut interpreter further in the future.</p>
<p>Next, we will start extending the Coconut interpreter functionality and hopefully, it will be clearer why we need all these components: Lexer, Parser, AST, and Bytecode in our VM.</p>
<p>This article was written for my own sake of understanding and the organisation of my thoughts as it was about knowledge sharing.</p>
<p>I trust that it proved valuable!</p>
]]></content:encoded></item><item><title><![CDATA[Writing an Interpreter in Rust: AST (part 2)]]></title><description><![CDATA[Abstract
We're continuing our journey of implementing an interpreter called coconut in Rust! If you haven't already, I recommend checking out my previous article here, where we set the stage for our i]]></description><link>https://blog.p3ld3v.dev/writing-an-interpreter-in-rust-ast-part-2</link><guid isPermaLink="true">https://blog.p3ld3v.dev/writing-an-interpreter-in-rust-ast-part-2</guid><dc:creator><![CDATA[Pavel Durov]]></dc:creator><pubDate>Thu, 02 Jul 2026 16:02:10 GMT</pubDate><content:encoded><![CDATA[<h2>Abstract</h2>
<p>We're continuing our journey of implementing an interpreter called coconut in Rust! If you haven't already, I recommend checking out my previous article here, where we set the stage for our interpreter project. Here, we'll delve into the concept of Abstract Syntax Trees (AST) and transition from parse-time evaluation to AST-time evaluation.</p>
<h2>What is an AST?</h2>
<p>In the <a href="https://blog.p3ld3v.dev/writing-interpreter-in-rust-using-grmtools">previous article,</a> we discussed lexing and parsing, which helped us convert source code into a structured representation. We ended up with something called parse-tree. We never called it by its name or used it explicitly but that's where we got to.</p>
<p>An AST is a more refined and abstract representation of the code's syntax. It strips away the extraneous details found in a parse tree, focusing solely on the essential elements required for execution.</p>
<h2>AST-Time Evaluation</h2>
<p>As we mentioned, we used parse-tree, and when we evaluated our computation we did it at parse-time - hense parse-time evaluation.</p>
<p>While this approach works just fine, it's not ideal for more complex programs.</p>
<h3>AST-time evaluation advantages:</h3>
<h4>Separation of Concerns</h4>
<p>With AST, we separate the parsing and evaluation phases. This makes our code more modular and easier to maintain.</p>
<h4>Optimisations</h4>
<p>ASTs allow us to optimise code before execution. Potentially, we can perform various transformations on the tree to improve performance.</p>
<h4>Error Handling</h4>
<p>ASTs provide a clearer structure for error reporting. We can pinpoint errors to specific locations in the code.</p>
<p>So to recap.</p>
<p><strong>Parse Tree</strong> - includes all the "useless" syntactic information that humans like/need but doesn't affect compilation</p>
<p><strong>AST</strong> - strip out that useless syntactic stuff, while keeping the esential, structural information.</p>
<p><strong>Evaluator</strong> - evaluates something (parse tree, AST, or opcodes) directly; a "compiler" converts one thing into another</p>
<h2>Where we ended up</h2>
<p>Our starting point is as follows:</p>
<p>Our lexer file <code>coconut.l</code>:</p>
<pre><code class="language-plaintext">%%
[0-9]+ "INTEGER"
\+ "ADD"
\* "MUL"
\( "LPAR"
\) "RPAR"
[\t\n ]+ ;
</code></pre>
<p>Our parser file <code>coconut.y</code>:</p>
<pre><code class="language-yacc">%start Expr
%%

Expr -&gt; Result&lt;u64, ()&gt;:
      Expr 'ADD' Term { Ok($1? + $3?) }
    | Term { $1 }
    ;

Term -&gt; Result&lt;u64, ()&gt;:
      Term 'MUL' Factor { Ok($1? * $3?) }
    | Factor { $1 }
    ;

Factor -&gt; Result&lt;u64, ()&gt;:
      'LPAR' Expr 'RPAR' { $2 }
    | 'INTEGER'
      {
          let v = $1.map_err(|_| ())?;
          parse_int($lexer.span_str(v.span()))
      }
    ;
%%

fn parse_int(s: &amp;str) -&gt; Result&lt;u64, ()&gt; {
    match s.parse::&lt;u64&gt;() {
        Ok(val) =&gt; Ok(val),
        Err(_) =&gt; Err(())
    }
}
</code></pre>
<p>Can you spot where we do the evaluation of addition and multiplication?</p>
<p>Out <code>main.rs</code>:</p>
<pre><code class="language-rust">use std::env;

use lrlex::lrlex_mod;
use lrpar::lrpar_mod;

lrlex_mod!("coconut.l"); // brings the Lexer for `coconut.l` into scope.
lrpar_mod!("coconut.y"); // brings the Parser for `coconut.y` into scope.

fn main() {
    let args: Vec&lt;String&gt; = env::args().collect();
    if args.len() &gt; 1 {
        let input = &amp;args[1]; // Create a lexer
        let lexer_def = coconut_l::lexerdef(); // Lex the input.
        let lexer = lexer_def.lexer(&amp;input);
        let (res, errs) = coconut_y::parse(&amp;lexer); // Parse the input.
        // Check for errors
        for e in errs {
            println!("{}", e.pp(&amp;lexer, &amp;coconut_y::token_epp));
        }
        // Print results
        match res {
            Some(Ok(r)) =&gt; println!("{:?}", r),
            _ =&gt; eprintln!("Unable to evaluate expression."),
        }
    } else {
        println!("Please provide at least one cli argument!")
    }
}
</code></pre>
<p>That's it. Let's add AST to our interpreter.</p>
<h2>Adding AST</h2>
<p>First, let's add new file <code>src/ast.rs</code>:</p>
<pre><code class="language-rust">#[derive(Debug)]
pub enum Node {
    Add { lhs: Box&lt;Node&gt;, rhs: Box&lt;Node&gt; },
    Mul { lhs: Box&lt;Node&gt;, rhs: Box&lt;Node&gt; },
    Number { value: u64 },
}
</code></pre>
<p>Nothing special here. We jsut defined our operations and the single basic type number.</p>
<p>Next we're going to change our Parser a bit:</p>
<pre><code class="language-yacc">%start Nodes
%avoid_insert "INTEGER"
%%

Nodes -&gt; Result&lt;Vec&lt;Node&gt;, ()&gt;:
    Nodes Node { flattenr($1, $2) }
  | { Ok(vec![]) }
  ;

Node -&gt; Result&lt;Node, ()&gt;:
      Node 'ADD' Term {
        Ok(Node::Add{ 
          lhs: Box::new($1?), 
          rhs: Box::new($3?) 
        })
      }
    | Term { $1 }
    ;

Term -&gt; Result&lt;Node, ()&gt;:
      Term 'MUL' Factor {
        Ok(Node::Mul{  
          lhs: Box::new($1?), 
          rhs: Box::new($3?) 
        })
      }
    | Factor { $1 }
    ;

Factor -&gt; Result&lt;Node, ()&gt;:
      'LPAR' Node 'RPAR' { $2 }
    | 'INTEGER' { 
        match $1.map_err(|err| format!("Parsing Error: {}", err)) {
            Ok(s) =&gt; {
              let s = $lexer.span_str(s.span());
              match s.parse::&lt;u64&gt;() {
                  Ok(n_val) =&gt; Ok(Node::Number{ value: n_val }),
                  Err(_) =&gt; Err(())
              }
            }
            Err(_) =&gt; Err(())
        }
      }
    ;
%%
use crate::ast::Node;

/// Flatten `rhs` into `lhs`.
fn flattenr&lt;T&gt;(lhs: Result&lt;Vec&lt;T&gt;, ()&gt;, rhs: Result&lt;T, ()&gt;) -&gt; Result&lt;Vec&lt;T&gt;, ()&gt; {
    let mut flt = lhs?;
    flt.push(rhs?);
    Ok(flt)
}
</code></pre>
<p>Note that we no longer execute the operations in our Parser, we just return enums as a vector of type <code>Vector&lt;ast::Node&gt;</code>.</p>
<p>The only thing we need to change in our <code>main.rs</code> is to add <code>mod ast;</code> to make ast module accessible And if we run our interpreter we should get the following:</p>
<pre><code class="language-shell">$ cargo run 2+2
[Add { lhs: Number { value: 2 }, rhs: Number { value: 2 } }]
$ cargo run 2+2*2
[Add { lhs: Number { value: 2 }, rhs: Mul { lhs: Number { value: 2 }, rhs: Number { value: 2 } } }]
</code></pre>
<p>That actually looks like an AST!. If we format it a bit, it will make more sense:</p>
<pre><code class="language-shell">    Add { 
      lhs: Number { value: 2 }, 
      rhs: Mul { 
        lhs: Number { value: 2 }, 
        rhs: Number { value: 2 } 
      }
    }
</code></pre>
<p>We have a tree-like structure with the correct order of operations.</p>
<p>That's all thanks to the grmtools Parser that does the heavy lifting here.</p>
<p>Looking at this tree, we can infer that we need to add 2 to a result of multiplication of 2 and 2.</p>
<p>And that's what we're going to do next!</p>
<h2>AST-time evaluation</h2>
<p>We have our AST defined and we removed the parse-time evaluation.</p>
<p>Next, we're going to implement the ast-time evalution. We're going to add two functions <code>eval</code> and <code>eval_exp</code>.</p>
<p>Notice that <code>eval_exp</code> is recursive, as it handles both operations and numbers.</p>
<p>Our <code>main.rs</code> content:</p>
<pre><code class="language-rust">use std::env;

use lrlex::lrlex_mod;
use lrpar::lrpar_mod;

lrlex_mod!("coconut.l"); // brings the lexer for `coconut.l` into scope.
lrpar_mod!("coconut.y"); // brings the Parser for `coconut.y` into scope.

mod ast;

fn main() {
    let args: Vec&lt;String&gt; = env::args().collect();
    if args.len() &gt; 1 {
        let input = &amp;args[1]; // Create a lexer
        let lexer_def = coconut_l::lexerdef(); // Lex the input.
        let lexer = lexer_def.lexer(&amp;input);
        let (res, errs) = coconut_y::parse(&amp;lexer); // Parse the input.
                                                    // Check for errors
        for e in errs {
            println!("{}", e.pp(&amp;lexer, &amp;coconut_y::token_epp));
        }
        // Print results
        match res {
            Some(Ok(r)) =&gt; println!("{:?}", eval(r).unwrap()),
            _ =&gt; eprintln!("Unable to evaluate expression."),
        }
    } else {
        println!("Please provide at least one cli argument!")
    }
}

pub fn eval(ast: Vec&lt;ast::Node&gt;) -&gt; Result&lt;u64, String&gt; {
    for node in ast {
        return eval_exp(node);
    }
    return Err(String::from("Couldn't evaluate given opcodes."));
}

fn eval_exp(exp: ast::Node) -&gt; Result&lt;u64, String&gt; {
    match exp {
        ast::Node::Add { lhs, rhs } =&gt; eval_exp(*lhs)?
            .checked_add(eval_exp(*rhs)?)
            .ok_or("overflowed".to_string()),
        ast::Node::Mul { lhs, rhs } =&gt; eval_exp(*lhs)?
            .checked_mul(eval_exp(*rhs)?)
            .ok_or("overflowed".to_string()),
        ast::Node::Number { value } =&gt; Ok(value),
    }
}
</code></pre>
<p>Let's run it.</p>
<pre><code class="language-shell">$ cargo run 2+2*2
6
</code></pre>
<p>Looks good. What about paranthesie?</p>
<pre><code class="language-shell">$ cargo run '(2+2)*3'
12
</code></pre>
<p>Also good! We preserved the operation precendece.</p>
<p>If you don't 100% sure how it works, there's no better teacher as your debugger.</p>
<p>Try running this code and step through the evaluation of our brand new AST. It will be worth it, I promise.</p>
<h2>Tests</h2>
<p>It's about time to add some tests to our interpreter. I am not going to cover all the edge cases, but we'll add at lest a demostration of how to test our code.</p>
<p>You'll notice that its not going to be easy to test our code. And its not cause its rocket science, its just cause its strucutred in a non-testable way.</p>
<p>Let's add a few changes. What we want is to test string input and assert the output. So let's do just that. We're going to extract the main login from <code>main</code> to <code>from_str</code>. The new function <code>from_str</code> will accept a string and will return result. That change will allow us to streamline our tests.</p>
<p>Our `main.rs`` with single test:</p>
<pre><code class="language-rust">use std::env;

use lrlex::lrlex_mod;
use lrpar::lrpar_mod;

lrlex_mod!("coconut.l"); // brings the lexer for `coconut.l` into scope.
lrpar_mod!("coconut.y"); // brings the Parser for `coconut.y` into scope.

mod ast;

fn main() {
    let args: Vec&lt;String&gt; = env::args().collect();
    if args.len() &gt; 1 {
        let input = &amp;args[1]; // Create a lexer
                              // Print results
        match from_str(input) {
            Ok(r) =&gt; println!("{:?}", r),
            _ =&gt; eprintln!("Unable to evaluate expression."),
        }
    } else {
        println!("Please provide at least one cli argument!")
    }
}

fn from_str(input: &amp;String) -&gt; Result&lt;u64, String&gt; {
    let lexer_def = coconut_l::lexerdef(); // Lex the input.
    let lexer = lexer_def.lexer(&amp;input);
    let (res, errs) = coconut_y::parse(&amp;lexer); // Parse the input.
                                                // Check for errors
    for e in errs {
        println!("{}", e.pp(&amp;lexer, &amp;coconut_y::token_epp));
    }
    // Print results
    match res {
        Some(Ok(r)) =&gt; eval(r),
        _ =&gt; Err("Unable to evaluate expression.".to_string()),
    }
}

pub fn eval(ast: Vec&lt;ast::Node&gt;) -&gt; Result&lt;u64, String&gt; {
    for node in ast {
        return eval_exp(node);
    }
    return Err(String::from("Couldn't evaluate given opcodes."));
}

fn eval_exp(exp: ast::Node) -&gt; Result&lt;u64, String&gt; {
    match exp {
        ast::Node::Add { lhs, rhs } =&gt; eval_exp(*lhs)?
            .checked_add(eval_exp(*rhs)?)
            .ok_or("overflowed".to_string()),
        ast::Node::Mul { lhs, rhs } =&gt; eval_exp(*lhs)?
            .checked_mul(eval_exp(*rhs)?)
            .ok_or("overflowed".to_string()),
        ast::Node::Number { value } =&gt; Ok(value),
    }
}

#[test]
fn eval_expressions() {
    assert_eq!(
        from_str(&amp;"0+1*1*1".to_string()).unwrap(),
        1,
        "expected 0+1*1*1"
    );
    assert_eq!(from_str(&amp;"1+1".to_string()).unwrap(), 2, "expected 1+1=2");
    assert_eq!(
        from_str(&amp;"1*(1+2)".to_string()).unwrap(),
        3,
        "expected 1*(1+2)=3"
    );
}
</code></pre>
<p>Now, ;ets run the tests:</p>
<pre><code class="language-shell">$ cargo test
running 1 test
test eval_expressions ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
</code></pre>
<p>Now we can sleep at night knowing that we're not breaking our code as add more changes to our coconut interpreter.</p>
<p>Let's wrap it up.</p>
<h1>Summary</h1>
<p>In this article we talked about our previous implementation of a parse-time evaluation interpreter.</p>
<p>We introduced the concept of AST, and compared it to Parse-tree and moved the evaluation from parse-time to ast-time.</p>
<p>We talked briefly about tests, and showed how can we restructure our code to make it more testable.</p>
<p>This article was written for my own sake of understanding and the organisation of my thoughts as it was about knowledge sharing.</p>
<p>I trust that it proved valuable!</p>
]]></content:encoded></item><item><title><![CDATA[Writing interpreter in Rust using grmtools (part 1)]]></title><description><![CDATA[Introduction
This article overviews the process of writing an interpreter with Rust programming language. We will use the grmtools Rust crate to help with the parsing. We will define tokens of our lan]]></description><link>https://blog.p3ld3v.dev/writing-interpreter-in-rust-using-grmtools-part-1</link><guid isPermaLink="true">https://blog.p3ld3v.dev/writing-interpreter-in-rust-using-grmtools-part-1</guid><category><![CDATA[Rust]]></category><category><![CDATA[interpreter]]></category><category><![CDATA[compilers]]></category><category><![CDATA[parsing]]></category><category><![CDATA[yacc]]></category><dc:creator><![CDATA[Pavel Durov]]></dc:creator><pubDate>Fri, 21 Apr 2023 17:00:52 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1682096301885/5df8c780-5b55-4c8b-89fc-7fe35acfd045.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2><strong>Introduction</strong></h2>
<p>This article overviews the process of writing an interpreter with Rust programming language. We will use the <code>grmtools</code> Rust crate to help with the parsing. We will define tokens of our language and create math addition and multiplication expressions. We will also cover some basic terms and concepts related to writing an interpreter as we go. This writing is heavily based on <code>grmtools</code> <a href="https://softdevteam.github.io/grmtools/master/book/quickstart.html">Quickstart</a> [1].</p>
<h3><strong>What are grmtools</strong></h3>
<p><code>Grmtools</code> is a collection of Rust libraries for text parsing at compile and run times. We will focus on the compile-time YACC features of <code>grmtools</code> as they will provide us with the framework for parsing instead of writing one from scratch.</p>
<h3><strong>What's YACC?</strong></h3>
<p><strong>YACC</strong> (Yet Another Compiler-Compiler) is an <strong>LALR</strong> (Look Ahead Left-to-Right Rightmost Derivation) parser generator [2].<br />Don't let these acronyms scare you. <strong>LALR</strong> parser is just a type of <strong>Parser</strong>. A <strong>Parser</strong> is a compiler component that translates the raw source code into a meaningful structured form.<br /><strong>YACC</strong> is written in a similar notation to <strong>BNF</strong> (Backus–Naur Form) [3], which will resemble what we will use. The only difference is that we'll have some Rust code and types involved.</p>
<h3>Why YACC?</h3>
<p>We don't have to use <strong>YACC</strong> or any <strong>YACC-compatible</strong> tools like <code>grmtools</code>, but it saves us time from defining our own Lexers and Parsers from scratch.If you do want to implement lexing and parsing from scratch, I would suggest reading one of these books: <a href="https://www.goodreads.com/book/show/58661468-crafting-interpreters">Crafting Interpreters</a>, <a href="https://www.goodreads.com/en/book/show/32681092-writing-an-interpreter-in-go">Writing Interpreter in Go.</a></p>
<h2><strong>Getting started</strong></h2>
<p>Grmtools includes a <strong>YACC-style</strong> <strong>Parser</strong> called <code>lrpar</code> and a <strong>Lexer</strong> called <code>lrlex</code>. We will also use <code>cfgrammar</code> which we'll use to identify the <strong>YACC</strong> variant we will use - <code>YaccKind::Grmtools</code>.</p>
<p><strong>Some Terminology:</strong><br /><strong>Lexeme</strong> - Part of the source code text<br /><strong>Lexer</strong> - Breaks the text into individual lexemes<br /><strong>Parser</strong> - Validates whether lexemes fit the formal grammar<br /><strong>Tokens</strong> - Parsed <strong>Lexemes</strong> with the assigned type</p>
<p>In the following sections, we will define our <strong>Lexer</strong> and <strong>Parser</strong> in a <strong>YACC-y</strong> way using <code>grmtools</code> libraries in a Rust project.</p>
<h3>Creating new project</h3>
<p>I will call my project <strong>Coconut</strong> for two reasons: Just because I can and also cause like coconuts.<br />Creating new cargo project:</p>
<pre><code class="language-bash">$ cargo new coconut
</code></pre>
<p>It should have the following content:</p>
<pre><code class="language-bash">$ tree coconut
    coconut
    ├── Cargo.toml
    └── src
        └── main.rs
</code></pre>
<h3>Setting up Cargo.toml</h3>
<pre><code class="language-ini">[package]
name = "coconut"
version = "0.0.1"
edition = "2021"

[[bin]]
doc = false
name = "coconut"

[build-dependencies]
cfgrammar = "0.13"
lrlex = "0.13.1"
lrpar = "0.13.1"

[dependencies]
cfgrammar = "0.13"
lrlex = "0.13.1"
lrpar = "0.13.1
</code></pre>
<p>Now we have all the dependencies we need.</p>
<h3>Lexer</h3>
<p>First, we need to define the vocabulary we will use in our interpreter. For that, we'll create a <code>coconut.l</code> file in our <code>src</code> directory and define our keywords as key-value pairs, where the key is a normal Regex expression, and the value is the formal <strong>Lexeme</strong> assigned to it, separated by whitespace.</p>
<pre><code class="language-rust">%%
[0-9]+ "INTEGER"
\+ "ADD"
\* "MUL"
\( "LPAR"
\) "RPAR"
[\t\n ]+ ;
</code></pre>
<p>Let's examine it line-by-line:</p>
<p><code>[0-9]+ "INTEGER"</code>- Any string that will match the numeric sequence <code>0-9</code> will be identified as an <code>INTEGER</code> <strong>Lexeme</strong>.</p>
<p><code>\+ "ADD"</code> - "+" characters will be identified as an <code>ADD</code> <strong>Lexeme</strong>.</p>
<p><code>\* "MUL"</code> - "*" characters will be identified as a <code>MUL</code> <strong>Lexeme</strong>.</p>
<p><code>\( "LPAR"</code> - "(" characters will be identified as an <code>LPAR</code> <strong>Lexeme</strong>.</p>
<p><code>\) "RPAR"</code> - ")" characters will be identified as an <code>RPAR</code> <strong>Lexeme</strong>.</p>
<p><code>[\t\n ]+ ;</code> - Any tabs, new lines and whitespaces will be replaced with empty strings. i.e. removed.</p>
<p>That should give you a general idea of what is possible. We can define any Regex and assign a formal lexeme to it that will be later used in the <strong>Parser</strong> if the Regex pattern matches a text sequence.</p>
<h3>Parser</h3>
<p>Defining the <strong>Parser</strong> will involve creating <code>coconut.y</code> in our <code>src</code> directory with the following content:</p>
<pre><code class="language-rust">%start Expr
%%

Expr -&gt; Result&lt;u64, ()&gt;:
      Expr 'ADD' Term { Ok($1? + $3?) }
    | Term { $1 }
    ;

Term -&gt; Result&lt;u64, ()&gt;:
      Term 'MUL' Factor { Ok($1? * $3?) }
    | Factor { $1 }
    ;

Factor -&gt; Result&lt;u64, ()&gt;:
      'LPAR' Expr 'RPAR' { $2 }
    | 'INTEGER'
      {
          let v = $1.map_err(|_| ())?;
          parse_int($lexer.span_str(v.span()))
      }
    ;
%%

fn parse_int(s: &amp;str) -&gt; Result&lt;u64, ()&gt; {
    match s.parse::&lt;u64&gt;() {
        Ok(val) =&gt; Ok(val),
        Err(_) =&gt; Err(())
    }
}
</code></pre>
<p>The <code>coconut.y</code> grammar file has three parts, separated by the <code>%%</code> lines:</p>
<p><strong>1st Part - General settings</strong></p>
<p>This is the part where general settings are set. At a minimum, it needs the start rule <code>%start</code> to know what rule should be the starting point for the Parser.</p>
<p><strong>2nd part - YACC Grammar rules</strong></p>
<p>Our example consists of three rules: <code>Expr</code>, <code>Term</code>, and <code>Factor</code> and six productions, also known as alternatives. Each rule can have multiple productions. Each production is separated by <code>|</code> character. Rules end with <code>;</code> a symbol. Productions can reference other rules or <strong>Lexemes</strong> defined in the <code>coconut.l</code> file. If the production pattern matches, its action code is executed. Action code is set between <code>{</code> <code>}</code> symbols.<br />The <code>$x</code> variables refer to the respective <strong>Lexeme</strong> in the production.<br />For example:<br /><code>Term { $1 }</code> references the first <code>$1</code> symbol.<br />while</p>
<p><code>Expr 'ADD' Term { Ok($1? + $3?) }</code> rule references the first <code>$1</code> and the third <code>$3</code> symbols.</p>
<p><strong>3rd part - Rust code</strong></p>
<p>Any Rust code called by production action code can be defined here. We can also import other rust code from the crate or external crates.<br />In our example, that's where we defined the <code>parse_int</code> function.<br />And there we have it! We went through the <code>grmtools</code> Quickstart example and defined our <strong>Lexer</strong> and <strong>Parser</strong>.<br />Next, we'll wire up our application!</p>
<h3>Compiling our Parser to Rust</h3>
<p>To use the grammar we just defined, we need to compile it into Rust code. For that, we will create a <a href="http://build.rs"><code>build.rs</code></a> file, which will provide us with such functionality.<br />Let's create <a href="http://build.rs"><code>build.rs</code></a> file in the root of the project with the following content:</p>
<pre><code class="language-rust">use cfgrammar::yacc::YaccKind;
use lrlex::CTLexerBuilder;

fn main() {
    CTLexerBuilder::new()
        .rust_edition(lrlex::RustEdition::Rust2021)
        .lrpar_config(|ctp| {
            ctp.yacckind(YaccKind::Grmtools)
                .rust_edition(lrpar::RustEdition::Rust2021)
                .grammar_in_src_dir("coconut.y")
                .unwrap()
        })
        .lexer_in_src_dir("coconut.l")
        .unwrap()
        .build()
        .unwrap();
}
</code></pre>
<p>If you're unfamiliar with <a href="http://build.rs"><code>build.rs</code></a> see more information in <a href="https://doc.rust-lang.org/cargo/reference/build-scripts.html">The Cargo Book</a> [4]</p>
<h3>Application entry point</h3>
<p>We did the Lexing, Parsing and Compilation steps. Here we will hook all of it to our application entry point - <a href="http://marin.rs"><code>main.rs</code></a>.<br />Our <a href="http://main.rs"><code>main.rs</code></a> content:</p>
<pre><code class="language-rust">use std::env;

use lrlex::lrlex_mod;
use lrpar::lrpar_mod;

lrlex_mod!("coconut.l"); // brings the lexer for `coconut.l` into scope.
lrpar_mod!("coconut.y"); // brings the Parser for `coconut.y` into scope.

fn main() {
    let args: Vec&lt;String&gt; = env::args().collect();
    if args.len() &gt; 1 {
        let input = &amp;args[1]; // Create a lexer
        let lexer_def = coconut_l::lexerdef(); // Lex the input.
        let lexer = lexer_def.lexer(&amp;input);
        let (res, errs) = coconut_y::parse(&amp;lexer); // Parse the input.
        // Check for errors
        for e in errs {
            println!("{}", e.pp(&amp;lexer, &amp;coconut_y::token_epp));
        }
        // Print results
        match res {
            Some(Ok(r)) =&gt; println!("{:?}", r),
            _ =&gt; eprintln!("Unable to evaluate expression."),
        }
    } else {
        println!("Please provide at least one cli argument!")
    }
}
</code></pre>
<p>In this code, we accept a single CLI argument as input, <strong>Lexing</strong> it, <strong>Parsing</strong> it and checking for errors.<br />And we're done!</p>
<h3>Running our interpreter</h3>
<p>Try adding two numbers:</p>
<pre><code class="language-bash">$ cargo -q run '2+2'
4
</code></pre>
<blockquote>
<p><code>-q</code> flag tells cargo to be quiet and not output verbose information.</p>
</blockquote>
<p>Our interpreter also supports operation precedence and more complex math expressions:</p>
<pre><code class="language-bash">$ cargo -q run '2+2*2+(2+2)'
10
</code></pre>
<p>If you're not impressed, it's OK. But there is something impressive going on here that we might have taken for granted.</p>
<p>If we're not careful, <code>1+2*3</code> math expression can be evaluated as: <code>1+2*3 = (1+2)*3 = 9</code>. Which is obviously wrong, at least by the conventional definition of <code>+</code> and <code>*</code> math operations. We know that <code>1+2*3</code> should be evaluated as <code>1+(2*3) = 7</code>.<br />We won't dive into how and why it worked; assume some magic is happening behind the scenes. But if you're curious, it is something to do with the fact that we just wrote an <strong>LR</strong> parser. Writing our grammar this way forces the "correct" parsing with "correct" operation precedence. It is essential mainly for arithmetic operations. Read more about it <a href="https://tratt.net/laurie/blog/2020/which_parsing_approach.html">here</a> [5].</p>
<h1>Summary</h1>
<p>We went through the steps of implementing our own interpreter using Rust programming language and <code>grmtools</code>: <code>cfgrammar</code>, <code>lrlex</code> and <code>lrpar</code> libraries.</p>
<p>We went through the quickstart of <code>grmtools</code> [1] and added a bit more context about the terminology used.</p>
<p>Rust project example can be found <a href="https://github.com/Pavel-Durov/blog/tree/main/src/writing-interpreter-with-rust">here.</a></p>
<p><a href="https://github.com/Pavel-Durov/blog/tree/main/src/writing-interpreter-with-rust">  
</a>This writing was for my own sake of understanding and the organisation of my thoughts as it was about knowledge sharing.</p>
<h1>References</h1>
<p>[1] <a href="https://softdevteam.github.io/grmtools/master/book/quickstart.html%EF%BF%BC%5B2%5D">https://softdevteam.github.io/grmtools/master/book/quickstart.html<br />[2]</a> <a href="https://www.geeksforgeeks.org/introduction-to-yacc/%EF%BF%BC%5B3%5D">https://www.geeksforgeeks.org/introduction-to-yacc/<br />[3]</a> <a href="https://en.wikipedia.org/wiki/Backus%E2%80%93Naur_form%EF%BF%BC%5B4%5D">https://en.wikipedia.org/wiki/Backus%E2%80%93Naur_form<br />[4]</a> <a href="https://doc.rust-lang.org/cargo/reference/build-scripts.html%EF%BF%BC%5B5%5D">https://doc.rust-lang.org/cargo/reference/build-scripts.html<br />[5]</a> <a href="https://tratt.net/laurie/blog/2020/which_parsing_approach.html">https://tratt.net/laurie/blog/2020/which_parsing_approach.html</a></p>
]]></content:encoded></item><item><title><![CDATA[Programming Languages Composition]]></title><description><![CDATA[Abstract
There is a variety of Programming Languages (PL) today, each with its speciality and features. But still, traditional software is written using a single programming language. We have tools and processes that allow language compositions, but ...]]></description><link>https://blog.p3ld3v.dev/programming-languages-composition</link><guid isPermaLink="true">https://blog.p3ld3v.dev/programming-languages-composition</guid><category><![CDATA[programming languages]]></category><category><![CDATA[composition]]></category><category><![CDATA[pypy]]></category><category><![CDATA[languages]]></category><dc:creator><![CDATA[Pavel Durov]]></dc:creator><pubDate>Sat, 10 Dec 2022 13:00:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1670764726122/mXWIhf7v-.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-abstract">Abstract</h2>
<p>There is a variety of Programming Languages (PL) today, each with its speciality and features. But still, traditional software is written using a single programming language. We have tools and processes that allow language compositions, but these tools are cumbersome, slow and complex. This article will overview the Programming Language Composition (PLC) problem space and the applied composition techniques.</p>
<h2 id="heading-introduction">Introduction</h2>
<p>Realistic software is usually not bound to a single domain; storage, serialisation, network, security, concurrency, and domain-specific problems are just a few examples. Domain-Specific Languages (DSL) are built and optimised for specific problems. Some general-purpose languages are better suited for performance-intensive applications, while others have better readability or broader library support.</p>
<p>PLC is a computer program written in multiple programming languages. PLC can enable us to write software that is not restricted by single PL limitations, allowing us to take advantage of different language features flexibly.</p>
<h2 id="heading-programming-language-composition-plc">Programming Language Composition (PLC)</h2>
<p>It happened to me multiple times when I was writing software in one PL, and I needed some functionality or library available only in another PL. And I was stuck! Usually, in that case, I default to writing some wrapper (aka glue code), invoking a CLI tool or some executable.</p>
<p>I see PLC as a solution for fast prototyping and experimentation with PL features. If we were not restricted by PL integration constraints, we would be able to migrate legacy software in an iterative way, module by module, function by function. Or write domain logic in a more readable PL (let's say Python) and implement the rest in a more performant language (for example, Rust).</p>
<p>Of course, combining multiple languages in the same project creates more complexity. It requires engineers to understand and be proficient in more than one language. But if we put this cognitive load aside. And do some blue-sky thinking; why would we want to build restrictive software solutions locked in one paradigm or ecosystem? After all, we know that we will reach a dead end.</p>
<p>Some tools and processes allow language composition. However, it comes with runtime performance impact and operational costs, such as integration effort, and a need for better tooling for tasks such as debugging and profiling.</p>
<p>Let's have an overview of these methods.</p>
<h3 id="heading-distributed-computing">Distributed Computing</h3>
<p><a target="_blank" href="https://en.wikipedia.org/wiki/Distributed_computing">Distributed Computing</a> [1] is a highly adopted approach in the IT industry that addresses this problem. Usually referred to as Microservices Architecture these days, but it doesn't have to be "micro" per see to be distributed. Distributed systems consist of multiple components that act together, communicating over the networking protocol. Since the network layer abstracts components' internals, it allows these components to be implemented independently, using any language or framework, as long as it has a well-defined communication protocol. However, it requires the application to be distributed and comes with significant infrastructure, networking and deployment complexity. Networking communication also comes with higher latency when compared to in-memory communication.</p>
<h3 id="heading-process-based-fork-exec-spawn">Process-based - Fork, Exec, Spawn</h3>
<p><a target="_blank" href="https://en.wikipedia.org/wiki/Fork%E2%80%93exec">Fork–exec</a> [2] is a Unix Operating System (OS) technique where an executing process spawns a new program. With a Process-based approach, the solution is composed of a collection of processes. One process can invoke another and create this relationship with internal abstraction on the OS level.</p>
<p>Fork - an operation where a process creates a copy of itself</p>
<p>Exec - an operation that runs an executable in an existing process context</p>
<p>Spawn - loads and executes a new "child" process. Where the "parent" process manages this "child"</p>
<h3 id="heading-process-based-ipc">Process-based - IPC</h3>
<p>Yet another process-based approach is Inter-Process Communication (IPC). IPC is a mechanism that allows multiple processes on the same machine to communicate over the local network or memory.</p>
<p>Memory - Processes communicate using the same memory. One produces data, and the other consumes it.</p>
<p>Messaging - Processes communicate without shared memory. Instead, they are using messages. Similarly to the Client-Server model, the connection is established over the network, and messages are sent from one process to another.</p>
<h3 id="heading-foreign-function-interface-ffi">Foreign Function Interface (FFI)</h3>
<p>Some of the most popular applications are built with a mix of languages. Firefox browser is one example; made with C/C++, Java, JavaScript and Python (maybe more). With this kind of project, it is a general practice to write an application base system in low-level programming languages and extend it with high-level languages. The main reason for adopting multiple programming languages is the code reuse of existing software; another is to take advantage of language-specific features [3].</p>
<p>Multi-language projects face a challenge where each language has separate runtimes and address space. Therefore, there must be an established cross-process communication protocol for these programs to interact. <a target="_blank" href="https://www.swig.org/papers/Tcl96/tcl96.html">SWIG</a> [4] is a popular tool that addresses such challenges. SWIG is an interface compiler that links programs written in C/C++ with other languages; it is based on messaging and type conversion via a Foreign Function Interface (FFI), which can be cumbersome and costly in runtime performance.</p>
<p>Another operational overhead with SWIG-like tools is that cross-interactions require the definition of interface files [5], which are foreign to the interfacing languages; to integrate with SWIG, one must understand SWIG protocol and its toolchain. It has a steep learning curve and creates another barrier to its adoption.</p>
<h3 id="heading-interpreters-based-composition">Interpreters-based composition</h3>
<p>Another approach to composing multiple languages as one solution is interpreter-based composition. If we have multiple interpreters written in the same language for different PLs, we can compose them in the same address space. However, it comes with challenges, such as Language Semantics. The <a target="_blank" href="https://soft-dev.org/pubs/pdf/barrett_bolz_tratt__unipycation_a_study_in_cross_language_tracing.pdf">Unipycation</a> [6] case study shows that it is feasible to create an efficient Virtual Machine (VM) cross-language composition.</p>
<p>Unipycation composes multiple RPython interpreters; <a target="_blank" href="https://www.pypy.org/">PyPy</a> [7] and <a target="_blank" href="https://www.bibsonomy.org/bibtex/2f58dd8a58d274eab26c1eaf6f501e493/gron?lang=de">Pyrolog</a> [8]. Both components have their own <a target="_blank" href="https://tratt.net/laurie/research/pubs/html/bolz_tratt__the_impact_of_metatracing_on_vm_design_and_implementation/">meta-tracing</a> [10] JIT, which enables a similar runtime performance when the composed outcome is compared to stand-alone interpreters.</p>
<p><a target="_blank" href="https://soft-dev.org/pubs/html/diekmann_tratt__eco_a_language_composition_editor/">Eco</a> [10] editor provides the fill of an ordinary text editor while using syntax-direct editing behind the scenes. Unlike traditional editors, Eco operates and saves ﬁles as tree structures rather than as a conventional text-based source ﬁles. This again creates friction when integrating with widely adopted engineering practices and tools, such as version control systems that are usually text-based.</p>
<p>The Unipycation case study shows that it can compose multiple languages without compromising performance with minimal engineering effort. But after all, this case study is experimental and not Production Ready in any form.</p>
<h2 id="heading-js-transpiled-web">JS-Transpiled Web</h2>
<p>JavaScript (JS) is the primary language that runs in our browsers. Recently we had a new addition of WebAssembly (WASM), but still, JS is the de-facto language of the web. JS has its limitations. So we came up with different js-transpired languages to address them, enabling us to write with strongly-typed code (TypeScript) or Haskell-like functional programming (ELM). Still, they all transpile into JS that runs in our browsers.</p>
<p>What will happen if we have a project written, let's say, in CoffeeScript, and we need to import a TypeScript (TS) library? We will need to convert our script to JS and import it. What if we use three different libraries implemented in three PLs? We would need to go through the same transpile process over and over again.</p>
<p>This is another place for the PLC application that can be a good fit.</p>
<h2 id="heading-summary">Summary</h2>
<p>In this article, we overviewed the problem of Programming Language Composition (PLC). As far as my research goes, I've enumerated all the main methods we have today for writing multi-lingual software.</p>
<p>The state of language composition and integration today is far from ideal, it's cumbersome and convoluted in my opinion, and I hope we see some improvements in that domain soon. We have solutions that work, and we keep on using them no matter the cost, without re-evaluating them. What we need is precisely that re-evaluation.</p>
<p>This write-up is for my own sake of understanding and the organisation of my thoughts as it was about knowledge sharing. It's based on my recent research proposal for a part-time PhD. I hope it was helpful.</p>
<p>If you have questions/objections/observations/complaints, don't hesitate to reach out!</p>
<h2 id="heading-references">References</h2>
<p>[1] <a target="_blank" href="https://en.wikipedia.org/wiki/Distributed_computing">https://en.wikipedia.org/wiki/Distributed_computing</a></p>
<p>[2] <a target="_blank" href="https://en.wikipedia.org/wiki/Fork%E2%80%93exec">https://en.wikipedia.org/wiki/Fork%E2%80%93exec</a></p>
<p>[3] <a target="_blank" href="https://ieeexplore.ieee.org/document/9246706">https://ieeexplore.ieee.org/document/9246706</a></p>
<p>[4] <a target="_blank" href="https://www.swig.org/papers/Tcl96/tcl96.html">https://www.swig.org/papers/Tcl96/tcl96.html</a></p>
<p>[5] <a target="_blank" href="https://swig.org/Doc4.0/SWIGDocumentation.html#Introduction_nn5">https://swig.org/Doc4.0/SWIGDocumentation.html#Introduction_nn5</a></p>
<p>[6] <a target="_blank" href="https://soft-dev.org/pubs/pdf/barrett_bolz_tratt__unipycation_a_study_in_cross_language_tracing.pdf">https://soft-dev.org/pubs/pdf/barrett_bolz_tratt__unipycation_a_study_in_cross_language_tracing.pdf</a></p>
<p>[7] <a target="_blank" href="https://www.pypy.org/">https://www.pypy.org/</a></p>
<p>[8] <a target="_blank" href="https://www.bibsonomy.org/bibtex/2f58dd8a58d274eab26c1eaf6f501e493/gron?lang=de">https://www.bibsonomy.org/bibtex/2f58dd8a58d274eab26c1eaf6f501e493/gron?lang=de</a></p>
<p>[9] <a target="_blank" href="https://tratt.net/laurie/research/pubs/html/bolz_tratt__the_impact_of_metatracing_on_vm_design_and_implementation/">https://tratt.net/laurie/research/pubs/html/bolz_tratt__the_impact_of_metatracing_on_vm_design_and_implementation/</a></p>
<p>[10] <a target="_blank" href="https://soft-dev.org/pubs/html/diekmann_tratt__eco_a_language_composition_editor/">https://soft-dev.org/pubs/html/diekmann_tratt__eco_a_language_composition_editor/</a></p>
]]></content:encoded></item><item><title><![CDATA[Cross Platform CLI application with Go and Cobra]]></title><description><![CDATA[Abstract
This article will cover the process and the involved components of building a Command-Line Interface (CLI)[1] application using the Go programming language. We will cover the required libraries, directory structure, configuration files, test...]]></description><link>https://blog.p3ld3v.dev/cross-platform-cli-application-with-go-and-cobra</link><guid isPermaLink="true">https://blog.p3ld3v.dev/cross-platform-cli-application-with-go-and-cobra</guid><category><![CDATA[Go Language]]></category><category><![CDATA[cli]]></category><category><![CDATA[cross platfom]]></category><category><![CDATA[cobra-cli]]></category><category><![CDATA[cobra]]></category><dc:creator><![CDATA[Pavel Durov]]></dc:creator><pubDate>Fri, 02 Dec 2022 16:42:48 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1669999194701/42b6ad5b-b270-46eb-9695-0db7ce67869f.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-abstract">Abstract</h2>
<p>This article will cover the process and the involved components of building a <a target="_blank" href="https://en.wikipedia.org/wiki/Command-line_interface">Command-Line Interface (CLI)</a>[1] application using the Go programming language. We will cover the required libraries, directory structure, configuration files, testing, and Cross-Platform build process.</p>
<h2 id="heading-command-line-interpreter-cli">Command-line interpreter (CLI)</h2>
<p><strong>CLI</strong> stands for <a target="_blank" href="https://en.wikipedia.org/wiki/Command-line_interface">Command-Line Interface</a> [1]. <strong>CLI</strong> application receives input from the user, do some computational tasks and produces an output. Compared to a <a target="_blank" href="https://en.wikipedia.org/wiki/Graphical_user_interface"><strong>Graphical User Interface (GUI)</strong></a> [2], <strong>CLI</strong> applications require fewer system resources since interactions with it don't involve graphics.</p>
<p>One of the cool things (at least in my opinion) about <strong>CLI</strong> applications is that when designed with composition in mind and having a well-defined In/Out interface. These applications can be composed together (similar to function composition) as one solution 🤓.</p>
<p>For example, if we have two <strong>CLI</strong> application <code>A</code> and <code>B</code>, we can create a composed solution of <code>A.B</code> having input of <code>A</code> and output of <code>B</code>.</p>
<h2 id="heading-cross-platform">Cross-Platform</h2>
<p><a target="_blank" href="https://en.wikipedia.org/wiki/Cross-platform_software">Cross-Platform</a>[5] applications are designed to work on more than one computing platform. For example, we can build the same software but run it on Linux, Windows and Android devices. These applications are also referred to as multi-platform, platform-agnostic or platform-independent.</p>
<p>The idea is very cool. Cause who wants to maintain more software than needed? But it also needs to be considered in the software design. <strong>Cross-Platform</strong> applications need to consider any Operatic System (OS) specifics. We will see examples of it when dealing with the local filesystem in the sections below 👇.</p>
<h2 id="heading-cli-project-setup"><strong>CLI</strong> project setup</h2>
<p>We're going to use <a target="_blank" href="https://cobra.dev/">Cobra</a> [3] <strong>CLI</strong> framework. <strong>Cobra</strong> is a very powerful, extendable and delightful framework to work with. You won't regret it, I promise 😶!</p>
<p>Go version used:</p>
<pre><code class="lang-bash">$ go version
go version go1.19 darwin/arm64
</code></pre>
<p>Initialise our project:</p>
<pre><code class="lang-bash">$ go mod init github.com/Pavel-Durov/cli-demo
go: creating new go.mod: module github.com/Pavel-Durov/cli-demo
</code></pre>
<p>Install <strong>Cobra</strong> and <strong>CobraCLI</strong>. <strong>CobraCLI</strong> will create our application and add <strong>CLI</strong> commands.</p>
<pre><code class="lang-bash">$ go get -u github.com/spf13/cobra/cobra
$ go install github.com/spf13/cobra-cli@latest
</code></pre>
<p>Now we can use <strong>CobraCLI</strong> to initialise our <strong>CLI</strong> app:</p>
<pre><code class="lang-bash">$ cobra-cli init
</code></pre>
<p>That's it. We have a working app! It should have the following structure:</p>
<pre><code class="lang-bash">$ tree 
├── cmd
│   └── root.go
├── go.mod
├── go.sum
└── main.go
</code></pre>
<p>We have the <code>main.go</code> file, which is the main entry point of our application. And one single <strong>CLI</strong> command called <code>root</code> that is the main entry point of <strong>Cobra</strong> framework. It's also a general convention to have application entry points for GO projects in the <code>cmd</code> directory.</p>
<p>Run our brand-new <strong>CLI</strong> app:</p>
<pre><code class="lang-bash">$ go run ./main.go 
A longer description that spans multiple lines and likely contains
examples and usage of using your application. For example:

Cobra is a CLI library <span class="hljs-keyword">for</span> Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.
Nothing match to see yet; we get the default message. Let<span class="hljs-string">'s add some functionality.</span>
</code></pre>
<p>For this demo, we're going to build a calculator <strong>CLI</strong> application. I know, very exciting!</p>
<p>Adding our first command</p>
<pre><code class="lang-go"><span class="hljs-comment">//file: ./cmd/add.go</span>
<span class="hljs-keyword">package</span> cmd

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"github.com/spf13/cobra"</span>
)

<span class="hljs-keyword">var</span> addCmd = &amp;cobra.Command{
    Use:   <span class="hljs-string">"add"</span>,
    Short: <span class="hljs-string">"Add operator"</span>,
    Long:  <span class="hljs-string">`Add operator, adds two integers and prints the result.`</span>,
    Run: <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(cmd *cobra.Command, args []<span class="hljs-keyword">string</span>)</span></span> {
        num1, _ := cmd.Flags().GetInt32(<span class="hljs-string">"n1"</span>)
        num2, _ := cmd.Flags().GetInt32(<span class="hljs-string">"n2"</span>)
        cmd.Printf(<span class="hljs-string">"%d + %d = %d\n"</span>, num1, num2, num1+num2)
    },
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">init</span><span class="hljs-params">()</span></span> {
    addCmd.Flags().Int32(<span class="hljs-string">"n1"</span>, <span class="hljs-number">0</span>, <span class="hljs-string">"--n1 1"</span>)
    addCmd.Flags().Int32(<span class="hljs-string">"n2"</span>, <span class="hljs-number">0</span>, <span class="hljs-string">"--n1 2"</span>)
    addCmd.MarkFlagRequired(<span class="hljs-string">"n1"</span>)
    addCmd.MarkFlagRequired(<span class="hljs-string">"n2"</span>)
}
</code></pre>
<p>We could use <strong>CobraCLI</strong> for that as well. But I decided to go manual here.</p>
<p>Note that we defined the <code>Use</code> property, which means that in order to use <code>add</code> command, we need to specify first <code>add</code> in our <strong>CLI</strong> parameters.</p>
<p>Wire <strong>CLI</strong> commands together.</p>
<pre><code class="lang-go"><span class="hljs-comment">//file: ./cmd/root.go</span>
<span class="hljs-keyword">package</span> cmd

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"os"</span>

    <span class="hljs-string">"github.com/spf13/cobra"</span>
)

<span class="hljs-keyword">var</span> rootCmd = &amp;cobra.Command{
    Use:   <span class="hljs-string">"[command]"</span>,
    Short: <span class="hljs-string">"A CLI calculator"</span>,
    Long:  <span class="hljs-string">`A CLI calculator that can add and subtract two numbers.`</span>,
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Execute</span><span class="hljs-params">()</span></span> {
    err := rootCmd.Execute()
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        os.Exit(<span class="hljs-number">1</span>)
    }
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">init</span><span class="hljs-params">()</span></span> {
    rootCmd.AddCommand(addCmd) <span class="hljs-comment">// adding add command to root</span>
}
</code></pre>
<p>Since our commands are part of the same package called <code>cmd</code> - the import and configuration are very straightforward.</p>
<p>Rerun our command, this time with <code>-h</code> flag:</p>
<pre><code class="lang-bash">$ go run ./main.go  -h
A CLI calculator that can add and subtractwo numbers.

Usage:
  calc [<span class="hljs-built_in">command</span>]

Available Commands:
  add         Add operator
  completion  Generate the autocompletion script <span class="hljs-keyword">for</span> the specified shell
  <span class="hljs-built_in">help</span>        Help about any <span class="hljs-built_in">command</span>

Flags:
  -h, --<span class="hljs-built_in">help</span>   <span class="hljs-built_in">help</span> <span class="hljs-keyword">for</span> calc

Use <span class="hljs-string">"calc [command] --help"</span> <span class="hljs-keyword">for</span> more information about a <span class="hljs-built_in">command</span>.
</code></pre>
<p>As you can see, cobra did a lot of work for us. It did configure how to parse flags, help messages etc.</p>
<p>Run the actual command with specified parameters:</p>
<pre><code class="lang-bash">$ go run ./main.go  add --n1=1 --n2=3
1 + 3 = 4
</code></pre>
<p>We have a fully-fledged <strong>CLI</strong> application that can add two numbers and print the result to the <code>stdout</code>.</p>
<p>Let's add another command! This time we will add substitution.</p>
<p>It will be as simple as:</p>
<pre><code class="lang-go"><span class="hljs-comment">//file: ./cmd/sub.go</span>
<span class="hljs-keyword">package</span> cmd

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"github.com/spf13/cobra"</span>
)

<span class="hljs-keyword">var</span> subCmd = &amp;cobra.Command{
    Use:   <span class="hljs-string">"sub"</span>,
    Short: <span class="hljs-string">"Sub operator"</span>,
    Long:  <span class="hljs-string">`Sub operator, subtracts two integers and prints the result.`</span>,
    Run: <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(cmd *cobra.Command, args []<span class="hljs-keyword">string</span>)</span></span> {
        num1, _ := cmd.Flags().GetInt32(<span class="hljs-string">"n1"</span>)
        num2, _ := cmd.Flags().GetInt32(<span class="hljs-string">"n2"</span>)
        cmd.Printf(<span class="hljs-string">"%d - %d = %d\n"</span>, num1, num2, num1-num2)
    },
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">init</span><span class="hljs-params">()</span></span> {
    subCmd.Flags().Int32(<span class="hljs-string">"n1"</span>, <span class="hljs-number">0</span>, <span class="hljs-string">"--n1 1"</span>)
    subCmd.Flags().Int32(<span class="hljs-string">"n2"</span>, <span class="hljs-number">0</span>, <span class="hljs-string">"--n1 2"</span>)
    subCmd.MarkFlagRequired(<span class="hljs-string">"n1"</span>)
    subCmd.MarkFlagRequired(<span class="hljs-string">"n2"</span>)
}
</code></pre>
<p>Same as before, add the new command to the <code>root</code>:</p>
<pre><code class="lang-go">...
rootCmd.AddCommand(subCmd)
...
</code></pre>
<p>Give it a go:</p>
<pre><code class="lang-bash">$ go run ./main.go  sub --n1=10 --n2=4
10 - 4 = 6
</code></pre>
<p>It works exactly as intended! You can imagine how using the same process we can extend our <strong>CLI</strong> application further.</p>
<h2 id="heading-adding-some-tests">Adding some tests</h2>
<p>I like tests, and I think you should too 😎. Adding unit tests in GO is very straightforward; however, testing <strong>CLI</strong> commands like Cobra might be a bit tricky. That's why I wanted to demonstrate how to do it.</p>
<p>Adding test to root <strong>CLI</strong> command:</p>
<pre><code class="lang-go"><span class="hljs-comment">// file: cmd/root_test.go</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">TestTypeLocal</span><span class="hljs-params">(t *testing.T)</span></span> {
    buf := <span class="hljs-built_in">new</span>(bytes.Buffer)
    rootCmd.SetOut(buf)
    rootCmd.SetArgs([]<span class="hljs-keyword">string</span>{<span class="hljs-string">"sub"</span>, <span class="hljs-string">"--n1=10"</span>, <span class="hljs-string">"--n2=4"</span>})

    err := rootCmd.Execute()
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        fmt.Println(err)
    }
    <span class="hljs-keyword">if</span> buf.String() != <span class="hljs-string">"10 - 4 = 6\n"</span> {
        t.Errorf(<span class="hljs-string">"Expected 10 - 4 = 6, got %s"</span>, buf.String())
    }
}
</code></pre>
<p>Here, we set a buffer as an out stream for the cobra command and pass <strong>CLI</strong> arguments (aka flags), then we assert the output result - nothing fancy.</p>
<p>Run the tests:</p>
<pre><code class="lang-bash">$ go <span class="hljs-built_in">test</span> ./...
ok      github.com/Pavel-Durov/cli-demo/cmd     0.207s
</code></pre>
<h2 id="heading-adding-profilesettings">Adding profile/settings</h2>
<p>What do we do if we want to store application configuration across sessions, or maybe we want to have secrets such as API keys defined outside of our application code. Whatever the reason, cobra got your back! Actually <a target="_blank" href="https://github.com/spf13/viper">Viper</a>[4] got your back. <strong>Viper</strong> is a configuration management tool for Go applications. <strong>Viper</strong> and <strong>Cobra</strong> works great together.</p>
<p>Install <strong><em>Viper</em></strong>:</p>
<pre><code class="lang-bash">$ go get github.com/spf13/viper
</code></pre>
<p>Configure Viper in our init function, root cmd:</p>
<pre><code class="lang-go"><span class="hljs-comment">// file: cmd/root.go</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">initConfig</span><span class="hljs-params">()</span></span> {
    home, err := os.UserHomeDir()
    cobra.CheckErr(err)
    viper.AddConfigPath(home)
    viper.SetConfigType(<span class="hljs-string">"yaml"</span>)
    viper.SetConfigName(<span class="hljs-string">".calc"</span>)
    viper.ReadInConfig()
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">init</span><span class="hljs-params">()</span></span> {
    cobra.OnInitialize(initConfig)
    ...
}
</code></pre>
<p>If we create a local <strong>YAML</strong> file called <code>.calc</code> in our <code>$HOME</code> directory (cause that's what we configured) with the content:</p>
<pre><code class="lang-shell">$ cat ~/.calc
username: kimchi
</code></pre>
<p>We can now read these values in our application:</p>
<pre><code class="lang-shell">username := viper.Get("username")
if username != nil {
    fmt.Println("Hello", username)
}
</code></pre>
<p>We don't have to use <strong>YAML</strong> or the <code>$HOME</code> directory, this setup can be configured in multiple ways.</p>
<h3 id="heading-note-on-dollarhome-directory">Note on <code>$HOME</code> directory</h3>
<p>Notice how we used <code>os.UserHomeDir()</code> to get the user's home directory. This is important if we want to build a <a target="_blank" href="https://en.wikipedia.org/wiki/Cross-platform_software"><strong>Cross-Platform</strong></a>[6] application. We could've hardcoded the path to the file. But why should we? GO has great platform-agnostic library support - <code>os.UserHomeDir()</code> will return the path to the <code>$HOME</code> directory specific to the machine it's running on without us changing a single line of code!</p>
<p>👉 On Unix (including macOS), it returns the <code>$HOME</code> environment variable</p>
<p>👉 On Windows, it returns <code>%USERPROFILE%</code></p>
<p>👉 On Plan 9, it returns the <code>$HOME</code> environment variable</p>
<h2 id="heading-building-our-cli-application">Building our <strong>CLI</strong> application</h2>
<p>Go has an incredible build system that comes with everything we need.</p>
<p>We can easily build our application for multiple architectures and operating systems (OS):</p>
<h3 id="heading-linux-target-build">Linux target build</h3>
<pre><code class="lang-bash">$ CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o out/linux-arm64-calc -ldflags=<span class="hljs-string">"-extldflags=-static"</span> <span class="hljs-comment"># linux, arm64 arch</span>
$ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o out/linux-amd64-calc -ldflags=<span class="hljs-string">"-extldflags=-static"</span> <span class="hljs-comment"># linux, amd64 arch</span>
</code></pre>
<h3 id="heading-mac-aka-darwin-target-build">Mac (aka darwin) target build</h3>
<pre><code class="lang-bash">$ CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -o out/darwin-arm64-calc -ldflags=<span class="hljs-string">"-extldflags=-static"</span> <span class="hljs-comment"># mac, arm64 arch</span>
$ CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o out/darwin-amd64-calc -ldflags=<span class="hljs-string">"-extldflags=-static"</span> <span class="hljs-comment"># mac, amd64 arch</span>
</code></pre>
<h3 id="heading-windows-target-build">Windows target build</h3>
<pre><code class="lang-bash">$ CGO_ENABLED=0 GOOS=windows GOARCH=arm64 go build -o out/windows-arm64-calc -ldflags=<span class="hljs-string">"-extldflags=-static"</span> <span class="hljs-comment"># windows, arm64 arch</span>
$ CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o out/windows-amd64-calc -ldflags=<span class="hljs-string">"-extldflags=-static"</span> <span class="hljs-comment"># windows, amd64 arch</span>
</code></pre>
<p>if we run all these build commands, we'll get these binaries:</p>
<pre><code class="lang-bash">$ ls -l ./out/
-rwxr-xr-x  1 ... darwin-amd64-calc
-rwxr-xr-x  1 ... darwin-arm64-calc
-rwxr-xr-x  1 ... linux-amd64-calc
-rwxr-xr-x  1 ... linux-arm64-calc
-rwxr-xr-x  1 ... windows-amd64-calc
-rwxr-xr-x  1 ... windows-arm64-calc
</code></pre>
<h3 id="heading-environment-variables">Environment Variables</h3>
<h4 id="heading-goos">GOOS</h4>
<p>You probably noticed that the only thing that changed between the build targets is the GOOS environment variable. And that's all you need to change with go-build tooling! It is seriously that easy to use!</p>
<h4 id="heading-goarch">GOARCH</h4>
<p>This is where we specify the <strong>CPU</strong> architecture we're targeting.</p>
<p>See all the supported <code>GOOS</code> and <code>GOARCH</code> combos:</p>
<pre><code class="lang-bash">$ go tool dist list
aix/ppc64
android/386
android/amd64
android/arm
android/arm64
darwin/amd64
....The list goes on
</code></pre>
<h4 id="heading-cgoenabled">CGO_ENABLED</h4>
<p>We also used <code>CGO_ENABLED</code> environment variable. <code>CGO_ENABLED=1</code> leads to faster and smaller builds - it allows dynamically loading of the host OS's native libraries. However, it relies on a host OS, a dependency we would like to avoid! Otherwise, our code behaviour might differ from machine to machine if our code relies on the host libraries.</p>
<h3 id="heading-linker-flags-ldflags">Linker flags - ldflags</h3>
<p>We used flags called <code>ldflags</code> - <code>ld</code> stands for <a target="_blank" href="https://pkg.go.dev/cmd/link">linker</a> [6] therefore <code>ldflags</code> stands for linker flags. A linker is a program that "links" the pieces of the compiled source code into the binary outcome. We are passing <code>extldflags</code> to our linker. According to the link tool documentation, these flags are passed to the external linker. Long story short, we're using these flags to indicate to the GO build tool to include all the dependencies into the binary and not rely on them provided by the environment it's running in. We set the flag as <code>-static</code>, indicating that the binary should include all its dependencies. If not specified, our binary would be dynamically linked. For the same reasons as with <code>CGO_ENABLED</code>, we would like to avoid it here.</p>
<h2 id="heading-summary">Summary</h2>
<p>We've seen how to setup <strong>Cobra</strong>-based <strong>CLI</strong> applications from scratch. We touched on <strong>Cross-Platform</strong> application properties, such as platform-agnostic filesystem paths. We added tests and briefly overviewed Linker and the GO build tooling with different target configurations.</p>
<p>Building GO applications for multiple targets is straightforward and fun once you get the basics.</p>
<p>This write-up was for my own sake of understanding and organising my thoughts as it was about knowledge sharing. I hope it was helpful. If you have questions/objections/observations/complaints, don't hesitate to reach out!</p>
<p>Full source code can be found 👉 <a target="_blank" href="https://github.com/Pavel-Durov/blog/tree/main/src/cross-platform-cli-apps-with-go-and-cobra/examples">here</a>.</p>
<h2 id="heading-references">References</h2>
<p>[1] <a target="_blank" href="https://en.wikipedia.org/wiki/Command-line_interface">https://en.wikipedia.org/wiki/Command-line_interface</a></p>
<p>[2] <a target="_blank" href="https://en.wikipedia.org/wiki/Graphical_user_interface">https://en.wikipedia.org/wiki/Graphical_user_interface</a></p>
<p>[3] <a target="_blank" href="https://cobra.dev/">https://cobra.dev/</a></p>
<p>[4] <a target="_blank" href="https://github.com/spf13/viper">https://github.com/spf13/viper</a></p>
<p>[5] <a target="_blank" href="https://en.wikipedia.org/wiki/Cross-platform_software">https://en.wikipedia.org/wiki/Cross-platform_software</a></p>
<p>[6] <a target="_blank" href="https://pkg.go.dev/cmd/link">https://pkg.go.dev/cmd/link</a></p>
]]></content:encoded></item><item><title><![CDATA[Progressive Delivery]]></title><description><![CDATA[Abstract
This article will cover Progressive Delivery techniques such as Feature Flags, A/B Testing, Ring Deployment, Dark Launching, Blue/Green and Canary deployments with high-level illustrations and a general concept overview.
Introduction
Continu...]]></description><link>https://blog.p3ld3v.dev/progressive-delivery</link><guid isPermaLink="true">https://blog.p3ld3v.dev/progressive-delivery</guid><category><![CDATA[ProgressiveDelivery]]></category><category><![CDATA[continuous deployment]]></category><category><![CDATA[cd]]></category><category><![CDATA[deployment]]></category><category><![CDATA[feature release]]></category><dc:creator><![CDATA[Pavel Durov]]></dc:creator><pubDate>Mon, 21 Nov 2022 08:05:47 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1668794872893/J4NRjmKwT.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-abstract">Abstract</h1>
<p>This article will cover <strong>Progressive Delivery</strong> techniques such as <strong>Feature Flags</strong>, <strong>A/B Testing</strong>, <strong>Ring Deployment</strong>, <strong>Dark Launching</strong>, <strong>Blue/Green</strong> and <strong>Canary</strong> deployments with high-level illustrations and a general concept overview.</p>
<h1 id="heading-introduction">Introduction</h1>
<p><strong>Continuous Delivery (CD)</strong> practice ensures that our code is constantly in a deployable state. Using <strong>CD</strong>, we can release faster with higher frequency.
That's all great fun, but we can do better!  When a new version is deployed in the traditional delivery manner - the old version is replaced with a new one, meaning that all of our users are immediately affected by the new release. <strong>Progressive Delivery</strong> offers techniques for deploying applications gradually over time while minimising deployment downtime. </p>
<p>Before jumping into <strong>Progressive Delivery</strong> implementation, we should ensure that we have a firm grasp of Agile, CI/CD, and DevOps practices, as <strong>Progressive Delivery</strong> includes more advanced software development practices and infrastructure management.</p>
<h1 id="heading-feature-flags">Feature Flags</h1>
<p>The idea behind <strong>Feature Flags (FF)</strong> is quite simple: We add logical branches to our code - <code>if/else</code> statements and control them via configuration. This can be achieved using one of the SaaS (Software as a Service) platforms, such as <a target="_blank" href="https://www.getunleash.io/">Unleash</a>[1] or <a target="_blank" href="https://launchdarkly.com/">LaunchDarkly</a>[2] for configuration management. These services require additional integration and might be costly, but they offer support and a range of features. But a similar idea can be achieved with any remotely managed configuration, a versioned JSON file, for example.</p>
<p>Application using <em>*Feature Flag</em>s* can be illustrated as follows:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1668712423074/KGg2FRR4T.png?width=500" alt="ffs.png" /></p>
<p>In this example, depending on whether the <code>FLAG</code> is <code>ON</code> or <code>OFF</code>,  we will add additional data to the response. <strong>Feature Flags</strong> are <strong>Progressive Delivery</strong> basics - it's the first step towards minimising deployment risk. It allows us to test in production, a powerful ability because test environments never represent production 100%!</p>
<p><strong>Feature Flags</strong> don't involve any infrastructural changes, only application code and configuration management integration.</p>
<p><strong>Feature Flags</strong> allow us to get <strong>immediate feedback</strong> for our changes functionality. And if something goes wrong, we can turn <code>OFF</code> the <code>FLAG</code> instead of rolling back the whole deployment. And guess what? We can turn it <code>ON</code>/<code>OFF</code> as many times as we like! Pretty cool concept 🤓!</p>
<p>Some call it "zero-risk" deployments. But I would avoid claiming that. If not managed carefully, it might also have a significant negative impact. Generally, <strong>Feature Flags</strong> should be short-lived to avoid confusion. If you don't need the <strong>Feature Flag</strong> anymore - delete it.</p>
<p>As always, all this goodness comes with additional complexity. Once our application has feature flags integration, we need to be confident in multiple flows and maintain more tests 😶.</p>
<h1 id="heading-ab-testing">A/B Testing</h1>
<p><a target="_blank" href="https://www.optimizely.com/optimization-glossary/ab-testing/"><strong>A/B Testing</strong></a>[3], aka bucket testing, aka split-run testing, is a user-experience research methodology [4]. <strong>A/B Testing</strong> can be thought of as an extension of <strong>Feature Flags</strong>. It allows us to experiment with features on different user audiences, monitor the behaviour, and conclude what works and does not. </p>
<p>For example, if we have two designs of a button, one is <code>Purple</code>, and the other is <code>Gray</code>, and we want to experiment to understand which one is better suited for our application - we can redirect <code>50%</code> of our users to the <code>Purple</code> button and the other half to the <code>Gray</code> one and analyse the engagement stats to make a decision. The same thing can be done with demographics-based or geo-location-based logic.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1668714933362/5BvtBYe_Y.png?height=400" alt="a-b.png" /></p>
<p>If you're familiar with <strong>Blue/Green Deployments</strong>, <strong>A/B Testing</strong> might look the same at first glance🤔. But it's quite different. <strong>A/B Testing</strong> is a behaviour-monitoring paradigm. <strong>Blue/Green Deployments</strong> is an engineering practice. They can be used both together and in segregation.</p>
<h1 id="heading-ring-deployment">Ring Deployment</h1>
<p>With <a target="_blank" href="https://learn.microsoft.com/en-us/azure/devops/migrate/phase-rollout-with-rings?view=azure-devops"><strong>Ring Deployment</strong></a>[8], we can gradually deploy, validate and monitor deployment impact while limiting the effect on our users.</p>
<p>Deployment rings:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1668712471702/IBg7w4JHY.png?height=200" alt="ring.png" /></p>
<p>With <strong>Ring Deployment</strong>, we can choose to release first to our alpha users. Afterwards, we can move on to the next ring, which will include our external beta users and so on and so forth. The impact, also referenced as <strong>Blast Radius</strong>, will increase as the software release propagate through the defined rings. </p>
<h1 id="heading-dark-launching">Dark Launching</h1>
<p>With <a target="_blank" href="https://martinfowler.com/bliki/DarkLaunching.html">Dark Launching</a>[7], we deploy back-end changes without affecting the end users with the new behaviour. This deployment method allows for assessing system performance and load impacts on the system before publicly releasing the new version.</p>
<p>Using <strong>Dark Launching</strong>, we can run different feature implementations in parallel and analyse the impact while returning only a single response to the user.</p>
<p><strong>Dark Launching</strong> in its all glory:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1668712496880/JqSFf4Brh.png?width=500" alt="dark-launching.png" /></p>
<p>In this example, we're fetching data from both data sources - SQL and MongoDB. However, we're returning only the MongoDb data to the end user.</p>
<p>This method is robust for testing system load and the system's resiliency. Especially beneficial when testing user interaction enhancement changes. As always, it can be applied with  <strong>Feature Flags</strong> to control the darkness 🧛.</p>
<h1 id="heading-bluegreen-deployments">Blue/Green Deployments</h1>
<p><a target="_blank" href="https://docs.cloudfoundry.org/devguide/deploy-apps/blue-green.html">Blue/Green Deployments</a>[5] is a deployment method where the new version is launched side-by-side with the old version - both versions co-exist during the deployment. It's one of the simplest ways to minimise deployment downtime on the infrastructure level. This method can be used in various applications such as Virtual Machines (VMs), Containers and Kubernetes clusters.</p>
<p>Blue/Green deployment phases:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1668713630245/hCHm8Xi-r.png" alt="blue-green.png" /></p>
<ol>
<li><p>All users' traffic is routed to V1 (Blue deployment)</p>
</li>
<li><p>V2 is deployed (Green deployment). This version has no traffic routed from the load balancer.</p>
</li>
<li><p>V2 internal tests are performed. If tests pass 1.  traffic is routed to V2.</p>
</li>
<li><p>If all is good, the old version is discarded. </p>
</li>
</ol>
<blockquote>
<p>The actual strategy and steps might vary - some prefer to keep the last deployment or even further history of deployments.</p>
</blockquote>
<p>The main benefit is that we can test release candidates in a production environment before affecting our users. If we decide to release the new version and find issues only after a while, we can quickly switch back. The load balancer switch is way faster than redeploying a new version! Therefore minimising the impact.</p>
<h1 id="heading-canary-deployments">Canary Deployments</h1>
<p>A <a target="_blank" href="https://martinfowler.com/bliki/CanaryRelease.html">Canary Deployment</a> [6] is a deployment when only a subset of users get access to the new version of the application.</p>
<p>Why are they called <strong>Canary Deployments</strong>? Glad you asked. </p>
<blockquote>
<p>Dating back to 1900, coal miners used canaries to detect carbon monoxide and other toxic gases before they hurt humans. Sad but true story.</p>
</blockquote>
<p><strong>Blue/Green Deployments</strong> minimise deployment downtime, but it's not perfect. If the deployment has an issue that is evident only after some time or with increasing load - 
then all users end up being affected since the traffic switch is all or nothing.</p>
<p><strong>Canary</strong> improve on <strong>Blue/Green Deployments</strong>. Instead of switching <code>100%</code> of traffic all at once to the new version, we redirect only a subset of users over time.</p>
<p>Canary illustration:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1668713864097/0vSP-pCP0.png" alt="canary.png" /></p>
<ol>
<li><p>V1 is the current version (Blue deployment).</p>
</li>
<li><p>V2 has been deployed. V2 gets only a fraction of the traffic, for example, 20%.</p>
</li>
<li><p>Deployment is monitored. If all is good, we will increase the traffic to V2.</p>
</li>
<li><p>Eventually, <code>100%</code> traffic is redirected to the new version.</p>
</li>
</ol>
<p>The exact configuration of traffic percentage and time frames are configurable, and the whole process can be manual or automated.</p>
<p>Canary deployments are more complicated than <strong>Blue/Green Deployments</strong> on the infrastructure level. The load balancer here has to be "smarter", as it needs to understand how to route portions of the traffic towards configurable destinations.</p>
<p>There are also cost implications to this approach - side-by-side deployments cost is higher than traditional deployments as we need extra infrastructure at the deployment time.</p>
<p>Handling databases can be tricky when it comes to schema changes. The problem is that databases must simultaneously work with multiple application versions during deployment. So, for example, if we have breaking schema changes, we’re in trouble. We need to maintain backward and forward compatibility as we make changes, which adds another layer of complexity to software development and maintenance.</p>
<h1 id="heading-summary">Summary</h1>
<p>We covered quite a few deployment strategies 😅. They all have their place and use cases, some more complex than others. Some include the need for "smart" networking such as load balancer, and even service mesh, while others are strictly application-level. </p>
<p>These techniques can be combined together <strong>Feature Flags</strong> with <strong>Canary Deployment</strong>, <strong>Feature Flags</strong> and <strong>Dark Launching</strong>, <strong>Canary</strong> and <strong>Ring Deployments</strong>, and the list go on. </p>
<p>The more "stuff" we include in our software, the more complexity we'll have and that's ok if it's needed, but if it isn't, then why bother?</p>
<p>If you are lost and don't know where to start, I would suggest starting from the basics - <strong>CI/CD</strong> and DevOps practices.</p>
<p>For further reading, I suggest 👉 <a target="_blank" href="https://www.goodreads.com/book/show/8686650-continuous-delivery?ac=1&amp;from_search=true&amp;qid=YgDjkA3y3F&amp;rank=1">Continuous Delivery: Reliable Software Releases Through Build, Test, and Deployment Automation</a></p>
<p>This write-up was for my own sake of understanding and the organisation of my thoughts as it was about knowledge sharing. </p>
<p>I hope it was useful!</p>
<h1 id="heading-references">References</h1>
<p>[1] https://www.getunleash.io/</p>
<p>[2] https://launchdarkly.com/</p>
<p>[3] https://www.optimizely.com/optimization-glossary/ab-testing/)</p>
<p>[4] https://quod.lib.umich.edu/w/weave/12535642.0001.101?view=text;rgn=main</p>
<p>[5] <a target="_blank" href="https://docs.cloudfoundry.org/devguide/deploy-apps/blue-green.html">Blue/Green Deployments</a></p>
<p>[6] https://martinfowler.com/bliki/CanaryRelease.html</p>
<p>[7] https://martinfowler.com/bliki/DarkLaunching.html</p>
<p>[8] https://learn.microsoft.com/en-us/azure/devops/migrate/phase-rollout-with-rings?view=azure-devops</p>
]]></content:encoded></item><item><title><![CDATA[Hexagonal Architecture/Ports and Adapters, DDD, DI and IoC]]></title><description><![CDATA[Abstract
In this article, I will overview the concept of Hexagonal Architecture (HA), the problem it addresses, its benefits, implications and some practical examples illustrated via code and diagrams.
There're many sources on this topic. My source i...]]></description><link>https://blog.p3ld3v.dev/hexagonal-architectureports-and-adapters-ddd-di-and-ioc</link><guid isPermaLink="true">https://blog.p3ld3v.dev/hexagonal-architectureports-and-adapters-ddd-di-and-ioc</guid><category><![CDATA[Hexagonal Architecture]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[inversion of conrtol]]></category><category><![CDATA[dependency injection]]></category><category><![CDATA[design patterns]]></category><dc:creator><![CDATA[Pavel Durov]]></dc:creator><pubDate>Mon, 14 Nov 2022 19:25:57 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1668259379321/jS5-tEcok.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-abstract">Abstract</h1>
<p>In this article, I will overview the concept of <strong>Hexagonal Architecture (HA)</strong>, the problem it addresses, its benefits, implications and some practical examples illustrated via code and diagrams.</p>
<p>There're many sources on this topic. My source is not the most detailed one, but my intention is to simplify the idea as much as I can and break it down to its core concepts and explain its jargon.</p>
<h1 id="heading-introduction">Introduction</h1>
<p><strong>Hexagonal architecture (HA)</strong> aka <strong>Ports</strong> and <strong>Adapters</strong> (P&amp;A), is a software architectural pattern. <strong>HA</strong> is an alternative to traditional <a target="_blank" href="https://en.wikipedia.org/wiki/Multitier_architecture">N-Tier architecture</a> [1] aka <strong>Layered Architecture.</strong> <strong>HA</strong> divides a system into several loosely-coupled components, placing the application domain, aka application core, at its centre. Usually, this architecture is implemented or referred to within a single project structure, although the same concepts can be applied elsewhere.</p>
<h1 id="heading-the-problem">The problem</h1>
<p>In 2005, Alistair Cockburn presented <strong>Hexagonal Architecture</strong> as a solution to problems with traditional layering, coupling and entanglement 👉 more <a target="_blank" href="https://alistair.cockburn.us/hexagonal-architecture/">here</a> [2].</p>
<p>The problem with layered architecture is that we (engineers) tend to take the separation of layers lightly. In layered applications, the domain logic usually "leaks" between layer boundaries.
Projects without well-defined domains are hard to reason about and maintain. Additionally, in practice, the architecture is usually quite complex for large projects, and it's hard to project it into rigid, one-dimensional layer drawings.</p>
<p>In <strong>HA</strong> we don't have this dimensional restriction. We have the application on the inside communicating using abstraction over interfaces with the external world. Strict boundaries, abstraction and separation of concerns are the key here.</p>
<h2 id="heading-ha-vs-n-tier-architecture">HA vs N-Tier Architecture</h2>
<p>Conceptually, our software has little difference between UI, Database, Network or other layers. All these software components can be looked at as "black boxes" that have inputs and outputs. With Layered architectural diagrams, we naturally separate layers by type and not by functionality; UI at the top, Database at the bottom, and we throw something in the middle. </p>
<p>When we run the same software in different modes, such as Production, Testing etc, we want to have the ability to replace one type of interaction with another; UI with a test suite, Actual Database with an in-memory instance, etc... In other words, we want the same functionality but different implementations. Well, that's what HA is all about ☝️.</p>
<h1 id="heading-ha-components">HA components</h1>
<h2 id="heading-domain">Domain</h2>
<p>This is the <strong>Core</strong>, the centre, and the meaning of the application. Without it, we will have a bunch of technologies wired together that don't do anything business-related. And although it might sound cool. What's the point?</p>
<p>I like the way my colleague explained it to me once; domain code should be kept isolated, simple and easy to explain to someone non-technical. For example, your product manager or your cat 😻. Some separate the core into domain and application, but we'll keep it simple and treat it as one thing.</p>
<h2 id="heading-ports">Ports</h2>
<p><strong>HA</strong> components are connected through <strong>Ports</strong>. <strong>Port</strong> can be anything that has a functionality contract, but for simplicity and probably practicality - imagine interfaces.</p>
<p>The term "<strong>Port</strong>" is inspired by the operating system (OS) concepts, where any device that respects the protocol of a port can be connected. For example, Universal Serial Bus (USB) 👉 more on <a target="_blank" href="https://alistair.cockburn.us/hexagonal-architecture/">that</a>[2].</p>
<h2 id="heading-adapters">Adapters</h2>
<p><strong>Adapters</strong> deal with the outside world. <strong>Adapters</strong> will implement the <strong>Port</strong> contract and provide its fulfillment. There might be multiple adapters for any one port.</p>
<p>The Adapter <strong>OOP</strong> design pattern is one of the well-known design patterns from the classic <a target="_blank" href="https://www.goodreads.com/book/show/85009.Design_Patterns">Design Patterns</a>[3] book. This pattern "adapts" two objects by converting one class's interface into another. The <strong>HA</strong> adapters are one implementation of this pattern.</p>
<h3 id="heading-primary-and-secondary">Primary and Secondary</h3>
<p>On the architectural level, all ports are equal in <strong>HA</strong>. However, in reality, adapters show up in two flavours: <strong>Primary</strong> and <strong>Secondary</strong>:</p>
<p><strong>Primary</strong> - Actors that drive the application. UI, HTTP Endpoints, Events etc...</p>
<p><strong>Secondary</strong> - Actors that are driven by the application. Database, EmailService etc...</p>
<p>This concept is referred to as Left-Right Asymmetry [2]</p>
<blockquote>
<p>The distinction lies in which component initiates the communication.</p>
</blockquote>
<h1 id="heading-why-is-it-called-hexoganal-where-is-the-hex">Why is it called Hexoganal? Where is the Hex?</h1>
<p>A hexagon is a six-sided polygon, so one might think that there are <code>6</code> components in <strong>HA</strong>, but we counted only <code>3</code> so far 🤔! If anything, it should've been called Triangular Architecture!</p>
<p>According to the author, the term "<strong>Hexagonal</strong>" is used not because of the number <code>6</code> significance or the beast 🤘. It's about aesthetics and convenience [2]. The idea is to allow architectural drawings with enough room to insert both <strong>Ports</strong> and <strong>Adapters</strong> as needed without the constraints of a one-dimensional layered drawing.</p>
<h1 id="heading-ha-features">HA features</h1>
<h2 id="heading-inversion-of-control-ioc">Inversion of Control (IoC)</h2>
<p>In <strong>HA</strong> we use <strong>Ports</strong>. Naturally, it inverts the dependency direction. With <strong>IoC</strong> our components are not dependent on other component implementations. Instead, they are dependent upon abstraction. We leverage decoupling, modularisation and ease of testing since the implementation of dependent contracts can be changed without affecting the dependent component. Martin Fowler has much more to say about <a target="_blank" href="https://martinfowler.com/articles/injection.html">Inversion of Control</a>[4].</p>
<h3 id="heading-hollywood-principle">Hollywood Principle</h3>
<p>Referencing the phrase "don't call us, we'll call you", a common dismissal used in film auditions. The <a target="_blank" href="https://wiki.c2.com/?HollywoodPrinciple">Hollywood Principle</a>[5] is where components' lifecycle is handled by its context rather than by the object itself. If component <strong>A</strong> uses component <strong>B</strong>, the instance of the <strong>B</strong> will be provided to <strong>A</strong> rather than obtained by <strong>A</strong>.</p>
<h3 id="heading-liscuv-substitute-principle">Liscuv Substitute Principle</h3>
<p>Remember <a target="_blank" href="https://en.wikipedia.org/wiki/Liskov_substitution_principle">Liscuv Substitute Principle</a> [6]?
Although the Liscuv principle is mainly about types and subtypes' relation, the idea is similar to me 🙂.</p>
<h2 id="heading-separation-of-concerns">Separation of Concerns</h2>
<p><a target="_blank" href="http://wiki.c2.com/?SeparationOfConcerns">Separation Of Concerns</a>[8] is about, surprisingly, concerns separation.
The problem is that our concerns are relative, but some concerns are quite obvious. If we have applications with UI concerns and Data persistence concerns, it's kind of obvious that these concerns should be separated.</p>
<p>In <strong>HA</strong>, our application layers are isolated and have clear responsibilities and definitions. The boundaries are connected and communicated through strict interfaces. Therefore it forces us to think about our concerns and their separation.</p>
<h2 id="heading-domain-driven-design-ddd">Domain-Driven Design (DDD)</h2>
<p>The application core is agnostic of technologies, its concern is only the business logic. It's easier to understand and grasp the business requirements and make changes to it when the application domain is well-defined and isolated. It can also be built and tested in isolation. <strong>DDD</strong> is a whole topic of its own.</p>
<h1 id="heading-application-of-ha">Application of HA</h1>
<p>Let's assume we have an online shop order API application. Without going into too much detail, we can describe the functionality in a sequence diagram:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1668254879190/-WziyqTRQ.png" alt="sequenceDiagram.png" /></p>
<p>So how would that might look in HA?
Something like that:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1668254962656/Z_lzgQqiY.jpeg" alt="Hexagonal architecture diagram" class="image--center mx-auto" /></p>
<p>Does it have to be a hexagon-like drawing? If you ask me I couldn't care less if it's a hex a circle or a bunch of stars shining on my whiteboard as long as it identifies the application components' boundaries and responsibilities.</p>
<p>Let's do the same thing without hexagons (it won't sound as cool, though):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1668254972193/_-nJKUdWd.jpeg" alt="hex_as_circle.jpeg" /></p>
<p>Nothing exotic here, just making a point again about why <strong>HA</strong> is called <strong>HA</strong> 🙃.</p>
<p>Let's break it down into software components:</p>
<pre><code class="lang-shell">  /src
    /core
      order
    /adapters
      http-server
      email
      email-mock
      db
      db-mock
    /ports
      email-port
      db-port
</code></pre>
<p><strong>http-server</strong> - primary adapter driving our application.</p>
<p><strong>email</strong> - secondary adapter, implements <strong>email-port</strong>, has no reference to anything in our application.</p>
<p><strong>email-mock</strong>- email mock, used for testing, implements <strong>email-port</strong>.</p>
<p><strong>db</strong> - secondary adapter, implements <strong>db-port</strong>, has no reference to anything in our application.</p>
<p><strong>db-mock</strong> - db mock, used for testing, implements <strong>db-port</strong></p>
<p>Have a look at <strong>http-server</strong> adapter. Why it has no port? Well, because it doesn't make sense. We're building a server-side service where <strong>http-server</strong> is the main interface of our application, are we going to reuse its <strong>Port</strong> in some way or call it from the core? It looks like a one-directional communication <strong>http-server</strong> -&gt; <strong>core</strong> to me. Please reach out if you strongly disagree and think we should have a port for it!</p>
<h2 id="heading-ioc-and-loose-coupling">IoC and Loose coupling</h2>
<p>The application <strong>Core</strong> can reference any adapters, but as we said, not directly, only through abstraction. One way to achieve that is by using <strong>Inversion of Control (IOC)</strong> and <strong>Dependency Injection (DI)</strong>. The idea is simple - instead of a <strong>Core</strong> module referencing other modules directly, i.e. knowing exactly where to find the <strong>DB</strong> adapter, all it's aware of is its interface. <strong>Core</strong> has zero knowledge of the interface implementation. Therefore the instance behind the interface can be an <strong>SQL DB</strong>, <strong>FileStorage</strong>, or mocked one.</p>
<p>Here's an example of tightly-coupled code:</p>
<pre><code class="lang-TypeScript"><span class="hljs-comment">// file: order-core.ts</span>
<span class="hljs-keyword">import</span> { Db } <span class="hljs-keyword">from</span> <span class="hljs-string">"./db-adapter"</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> Order {
  <span class="hljs-keyword">private</span> db: Db

  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"></span>){
    <span class="hljs-built_in">this</span>.db = <span class="hljs-keyword">new</span> Db()
  }

  placeOrder(){
    <span class="hljs-built_in">this</span>.db.saveOrder({})
  }
}
</code></pre>
<pre><code class="lang-TypeScript"><span class="hljs-comment">// file: db-adapter.ts</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> Db {
  saveOrder(order: <span class="hljs-built_in">any</span>) {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Saving order to db"</span>);
    <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>
  }
}
</code></pre>
<p>Our application:</p>
<pre><code class="lang-TypeScript"><span class="hljs-comment">// file: index.ts</span>
<span class="hljs-keyword">import</span> { Order } <span class="hljs-keyword">from</span> <span class="hljs-string">"./order-core"</span>;

<span class="hljs-keyword">const</span> order = <span class="hljs-keyword">new</span> Order()

order.placeOrder()
</code></pre>
<p>In this example, we have a rigid code with a dependency between <strong>Order</strong> and <strong>Db</strong> modules.
<strong>Order</strong> class references <strong>Db</strong> class directly 👉 tightly-coupled components.
The main idea here is that this code will be hard to change. Also, notice that our <strong>Order</strong> class decides how to initialise <strong>Db</strong> class. This sounds like a concern that <strong>Order</strong> class should not have.</p>
<p>Suppose I want to use another <strong>Db</strong> logic such as mocked one I will need to update all its references. We can live with that, but we can do better!</p>
<p>Alternative (with IoC):</p>
<pre><code class="lang-TypeScript"><span class="hljs-comment">// file: db-adapter.ts</span>
<span class="hljs-keyword">import</span> { DbPort } <span class="hljs-keyword">from</span> <span class="hljs-string">"./db-port"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> Db <span class="hljs-keyword">implements</span> DbPort {
  saveOrder(order: <span class="hljs-built_in">any</span>): <span class="hljs-built_in">boolean</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Saving order to db"</span>);
    <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>
  }
}
</code></pre>
<pre><code class="lang-TypeScript"><span class="hljs-comment">// file: order-core.ts</span>
<span class="hljs-keyword">import</span> { DbPort } <span class="hljs-keyword">from</span> <span class="hljs-string">"./db-port"</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> Order {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> Db: DbPort</span>) {
    <span class="hljs-built_in">this</span>.Db = Db
  }

  placeOrder(){
    <span class="hljs-built_in">this</span>.Db.saveOrder({})
  }
}
</code></pre>
<p>Our application:</p>
<pre><code class="lang-Typescript"><span class="hljs-comment">// file: index.ts</span>
<span class="hljs-keyword">import</span> { Order } <span class="hljs-keyword">from</span> <span class="hljs-string">"./order-core"</span>;
<span class="hljs-keyword">import</span> { Db } <span class="hljs-keyword">from</span> <span class="hljs-string">"./db-adapter"</span>;

<span class="hljs-keyword">const</span> order1 = <span class="hljs-keyword">new</span> Order(<span class="hljs-keyword">new</span> Db())
order1.placeOrder()
</code></pre>
<p>Here we don't have tight coupling between the <strong>Core</strong> component and the <strong>Adapter</strong>. <strong>Core</strong> knows about the <strong>Port</strong> interface and that's it! We can now define different behaviours of our <strong>Port</strong>!</p>
<pre><code class="lang-Typescript"><span class="hljs-keyword">import</span> { OrderPort } <span class="hljs-keyword">from</span> <span class="hljs-string">"./db-port"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> MockDb <span class="hljs-keyword">implements</span> OrderPort {
  saveOrder(order: <span class="hljs-built_in">any</span>): <span class="hljs-built_in">boolean</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"You've been mocked!"</span>);
    <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>
  }
}
</code></pre>
<p>Our application:</p>
<pre><code class="lang-Typescript"><span class="hljs-keyword">import</span> { Order } <span class="hljs-keyword">from</span> <span class="hljs-string">"./order-core"</span>;
<span class="hljs-keyword">import</span> { Db } <span class="hljs-keyword">from</span> <span class="hljs-string">"./db-adapter"</span>;
<span class="hljs-keyword">import</span> { MockDb } <span class="hljs-keyword">from</span> <span class="hljs-string">"./db-adapter-mock"</span>;

<span class="hljs-keyword">const</span> prod = <span class="hljs-keyword">new</span> Order(<span class="hljs-keyword">new</span> Db())
prod.placeOrder()

<span class="hljs-keyword">const</span> test = <span class="hljs-keyword">new</span> Order(<span class="hljs-keyword">new</span> MockDb())
test.placeOrder()
</code></pre>
<p>Boom 🤯! Revolutionary. Did your head explode?
You've just experienced the power of <strong>Dependency Injection (DI)</strong> and <strong>Inversion of Control (IoC)</strong> 💪.</p>
<p>In this simple example, we need to configure each object constructor with its dependencies. It might be too much. As with anything, there're multiple ways of achieving the same thing. Most of the material about <strong>DI</strong> and <strong>IoC</strong> will cover more complex configurations such as <a target="_blank" href="https://wiki.c2.com/?IocContainerComparison">IoC Containers</a>[7]. Such containers might appear overwhelming, but they all have their place and use cases. Remember that it's all based on simple concepts.</p>
<h2 id="heading-configuration-management">Configuration Management</h2>
<p>This might not be 100% on the topic of <strong>HA</strong> and <strong>IoC</strong>, but I think it's important to mention it anyway.
Software misconfiguration is a very common source of bugs. It always happens; using HTTP instead of HTTPS protocol, wrong module import or invalid environment variables. Therefore, it makes sense to keep our configuration isolated in a single place of truth that defines its behaviour. We can also define our <strong>Adaptors</strong> there in <strong>HA</strong>. For example, the configuration of our logger type, whether it will stream the logs to a remote aggregation service or stream it to a local file, should be defined in one place. It will make our life easier when we try to understand what happened to our logs. In contrast, if each module is responsible for its log configuration, we might end up with different parts of our software producing different types of logs - probably a bad idea.</p>
<h1 id="heading-summary">Summary</h1>
<p>We started with <strong>Hexagonal Architecture</strong> and then talked about <strong>N-Tier</strong> architecture and how these two differ. We covered more abstract topics, such as <strong>Separation of Concerns</strong> and <strong>Inversion of Control</strong>, and briefly touched on <strong>Domain-Driven Design</strong> and Application configuration management. With <strong>HA</strong>, we are more flexible in introducing changes, defining the application domain and enforcing the application's testability.</p>
<p>This write-up was for my own sake of understanding and organising my thoughts as it was about knowledge sharing. I hope it was helpful. If you have questions/objections/observations/complaints, don't hesitate to reach out!</p>
<p>If you want to learn more, check out this <a target="_blank" href="https://www.youtube.com/watch?v=th4AgBcrEHA">this</a> recording of Alistair Cockburn, the quality is not great, but its content is very valuable. And explore the provided references 👇.</p>
<h1 id="heading-references">References</h1>
<p>[1] https://en.wikipedia.org/wiki/Multitier_architecture</p>
<p>[2] https://alistair.cockburn.us/hexagonal-architecture/</p>
<p>[3] https://www.goodreads.com/book/show/85009.Design_Patterns</p>
<p>[4] https://martinfowler.com/articles/injection.html</p>
<p>[5] https://wiki.c2.com/?HollywoodPrinciple</p>
<p>[6] https://en.wikipedia.org/wiki/Liskov_substitution_principle</p>
<p>[7] https://wiki.c2.com/?IocContainerComparison</p>
<p>[8] http://wiki.c2.com/?SeparationOfConcerns</p>
]]></content:encoded></item><item><title><![CDATA[Graceful Termination in K8S - SIGTERM and UNIX processes]]></title><description><![CDATA[Abstract
In this article, we're going to cover UNIX process signals and SIGTERM signals in particular. We will cover how to handle them with practical examples using Node[1], TypeScript [2], Docker[3] and Kind[4] local cluster.
UNIX Signals and SIGTE...]]></description><link>https://blog.p3ld3v.dev/graceful-termination-in-k8s-sigterm-and-unix-processes</link><guid isPermaLink="true">https://blog.p3ld3v.dev/graceful-termination-in-k8s-sigterm-and-unix-processes</guid><category><![CDATA[k8s]]></category><category><![CDATA[unix]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[horizontal scaling]]></category><category><![CDATA[sla]]></category><dc:creator><![CDATA[Pavel Durov]]></dc:creator><pubDate>Mon, 07 Nov 2022 08:50:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1667654870886/o_XdhFMJu.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-abstract">Abstract</h2>
<p>In this article, we're going to cover <code>UNIX</code> process signals and <code>SIGTERM</code> signals in particular. We will cover how to handle them with practical examples using <a target="_blank" href="https://nodejs.org/en/">Node</a>[1], <a target="_blank" href="https://www.typescriptlang.org/">TypeScript</a> [2], <a target="_blank" href="https://www.docker.com/">Docker</a>[3] and <a target="_blank" href="https://kind.sigs.k8s.io/">Kind</a>[4] local cluster.</p>
<h2 id="heading-unix-signals-and-sigterm">UNIX Signals and SIGTERM</h2>
<p>Unix-based operating systems (OS) have multiple processes. OS uses software <a target="_blank" href="https://en.wikipedia.org/wiki/Interrupt">interrupts</a>[5] (aka signals) as a way to communicate with the running processes, these signals are indicating that some sort of event has occurred and they can vary in their intent and purpose.</p>
<p>Here's a list of signals available on my machine:</p>
<pre><code class="lang-shell">$ kill -l
 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
 6) SIGABRT      7) SIGEMT       8) SIGFPE       9) SIGKILL     10) SIGBUS
11) SIGSEGV     12) SIGSYS      13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGURG      17) SIGSTOP     18) SIGTSTP     19) SIGCONT     20) SIGCHLD
21) SIGTTIN     22) SIGTTOU     23) SIGIO       24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGINFO     30) SIGUSR1
31) SIGUSR2
</code></pre>
<p>There're a few, but we're going to focus on <code>SIGTERM</code>.</p>
<p><code>SIGTERM</code> signal is a way for the operating system to terminate a program gracefully. By graceful, we mean that the program is given time to perform the final cleanup before shutdown. Depending on the application, cleanup tasks can vary. Interestingly enough, Unix processes can block and ignore <code>SIGTERM</code>. But if we want to have quality process/service we need to handle these signals as they intended, with respect, otherwise our process will be shutting down with force.</p>
<h2 id="heading-unix-process-as-http-server">Unix Process as HTTP Server</h2>
<p>For demonstration, we're going to create a sample HTTP server using <a target="_blank" href="https://www.typescriptlang.org/">TypeScript</a>[1], and <a target="_blank" href="https://hapi.dev/">hapi</a>[2].</p>
<p>Let's create <a target="_blank" href="https://www.npmjs.com/">NPM</a>[6] project and follow the prompt:</p>
<pre><code>$ npm init
...
</code></pre><p>Install dependencies:</p>
<pre><code>$ npm install @hapi/hapi typesript @types/node @types/node @types/hapi__hapi
</code></pre><p>We're not going to bother with the split of development and production dependencies here 😉.</p>
<p>Create a local file, call it <code>index.ts</code> (or whatever you feel like today, go wild):</p>
<pre><code class="lang-TypeScript"><span class="hljs-keyword">import</span> { Server } <span class="hljs-keyword">from</span> <span class="hljs-string">"@hapi/hapi"</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sleep</span>(<span class="hljs-params">ms: <span class="hljs-built_in">number</span>, value: <span class="hljs-built_in">string</span></span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve</span>) =&gt;</span> <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> resolve(value), ms));
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">startServer</span>(<span class="hljs-params">host: <span class="hljs-built_in">string</span>, port: <span class="hljs-built_in">number</span></span>): <span class="hljs-title">Promise</span>&lt;<span class="hljs-title">Server</span>&gt; </span>{
  <span class="hljs-keyword">const</span> server = <span class="hljs-keyword">new</span> Server({ port, host });

  server.route({
    method: <span class="hljs-string">"GET"</span>,
    path: <span class="hljs-string">"/work"</span>,
    handler: <span class="hljs-keyword">async</span> () =&gt; sleep(<span class="hljs-number">10</span> * <span class="hljs-number">1000</span>, <span class="hljs-string">`done something for 10 seconds\n`</span>),
  });

  <span class="hljs-keyword">await</span> server.start();
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Server running at: <span class="hljs-subst">${server.info.uri}</span>, PID: <span class="hljs-subst">${process.pid}</span>`</span>);
  <span class="hljs-keyword">return</span> server;
}

startServer(<span class="hljs-string">"0.0.0.0"</span>, <span class="hljs-number">3000</span>);
</code></pre>
<p>Here, we start the local server on <code>0.0.0.0</code> host and <code>3000</code> port. We also configured a single endpoint <code>GET /work</code> that simulates something that takes time to compute - 10 seconds in our case. In a real-case scenario that might be the time needed to perform some kind of a Database query.</p>
<blockquote>
<p>I like node, its so cool that in 27 lines of code you can define a server, you actually need less, cause I added sleep function etc. But yeah, its great 😎.</p>
</blockquote>
<p>Let's run our server:</p>
<pre><code class="lang-shell">$ ./node_modules/.bin/ts-node ./index.ts
Server running at: http://0.0.0.0:3000, PID: 16544
</code></pre>
<p>All good, we have server running!</p>
<p>Now let's send <code>GET /work</code> HTTP request to our endpoint. Pick any networking tool of your choice, I'm going to use <a target="_blank" href="https://curl.se/">CURL</a> [7]:</p>
<pre><code class="lang-shell">$ curl http://0.0.0.0:3000/work
done something for 10 seconds
</code></pre>
<p>So far so good. But what will happen to that HTTP request if the server is suddenly killed, terminated or in other words, is no more 🤔. What response would we get on the client side? Will we get anything at all? What will be serving the request after the server is dead? Many questions! Let's give it a go:</p>
<p>Send another request to the server:</p>
<pre><code class="lang-shell">$ curl http://0.0.0.0:3000/work
done something for 10 seconds
</code></pre>
<p>But this time, while the server is doing the simulated work for 10 seconds - let's KILL it 😈!</p>
<pre><code>$ kill <span class="hljs-number">-15</span> <span class="hljs-number">19346</span>
</code></pre><blockquote>
<p>Note: I'm using Process Id (PID) I got from index.ts output! Your local PID would we something else for sure! If it not, then its destiny and definitely send me an email 🤓.</p>
</blockquote>
<p>One might wonder, why not just terminate the shell process of the server by pressing the <code>CTRL</code>+<code>C</code> keys? That would send <code>SIGINT</code> signal and we want <code>SIGTERM</code>!</p>
<p>Ok, so what happened to our client connection when the server is shut down? That's what happens:</p>
<pre><code class="lang-shell">$ curl http://0.0.0.0:3000/work
curl: (52) Empty reply from server
</code></pre>
<p>It got an empty reply, meaning that it got no information, nada. Let's add <code>-v</code> (stands for verbose) flag to our CURL command to see more information.</p>
<pre><code>$ curl -v http:<span class="hljs-comment">//0.0.0.0:3000/work</span>
*   Trying <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>:<span class="hljs-number">3000.</span>..
* Connected to <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span> (<span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>) port <span class="hljs-number">3000</span> (#<span class="hljs-number">0</span>)
&gt; GET /work HTTP/<span class="hljs-number">1.1</span>
&gt; Host: <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>:<span class="hljs-number">3000</span>
&gt; User-Agent: curl/<span class="hljs-number">7.84</span><span class="hljs-number">.0</span>
&gt; Accept: *<span class="hljs-comment">/*
&gt; 
* Empty reply from server
* Closing connection 0
curl: (52) Empty reply from server</span>
</code></pre><p>Looks like the server just closed the connection abruptly 👀. That's not good. What if this happens to the server clients in production? If you're running your application in one of the cloud orchestration tools, containers being shut down might be not out of ordinary. </p>
<p>I am using <a target="_blank" href="https://kubernetes.io/">Kubernetes (K8S)</a>[8] so I'm going to talk about that. K8S is like OS for the cloud. K8S might terminate the running <a target="_blank" href="https://kubernetes.io/docs/concepts/workloads/pods/">Pods</a>[9] (aka containers) at will, after all, the whole purpose of K8S is to orchestrate the distributed system. If one of the Pods is requesting too many resources or if the application is being scaled down, the container might get a signal from K8S that it's time to go 😢. 
That's why we have the notion of signals, to gracefully terminate our containers ☝️.</p>
<h2 id="heading-service-graceful-termination">Service Graceful Termination</h2>
<p>In the previous section, we talk about how our processes or containers might receive different signals.
When K8S needs to terminate a Pod, it will send <code>SIGTERM</code>. That way our service is not just being cut out of resources, instead it will have some time to perform finalisation tasks.</p>
<p>Let's implement this logic in our node server:</p>
<pre><code class="lang-TypeScript"><span class="hljs-keyword">import</span> { Server } <span class="hljs-keyword">from</span> <span class="hljs-string">"@hapi/hapi"</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sleep</span>(<span class="hljs-params">ms: <span class="hljs-built_in">number</span>, value: <span class="hljs-built_in">string</span></span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve</span>) =&gt;</span> <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> resolve(value), ms));
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">startServer</span>(<span class="hljs-params">host: <span class="hljs-built_in">string</span>, port: <span class="hljs-built_in">number</span></span>): <span class="hljs-title">Promise</span>&lt;<span class="hljs-title">Server</span>&gt; </span>{
  <span class="hljs-keyword">const</span> server = <span class="hljs-keyword">new</span> Server({ port, host });

  server.route({
    method: <span class="hljs-string">"GET"</span>,
    path: <span class="hljs-string">"/work"</span>,
    handler: <span class="hljs-keyword">async</span> () =&gt; sleep(<span class="hljs-number">10</span> * <span class="hljs-number">1000</span>, <span class="hljs-string">`done something for 10 seconds\n`</span>),
  });

  process.on(<span class="hljs-string">"SIGTERM"</span>, <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Received SIGTERM`</span>);
    <span class="hljs-keyword">await</span> server.stop({ timeout: <span class="hljs-number">10</span> * <span class="hljs-number">1000</span> });
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Server stopped.`</span>);
    process.exit(<span class="hljs-number">0</span>);
  });
  <span class="hljs-keyword">await</span> server.start();
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Server running at: <span class="hljs-subst">${server.info.uri}</span>, PID: <span class="hljs-subst">${process.pid}</span>`</span>);
  <span class="hljs-keyword">return</span> server;
}

startServer(<span class="hljs-string">"0.0.0.0"</span>, <span class="hljs-number">3000</span>);
</code></pre>
<p>We added a listener on <code>SIGTERM</code> event. When such events occur, we're stopping the server but we're not just terminating it. Between the time that <code>SIGTERM</code> arrives and the specified <code>timeout</code> parameter, our server will refuse to accept any new requests and will finalise ongoing requests.</p>
<p>We can test that statement! Start the server again:</p>
<pre><code>$ ts-node ./index.ts
Server running at: http:<span class="hljs-comment">//0.0.0.0:3000, PID: 67116</span>
</code></pre><p>Run CURL request and terminate the server:</p>
<pre><code>$ curl -v http:<span class="hljs-comment">//0.0.0.0:3000/work</span>
</code></pre><p>We should see that the request is finished and a response sent back to the client - no more <code>(52) Empty reply from server</code> errors.
If we try to access the server in the time frame between <code>SIGTERM</code> and the actual shutdown we will get: </p>
<pre><code>$ curl -v http:<span class="hljs-comment">//0.0.0.0:3000/work</span>
*   Trying <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>:<span class="hljs-number">3000.</span>..
* connect to <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span> port <span class="hljs-number">3000</span> failed: Connection refused
* Failed to connect to <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span> port <span class="hljs-number">3000</span> after <span class="hljs-number">3</span> ms: Connection refused
* Closing connection <span class="hljs-number">0</span>
<span class="hljs-attr">curl</span>: (<span class="hljs-number">7</span>) Failed to connect to <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span> port <span class="hljs-number">3000</span> after <span class="hljs-number">3</span> ms: Connection refused
</code></pre><p>We'll get the same error if there's no server at all!</p>
<p>But that's all we need to do for the graceful termination of node processes. Pretty easy 😃.</p>
<h1 id="heading-sending-sigterm-to-k8s-pods">Sending SIGTERM to K8S Pods</h1>
<p>As we mentioned, whenever the K8S Pod is terminated, it is sent a <code>SIGTERM</code> signal.
Similarly to the way we used <code>kill</code> command for local processes, we can terminate pods using <code>delete</code> command:</p>
<pre><code class="lang-shell">$ kubectl delete pod my-pod-qgldf
</code></pre>
<p>When K8S decides for whatever reason to terminate the Pod, the <code>SIGTERM</code> signal will be sent to it, then to the Docker container, and eventually to the running process.
You don't have to believe me, give it a go.</p>
<h2 id="heading-k8s-sample-application">K8S sample application</h2>
<p>We're going to reuse the server code, but containarise it and deploy it on the local K8S cluster.</p>
<p>Our Dockerfile:</p>
<pre><code class="lang-Dockerfile">FROM node:19-bullseye
WORKDIR /app
COPY index.ts package.json package-lock.json /app/
RUN npm install
EXPOSE 3000
ENTRYPOINT ["/app/node_modules/.bin/ts-node", "index.ts"]
</code></pre>
<p>Build docker container:</p>
<pre><code class="lang-shell">$ docker build -t poc -f ./Dockerfile .
</code></pre>
<p>First let's run it and see if we can get <code>SIGTERM</code> related logs:</p>
<pre><code class="lang-shell">$ docker run -t poc:latest
</code></pre>
<p>Get the docker id:</p>
<pre><code class="lang-shell">$ docker ps
CONTAINER ID   IMAGE                  
86b0a46730ba   poc:latest
</code></pre>
<p>And stop it:</p>
<pre><code class="lang-shell">$ docker stop 86b0a46730ba
</code></pre>
<p>We should see the docker run command terminated as well as our docker container with the same output as we had with the process.</p>
<pre><code>$  docker run -t poc:latest
Server running at: http:<span class="hljs-comment">//0.0.0.0:3000, PID: 1</span>
Received SIGTERM
Server stopped.
</code></pre><p>Now let's do some K8S!</p>
<p>First, we need to load our container image to the cluster:</p>
<pre><code class="lang-shell">$ kind load docker-image poc:latest --name my-cluster
</code></pre>
<p>K8S deployment manifest:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Namespace</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">poc-namespace</span>
<span class="hljs-meta">---</span>
<span class="hljs-attr">apiVersion:</span> <span class="hljs-string">apps/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Deployment</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">poc-deployment</span>
  <span class="hljs-attr">namespace:</span> <span class="hljs-string">poc-namespace</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">selector:</span>
      <span class="hljs-attr">matchLabels:</span>
        <span class="hljs-attr">app:</span> <span class="hljs-string">poc-app</span>
  <span class="hljs-attr">template:</span>
    <span class="hljs-attr">metadata:</span>
      <span class="hljs-attr">labels:</span>
        <span class="hljs-attr">app:</span> <span class="hljs-string">poc-app</span>
    <span class="hljs-attr">spec:</span>
      <span class="hljs-attr">containers:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">poc</span>
        <span class="hljs-attr">image:</span> <span class="hljs-string">poc:latest</span>
        <span class="hljs-attr">ports:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">3000</span>
</code></pre>
<p>Deploy to our cluster:</p>
<pre><code class="lang-yaml"><span class="hljs-string">$</span> <span class="hljs-string">kubectl</span> <span class="hljs-string">apply</span> <span class="hljs-string">-f</span> <span class="hljs-string">./deployment.yaml</span> 
<span class="hljs-string">namespace/poc-namespace</span> <span class="hljs-string">created</span>
<span class="hljs-string">deployment.apps/poc-deployment</span> <span class="hljs-string">created</span>
</code></pre>
<p>If you stream pod logs:</p>
<pre><code class="lang-bash">$ k logs -f poc-deployment-bf749f576-wfmv9
</code></pre>
<p>And then delete the pod:</p>
<pre><code>$ kubectl <span class="hljs-keyword">delete</span> pod poc-deployment-bf749f576-wfmv9
</code></pre><p>Should get the same results:</p>
<pre><code class="lang-shell">$ k logs -f poc-deployment-bf749f576-wfmv9
Server running at: http://0.0.0.0:3000, PID: 1
Received SIGTERM
Server stopped.
</code></pre>
<p>And that's a wrap 🌯!</p>
<h1 id="heading-summary">Summary</h1>
<p>In this article, we covered <code>SIGTERM</code> signals, how they are used in Unix-based and cloud-based systems as well as showed practical applications of handling to handle these signals and their potential impact of it.</p>
<p><code>SIGTERM</code> handling is essential for <a target="_blank" href="https://en.wikipedia.org/wiki/Scalability#Horizontal_(scale_out)_and_vertical_scaling_(scale_up)">horizontally scalable</a>[10] cloud applications. It allows applications to scale up and down on demand without impacting the client's stability. </p>
<h1 id="heading-references">References</h1>
<p>[1] https://nodejs.org/en/</p>
<p>[2] https://www.typescriptlang.org/</p>
<p>[3] https://www.docker.com/</p>
<p>[4] https://kind.sigs.k8s.io/</p>
<p>[5] https://en.wikipedia.org/wiki/Interrupt</p>
<p>[6] https://www.npmjs.com/</p>
<p>[7] https://curl.se/</p>
<p>[8] https://kubernetes.io/</p>
<p>[9] https://kubernetes.io/docs/concepts/workloads/pods/</p>
<p>[10] https://en.wikipedia.org/wiki/Scalability#Horizontal_(scale_out)_and_vertical<em>scaling</em>(scale_up)</p>
]]></content:encoded></item><item><title><![CDATA[macOS FS vs Linux FS - case sensitivity]]></title><description><![CDATA[macOS FS vs Linux FS - case sensitivity
Abstract
In this article, I will overview the case-sensitivity difference between macOS default filesystem (FS) and Linux filesystem with some practical examples and gotchas.
Intro
Unix and Linux might sound th...]]></description><link>https://blog.p3ld3v.dev/macos-fs-vs-linux-fs-case-sensitivity</link><guid isPermaLink="true">https://blog.p3ld3v.dev/macos-fs-vs-linux-fs-case-sensitivity</guid><category><![CDATA[macOS]]></category><category><![CDATA[Linux]]></category><category><![CDATA[linux-file-system]]></category><category><![CDATA[filesystem]]></category><category><![CDATA[Compatibility]]></category><dc:creator><![CDATA[Pavel Durov]]></dc:creator><pubDate>Mon, 31 Oct 2022 09:00:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1667143016949/8UXrE6e_n.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-macos-fs-vs-linux-fs-case-sensitivity">macOS FS vs Linux FS - case sensitivity</h1>
<h1 id="heading-abstract">Abstract</h1>
<p>In this article, I will overview the case-sensitivity difference between macOS default filesystem (FS) and Linux filesystem with some practical examples and gotchas.</p>
<h1 id="heading-intro">Intro</h1>
<p>Unix and Linux might sound the same to some of us, but they are different beasts. 
macOS aka OS X or Max OS X is a <strong>Unix-based</strong> operating system, while Linux is <strong>Unix-like</strong> OS. There are many articles explaining the difference [1]. No need to repeat. </p>
<h1 id="heading-experiment-maxos-fs-case-sensitivity">Experiment - maxOS FS case sensitivity</h1>
<p>Let's do an experiment 🤓. 
Assuming that you're running on macOS, create a file called <code>entrypoint.sh</code> with content:</p>
<pre><code>#!<span class="hljs-regexp">/bin/</span>bash

echo <span class="hljs-string">"hello"</span>
</code></pre><p>Try running it as:</p>
<pre><code class="lang-shell">$ bash ./entrypoint.sh
hello
</code></pre>
<p>Now rename it to <code>Entrypoint.sh</code> (with capital E) and run again:</p>
<pre><code class="lang-shell">$ bash ./entrypoint.sh
hello
</code></pre>
<p>Works anyway! File names are case insensitive, meaning that you can run it even as:</p>
<pre><code class="lang-shell">$ bash ./eNtryPOInt.sh
hello
</code></pre>
<p>I don't know how about you, but when I found it for the first time I was really surprised. 
When working with a mac, on the surface, it looks and feels very Linuxy, the terminal, the shell, the commands, the paths... It's all been a lie 😭.</p>
<h2 id="heading-lets-see-how-case-sensitivity-would-work-on-linux-using-docker">Let's see how case-sensitivity would work on Linux using Docker</h2>
<p>Define a simple dockerfile:</p>
<pre><code class="lang-docker">FROM alpine:3.14
WORKDIR /poc
COPY ./Entrypoint.sh /poc/Entrypoint.sh
ENTRYPOINT ["/bin/sh", "/poc/entrypoint.sh" ]
</code></pre>
<p>Here we're copying the <code>entrypoint.sh</code> file and running it as the <code>ENTRYPOINT</code> command. Notice that we run the lowercased file but we copied in uppercased file!
Well, running it won't work since Linux filesystem is case-sensitive:</p>
<pre><code class="lang-shell">$ docker build -t macOS-FS . &amp;&amp; docker run -t macOS-FS
/bin/sh: can't open '/poc/entrypoint.sh': No such file or directory
</code></pre>
<p>If we change the entrypoint to <code>/poc/Entrypoint.sh</code> it should work. Try it yourself!</p>
<h2 id="heading-fs-case-sensitivity-and-git">FS case-sensitivity and Git</h2>
<p>Another thing to note is that since FS is case-insensitive, these changes will not be tracked in Git 😶. I know! Quite shocking to stop thinking of Git as the source of the absolute truth of text-based file changes! The same experiment can be done by renaming one of the tracked files and seeing the Git-detected changes 🤯. It got me personally multiple times over the past years when programs worked locally but failed to launch in Linux-based containers or in my lovely CI/CD pipelines.</p>
<h2 id="heading-using-macos-case-sensitive-file-system">Using macOS case-sensitive file system</h2>
<p>It's possible to have a case-sensitive filesystem on macOS. For that, we need to set the disc to use APFS (Case-sensitive) format:
"APFS (Case-sensitive): Uses the APFS format, is case-sensitive to file and folder names. For example, folders named “Homework” and “HOMEWORK” are two different folders." [2].</p>
<p>However, it might be risky. Most macOS applications don’t recognize a case-sensitive file system. And probably won’t work as expected. So wouldn't recommend it. On the other hand, it might make sense to set a separate partition on your drive to be case-sensitive, especially if you work with Linux-based systems quite often. But not the whole drive.</p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>macOS and Linux might look and feel the same, but in reality, they are quite different. Filesystem case sensitivity is only one example of such diffrence.
I hope that this short overview of case-sensitivity differences was useful. And you won't be caught off-guard next time your application won't run when Git didn't pick up on file rename changes on macOS.</p>
<h1 id="heading-references">References</h1>
<p>[1] https://www.softwaretestinghelp.com/unix-vs-linux/</p>
<p>[2] https://support.apple.com/en-gb/guide/disk-utility/dsku19ed921c/mac</p>
]]></content:encoded></item><item><title><![CDATA[Db data migration using K8S jobs Helm]]></title><description><![CDATA[Abstract
This article will cover the data migration process between two data stores, the evaluation of possible alternatives, and final outcome. We will cover the difference between sequential, asynchronous, multi-threaded, and distributed applicatio...]]></description><link>https://blog.p3ld3v.dev/db-data-migration-using-k8s-jobs-helm</link><guid isPermaLink="true">https://blog.p3ld3v.dev/db-data-migration-using-k8s-jobs-helm</guid><category><![CDATA[k8s]]></category><category><![CDATA[Kubernetes]]></category><category><![CDATA[distributed system]]></category><category><![CDATA[datamigration]]></category><category><![CDATA[Helm]]></category><dc:creator><![CDATA[Pavel Durov]]></dc:creator><pubDate>Sat, 29 Oct 2022 14:22:29 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1667053335914/wFSJmKVkK.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-abstract">Abstract</h1>
<p>This article will cover the data migration process between two data stores, the evaluation of possible alternatives, and final outcome. We will cover the difference between sequential, asynchronous, multi-threaded, and distributed applications.
And finally, we will overview the practical application of Helm and K8S jobs for data migration tasks.</p>
<p>👉 If you care only about the K8S and Helm stuff, skip to <strong>K8S jobs and Helm
section</strong>. It's ok.</p>
<h1 id="heading-introduction">Introduction</h1>
<p>Recently I was working on data migration. We had 2M records in AWS DocumentDb that we wanted to migrate to AWS RDS PostgreSQL; the actual database tech stack is irrelevant as we will not cover the application-level implementation here. </p>
<p>Long story short, on top of migrating the data from one source to another and adapting record schemas accordingly, it also involved data enrichment and out-of-db context processing, such as re-hosting image links.</p>
<p>First, we took the simplest approach: We created a Python script that migrated one record from DocumentDb to RDS. But soon enough, we realized that:</p>
<p>It takes <code>~3</code> seconds to migrate one record, and we have <code>2M</code> records. 
So the expected time for complete db migration will be <code>(2M * 3) / 60 / 60 / 24 = ~69 days</code>. Well, we don't want to wait <code>69</code> days, so we had to come up with something with higher throughput.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667327692638/Gy9fSmbjy.png" alt="skeleton.png" /></p>
<p>In general, I always try to keep things as simple as possible and only introduce complexity when it's needed. A wise man once said, </p>
<blockquote>
<p>“Premature optimization is the root of all evil. Yet we should not pass 
up our opportunities in that critical 3%.” (Donald E. Knuth).</p>
</blockquote>
<p>So we evaluated multiple alternatives.</p>
<h1 id="heading-alternatives">Alternatives</h1>
<h2 id="heading-alternative-1-multiple-local-machines">Alternative 1 - Multiple local machines</h2>
<p>It might seem like the simplest solution. But it comes with its complexity. If something goes wrong, it will be challenging to understand the cause. Having the application distributed to individual machines means that we don't have aggregated place to look at the logs, execution stats etc. Additionally, we might run into local setup issues, slow networking, ISP issues etc. It will also be quite expensive, as we would pay a significant amount of money for the AWS VPC in/out data.</p>
<h2 id="heading-alternative-2-asynchronous-code">Alternative 2 - Asynchronous code</h2>
<p>We could take advantage of asynchronous code. Our tasks are not CPU-bound; most of the time is spent on networking requests and db queries. But unfortunately, we used Django for our PostgreSQL db and wanted to reuse the modes. It turns out Django is not that friendly when it comes to async code 😢. Django ORM calls from async code need to be wrapped with <code>sync_to_async</code>, which is blocking, so it beats the purpose of asynchronous code.</p>
<h2 id="heading-alternative-3-multi-threading">Alternative 3 - Multi-Threading</h2>
<p>Another valid approach to consider is multi-threading.
Threads can be created based on the number of CPUs. The logic can be described as follows:</p>
<pre><code>FOR c <span class="hljs-keyword">in</span> CPUs
  skip = THREAD_NUMBER * BATCH_SIZE
  limit = BATCH_SIZE
  SPAWN thread <span class="hljs-keyword">with</span> batch sized portion <span class="hljs-keyword">of</span> records
JOIN all threads
</code></pre><p>Although it worked, this approach has a couple of disadvantages. First, multi-threading is hard to reason about, debug and observe in a cloud environment. In the cloud, we deal with distributed applications. We already have system components working in parallel and asynchronously; adding concurrency within these components adds another layer of complexity. I kind of reason in big O notation:</p>
<blockquote>
<p>Distributed applications are of complexity <code>D(n)</code>, where <code>n</code> is the number of components, adding multi-threaded applications will have complexity of <code>D(n * t)</code>, where <code>t</code> is the number of threads. </p>
</blockquote>
<p>If I had to choose which system to maintain, I would select <code>D(n)</code> over <code>D(n * t)</code>!
Second, threading creates overhead of synchronization as well as observing the logs and dealing with data becomes much more complicated.
As well as testing, testing is way harder in a multi-threaded environment! And I love testing! So why make my life harder than it should be? The answer might be subjective...</p>
<h2 id="heading-alternative-4-distributed-approach">Alternative 4 - Distributed approach</h2>
<p>Similar to the Multi-Threaded approach. But instead of having threads, we have multiple machines working together on the data migration. In this context, we're talking about K8S jobs and pods, but it can be applied to any orchestration tool.</p>
<p>The thinking is similar to multi-threaded:</p>
<pre><code>FOR j <span class="hljs-keyword">in</span> JOBs
  skip = JOB_NUMBER * BATCH_SIZE
  limit = BATCH_SIZE
  SPAWN K8S job <span class="hljs-keyword">with</span> batch sized portion <span class="hljs-keyword">of</span> records
WAIT <span class="hljs-keyword">for</span> all created jobs to complete
</code></pre><p>Notice that the number of CPUs no longer limits us; we're limited by the number of jobs we define. I want to emphasize the <strong>WE</strong> in this sentence! It just brings me the nice feeeling of being in control, if im the one defining stuff, I know what to expect! </p>
<p>This definition/configuration can be infinite(ish). Of course, there's a physical limit to K8S node instances, but that's a threshold way higher than a single-pod CPU limitation. In our micro view from the db migration job, it might feel as infinite as it gets.</p>
<h2 id="heading-going-with-the-distributed-approach">Going with the distributed approach</h2>
<p>We went through the approaches and decided to go with the distributed one, as this alternative is simple enough. We have a synchronous Python script ready and tested. We need to multiply it <code>X</code> times and assign the data slices to K8S jobs that will run that script.
As a result, we get distributed application that doesn't need synchronization or any operational overhead. Logs are easily accessible, and job resources are visible.</p>
<h2 id="heading-batch-allocation-or-data-source-slices">Batch allocation, or data source slices</h2>
<p>As we mentioned above, we will slice the data into chunks and assign it an appropriate K8S job.
This can be illustrated as follows:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667327706683/_2hHEfqpH.jpeg" alt="db_migration_jobs.jpeg" /></p>
<p>So we get the gist - now we need to actually run some math:</p>
<h3 id="heading-calculating-the-number-of-jobs-and-the-batch-size">Calculating the number of jobs and the batch size</h3>
<p>I'm going to use javascript for that, just because I can?</p>
<pre><code class="lang-javascript">
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getMigrationConfig</span>(<span class="hljs-params">numberOfRecords, expectedHours</span>)</span>{
  <span class="hljs-keyword">const</span> singleRecordMigrationTimeSeconds = <span class="hljs-number">3</span> 
  <span class="hljs-keyword">const</span> overallExecutionSeconds = numberOfRecords * singleRecordMigrationTimeSeconds
  <span class="hljs-keyword">const</span> overallHours = overallExecutionSeconds / <span class="hljs-number">60</span> / <span class="hljs-number">60</span>
  <span class="hljs-keyword">const</span> workerCount = overallHours / expectedHours
  <span class="hljs-keyword">const</span> batchSize = numberOfRecords / workerCount
  <span class="hljs-keyword">return</span> { workerCount, batchSize }
}
</code></pre>
<p>So for <code>2M</code> records and <code>1-hour</code> migration, we get:</p>
<pre><code class="lang-shell">&gt; getMigrationConfig(2000000, 1)
{ workerCount: 1666.6666666666667, batchSize: 1200 }
</code></pre>
<p><code>1666</code> seems to be a lot of K8S pods; maybe it's okay, maybe not; it really depends on the infrastructure.
How about <code>5-hour</code> migration?</p>
<pre><code class="lang-shell">&gt; getMigrationConfig(2000000, 5)
{ workerCount: 333.33333333333337, batchSize: 5999.999999999999 }
</code></pre>
<p>Well, you get the point. We can choose the configuration we want. The cool thing here is that I'm in control; I can go between <code>1</code> and <code>infinity(ish)</code>. That wouldn't be possible with threads cause there's way a lower limit on an available number of cores.</p>
<p>This is great 🤩! We went from an initial sequential processing time of <code>69</code> days to just under a couple of hours 🚀! It's quite cool, don't you think?</p>
<h2 id="heading-k8s-jobs-and-helm">K8S jobs and Helm</h2>
<p>Ok, we got the theory and general understanding of what we want to achieve here. Now we'll get our hands dirty with K8S and Helm.</p>
<h3 id="heading-whats-helm">What's Helm?</h3>
<p>Helm helps you manage Kubernetes applications — Helm Charts help you define, install, and upgrade even the most complex Kubernetes application [1].</p>
<p>In our case, we will use only a subset of its functionality. We're going to define variables and enumeration on the K8S job template.</p>
<h3 id="heading-defining-our-k8s-job">Defining our K8S job</h3>
<p>Let's start with a simple K8S job [5] definition as if we do not intend to use Helm yet:</p>
<blockquote>
<p>Note: I am using an example docker image - <code>nginx-helloworld</code>. This is only for illustration purposes, don't expect it to work out of the box!</p>
</blockquote>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">batch/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Job</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">db-migration-job</span>
  <span class="hljs-attr">namespace:</span> <span class="hljs-string">db-migration</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">template:</span>
    <span class="hljs-attr">spec:</span>
      <span class="hljs-attr">containers:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">db-migration</span>
        <span class="hljs-attr">image:</span> <span class="hljs-string">nginx-helloworld</span>
        <span class="hljs-attr">env:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">DB_MIGRATE_JOB_NUMBER</span>
            <span class="hljs-attr">value:</span> <span class="hljs-number">0</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">DB_MIGRATE_BATCH_SIZE</span>
            <span class="hljs-attr">value:</span> <span class="hljs-number">1000</span>
      <span class="hljs-attr">restartPolicy:</span> <span class="hljs-string">Never</span>
  <span class="hljs-attr">backoffLimit:</span> <span class="hljs-number">1</span>
</code></pre>
<p>Pretty standard, we defined a job manifest with a docker image reference and two environment variables: 
<code>DB_MIGRATE_JOB_NUMBER</code> and <code>DB_MIGRATE_BATCH_SIZE</code>.</p>
<p>So what would we do if we wanted to replicate this definition more than once? We could copy-paste this file multiple times, changing the job and batch variables or some kind of bash scripting.</p>
<p>It's definitely possible, but as one platform engineer I'm working with asked me: </p>
<blockquote>
<p>What would you choose?</p>
</blockquote>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667327718990/v8I3wtY3W.png" alt="matrix.png" /></p>
<p>I choose to avoid the crap pill.</p>
<p>Let's see how we'll do it using Helm:</p>
<pre><code class="lang-yaml">{{<span class="hljs-bullet">-</span> <span class="hljs-string">range</span> <span class="hljs-string">$i</span>, <span class="hljs-string">$job</span> <span class="hljs-string">:=</span> <span class="hljs-string">until</span> <span class="hljs-string">(.Values.jobsCount</span> <span class="hljs-string">|</span> <span class="hljs-string">int)</span> }}
<span class="hljs-attr">apiVersion:</span> <span class="hljs-string">batch/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Job</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">db-migration-job-{{</span> <span class="hljs-string">$i</span> <span class="hljs-string">}}</span>
  <span class="hljs-attr">namespace:</span> <span class="hljs-string">db-migration</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">template:</span>
    <span class="hljs-attr">spec:</span>
      <span class="hljs-attr">containers:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">db-migration</span>
        <span class="hljs-attr">image:</span> <span class="hljs-string">nginx-helloworld</span>
        <span class="hljs-attr">env:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">DB_MIGRATE_JOB_NUMBER</span>
            <span class="hljs-attr">value:</span> {{ <span class="hljs-string">$i</span> <span class="hljs-string">|</span> <span class="hljs-string">squote</span> }}
          <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">DB_MIGRATE_BATCH_SIZE</span>
            <span class="hljs-attr">value:</span> {{ <span class="hljs-string">$.Values.migrationBatchSize</span> <span class="hljs-string">|</span> <span class="hljs-string">squote</span> }}
      <span class="hljs-attr">restartPolicy:</span> <span class="hljs-string">Never</span>
  <span class="hljs-attr">backoffLimit:</span> <span class="hljs-number">1</span>
<span class="hljs-meta">---</span>
{{ <span class="hljs-string">end</span> }}
</code></pre>
<p>Helm values file content:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">jobsCount:</span> <span class="hljs-number">2</span>
<span class="hljs-attr">migrationBatchSize:</span> <span class="hljs-number">100</span>
</code></pre>
<p>So what have we done here:</p>
<ol>
<li>Range loop at the top. It will create this YAML definition as the number of <code>jobsCount</code> in the values file.</li>
<li>Added placeholders <code>DB_MIGRATE_BATCH_SIZE</code>, and <code>DB_MIGRATE_JOB_NUMBER</code> env variables.</li>
<li>Dynamically define job name by appending the <code>{{ i }}</code> variable</li>
</ol>
<p>If you dealt with templating engines, I think this should be quite simple to grasp. If not, then welcome to the future!</p>
<p><img src="https://media2.giphy.com/media/tel4DU3dCiDdVUPhIg/giphy-downsized.gif?cid=6104955ef20a9yllosb2c6d70uogo7skk6fx3ernnrfhcn5u&amp;rid=giphy-downsized.gif&amp;ct=g" /></p>
<h2 id="heading-running-helm">Running Helm</h2>
<p>First, install Helm if needed, <a target="_blank" href="https://helm.sh/docs/intro/install/">docs</a>.</p>
<p>I have the following directory structure in my example:</p>
<pre><code class="lang-sell">$ tree
├── Chart.yaml         &lt;- Helm chart config
├── templates
│   ├── job.yaml       &lt;- K8S job
│   └── namespace.yaml &lt;- K8S namespace
└── values.yaml        &lt;- template values
</code></pre>
<p>Where <code>Chart.yaml</code>[2] content is simple Helm chart config:</p>
<pre><code>apiVersion: v2
<span class="hljs-attr">name</span>: k8s-job
<span class="hljs-attr">description</span>: A Helm chart <span class="hljs-keyword">for</span> Kubernetes
<span class="hljs-attr">type</span>: application
<span class="hljs-attr">version</span>: <span class="hljs-number">0.1</span><span class="hljs-number">.0</span>
<span class="hljs-attr">appVersion</span>: <span class="hljs-string">"1.16.0"</span>
</code></pre><p>And <code>templates/namespace.yaml</code> file:</p>
<pre><code>apiVersion: v1
<span class="hljs-attr">kind</span>: Namespace
<span class="hljs-attr">metadata</span>:
  name: db-migration
</code></pre><p>Running Helm as:</p>
<pre><code class="lang-shell">$ helm template . --values ./values.yaml
</code></pre>
<p>Produces:</p>
<pre><code class="lang-yaml"><span class="hljs-comment"># Source: k8s-job/templates/job.yaml</span>
<span class="hljs-attr">apiVersion:</span> <span class="hljs-string">batch/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Job</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">db-migration-job-0</span>
  <span class="hljs-attr">namespace:</span> <span class="hljs-string">db-migration</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">template:</span>
      <span class="hljs-attr">containers:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">db-migration</span>
        <span class="hljs-attr">image:</span> <span class="hljs-string">MY_DOCKER_IMAGE</span>
        <span class="hljs-attr">env:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">DB_MIGRATE_JOB_NUMBER</span>
            <span class="hljs-attr">value:</span> <span class="hljs-string">'0'</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">DB_MIGRATE_BATCH_SIZE</span>
            <span class="hljs-attr">value:</span> <span class="hljs-string">'100'</span>
<span class="hljs-meta">---</span>
<span class="hljs-comment"># Source: k8s-job/templates/job.yaml</span>
<span class="hljs-attr">apiVersion:</span> <span class="hljs-string">batch/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Job</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">db-migration-job-1</span>
  <span class="hljs-attr">namespace:</span> <span class="hljs-string">db-migration</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">template:</span>
      <span class="hljs-attr">containers:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">db-migration</span>
        <span class="hljs-attr">image:</span> <span class="hljs-string">MY_DOCKER_IMAGE</span>
        <span class="hljs-attr">env:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">DB_MIGRATE_JOB_NUMBER</span>
            <span class="hljs-attr">value:</span> <span class="hljs-string">'1'</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">DB_MIGRATE_BATCH_SIZE</span>
            <span class="hljs-attr">value:</span> <span class="hljs-string">'100'</span>
</code></pre>
<p>That output can be piped with <code>kubectl</code> command and deployed to our K8S cluster:</p>
<pre><code class="lang-shell">$ helm template . --values ./values.yaml | kubectl apply -f -
namespace/db-migration unchanged
job.batch/db-migration-job-0 created
job.batch/db-migration-job-1 created
</code></pre>
<p>List the jobs:</p>
<pre><code class="lang-shell">$ kubectl get jobs -n db-migration
NAME                 COMPLETIONS   DURATION   AGE
NAME                           COMPLETIONS   DURATION   AGE
db-migration-0    1/1           7m51s        7m51s
db-migration-1    0/1           7m51s      7m51s
..
db-migration-99   0/1           7m41s      7m41s
db-migration-100  0/1           7m41s      7m41s
</code></pre>
<p>And Pods:</p>
<pre><code class="lang-shell">$ kubectl get pods -n db-migration

AME                                 READY   STATUS      RESTARTS   AGE
db-migration-0-5k5nb    0/1     Completed   0          8m44s
db-migration-1-9splv    1/1     Running     0          8m44s
...
db-migration-99-7klz2   1/1     Running     0          8m40s
db-migration-100-nwlg9  1/1     Running     0          8m40s
</code></pre>
<p>K8S makes it easy to see which job was completed, which failed, and the pod logs.</p>
<h2 id="heading-things-to-consider">Things to consider</h2>
<p>There are a few things to consider when spawning many K8S pods:</p>
<h3 id="heading-k8s-node-scaling-and-resource-allocation">K8S node scaling and resource allocation</h3>
<p>Depending on the application program, pods might require specific memory or CPU resource allocation. When we talk about many jobs requesting these resources at the same time, depending on the cluster scaling strategy it might take time to actually get them. That needs to be tailored to the application, or pre-scale the nodes.
We need to calculate whether our K8S cluster will handle the expected load. In our case defined as AWS node groups [3].</p>
<h3 id="heading-ip-range">Ip range</h3>
<p>It really depends on the subnet VPC CIDR block [4] configuration. For example, IPv4 CIDR of <code>172.20.0.0/16</code> will allow a 65k+ IP range but of course, it also depends on how many IPs are already in use. </p>
<h2 id="heading-theory-meets-practice">Theory meets practice</h2>
<h3 id="heading-k8s-control-plane">K8S Control Plane</h3>
<p>As with anything in our practice, everything works fine in our local environment but when it needs to run somewhere else... Different story.
Spawning these jobs on actual K8S clusters gave us this error from the K8S control plane:</p>
<blockquote>
<p>Resource: "batch/v1, Resource=jobs", GroupVersionKind: "batch/v1, Kind=Job"
Name: "db-migration-240", Namespace: "db-migration"
from server for: "STDIN": Get "...": dial tcp: lookup </p>
</blockquote>
<p>That error came when we did more than 100 jobs at a time. So then we had to batch that as well! </p>
<p>That's it :)</p>
<h2 id="heading-summary">Summary</h2>
<p>We covered the thought process behind the recent data migration I had to do on AWS cloud using K8S jobs and Helm. I think Helm's simplicity and power really shines in this application. </p>
<p>We haven't covered the application-level logic here as it's peculiar to our case and way beyond the the scope this article.</p>
<p>Hope it helped anyone that has to deal with something similar task!</p>
<h1 id="heading-references">References</h1>
<p>[1] https://helm.sh/</p>
<p>[2] https://helm.sh/docs/topics/charts/#the-chartyaml-file</p>
<p>[3] https://docs.aws.amazon.com/eks/latest/userguide/managed-node-groups.html</p>
<p>[4] https://docs.aws.amazon.com/vpc/latest/userguide/configure-your-vpc.html#vpc-cidr-blocks</p>
<p>[5] https://kubernetes.io/docs/concepts/workloads/controllers/job</p>
]]></content:encoded></item><item><title><![CDATA[Python logging module quirks]]></title><description><![CDATA[Introduction
Python comes with the default, ready-to-use logging module. It's kind of nice cause we don't have to install additional libraries to start logging in to our application. However, for someone coming from other language backgrounds, such a...]]></description><link>https://blog.p3ld3v.dev/python-logging-module-quirks</link><guid isPermaLink="true">https://blog.p3ld3v.dev/python-logging-module-quirks</guid><category><![CDATA[stderr]]></category><category><![CDATA[Python]]></category><category><![CDATA[logging]]></category><category><![CDATA[configuration]]></category><category><![CDATA[observability]]></category><dc:creator><![CDATA[Pavel Durov]]></dc:creator><pubDate>Thu, 20 Oct 2022 19:20:47 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1667059686972/73Tqoc07M.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>Python comes with the default, ready-to-use logging module. It's kind of nice cause we don't have to install additional libraries to start logging in to our application. However, for someone coming from other language backgrounds, such as Javascript, TypeScript, Java, C#, and Go. The Python logger module might resemble familiar tools, but it has its specifics. In this article, I would like to cover some quirks that I found along the way while I was working recently on a python project.</p>
<h2 id="heading-read-the-documentation">Read the documentation</h2>
<p>Yeah, that would be a sensible thing to do. Make a cup of coffee, relax and read the logging module <a target="_blank" href="https://docs.python.org/3/library/logging.html">documentation</a> [1], all the 13 sub-pages, of course 🙂. Unfortunately, not everyone has the time to do so. I usually skim through the documentation, but when I deal with multiple programming languages daily, it's hard to hold all the language and framework-specifics in my head. Anyway, enough about me and my saga with Python documentation; let's look at the logger module.</p>
<h2 id="heading-log-levels">Log levels</h2>
<p>Why do we need logging levels? Good question. Because depending on how we run our application, we might want to have different types of logs. For example, if we're in the developing phase, we would like to see verbose logs of what exactly happens in the application. But when we're ready for production, we would probably want to limit our logs to only errors - otherwise, we will have a lot of logs, where most of them are probably going to be just noise. By noise, I mean they will not have useful information while simultaneously being very verbose and distracting. So we need to understand what kind of information we need in our logs. Having everything everywhere usually will not get us far.</p>
<p>The logger module comes with several <a target="_blank" href="https://docs.python.org/3/library/logging.html#logging-levels">levels</a>[2]:</p>
<pre><code>CRITICAL = <span class="hljs-number">50</span>
ERROR = <span class="hljs-number">40</span>
WARNING = <span class="hljs-number">30</span>
INFO = <span class="hljs-number">20</span>
DEBUG = <span class="hljs-number">10</span>
NOTSET = <span class="hljs-number">0</span>
</code></pre><p>I think the names are quite self-explanatory, but you can always go back to the original <a target="_blank" href="https://docs.python.org/3/library/logging.html#logging-levels">docs</a>.
Each level has a number assigned to it. The higher the number, the higher its priority. For example, if we set the log level to be <code>WARNING</code>, we will only get logs with a priority higher than <code>WARNING</code>, i.e. higher than 30. Similarly, if we set it to <code>NOTSET</code> level we will get all the logs.</p>
<p>Let's run an example logging python application:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> logging

logging.debug(<span class="hljs-string">'test'</span>)
logging.info(<span class="hljs-string">'test'</span>)
logging.warning(<span class="hljs-string">'test'</span>)
logging.error(<span class="hljs-string">'test'</span>)
logging.critical(<span class="hljs-string">'test'</span>)
</code></pre>
<p>Expected output:</p>
<pre><code class="lang-shell">WARNING:root:test
ERROR:root:test
CRITICAL:root:test
</code></pre>
<p>Where did my logs go? We have only <code>WARNING</code>, <code>ERROR</code>, and <code>CRITICAL</code> 🤔. As we mentioned above, it's all about the level of priority set.</p>
<p>Let's set the log level as <code>DEBUG</code>:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> logging

logging.basicConfig(level=logging.DEBUG)

logging.debug(<span class="hljs-string">'test'</span>)
logging.info(<span class="hljs-string">'test'</span>)
logging.warning(<span class="hljs-string">'test'</span>)
logging.error(<span class="hljs-string">'test'</span>)
logging.critical(<span class="hljs-string">'test'</span>)
</code></pre>
<p>Run it, and observe the output:</p>
<pre><code class="lang-shell">$ python main.py 
DEBUG:root:test
INFO:root:test
WARNING:root:test
ERROR:root:test
CRITICAL:root:test
</code></pre>
<p>As expected, we get all the logging levels 🥳.</p>
<h2 id="heading-logging-to-files">Logging to files</h2>
<p>Similarly to what we did above, we can log into a local file:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> logging

logging.basicConfig(filename=<span class="hljs-string">'test.log'</span>, filemode=<span class="hljs-string">'w'</span>)
logging.warning(<span class="hljs-string">'test'</span>)
</code></pre>
<p>expected test.log file content:</p>
<pre><code class="lang-shell">WARNING:root:test
</code></pre>
<p>There're more configuration options <a target="_blank" href="https://docs.python.org/3/library/logging.html#logging.basicConfig">available</a> [3]</p>
<h2 id="heading-formatting-the-output">Formatting the output</h2>
<p>When we have multiple components in the same application logging message, it's natural to want them to log in the same format; this way, we can apply the exact configuration for message content, such as process name, level, and module names across our application.</p>
<pre><code class="lang-shell">import logging

logging.basicConfig(format='%(process)d-%(levelname)s-%(message)s')
logging.error('test')
</code></pre>
<p>Same here. More formatting options in the <a target="_blank" href="https://docs.python.org/3/library/logging.html#formatter-objects">docs</a> [4]</p>
<h2 id="heading-capturing-stack-traces">Capturing stack traces</h2>
<p>What happens when we have an error? What information are we expected to see? Usually, we would want a short description of what happened and a stack trace. Stack trace provides information about the execution path, how we got to that error, function names, calls and execution context.</p>
<p>But first, Let's see if we can guess would the following log be. We're going to raise a <code>ZeroDivisionError</code> exception intentionally:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> logging

<span class="hljs-keyword">try</span>:
  <span class="hljs-number">1</span> / <span class="hljs-number">0</span>
<span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
  logging.error(e)
</code></pre>
<p>Looks ok, right? Well, that would just log:</p>
<pre><code class="lang-bash">$ python ./main.py 
ERROR:root:division by zero
</code></pre>
<p>I don't know about you, but the first time I saw this log in production, I was pretty puzzled about it. If you made it that far I salute you 🫡. That's one of the quirks I was referring to in my intro.</p>
<p>We need to add the <code>exec_info=True</code> parameter or use <code>logging.exception</code> function to get the stack trace information.</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> logging

<span class="hljs-keyword">try</span>:
  <span class="hljs-number">1</span> / <span class="hljs-number">0</span>
<span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
  logging.error(<span class="hljs-string">'OOPS'</span>, exc_info=<span class="hljs-literal">True</span>)
  <span class="hljs-comment"># OR: logging.exception('OOPS')</span>
</code></pre>
<p>Expected output:</p>
<pre><code class="lang-shell">ERROR:root:OOPS
Traceback (most recent call last):
  File "...py", line 4, in &lt;module&gt;
    1 / 0
ZeroDivisionError: division by zero
</code></pre>
<p>Yep. We got our stack trace. Let's move on 🚀.</p>
<h2 id="heading-module-level-logs-namespaces-or-however-you-want-call-them">Module level logs, namespaces, or however you want call them</h2>
<p>So far, we have used the default logger, aka the root logger. 
That's all fine, but most Python applications consist of multiple modules logging altogether. 
In that case, we would probably want to differentiate which logs come from which modules. I mean, why not?</p>
<p>Let's see an example of such a <a target="_blank" href="https://docs.python.org/3/library/logging.html#module-level-functions">module level</a> logs [5]:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> logging

module_logger = logging.getLogger(<span class="hljs-string">'module_logger'</span>)
module_logger.warning(<span class="hljs-string">'module_logger log message'</span>)

logging.warning(<span class="hljs-string">'module_logger log message'</span>)
</code></pre>
<p>Expected output:</p>
<pre><code class="lang-shell">module_logger test
root test
</code></pre>
<p>We created a custom logger instance named <code>module_logger</code>. Different from the root logger, its name is not part of the output. But have no fear! We can add to our output via <a target="_blank" href="https://docs.python.org/3/library/logging.html#formatter-objects">formatting</a> [4]:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> logging

logging.basicConfig(format=<span class="hljs-string">'%(name)s %(message)s'</span>,)
module_logger = logging.getLogger(<span class="hljs-string">'module_logger'</span>)
module_logger.warning(<span class="hljs-string">'test'</span>)
logging.warning(<span class="hljs-string">'test'</span>)
</code></pre>
<p>Observe the output:</p>
<pre><code class="lang-shell">module_logger log message
WARNING:root:module_logger log message
</code></pre>
<p>Kind of cool. I would say 😎.</p>
<h2 id="heading-handlers">Handlers</h2>
<p>Let's have a look now at log <a target="_blank" href="https://docs.python.org/3/library/logging.handlers.html">handlers</a>[6]. Handlers are helpful when we want to(surprisingly) handle logs in multiple ways, for example, send the output to various destinations. Each logger might have multiple handlers.</p>
<p>Example:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> logging

logger = logging.getLogger(__name__)

logger.addHandler(logging.StreamHandler())
logger.addHandler(logging.FileHandler(<span class="hljs-string">'out.log'</span>))

logger.warning(<span class="hljs-string">'test'</span>)
logger.error(<span class="hljs-string">'test'</span>)
</code></pre>
<p>Let's run it. We should see the message both in our terminal and in the local file named: <code>out.log</code>. Notice that the file will not be overridden when we re-run the program; content will be appended. One common use of logging handlers is sending logs over the network. For example, log aggregation services.</p>
<h2 id="heading-structured-logs">Structured Logs</h2>
<p>We covered briefly in previous sections the log formatting. That helps with log structure. But once we start configuring our logger, we might find ourselves writing and maintaining a boilerplate code. 
I found <a target="_blank" href="https://pypi.org/project/python-json-logger/">jsonlogger</a>[7] very useful for JSON-structured logs. It is easy to configure, it just works. Once installed, here's an example of how to use it:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> logging
<span class="hljs-keyword">from</span> pythonjsonlogger <span class="hljs-keyword">import</span> jsonlogger

root_logger = logging.getLogger()
logHandler = logging.StreamHandler()
logHandler.setFormatter(jsonlogger.JsonFormatter())

root_logger.addHandler(logHandler)

root_logger.setLevel(logging.DEBUG)

logger = logging.getLogger(<span class="hljs-string">'test'</span>)
logger.info(<span class="hljs-string">'test'</span>)
logger.warning(<span class="hljs-string">'test'</span>)
logger.error(<span class="hljs-string">'test'</span>)
</code></pre>
<p>Expected output:</p>
<pre><code class="lang-shell">{"message": "test"}
{"message": "test"}
{"message": "test"}
</code></pre>
<p>JSON-structured logs are useful when processing log files for analytics or BI (business intelligence). As well as for searching. Searching and correlating log messages is very valuable when developing and troubleshooting.</p>
<h2 id="heading-logs-and-unix-streams">Logs and Unix streams</h2>
<p>In Unix-based systems, we have <code>stdin</code>, <code>stdout</code>, and <code>stderr</code> streams. Unix streams are like everything else in Unix, treated as files 🐧. What I mean is that we can read and write to streams as if we were handling files. Well, kind of.</p>
<p>Ok, cool. how is it relevant to this post? 
Let's run one of the logging applications from above, and check which stream is it using:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> logging

logger = logging.getLogger(__name__)

logger.addHandler(logging.StreamHandler())
logger.addHandler(logging.FileHandler(<span class="hljs-string">'out.log'</span>))

logger.warning(<span class="hljs-string">'test'</span>)
logger.error(<span class="hljs-string">'test'</span>)
</code></pre>
<p>We can run and redirect each stream to a separate file.</p>
<pre><code class="lang-shell">$ python logs.py 1&gt; stdout.log 2&gt; stderr.log
</code></pre>
<p>Here, 1 stands for <code>stdout</code>, 2 for <code>stderr</code>. We redirect <code>stdout</code> stream to <code>stdout.log</code> file and <code>stderr</code> to <code>stderr.log</code>. More info about <a target="_blank" href="https://www.gnu.org/software/bash/manual/bash.html#Redirecting-Output">redirecting output</a> [8].</p>
<p>If we check your <code>stderr.log</code> file, it has all the content. How come? Was it an actual error? No? Well, that's the default behavior of the Python <code>logging</code> module. I found it quite surprising 😶. It's problematic cause if our application sends logs to some kind of log aggregation service such as (logstash)[https://www.elastic.co/logstash/] or (datadog)[https://www.datadoghq.com/] our logs will be identified as errors when in reality they are not.</p>
<p>We can add <code>stream</code> parameter to point to <code>stdout</code>:</p>
<pre><code class="lang-python">logger.addHandler(logging.StreamHandler(stream=sys.stdout))
</code></pre>
<p>We should be able to locate the log content in <code>stdout.log</code> now. </p>
<h1 id="heading-streaming-to-both-stdout-and-stderr-segregation">Streaming to both stdout and stderr segregation</h1>
<p>When our application is monitored by other services, it can be useful to have the segregation of error and other logs.</p>
<p>One way to achieve to define multiple handlers:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> sys
<span class="hljs-keyword">import</span> logging
<span class="hljs-keyword">from</span> pythonjsonlogger <span class="hljs-keyword">import</span> jsonlogger

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CustomJsonFormatter</span>(<span class="hljs-params">jsonlogger.JsonFormatter</span>):</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">add_fields</span>(<span class="hljs-params">self, log_record, record, message_dict</span>):</span>
        super(CustomJsonFormatter, self).add_fields(log_record, record, message_dict)
        log_record[<span class="hljs-string">"level"</span>] = record.levelname
        log_record[<span class="hljs-string">"name"</span>] = record.name
        log_record[<span class="hljs-string">"message"</span>] = record.getMessage()
        <span class="hljs-keyword">if</span> record.levelname == <span class="hljs-string">"ERROR"</span> <span class="hljs-keyword">or</span> record.levelname == <span class="hljs-string">"EXCEPTION"</span>:
            log_record[<span class="hljs-string">"exception"</span>] = record.exc_info
            log_record[<span class="hljs-string">"stacktrace"</span>] = record

formatter = CustomJsonFormatter(<span class="hljs-string">"%(level)s %(name)s %(message)s"</span>)

stdoutHandler = logging.StreamHandler(stream=sys.stdout)
stdoutHandler.setLevel(logging.DEBUG)
stdoutHandler.setFormatter(formatter)

stderrHandler = logging.StreamHandler(stream=sys.stderr)
stderrHandler.setLevel(logging.ERROR)
stderrHandler.setFormatter(formatter)

root_logger = logging.getLogger()
root_logger.setLevel(logging.DEBUG)

root_logger.addHandler(stderrHandler)
root_logger.addHandler(stdoutHandler)
</code></pre>
<p>However, that results in duplication of logs; the ERROR logs will result in both <code>stderr</code> and <code>stdout</code> streams. That's far from ideal.</p>
<p>But! We can use builtin <code>Filter Objects</code>. Example:</p>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">StdoutFilter</span>(<span class="hljs-params">logging.Filter</span>):</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">filter</span>(<span class="hljs-params">self, record</span>):</span>
        <span class="hljs-keyword">return</span> record.levelno &gt;= ROOT_LEVEL <span class="hljs-keyword">and</span> record.levelno &lt; logging.ERROR
</code></pre>
<p>Here we defined a class that inherits from <code>logging.Filter</code>. It will decide for each log record whether it should be handled by the handler or not.</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> sys
<span class="hljs-keyword">import</span> logging
<span class="hljs-keyword">from</span> pythonjsonlogger <span class="hljs-keyword">import</span> jsonlogger


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CustomJsonFormatter</span>(<span class="hljs-params">jsonlogger.JsonFormatter</span>):</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">add_fields</span>(<span class="hljs-params">self, log_record, record, message_dict</span>):</span>
        super(CustomJsonFormatter, self).add_fields(
            log_record, record, message_dict)
        log_record[<span class="hljs-string">"level"</span>] = record.levelname
        log_record[<span class="hljs-string">"name"</span>] = record.name
        log_record[<span class="hljs-string">"message"</span>] = record.getMessage()
        <span class="hljs-keyword">if</span> record.levelname == <span class="hljs-string">"ERROR"</span> <span class="hljs-keyword">or</span> record.levelname == <span class="hljs-string">"EXCEPTION"</span>:
            log_record[<span class="hljs-string">"exception"</span>] = record.exc_info
            log_record[<span class="hljs-string">"stacktrace"</span>] = record


ROOT_LEVEL = logging.DEBUG


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">StdoutFilter</span>(<span class="hljs-params">logging.Filter</span>):</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">filter</span>(<span class="hljs-params">self, record</span>):</span>
        <span class="hljs-keyword">return</span> record.levelno &gt;= ROOT_LEVEL <span class="hljs-keyword">and</span> record.levelno &lt; logging.ERROR


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">StderrFilter</span>(<span class="hljs-params">logging.Filter</span>):</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">filter</span>(<span class="hljs-params">self, record</span>):</span>
        <span class="hljs-keyword">return</span> record.levelno &gt;= logging.ERROR


formatter = CustomJsonFormatter(<span class="hljs-string">"%(level)s %(name)s %(message)s"</span>)

stdoutHandler = logging.StreamHandler(stream=sys.stdout)
stdoutHandler.setFormatter(formatter)
stdoutHandler.addFilter(StdoutFilter())

stderrHandler = logging.StreamHandler(stream=sys.stderr)
stderrHandler.setFormatter(formatter)
stderrHandler.addFilter(StderrFilter())


root_logger = logging.getLogger()
root_logger.setLevel(ROOT_LEVEL)

root_logger.addHandler(stderrHandler)
root_logger.addHandler(stdoutHandler)


logging.debug(<span class="hljs-string">'test'</span>)
logging.info(<span class="hljs-string">'test'</span>)
logging.warning(<span class="hljs-string">'test'</span>)
logging.error(<span class="hljs-string">'test'</span>)
logging.critical(<span class="hljs-string">'test'</span>)
</code></pre>
<blockquote>
</blockquote>
<p>Run it:</p>
<pre><code class="lang-shell">$ python logs.py 1&gt; stdout.log 2&gt; stderr.log
</code></pre>
<p>And observe file content:</p>
<p><code>stdout.log</code>:</p>
<pre><code class="lang-text">{"level": "DEBUG", "name": "root", "message": "test"}
{"level": "INFO", "name": "root", "message": "test"}
{"level": "WARNING", "name": "root", "message": "test"}
</code></pre>
<p><code>stderr.log</code>:</p>
<pre><code class="lang-text">{"level": "ERROR", "name": "root", "message": "test", "exception": null, "stacktrace": "&lt;LogRecord: root, 40, ...main.py, 53, \"test\"&gt;"}
{"level": "CRITICAL", "name": "root", "message": "test"}
</code></pre>
<p>Another promising library I've encountered is <a target="_blank" href="https://loguru.readthedocs.io/en/stable/overview.html">loguru</a>9]. However, I couldn't figure out how to configure stream segregation there.</p>
<h2 id="heading-summary">Summary</h2>
<p>Python logging module should be flexible enough to fit the general purpose. First, however, we need to understand how it works, especially if we come from another programming language background. Python logging module default behavior, and the difference between root and module loggers, were alien to me. But I survived 💪. I hope it makes sense and gives a high-level overview of how to use the logging module and its configurations.</p>
<p>Another thing to mention is that all these logging examples could've been implemented as <a target="_blank" href="https://docs.python.org/3.10/howto/logging-cookbook.html#custom-handling-of-levels">configuration files</a>[10].</p>
<h2 id="heading-references">References</h2>
<p>[1] https://docs.python.org/3/library/logging.html</p>
<p>[2] https://docs.python.org/3/library/logging.html#logging-levels</p>
<p>[3] https://docs.python.org/3/library/logging.html#logging.basicConfig</p>
<p>[4] https://docs.python.org/3/library/logging.html#formatter-objects</p>
<p>[5] https://docs.python.org/3/library/logging.html#module-level-functions</p>
<p>[6] https://docs.python.org/3/library/logging.handlers.html</p>
<p>[7] https://pypi.org/project/python-json-logger/</p>
<p>[8] https://www.gnu.org/software/bash/manual/bash.html#Redirecting-Output</p>
<p>[9] https://loguru.readthedocs.io/en/stable/overview.html</p>
<p>[10] https://docs.python.org/3.10/howto/logging-cookbook.html#custom-handling-of-levels</p>
]]></content:encoded></item><item><title><![CDATA[React website in 6 steps (without HTTP server)]]></title><description><![CDATA[This short article will demonstrate how I built a "Hello World" React application-based website, without serving it from an HTTP server. Nothing fancy; I just thought it might be helpful for someone with the same issues as me. 
I found the documentat...]]></description><link>https://blog.p3ld3v.dev/react-website-in-6-steps-without-http-server</link><guid isPermaLink="true">https://blog.p3ld3v.dev/react-website-in-6-steps-without-http-server</guid><category><![CDATA[React]]></category><category><![CDATA[Static Website]]></category><category><![CDATA[serverless]]></category><dc:creator><![CDATA[Pavel Durov]]></dc:creator><pubDate>Tue, 11 Oct 2022 17:38:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1666362836782/oU8klktqf.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This short article will demonstrate how I built a "Hello World" React application-based website, without serving it from an HTTP server. Nothing fancy; I just thought it might be helpful for someone with the same issues as me. 
I found the documentation a bit off. I mean, it's there, if you know where to look 👀.</p>
<p>I will use yarn as my package manager, just cause I like it, but similar (even almost identical) can be achieved using npm.</p>
<p>Ok, let's go :)</p>
<h4 id="heading-step-1-install-create-react-app">Step 1. Install  create-react-app</h4>
<pre><code class="lang-shell">$ yarn global add create-react-app
</code></pre>
<p>It will install the <code>create-react-app</code> package globally on our machine, which means it will be accessible from every terminal [1].</p>
<h4 id="heading-step-2-create-react-app">Step 2. Create React app</h4>
<pre><code class="lang-shell">$ create-react-app me --template typescript
</code></pre>
<p>This command will create the "Hello World" project with the TypeScript template. Why TypeScript? Why not? I love it!</p>
<h4 id="heading-step-3-install-dependencies">Step 3. Install dependencies</h4>
<p>Go into the generated folder (in our case, <code>me</code>) and install all the dependencies of the project:</p>
<pre><code class="lang-shell">$ cd me &amp;&amp; yarn
</code></pre>
<p>Should get a file structure similar to this:</p>
<pre><code class="lang-shell">→ ls -la
total 3144
-rw-r--r--   ... .gitignore
-rw-r--r--   ... README.md
drwxr-xr-x   ... build
drwxr-xr-x   ... node_modules
-rw-r--r--   ... package-lock.json
-rw-r--r--   ... package.json
drwxr-xr-x   ... public
drwxr-xr-x   ... src
-rw-r--r--   ... tsconfig.json
-rw-r--r--   ... yarn.lock
</code></pre>
<h4 id="heading-step-4-build-your-app">Step 4. Build your app</h4>
<p>Build script creates a build directory with a production build of your app.</p>
<pre><code class="lang-shell">$ yarn build
</code></pre>
<p>This should result in similar to the following:</p>
<pre><code class="lang-shell">→ ls -l ./build/
total 80
-rw-r--r-- ... .gitignore
-rw-r--r-- ... README.md
drwxr-xr-x ... build
drwxr-xr-x ... node_modules
-rw-r--r-- ... package-lock.json
-rw-r--r-- ... package.json
drwxr-xr-x ... public
drwxr-xr-x ... src
-rw-r--r-- ... tsconfig.json
-rw-r--r-- ... yarn.lock
</code></pre>
<h4 id="heading-step-5-launch-our-static-website-in-the-browser-and-fail">Step 5. Launch our static website in the browser and fail</h4>
<p>Navigate to your file in your browser. 
You can type the path to your file in the search bar something like:</p>
<pre><code class="lang-shell">file:///&lt;YOUR MACHINE PATH HERE&gt;/build/index.html
</code></pre>
<p>Or just drag and drop it into a new tab. Anything that works for you.</p>
<p>Now observe. It's empty. Simplistic. Minimal. Beautiful. But not quite what you wanted, innit?</p>
<p>The problem is that our react app is built to be served by a server. </p>
<p>Open up the console and observe the error logs:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665507404281/FKBYqiduz.png" alt="Screenshot 2022-10-11 at 17.56.21.png" /></p>
<h4 id="heading-step-6-find-solutions-to-our-problems">Step 6. Find solutions to our problems!</h4>
<p>Have no fear! The documentation is here! [2]</p>
<p>If you read the docs, its sates:</p>
<pre><code class="lang-text">....set up your favorite HTTP server ...
</code></pre>
<p>But... I don't want to set up my FAVOURITE HTTP server. I'm not even sure I have one...</p>
<p>If you look further down you will see a section called <strong>Building for Relative Paths</strong> [3].</p>
<p>That's what we need!</p>
<p>Let's set <code>"homepage": "./",</code> in your package JSON:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"me"</span>,
  <span class="hljs-attr">"version"</span>: <span class="hljs-string">"0.1.0"</span>,
  <span class="hljs-attr">"private"</span>: <span class="hljs-literal">true</span>,
 ...
  <span class="hljs-attr">"homepage"</span>:<span class="hljs-string">"."</span>, &lt;---ADD THIS
 ...
</code></pre>
<p>Build the app again. Open<code>index.html</code> again. And you should get the spinning React logo :)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665507637854/KhescvX7w.png" alt="Screenshot 2022-10-11 at 18.00.12.png" /></p>
<p>That's it!</p>
<h4 id="heading-references">References</h4>
<p>[1] https://classic.yarnpkg.com/lang/en/docs/cli/global/</p>
<p>[2] https://create-react-app.dev/docs/deployment/</p>
<p>[3] https://create-react-app.dev/docs/deployment/#building-for-relative-paths</p>
]]></content:encoded></item><item><title><![CDATA[Linux VNC server & macOS VNC client]]></title><description><![CDATA[If you like me, having a Linux PC or a laptop at home but you're using your mac for work on a day-to-day basis. A local VNC server can be nice if you want to run or do some experiments with a native Linux OS.
In my case, I am running different compil...]]></description><link>https://blog.p3ld3v.dev/linux-vnc-server-macos-vnc-client</link><guid isPermaLink="true">https://blog.p3ld3v.dev/linux-vnc-server-macos-vnc-client</guid><category><![CDATA[VNC]]></category><category><![CDATA[Linux]]></category><category><![CDATA[macOS]]></category><dc:creator><![CDATA[Pavel Durov]]></dc:creator><pubDate>Mon, 10 Oct 2022 16:27:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1666363044377/idEelkFct.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you like me, having a Linux PC or a laptop at home but you're using your mac for work on a day-to-day basis. A local VNC server can be nice if you want to run or do some experiments with a native Linux OS.</p>
<p>In my case, I am running different compilation processes that are CPU-specific. Since my mac comes with an M1 chip, not everything is 100% compatible, especially when you go to the more niche software or when new updates break some kind of bizarre C lib dependencies 😶.</p>
<p>So, here’s a guide on how I installed the local VNC server that I used daily in segregation with plain ssh when CLI access is just enough. But sometimes GUI is all you need.</p>
<p>I will cover Linux VNC server setup and macOS client configuration, but as you probably guessed, you can also connect with not only macOS clients to the Linux VNC server.</p>
<h2 id="heading-vnc-server-install-andamp-config">VNC server install &amp; config</h2>
<p>There are several options for VNC servers; we’re going to use x11vn [1]. VNC server allows you to view and interact remotely with a machine.</p>
<h3 id="heading-installation">Installation:</h3>
<p>My Linux machine distro: Ubuntu 21.04</p>
<p>Installing VNC server and X window system:</p>
<pre><code class="lang-shell">$ sudo apt install lightdm x11vnc
</code></pre>
<p>You will be prompted with a message to choose a display manager. I chose lightdm [2], don't ask me why 🙃.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665418912551/4kXkFMK1B.png" alt="image.png" /></p>
<p>Try running it after the installation to check that we’re good.</p>
<pre><code class="lang-bash">$ x11vnc
</code></pre>
<p>If all is good, you should see something like this; if not, then go fish 🎣.</p>
<pre><code class="lang-shell">28/09/2022 17:58:12 *** XOpenDisplay failed. No -display or DISPLAY.
28/09/2022 17:58:12 *** Trying “:0” in 4 seconds. Press Ctrl-C to abort.
28/09/2022 17:58:12 *** 1 2 3 4
28/09/2022 17:58:16 *** XOpenDisplay of “:0” successful.
....
The VNC desktop is:      kimchi-machine:0
PORT=5900
</code></pre>
<p>This means that x11vnc could successfully identify X display :0. I guess that meant that we could try and connect to it 🤔!</p>
<p>But before that, let’s configure our VNC server ☝️.</p>
<h2 id="heading-creating-systemd-service">Creating systemd service</h2>
<p>It's up to you, really, you don’t have to create a service to run on the server, but if you want the VNC server to run in the background instead of launching it from your terminal every time and make sure that nothing is closed the terminal session, you do need it. And tbh its quite easy todo.</p>
<p>Create our vnc service config [3] — located under/lib/systemd/system let’s call it vnc.service :</p>
<pre><code class="lang-shell">$ sudo vim /lib/systemd/system/vnc.service
</code></pre>
<p>I’m using vim as a text editor; you can use whatever. Here’s nano command:</p>
<pre><code class="lang-shell">$ sudo nano /lib/systemd/system/vnc.service
</code></pre>
<p>Then enter the following config, see comments for description:</p>
<pre><code class="lang-shell">[Unit]
# service description
Description=my local x11vnc kimchi service
# start this service after:
After=display-manager.service network.target syslog.target
[Service]
# the type of the service
Type=simple
# process config
ExecStart=/usr/bin/x11vnc -forever -display :0 -auth guess -passwd kimchi
# do this on process stop
ExecStop=/usr/bin/killall x11vnc
# restart when failed
Restart=on-failure
[Install]
# start this service before multi-user target
WantedBy=multi-user.target
</code></pre>
<p>You might ask — whats multi-user.target? I’m not an expert, but a quick internet search revealed that in this context, it means that our service would start when the system reaches run level 2 [4]. I guess that’s cool to know 😐.</p>
<p>note that I set the password to be “kimchi”, you should probably change it!</p>
<h2 id="heading-starting-our-systemd-service">starting our systemd service</h2>
<p>Reaload systemd [5] to load our new service:</p>
<pre><code class="lang-shell">$ systemctl daemon-reload
</code></pre>
<p>Enable our service:</p>
<pre><code class="lang-shell">$ systemctl enable vnc.service
</code></pre>
<p>Start our service:</p>
<pre><code class="lang-shell">$ systemctl start vnc.service
</code></pre>
<p>Check our service status:</p>
<pre><code class="lang-shell">$ systemctl status x11vnc.service
</code></pre>
<p>Should get something like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665419083983/V33Z05DDC.png" alt="image.png" /></p>
<p>If you ever want to stop our VNC service, run:</p>
<pre><code class="lang-shell">$ systemctl disable x11vnc.service
$ systemctl stop x11vnc.service
</code></pre>
<h2 id="heading-finally-connect-from-your-mac">Finally, connect from your mac</h2>
<p>First, find your VNC server machine IP. You can do it by running:</p>
<pre><code class="lang-shell">$ ifconfig
</code></pre>
<p>the output differs between networks, mine looks like this:</p>
<pre><code class="lang-shell">enp5s0: flags=....
        inet ....
 broadcast 192.168.88.255
</code></pre>
<p>So we go the IP — 192.168.88.255 💪</p>
<p>Next, open up your Finder and navigate to GO &gt; Connect to Server</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665419140001/V8cTIxV0s.png" alt="image.png" /></p>
<p>Enter the IP we got with the default port for VNC</p>
<p>IP — 192.168.88.255</p>
<p>Port — 5900</p>
<p>Protocol: VNC</p>
<p>All together now:</p>
<pre><code class="lang-shell">vnc://192.168.88.255:5900
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665419158703/mvY2sxQmf.png" alt="image.png" /></p>
<p>You’ll be prompted with a password. In case you forgot, its “kimchi” (or whatever you set):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665419169406/hiAOaDMoJ.png" alt="image.png" /></p>
<p>And Voilà</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665419178909/zy6USgnm2.png" alt="image.png" /></p>
<p>That’s it!</p>
<h2 id="heading-references">References</h2>
<p>[1] https://wiki.archlinux.org/title/X11vnc</p>
<p>[2] https://wiki.archlinux.org/title/LightDM</p>
<p>[3] https://www.digitalocean.com/community/tutorials/understanding-systemd-units-and-unit-files</p>
<p>[4] https://unix.stackexchange.com/questions/404667/systemd-service-what-is-multi-user-target</p>
<p>[5] https://wiki.archlinux.org/title/Systemd</p>
]]></content:encoded></item><item><title><![CDATA[PyPy,Python and performance benchmarking]]></title><description><![CDATA[In this article, I will cover my experience with the PyPy that I was only recently exposed to. This article complements the Writing an Interpreter with PyPy tutorial from 2011 [1]. When I first tried to follow the steps of this blog post, I encounter...]]></description><link>https://blog.p3ld3v.dev/pypypython-and-performance-benchmarking</link><guid isPermaLink="true">https://blog.p3ld3v.dev/pypypython-and-performance-benchmarking</guid><category><![CDATA[pypy]]></category><category><![CDATA[Python]]></category><category><![CDATA[Benchmark]]></category><category><![CDATA[performance]]></category><dc:creator><![CDATA[Pavel Durov]]></dc:creator><pubDate>Mon, 10 Oct 2022 12:49:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1667059872872/WTrHgEOMz.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this article, I will cover my experience with the PyPy that I was only recently exposed to. This article complements the <a target="_blank" href="https://morepypy.blogspot.com/2011/04/tutorial-writing-interpreter-with-pypy.html">Writing an Interpreter with PyPy</a> tutorial from 2011 [1]. When I first tried to follow the steps of this blog post, I encountered many issues, such as out-of-date documentation, out-of-date code references, python version incompatibility etc. I will try to cover the gotchas and my learning experience here.</p>
<p>Writing an Interpreter with PyPy is about creating a <a target="_blank" href="https://en.wikipedia.org/wiki/Brainfuck">BF</a> [2] interpreter and the translation process of it with the PyPy toolchain. When I followed along, the main issues for me were related to the toolchain itself. Hence I decided to centre my attention on it here. We will not cover the BF interpreter here since it's all explained in the original blog post, but I will go through the PyPy toolchain and some basic benchmarking concepts essential for understanding the topic.</p>
<h2 id="heading-what-exactly-is-pypy">What exactly is PyPy 🤔</h2>
<p>If you tried Python before, you were probably running <a target="_blank" href="https://en.wikipedia.org/wiki/CPython">CPython</a> [3]. CPython is the most common implementation of Python VM (Virtual Machine). <a target="_blank" href="https://www.pypy.org/">PyPy</a> [4] is an alternative to CPython.
With PyPy, we write our programs in <a target="_blank" href="https://doc.pypy.org/en/latest/coding-guide.html#restricted-python">RPython</a> [5], and apply the RPython translation toolchain that generates binary executable. One major advantage of PyPy over CPython is speed which will be demonstrated later in this article using the basic benchmarking tool.</p>
<p><strong>Jargon summary:</strong></p>
<p>CPython — The most common Python implementation</p>
<p>PyPy — CPython alternative</p>
<p>RPython — restricted version of Python</p>
<p>Benchmark — the act of assessing program relative performance (time in our case)</p>
<h2 id="heading-getting-hands-on-with-rpython">Getting hands-on with RPython 👐</h2>
<p>First, we need to write a simple RPython program, call it <code>python_prime.py</code>:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> os
<span class="hljs-keyword">import</span> sys


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">prime</span>(<span class="hljs-params">n</span>):</span>
    primes = []
    <span class="hljs-keyword">for</span> num <span class="hljs-keyword">in</span> range(<span class="hljs-number">0</span>, n):
        <span class="hljs-keyword">if</span> num &gt; <span class="hljs-number">1</span>:
            <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(<span class="hljs-number">2</span>, num):
                <span class="hljs-keyword">if</span> (num % i) == <span class="hljs-number">0</span>:
                    <span class="hljs-keyword">break</span>
            <span class="hljs-keyword">else</span>:
                primes.append(num)
    <span class="hljs-keyword">return</span> primes


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">write</span>(<span class="hljs-params">s</span>):</span>
    os.write(<span class="hljs-number">1</span>, bytes(s))


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">entry_point</span>(<span class="hljs-params">argv</span>):</span>
    num = int(argv[<span class="hljs-number">1</span>])
    primes = prime(num)
    write(<span class="hljs-string">'calculated primes: \n'</span>)
    <span class="hljs-keyword">for</span> p <span class="hljs-keyword">in</span> primes:
        write(str(p) + <span class="hljs-string">' '</span>)
    <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">target</span>(<span class="hljs-params">*args</span>):</span>
    <span class="hljs-string">"""
    "target" returns the entry point.
    The translation process imports your module and looks for that name,
    calls it, and the function object returned is where it starts the translation.
    """</span>
    <span class="hljs-keyword">return</span> entry_point, <span class="hljs-literal">None</span>


<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">'__main__'</span>:
    entry_point(sys.argv)
</code></pre>
<p>It definitely resembles Python 👀. That’s cause RPython is Restricted Python. We had to choose some kind of computational problem, so we went with a prime number sequence generator. The actual compute work is not the point here 🙂.</p>
<p>Our RPython program also has some PyPy hooks — a function called <code>target</code> (the comment should be self-explanatory).</p>
<h2 id="heading-running-the-program">Running the program 🏃</h2>
<p>We will use the Python 2.7.18 version since I found incompatibility issues with higher versions. If you don’t have the 2.7 Python version installed, you can use of the tools such as <a target="_blank" href="https://github.com/pyenv/pyenv">pyenv</a> [6] or <a target="_blank" href="https://docs.conda.io/projects/conda/en/latest">conda</a> [7].</p>
<p>Run our program:</p>
<pre><code class="lang-bash">$ python ./python_prime.py 10
</code></pre>
<p>We should get similar output to this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665405585535/2UxJcyiOF.png" alt="image.png" /></p>
<h2 id="heading-benchmarking">Benchmarking 🏋️</h2>
<p>Now when we have our program in our hands, we can do some benchmarking to understand its runtime performance.
We will use a tool called <a target="_blank" href="https://github.com/sharkdp/hyperfine">hyperfine</a> [8] for that.</p>
<p>Mac installation:</p>
<pre><code class="lang-bash">$ brew install hyperfine
</code></pre>
<p>Linux Debian Installation:</p>
<pre><code class="lang-bash">$ wget https://github.com/sharkdp/hyperfine/releases/download/v1.15.0/hyperfine_1.15.0_amd64.deb 
$ sudo dpkg -i hyperfine_1.15.0_amd64.deb
</code></pre>
<p>Similarly to how we run our RPython program, we can run it through the hyperfine utility:</p>
<pre><code class="lang-bash">$ hyperfine <span class="hljs-string">'python ./python_prime.py 10000'</span>
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665405740588/9LzqectmI.png" alt="1_XeBN0O8cIxjQUS5IQahQPQ.png" /></p>
<p>Hyperfine runs our program ten times by default and gives us stats such as minimum and maximum execution times. We're not going to cover hyperfine in detail here; the important thing is to get a feel for it and a high-level understanding when comparing runtimes.</p>
<p>Generally, benchmarking results might be affected by different factors, such as the machine you run on, other processes that run simultaneously as you run benchmarking, hot vs cold starts, caching, etc. But, for our purpose, this benchmarking process we have here is good enough.</p>
<h2 id="heading-translating-our-program-using-pypy">Translating our program using PyPy 🤓</h2>
<p>The first step would be to get the PyPy source code:</p>
<pre><code class="lang-bash">$ wget https://downloads.python.org/pypy/pypy2.7-v7.3.9-src.tar.bz2 
$ tar -xvf pypy2.7-v7.3.9-src.tar.bz2 <span class="hljs-comment"># extracting </span>
$ tar mv pypy2.7-v7.3.9-src ./pypy
</code></pre>
<p>Here we're downloading the source code, extracting it and renaming the extracted directory for later convenience.
Once downloaded and extracted, run the PyPy translation toolchain:</p>
<pre><code class="lang-bash">$ <span class="hljs-built_in">export</span> PYTHONPATH=<span class="hljs-variable">${PWD}</span>:<span class="hljs-variable">${PWD}</span>/pypy/ 
$ python ./pypy/rpython/translator/goal/translate.py python_prime.py
</code></pre>
<p>If you compare the tutorial code to mine, you will notice the difference in the file paths - translate.py was moved from pypy/translator/goal/translate.py to rpython/translator/goal/translate.py.</p>
<p>Also, I set the PYTHONPATH [9] environment variable to the current directory and the path to the PyPy source code. That would make the PyPy modules available for our program to run.</p>
<p>When running it, you should see toolchain logs on your terminal (it takes around 30 seconds on my M1 chip); when the process is complete, we should get the translated binary with the same name as the python file but with "-c" postfix.</p>
<pre><code class="lang-bash">$ ls -lah ./python_prime-c 
-rwxr-xr-x 1 medium medium 217K 24 Sep 11:34 ./python_plain_class-c
</code></pre>
<p>Let's have a look inside 👀</p>
<pre><code class="lang-bash">$ file ./python_plain_class.py ./python_plain_class-c 
python_plain_class.py: Python script text executable, ASCII text python_plain_class-c: Mach-O 64-bit executable x86_64
</code></pre>
<p>In case you don't trust the file command:</p>
<pre><code class="lang-bash">$ head ./python_plain_class-c
������ H__PAGEZEROx__TEXT__text__TEXT0/��0/�__stubs__TEXT��&gt;��__stub_helper__TEXT�<span class="hljs-string">"��__cstring__TEXT2�q2�__const__TEXT�����__unwind_info__TEXTD��D�__eh_frame__TEXT8��...</span>
</code></pre>
<p>Yep, it's indeed a binary file 🙂.
Ok, so we translated Python using PyPy to a binary; now what?</p>
<h2 id="heading-compare-benchmarks">Compare benchmarks 🔬</h2>
<p>Run hyperfine on our translated program:</p>
<pre><code class="lang-bash">$ hyperfine <span class="hljs-string">'./python_prime-c 10000'</span>
</code></pre>
<p>Should get something like this output:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665405908291/WuSV1_uDd.png" alt="image.png" /></p>
<p>Spot the difference? We can also just run hyperfine to compare both programs:</p>
<pre><code>$ hyperfine <span class="hljs-string">'./python_prime-c 10000'</span> <span class="hljs-string">'python ./python_prime.py 10000'</span>
</code></pre><p>See full result:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665405940109/Eo0vZZ8ox.png" alt="image.png" /></p>
<p>Say What!? 72 times faster 🤯?!</p>
<p>Well yeah, apparently 😶. Hyperfine won't lie to you. You can trust it☝️.</p>
<p>Did you actually expect that ?? Maybe I should have named this post - "Run your python programs X70 faster"? I think that's really cool… Ok, moving along 🙃.</p>
<p>Let's run hyperfine again, this time with worm-up and more runs:</p>
<pre><code class="lang-bash">$ hyperfine --warmup 10 <span class="hljs-string">'./python_prime-c 10000'</span> <span class="hljs-string">'python ./python_prime.py 10000'</span> \
&amp;&amp; hyperfine -m 100 -M 100 <span class="hljs-string">'./python_prime-c 10000'</span> <span class="hljs-string">'python ./python_prime.py 10000'</span>
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665405969464/CVDx3BJOC.png" alt="image.png" /></p>
<p>We executed ten warmups before running the benchmark, then ran our programs 100 times each and recorded their execution times. The result is not far from the previous one; it's exactly the same metrics since the benchmark might have been skewed a bit as I'm not running it in a lab!</p>
<h2 id="heading-summary">Summary ✏️</h2>
<p>So what have we done? We built a simple RPython program, translated it using the PyPy toolchain, and witnessed substantial performance improvement with our eyes when comparing both program execution times side by side using hyperfine.</p>
<p>PyPy seems like a good choice for writing computational-intensive applications, especially if you want to stay in Python land. However, if you try it for a while, you will see that it might be less convenient than working with CPython; you might experience some obscure type annotation errors or unsupported function call exceptions. As with everything in life, it has its advantages and drawbacks.</p>
<p>I know, I know. Who cares about Python 2.7?! At the time of the article, we're already at Python 3.10.7. Why are we bothering with Python 2.7? It also reached the end of support cause you missed it!</p>
<p>The main reason is that I was following the article from 2011, and guess what? There was no Python version 3.10.7 in 2011. By the way, I bet you can use newer versions, as I've seen on the official download page. I just haven't tried them yet.
I was excited when I heard about PyPy (almost a decade since I wrote my first Python language). Even if I'm not going to use it in production yet, just knowing that it's practically feasible to perform such optimisation is eye-opening.</p>
<h2 id="heading-whats-next">What's Next ❓️</h2>
<p>Next, we will look at PyPy jit trace logs and perform further optimisations now in our RPython applications.</p>
<h2 id="heading-references">References</h2>
<p>[1] https://morepypy.blogspot.com/2011/04/tutorial-writing-interpreter-with-pypy.html</p>
<p>[2] https://en.wikipedia.org/wiki/Brainfuck</p>
<p>[3] https://en.wikipedia.org/wiki/CPython</p>
<p>[4] https://www.pypy.org/</p>
<p>[5] https://doc.pypy.org/en/latest/coding-guide.html#restricted-python</p>
<p>[6] https://github.com/pyenv/pyenv</p>
<p>[7] https://docs.conda.io/projects/conda/en/latest</p>
<p>[8] https://github.com/sharkdp/hyperfine</p>
<p>[9] https://bic-berkeley.github.io/psych-214-fall-2016/using_pythonpath.html</p>
]]></content:encoded></item></channel></rss>