mir ist das Aufräumen auf den Wecker gegangen und so richtig hat es auch nicht funktioniert. Deshalb hab ich für mich mal ein Script geschrieben, das einige Herausforderungen für mich behebt.
Herausforderung der Datenbehandlung:
* Datenbankwachstum
* Ladegeschwindigkeit bei grossen Zeiträumen
* Unterschiedliche Zeiträume die in der Datenbank verbleiben sollen
* keine Komprimierung möglich
Mir war es wichtig das ganze fein zu konfigurieren, heißt individuelle Zeiträume, individuelle Wert-Ermittlung bei Komprimierung.
Derzeit ist das ganz noch im Teststadium, bin auf eure Kommentare aber sehr gespannt und Achtung bei falscher Konfiguration können sehr leicht Daten gelöscht werden.
Natürlich verliert man mit der Komprimierung etwas an Details, das aber bei vielen Werten ab einen bestimmten Zeitpunkt nicht mehr für mich wichtig ist. Dafür gewinnt man Ladegeschwindigkeit bei z.b. Jahresvergleiche!
Das folgende Script kann ohne Änderung zum direkten Testen verwendet werden und läuft so im Test-Modus.
Mit der Variable testRun=false kann ihn den Schreib-Modus gewechselt werden und damit werden Daten gelöscht und / oder komprimiert.
Mit der Variable config kann man die gewünschten Datenpunkte konfigurieren:
Parameter 1 z.b. "*DutyCycle*" ist der Displayname und es können * (Wildcards verwendet werden)
Parameter 2 z.b. 365 ist die Anzahl der Tage die in der Datenbank verbleiben sollen, 365 wäre hier ein Jahr, alle Daten drüber ob komprimiert oder nicht werden gelöscht (es gibt auch volle Monate oder volle Jahre "5M" oder "3Y" )
Parameter 3 z.b. 10 ist hier die Anzal der Tage nachdem die Werte komprimiert werden
Parameter 4 z.b. 60*60*1 bestimmt den Komprimierungszeitraum das wäre 1 Std. (gängige Werte für mich 60*60*2 oder 60*60*24)
Parameter 5 z.b. "max" bestimmt die Art der Komprimierung "max" = maximaler Wert in 1 Std. ebenso gibt es noch:
"min" - Minimuwert in 1Std.
"avg" - Durchschnittswert verwende ich bei Temperaturwerten oder Feuchtigkeitswerten
"first" - erster Wert im Komprimierungszeitraum verwende ich bei Zählern
"last" - letzter Wert im Komprimierungszeitraum
"integral" - Durchschittswert mit Zeitfaktor, funktioniert aber nur bei vielen Wert in einem Komprimierungszeitraum spricht Tagesrdurchschnitt
Viel Spaß beim Testen und auf eure Erfahrungen und Verbesserungen bin ich schon gespannt
LG wak
Code: Alles auswählen
// Datenreihen löschen und komprimieren, V1.0
// min, max, first, last or avg as value for compression
def config = [
["*DUTYCYCLE*" , 365, 10, 60*60*1, "max" ], // Std. Max-Wert nach 10 Tagen und nach 365 Tagen löschen
["*CARRIER_SENSE*" , 365, 10, 60*60*1, "max" ], // Std. Max-Wert nach 10 Tagen und nach 365 Tagen löschen
["*HUMIDITY" ,"3Y", 30, 60*60*2, "avg" ], // 2 Std. Durchschnitt nach 30 Tagen und nach 3 vollen Jahren löschen
["*TEMPERATURE" ,"3Y", 30, 60*60*2, "avg" ], // 2 Std. Durchschnitt nach 30 Tagen und nach 3 vollen Jahren löschen
["*ENERGY_COUNTER" ,"5Y", 30, 60*60*2, "first" ], // 2 Std. bei Zähler 1. Wert nach 30 Tagen und nach 5 vollen Jahren
]
// Testlauf durchführen? Bei einem Testlauf wird die Datenbank nicht verändert.
// (Ja: true, Nein: false)
def testRun = true
// *** Skript ***
def dateFormat = "yyyy-MM-dd" // standard Timestamp Format
def jetzt = new Date() // aktuellen StartTimeStamp speichern für Berechnungen
// 24 Bits für Flag berechnen
def int bit24 = Math.pow(2,23) // 8388608;
config.each { conf ->
// Löschdatum 5Y oder 3M auf Tage aktuell umrechnen mit 1. des Monats
def orgConf = conf[1]
if (conf[1].getClass() == String ) {
if (conf[1].endsWith("Y") // volle Jahre
and conf[1].substring(0, conf[1].length() - 1).isInteger()
and conf[1].substring(0, conf[1].length() - 1).toInteger() > 0 ) {
def dateTmp = new Date(year: (jetzt.getYear() - conf[1].substring(0, conf[1].length() - 1).toInteger()) , month: 0, date: 1, hours: 0, minutes: 0, seconds: 0)
conf[1] = jetzt - dateTmp // berechnete Löschtage setzen für xY
} else if (conf[1].endsWith("M") // volle Monate
and conf[1].substring(0, conf[1].length() - 1).isInteger()
and conf[1].substring(0, conf[1].length() - 1).toInteger() > 0 ) {
def cal=Calendar.getInstance().clearTime()
cal.add(Calendar.MONTH, conf[1].substring(0, conf[1].length() - 1).toInteger() * -1);
cal.set(Calendar.DAY_OF_MONTH, 1);
conf[1] = jetzt - cal.getTime() // berechnete Löschtage setzen für xM
}
def delDate = jetzt - conf[1]
delDate.clearTime()
println conf[0].padRight(40) + " Löschdatum berechnet " + delDate.format(dateFormat) + " " + orgConf + " " + conf[1]
}
// wildcards in regex umbauen
def txtResult = new StringBuffer()
conf[0].each { ch ->
switch (ch) {
case '*':
// Single '*' matches single dir/file; Double '*' matches sequence of zero or more dirs/files
txtResult << /[^\/]*/
break
case '?':
// Any character except the normalized file separator ('/')
txtResult << /[^\/]/
break
case ['$', '|', '[', ']', '(', ')', '.', ':', '{', '}', '\\', '^', '+']:
txtResult << '\\' + ch
break
default: txtResult << ch
}
}
conf[0] = txtResult.toString().toUpperCase()
// println dptxt[0]
}
def comprFactor=0
def dpCount=[]
def summeDel=0
def summeComp=0
def summetotal=0
def lastDPState
database.dataPoints.sort{ it.displayName.toUpperCase() }.each { dp ->
// check dp.displayName wird in config gefunden
def found=null
def delBegin=null
def delEnd=null
def komprBegin=null
def cntNew
// Datenpunkt in Liste über REGEX suchen
config.find { con ->
if (dp.displayName.toUpperCase()==~con[0]) {
// println con[0] + " -> " + dp.displayName // check REGEX Regeln
found = con
// beim ersten gefunden, merken und Schleife verlassen
return true // break
}
}
if (found) {
// Datenlöschung *******
if (found[1] > 10) {
cntNew=0
// Löschzeitraum bestimmen
delBegin=database.getFirstTimestamp(dp)
delEnd=jetzt-found[1] // found[1] = 2 spalte aus der Konfiguration Tabelle am Anfang
delEnd.clearTime() // Zeit auf 00:00:00 stellen
if (testRun) {
cntNew=database.getCount(dp, delBegin , delEnd)
if (cntNew>0) println "$dp.displayName: $cntNew werden gelöscht! von: " + delBegin.format(dateFormat) + " bis: " + delEnd.format(dateFormat) + " (Testlauf)"
} else {
cntNew=database.deleteTimeSeries(dp, delBegin , delEnd)
if (cntNew>0) println "$dp.displayName: $cntNew gelöscht! von: " + delBegin.format(dateFormat) + " bis: " + delEnd.format(dateFormat)
}
summeDel=summeDel+cntNew
}
// Datenkomprimierung ******
if (found[2] > 5) {
// Komprimierungs Zeitraum bestimmen
if (delEnd) {
komprBegin=delEnd
} else {
komprBegin=database.getFirstTimestamp(dp)
}
def komprEnd=jetzt-found[2]
komprEnd.clearTime() // Zeit auf 00:00:00 stellen
comprFactor = 1000*found[3] // Sekunden von Konfigurationtabelle * 1000 auf Millisekungen
def comprValue = found[4] // von Konfigurationtalle "AVG", "MIN", "MAX", ...
def cnt
// Zeitreihe holen
def ts=database.getTimeSeriesRaw(dp, komprBegin, komprEnd)
// erste Zeitreihe ohne komprimierung finden
def komprBeg = komprEnd
ts.find { pv ->
if (!(pv.state&bit24)) {
komprBeg = pv.timestamp // neues begin ermittelt
return true // break
}
}
//println "Erste Timestamp ohne KompFlag: $komprBeg"
// Zeitreihe holen neu ohne bereits komprimierte
ts=database.getTimeSeriesRaw(dp, komprBeg , komprEnd)
cnt=ts.size
// Statistik berechnen
def duration=komprEnd.time - komprBeg.time
def min=Double.POSITIVE_INFINITY
def max=Double.NEGATIVE_INFINITY
def integr=0
def intsum=0
def intwert=0
def anzahl=0
def summe=0
def lastTime=0
def thisTime=0
def firstValue=0
def lastValue=0
def avg=0
def previous
def comprPosible=false
// neue komprimierte Zeitreihe erstellen
def timeSeries=new TimeSeries(dp)
ts.each { pv ->
thisTime=new Date( ( ( Math.floor(pv.timestamp.time/comprFactor)*comprFactor) as long) )
if (thisTime!=lastTime) {
if (lastTime!=0) {
duration=lastTime.time - thisTime.time
// Durchnitt ist Integral/Zeitbereichslänge in Millisekunden.
// def avg=integr/duration
avg = Math.round(summe / anzahl * 10) / 10
if (intsum!=0) intwert = Math.round(integr / intsum * 10) / 10
// println lastTime.format(dateFormat) + " Anzahl: $anzahl, Minimum: $min, Maximum: $max, Integral: $intwert, Durchschnitt: $avg, Duration: $duration, First: $firstValue, Last: $lastValue"
switch(comprValue) {
case "min":
timeSeries.add(new ProcessValue(lastTime, min, bit24|lastDPState ))
break;
case "max":
timeSeries.add(new ProcessValue(lastTime, max, bit24|lastDPState ))
break;
case "first":
timeSeries.add(new ProcessValue(lastTime, firstValue, bit24|lastDPState ))
break;
case "last":
timeSeries.add(new ProcessValue(lastTime, lastValue, bit24|lastDPState ))
break;
case "avg":
timeSeries.add(new ProcessValue(lastTime, avg, bit24|lastDPState ))
break;
case "integral":
timeSeries.add(new ProcessValue(lastTime, avg, bit24|lastDPState ))
break;
}
if (anzahl>1) comprPosible=true
}
min=pv.value
max=pv.value
anzahl=0
summe=0
integr=0
intsum=0
intwert=pv.value
firstValue=pv.value
lastDPState=pv.state
lastTime=thisTime
}
if (pv.value<min) min=pv.value
if (pv.value>max) max=pv.value
if (previous!=null) {
// Teilintegral berechnen: Messwert*Millisekunden
integr+=previous.value*(pv.timestamp.time-previous.timestamp.time)
intsum+=(pv.timestamp.time-previous.timestamp.time)
}
lastValue=pv.value
anzahl=anzahl+1
summe=summe+pv.value
previous=pv
}
if (lastTime!=0) {
duration=lastTime.time - thisTime.time
// Durchnitt ist Integral/Zeitbereichslänge in Millisekunden.
// def avg=integr/duration
avg = Math.round(summe / anzahl * 10) / 10
if (intsum!=0) intwert = Math.round(integr / intsum * 10) / 10
// println lastTime.format(dateFormat) + " Anzahl: $anzahl, Minimum: $min, Maximum: $max, Integral: $intwert, Durchschnitt: $avg, Duration: $duration, First: $firstValue, Last: $lastValue"
switch(comprValue) {
case "min":
timeSeries.add(new ProcessValue(lastTime, min, bit24|lastDPState ))
break;
case "max":
timeSeries.add(new ProcessValue(lastTime, max, bit24|lastDPState ))
break;
case "first":
timeSeries.add(new ProcessValue(lastTime, firstValue, bit24|lastDPState ))
break;
case "last":
timeSeries.add(new ProcessValue(lastTime, lastValue, bit24|lastDPState ))
break;
case "avg":
timeSeries.add(new ProcessValue(lastTime, avg, bit24|lastDPState ))
break;
case "integral":
timeSeries.add(new ProcessValue(lastTime, avg, bit24|lastDPState ))
break;
}
if (anzahl>1) comprPosible=true
}
if (comprPosible) {
def comprFactorSek = comprFactor/1000
println "$dp.displayName: Konfig " + komprBegin.format(dateFormat) + " bis: " + komprEnd.format(dateFormat) + " Zeitraum Sek: $comprFactorSek Wert: $comprValue"
cntNew=0
if (testRun) {
println "$dp.displayName: komprimiert! $cnt -> $timeSeries.size von: " + komprBeg.format(dateFormat) + " bis: " + komprEnd.format(dateFormat) + " (Testlauf)"
cntNew=timeSeries.size
} else {
println "$dp.displayName: komprimiert! $cnt -> $timeSeries.size von: " + komprBeg.format(dateFormat) + " bis: " + komprEnd.format(dateFormat)
cntNew=database.replaceTimeSeries(dp, timeSeries, komprBeg, komprEnd)
}
summeComp=summeComp+ (cnt-cntNew)
summetotal=summetotal+cnt
}
}
}
}
println "Summe Datenzeilen gelöscht: $summeDel"
def delSum = summetotal - summeComp
println "Summe Datenzeilen komprimiert: $summeComp von $summetotal -> $delSum"
use(groovy.time.TimeCategory) {
def duration = new Date() - jetzt
print "Laufzeit: Std.: ${duration.hours}, Min.: ${duration.minutes}, Sek.: ${duration.seconds}"
}