Web, Ops & Data - Février 2021


24/02/2021 java repository artefact timescale postgres kapacitor grafana nomad hashicorp podman docker-compose registry docker golang vscode warp10 dataviz transformation vector linter

Container et orchrestration

  • Running Nomad for home server : pour avoir mené une expérience très similaire sur le mois de janvier, je me retrouve complètement dans ce retour d’expérience sur nomad (vs kubernetes dans une certaine mesure). Le trio nomad/consul/vault permet de faire des choses assez proches de ce que l’on peut faire avec kubernetes et parfois même de façon plus simple. Et ce, avec moins de couches intermédiaires (CSI, CNI, etc) mais aussi quelques fonctionnalités en moins. Un compromis assez réussi je trouve entre un docker nu et/ou avec docker-compose et un kubernetes.
  • Podman 3.0 has been released! : support de docker-compose, support des noms courts d’image, amélioration sur le réseau, apport de la dernière version de buildah, correction d’une CVE, etc.
  • Donating Docker Distribution to the CNCF : Docker Inc donne sa registry à la fondation CNCF pour fédérer les initiatives autour d’un même standard et élargir le champ des contributeurs/mainteneurs.
  • Panorama des outils de sécurité autour des conteneurs : comparaison des outils de bonnes pratiques et d’analyses de vulnérabilités des containers docker pour améliorer la sécurité de vos conteneurs.

Code

Monitoring & observabilité

Time Series

Si vous êtes en manque de news, vous pouvez aller consulter (et vous abonner) aux brèves du BigData Hebdo

Ma comptabilité, une série temporelle comme les autres - partie 3


19/02/2021 warp10 timeseries comptabilité remote execution rexec lmap template

A l’issue du précédent billet, depuis le WarpStudio et en stockant les données dans la sandbox, nous avons manipuler les données pour :

  • refaire les précisions de juin à décembre 2020 à partir des données de 01/2017 à 05/2017
  • comparer ses prévisions avec les résultats réels
  • faire les prévisions pour 2021.

Cependant, cela n’est pas parfait :

  • La durée de vie des données dans la sandbox est limitée à 2 semaines
  • Il faut copier/coller ses requêtes dans le studio pour obtenir les graphiques
  • Mais cela permet d’évaluer les extensions commerciales déployées sur la sandbox mais que vous n’avez pas forcément sur votre instance Warp 10 (au moins pour le moment)

Nous allons voir aujourd’hui comment rappatrier les données générées dans sa propre instance Warp 10.

Génération des prévisions

Pour commencer du bon pied et être sur que tout le monde est au même niveau, nous allons regénérer les prévisions et chaque prévision sera alors stockée dans une sériée dédiée. Cela nous permet d’avoir notre jeu de données de départ. On doit pouvoir sauver directement nos données dans une base distante, mais pour simplifier le tutoriel, nous allons le faire en deux étapes.

'<read token>' 'readToken' STORE
'<write token>' 'writeToken' STORE

// Récupération des séries 2017 > 2020
[ $readToken 'expense' { 'company' '=cerenit' } '2016-12-01T00:00:00Z' '2021-01-01T00:00:00Z' ] FETCH
0 GET
'exp' STORE

[ $readToken 'revenue' { 'company' '=cerenit' } '2016-12-01T00:00:00Z' '2021-01-01T00:00:00Z' ] FETCH
0 GET
'revenue' STORE

[ $readToken 'result' { 'company' '=cerenit' } '2016-12-01T00:00:00Z' '2021-01-01T00:00:00Z' ] FETCH
0 GET
'result' STORE

// Prévision sur les 12 prochains mois avec AUTO
// On n'a pas besoin d'afficher les données
// Donc plus besoin de les stocker sous forme de variable avec utilisation de la fonction STORE
// Par contre, on veut perstister les données en base
// ce qui se fait avec UPDATE et l'utilsation d'un token en écriture
[ $result mapper.todouble 0 0 0 ] MAP
AUTO 12 FORECAST.ADDVALUES
"auto_result" RENAME
$writeToken UPDATE

[ $revenue mapper.todouble 0 0 0 ] MAP
AUTO 12 FORECAST.ADDVALUES
"auto_revenue" RENAME
$writeToken UPDATE

[ $exp mapper.todouble 0 0 0 ] MAP
AUTO 12 FORECAST.ADDVALUES
"auto_expense" RENAME
$writeToken UPDATE

# Prévisions avec SAUTO
[ $result mapper.todouble 0 0 0 ] MAP
12 SAUTO 12 FORECAST.ADDVALUES
"sauto_result" RENAME
$writeToken UPDATE

[ $revenue mapper.todouble 0 0 0 ] MAP
12 SAUTO 12 FORECAST.ADDVALUES
"sauto_revenue" RENAME
$writeToken UPDATE

[ $exp mapper.todouble 0 0 0 ] MAP
12 SAUTO 12 FORECAST.ADDVALUES
"sauto_expense" RENAME
$writeToken UPDATE

Si nous vouulons vérifier que vos données sont bien en base, il faut utiliser FETCH :

// Ex de FETCH avec la série auto_revenue
'<read token>' 'readToken' STORE
[ $readToken 'auto_revenue' { 'company' '=cerenit' } '2016-12-01T00:00:00Z' '2022-01-01T00:00:00Z' ] FETCH
0 GET

Attaquons maintenant la grande traversée des données vers mon instance Warp 10.

Activation de l’exécution à distance

Alors on pourrait simplement migrer les données à coup de “curl in/curl out” mais l’idée ici est plus d’illustrer les interactions possibles entre des instances Warp 10.

Pour exécuter du warpscrip sur une instance distante, il faut utiliser la fonction REXEC

Cette exécution distante est désactivée par défaut, il faut donc activer cette extension:

Dans /path/to/warp10/etc/conf.d/70--extensions.conf, nous avons :

// REXEC
#warpscript.extension.rexec = io.warp10.script.ext.rexec.RexecWarpScriptExtension
#warpscript.rexec.endpoint.patterns = .*
// REXEC connect timeout in ms (default = 0, no timeout)
#warpscript.rexec.timeout.connect = 3600000
// REXEC read timeout in ms (default = 0, no timeout)
#warpscript.rexec.timeout.read = 3600000

Décommentons ces lignes et relançons warp 10.

Pour valider que REXEC est bien activé, depuis le studio, nous pouvons faire un test simple:

'2 2 +' 'https://warp10.url:port/api/v0/exec' REXEC

La réponse est 4.

Attention, il faut choisir votre instance comme endpoint dans la liste déroulante du studio. Si vous êtes sur le endpoint de la sandbox, vous auez le message d’erreur suivant :

https://sandbox.senx.io/api/v0/exec
line #2: Exception at ''2%202%20+' 'https://warp10.url:port/api/v0/exec' =>REXEC<=' in section [TOP] (REXEC encountered a forbidden URL 'http://warp10.url:port/api/v0/exec')

En effet, depuis la Sandbox, il n’est pas possible d’accéder à n’importe quelle machine par mesure de sécurité.

Requêtage simple à distance

Dans le studio, à partir de maintenant, il doit être configuré pour utliiser votre instance Warp 10 comme endpoint.

Le test simple étant fonctionnel, passons à un test un peu plus compliqué, à savoir recupérer nos données hébergées depuis la sandbox en lecture pour le moment.

// tokens pour la sandbox
'<sandboxReadToken>' 'sandboxReadToken' STORE
'<sandboxWriteToken>' 'sandoxWriteToken' STORE

// Url de la sandbox
'https://sandbox.senx.io/api/v0/exec' 'url' STORE

// On introduit ici la notion de template - comme on va vouloir récupérer plusieurs séries avec les mêmes paramètres
// Autant automatiser un peu et s'appuyer sur une boucle ! :-)
// On crée donc un TEMPLATE pour la fonction FETCH qui va récupérer un token en écriture
// et un nom de classe permettant de récupérer nos GTS.
// Rappel le <' ... '> permet de faire des strings en multi-lignes
// On stocke le template sous la forme d'une variable fetchTpl.
<'
  {
    'token' '{{ remoteReadToken }}'
    'class' '{{ remoteClass }}'
    'labels' {}
    'start' '2016-12-01T00:00:00Z'
    'end' '2022-01-31T00:00:00Z'
  } FETCH
'>
'fetchTpl' STORE

// Avec la fonction TEMPLATE, on remplace les clés par leurs valeurs en fournissant le template
// et un dictionnaire à la fonction.
$fetchTpl
{ 'remoteReadToken' $sandboxReadToken 'remoteClass' 'revenue' } TEMPLATE

// Execution de la requête distante avec REXECZ
// La différence avec REXEC est qu'une compression est appliquée sur la réponse à la requête
$url REXECZ
// La liste de GTS issue de FETCH ne contient qu'une liste, on prend donc la première
0 GET
// Stockage sous la forme d'une variable
'revenueGTS' STORE
// Affichage de la série
$revenueGTS

Si vous allez dans l’onglet dataviz, vous pouvez constater que vos données issues de la sandbox mais qui ont transité via votre instance sont bien disponibles.

Transfert des données - 1 série

Si nous commençons par une seule série :

// tokens de l'instance
'<instanceReadToken>' 'instanceReadToken' STORE
'<instanceWriteToken>' 'instanceWriteToken' STORE

// tokens pour la sandbox
'<sandboxReadToken>' 'sandboxReadToken' STORE
'<sandboxWriteToken>' 'sandoxWriteToken' STORE

// Url de la sandbox
'https://sandbox.senx.io/api/v0/exec' 'url' STORE

// Template de code warpscript
<'
  {
    'token' '{{ remoteReadToken }}'
    'class' '{{ remoteClass }}'
    'labels' {}
    'start' '2016-12-01T00:00:00Z'
    'end' '2022-01-31T00:00:00Z'
  } FETCH
'>
'fetchTpl' STORE

// Substitution des variables
$fetchTpl
{ 'remoteReadToken' $sandboxReadToken 'remoteClass' 'revenue' } TEMPLATE
// Exécution de la requête
$url REXECZ
// La liste de GTS issue de FETCH ne contient qu'une liste, on prend donc la première
0 GET
// Il faut renommer "localement" la série avant de pouvoir la stocker dans l'instance
// Peut éviter de mauvaises manipulations que l'on pourrait regretter :-)
"revenue" RENAME
// Persistance des données
$instanceWriteToken UPDATE

Il y a quelques occurences de “revenue” en dur dans le code, il va falloir améliorer cela.

Transfert des données - plusieurs séries

Et maintenant, traitons nos 9 series d’un coup

// tokens de l'instance
'<instanceReadToken>' 'instanceReadToken' STORE
'<instanceWriteToken>' 'instanceWriteToken' STORE

// tokens pour la sandbox
'<sandboxReadToken>' 'sandboxReadToken' STORE
'<sandboxWriteToken>' 'sandoxWriteToken' STORE

// Url de la sandbox
'https://sandbox.senx.io/api/v0/exec' 'url' STORE

// Template de code warpscript
<'
  {
    'token' '{{ remoteReadToken }}'
    'class' '{{ remoteClass }}'
    'labels' {}
    'start' '2016-12-01T00:00:00Z'
    'end' '2022-01-31T00:00:00Z'
  } FETCH
'>
'fetchTpl' STORE

// Création d'une liste avec nos 9 séries
// C'est cette liste que nous allons passer ensuite dans une MACRO.
// Cette MACRO va être exécutée sur chaque élément de la liste via l'utilisation de la fonction LMAP
// https://www.warp10.io/doc/LMAP
[ 'revenue' 'exp' 'result' 'auto_revenue'  'auto_result'  'auto_expense' 'sauto_revenue'  'sauto_result'  'sauto_expense' ]
// Début de la MACRO
<%
  // On récupère l'item de la liste que l'on stocke sous la forme d'une variable
  'remoteClass' STORE

  // Substitution des valeurs de template
  $fetchTpl
  { 'remoteReadToken' $sandboxReadToken 'remoteClass' $remoteClass } TEMPLATE

  // Exécution distante de la requête
  $url REXECZ
  // On récupère ici une liste de GTS - plutôt que d'en extraire la GTS comme précédemment
  // on va garder une liste de GTS à 1 élément, mais ce qui permet à nouveau d'utiliser la fonction LMAP
  // Sur chaque entrée de la liste, une seconde macro est appliquée
  // Le contenu de notre macro consiste à utliser la fonction RENAME
  // '+' RENAME, cela revient à renommer la GTS en prenant le même nom que celui qui est fourni
  // '+x' RENAME aurait ajouté un x au nom de la série
  // Il reste l'index de la liste à traiter - soit on le supprime avec DROP
  //<% DROP '+' RENAME %> LMAP
  // Soit on passe F comme 3ème argument à LMAP - cela permet d'ignorer cet index
  // <% '+' RENAME %> F LMAP
  // Prenons la seconde forme :
  <% '+' RENAME %> F LMAP
  // Toutes nos séries ont été correctement renommées !
  // On persiste la GTS dans la base locale
  $instanceWriteToken UPDATE
%>
// Fin de la MACRO
// Application de la fonction LMAP pour que notre macro soit exécutée sur chaque élément de la liste.
LMAP

Et voilà, nos données ont été récupérées de la Sandbox et stockées dans notre instance locale.

Bonus

Une version alternative - dans mes données, je peux tricher et ne filtrer que sur le label company avec pour valeur cerenit:

// tokens de l'instance
'<instanceReadToken>' 'instanceReadToken' STORE
'<instanceWriteToken>' 'instanceWriteToken' STORE

// tokens pour la sandbox
'<sandboxReadToken>' 'sandboxReadToken' STORE
'<sandboxWriteToken>' 'sandoxWriteToken' STORE

// Url de la sandbox
'https://sandbox.senx.io/api/v0/exec' 'url' STORE

// Warpscript template
<'
  {
    'token' '{{ remoteReadToken }}'
    'class' '~.*'
    'labels' { 'company' 'cerenit' }
    'start' '2016-12-01T00:00:00Z'
    'end' '2022-01-31T00:00:00Z'
  } FETCH
'>
'fetchTpl' STORE

// Substitution dans le template
$fetchTpl
{ 'remoteReadToken' $sandboxReadToken } TEMPLATE
// Execution de la requête
$url REXECZ
// Renommage des séries
<% '+' RENAME %> F LMAP
// Presistence des données
$instanceWriteToken UPDATE

Bravo si vous m’avez suivi jusqu’ici, nous avons pu voir l’utilisation de :

  • Comment permettre un requêtage à distance d’une instance Warp 10 avec les fonctions REXEC et REXECZ
  • Comment traiter dynamiquement notre liste de GTS avec LMAP et une MACRO
  • Comment faire un template warpscript et de la substitution de variables avec TEMPLATE

Nous verrons dans un prochain épisode :

  • Comment utiliser Discovery pour faire nos premiers dashboards
  • Pourquoi/comment utiliser des macros coté server (spoiler: pour éviter que vos tokens se retrouvent dans votre navigateur à l’exécution des dashboards)

Ma comptabilité, une série temporelle comme les autres - partie 2


05/02/2021 warp10 timeseries comptabilité prévision forecast

L’année dernière, nous avions travaillé sur Warp 10 et mes données de comptabilité et jouer un peu avec les algo de prévision.

Les données comptables ayant été un peu ajustées entre temps et la librairie de prévision ayant aussi évolué coté SenX, les résultats ne sont plus tout à fait les mêmes. Nous allons donc reprendre tout ça.

Rappel des fais et prévisions à fin 2020

En septembre dernier, nous avions ce code pour avoir les données jusqu’au mois de Mai 2020 et une prévision jusqu’à la fin d’année:

'<read token>' 'readToken' STORE
'<write token>' 'writeToken' STORE

# Récupération des données de dépenses / chiffre d'affaires / résult pour la période du 01/01/2017 -> 31/05/2020
# Chaque série est stockée dans une variable
[ $readToken 'expense' { 'company' '=cerenit' } '2016-12-01T00:00:00Z' '2020-06-01T00:00:00Z' ] FETCH
0 GET
'exp' STORE

[ $readToken 'revenue' { 'company' '=cerenit' } '2016-12-01T00:00:00Z' '2020-06-01T00:00:00Z' ] FETCH
0 GET
'revenue' STORE

[ $readToken 'result' { 'company' '=cerenit' } '2016-12-01T00:00:00Z' '2020-06-01T00:00:00Z' ] FETCH
0 GET
'result' STORE

# On affiche les trois courbes
$revenue
$exp
$result

# On génère et affiche les prévisions - on renomme les séries pour mieux les différencier ensuite au niveau dataviz
[ $result mapper.todouble 0 0 0 ] MAP
AUTO 7 FORECAST.ADDVALUES
"forecast_result" RENAME

[ $revenue mapper.todouble 0 0 0 ] MAP
AUTO 7 FORECAST.ADDVALUES
"forecast_revenue" RENAME

[ $exp mapper.todouble 0 0 0 ] MAP
AUTO 7 FORECAST.ADDVALUES
"forecast_expense" RENAME

Au global :

warp10

Focus 2020 avec la partie prévision à partir de juin :

warp10

Si on fait la même chose en prenant un algo incluant un effet de saisonnalité :

'<read token>' 'readToken' STORE
'<write token>' 'writeToken' STORE

# Récupération des données de dépenses / chiffre d'affaires / résult pour la période du 01/01/2017 -> 31/05/2020
# Chaque série est stockée dans une variable
[ $readToken 'expense' { 'company' '=cerenit' } '2016-12-01T00:00:00Z' '2020-06-01T00:00:00Z' ] FETCH
0 GET
'exp' STORE

[ $readToken 'revenue' { 'company' '=cerenit' } '2016-12-01T00:00:00Z' '2020-06-01T00:00:00Z' ] FETCH
0 GET
'revenue' STORE

[ $readToken 'result' { 'company' '=cerenit' } '2016-12-01T00:00:00Z' '2020-06-01T00:00:00Z' ] FETCH
0 GET
'result' STORE

# On affiche les trois courbes
$revenue
$exp
$result

[ $result mapper.todouble 0 0 0 ] MAP
12 SAUTO 7 FORECAST.ADDVALUES
"forecast_result" RENAME

[ $revenue mapper.todouble 0 0 0 ] MAP
12 SAUTO 7 FORECAST.ADDVALUES
"forecast_revenue" RENAME

[ $exp mapper.todouble 0 0 0 ] MAP
12 SAUTO 7 FORECAST.ADDVALUES
"forecast_expense" RENAME

Au global :

warp10

Focus 2020 avec la partie prévision à partir de juin :

warp10

On a bien un petit écart de comportement sur la prévision entre les deux modèles (focus sur 2020 avec les différentes prévisions à partir de juin) :

'<read token>' 'readToken' STORE
'<write token>' 'writeToken' STORE

# Récupération des données de dépenses / chiffre d'affaires / résult pour la période du 01/01/2017 -> 31/05/2020
[ $readToken 'expense' { 'company' '=cerenit' } '2016-12-01T00:00:00Z' '2020-06-01T00:00:00Z' ] FETCH
0 GET
'exp' STORE

[ $readToken 'revenue' { 'company' '=cerenit' } '2016-12-01T00:00:00Z' '2020-06-01T00:00:00Z' ] FETCH
0 GET
'revenue' STORE

[ $readToken 'result' { 'company' '=cerenit' } '2016-12-01T00:00:00Z' '2020-06-01T00:00:00Z' ] FETCH
0 GET
'result' STORE

[ $result mapper.todouble 0 0 0 ] MAP
12 SAUTO 7 FORECAST.ADDVALUES
"sauto_result" RENAME

[ $revenue mapper.todouble 0 0 0 ] MAP
12 SAUTO 7 FORECAST.ADDVALUES
"sauto_revenue" RENAME

[ $exp mapper.todouble 0 0 0 ] MAP
12 SAUTO 7 FORECAST.ADDVALUES
"sauto_expense" RENAME

# On génère et affiche les prévisions - on renomme les séries pour mieux les différencier ensuite au niveau dataviz
[ $result mapper.todouble 0 0 0 ] MAP
AUTO 7 FORECAST.ADDVALUES
"auto_result" RENAME

[ $revenue mapper.todouble 0 0 0 ] MAP
AUTO 7 FORECAST.ADDVALUES
"auto_revenue" RENAME

[ $exp mapper.todouble 0 0 0 ] MAP
AUTO 7 FORECAST.ADDVALUES
"auto_expense" RENAME

warp10

Prévisions vs réalité

Comparons maintenant les prévisions à la réalité - je vais rajouter les requêtes pour avoir la vue complète des données - pour éviter de trop surcharger le graphique, comme les séries forecast_* reprennent les données sources et y ajoutent la prévision, je ne vais afficher que ces séries et les séries réelles :

'<read token>' 'readToken' STORE
'<write token>' 'writeToken' STORE

# Récupération des données de base qui serviront ensuite pour la prévision
[ $readToken 'expense' { 'company' '=cerenit' } '2016-12-01T00:00:00Z' '2020-06-01T00:00:00Z' ] FETCH
0 GET
'exp' STORE

[ $readToken 'revenue' { 'company' '=cerenit' } '2016-12-01T00:00:00Z' '2020-06-01T00:00:00Z' ] FETCH
0 GET
'revenue' STORE

[ $readToken 'result' { 'company' '=cerenit' } '2016-12-01T00:00:00Z' '2020-06-01T00:00:00Z' ] FETCH
0 GET
'result' STORE

# Récupération des données réelles de la période 01/01/2017 > 31/12/2020
[ $readToken 'expense' { 'company' '=cerenit' } '2016-12-01T00:00:00Z' '2021-01-01T00:00:00Z' ] FETCH
0 GET
'real_exp' STORE

[ $readToken 'revenue' { 'company' '=cerenit' } '2016-12-01T00:00:00Z' '2021-01-01T00:00:00Z' ] FETCH
0 GET
'real_revenue' STORE

[ $readToken 'result' { 'company' '=cerenit' } '2016-12-01T00:00:00Z' '2021-01-01T00:00:00Z' ] FETCH
0 GET
'real_result' STORE

# Génération des prévisions
# Pour SAUTO, il faut définir en plus un cycle, ici 12 mois
[ $result mapper.todouble 0 0 0 ] MAP
12 SAUTO 7 FORECAST.ADDVALUES
"forecast_result" RENAME

[ $revenue mapper.todouble 0 0 0 ] MAP
12 SAUTO 7 FORECAST.ADDVALUES
"forecast_revenue" RENAME

[ $exp mapper.todouble 0 0 0 ] MAP
12 SAUTO 7 FORECAST.ADDVALUES
"forecast_expense" RENAME

$real_result
$real_revenue
$real_exp

Ce qui nous donne au global :

warp10

et avec le focus 2020 :

warp10

Si on fait la même chose avec SAUTO

'<read token>' 'readToken' STORE
'<write token>' 'writeToken' STORE

# Récupération des données de base qui serviront ensuite pour la prévision
[ $readToken 'expense' { 'company' '=cerenit' } '2016-12-01T00:00:00Z' '2020-06-01T00:00:00Z' ] FETCH
0 GET
'exp' STORE

[ $readToken 'revenue' { 'company' '=cerenit' } '2016-12-01T00:00:00Z' '2020-06-01T00:00:00Z' ] FETCH
0 GET
'revenue' STORE

[ $readToken 'result' { 'company' '=cerenit' } '2016-12-01T00:00:00Z' '2020-06-01T00:00:00Z' ] FETCH
0 GET
'result' STORE

# Récupération des données réelles de la période 01/01/2017 > 31/12/2020
[ $readToken 'expense' { 'company' '=cerenit' } '2016-12-01T00:00:00Z' '2021-01-01T00:00:00Z' ] FETCH
0 GET
'real_exp' STORE

[ $readToken 'revenue' { 'company' '=cerenit' } '2016-12-01T00:00:00Z' '2021-01-01T00:00:00Z' ] FETCH
0 GET
'real_revenue' STORE

[ $readToken 'result' { 'company' '=cerenit' } '2016-12-01T00:00:00Z' '2021-01-01T00:00:00Z' ] FETCH
0 GET
'real_result' STORE

# Génération des prévisions
[ $result mapper.todouble 0 0 0 ] MAP
AUTO 7 FORECAST.ADDVALUES
"forecast_result" RENAME

[ $revenue mapper.todouble 0 0 0 ] MAP
AUTO 7 FORECAST.ADDVALUES
"forecast_revenue" RENAME

[ $exp mapper.todouble 0 0 0 ] MAP
AUTO 7 FORECAST.ADDVALUES
"forecast_expense" RENAME

$real_result
$real_revenue
$real_exp

Au global :

warp10

Focus 2020 avec la partie prévision à partir de juin :

warp10

Essayons d’analyser tout ça (il faut regarder les fins de mois - les points sont en date du dernier jour du mois) :

  • Pour Juin/Juillet, la prévision est plutôt bonne.
  • Pour Aout : l’écart vient du fait que j’ai pris mes vacances en aout et pas à cheval sur juillet/aout comme les autres années
  • Pour Septembre, c’est correct
  • Pour Octobre, il faut voir que j’ai tardé à éditer mes factures - elles ont donc été pris en compte sur Novembre - si on divise le montant de Novembre en deux, on retombe à peu près sur nos points
  • Pour décembre, un effet vacances également.

La pertinence est prévisions est donc plutôt correct au global et les écarts sont expliquables.

Consolidation annuelle

Et au niveau annuel ? Est-ce que les prévisions de chiffres d’affaires / dépenses / résultats sont bonnes si on ne tient plus compte des petits écarts de temps ci-dessus ?

Voyons celà :

'<read token>' 'readToken' STORE
'<write token>' 'writeToken' STORE

# Récupération des différentes séries comme précédemment
[ $readToken 'expense' { 'company' '=cerenit' } '2016-12-01T00:00:00Z' '2020-06-01T00:00:00Z' ] FETCH
0 GET
'exp' STORE

[ $readToken 'revenue' { 'company' '=cerenit' } '2016-12-01T00:00:00Z' '2020-06-01T00:00:00Z' ] FETCH
0 GET
'revenue' STORE

[ $readToken 'result' { 'company' '=cerenit' } '2016-12-01T00:00:00Z' '2020-06-01T00:00:00Z' ] FETCH
0 GET
'result' STORE

[ $readToken 'expense' { 'company' '=cerenit' } '2016-12-01T00:00:00Z' '2021-01-01T00:00:00Z' ] FETCH
0 GET
'real_exp' STORE

[ $readToken 'revenue' { 'company' '=cerenit' } '2016-12-01T00:00:00Z' '2021-01-01T00:00:00Z' ] FETCH
0 GET
'real_revenue' STORE

[ $readToken 'result' { 'company' '=cerenit' } '2016-12-01T00:00:00Z' '2021-01-01T00:00:00Z' ] FETCH
0 GET
'real_result' STORE

# Calcul des prévisions comme précédemment
# Petit ajout, on stocke le résultat sous la forme d'une variable pour être réutilisé ultérieurement
[ $result mapper.todouble 0 0 0 ] MAP
AUTO 7 FORECAST.ADDVALUES
"auto_result" RENAME
'auto_result' STORE

[ $revenue mapper.todouble 0 0 0 ] MAP
AUTO 7 FORECAST.ADDVALUES
"auto_revenue" RENAME
'auto_revenue' STORE

[ $exp mapper.todouble 0 0 0 ] MAP
AUTO 7 FORECAST.ADDVALUES
"auto_expense" RENAME
'auto_expense' STORE

# Aggrégation annuelle
# Utilisation de BUCKETIZE.CALENDAR et de la macro BUCKETIZE.byyear qui s'appuie dessus et qui permet de faire une aggrégation annuelle sur des données
# bucketizer.sum permet d'appliquer une somme sur les données regroupées par année
# UNBUCKETIZE.CALENDAR permet de retransformer l'indice issue de BUCKETIZE.CALENDAR en timestamp
[ $real_revenue bucketizer.sum ] @senx/cal/BUCKETIZE.byyear UNBUCKETIZE.CALENDAR
[ $real_result bucketizer.sum ] @senx/cal/BUCKETIZE.byyear UNBUCKETIZE.CALENDAR
[ $real_exp bucketizer.sum ] @senx/cal/BUCKETIZE.byyear UNBUCKETIZE.CALENDAR

[ $auto_revenue bucketizer.sum ] @senx/cal/BUCKETIZE.byyear UNBUCKETIZE.CALENDAR
[ $auto_result bucketizer.sum ] @senx/cal/BUCKETIZE.byyear UNBUCKETIZE.CALENDAR
[ $auto_expense bucketizer.sum ] @senx/cal/BUCKETIZE.byyear UNBUCKETIZE.CALENDAR


# Meme chose pour SAUTO
[ $result mapper.todouble 0 0 0 ] MAP
12 SAUTO 7 FORECAST.ADDVALUES
"sauto_result" RENAME
'sauto_result' STORE

[ $revenue mapper.todouble 0 0 0 ] MAP
12 SAUTO 7 FORECAST.ADDVALUES
"sauto_revenue" RENAME
'sauto_revenue' STORE

[ $exp mapper.todouble 0 0 0 ] MAP
12 SAUTO 7 FORECAST.ADDVALUES
"sauto_expense" RENAME
'sauto_expense' STORE

[ $sauto_revenue bucketizer.sum ] @senx/cal/BUCKETIZE.byyear UNBUCKETIZE.CALENDAR
[ $sauto_result bucketizer.sum ] @senx/cal/BUCKETIZE.byyear UNBUCKETIZE.CALENDAR
[ $sauto_expense bucketizer.sum ] @senx/cal/BUCKETIZE.byyear UNBUCKETIZE.CALENDAR

Pour expliciter un peu au dessus :

On veut obtenir un résultat annuel couvant la période du 01/01 au 31/12 d’une année. Il faut donc prendre tous les points de l’année en question et en fait la somme.

Si on fait:

[ $real_revenue bucketizer.sum ] @senx/cal/BUCKETIZE.byyear

On obtient :

[{"c":"revenue","l":{"company":"cerenit",".app":"52274aa9-8242-49ee-b3e8-dbc6f514999d",".uuid":"52274aa9-8242-49ee-b3e8-dbc6f514999d"},"a":{".buckettimezone":"UTC",".bucketduration":"P1Y",".bucketoffset":"0"},"la":1612528364518,"v":[[47,100850],[48,132473],[49,151714],[50,139146]]}]

Les valeus obtenues sont :

[[47,100850],[48,132473],[49,151714],[50,139146]]

Les indices 47, 48, 49, 50 sont en fait un delta par rapport au 01/01/70. En effet, 2020 = 1970 + 50

En appliquant UNBUCKETIZE.CALENDAR, on retransforme ce 50 par ex en son équivalent sous la forme d’un timestamp : 1609459199999999.

On peut aussi utiliser TIMESHIFT de la façon suivante :

[ $real_revenue bucketizer.sum ] @senx/cal/BUCKETIZE.byyear 1970 TIMESHIFT

Pour obtenir pour la partie valeur :

[[2017,100850],[2018,132473],[2019,151714],[2020,139146]]

Pour en savoir plus sur BUCKETIZE.CALENDAR et ses utilisations : Aggregate by calendar duration in WarpScript

Une fois qu’on reprend toutes ses données, on peut essayer de mesurer les écarts entre le réél et les prévisions des deux modèles :

AUTO SAUTO Réel AUTO vs Réel SAUTO vs Réel
Chiffre d’affaires 144.029 125.128 139.146 -3,39% +11,20%
Dépénses 117.701 113.765 129.464 +9,99% +13,80%
Résultat 14.754 16.893 9.682 -34,38% -42,69%
Résultat corrigé 26.328 11.363 9.682 -63,23% -14,79%

Intéressant, la prévision de résultat n’est pas égale à la différence entre la prévision de chiffre d’affaires et la prévision des dépenses ! C’est la raison de la ligne “Résultat corrigé”.

A ce stade, il ne me semble pas possible de privilégier un modèle plus qu’un autre - même si du fait de la récurrence des vacances, on peut supposer que le modèle avec saisonnalité pourrait être plus pertinent.

Prévisions pour 2021

Pour aller au bout de cet exerice, il ne reste plus qu’à voir ce que nos algoritmes prévoient pour 2021 :

'<read token>' 'readToken' STORE
'<write token>' 'writeToken' STORE

# Récupération des séries 2017 > 2020
[ $readToken 'expense' { 'company' '=cerenit' } '2016-12-01T00:00:00Z' '2021-01-01T00:00:00Z' ] FETCH
0 GET
'exp' STORE

[ $readToken 'revenue' { 'company' '=cerenit' } '2016-12-01T00:00:00Z' '2021-01-01T00:00:00Z' ] FETCH
0 GET
'revenue' STORE

[ $readToken 'result' { 'company' '=cerenit' } '2016-12-01T00:00:00Z' '2021-01-01T00:00:00Z' ] FETCH
0 GET
'result' STORE

# Prévision sur les 12 prochains mois
[ $result mapper.todouble 0 0 0 ] MAP
AUTO 12 FORECAST.ADDVALUES
"auto_result" RENAME
'auto_result' STORE

[ $revenue mapper.todouble 0 0 0 ] MAP
AUTO 12 FORECAST.ADDVALUES
"auto_revenue" RENAME
'auto_revenue' STORE

[ $exp mapper.todouble 0 0 0 ] MAP
AUTO 12 FORECAST.ADDVALUES
"auto_expense" RENAME
'auto_expense' STORE

# Consolidation annuelle avec AUTO
[ $auto_revenue bucketizer.sum ] @senx/cal/BUCKETIZE.byyear 1970 TIMESHIFT
[ $auto_result bucketizer.sum ] @senx/cal/BUCKETIZE.byyear 1970 TIMESHIFT
[ $auto_expense bucketizer.sum ] @senx/cal/BUCKETIZE.byyear 1970 TIMESHIFT

# Prévisions avec SAUTO
[ $result mapper.todouble 0 0 0 ] MAP
12 SAUTO 12 FORECAST.ADDVALUES
"sauto_result" RENAME
'sauto_result' STORE

[ $revenue mapper.todouble 0 0 0 ] MAP
12 SAUTO 12 FORECAST.ADDVALUES
"sauto_revenue" RENAME
'sauto_revenue' STORE

[ $exp mapper.todouble 0 0 0 ] MAP
12 SAUTO 12 FORECAST.ADDVALUES
"sauto_expense" RENAME
'sauto_expense' STORE

# Consolidation annuelle avec SAUTO
[ $sauto_revenue bucketizer.sum ] @senx/cal/BUCKETIZE.byyear 1970 TIMESHIFT
[ $sauto_result bucketizer.sum ] @senx/cal/BUCKETIZE.byyear 1970 TIMESHIFT
[ $sauto_expense bucketizer.sum ] @senx/cal/BUCKETIZE.byyear 1970 TIMESHIFT

On passe tout ça dans le shaker et on obtient :

Prévu avec AUTO Prévu avec SAUTO
Chiffre d’affaires 78.230 129.465
Dépénses 118.383 110.434
Résultat prévu 5.730 4.049
Résultat corrigé -40.153 19.031

Rendez-vous à la fin de l’année pour voir ce qu’il en est… et on peut espérer que la réalité sera proche du modèle avec saisonnalité !

Pour le moment, on travalle toujours dans le WarpStudio et on voudrait bien avoir des (jolis) dashboards qui font tout ça pour nous plutôt que de copier/coller du Warpscript. Ce sera le sujet de la partie 3.

Web, Ops & Data - Janvier 2021


27/01/2021 timeseries prometheus promql ovhcloud iot openhab vector timescaledb ptsm anomalie label machine-learning iac ansible libssh vector log warp10 influxdb openssh gpg podman docker-compose sudo

Cloud

Code

  • GitLab release feature report : le code qui permet de générer le rapport ce qui a changé entre les versions de Gitlab.
  • SSH is the new GPG : les dernières versions d’OpenSSH permettent de signer un fichier. Une solution intermédiaire entre de la signature de fichiers à base de MD5 & co qui donnent des informations de conformité mais sans indiquer qui a signé le fichier et une solution GPG plus complexe à mettre en oeuvre ?

Container et orchestration

  • Using Podman and Docker Compose : podman, le “daemonless container engine” va permettre d’être utilisé avec docker-compose dans le cadre de la version 3.0. De quoi favoriser l’adoption de podman ?

Infra as code

  • New LibSSH Connection Plugin for Ansible Network Replaces Paramiko, Adds FIPS Mode Enablement : Ansible change de librairie pour les connexions ssh en remplaçant paramiko par libssh. Elle se veut plus performante et peut être requis dans un contexte demandant du FIPS. Pensez à installer le paquet libssh-dev(el) suivant votre distribution pour pouvoir installer ansible-pylibssh. Mes premiers essais ne notent pas une amélioration sensible des performances… à voir sur d’autres machines et dans la durée…

IoT

  • openHAB 3.0 Release et Release Notes : OpenHAB est une plateforme open source de gestion de périphétiques IoT et d’automatisation autour de ces périphériques. Elle est développée en Java, support 2000 “Things” (objets, équipements, protocoles). La version 3.0 apporte une refonte et l’unification de l’UI et des composants, le passage à Java 11 et plein d’autres choses. La migration depuis une version 2.x se fait assez simplement. Avec le nouveau moteur de règle, j’ai pu supprimer mon code spécifique. Reste encore la partie “Pages” à appréhender… J’avais préféré OpenHAB à Jeedom et Home Assistant
  • Meet Raspberry Silicon: Raspberry Pi Pico now on sale at $4 : la fondation Raspberry Pi se lance dans les micro-controlleurs avec le Pico au prix de 4$.
  • Raspberry Pi PICO la carte Microcontrôleur de la Fondation : un article très détaillé sur la prise en main du pico.

Observabilité

Système

Time Series

Syndication

Restez informé(s) de notre actualité en vous abonnant au flux du blog (Atom)

Nuage de tags

docker kubernetes influxdb timeseries traefik grafana ansible kafka postgres warp10 elasticsearch python aws sécurité mysql redis terraform tick cassandra cloud helm ovh docker-compose git swarm telegraf timescaledb ptsm rancher résilience test chronograf flux gitlab log machine-learning prometheus architecture arm confluent dashboard devops hashicorp iac java ksql microservice monitoring s3 serverless spark vscode angularjs api bilan cert-manager cncf container cérénit dns gcp graphql ingress javascript kapacitor opensource operator optimisation perspective podman raspberrypi service-mesh sql ssh stream warpscript windows csp documentation elastic flows forecast gke hpkp influxace jenkins kafka-streams kibana kubedb lambda lean licence maesh maintenance mariadb microsoft mobile nginx npm orientdb performance pipeline redhat registry rest rethinkdb reverse-proxy rook sauvegarde scaleway vector agile apm automatisation azure bash big-data bigdatahebdo ceph certificat ci/cd cli cluster comptabilité containerd continous-delivery continous-integration cookie dataviz deployment diff fluxlang framework gdpr gitlab-ci grav hsts http/3 https hypriot hébergement influxdata influxdays iot istio jq json k3s lets-encrypt linux load-balancer longhorn meetup molecule mongodb nomad nosql nvidia openebs openssh ovhcloud percona php pip postgresql reaper replication rootless rpi rsyslog runc scale secrets société solr sre systemd timescale timezone tls vault virtualenv vitess vue.js wagtail warpfleet yarn accessibilité acme akka alerte alibaba amazon-emr amqp anomalie anonymisation anthos apache-pulsar ara arima arrow artefact audit bastion beam beat bounded-context branche brigade browser buildkit cahier-des-charges calico cassandra-reaper cd cdc cdk centos centralisation-de-logs certificats cgroups chart checklist chrome ci cilium cloud-init cloud-native cloud-storage clusterip cnab cni cockroachdb code codeurs-en-seine commit confluence conftest consul context continous-deployment conventional-commit coreos cors covid19 cqrs crash cri cron crontab csi csrf css curl d3.js daemonset data data-engineer data-pipelining data.gouv.fr datacenter date date-scientist ddd debezium debian delta deprek8 desktop devoxx dig distributed-systems dive docker-app docker-hub docker-registry docker-swarm dockershim documentdb dog dokcer données-personnelles draft drop-in duration déploiement développement-du-site e-commerce ebs ec2 edge elassandra electron elk engineering entreprise ergonomie etcd event-sourcing faas facebook faisabilité falcor feature-policy fedora feed filebeat firebase firefox fish flash flask fleet flink fluentd formation foundation frontend fsync fullstack github gitignore glacier glowroot golang google google-cloud-next gpg gpu grid géospatial hacker hadoop haproxy harbor hdfs header html html5 http hue ia iaac ibm immutable incident index influxcloud infrastructure-as-code ingénierie inspec jquery jwt k3d k8s k9s kotlin kubeadm kubecon kubectl label laravel letsencrypt libssh linky linter liste-de-diffusion lmap loadbalancer logstash logstatsh loi mailing-list management maturité mesh mesos message metallb micro-service mot-de-passe mqtt multi-cloud médecine métrique network newsletter nodeport null object-storage observability observabilité opa opendata openhab openmetrics openshit openstack openweb over-engineering packaging pandas parquet partiql password persistent-volume-claim pipenv pod portainer portworx prediction prescience production promql prévision ptyhon publicité pubsub pulsar push pyenv pérénnité qualité quasardb quay questdb queue quic ram rambleed raml react recaptcha recherche redistimeseries reindex reinvent reliability remote-execution repository responsive revocation revue-de-code rexec rgpd rhel rkt rolespec root rpo rto rust rwd safe-harbor scalabilité scanner schema scp sdk search select serverless-architecture service service-account service-worker setuptools sftp sha1 sharding shell shipyard sidecar souveraineté-numérique spinnaker spécifications sri ssh-agent ssl stabilité stash statistique storage sudo superset suse sympa syslog-ng sérénité template terracost terrascan test-unitaire tidb tiers timer timestream training transformation travail tsl ubuntu unikernel unit ux vendredi victoria-metrics vie-privée virtualbox virtualisation vm vnc volume voxxeddays vpc warpstudio web yaml yq yubikey