Simulare il carico di un server con Groovy, Apache Commons Math e JFreeChart

Submitted by dimitri on Tue, 07/29/2014 - 22:59
JFreeChart

È ormai da un po' di tempo che ho imparato a giocherellare con Groovy e devo dire che lo sto apprezzando ogni giorno di più. In questo articolo voglio mostrarvi come sia facile simulare, utilizzando Groovy e la libreria Apache Commons Math, i dati di carico tipici di un server che lavora 24/7, con tanto di picchi di lavoro durante le ore diurne.

Due parole su Groovy

A suo tempo mi misi a cercare un nuovo linguaggio di programmazione oltre a Java (non vi preoccupate, continuerò ad utilizzarlo per molto tempo ancora!). Sentivo il bisogno di qualcosa di più agile e produttivo, soprattutto per quanto riguarda la creazione di piccole utilità di sistema, script one-shot, e così via. Tutto ciò assecondando la mia idiosincrasia per (in ordine di importanza):

  • script batch di Windows (per non parlare di VBScript). Forse un pochino meglio è PowerShell, ma lasciamo perdere;
  • Sh/Bash/Csh & affini;
  • Perl & affini;
  • ecc. ecc. Devo continuare?

Insomma, per farla breve, dopo aver provato varie alternative come Ruby, Python, Clojure(!), Scala(!!!) e altri, ho finalmente incontrato Groovy... e me ne sono innamorato.

Prometto quindi che ne riparleremo su questo blog. Ora però... al lavoro!

L'idea

In questo periodo sto lavorando al mio progetto per la Laurea in Informatica presso l'Università degli Studi di Milano Bicocca, argomento principale: NoSQL e Big Data. Tra le varie fonti di informazione ho scelto il celeberrimo
Seven Databases in Seven Weeks: A Guide to Modern Databases and the NoSQL Movement (affil. link):

Ad un certo punto è sorta la necessità di inserire in questi database tonnellate e tonnellate di dati a piacere, giusto per vedere che se la cavavano con carichi "importanti". Ho pensato quindi che fosse una ottima scusa per usare Groovy...

L'algoritmo

Per creare i dati mi sono servito di diversi ingredienti:

  • una funzione periodica, con una forma che ricordi (vagamente) un'onda quadra, ma che non sia troppo spigolosa, che consenta di simulare il carico maggiore in una specifica fascia oraria;
  • una funzione Distribuzione Normale, configurabile con media e deviazione standard, per ottenere una variabilità realistica.

Per la Distribuzione Normale mi sono affidato alla Apache Commons Math, che è molto semplice da includere (non ha altre dipendenze) e da utilizzare.

Script n.1: generazione dei dati

Questo script contiene unicamente la classe DataSimulationUtils con un solo metodo (statico): simulateServerLoad(). I parametri del metodo sono:

  • startDate: data di inizio delle misurazioni;
  • endDate: data di fine;
  • intervalMinutes: intervallo tra una misurazione e l'altra
  • mean: media dei valori generati;
  • stdDev: deviazione standard

Vorrei inoltre far notare le direttive @Grapes all'inizio del file: queste vengono utilizzate per scaricare in automatico le librerie necessarie, sfruttando Apache Ivy come meccanismo di risoluzione delle dipendenze. Vi rimando alla documentazione ufficiale per i dettagli.

DataSimulationUtils.groovy

@Grapes(
    @Grab(group='org.apache.commons', module='commons-math3', version='3.3')
)

import org.apache.commons.math3.distribution.NormalDistribution

/**
 * Class that generates dummy data for testing purposes.
 * By Dimitri De Franciscis
 * http://www.megadix.it/
 * CCDF3FB0-165D-11E4-8C21-0800200C9A66
 */
class DataSimulationUtils {

    public static SortedMap<Date, Double> simulateServerLoad(
            Date startDate, Date endDate,
            int intervalMinutes,
            double mean, double stdDev) {

        def variability = 0.005

        def result = new TreeMap<String, Double>()

        // normal distribution
        def valDistrib = new NormalDistribution(mean, stdDev);
        def perturbationDist = new NormalDistribution(0.0, variability);

        def phase = - Math.PI / 2
        // calculate phase increment for a 24 hours cycle
        def numIncrements = 1440 / intervalMinutes
        def phaseIncr = Math.PI * 2.0 / numIncrements
        def perturbation = 0.0

        def cal = startDate.toCalendar()

        while (cal.getTime().before(endDate)) {
            def val = (Math.sin(phase) +
                    (Math.sin(phase) + Math.sin(phase * 3.0) / 3.0) +
                    Math.sin(phase * 5.0) / 5.0 +
                    Math.sin(phase * 7.0) / 7.0 +
                    Math.sin(phase * 9.0) / 9.0 +
                    Math.sin(phase * 11.0) / 11.0
                    ) / 6.0 / 2.0

            val += Math.sin(perturbation) / 2
            val += valDistrib.sample()

            val = Math.min(1.0, val)
            val = Math.max(0.0, val)

            result.put(cal.getTime(), val)
            cal.add(Calendar.MINUTE, intervalMinutes)
            phase = phase + phaseIncr
            perturbation += perturbationDist.sample()
        }

        return result
    }
}

Grafici dei dati con JFreeChart

Il bello di Groovy è la sua estrema sintesi e una completa libreria di base che permette di creare al volo una interfacce GUI, anche molto sofisticate, utilizzando le librerie Swing standard.

In questo esempio vedremo come:

  • utilizzare la classe DataSimulationUtils per creare i dati di test;
  • creare una GUI con l'utility SwingBuilder di Groovy;
  • generare un grafico con JFreeChart e mostrarlo nella GUI.

DataSimulationUtils_Chart.groovy

@Grapes(
    @Grab(group='org.jfree', module='jfreechart', version='1.0.18')
)

import static javax.swing.WindowConstants.*

import groovy.swing.SwingBuilder

import java.awt.*
import java.text.SimpleDateFormat

import org.jfree.chart.*
import org.jfree.chart.plot.XYPlot
import org.jfree.data.time.*

def dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss");

// Simulation parameters
def startDate = dateFormat.parse("20140101_000000")
def endDate = dateFormat.parse("20140104_000000")
def intervalMinutes = 30

def memData = DataSimulationUtils.simulateServerLoad(
        startDate, endDate, intervalMinutes,
        0.5, 0.005)

def cpuData = DataSimulationUtils.simulateServerLoad(
        startDate, endDate, intervalMinutes,
        0.3, 0.05)

def mem = new TimeSeries("Mem");
def cpu = new TimeSeries("Cpu");

def i = 0
def sumMem = 0
def sumCpu = 0

memData.keySet().each { date ->
    mem.add(new Minute(date), memData.get(date) * 100)
    cpu.add(new Minute(date), cpuData.get(date) * 100)
}

def dataset = new TimeSeriesCollection();
dataset.addSeries(mem)
dataset.addSeries(cpu)

def chart = ChartFactory.createTimeSeriesChart(
        "Server Load",
        "Time",
        "%",
        dataset,
        true, true, false
        )

def plot = chart.getPlot() as XYPlot
plot.getRangeAxis().setRange(0.0, 100.0)

def swing = new SwingBuilder()

swing.edt {
    frame(title: 'Server Load Chart', defaultCloseOperation: EXIT_ON_CLOSE,
    pack: true, show: true, id: "frame") {
        panel(id:'canvas') {
            widget(new ChartPanel(chart))
        }
    }
}

Pronti? Via!

Per lanciare l'esempio è sufficiente, oltre ad avere Groovy installato, posizionarsi nella directory dove sono salvati gli script ed eseguire il comando:

groovy DataSimulationUtils_Chart.groovy

Il risultato sarà molto simile a questo:

Chart: server load simulation

 

 

Linkografia