Skip to content

Instantly share code, notes, and snippets.

@chobits
Last active August 14, 2024 07:35
Show Gist options
  • Select an option

  • Save chobits/d21f21d83ca24076377ff31db7ab42c1 to your computer and use it in GitHub Desktop.

Select an option

Save chobits/d21f21d83ca24076377ff31db7ab42c1 to your computer and use it in GitHub Desktop.

Revisions

  1. chobits revised this gist Aug 14, 2024. 1 changed file with 51 additions and 70 deletions.
    121 changes: 51 additions & 70 deletions perf.lua
    Original file line number Diff line number Diff line change
    @@ -1,27 +1,23 @@
    --[[
    run it by this command:
    $ resty --shdict "kong_dns_cache 10m" --shdict "kong_dns_cache_ipc 10m" ./perf.lua
    $ resty --shdict "kong_dns_cache 10m" ./perf.lua
    ]]

    setmetatable(_G, nil) -- disable the _G write guard alert log introduced in OpenResty 1.15.8.1

    pcall(require, "luarocks.loader")
    require("kong.globalpatches")()
    local json = require("cjson").encode

    -- hosted in Route53 in the AWS sandbox
    local TEST_NS = "192.168.1.1:53"
    local TEST_NS = "192.168.5.2:53" -- Kong local official env

    local TEST_NSS = { TEST_NS }

    local not_found_answers = { errcode = 3, errstr = "not found" }

    local a_answers = {{ address = "127.0.0.1", class = 1, ttl = 300, type = 1 }}

    -- inject r.query
    package.loaded["resty.dns.resolver"] = nil
    local resolver = require("resty.dns.resolver")

    local query_func = function(self, original_query_func, name, options)
    return original_query_func(self, name, options)
    end
    @@ -38,99 +34,84 @@ resolver.new = function(...)
    end
    return r
    end

    resolver.tag = "modified"
    --- end of inject r.query

    -- restore its API overlapped by the compatible layer
    package.loaded["kong.resty.dns_client"] = nil
    local client = require("kong.resty.dns_client")
    local client = require("kong.dns.client")
    client.resolve = client._resolve

    _G.busted_legacy_dns_client = true
    package.loaded["kong.resty.dns.client"] = nil
    local dns_client = require("kong.resty.dns.client")

    --- TEST APIs
    local t
    local n

    local t
    local function measure_function_time(f)
    t = os.clock()
    f()
    local delta = os.clock() - t
    print(string.format(" run resolve() %d times: %.4f s; avg: %.6f ms per calls",
    n, delta, delta/n *1000))
    return delta
    end


    local function test(name, f)
    local new_delta, old_delta
    print(name)

    -- new
    local cli = assert(client.new({
    nameservers = TEST_NSS,
    order = { "LAST", "SRV", "A", "CNAME" },
    }))
    t = os.clock()
    f(function (host) return cli:resolve(host) end)
    new_delta = os.clock() - t
    print("NEW DNS client consumes: ", new_delta, " 1 try=", new_delta/n *1000, " ms")
    local cli = assert(client.new({ family = { "SRV", "A" }, }))

    print("+ NEW DNS client")
    local new_delta = measure_function_time(function ()
    f(function (host) return cli:resolve(host, nil, nil, {}) end)
    end)
    -- print("+ stats: ", json(cli.stats))

    -- old
    package.loaded["kong.resty.dns.client"] = nil
    local cli = require("kong.resty.dns.client")
    assert(cli.init({
    resolvConf = { "nameserver " .. TEST_NS },
    order = { "LAST", "SRV", "A", "CNAME" },
    }))

    local t = os.clock()
    f(function (host) return cli.resolve(host) end)
    old_delta = os.clock() - t
    print("OLD DNS client consumes: ", old_delta, " 1 try=", old_delta/n *1000, " ms")
    assert(dns_client.init({ order = { "LAST", "SRV", "A", "CNAME" }, }))

    print("+ OLD DNS client")
    local old_delta = measure_function_time(function ()
    f(function (host) return dns_client.resolve(host, nil, nil, {}, nil) end)
    end)

    -- last
    local improved = (n/new_delta)/(n/old_delta) - 1
    print(("Improved: %.2f%%\n"):format(improved * 100))
    local improved = (1/(new_delta/n))/(1/(old_delta/n)) - 1
    print(("+ Improved: %.2f%%\n"):format(improved * 100))
    end

    --- run test
    print("\n\n\n\n\n--- runing perf test ---\n")
    print("\n--- runing perf test ---\n")

    --- 100% Hit
    n = 100000
    local title = "+ hit 100% (miss the first time)"
    n = 10000000
    local title = "+ 100% hit (miss the first time)"

    test(tile, function (cli_resolve)
    test(title, function (cli_resolve)
    local answers, err, tries

    -- skip first miss --
    answers, err, tries = cli_resolve("www.konghq.com")
    t = os.clock() -- skip first miss
    assert(not err, "err:".. tostring(err) .. " tries:" .. json(tries))
    t = os.clock()

    -- run HIT query
    for i = 1, n do
    answers, err, tries = cli_resolve("www.konghq.com")
    end
    assert(not err)
    assert(answers[1].name == "www.konghq.com")
    end)


    --- ?% Hit
    --[[
    local rate = 9999
    local title = ("+ %s%% Hit (Every query to the nameserver succeeds.)"):format(rate)
    local count = 0
    query_func = function(self, original_query_func, name, options)
    count = count + 1
    if options.qtype == 33 then -- SRV
    return not_found_answers
    end
    a_answers[1].name = name
    return a_answers
    --return {{ address = "127.0.0.1", class = 1, ttl = 300, type = 1, name = name }}
    end
    test(title, function (cli_resolve)
    local answers, err, tries, host
    answers, err, tries = cli_resolve("www.konghq.com")
    t = os.clock() -- skip first miss
    count = 0
    for i = 1, n do
    host = ((i % 10000) < rate) and "www.konghq.com" or i .. ".konghq.com"
    answers, err, tries = cli_resolve(host)
    --assert(answers[1].name == host)
    end
    print(" rate:", 1 - count / (2 * n))
    -- assert(count == 2 * n)
    end)
    ]]

    --- 0% Hit
    n = 10000
    n = 100000
    local title = "+ 0% Hit (Every query to the nameserver succeeds.)"
    local count = 0

    @@ -150,5 +131,5 @@ test(title, function (cli_resolve)
    answers, err, tries = cli_resolve(i .. ".konghq.com")
    end
    assert(answers[1].name == n .. ".konghq.com")
    assert(count == 2 * n)
    assert(count == n or count == n*2)
    end)
  2. chobits revised this gist Jan 22, 2024. 1 changed file with 62 additions and 174 deletions.
    236 changes: 62 additions & 174 deletions perf.lua
    Original file line number Diff line number Diff line change
    @@ -6,44 +6,22 @@ $ resty --shdict "kong_dns_cache 10m" --shdict "kong_dns_cache_ipc 10m" ./perf.l
    ]]

    pcall(require, "luarocks.loader")

    require("kong.globalpatches")()
    local _writefile = require("pl.utils").writefile
    local tmpname = require("pl.path").tmpname
    local json = require("cjson").encode

    -- hosted in Route53 in the AWS sandbox
    local TEST_DOMAIN = "kong-gateway-testing.link"
    local TEST_NS = "198.51.100.0:53"
    local TEST_NS = "192.168.5.2:53" -- Kong local official env
    local TEST_NS = "192.168.1.1:53"
    local TEST_NS = "192.168.5.2:53" -- Kong local official env

    local TEST_NSS = { TEST_NS }

    local not_found_answers = { errcode = 3, errstr = "not found" }
    local a_answers = {{
    address = "127.0.0.1",
    class = 1,
    ttl = 300,
    type = 1
    }}

    local resolv_path, hosts_path

    local function writefile(path, text)
    _writefile(path, type(text) == "table" and table.concat(text, "\n") or text)
    end

    -- create temp resolv.conf and hosts
    local resolv_path = tmpname()
    local hosts_path = tmpname()
    ngx.log(ngx.DEBUG, "create temp resolv.conf:", resolv_path, " hosts:", hosts_path)

    local a_answers = {{ address = "127.0.0.1", class = 1, ttl = 300, type = 1 }}

    -- inject r.query
    package.loaded["resty.dns.resolver"] = nil
    local resolver = require("resty.dns.resolver")

    -- replace this `query_func` upvalue to spy on resolver query calls.
    local query_func = function(self, original_query_func, name, options)
    return original_query_func(self, name, options)
    end
    @@ -60,207 +38,117 @@ resolver.new = function(...)
    end
    return r
    end
    --- end of inject r.query

    -- restore its API overlapped by the compatible layer
    package.loaded["kong.resty.dns_client"] = nil
    local client = require("kong.resty.dns_client")
    client.resolve = client._resolve

    package.loaded["kong.resty.dns.client"] = nil
    local old_client = require("kong.resty.dns.client")

    --- TEST apis
    --- TEST APIs
    local t

    local function client_new(opts)
    opts = opts or {}
    opts.resolv_conf = resolv_path
    opts.hosts = hosts_path
    return client.new(opts)
    end

    local n
    local new_delta
    local old_delta
    local function test_new(name, f)
    print("[New DNS client]", name)
    writefile(resolv_path, {
    "nameserver " .. TEST_NS
    })
    local cli = assert(client_new({ order = { "LAST", "SRV", "A", "AAAA", "CNAME" } }))

    local function test(name, f)
    local new_delta, old_delta
    print(name)
    -- new
    local cli = assert(client.new({
    nameservers = TEST_NSS,
    order = { "LAST", "SRV", "A", "CNAME" },
    }))
    t = os.clock()
    f(cli)
    f(function (host) return cli:resolve(host) end)
    new_delta = os.clock() - t
    print("It consumes: ", new_delta, "s")
    end
    print("NEW DNS client consumes: ", new_delta, " 1 try=", new_delta/n *1000, " ms")

    -- old
    package.loaded["kong.resty.dns.client"] = nil
    local cli = require("kong.resty.dns.client")
    assert(cli.init({
    resolvConf = { "nameserver " .. TEST_NS },
    order = { "LAST", "SRV", "A", "CNAME" },
    }))

    local function test_old(name, f)
    print("[Old DNS client]", name)
    assert(old_client.init({
    resolvConf = { "nameserver " .. TEST_NS,
    order = { "LAST", "SRV", "A", "CNAME" }
    }}))
    local t = os.clock()
    f()
    f(function (host) return cli.resolve(host) end)
    old_delta = os.clock() - t
    print("It consumes: ", old_delta, "s")
    print("OLD DNS client consumes: ", old_delta, " 1 try=", old_delta/n *1000, " ms")

    -- last
    local improved = (n/new_delta)/(n/old_delta) - 1
    print(("Improved: %.2f%%\n"):format(improved * 100))
    end

    --- run test
    print("\n\n\n\n\n--- runing perf test ---\n")

    --------------------
    n = 1000000
    local title = "+ REAL SENARIO: miss the first time, hit 99%"
    test_new(title, function (cli)
    local answers, err, tries
    answers, err, tries = cli:resolve("www.konghq.com")
    t = os.clock() -- skip first miss
    for i = 1, n do
    answers, err, tries = cli:resolve("www.konghq.com")
    end
    assert(answers[1].name == "www.konghq.com")
    end)
    test_old(title, function ()
    local answers, err, tries
    answers, err, tries = old_client.resolve("www.konghq.com")
    t = os.clock() -- skip first miss
    for i = 1, n do
    answers, err, tries = old_client.resolve("www.konghq.com")
    end
    assert(answers[1].name == "www.konghq.com")
    end)

    --- 100% Hit
    n = 100000
    local title = "+ hit 100% (miss the first time)"

    test_new(title .. " qtype=A", function (cli)
    local answers, err, tries
    answers, err, tries = cli:resolve("www.konghq.com", {qtype=1})
    t = os.clock() -- skip first miss
    for i = 1, n do
    answers, err, tries = cli:resolve("www.konghq.com", {qtype=1})
    end
    assert(answers[1].name == "www.konghq.com")
    end)
    test_old(title .. "qtype=A", function ()
    test(tile, function (cli_resolve)
    local answers, err, tries
    answers, err, tries = old_client.resolve("www.konghq.com", {qtype=1})
    answers, err, tries = cli_resolve("www.konghq.com")
    t = os.clock() -- skip first miss
    for i = 1, n do
    answers, err, tries = old_client.resolve("www.konghq.com", {qtype=1})
    answers, err, tries = cli_resolve("www.konghq.com")
    end
    assert(answers[1].name == "www.konghq.com")
    end)

    --------------------
    n = 10000
    local title = "+ 100% miss, query suceeds (subtracting network I/O time.)"

    --- ?% Hit
    --[[
    local rate = 9999
    local title = ("+ %s%% Hit (Every query to the nameserver succeeds.)"):format(rate)
    local count = 0
    query_func = function(self, original_query_func, name, options)
    count = count + 1
    if options.qtype == 33 then
    if options.qtype == 33 then -- SRV
    return not_found_answers
    end
    a_answers[1].name = name
    return a_answers
    --return {{ address = "127.0.0.1", class = 1, ttl = 300, type = 1, name = name }}
    end
    test_new(title, function (cli)
    local answers, err, tries
    count = 0
    for i = 1, n do
    answers, err, tries = cli:resolve(i .. ".konghq.com")
    end
    assert(answers[1].name == n .. ".konghq.com")
    assert(count == 2 * n)
    end)
    test_old(title, function ()
    local answers, err, tries
    count = 0
    for i = 1, n do
    answers, err, tries = old_client.resolve(i .. ".konghq.com")
    end
    assert(answers[1].name == n .. ".konghq.com")
    assert(count == 2 * n)
    end)


    test_new(title .. " qtype=A", function (cli)
    local answers, err, tries
    count = 0
    for i = 1, n do
    answers, err, tries = cli:resolve(i .. ".konghq.com", {qtype=1})
    end
    assert(answers[1].name == n .. ".konghq.com")
    assert(count == n)
    end)
    test_old(title .. " qtype=A", function ()
    local answers, err, tries
    test(title, function (cli_resolve)
    local answers, err, tries, host
    answers, err, tries = cli_resolve("www.konghq.com")
    t = os.clock() -- skip first miss
    count = 0
    for i = 1, n do
    answers, err, tries = old_client.resolve(i .. ".konghq.com", {qtype =1})
    host = ((i % 10000) < rate) and "www.konghq.com" or i .. ".konghq.com"
    answers, err, tries = cli_resolve(host)
    --assert(answers[1].name == host)
    end
    assert(answers[1].name == n .. ".konghq.com")
    assert(count == n)
    print(" rate:", 1 - count / (2 * n))
    -- assert(count == 2 * n)
    end)
    ]]

    --------------------
    --- 0% Hit
    n = 10000
    local title = "+ 100% miss, query gets empty record (subtracting network I/O time)."
    local title = "+ 0% Hit (Every query to the nameserver succeeds.)"
    local count = 0

    query_func = function(self, original_query_func, name, options)
    -- print(" query: ", name .. options.qtype)
    count = count + 1
    return not_found_answers
    end

    test_new(title, function (cli)
    local answers, err, tries
    count = 0
    for i = 1, n do
    answers, err, tries = cli:resolve(i .. ".konghq.com")
    end
    assert(not answers)
    assert(count == 4 * n)
    end)

    test_old(title, function ()
    local answers, err, tries
    count = 0
    for i = 1, n do
    answers, err, tries = old_client.resolve(i .. ".konghq.com")
    if options.qtype == 33 then -- SRV
    return not_found_answers
    end
    --assert(not answers)
    assert(count == 4 * n)
    end)
    a_answers[1].name = name
    return a_answers
    end

    test_new(title .. " qtype=A", function (cli)
    test(title, function (cli_resolve)
    local answers, err, tries
    count = 0
    for i = 1, n do
    answers, err, tries = cli:resolve(i .. ".konghq.com", {qtype=1})
    answers, err, tries = cli_resolve(i .. ".konghq.com")
    end
    assert(not answers)
    assert(count == n)
    end)
    test_old(title .. " qtype=A", function ()
    local answers, err, tries
    count = 0
    for i = 1, n do
    answers, err, tries = old_client.resolve(i .. ".konghq.com", {qtype=1})
    end
    --assert(not answers)
    assert(count == n)
    assert(answers[1].name == n .. ".konghq.com")
    assert(count == 2 * n)
    end)

    --- end

    if resolv_path then
    os.remove(resolv_path)
    end
    if hosts_path then
    os.remove(hosts_path)
    end
  3. chobits revised this gist Jan 21, 2024. 1 changed file with 8 additions and 1 deletion.
    9 changes: 8 additions & 1 deletion perf.lua
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,10 @@
    --[[
    run it by this command:
    $ resty --shdict "kong_dns_cache 10m" --shdict "kong_dns_cache_ipc 10m" ./perf.lua
    ]]

    pcall(require, "luarocks.loader")

    require("kong.globalpatches")()
    @@ -256,4 +263,4 @@ if resolv_path then
    end
    if hosts_path then
    os.remove(hosts_path)
    end
    end
  4. chobits created this gist Jan 21, 2024.
    259 changes: 259 additions & 0 deletions perf.lua
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,259 @@
    pcall(require, "luarocks.loader")

    require("kong.globalpatches")()
    local _writefile = require("pl.utils").writefile
    local tmpname = require("pl.path").tmpname

    -- hosted in Route53 in the AWS sandbox
    local TEST_DOMAIN = "kong-gateway-testing.link"
    local TEST_NS = "198.51.100.0:53"
    local TEST_NS = "192.168.5.2:53" -- Kong local official env
    local TEST_NS = "192.168.1.1:53"

    local TEST_NSS = { TEST_NS }

    local not_found_answers = { errcode = 3, errstr = "not found" }
    local a_answers = {{
    address = "127.0.0.1",
    class = 1,
    ttl = 300,
    type = 1
    }}

    local resolv_path, hosts_path

    local function writefile(path, text)
    _writefile(path, type(text) == "table" and table.concat(text, "\n") or text)
    end

    -- create temp resolv.conf and hosts
    local resolv_path = tmpname()
    local hosts_path = tmpname()
    ngx.log(ngx.DEBUG, "create temp resolv.conf:", resolv_path, " hosts:", hosts_path)


    -- inject r.query
    package.loaded["resty.dns.resolver"] = nil
    local resolver = require("resty.dns.resolver")

    -- replace this `query_func` upvalue to spy on resolver query calls.
    local query_func = function(self, original_query_func, name, options)
    return original_query_func(self, name, options)
    end

    local old_new = resolver.new
    resolver.new = function(...)
    local r, err = old_new(...)
    if not r then
    return nil, err
    end
    local original_query_func = r.query
    r.query = function(self, ...)
    return query_func(self, original_query_func, ...)
    end
    return r
    end

    -- restore its API overlapped by the compatible layer
    package.loaded["kong.resty.dns_client"] = nil
    local client = require("kong.resty.dns_client")
    client.resolve = client._resolve

    package.loaded["kong.resty.dns.client"] = nil
    local old_client = require("kong.resty.dns.client")

    --- TEST apis
    local t

    local function client_new(opts)
    opts = opts or {}
    opts.resolv_conf = resolv_path
    opts.hosts = hosts_path
    return client.new(opts)
    end

    local n
    local new_delta
    local old_delta
    local function test_new(name, f)
    print("[New DNS client]", name)
    writefile(resolv_path, {
    "nameserver " .. TEST_NS
    })
    local cli = assert(client_new({ order = { "LAST", "SRV", "A", "AAAA", "CNAME" } }))
    t = os.clock()
    f(cli)
    new_delta = os.clock() - t
    print("It consumes: ", new_delta, "s")
    end

    local function test_old(name, f)
    print("[Old DNS client]", name)
    assert(old_client.init({
    resolvConf = { "nameserver " .. TEST_NS,
    order = { "LAST", "SRV", "A", "CNAME" }
    }}))
    local t = os.clock()
    f()
    old_delta = os.clock() - t
    print("It consumes: ", old_delta, "s")
    local improved = (n/new_delta)/(n/old_delta) - 1
    print(("Improved: %.2f%%\n"):format(improved * 100))
    end

    --- run test
    print("\n\n\n\n\n--- runing perf test ---\n")

    --------------------
    n = 1000000
    local title = "+ REAL SENARIO: miss the first time, hit 99%"
    test_new(title, function (cli)
    local answers, err, tries
    answers, err, tries = cli:resolve("www.konghq.com")
    t = os.clock() -- skip first miss
    for i = 1, n do
    answers, err, tries = cli:resolve("www.konghq.com")
    end
    assert(answers[1].name == "www.konghq.com")
    end)
    test_old(title, function ()
    local answers, err, tries
    answers, err, tries = old_client.resolve("www.konghq.com")
    t = os.clock() -- skip first miss
    for i = 1, n do
    answers, err, tries = old_client.resolve("www.konghq.com")
    end
    assert(answers[1].name == "www.konghq.com")
    end)


    test_new(title .. " qtype=A", function (cli)
    local answers, err, tries
    answers, err, tries = cli:resolve("www.konghq.com", {qtype=1})
    t = os.clock() -- skip first miss
    for i = 1, n do
    answers, err, tries = cli:resolve("www.konghq.com", {qtype=1})
    end
    assert(answers[1].name == "www.konghq.com")
    end)
    test_old(title .. "qtype=A", function ()
    local answers, err, tries
    answers, err, tries = old_client.resolve("www.konghq.com", {qtype=1})
    t = os.clock() -- skip first miss
    for i = 1, n do
    answers, err, tries = old_client.resolve("www.konghq.com", {qtype=1})
    end
    assert(answers[1].name == "www.konghq.com")
    end)

    --------------------
    n = 10000
    local title = "+ 100% miss, query suceeds (subtracting network I/O time.)"
    local count = 0

    query_func = function(self, original_query_func, name, options)
    count = count + 1
    if options.qtype == 33 then
    return not_found_answers
    end
    a_answers[1].name = name
    return a_answers
    end

    test_new(title, function (cli)
    local answers, err, tries
    count = 0
    for i = 1, n do
    answers, err, tries = cli:resolve(i .. ".konghq.com")
    end
    assert(answers[1].name == n .. ".konghq.com")
    assert(count == 2 * n)
    end)
    test_old(title, function ()
    local answers, err, tries
    count = 0
    for i = 1, n do
    answers, err, tries = old_client.resolve(i .. ".konghq.com")
    end
    assert(answers[1].name == n .. ".konghq.com")
    assert(count == 2 * n)
    end)


    test_new(title .. " qtype=A", function (cli)
    local answers, err, tries
    count = 0
    for i = 1, n do
    answers, err, tries = cli:resolve(i .. ".konghq.com", {qtype=1})
    end
    assert(answers[1].name == n .. ".konghq.com")
    assert(count == n)
    end)
    test_old(title .. " qtype=A", function ()
    local answers, err, tries
    count = 0
    for i = 1, n do
    answers, err, tries = old_client.resolve(i .. ".konghq.com", {qtype =1})
    end
    assert(answers[1].name == n .. ".konghq.com")
    assert(count == n)
    end)

    --------------------
    n = 10000
    local title = "+ 100% miss, query gets empty record (subtracting network I/O time)."
    local count = 0

    query_func = function(self, original_query_func, name, options)
    -- print(" query: ", name .. options.qtype)
    count = count + 1
    return not_found_answers
    end

    test_new(title, function (cli)
    local answers, err, tries
    count = 0
    for i = 1, n do
    answers, err, tries = cli:resolve(i .. ".konghq.com")
    end
    assert(not answers)
    assert(count == 4 * n)
    end)

    test_old(title, function ()
    local answers, err, tries
    count = 0
    for i = 1, n do
    answers, err, tries = old_client.resolve(i .. ".konghq.com")
    end
    --assert(not answers)
    assert(count == 4 * n)
    end)

    test_new(title .. " qtype=A", function (cli)
    local answers, err, tries
    count = 0
    for i = 1, n do
    answers, err, tries = cli:resolve(i .. ".konghq.com", {qtype=1})
    end
    assert(not answers)
    assert(count == n)
    end)
    test_old(title .. " qtype=A", function ()
    local answers, err, tries
    count = 0
    for i = 1, n do
    answers, err, tries = old_client.resolve(i .. ".konghq.com", {qtype=1})
    end
    --assert(not answers)
    assert(count == n)
    end)

    --- end

    if resolv_path then
    os.remove(resolv_path)
    end
    if hosts_path then
    os.remove(hosts_path)
    end