As the title says... Chat on...

User avatar
By lepido
#45597 Hey,

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... :-) ). Hence my question.

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
Code: Select all-- 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:

Code: Select all-- 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.
User avatar
By lepido
#45603 This is the heap situation after restart and after requesting a non existing page (resulting in the delivery of the 404 message):

=node.heap()
20056
> client disconnected - heap: 17928
=node.heap()
19840
>


As you can see, the garbage collection does some work but is not able to recover all of the memory. I even tried to set client = nil when the "disconnect" event occurs. It, however, didn't change anything. :-(
User avatar
By TerryE
#45770 The client upval in the two send routines is latched by the on() creating a persistent reference. Set client = nil after the close. Also don't do a send and close consecutively as this is against Espressif guidelines. Do
Code: Select alllocal close=client.close
client:send(rec,close)
client=nil

Also doing
Code: Select allrec = rec .. "some bit of HTML
is terribly storage inefficient. Use a single mutiline [[ ]] operator ( and string.format to insert variables if necessary) or table.concat(recs,"\n").