Pillole Java: Gestione della memoria

Architettura Hotspot JVM

La Java Virtual Machine è un software che fornisce un ambiente operativo virtuale per applicazioni Java.

L’architettua Hotspot è composta di tre elementi

  • il motore di esecuzione:
    • è composto di un compilatore a tempo immediato (just in time o JIT compiler) che traduce in bytecode il programma di testo (compilazione) al momento in cui deve essere utilizzato
    • e di un garbage collector (che è ciò su cui ci focalizziamo), un collettore di spazzatura che fa la raccolta differenziata degli oggetti usati e li rimuove con una specifica politica
  • Runtime data area, o area dati di esecuzione che è una memoria organizzata in cui vengono caricati i metodi, i threads, lo heap (che è la zona che ci interessa) e altre zone. In particolare lo heap (mucchio) è la zona riservata ai dati trattati dall’applicazione
  • il class loader che è un regista che carica in memoria le classi e i metodi quando richiesti

Lo heap

Lo heap è una zona di memoria RAM nella quale Hotspot gestisce i dati dell’applicazione. Viene implementata una vera e propria raccolta differenzaiata in base all’età di occupazione. L’età di occupazione è calcolata con un contatore per ogni oggetto che, ogni volta che viene inizializzato, incremento il contatore. Ogni volta che imposto la variabile a null, lo decremento di 1. Quando il contatore raggiuunge 0 è tempo di buttare via l’oggetto. Quindi programmaticamente è buona norma impostare a null una variabile se non la si vuole più usare, perché così riesco a raggiungere l’evento minor garbage collection che determina lo spostamento o l’eliminazione fisica dell’area di memoria che contiene la variabile.

I dati vengono diversificati in tre zone “temporali”:

  • Young Generation
  • Old Generation
  • Permanent Generation
Struttura dello Heap (Wikipedia)

La zona Young Generation è dove vengono allocati tutti gli oggetti che vengono utilizzati per la prima volta; è a sua volta suddivisa in Eden (oggetti appena creati) e Survivor. Il raccoglitore differenziato (minor garbage collector) sposta gli oggetti dall’Eden alla zone di Sopravvivenza e da qui alla Old Generation. Uno spostamento da Young a Old è considerato un evento di minor garbage collcection.

Gli oggetti un po’ più datati vivono nella Old generation; vengono spostati da qui dal racccoglitore differenziato maggiore ed eliminati, Questo evento è condierato una major garbage collection.

Nella zona Permament Generation vengono collezionati metodi e classi, che sono considerati metadati. Anche l’eliminazione da parte del GC degli oggetti di questa zona è considerata major.

Quindi gli oggetti vengono spostati gradualmente da una zona all’altra fino alla loro eliminazione completa.

Tipi di GC

I garbage collectors di Hotspot sono 4:

  • serial
  • parallel
  • CMS
  • G1

Questi si comportano sostanzialmente in modo diverso, ma dobbiamo pensare che ci sono processi per così dire “attivi” che portano avanti l’esecuzione del programma principale e processi per così dire “passivi” che invece si occupano di fare pulizia. A volte i secondi devono interrompere i primi e questa modalità è detta “Stop-the-world mode GC”.

Serial è un unico thread che si occupa di eliminare molti garbage uno alla volta.

Parallel si occupa di eventi minori (come lo spostamento da un contentore all’altro) e ogni garbage è spostato da un thread separato, quindi si gestiscono più segmenti alla volta.

CMS (Concurrent Mark-sweep) vengono marcati e cancellati più segmenti tramite thread paralleli per minimizzare l’arresto del programma principale (per minimizzare gli Stop-the-world).

G1 (Garbage First) è un’alternatva concorrente di CMS.

Compattazione

Il modo di gestire la cancellazione degli oggetti non più usati è allo stesso modo critica: si possono adottare diverse tecniche, la più ingenua è cancellare loggetto sic et simpliciter (tecnica Mark-Sweep), ma questo reimpirebbe la memoria di “buchi” e sarebbe diffficile riutilizzae lo spazio reso disponibile.

Mark-sweep

Quindi ci sono dei metodi più evoluti, come il Copy o il Mark-Compact che riallocano i dati da cancellare in modo da renderli contigui e creare delle zone libere più grandi possibili e continue (senza buchi).

Mark-Compact

Bibliografia

Pillole Mirthconnect/NextGen: un semplice canale di echo

Costruiamo un semplice web service con Mirth che acquisice la request e ne ripropone il contenuto nella response (il comportamento del comando Unix echo)

Configuriamo per ingresso e uscita il tipo di file XML (oppure raw):

Definiamo come Source il componente Web Service Listener:

Ho scelto una porta 7100 per poi poterla collegare ad internet con un Reverse Proxy Apache.

È importante impostare la Response: si può fare anche dopo aver scritto la destination (anzi, così ci ritroviamo la viariabile che contine il valore da ritornare già nella combo, in questo caso response).

Per i rimanenti parametri lasciamo default. In particolare il nome del servizio da chiamare sarà Mirth. Quindi l’url di invocazione sarà qualcosa del tipo

https://betaingegneria.it/services/Mirth

Definiamo quandi una destination di tipo Javascript Writer e scriviamo questo programma:

Qui carichiamo una variabile javascript locale “text” con il contenuto grezzo del messaggio in entrata; lo scriviamo quindi nel logger (per controllare il traffico) e resitutiamo lo stesso valore alla mappa del canale nella variabile response.

Dopo aver scritto questo frammento potremo ritrovare la variabile response nella combo box di configurazione della Source – come visto prima.

A questo punto deployamo il canale e testiamo con SoapUI:

Ecco realizzato un webservice di echo.

Pillole Apache: Virtualhost su localhost

Problema: definisco un virtualhost su un nuovo file di configurazione sotto la directory:

/etc/apache2/sites-available/newsite.conf

che contiene queste informazioni

<VirtualHost *:80>
        ProxyPreserveHost On
        ProxyPass /beta http://betaingegneria.it
        ProxyPassReverse /beta http://betaingegneria.it
        ServerName localhost
        ErrorLog ${APACHE_LOG_DIR}/error_newsite.log
        CustomLog ${APACHE_LOG_DIR}/access_newsite.log combined
</VirtualHost>

Lo abilito:

# a2ensite newsite
Enabling site newsite.
To activate the new configuration, you need to run:
systemctl reload apache2

Questo comando crea un link simbolico nella directory

/etc/apache2/sites-enabled/newsite.conf

Faccio ricaricare la configurazione ad Apache:

# systemctl reload apache2

Il risultato è deludente: puntando a http://localhost/beta ho sempre un errore 404 anziché vedere sul browser il sito betaingegneria.it

Però, se invece di creare un nuovo file scrivo la configurazione del proxy dentro al file 000-default.conf, rileggendo (reload) la configurazione nel browser, all’indirizzo http://localhost/beta vedo il mio sito. Questo era l’effetto che volevo ma creando un nuovo file di configurazione separato.

Per non farvela lunga (io invece un po’ di tempo ce l’ho perso) il problema è il “conflitto” tra il file 000.default.conf e il nuovo newsite.conf: infatti in questa directory ci dovrebbe esser un solo file per ogni nome di dominio; il file che avevo creato usava come ServerName il nome localhost, esattamente quello di default a cui si riferisce Apache quando ha esaurito i nomi di dominio definiti, gestito in 000.default.conf.

Quindi o disabilito 000-default.conf e scrivo la nuova configurazione nel nuovo file, oppure viceversa.

Disabilitare 000-default.conf a lungo andare non è la scelta più oculata perché in caso dismettessi il nuovo sito, se non mi ricordo questa cosa, lascio Apache senza un sito di default per localhost.

#bugfix: 404-not found ma la rotta esiste

Ho incontrato questo problema: ho definito una rotta nel file routes/web.php:

Route::get('/activities/start', 'ActivityController@start');

e ho definito il metodo start() nell’ActivityController

    /**
     * start - start the day first activity
     * @return bool
     */
    public function start()
    {
        //
        $activity = new Activity;
        $activity->order_id = Config::get('constants.START');
        $activity->description = Config::get('constants.START_TEXT');
        $activity->notes = '';

        $activity->save();

        return true;
    }

Il risultato è deludente e alquanto irritante, visto che ho seguito questa prassi – che ritengo corretta – fin dall’inizio:

La soluzione è semplice: il metodo resource() nella definzione delle rotte va collocato dopo le dichiarazioni puntuali delle singole rotte:

...
Route::get('/activities/start', 'ActivityController@start'); // 'ActivityController@start'
Route::get('/activities/report', 'ActivityController@reportForm'); //'ActivityController@reportForm'
// at the end...
Route::resource('/activities', 'ActivityController');

In sostanza devo riorganizzare i metodi perché c’è qualche conflitto con le rotte RESTful generate dal resource()e quelle da me definite “a mano”.

So far so good.

#bugfix: problemi tra Laravel e curl

cURL è una nota libreria per trattare gli url e gestire chiamate http.

Utilizzo una libreria per Laravel che mi permette di fare delle richieste HTTP da server a sever che si chiama ixudra/curl.

La libreria funziona a meraviglia, se non che, per un problema di configurazione del mio SO, ogni volta che aggiorno la libreria, cURL smette di funzionare.

Il sintomo

Il sintomo del problema che Laravel esibisce è questo

Symfony\Component\Debug\Exception\FatalThrowableError
Call to undefined function Ixudra\Curl\curl_init()

In realtà il problema risiede ancora più in basso poiché nemmeno il client PHP è immune dagli effetti di questa errata configurazione; invocando semplicemente il client da riga di comando ho:

$ php -v
PHP Warning:  PHP Startup: Unable to load dynamic library 'curl.so' (tried: /usr/lib/php/20190902/curl.so (/usr/lib/php/20190902/curl.so: symbol curl_mime_addpart version CURL_OPENSSL_4 not defined in file libcurl.so.4 with link time reference), /usr/lib/php/20190902/curl.so.so (/usr/lib/php/20190902/curl.so.so: cannot open shared object file: No such file or directory)) in Unknown on line 0
PHP 7.4.6 (cli) (built: May 14 2020 10:02:44) ( NTS )
...

Il problema

La radice del problema si trova nella directory /usr/local/lib

$ cd /usr/local/lib/
$ ll
totale 12924
drwxr-xr-x  8 root root     4096 mag 27 08:48 ./
drwxr-xr-x 13 root root     4096 ott 17  2018 ../
-rw-r--r--  1 root root  1006444 mag 23  2017 libcurl.a
-rwxr-xr-x  1 root root     1042 mag 23  2017 libcurl.la*
lrwxrwxrwx  1 root root       16 mag 21 19:12 libcurl.so.4 -> libcurl.so.4.4.0*
-rwxr-xr-x  1 root root   542272 mag 23  2017 libcurl.so.4.4.0*
...

La “sconfigurazione” è dovuta alla presenza del link simbolico libcurl.so.4, la soluzione proposta è quella di interromperlo perché prenda il sopravvento la versione di sistema. Il fatto è che ogni volta che viene eseguito un aggiornamento di cURL, viene ripristinato anche questo link simbolico reintroducendo l’errore.

La soluzione

Nell’attesa di escogitare una soluzione più definitiva ho seguito il consiglio che si dava nel forum:

$ sudo mv libcurl.so.4.4.0 libcurl.so.4.4.0.OLD

Così facendo non ho più il problema al client:

$ php -v
PHP 7.4.6 (cli) (built: May 14 2020 10:02:44) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
    with Zend OPcache v7.4.6, Copyright (c), by Zend Technologies
    with Xdebug v2.9.5, Copyright (c) 2002-2020, by Derick Rethans

Come si vede, l’errore è scomparso. Affinche anche Apache possa beneficiare della soluzione del problema occorre riavviarlo:

$ sudo service apache2 restart

A questo punto anche l’applicazione Laravel torna a funzionare.

Laravel avanzato: l’utilizzo di cron

Ho bisogno di eseguire un determinato metodo di un controller regolarmente una volta al giorno.

Avevo fatto un programma bash e l’avevo inserito nella crontab, ci dovevo fare un aggiornamento quotidiano del mio DB da una sorgente dati.

Questo database mi serve per una applicazione live, per cui devo rendermi autonomo da operazioni manuali.

Avrei potuto replicare la modalità anche in remoto, ma ho preferito farlo attraverso Laravel, tirando dentro all’applicazione tutta la logica eseguita nello script di shell.

Un primo livello è stato quello di scrivere dei programmi PHP che leggevano la sorgente e aggiornavano il db attraverso un nuovo metodo dei miei controller dedicati a quelle entità relazionali.

Quindi potevo lanciare manualmente invocando un URL la funzionalità e tutto filava meravigliosamente.

L’ultimo passo è rendere autonoma l’applicazione Laravel perché ad una certa ora del giorno esegua il task. Qui arriva l’utilissimo Scheduler di Laravel.

L’idea di fondo è quella di invocare in modalità cron la rotta URI che scatena il metodo del controller. Questa funzionalità di Laravel si basa comunque sul clock del sistema operativo: un cron batch che viene lanciato ogni minuto e che viene preso come riferimento da Laravel per temporizzazioni diverse. Quindi è comunque obbligatorio agire su due fronti:

  • sullo scheduler di Laravel e
  • sul crontab di sistema.

Vediamo come si fa.

Estendere Artisan creando un comando che chiama una rotta

Premetto che sto utilizzando questa versione di Laravel:

$ php artisan --version
Laravel Framework 6.18.8

Possiamo estendere le funzionalità di artisan che usiamo correntemente come ad esempio:

$ php artisan make:controller InvoiceController

per fare in modo che si possa invocare un nostro comando artisan; quindi la prima cosa da fare è creare un nuovo comando artisan che chiamo route:call. Ho bisogno inoltre che a questo comando venga affiancata una serie di parametri opzionali.

Esistono già dei comandi artisan per gestire le rotte; se provo a lanciare il mio comando ancora da definire ho questa risposta:

$ php artisan route:call
  Command "route:call" is not defined.
  Did you mean one of these?
      route:cache
      route:clear
      route:list

Per creare un nuovo comando artisan si fa così:

$ php artisan make:command RouteCall

Laravel creerà un nuovo comando che consiste in una classe che si chiamerà come il primo parametro (RouteCall) e la sintassi (signature) sarà definita all’interno del file contenente la nuova classe, che viene creata dentro la cartella app/Console/Commands ed estende la classe Command:

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\Request;

class RouteCall extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'route:call {--uri=}';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Call route from CLI';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        $request = Request::create($this->option('uri'), 'GET');
        $this->info(app()['Illuminate\Contracts\Http\Kernel']->handle($request));
    }
    
    protected function getOptions()
    {
        return [
            ['uri', null, InputOption::VALUE_REQUIRED, 'The path of the route to be called', null],
        ];
    }

}

In particolare osserviamo la sintassi del comando contenuta nella proprietà $signature che è quella che abbiamo definito nel creare la class nel comando sopra a cui però ho aggiunto il parametro {--uri} che è l’URI del metodo da chiamare. All’interno della classe interessata aggiungerò anche una gestione del parametro in modo tale da renderlo opzionale: in pratica il parametro è una data in formato YYYY-MM-DD: se non la passo nell’URI, sottointendo data odierna.

Il metodo chiave è handle() che trasforma un comando Linux in una request HTTP. Come si vedrà adesso il controllo passa al modulo Kernel.php.

Schedulazione del comando

Il secondo passaggio è quello di definire la temporizzazione del comando:

<?php
// file app/Console/Kernel.php

namespace App\Console;

use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;

class Kernel extends ConsoleKernel
{
    /**
     * The Artisan commands provided by your application.
     *
     * @var array
     */
    protected $commands = [
        'App\Console\Commands\CallRoute',
    ];

    /**
     * Define the application's command schedule.
     *
     * @param  \Illuminate\Console\Scheduling\Schedule  $schedule
     * @return void
     */
    protected function schedule(Schedule $schedule)
    {
        $schedule->command('call:route')->dailyAt('19:00');
    }

    /**
     * Register the commands for the application.
     *
     * @return void
     */
    protected function commands()
    {
        $this->load(__DIR__.'/Commands');

        require base_path('routes/console.php');
    }
}

Il nodo centrale è il metodo schedule() che contiene uno o più comandi, ognuno con la temporizzazione desiderata, in questo caso voglio che il Kernel dello scheduler avvii il comando route:call ogni giorno alle 19:00.

Avvio dello scheduler

La terza operazione è quella di creare una nuova voce nella crontab che avvia lo scheduler:

$ crontab -e
...
# For more information see the manual pages of crontab(5) and cron(8)
# 
# m h  dom mon dow   command
* * * * * cd path/to/project/ && php artisan schedule:run >> /dev/null 2>&1

questo job è chiamato con la risoluzione massimo di 1 volta al minuto, poi la logica di Laravel farà in modo che i singolo job vengano eseguiti su temporizzazioni diverse – come sono state definite nel metodo Kernel->schedule()– ma sempre con il clock di riferimento dettato dalla crontab.

Quindi riassumendo, a partire dalla causa primaria, fino all’effetto ultimo:

  • crontab invoca lo scheduler di Laravel ogni minuto;
  • lo scheduler controlla se c’è quacosa da eseguire in base alle diretive everyMinute(), dailyAt(), daily() eccetera definite per i comandi invocati nel metodo schedule() del Kernel;
  • il Kernel avvia tutti i comandi registrati nel metodo schedule();
  • ognuno di questi metodi è invocato con un comando artisan(es. route:call) ma in generale potrebbe anche essere una chamata a Eloquent per scrivere qualcosa nel database;
  • il singolo comando artisan è definito in una classe specifica (es. RouteCall) contenuta nella directory app/Console/Commands/
  • il metodo che esegue quanto si desidera (nel mio caso la trasformazione di un comando shell come php artisan route:call in una request HTTP) è il metodo handle()

È tutto.

Creare ed eliminare una singola tabella database con Laravel

In fase di progetto, o ancora più spesso in fase di prototipazione, ci si trova a dover ridisegnare una tabella (ad esempio per dover modificare il tipo dei campi o la dimensione, o aggiungere / togliere campi).

E spesso non è nemmeno richiesto di conservare una storia di queste modifiche perché semplicemente si sta modellando una versione finale non ancora soddisfacente.

Quindi invece di procedere con modifiche puntuali sul campo, ad esempio con un equivalente di

ALTER TABLE RENAME COLUMN myField AS Field1;

è più immediato fare un drop e ricreare la tabella, eventualmente salvando i dati o preparando uno script di alimentazione da lanciare all’occorrenza.

Sebbene si possano effettuare entrambe le operazioni (più soft con l’alter table o più drastica con il drop/create) vediamo come si fa a “droppare” e ricreare la tabella con i comandi di migrazione di Laravel.

Lo script di creazione tabella contiene già per default i due metodi per costruire e distruggere la tabella, rispettivamente up() e donw():

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateDatiRegioniTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('dati_regioni', function (Blueprint $table) {
            $table->bigIncrements('id');
            ...
         });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('dati_regioni');
    }
}

Il metodo up() viene invocato quando si esegue la migrazione:

$ php artisan migrate

ed il file ad essere eseguito è l’ultimo (il più recente) che si trova nella directory database/migrations. Per droppare la tabella si esegue:

$ php artisan migrate:rollback

e successivamente, per ricrearla:

$ php artisan migrate

Per eseguire operazioni su una determinata tabella, individuare il file contente le migrazioni per quella tabella e modificarne il nome in modo tale che la stringa sia in senso lessicografico la più grande di tutte

2020_04_17_154056_create_regioni_table.php 
2020_04_17_155500_create_dati_regioni_table.php

Impiego della tecnica push-to-deploy con Git

Questo articolo molto vecchio l’ho trovato originariamente nel blog di Kris Jordan ma mi è tornato utile adesso. Lo accomodo per il caso di mio interesse.

Ho un progetto in un host locale, un ambiente di produzione e un ambiente di staging al quale vorrei applicare in tempo reale le modifiche appena consegnate (commit) alla versione di sviluppo.

Questo aticolo, per arrivare a questo obiettivo, propone di creare un repository “bare” in remoto che serve solo per fare da tramite tra cartella di sviluppo in localhost e la cartella di deploy/stage in remoto, come illustrato nella figura 1

Fig. 1 Illustrazione della tecnica push – post – receive

Questa metodologia è stata inizalmente sviluppata da Heroku.

Preparazione dei repository

In questa versione personalizzata, a differenza dell’articolo di riferimento (nel quale si mette tutto in localhost):

  • lasceremo il repository git “myProject” in localhost;
  • creeremo il repository git “push” in mydomain.com
  • la directory “stage” ovviamente in mydomain.com

come illustrato in Fig. 1.

me@localhost:$ cd IdeaProjects/myProject

Ora dobbiamo spostarci in remoto e qui è indispensabile avere l’accesso shell al server.

Qui inizializziamo un repository detto “bare” (nudo) che avrà solo la funzione di passamano tra il Git locale e la directory di deploy in remoto

me@localhost:$ ssh myUser@mydomain.com
Password:
myUser@remotehost:$ git init --bare myProjectPush.git

Questo repository myProjectPush.git non conterrà il codice sorgente, bensì farà da passamano tra il client Git a bordo del nostro server di sviluppo e la cartella di staging “stage” nel server di staging

Ora nel Git locale dobbiamo creare un riferimento al repository remoto di push che chiameremo “push” (magari non è la scelta più felice… ma basta fare attenzione):

me@localhost:$  git remote add push myUser@mydomain.com:myProjectPush.git

Il nuovo repository remoto lo troviamo scritto nel file .git/config:

...
[remote "push"]
        url = myUser@mydomain.com:public_html/myProjectPush.git
        fetch = +refs/heads/*:refs/remotes/push/*

A questo dobbiamo istruire il Git remoto perché copi i file nella directory stage

Impostazione Push-to-Stage

Dopo aver creato il repository Git remoto dobbiamo scrivere un programma/script che istruisce git su come trattare i file che arrivano dal Git locale. Nella directory hooks della cartella myProjectPush.git ci sono gli script che Git lancia al verificarsi di un particolare evento. Ce ne sono già parecchi di esempio scritti in linguaggio bash, ma per questo scopo si può usare un qualsiasi linguaggio, nell’esempio che ho trovato è scritto in Ruby. Ma prima di tutto creiamo il file:

myUser@remotehost:$ cd myProjectPush.git/.git/hooks
myUser@remotehost:$ touch post-receive
myUser@remotehost:$ chmod +x post-receive

Il “gancio” che ci serve è quello che ci collega all’evento post-receive che è l’evento che accade quando il repository remoto riceve e accetta un push da parte di un repository di sviluppo. Quindi è necessario che il sistema operativo veda il file come uno script eseguibile.

Poi apriamo con l’editor che vogliamo il file post-receive e copiamo incolliamo questo programma Ruby (accertiamoci che l’interprete ruby sia installato con un comando “which ruby”)

#!/usr/bin/env ruby
# post-receive

# 1. Read STDIN (Format: "from_commit to_commit branch_name")
from, to, branch = ARGF.read.split " "

# 2. Only deploy if master branch was pushed
if (branch =~ /master$/) == nil
    puts "Received branch #{branch}, not deploying."
    exit
end

# 3. Copy files to deploy directory
deploy_to_dir = File.expand_path('../stage')
`GIT_WORK_TREE="#{deploy_to_dir}" git checkout -f master`
puts "DEPLOY: master(#{to}) copied to '#{deploy_to_dir}'"

# 4.TODO: Deployment Tasks
# i.e.: Run Puppet Apply, Restart Daemons, etc

Vediamo cosa fa questo script:

  1. Quando viene attivato lo script post-receive, Git gli passa in STDIN tre parametri: ID commit HEAD precedente, ID commit HEAD presente e il nome del branch da trasferire. Assegnamo questi tre valori alle variabili from, to e branch.
  2. Facciamo in modo che vengano pushati solo commit sul master branch (quello principale); questo sarebbe ragionevole per un push finale verso un server di produzione, ma io adotterò questa tecnica solo per lo staging, quindi rilasso questa parte (posso fare anche push di branch di bugfix per esempio).
  3. La prossima cosa da fare è fare un checkout dal branch attuale alla cartelle “stage”, cioè una copia dei file fisici nella cartella stage.
  4. Il lavoro è finito; sarebbe possibile completare lo script con comandi che dovessero rendersi necessari, come un restart del web server (se ne abbiamo la facoltà) o cancellare file di cache. Ma intanto mi fermo qui.

Salviamo il file e vediamo cosa succede.

Test con Pushing

Possiamo testare facendo un commit e un push verso il remote “push”:

me@localhost:$ git commit -m 'Test push-to-stage'
me@localhost:$ git push push master
muser@mydomain.com's password: 
Counting objects: 510, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (477/477), done.
Writing objects: 100% (510/510), 18.16 MiB | 148.00 KiB/s, done.
Total 510 (delta 260), reused 0 (delta 0)
remote: Already on 'master'
remote: DEPLOY: master(915c38ce6cd49905ccff2abf4d9fea9a6ad61a3d) copied to '/home/myUser/domains/mydomain.com/public_html/stage'
To mydomain.com:public_html/myProjectPush.git
 * [new branch]      master -> master

Nell’output, le righe che iniziano con “remote:” sono quelle generate del nostro script! In particolare vediamo che è stata fatta la copia nella cartella stage.

That’s all folks!!

Monitoraggio COVID-19

Ho realizzato questo semplice cruscotto che riporta sia in forma tabellare che grafica (per solo alcuni indicatori) i dati sull’epidemia di COVID-19.

La situazione viene aggiornata verso le 18 di ogni giorno con i nuovi dati in arrivo dal Dipartimento della Protezione Civile.

Note

Si noti il grafico della derivata seconda: questo da’ una stima dell’inversione di tendenza: quando la derivata seconda è negativa, la curva tende a trivolgere la concavità verso il basso (o la “pancia” verso l’alto), cioè il contagio rallenta (pur continuando ad aumentare).

L’inversione di tendenza come si vede si è verificata più volte in modo anche più pronunciato di questi giorni però ha avuto carattere occasionale. In questi giorni invece la derivata secondna si mantiene negativa per il terzo giorno, anche se di poco, ad indivduare forse una tendenza più stabile.

Link all’applicazione.

Pillole MySQL – come l’utility apparmor può darci problemi

Situazione: ho due schemi database. Attenzione: nello stesso server.

Nel primo importo i dati in una tabella da un file csv utilizzando l’istruzione LOAD DATA senza problemi.

Nel secondo voglio fare la stessa cosa, sullo stesso server, ma ottengo questo errore:

Error Code: 1290. The MySQL server is running with the --secure-file-priv option so it cannot execute this statement

Cioò che dice la documentazione di MySQL e ciò che trovo in tutti i forum è di modificare il file di configurazione

/etc/mysql/my.cnf

per aggiungere una riga che specifichi l’opzione secure-file-priv, per dichiarare qual è la directory preposta ai file da caricare, ma ci sono due problemi

  • l’opzione è già specificata e anche se metto i file in quella cartella, l’errore non scomapre; anche se cambio la cartella e riavvio il server (e posiziono i file csv in quest’ultima cartella), l’errore non scompare
  • il server è lo stesso

Il problema è l’applicazione apparmor di cui ho già parlato in un altro articolo. In sostanza la soluzione era far rileggere ad apparmor il suo file di configurazione (operazione che di tanto intanto si rende necesssaria per cui ho raccolto i comandi uno script Bash):

#!/bin/bash
apparmor_parser -r /etc/apparmor.d/usr.sbin.mysqld
service mysql stop
service mysql start

E tutto va a posto. Non mi spiego ancora, però, come fosse possibile eseguire il LOAD DATA per uno schema e non per un’altro.

Stay tuned.