Logic Machine Forum
Reading trends from RRD in Lua - Printable Version

+- Logic Machine Forum (https://forum.logicmachine.net)
+-- Forum: LogicMachine eco-system (https://forum.logicmachine.net/forumdisplay.php?fid=1)
+--- Forum: Scripting (https://forum.logicmachine.net/forumdisplay.php?fid=8)
+--- Thread: Reading trends from RRD in Lua (/showthread.php?tid=719)

Pages: 1 2


RE: Reading trends from RRD in Lua - Gadjoken - 29.08.2018

Hello everyone
is it possible to have a CSV file with all curves with this method?

Thanks.
BR


RE: Reading trends from RRD in Lua - buuuudzik - 29.08.2018

For sure but you must change a script because there are 2 things:
1) how connect 2 trends with different resolution
2) be aware about the size of generated .csv (because of email limits and because of LM CPU power and memory)

This is why I'm not created the script for doing all trends in one .csv.

I think that better is create such script which generate all trends one by one with some pause between each email. And then you have all scripts and e.g. you can connect them via excel(if they are not too big) or via nodejs script or some windows program for such things.


RE: Reading trends from RRD in Lua - Gadjoken - 29.08.2018

(29.08.2018, 08:49)buuuudzik Wrote: For sure but you must change a script because there are 2 things:
1) how connect 2 trends with different resolution
2) be aware about the size of generated .csv (because of email limits and because of LM CPU power and memory)

This is why I'm not created the script for doing all trends in one .csv.

I think that better is create such script which generate all trends one by one with some pause between each email. And then you have all scripts and e.g. you can connect them via excel(if they are not too big) or via nodejs script or some windows program for such things.

Indeed the CPU can be used too much.
After making a backup there is no possibility to recover all of this data in a file?
I would like to retrieve all the data on 65 curves (same resolution) for 6 months ... it's really a lot by mail

Thank you for Reply.
BR



RE: Reading trends from RRD in Lua - buuuudzik - 06.07.2022

I've updated a trends_pro library to allow simple modifying its data and adding more calculated column.

Code:
-- This function prepare csv table content from data and passed headers
function generateCSVTable(rows, headers)
  if #rows > 0 then
    if #rows[1] > #headers then
      error("There should be " .. tostring(#rows[1]) .. " header columns!")
    end
  end
 
  -- Prepare .csv file
  for k,v in ipairs(rows) do     
      rows[k] = table.concat(v, ",")
    end

  csv = table.concat(headers, ",") .. "\r\n" .. table.concat(rows, "\r\n")
  return csv
end

-- Allows to add columns based on a current row or a previous one to current row
function addColumnsToRow(row, prevRow, columnGetters)
  for k,getter in ipairs(columnGetters) do
    table.insert(row, getter(row, prevRow))
  end
  return row
end

-- Allows to add columns based on a current row or a previous one to every row
function addColumnsToRows(rows, columnGetters)
  local prev = nil
  for k,row in ipairs(rows) do     
    rows[k] = addColumnsToRow(row, prevRow, columnGetters)
    prev = row
    end
  return rows
end

function copyObject(obj)
  return json.decode(json.encode(obj))
end

-- This function prepare export table and .csv file from defined range
function trend_export(name, dates, mode, timestampType)
 
  require('trends')

  -- detect timeShift
  function detectTimeShift(t0, t1)
  t0_isdst, t1_isdst = os.date('*t', t0).isdst, os.date('*t', t1).isdst

    if (t0_isdst ~= t1_isdst) then
      if (t1_isdst) then
        -- change to summertime
        return 1
      else
        -- change to wintertime
        return 2
      end
    else
      return false
    end
  end
 
  require("json")
  dates = copyObject(dates)

  -- Convert word "now" in "start" to data table
  if type(dates['start']) == "string" then
    if dates['start'] == "now" then dates['start'] = os.date("*t", os.time())
    elseif dates['start'] == "yesterday" then dates['start'] = os.date("*t", os.time()-24*3600)
    elseif dates['start'] == "week_ago" then dates['start'] = os.date("*t", os.time()-7*24*3600)
    elseif dates['start'] == "month_ago" then dates['start'] = os.date("*t", os.time()-30*24*3600)
    elseif dates['start'] == "year_ago" then dates['start'] = os.date("*t", os.time()-365*24*3600)
    end
  end

  -- Convert word "now" in "end" to data table
  if type(dates['end']) == "string" then
    if dates['end'] == "now" then dates['end'] = os.date("*t", os.time())
    elseif dates['end'] == "yesterday" then dates['end'] = os.date("*t", os.time()-24*3600)
    elseif dates['end'] == "week_ago" then dates['end'] = os.date("*t", os.time()-7*24*3600)
    elseif dates['end'] == "month_ago" then dates['end'] = os.date("*t", os.time()-30*24*3600)
    elseif dates['end'] == "year_ago" then dates['end'] = os.date("*t", os.time()-365*24*3600)
    end
  end         

  -- Start and end in unix time
  starttime = os.time(dates['start'])
  endtime = os.time(dates['end'])

  -- Invert "start" and "end" when "end" is smaller than "start"
  if starttime > endtime then
    _ = dates['start']
    dates['start'] = dates['end']
    dates['end'] = _
  end

  -- Read trend resolution from DB
  resolution = db:getlist("SELECT resolution*60 FROM trends WHERE name='" .. name .. "'")[1]

  -- Prepare a table with the data which include the whole data from specified range
  if resolution then
      -- Expand a range with a one more day if there is some data in the last day
    if (os.date("%H", endtime)*3600 + os.date("%M", endtime)*60) >= resolution then
      dates['end'].day = dates['end'].day + 1
      raw_data = trends.fetch(name, dates, resolution)
    else
      raw_data = trends.fetch(name, dates, resolution)
    end

    start = 1
    data = {}
    start_timestamp = os.time(dates['start']) + resolution
    -- Cut the data and prepare a table with the data only from specified range
    offset = 0
    skip = 0
    k = 1
    while k <= #raw_data do
      v = raw_data[k]
     
      if k == 1 then last_timestamp = start_timestamp else last_timestamp = current_timestamp end
      current_timestamp = start_timestamp + (k-1) * resolution + offset
           
      timeShiftStatus = detectTimeShift(last_timestamp, current_timestamp)
      if timeShiftStatus then
        if timeShiftStatus == 1 then -- winter->summer
          skip = 6 -- skip 1 hour empty data
          offset = offset - 3600
        elseif timeShiftStatus == 2 then -- summer->winter
          offset = offset + 3600
        end
      end
     
      -- Add to new table only records from specified range
      if current_timestamp >= starttime and current_timestamp <= endtime then
        if timestampType == 'unixepoch' then data[start] = {current_timestamp, v}
        else data[start] = {os.date('%Y.%m.%d %H:%M:%S', current_timestamp), v} end
        start = start+1
      end

      k = k + 1 + skip
      skip = 0
    end
    
    if mode == 'csv' then
      -- Prepare .csv file
      csv = generateCSVTable(data, { "timestamp", "value" })
      return name, csv, resolution
    elseif mode == 'table' then
      return name, data, resolution
    end
  else
      alert('There is no trend with such a name')
  end
end

-- Merge values from multiple trends in one table
function mergeRows(baseRows, rows)
  for k,row in ipairs(baseRows) do
    if k < 3 then log(k, "base", row) end
    table.insert(baseRows[k], rows[k][2])
    if k < 3 then log(k, "next", rows[k]) end
  end
  return baseRows
end

function getMultipleColumnHeader(names)
  local columns = { "timestamp" }
  for k,name in ipairs(names) do
    table.insert(columns, name)
  end
  return columns
end

-- This function prepare export table and .csv file from defined range
function multiple_trend_export(names, dates, mode, timestampType)
  local baseRows = {}
 
  for k,name in ipairs(names) do
    local trend_name, rows = trend_export(name, dates, 'table', timestampType)
    if #baseRows == 0 then
      baseRows = rows
    else
      baseRows = mergeRows(baseRows, rows)
    end
  end
 
  if mode == 'csv' then
    -- Prepare .csv file
    csv = generateCSVTable(baseRows, getMultipleColumnHeader(names))
    return names, csv, resolution
  elseif mode == 'table' then
    return names, baseRows, resolution
  end
end

What's changed:
I've added these function:

addColumnsToRows(rows, columnGetters) - which will allow to add more columns and calculate values based on current row and a previous one

generateCSVTable(rows, headers) - which will produce a csv table content with named headers which should have same length as data rows

multiple_trend_export(names, dates, mode, timestampType) - which can be used for generating a table with multiple trends

Usage example:
Code:
require('user.trends_pro')


dates = {}
dates['start'] = 'yesterday'
dates['end'] = 'now'

name, rows = trend_export("Salon_Temperatura", dates, 'table', 'unixepoch')

addColumnsToRows(rows, {
    function(row, prevRow)
      local value = row[2]
     
      local prevValue = 0
      if prevRow then prevValue = prevRow[2] end
     
     
      if value > 3 or prevValue > 3 then
        return "YES"
      else
        return "NO"
      end
    end
})

csv = generateCSVTable(rows, { 'timestamp', 'value', 'isBiggerThan3' })

log(name, csv)


-- Generating a table with multiple trends in same rows
names, rows = multiple_trend_export({"Kitchen_Temp", "Outdoor_Temp"}, dates, 'csv', 'unixepoch')

log(names, rows)



RE: Reading trends from RRD in Lua - iJAF - 07.07.2022

Hi!

Thanks for your effort with this Lib ! I is very useful !

I have tried it and noticed a small difference from the Export from Visualization and the Export from the Script.

The resolution for this trends I have tested are 30min.

There is an offset of 30 minutes in the timestamp:


Code:
-- This the timewindow I have used to test

dates = {}
dates['start'] = { year = 2022, month = 6, day = 1,  hour = 0, min = 0, sec = 0}
dates['end']   = { year = 2022, month = 7, day = 1,  hour = 0, min = 0, sec = 0}