the subject already says it all: Is there a way to do memory profiling on the nodeMCU in LUA?
I have a nodeMCU which interfaces some sensors and provides a small HTTP server with a page to display the sensor readings. Everything works fine, expect that I am constantly loosing memory with every HTTP request. It seems as if I have a memory leak somewhere in my code but I am not able to figure it out by just inspecting the LUA code (I am not an expert in LUA, which makes the thing even worse...
Attached is the source code from the HTTP part which causes the memory leak. The rest of the code does not show any decrease in heap size. I already tried to kick off the garbage collection manually with no success. Even if I wait for a while (as I understand the garbage collection runs with a low priority in the background) the heap size does not increase.
init.lua
-- Webserver
srv=net.createServer(net.TCP,1)
srv:listen(80,function(conn)
conn:on("receive", function(client,request)
-- print(request)
-- check who querried us
local _, _, method, path, vars = string.find(request, "([A-Z]+) (.+)?(.+) HTTP");
if(method == nil)then
_, _, method, path = string.find(request, "([A-Z]+) (.+) HTTP");
end
-- querry with a valid request?
if (method == "GET") then
if (path == "/") then
local w_height = 78.0 - values.distance
-- handle proper request
sendPage(client,w_height,values.temp,values.humi,conf)
elseif (path == "/favicon.ico") then
sendFile(client,"favicon.ico","ico")
elseif (path == "/wl.png") then
sendFile(client,"wl.png","png")
elseif (path == "/temp.png") then
sendFile(client,"temp.png","png")
elseif (path == "/humi.png") then
sendFile(client,"humi.png","png")
elseif (path == "/apple-touch-icon.png") then
sendFile(client,"apple-touch-icon.png","png")
else
send404(client,path)
end
else
print ("NOT A PROPER REQUEST (or one we can not handle - e.g., POST)")
end
client:on("disconnection",function(c)
print("client disconnected - heap: " .. node.heap())
collectgarbage()
end)
end)
end)and the HTTP functions:
-- Helper function to determine proper HTTP MIME type text
function getMimeType(ext)
-- A few MIME types. Keep list short. If you need something that is missing, let's add it.
local mt = {css = "text/css", html = "text/html", ico = "image/x-icon", jpeg = "image/jpeg", jpg = "image/jpeg", js = "application/javascript", json = "application/json", png = "image/png", xml = "text/xml"}
if mt[ext] then return mt[ext] else return "text/plain" end
end
-- Helper function to send 404
function send404(client, path)
local buf = ""
buf = buf .. "HTTP/1.1 404 Not found\n\n"
buf = buf .. "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n"
buf = buf .. "<html><head>\n"
buf = buf .. "<title>404 Not Found</title>\n"
buf = buf .. "</head><body>\n"
buf = buf .. "<h1>Not Found</h1>\n"
buf = buf .. "<p>The requested URL " .. path .. " was not found on this server.</p>\n"
buf = buf .. "</body></html>\n"
client:send(buf)
client:close();
buf = nil
end
-- send file function
function sendFile(client, fileName, extension)
-- get file size
local fileSize = 0
for name,size in pairs(file.list()) do
if (name == fileName) then fileSize = size end
end
if (fileSize == 0) then
send404(client,"/" .. fileName)
return
end
local bytesSent = 0
local chunkSize = 1024
local response = {}
response[#response + 1] = "HTTP/1.1 200\nContent-Type: " .. getMimeType(extension) .. "\nCache-Control: public, max-age=3600\nContent-Length: " .. fileSize .. "\nConnection: close\n\n"
-- sends and removes the first element from the 'response' table
local function send()
if (#response > 0) then
client:send(table.remove(response, 1))
-- add file chunks to the response table
-- NOTE: in that way not the full file is stored in memory -> would result in "out of mem" issues
if (bytesSent < fileSize) then
file.open(fileName)
file.seek("set", bytesSent)
response[#response + 1] = file.read(chunkSize)
file.close()
bytesSent = bytesSent + chunkSize
end
else
client:close()
response = nil
end
end
-- triggers the send() function again once the first chunk of data was sent
client:on("sent", send)
send()
end
-- send webpage function
function sendPage(client, wl, temp, humi, conf)
-- assemble response
local response = {}
response[#response + 1] = "HTTP/1.1 200 OK\n\n"
response[#response + 1] = "<!DOCTYPE HTML>\n"
response[#response + 1] = "<html>\n"
---
--- <snip> couple of HTML lines
---
response[#response + 1] = "</html>\n"
-- sends and removes the first element from the 'response' table
local function send()
if #response > 0
then client:send(table.remove(response, 1))
else
client:close()
response = nil
end
end
-- triggers the send() function again once the first chunk of data was sent
client:on("sent", send)
send()
end
To give you an idea of the magnitude of the leakage:
- 404 page results in roughly 200 bytes gone
- temp.png (a 2695 byte file) results in a reduced heap size by roughly 600 bytes
I am greatful for any hints on how to tackle this problem.