perf: enable local cache and use `tablepool` to reuse the temporary table (#49)

diff --git a/.luacheckrc b/.luacheckrc
new file mode 100644
index 0000000..6b3ce04
--- /dev/null
+++ b/.luacheckrc
@@ -0,0 +1,4 @@
+std = "ngx_lua"
+unused_args = false
+redefined = false
+max_line_length = 100
diff --git a/README.md b/README.md
index d3f180a..d8606c3 100644
--- a/README.md
+++ b/README.md
@@ -33,10 +33,16 @@
         metadata_buffer:set('serviceInstanceName', 'User Service Instance Name')
         -- type 'boolean', mark the entrySpan include host/domain
         metadata_buffer:set('includeHostInEntrySpan', false)
-        
+
         -- set random seed
         require("skywalking.util").set_randomseed()
         require("skywalking.client"):startBackendTimer("http://127.0.0.1:8080")
+
+        -- If there is a bug of this `tablepool` implementation, we can
+        -- disable it in this way
+        -- require("skywalking.util").disable_tablepool()
+
+        skywalking_tracer = require("skywalking.tracer")
     }
 
 
@@ -52,9 +58,9 @@
                 --
                 -- Currently, we can not have the upstream real network address
                 ------------------------------------------------------
-                require("skywalking.tracer"):start("upstream service")
+                skywalking_tracer:start("upstream service")
                 -- If you want correlation custom data to the downstream service
-                -- require("skywalking.tracer"):start("upstream service", {custom = "custom_value"})
+                -- skywalking_tracer:start("upstream service", {custom = "custom_value"})
             }
 
             -- Target upstream service
@@ -62,12 +68,12 @@
 
             body_filter_by_lua_block {
                 if ngx.arg[2] then
-                    require("skywalking.tracer"):finish()
+                    skywalking_tracer:finish()
                 end
             }
 
             log_by_lua_block {
-                require("skywalking.tracer"):prepareForReport()
+                skywalking_tracer:prepareForReport()
             }
         }
     }
diff --git a/examples/nginx.conf b/examples/nginx.conf
index 0039bc8..3c6da29 100644
--- a/examples/nginx.conf
+++ b/examples/nginx.conf
@@ -40,11 +40,19 @@
         -- type 'boolean', mark the entrySpan include host/domain
         metadata_buffer:set('includeHostInEntrySpan', false)
 
+        -- set randomseed
         require("skywalking.util").set_randomseed()
+
         require("skywalking.client"):startBackendTimer("http://127.0.0.1:8080")
 
         -- Any time you want to stop reporting metrics, call `destroyBackendTimer`
         -- require("skywalking.client"):destroyBackendTimer()
+
+        -- If there is a bug of this `tablepool` implementation, we can
+        -- disable it in this way
+        -- require("skywalking.util").disable_tablepool()
+
+        skywalking_tracer = require("skywalking.tracer")
     }
 
 
@@ -62,21 +70,21 @@
                 --
                 -- Currently, we can not have the upstream real network address
                 ------------------------------------------------------
-                require("skywalking.tracer"):start("upstream service")
+                skywalking_tracer:start("upstream service")
                 -- If you want correlation custom data to the downstream service
-                -- require("skywalking.tracer"):start("upstream service", {custom = "custom_value"})
+                -- skywalking_tracer:start("upstream service", {custom = "custom_value"})
             }
 
             proxy_pass http://127.0.0.1:8080/tier2/lb;
 
             body_filter_by_lua_block {
                 if ngx.arg[2] then
-                    require("skywalking.tracer"):finish()
+                    skywalking_tracer:finish()
                 end
             }
 
             log_by_lua_block {
-                require("skywalking.tracer"):prepareForReport()
+                skywalking_tracer:prepareForReport()
             }
         }
 
@@ -84,19 +92,19 @@
             default_type text/html;
 
             rewrite_by_lua_block {
-                require("skywalking.tracer"):start("backend service")
+                skywalking_tracer:start("backend service")
             }
 
             proxy_pass http://127.0.0.1:8080/backend;
 
             body_filter_by_lua_block {
                 if ngx.arg[2] then
-                    require("skywalking.tracer"):finish()
+                    skywalking_tracer:finish()
                 end
             }
 
             log_by_lua_block {
-                require("skywalking.tracer"):prepareForReport()
+                skywalking_tracer:prepareForReport()
             }
         }
 
diff --git a/lib/skywalking/client.lua b/lib/skywalking/client.lua
index 783cf64..ed22699 100644
--- a/lib/skywalking/client.lua
+++ b/lib/skywalking/client.lua
@@ -17,6 +17,7 @@
 local Const = require('skywalking.constants')
 
 
+local ngx = ngx
 local SEGMENT_BATCH_COUNT = 100
 
 local Client = {
@@ -34,7 +35,6 @@
     local check
 
     local log = ngx.log
-    local DEBUG = ngx.DEBUG
     local ERR = ngx.ERR
 
     check = function(premature)
@@ -118,7 +118,6 @@
 -- Ping the backend to update instance heartheat
 function Client:ping(metadata_buffer, backend_http_uri)
     local log = ngx.log
-    local DEBUG = ngx.DEBUG
     local ERR = ngx.ERR
 
     local serviceName = metadata_buffer:get('serviceName')
@@ -153,7 +152,6 @@
 -- Send segemnts data to backend
 local function sendSegments(segmentTransform, backend_http_uri)
     local log = ngx.log
-    local DEBUG = ngx.DEBUG
     local ERR = ngx.ERR
 
     local http = require('resty.http')
@@ -184,7 +182,6 @@
 function Client:reportTraces(metadata_buffer, backend_http_uri)
     local log = ngx.log
     local DEBUG = ngx.DEBUG
-    local ERR = ngx.ERR
 
     local queue = ngx.shared.tracing_buffer
     local segment = queue:rpop(Const.segment_queue)
diff --git a/lib/skywalking/segment.lua b/lib/skywalking/segment.lua
index 77ccea6..f379103 100644
--- a/lib/skywalking/segment.lua
+++ b/lib/skywalking/segment.lua
@@ -18,6 +18,7 @@
 -- Segment represents a finished tracing context
 -- Including all information to send to the SkyWalking OAP server.
 local Span = require('skywalking.span')
+local Util = require('skywalking.util')
 
 local _M = {}
 -- local Segment = {
@@ -42,17 +43,16 @@
 
 -- Return SegmentProtocol
 function _M.transform(segment)
-    local segmentBuilder = {}
+    local segmentBuilder = Util.tablepool_fetch()
     segmentBuilder.traceId = segment.trace_id
     segmentBuilder.traceSegmentId = segment.segment_id
     segmentBuilder.service = segment.service
     segmentBuilder.serviceInstance = segment.service_instance
 
-    segmentBuilder.spans = {}
+    segmentBuilder.spans = Util.tablepool_fetch()
 
     if segment.spans ~= nil and #segment.spans > 0 then
-        for i, span in ipairs(segment.spans)
-        do
+        for _, span in ipairs(segment.spans) do
             segmentBuilder.spans[#segmentBuilder.spans + 1] = Span.transform(span)
         end
     end
diff --git a/lib/skywalking/span.lua b/lib/skywalking/span.lua
index 3882d6c..ce52b01 100644
--- a/lib/skywalking/span.lua
+++ b/lib/skywalking/span.lua
@@ -18,6 +18,7 @@
 local spanLayer = require("skywalking.span_layer")
 local Util = require('skywalking.util')
 local SegmentRef = require("skywalking.segment_ref")
+local table = table
 
 local CONTEXT_CARRIER_KEY = 'sw8'
 
@@ -76,7 +77,7 @@
                 -- If current trace id is generated by the context, in LUA case, mostly are yes
                 -- use the ref trace id to override it, in order to keep trace id consistently same.
                 context.internal.addRefIfFirst(context.internal, ref)
-                span.refs[#span.refs + 1] = ref
+                table.insert(span.refs, ref)
             end
         end
     end
@@ -143,13 +144,13 @@
 end
 
 function _M.newNoOP()
-    return {
-        layer = spanLayer.NONE,
-        is_entry = false,
-        is_exit = false,
-        error_occurred = false,
-        is_noop = true
-    }
+    local obj = Util.tablepool_fetch()
+    obj.layer = spanLayer.NONE
+    obj.is_entry = false
+    obj.is_exit = false
+    obj.error_occurred = false
+    obj.is_noop = true
+    return obj
 end
 
 ---- All belowing are instance methods
@@ -240,28 +241,29 @@
     end
 
     if span.logs == nil then
-        span.logs = {}
+        span.logs = Util.tablepool_fetch()
     end
 
-    local logEntity = {time = timestamp, data = keyValuePairs}
-    span.logs[#span.logs + 1] = logEntity
+    local logEntity = Util.tablepool_fetch()
+    logEntity.time = timestamp
+    logEntity.data = keyValuePairs
 
+    table.insert(span.logs, logEntity)
     return span
 end
 
 -- Return SpanProtocol
 function _M.transform(span)
-    local spanBuilder = {}
+    local spanBuilder = Util.tablepool_fetch("sw_spanBuilder", 0, 32)
     spanBuilder.spanId = span.span_id
     spanBuilder.parentSpanId = span.parent_span_id
     spanBuilder.startTime = span.start_time
     spanBuilder.endTime = span.end_time
     -- Array of RefProtocol
     if #span.refs > 0 then
-        spanBuilder.refs = {}
-        for i, ref in ipairs(span.refs)
-        do
-            spanBuilder.refs[#spanBuilder.refs + 1] = SegmentRef.transform(ref)
+        spanBuilder.refs = Util.tablepool_fetch("sw_spanBuilder_refs", 4, 0)
+        for _, ref in ipairs(span.refs) do
+            table.insert(spanBuilder.refs, SegmentRef.transform(ref))
         end
     end
 
diff --git a/lib/skywalking/span_layer.lua b/lib/skywalking/span_layer.lua
index fcf0baa..6d05411 100644
--- a/lib/skywalking/span_layer.lua
+++ b/lib/skywalking/span_layer.lua
@@ -1,19 +1,19 @@
--- 
+--
 -- Licensed to the Apache Software Foundation (ASF) under one or more
 -- contributor license agreements.  See the NOTICE file distributed with
 -- this work for additional information regarding copyright ownership.
 -- The ASF licenses this file to You under the Apache License, Version 2.0
 -- (the "License"); you may not use this file except in compliance with
 -- the License.  You may obtain a copy of the License at
--- 
+--
 --    http://www.apache.org/licenses/LICENSE-2.0
--- 
+--
 -- Unless required by applicable law or agreed to in writing, software
 -- distributed under the License is distributed on an "AS IS" BASIS,
 -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
--- 
+--
 
 local Layer = {
     NONE = {name = "Unknown", value=0},
diff --git a/lib/skywalking/tracer.lua b/lib/skywalking/tracer.lua
index 9e5a3cd..88941b2 100644
--- a/lib/skywalking/tracer.lua
+++ b/lib/skywalking/tracer.lua
@@ -18,7 +18,7 @@
 local TC = require('skywalking.tracing_context')
 local Layer = require('skywalking.span_layer')
 local Segment = require('skywalking.segment')
-local Util = require('skywalking.util')
+local Util = require("skywalking.util")
 local Const = require('skywalking.constants')
 local json = require('cjson.safe')
 
@@ -39,16 +39,17 @@
     -- Constant pre-defined in SkyWalking main repo
     -- 6000 represents Nginx
 
-    local contextCarrier = {}
+    local req_uri = ngx.var.uri
+
+    local contextCarrier = Util.tablepool_fetch("sw_contextCarrier")
     contextCarrier["sw8"] = ngx.var.http_sw8
     contextCarrier["sw8-correlation"] = ngx.var.http_sw8_correlation
-
     local time_now = ngx.now() * 1000
     local entrySpan
     if (includeHostInEntrySpan)  then
-        entrySpan = TC.createEntrySpan(tracingContext, ngx.var.host .. ngx.var.uri, nil, contextCarrier)
+        entrySpan = TC.createEntrySpan(tracingContext, ngx.var.host .. req_uri, nil, contextCarrier)
     else
-        entrySpan = TC.createEntrySpan(tracingContext, ngx.var.uri, nil, contextCarrier)
+        entrySpan = TC.createEntrySpan(tracingContext, req_uri, nil, contextCarrier)
     end
     Span.start(entrySpan, time_now)
     Span.setComponentId(entrySpan, nginxComponentId)
@@ -58,13 +59,12 @@
     Span.tag(entrySpan, 'http.params',
              ngx.var.scheme .. '://' .. ngx.var.host .. ngx.var.request_uri )
 
-    contextCarrier = {}
+    contextCarrier = Util.tablepool_fetch("sw_contextCarrier")
     -- Use the same URI to represent incoming and forwarding requests
     -- Change it if you need.
-    local upstreamUri = ngx.var.uri
     local upstreamServerName = upstream_name
     ------------------------------------------------------
-    local exitSpan = TC.createExitSpan(tracingContext, upstreamUri, entrySpan,
+    local exitSpan = TC.createExitSpan(tracingContext, req_uri, entrySpan,
                         upstreamServerName, contextCarrier, correlation)
     Span.start(exitSpan, time_now)
     Span.setComponentId(exitSpan, nginxComponentId)
@@ -117,14 +117,16 @@
         ngx.log(ngx.ERR, "failed to encode segment: ", err)
         return
     end
-    ngx.log(ngx.DEBUG, 'segment = ', segmentJson)
+    -- ngx.log(ngx.DEBUG, 'segment = ', segmentJson)
 
     local length, err = metadata_shdict:lpush(Const.segment_queue, segmentJson)
     if not length then
         ngx.log(ngx.ERR, "failed to push segment: ", err)
         return
     end
-    ngx.log(ngx.DEBUG, 'segment buffer size = ', length)
+    -- ngx.log(ngx.DEBUG, 'segment buffer size = ', length)
+
+    Util.tablepool_release()
 end
 
 return Tracer
diff --git a/lib/skywalking/tracing_context.lua b/lib/skywalking/tracing_context.lua
index 3da161a..8e19b63 100644
--- a/lib/skywalking/tracing_context.lua
+++ b/lib/skywalking/tracing_context.lua
@@ -77,13 +77,13 @@
 
 -- Create an internal instance
 function Internal.new()
-    local internal = {}
+    local internal = Util.tablepool_fetch()
 
     internal.self_generated_trace_id = true
     internal.span_id_seq = 0
-    internal.active_spans = {}
+    internal.active_spans = Util.tablepool_fetch()
     internal.active_count = 0
-    internal.finished_spans = {}
+    internal.finished_spans = Util.tablepool_fetch()
     internal.addRefIfFirst = addRefIfFirst
     internal.addActive = addActive
     internal.finishSpan = finishSpan
@@ -112,7 +112,7 @@
         return _M.newNoOP()
     end
 
-    local tracing_context = {}
+    local tracing_context = Util.tablepool_fetch()
     tracing_context.trace_id = Util.newID()
     tracing_context.segment_id = tracing_context.trace_id
     tracing_context.service = serviceName
@@ -173,7 +173,7 @@
     if tracingContext.internal.active_count ~= 0 then
         return false, nil
     else
-        local segment = {}
+        local segment = Util.tablepool_fetch()
         segment.trace_id = tracingContext.trace_id
         segment.segment_id = tracingContext.segment_id
         segment.service = tracingContext.service
diff --git a/lib/skywalking/util.lua b/lib/skywalking/util.lua
index 15eb7eb..942306b 100644
--- a/lib/skywalking/util.lua
+++ b/lib/skywalking/util.lua
@@ -119,4 +119,56 @@
 end
 
 
+if _M.is_ngx_lua then
+    local tablepool = require("tablepool")
+    local clear_tab = require("table.clear")
+    local insert_tab = table.insert
+    local ngx = ngx
+    _M.tablepool_fetch = function(name, narr, nrec)
+        narr = narr or 8
+        nrec = nrec or 8
+        name = name or "sw_default_tab"
+
+        local sw_tab_pool = ngx.ctx.sw_tab_pool
+        if not sw_tab_pool then
+            sw_tab_pool = tablepool.fetch("sw_tab_pool", 128, 0)
+            insert_tab(sw_tab_pool, "sw_tab_pool")
+            insert_tab(sw_tab_pool, sw_tab_pool)
+
+            ngx.ctx.sw_tab_pool = sw_tab_pool
+        end
+
+        local tab = tablepool.fetch(name, narr, nrec)
+        insert_tab(sw_tab_pool, name)
+        insert_tab(sw_tab_pool, tab)
+        return tab
+    end
+    _M.tablepool_release = function()
+        local sw_tab_pool = ngx.ctx.sw_tab_pool
+        if not sw_tab_pool then
+            return
+        end
+
+        for i = #sw_tab_pool, 1, -2 do
+            local name = sw_tab_pool[i - 1]
+            local tab = sw_tab_pool[i]
+            tablepool.release(name, tab)
+            -- ngx.log(ngx.INFO, "release name: ", name, " ", tostring(tab))
+        end
+        clear_tab(sw_tab_pool)
+
+        ngx.ctx.sw_tab_pool = nil
+    end
+else
+    _M.tablepool_fetch = function () return {} end
+    _M.tablepool_release = function () return true end
+end
+
+
+function _M.disable_tablepool()
+    _M.tablepool_fetch = function () return {} end
+    _M.tablepool_release = function () return true end
+end
+
+
 return _M
diff --git a/t/util.t b/t/util.t
index fe80bf2..0b5a027 100644
--- a/t/util.t
+++ b/t/util.t
@@ -74,3 +74,128 @@
 done
 --- no_error_log
 [error]
+
+
+
+=== TEST 3: tablepool, use different name
+--- http_config eval: $::HttpConfig
+--- config
+    location /t {
+        content_by_lua_block {
+            local util = require('skywalking.util')
+
+            local tab1_name = util.tablepool_fetch("name1", 1, 1)
+            local tab1_name2 = util.tablepool_fetch("name2", 1, 1)
+            util.tablepool_release()
+
+            local tab2_name = util.tablepool_fetch("name1", 1, 1)
+            local tab2_name2 = util.tablepool_fetch("name2", 1, 1)
+            util.tablepool_release()
+
+            if tab1_name == tab2_name then
+                ngx.say("fetch same table by name1")
+            else
+                ngx.say("fetch different table by name1")
+            end
+
+            if tab1_name2 == tab2_name2 then
+                ngx.say("fetch same table by name2")
+            else
+                ngx.say("fetch different table by name2")
+            end
+        }
+    }
+--- request
+GET /t
+--- response_body
+fetch same table by name1
+fetch same table by name2
+--- no_error_log
+[error]
+
+
+
+=== TEST 4: tablepool, use default name
+--- http_config eval: $::HttpConfig
+--- config
+    location /t {
+        content_by_lua_block {
+            local util = require('skywalking.util')
+
+            local tab1_name = util.tablepool_fetch()
+            local tab1_name2 = util.tablepool_fetch()
+            util.tablepool_release()
+
+            local tab2_name = util.tablepool_fetch()
+            local tab2_name2 = util.tablepool_fetch()
+            util.tablepool_release()
+
+            if tab1_name == tab2_name then
+                ngx.say("fetch same table by default name[1]")
+            else
+                ngx.say("fetch different table by default name[1]")
+            end
+
+            if tab1_name2 == tab2_name2 then
+                ngx.say("fetch same table by default name[2]")
+            else
+                ngx.say("fetch different table by default name[2]")
+            end
+
+            util.tablepool_release()
+            util.tablepool_release()
+            ngx.say("done")
+        }
+    }
+--- request
+GET /t
+--- response_body
+fetch same table by default name[1]
+fetch same table by default name[2]
+done
+--- no_error_log
+[error]
+
+
+
+=== TEST 5: tablepool, call `disable_tablepool` to disable tablepool
+--- http_config eval: $::HttpConfig
+--- config
+    location /t {
+        content_by_lua_block {
+            local util = require('skywalking.util')
+
+            local tab1 = util.tablepool_fetch()
+            util.tablepool_release()
+
+            local tab2 = util.tablepool_fetch()
+            util.tablepool_release()
+
+            if tab1 == tab2 then
+                ngx.say("enabled tablepool: fetched same tables")
+            else
+                ngx.say("enabled tablepool: fetched different tables")
+            end
+
+            util.disable_tablepool()
+
+            local tab1 = util.tablepool_fetch()
+            util.tablepool_release()
+
+            local tab2 = util.tablepool_fetch()
+            util.tablepool_release()
+
+            if tab1 == tab2 then
+                ngx.say("disable tablepool: fetched same tables")
+            else
+                ngx.say("disable tablepool: fetched different tables")
+            end
+        }
+    }
+--- request
+GET /t
+--- response_body
+enabled tablepool: fetched same tables
+disable tablepool: fetched different tables
+--- no_error_log
+[error]
diff --git a/test/e2e/e2e-with-mock-collector/docker/conf.d/nginx.conf b/test/e2e/e2e-with-mock-collector/docker/conf.d/nginx.conf
index cd1e679..be89a69 100644
--- a/test/e2e/e2e-with-mock-collector/docker/conf.d/nginx.conf
+++ b/test/e2e/e2e-with-mock-collector/docker/conf.d/nginx.conf
@@ -40,6 +40,12 @@
 
         require("skywalking.util").set_randomseed()
         require("skywalking.client"):startBackendTimer("http://${collector}:12800")
+
+        -- If there is a bug of this `tablepool` implementation, we can
+        -- disable it in this way
+        -- require("skywalking.util").disable_tablepool()
+
+        skywalking_tracer = require("skywalking.tracer")
     }
 
     server {
@@ -49,17 +55,17 @@
             default_type text/html;
 
             rewrite_by_lua_block {
-                require("skywalking.tracer"):start("e2e-test-with-mock-collector:upstream_ip:port")
+                skywalking_tracer:start("e2e-test-with-mock-collector:upstream_ip:port")
             }
 
             proxy_pass http://127.0.0.1:8080/tier2/lb;
 
             body_filter_by_lua_block {
-                require("skywalking.tracer"):finish()
+                skywalking_tracer:finish()
             }
 
             log_by_lua_block {
-                require("skywalking.tracer"):prepareForReport()
+                skywalking_tracer:prepareForReport()
             }
         }
 
@@ -67,17 +73,17 @@
             default_type text/html;
 
             rewrite_by_lua_block {
-                require("skywalking.tracer"):start("e2e-test-with-mock-collector:upstream_ip2:port2")
+                skywalking_tracer:start("e2e-test-with-mock-collector:upstream_ip2:port2")
             }
 
             proxy_pass http://127.0.0.1:8080/backend;
 
             body_filter_by_lua_block {
-                require("skywalking.tracer"):finish()
+                skywalking_tracer:finish()
             }
 
             log_by_lua_block {
-                require("skywalking.tracer"):prepareForReport()
+                skywalking_tracer:prepareForReport()
             }
         }