Langzeitmonitoring von Freifunk-Nodes mit Google Tabellen und Google Apps Script

Aus wiki.freifunk.net
Zur Navigation springenZur Suche springen


Langzeitmonitoring von Freifunk-Nodes mit Google Tabellen und Google Apps Script

Mithilfe von Google Tabellen und einem Google Apps Script (GAS) können auf einfache Art und Weise Langzeitstatistiken von Freifunk-Nodes erstellt werden. Alles was dazu benötigt wird, ist ein Google-Konto und die URL zu der nodes.json des jeweiligen ffmap-backends. Die periodische Datenabfrage und Datenhaltung übernimmt Google für uns. Die in Google Tabellen gesammelten Informationen können dann zum Beispiel als CSV exportiert (automatisiert heruntergeladen) und ausgewertet werden oder direkt in Google Tabellen durch Diagramme visualisiert werden.

Die folgende bebilderte Anleitung zeigt Schritt für Schritt wie es funktioniert.


Schritt 1

Nach dem Einloggen in Google Drive wird als erstes eine neue Tabelle erstellt.

Monitor nodes with gas step1.png


Schritt 2

Innerhalb der neuen Tabelle wird der Skripteditor geöffnet.

Monitor nodes with gas step2.png


Schritt 3

Der Inhalt der bereits vorhanden Datei Code.gs wird durch dieses Skript ersetzt und anschließend gespeichert.

/*
  A Google Apps Script to periodically fetch nodes.json 
  and push data into Google Sheets for selected nodes.
  
  Copyright (c) 2016 Florian Schäfer <florian.schaefer+freifunk@gmail.com>
*/

var NODES_JSON_URL = "http://gw2.freifunk-lueneburg.de/meshviewer/data/nodes.json";

// create a log for these nodes
var NODES = [
  "f81a675304a3",
  "687251628f83",
  "18a6f76be754"
];

// the date the script was triggered
var date = Utilities.formatDate(new Date(), "GMT", "yyyy-MM-dd'T'HH:mm:ss'Z'");

// definition to map from JSON to a sheet column
var DataMappings = [
  { "caption": "Date", "fn": function (node) { return date; } },
  { "caption": "ID", "fn": function (node) { return node.nodeinfo.node_id; } },
  { "caption": "Hostname", "fn": function (node) { return node.nodeinfo.hostname; } },
  { "caption": "Hardware", "fn": function (node) { return node.nodeinfo.hardware.model; } },
  { "caption": "Online", "fn": function (node) { return node.flags.online; } },
  { "caption": "Clients", "fn": function (node) { return node.statistics.clients; } },
  { "caption": "Uptime", "fn": function (node) { return node.statistics.uptime; } },
  { "caption": "Memory Usage", "fn": function (node) { return node.statistics.memory_usage; } },
  { "caption": "Load Avg", "fn": function (node) { return node.statistics.loadavg; } },
  { "caption": "First Seen", "fn": function (node) { return node.firstseen; } },
  { "caption": "Last Seen", "fn": function (node) { return node.lastseen; } },
  { "caption": "RX", "fn": function (node) { return node.statistics.traffic.rx.bytes; } },
  { "caption": "TX", "fn": function (node) { return node.statistics.traffic.tx.bytes; } }
];

function FFNodeMonitor (endpoint, nodes) {

  this.endpoint = endpoint;
  this.nodes = nodes;
  this.activeSpreadSheet = SpreadsheetApp.getActive();
  this.activeSheet = SpreadsheetApp.getActiveSheet();

  // check if the clock based trigger is already installed
  this.triggerId = PropertiesService.getScriptProperties().getProperty("triggerId");
  
  // if not, do so
  if (!this.triggerId) {
    this.triggerId = ScriptApp.newTrigger("main")
      .timeBased()
      .everyMinutes(15)
      .create()
      .getUniqueId();
    PropertiesService.getScriptProperties().setProperty("triggerId", this.triggerId);
  }
}

FFNodeMonitor.prototype.fetchData = function () {
  // fetch the data from endpoint and parse it to JSON
  var response = UrlFetchApp.fetch(this.endpoint);
  this.data = JSON.parse(response);
  return this;
};

FFNodeMonitor.prototype.fillCells = function () {
  this.nodes.forEach(function (nodeId, i) {
    
    var scheetName = nodeId;

    // create one sheet per node with the node id as name
    this.dataSheet = this.activeSpreadSheet.getSheetByName(scheetName);
    if (!this.dataSheet) {
      this.dataSheet = this.activeSpreadSheet.insertSheet(scheetName);
    }
    
    // setup column head lines
    DataMappings
    .forEach(function (mapping, i) {
      this.dataSheet.getRange(String.fromCharCode(65 + i) + 1).setValue(mapping.caption);
    }.bind(this));
    
    // get last free row and fill it with data from mapping
    var offset = this.dataSheet.getLastRow() + 1;
    DataMappings
    .forEach(function (mapping, i) {
        this.dataSheet.getRange(String.fromCharCode(65 + i) + offset).setValue(mapping.fn(this.data.nodes[nodeId]));
        this.dataSheet.autoResizeColumn(i + 1);
    }.bind(this));
  }.bind(this));
};

function main() {
  new FFNodeMonitor(NODES_JSON_URL, NODES)
    .fetchData()
    .fillCells();
}

Monitor nodes with gas step3.png


Schritt 4

In dem Menü Funktion auswählen wird die Funktion main des Google Apps Skripts ausgewählt.

Monitor nodes with gas step4.png


Schritt 5

Mit der Variable NODES_JSON_URL wird die URL zur nodes.json des ffmap-backends angegeben. Im Array NODES werden die zu protokollierenden Node-IDs eingetragen.

Monitor nodes with gas step5.png


Schritt 6

Nachdem die Node-IDs eingetragen wurden, wird nun das Skript zum ersten Mal ausgeführt.

Monitor nodes with gas step6.png


Schritt 7

Bevor die Ausführung des Skripts beginnt, müssen dem Skript einmalig Berechtigungen erteilt werden.

Monitor nodes with gas step7.png


Schritt 8

Nach der Bestätigung wird das Skript nun gestartet.


Monitor nodes with gas step8.png


Schritt 9

Das Skript legt in der Google Tabelle für jede Node-ID ein neues Tabellenblatt an und schreibt die abgerufenen Daten für jeden Node in die erste leere Zeile der Tabelle.

Monitor nodes with gas step9.png



Schritt 10

Das Skript erstellt programmatisch einen Trigger. Dieser ruft das Skript alle 15 Minuten auf, auch wenn die Tabelle im Browser geschlossen ist. Dieser Trigger kann durch einen Klick auf die Schaltfläche Trigger des aktuellen Projekts kontrolliert und angepasst werden.

Monitor nodes with gas step10.png



Anmerkungen

Das Skript ist ein Prototyp und wurde nur mit der nodes.json der Freifunk Lueneburg Community getestet. Es sollte aber auch mit den Servern der anderen Communities funktionieren. Mit ein paar kleinen Änderungen könnten auch Nachrichten (z. B. E-Mails) bei bestimmten Ereignissen (z. B. Clientanzahl > X) versendet werden.