giovedì 14 novembre 2019

un TCP ... paziente

Solo qualche appunto che serve a me come promemoria...
Si tratta di cose lette tanto tanto tempo fa e in cui mi sono nuovamente imbattuto stamattina: tanto vale scriverci sopra qualche nota.
Si parla di protocollo TCP e di gestione delle situazioni di congestione.
(di solito di TCP non mi occupo minimamente: ma è pur sempre il protocolo che abbiamo "sotto il culo" (cit.). Per cui un minimo minimo di ripassino, quando mi capita, me lo faccio)

Ok, veniamo a noi...
Ci sono due soluzioni parzialmente contradditorie (vedremo perché) per contenere la congestione su una rete TCP:

  • algoritmo di Nagle
  • ACK differiti
In soldoni l'algoritmo di Nagle prevede che la logica della socket implementi una sorta di aggregazione di dati prima di spedirli alla destinazione: il grado di aggregazione dei dati è controllato da dimensione della Sliding Window, dal valore dell' MSS ed infine ... dal ricevimento di ACK!


Vediamo in dettaglio il ruolo del ricevimento dell'ACK da parte del destinatario. Il mittente, a parte fare dei  calcoli sulla dimensione massima per l'aggregazione, applica il seguente criterio per decidere di inviare il pacchetto, si chiede: "sono stati inviati dati non ancora confermati"? Sì? Allora memorizza i dati ma non inviarli ancora".

Quello appena descritto è uno dei passi chiave dell'algoritmo di Nagle, ora accantoniamolo per un attimo l'algoritmo di Nagle e vediamo ora cosa significhi invece ACK differiti. La tecnica degli ACK differiti è anche essa una forma di ottimizzazione che punta a limitare il numero di ACK inviati dal destinatario: in pratica il destinatario risponde con ACK non pacchetto per pacchetto bensì solo dopo che ha ricevuto vari pacchetti. A il destinatario invia un unico pacchetto  ACK che contiene anche informazioni sull' aggiornamento della dimensione della finestra (Sliding Window) ed eventualmente anche un payload di risposta.

Bang!!!

Chiaro il problema? Vediamolo con un esempio di due interlocutori che applicano rispettivamente "algoritmo di Nagle" lato mittente e "ACK differiti" lato destinatario.

- Mittente: ho delle cose da dirti... ma dimmi tu se ti va bene (attesa dell'ACK)
- Destinatario: (sta aspettando altri pacchetti dal Mittente)
- Mittente: (il mittente sta aspettando l'ACK dal Destinatario)
- Destinatario: <... ok, mi sono stufato ...> "Sì, ci sono: che c'è?" (invio ACK dopo timeout)
- Mittente: ecco qua un bel pacchettone di dati per te

Fortuna che ci sono i timeout!
Nella vita reale una comunicazione simile porta a divorzi o dimissioni... nel campo del TCP introduce solo ritardi

Tutta la descrizione precedente parte da un vecchio post della ottima Julia Evans e su cui mi sono reimbattuto oggi https://jvns.ca/blog/2015/11/21/why-you-should-understand-a-little-about-tcp/

Non ho fatto molto altro se non riformularlo in italiano.
Come bonus aggiungo un minimo di "glossario" che è facilmente approfondibile online:

"Nagle's alghorithm": messo a punto da Nagle a metà degli anni ottanta e orientato a ridurre la congestione sulle reti TCP. Può essere disattivato su una singola socket tramite l'opzione TCP_NODELAY

"ACK delay": altra proposta emersa più o meno nello stesso periodo e con lo stesso obiettivo (... pare che i due gruppi fossero all'oscuro l'uno dei lavori dell'altro...). Può essere disattivato su una singola socket tramite l'opzione TCP_QUICKACK (il "setsockopt" va rieseguito dopo ogni "recv")

"Sliding Windows": la stabilisce il destinatario e la aggiorna via via durante la comunicazione passando il valore nel pacchetto ACK: rappresenta la dimensione del payload che il destinatario si aspetta di ricevere. La finestra potrebbe perfino chiudersi durante la comunicazione, questo avviene nel caso il destinatario rilevi una potenziale congestione sulla rete.

"MSS": massima dimensione del payload in un segmento TCP, è negoziata nell'handshake iniziale e non cambia durante la comunicazione

"MTU": massima dimensione del pacchetto TCP, normalmente il MSS è pari all'MTU meno 40 byte (20 byte di header IP e 20 byte di header TCP)

Può essere che non sia stato molto accurato nella descrizione precedente: ripeto che al momento sono solo degli appunti di promemoria: se avete qualche osservazione o correzione vi ringrazio molto!

... strace, tcpdump, wireshark sono ovviamente ottimi amici se vogliamo monitorare il comportamento di queste cose

sabato 11 agosto 2018

Python vs Lua (4)

Ok, ho messo un po' in ordine i miei esperimenti con micropython ed ho raccolto il tutto in un repo su github. Ecco il link

https://github.com/depaolim/micropython_vs_lua

Ricapitolo il mio obiettivo:

  1. costruire un eseguibile C++ che contenga un interprete rispettivamente python e lua (fase di "embedding")
  2. esporre delle funzioni C++ che possano essere chiamate dall'interprete (fase di "extending")

L'esperimento preliminare è sostanzialmente riuscito, per cui provo a condividere qui alcune considerazioni sull'esperienza.
Premetto che non ho ancora fatto alcuna prova sull'effettivo utilizzo di memoria, sulla efficienza di elaborazione e neppure, cosa più importante, non ho ancora provato a far girare il tutto su una schedina hardware effettiva bensì mi sono limitato a far girare il tutto su PC linux
L'ultima avvertenza preliminare è che sia per micropython che per lua non li ho utilizzati in modo esteso.
Dopo tutte queste avvertenze veniamo ora ad una bella considerazione netta:

L'embedding di micropython è un pochino più complesso di quello di Lua. In compenso l'efficienza e la configurabilità di micropython mi paiono nettamente superiori.

Nessuna sorpresa: entrambi i comportamenti sono conseguenza di scelte di design ben precise e perfettamente giustificare per l'obiettivo che ciascuno dei due si pone.

Lua fa una scelta molto semplice e "pulita": localizza tutto lo stato di esecuzione (compresi i puntatori alle funzioni globali) in una struttura denominata Stack.
Per cui, come si può vedere nel file shell_lua.cpp tutta l'inizializzazione risulta concettualmente molto essenziale.
Utilizzando però in modo pervasivo lo Stack Lua ha i seguenti svantaggi:

  1. introduce sistematicamente un ulteriore livello di indirezione in tutti gli accessi alla memoria
  2. consente un controllo pressoché nullo sulle modalità di allocazione e gestione della memoria

La scelta di micropython è quasi opposta. Lo si nota analizzando il file shell_upy.cpp dove si vede chiaramente: allocazione esplicita dello heap, dello stack e, perfino, del garbage-collector: insomma varie istruzioni che "maneggiano" oggetti a basso livello solo per inizializzare e configurare il run-time dell'interprete. Tra l'altro la maggior parte di queste strutture risultano essere statiche e globali.
Ne risulta indubbiamente una maggiore complessità per un programmatore che voglia fare l'embedding di micropython rispetto a Lua
Ma ci sono motivi ben precisi di efficienza per l'uso di queste strutture statiche e il fatto che tutto ciò venga inizializzato esplicitamente è, in effetti uno dei punti di forza delle scelte architetturali di micropython: ognuna di queste configurazioni è sotto il pieno controllo dello sviluppatore!

Non dimentichiamo che micropython ha l'esigenza primaria di poter girare su schedine a microcontrollore per cui l'efficienza, in particolare nell'accesso alla memoria, è un imperativo fondamentale
A partire dal progetto su github (rilasciato con licenza MIT, per altro) ognuno può costruire un micropython ritagliato perfettamente "su misura" sulle caratteristiche dell'hardware su cui dovrà girare.
Solo per citare alcune delle moltissime possibili scelte di embedding posso costruire un micropython: che supporta o non supporta i thread, che supporta o meno le primitive matematiche, che gira senza garbage-collector o con un gc custom, che alloca lo stack sull'uno o l'altro supporto di memoria della schedina, etc. etc. In generale micropython non da nulla per scontato, neppure la disponibilità di un filesystem: per cui prevede di poter configurare il meccanismo di import dei moduli di python andando via via a reperire i moduli sui vari supporti di memoria (Tra l'altro ho notato, disseminati nel codice, vari "punti di misurazione" per controllare, passo per passo, ad esempio, l'occupazione di memoria).

La cosa straordinaria è che tutta questa configurabilità è comunque a fronte della messa a disposizione di un linguaggio decisamente esteso ed efficace come è python.
Insomma mi pare che la sfida di Damien George di quasi 5 anni fa sia stata sostanzialmente vinta: l'intero linguaggio python disponibile per girare su schedine ridottissime.
Damien inoltre mi risulta pienamente attivo sul progetto github coadiuvato da alcuni collaboratori che si sono via via aggiunti negli anni

sabato 4 agosto 2018

Python vs Lua (3)

Nelle puntate precedenti abbiamo parlato di micropython.
... e Lua?
Prima di tutto Lua c'entra perché è una delle opzioni sul tappeto avendo fama di essere un sistema di scripting poco esoso in termini di dimensioni dell'interprete ed inoltre facilmente integrabile in C.
Il limite è che lua è un linguaggio di scripting (con una sintassi anche opinabile) e non certo un linguaggio esteso e general-purpose come python. In merito, lasciate pure i vostri commenti e critiche qui sotto, grazie!
Ok, allora facciamo qualche prova...

Per fare qualche prova senza rischiare di sporcare il mio PC ho preferito usare vagrant
Se non lo avete ancora usato ve lo consiglio: la comodità di un intera macchina virtuale autocontenuta a riga di comando e costruita con due semplici istruzioni!
(Vagrant ha bisogno, come base, di VirtualBox o VMWare, io uso VirtualBox e va benissimo. Ma non è vagrant l'obiettivo di questo post...)

Nel mio caso volevo una ubuntu virtuale per cui ho fatto

$ vagrant init bento/ubuntu-18.04
$ vagrant up

dopo qualche tempo (tempo di scaricamento dell'immagine, se non esiste già sul PC, e avvio della macchina virtuale) avrete possiamo entrare nella macchina

$ vagrant ssh

Da dentro la sessione ssh scarichiamo e compiliamo i sorgenti di lua

    curl -R -O http://www.lua.org/ftp/lua-5.3.5.tar.gz
    tar zxf lua-5.3.5.tar.gz
    cd lua-5.3.5
    make linux test
    ./src/lua -v
    sudo make install

l'ultimo passo è una comodità che possiamo permetterci tanto più che abbiamo una macchina virtuale tutta per lua

Proviamo il primo script lua, salviamo un file fac.lua con il seguente contenuto

function factorial(n)
    if n == 0 then
        return 1
    end
        return n * factorial(n - 1)
end

print(factorial(4))

e lo eseguiamo con:

    lua fac.lua

Ok, ora possiamo passare a qualcosa di un po' più complesso: lanciare lua da C++ e lanciare funzioni C++ da lua

Qui corro un pochino nelle spiegazioni. Se vi interessa qualcosa scrivetelo pure nei commenti. Se posso aiutarvi, volentieri

Prima di tutto ci serve un sorgente C++ che da un lato incorpori l'interprete lua e dall'altro esponga una funzione che poi lua utilizerà. Ecco qui:

#include <assert .h="">
#include <iostream>
#include <vector>
#include <thread>
#include <unistd .h="">

#include "lua.hpp"

static const char* PROMPT = "> ";
static const int num_of_threads = 10;
std::vector threads;


void call_from_thread() {
    std::cout << "Hello, World!" << std::endl;
}


static int lua_sleep(lua_State* L) {
    int isnum;
    int m = lua_tointegerx(L, 1, &isnum);
    assert(isnum);
    usleep(m * 1000);
    return 0;
}


static void add_functions(lua_State* L) {
    lua_pushcfunction(L, lua_sleep);
    lua_setglobal(L, "sleep");
}


int main(void) {
    threads.push_back(std::thread(call_from_thread));

    for (std::thread& t : threads)
        t.join();

 std::cout << "Lua shell 0.0.1" << std::endl;
 lua_State *L = luaL_newstate();  // opens Lua
 luaL_openlibs(L);  // opens the standard libraries
    add_functions(L);  // add the private library

 std::string line;
 int error;
 std::cout << PROMPT;

 while (std::getline(std::cin, line)) {
  error = luaL_loadstring(L, line.c_str());
  if (not error) {
   error = lua_pcall(L, 0, 0, 0);
  }
  if (error) {
   std::cerr << lua_tostring(L, -1) << std::endl;
   lua_pop(L, 1);  // pop error message from stack
  }
  std::cout << PROMPT;
 }

 std::cout << std::endl;

 lua_close(L);
 return 0;
}

Poi ci serve un Makefile per poter generare l'eseguibile (è un po' naive...):

shell: shell.o
    g++ -o shell shell.o -llua -lm -ldl -pthread

shell.o: shell.cpp
    g++ -std=c++11 -c shell.cpp

test: shell
    python tests.py

clean:
    rm shell *.o

Infine ci servono dei test case python che verifichino il tutto, salviamo il seguente file tests.py:

import subprocess
import unittest

class Shell:
    def __init__(self):
        self.shell = subprocess.Popen(
                ["./shell"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

    def __getattr__(self, item):
        return getattr(self.shell, item)


class Test(unittest.TestCase):
    def test_print(self):
        shell = Shell()
        shell.stdin.write("print(\"ciao\")")
        stdoutdata, stderrdata = shell.communicate()
        self.assertEqual(shell.returncode, 0)
        self.assertIn("ciao", stdoutdata)
        self.assertFalse(stderrdata)

    def test_print_a_variable_value(self):
        shell = Shell()
        shell.stdin.write(
                "a = 10\n"
                "print(a)\n")
        stdoutdata, stderrdata = shell.communicate()
        self.assertEqual(shell.returncode, 0)
        self.assertIn("10", stdoutdata)
        self.assertFalse(stderrdata)

    def test_call_a_non_existent_function(self):
        shell = Shell()
        shell.stdin.write("_sleep(10)\n")
        stdoutdata, stderrdata = shell.communicate()
        self.assertEqual(shell.returncode, 0)
        self.assertTrue(stderrdata)

    def test_call_a_private_function(self):
        shell = Shell()
        shell.stdin.write(
                "a = sleep(10)\n"
                "print(\"return value: \")\n"
                "print(a)\n")
        stdoutdata, stderrdata = shell.communicate()
        self.assertEqual(shell.returncode, 0)
        self.assertFalse(stderrdata)
        self.assertIn("return value: \n> nil", stdoutdata)


    def test_call_with_wrong_argument_type(self):
        shell = Shell()
        shell.stdin.write("sleep(\"wrongtype\")")
        stdoutdata, stderrdata = shell.communicate()
        self.assertNotEqual(shell.returncode, 0)
        self.assertIn("Assertion", stderrdata)


if __name__ == '__main__':
    unittest.main()

Eh, già, nel caso ve lo stiate chiedendo, vi confermo che sono un po' appassionato sia di python che di testcase. Anzi, se devo dire la verità il mio percorso di sviluppo precedente è stato più o meno l'opposto di quello che vi ho scritto prima: sono arrivato ad una shell.cpp super-minimale con il relativo super-minimale Makefile, a quel punto ho scritto via via i test in python ed ho via via esteso lo shell.cpp per aggiungere funzionalità. Quello che trovate sopra è il risultato finale dei miei esperimenti, il mio percorso però è stato molto più "test-driven". Ma anche il TDD non c'entra con questo post per cui proseguiamo:

Ora che abbiamo i 3 file precedenti basta compilare la shell e lanciare i test (vi ricordo che siamo sempre dentro la macchina virtuale vagrant, se siete usciti basta un "vagrant ssh" per rientrare)

    make
    python tests.py

Et, voilà!

Nulla da dire, lua si integra in c++ in modo semplice e pulito e la funzione lua_sleep, per quanto idiota, rimane indubbiamente una funzione esposta da C++ e lanciata da lua.

Quello che ci rimane da fare, prossimamente perché ora si svegliano i figli e non ho più voglia di stare al PC :-), è di fare una cosa simile con python e micropython.

In passato CPython (anche grazie a "boost" e , in particolare, "boost-python") eravamo riusciti ad integrarlo liscio come l'olio ed era stato possibile esporre a python non solo funzioni bensì anche intere classi
... con micropython? vedremo!

Se la macchina virtuale non vi serve più per il momento uscite e date il comando

$ vagrant halt

A quel punto, se volete proprio buttarla via

$ vagrant destroy

Python vs Lua (2)

Nel post precedente abbiamo parlato di micropython e ci siamo lasciati con un micropython compilato per PC.
Oggi scopriamo che micropython, pur essendo tarato per girare su hardware ridottissimo, ha a disposizione anche funzionalità che non ti aspetteresti.
Ne vediamo, ad esempio due:
  • un sistema di gestione pacchetti tipo pip
  • e il modulo standard per i thread
Useremo entrambi per fare delle prove con mqtt

Partiamo dai thread

Micropython, battery-included non ha il tradizionale modulo standard multithreading bensì, al momento, ha solo il modulo base _thread con le API di basso livello
(uhm, chissà se il modulo _thread esiste su tutte le piattaforme supportate da micropython, io sono su linux: c'è posix sotto, è comunque una situazione favorita, sulle altre piattaforme sarà da verificare)
Comunque, anche se abbiamo solo _thread possiamo già fare qualche bel giochino

Prima di tutto, per non dover lavorare con _thread, definiamoci un modulo threading "hand-made" che che esponga l'API standard

Ecco qui:

import _thread
get_ident = _thread.get_ident

class Thread:

    def __init__(self, group=None, target=None, name=None, args=(), kwargs=None):
        self.target = target
        self.args = args
        self.kwargs = {} if kwargs is None else kwargs

    def start(self):
        self.running = _thread.allocate_lock()
        self.running.acquire()
        _thread.start_new_thread(self.run, ())

    def join(self):
        assert self.running.acquire()

    def run(self):
        self.target(*self.args, **self.kwargs)
        self.running.release()

salvandolo su un file con nome threading.py potremo banalmente utilizzarlo come se avessimo a disposizione effettivamente threading
(L'implementazione è molto banale e naive, mi sono ispirato ai sorgenti standard riducendoli drasticamente)

A questo punto siamo in grado di costruire il nostro primo test con i thread
(Notare che il modulo utime potrebbe non esserci su tutte le piattaforme oppure alcune piattaforme potrebbero disporre di moduli specifici esempio per la esp8266)

import utime
import threading

def target(name, count):
    for idx in range(count):
        utime.sleep(0.5)
        print("I'm {} - {}".format(name, threading.get_ident()))

def main():
    print("begin...")
    t = threading.Thread(target=target, args=("THREAD", 10))
    t.start()
    target("MAIN", 3)
    t.join()
    print("end.")

if __name__ == '__main__':
    main()

Salviamo con nome first_thread_test.py e lanciamo con micropython first_thread_test.py

Et, voilà! Abbiamo due thread che scrivono in modo un po' confuso sullo standard output
E' ora di alcune osservazioni conclusive

micropython funziona bene. In meno di 400K di eseguibile abbiamo a disposizione la grandissima parte del linguaggio (se ci pensate abbiamo utilizzato funzioni, classi, import di moduli...) ed una nutrita schiera di moduli della libreria standard.
L'esempio con i thread è uno dei più "cattivi" perché utilizza primitive di sistema operativo abbastanza specifiche. Nel nostro caso eravamo su un sistema posix e abbiamo potuto raggiungere egregiamente l'obiettivo

So far so good!

Vi avevo promesso prove con mqtt... e con Lua!... yes! Stay tuned! ;-)

Per non lasciarci con troppe cose in sospeso eccovi una chicca: micropython, per quanto ridotto ai minimi termini, dispone di un sistema di gestione dei pacchetti compatibile con pip.
E su PyPI ci sono tanti moduli specifici per micropython

Ecco ad esempio come installare un modulo ed utilizzarlo:

./micropython -m upip install micropython-pystone

(Il modulo, di default, finisce in $HOME/.micropython/lib)

L'esempio completo è qui

Lua vs Python

Stiamo valutando le opzioni disponibili per inserire un linguaggio di "scripting" in un sistema scritto in C.
I moduli C possono trovarsi a girare su un PC con sistema operativo, su sistemi embedded o, al limite, perfino su microcontrollori.
La prima ipotesi che viene in mente, da pythonista, è quella di micropython.
https://micropython.org/

Dando un occhiata al progetto su github scopro che si tratta di un progetto interessante e in piena attività
https://github.com/micropython/micropython/graphs/code-frequency

E' vero che, come al solito, pochi contributor fanno la gran parte dei commit
https://github.com/micropython/micropython/graphs/contributors

Però anche il fatto che github, ad oggi, conti ben 269 contributor suona bene

Ok, Allora proviamolo!

Senza la pretesa di farlo girare su schedine dedicate, come primo passo, mi accontento di compilarlo per PC

L'opzione è perfettamente prevista (come tanti altri porting, perfino per Windows) e funziona. Ecco i, molto semplici passi

Io per prima cosa ho preferito creare un virtualenv con python 3.6.6, per non dipendere dal python2 di sistema. Si tratta di un passo opzionale. E comunque nel README dice che richiede python (at least 2.7 or 3.3)

Gli altri passi, per Ubuntu, sono riportati qui:
https://github.com/micropython/micropython/wiki/Getting-Started#debian-ubuntu-mint-and-variants

git clone https://github.com/micropython/micropython.git
cd ports/unix
make axtls
make

Et voilà, abbiamo micropython! Basta un bel ./micropython e potete iniziare a dare i primi comandi

Ok, per il momento ci fermiamo qui, stay tuned!
... ma il titolo non parlava anche di Lua? Yes, stay tuned ;-)

venerdì 27 luglio 2018

Python, il modo più semplice per suonare un wav

Per aiutare un amica cercavo un modo semplicissimo per eseguire un file wav da python
Dopo alcune prove il modo più essenziale è risultato "simpleaudio"

pip install simpleaudio

(... ovviamente prima avevate creato il virtualenv, giusto?!? )

Su ubuntu potreste aver bisogno anche delle librerie ALSA

sudo apt-get install libasound2-dev

Ok, ora potete suonare qualcosa
Procuratevi un piccolo file wav, per sempio da qui:
SampleRadar: 214 free 8-bit bonanza samples

Chiamatelo ad esempio "noise.wav" e mettetelo nella stessa cartella dello script seguente:

import simpleaudio as sa
wave_obj = sa.WaveObject.from_wave_file("noise.wav")
play_obj = wave_obj.play()
play_obj.wait_done()


Alzate il volume del PC, lanciate lo script... e via

giovedì 12 luglio 2018

Fede e potere politico

Briciole dalla mensa - 15° Domenica Tempo Ordinario (anno B) - 15 luglio 2018

"
Al profeta Amos viene vietato di compiere la sua missione a Betel perché «è il santuario del re e il tempio del regno». Il santuario e il tempio, luogo d'incontro dell'uomo con Dio, sono diventati strumenti al servizio «del re e del regno». È impressionante: la religione, il culto, la fede sono sottomessi al potere politico. Per questa ragione probabilmente il profeta viene mandato via: egli denunciava tale iniquità.
Anche noi, oggi, credo che avremmo bisogno di qualche voce autorevole che faccia risuonare la schiettezza della parola di Dio. Perché stiamo assistendolo a un'impudica strumentalizzazione e falsificazione dei segni della nostra fede: come il Vangelo, la croce, il rosario. Per crearsi un consenso, certi uomini politici ne fanno pubblica ostentazione per far credere di essere cristiani e per affermare che in nome della civiltà cristiana combattono contro l'inciviltà: rifiutano il diritto ad uno spazio dove poter vivere a chi viene da altre  regioni, a chi ha un’altra cultura, a chi appartiene ad altra religione. Si tratta della distorsione più completa della nostra fede. Perché Gesù (il Gesù del Vangelo, della croce e del rosario) ha, invece, affermato che ogni uomo è abitazione di Dio. Perciò chi non rispetta e non promuove l'uomo, soprattutto il povero, profana Dio stesso. Se non viene detta oggi una parola chiara di denuncia di tale falsità, molte persone continueranno ad andare a Messa, a dire le preghiere e contemporaneamente ad essere razziste, come niente fosse."

[Alberto Vianello - http://www.monasteromarango.it/la-vocazione-profetica-della-chiesa]