508 lines
12 KiB
Lua
Executable File
508 lines
12 KiB
Lua
Executable File
#!/usr/bin/env lua
|
||
local posix = require("Posix")
|
||
local ubus = require ("ubus")
|
||
local fs = require "nixio.fs"
|
||
|
||
local cfg = {
|
||
['postfile'] = "/tmp/postfile.dat",
|
||
['postfilesize'] = 1024, -- kbyte
|
||
['posturl'] = "http://netsp.master.qq.com/cgi-bin/netspeed",
|
||
['geturl'] = "http://dlied6.qq.com/invc/qqdoctor/other/test32mb.dat",
|
||
['nr'] = 320, --Number of requests to perform
|
||
['nc'] = 8, --Number of multiple requests to make at a time
|
||
['timelimit'] = 9,
|
||
['timestep'] = 1,
|
||
['interval'] = 1,
|
||
['weight'] = 0.95, -- smooth net tarffic burst at first second
|
||
['qos_weight'] = 0.98,
|
||
['burstrate'] = 2,
|
||
['ab'] = "/usr/bin/ab",
|
||
['dd'] = "/bin/dd",
|
||
['debug'] = 0,
|
||
['xmlfile'] = "/usr/share/speedtest.xml",
|
||
['tmp_speedtest_xml'] = "/tmp/speedtest_urls.xml",
|
||
}
|
||
|
||
VERSION="__UNDEFINED__"
|
||
|
||
if VERSION == "LESSMEM" then
|
||
cfg.postfilesize = 640
|
||
cfg.nc = 6
|
||
end
|
||
|
||
local filename = ""
|
||
filexml = io.open(cfg.tmp_speedtest_xml)
|
||
if filexml then
|
||
filexml:close()
|
||
filename = cfg.tmp_speedtest_xml
|
||
else
|
||
filename = cfg.xmlfile
|
||
end
|
||
|
||
local pp = io.open(filename)
|
||
local line = pp:read("*line")
|
||
local size = 0
|
||
local resources = {}
|
||
local u = ""
|
||
local pids = {}
|
||
|
||
function die(err)
|
||
posix.openlog(arg[0], "cp", posix.LOG_LOCAL7)
|
||
posix.syslog(posix.LOG_ERR, err)
|
||
posix.closelog()
|
||
os.exit(1)
|
||
end
|
||
|
||
function logger(loglevel,msg)
|
||
posix.openlog("speedtest","np",LOG_USER)
|
||
posix.syslog(loglevel,msg)
|
||
posix.closelog()
|
||
end
|
||
|
||
print(string.format("upload using %s...", filename))
|
||
print(string.format("upload using file size: %d nc: %d", cfg.postfilesize, cfg.nc))
|
||
logger(3, string.format("upload using %s...", filename))
|
||
logger(3, string.format("upload using file size: %d nc: %d", cfg.postfilesize, cfg.nc))
|
||
|
||
function mrandom(min,max,num)
|
||
local reverse = {}
|
||
local t = {}
|
||
local ret = {}
|
||
local i = min
|
||
local index
|
||
while i <= max do
|
||
table.insert(t, i)
|
||
i = i + 1
|
||
end
|
||
i = num
|
||
math.randomseed(os.time())
|
||
|
||
while i > 0 do
|
||
index = math.random(table.getn(t))
|
||
table.insert(ret,t[index])
|
||
if index == table.getn(t) then
|
||
table.remove(t)
|
||
else
|
||
local top = table.remove(t)
|
||
t[index] = top
|
||
end
|
||
i = i - 1
|
||
end
|
||
return ret
|
||
end
|
||
|
||
|
||
function execa(cmd)
|
||
local p = io.popen(cmd)
|
||
local line = p:read("*l")
|
||
while(line) do
|
||
print(line)
|
||
line = p:read("*l")
|
||
end
|
||
p:close()
|
||
end
|
||
|
||
function wget_work(url)
|
||
local _url = url
|
||
pid = posix.fork()
|
||
if pid < 0 then
|
||
print("fork error")
|
||
return -1
|
||
elseif pid > 0 then
|
||
--print(string.format("child pid %d\n", pid))
|
||
else
|
||
os.execute('for i in $(seq '.. math.floor(cfg.nr/cfg.nc) ..'); do wget '.. url ..
|
||
" -q -O /dev/null; done")
|
||
end
|
||
return pid
|
||
end
|
||
|
||
function wan_device()
|
||
local conn = ubus.connect()
|
||
if not conn then
|
||
elog("Failed to connect to ubusd")
|
||
end
|
||
local status = conn:call("network.interface.wan", "status",{})
|
||
conn:close()
|
||
if not status then
|
||
return nil
|
||
else
|
||
return (status.l3_device and status.l3_device) or status.device
|
||
end
|
||
end
|
||
|
||
function get_pstree(root, pids)
|
||
local pp = io.popen("pgrep -P "..root)
|
||
pid = pp:read("*line")
|
||
while pid do
|
||
data = table.insert(pids, pid)
|
||
get_pstree(pid, pids)
|
||
pid = pp:read("*line")
|
||
end
|
||
pp:close()
|
||
end
|
||
|
||
function execl2(command)
|
||
local pp = io.popen(command)
|
||
local line = ""
|
||
local data = {}
|
||
|
||
while true do
|
||
line = pp:read()
|
||
if line == nil then
|
||
break
|
||
end
|
||
data[#data+1] = line
|
||
end
|
||
pp:close()
|
||
return data
|
||
end
|
||
|
||
function get_pstree2(root, pids)
|
||
local cmd = "pstree -p "..root
|
||
local res = execl2(cmd)
|
||
|
||
print("type root is: "..type(root))
|
||
if res and next(res)~=nil then
|
||
for k,v in ipairs(res) do
|
||
local j = 0
|
||
while true do
|
||
_,j,pid = string.find(v, '%(([0-9]+)%)', j+1)
|
||
if pid then
|
||
if tonumber(pid) ~= root then
|
||
table.insert(pids, pid)
|
||
end
|
||
else
|
||
break
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
function done(signo)
|
||
io.output("/dev/null")
|
||
local fd = io.open("/dev/null", "rw")
|
||
if not posix.dup(fd, io.stdout) then
|
||
die("error dup2-ing")
|
||
end
|
||
if not posix.dup(fd, io.stderr) then
|
||
die("error dup2-ing")
|
||
end
|
||
get_pstree2(posix.getpid("pid"), pids)
|
||
for k, pid in ipairs(pids) do
|
||
print("kill pid:" .. pid)
|
||
posix.kill(pid, posix.SIGINT)
|
||
end
|
||
os.exit(0)
|
||
end
|
||
|
||
function read_line(filename)
|
||
local fd = io.open(filename)
|
||
local line = fd:read("*line")
|
||
fd:close()
|
||
return line
|
||
end
|
||
|
||
|
||
function get_uptime()
|
||
local _, _, uptime, idle = string.find(read_line("/proc/uptime"),'^([0-9.]+)%s+([0-9.]+)$')
|
||
return tonumber(uptime)
|
||
end
|
||
|
||
|
||
function get_rt(ifname)
|
||
local line
|
||
local face, r_bytes, r_packets, r_errs, r_drop, r_fifo, r_frame, r_compressed, r_multicast
|
||
local t_bytes, t_packets, t_errs, t_drop, t_fifo, t_colls, t_carrier, t_compressed
|
||
local _nic = {}
|
||
if fs.access("/proc/net/dev") then
|
||
for line in io.lines("/proc/net/dev") do
|
||
_, _, face, r_bytes, r_packets, r_errs, r_drop, r_fifo, r_frame, r_compressed, r_multicast,
|
||
t_bytes, t_packets, t_errs, t_drop, t_fifo, t_colls, t_carrier, t_compressed = string.find(line,
|
||
'%s*(%S+):%s+(%d+)%s+(%d+)%s+(%d+)%s+(%d+)%s+(%d+)%s+(%d+)%s+(%d+)%s+(%d+)%s+(%d+)%s+(%d+)%s+(%d+)%s+(%d+)%s+(%d+)%s+(%d+)%s+(%d+)%s+(%d+)%s*')
|
||
if (face == ifname) then
|
||
return {
|
||
r_bytes = tonumber(r_bytes),
|
||
r_packets = tonumber(r_packets),
|
||
t_bytes = tonumber(t_bytes),
|
||
t_packets = tonumber(t_packets),
|
||
uptime = get_uptime()
|
||
}
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
function print_rt(rt, rt_last)
|
||
local delta_time = rt.uptime - rt_last.uptime
|
||
print(string.format("Time(ms):%.0f\tr_x:%.2f t_x:%.2f",
|
||
delta_time*1000,
|
||
(rt.r_bytes - rt_last.r_bytes) / delta_time / 1024,
|
||
(rt.t_bytes - rt_last.t_bytes) / delta_time / 1024))
|
||
end
|
||
|
||
function ab_work(url)
|
||
pid = posix.fork()
|
||
if pid < 0 then
|
||
print("fork error")
|
||
return -1
|
||
elseif pid > 0 then
|
||
--print(string.format("child pid %d\n", pid))
|
||
else
|
||
local cmd = (string.format("%s -N -p %s '%s' > /dev/null", cfg.ab, cfg.postfile, url))
|
||
--print("ab work cmd: "..cmd)
|
||
os.execute('for i in $(seq '.. math.floor(cfg.nr/cfg.nc) ..'); do ' .. cmd .. "; done")
|
||
|
||
end
|
||
return pid
|
||
end
|
||
|
||
function ab_work_lowmem()
|
||
local cmd = string.format("%s -N '%s' '%s' '%s' '%s' '%s' '%s' -L 6 -R 100 -t 15 -M 15 > /dev/null", cfg.ab, 'http://www.taobao.com/',
|
||
'http://www.so.com/', 'http://www.qq.com/', 'http://www.sohu.com/', 'http://www.tudou.com/', 'http://www.kankan.com/')
|
||
pid = posix.fork()
|
||
if pid < 0 then
|
||
print("fork error")
|
||
return -1
|
||
elseif pid > 0 then
|
||
--print(string.format("child pid %d\n", pid))
|
||
else
|
||
os.execute(cmd)
|
||
|
||
end
|
||
return pid
|
||
end
|
||
|
||
function dump_dot_data(dotdata)
|
||
for i=1,#dotdata do
|
||
print(string.format("[%.2d] %.2f %.2f %.2f", i, dotdata[i].r_bytes,dotdata[i].t_bytes, dotdata[i].uptime))
|
||
end
|
||
end
|
||
-----------------------------------------------------------------------
|
||
|
||
|
||
|
||
posix.signal(posix.SIGTERM, done);
|
||
|
||
local wan_ifname = wan_device()
|
||
if not wan_ifname then
|
||
print("got invalid wan device")
|
||
print(string.format("avg tx:%.2f", 0))
|
||
logger(3, "stat_points_privacy network_speedtest=10|upload|no_wan_dev")
|
||
os.remove(cfg.postfile)
|
||
done()
|
||
end
|
||
|
||
while line do
|
||
local _, _, url = string.find(line,'<item uploadurl="(.*)"/>')
|
||
if url then
|
||
table.insert(resources, url)
|
||
end
|
||
line = pp:read("*line")
|
||
end
|
||
pp:close()
|
||
|
||
--<2D><><EFBFBD><EFBFBD>post<73>ļ<EFBFBD>
|
||
if VERSION ~= "LESSMEM" then
|
||
os.execute(string.format("%s if=/dev/zero of=%s bs=1k count=%d >/dev/null 2>&1",
|
||
cfg.dd, cfg.postfile, cfg.postfilesize))
|
||
|
||
if (posix.stat(cfg.postfile) == nil) then
|
||
print("create postfile error")
|
||
logger(3, "stat_points_privacy network_speedtest=10|upload|postfile_err")
|
||
os.exit(1)
|
||
end
|
||
end
|
||
|
||
local urls = mrandom(1, table.getn(resources), cfg.nc)
|
||
|
||
if VERSION == "LESSMEM" then
|
||
local pid = ab_work_lowmem()
|
||
if(pid == 0) then
|
||
os.exit(0)
|
||
elseif(pid == -1) then
|
||
done()
|
||
end
|
||
else
|
||
for k, v in ipairs(urls) do
|
||
local pid = ab_work(resources[v])
|
||
if(pid == 0) then
|
||
os.exit(0)
|
||
elseif(pid == -1) then
|
||
os.remove(cfg.postfile)
|
||
done()
|
||
end
|
||
end
|
||
end
|
||
|
||
local rt = get_rt(wan_ifname)
|
||
if not rt then
|
||
print("got invalid data")
|
||
print(string.format("avg tx:%.2f", 0))
|
||
logger(3, "stat_points_privacy network_speedtest=10|upload|no_wan_info")
|
||
os.remove(cfg.postfile)
|
||
done()
|
||
end
|
||
|
||
|
||
local rt_last
|
||
local dot_datas = {}
|
||
local dot = 1
|
||
dot_datas[dot] = rt
|
||
|
||
while dot <= cfg.timelimit do
|
||
posix.sleep(1)
|
||
|
||
dot = dot + 1
|
||
rt_last = rt
|
||
rt = get_rt(wan_ifname)
|
||
if not rt then
|
||
print("got invalid data")
|
||
print(string.format("avg tx:%.2f", 0))
|
||
logger(3, "stat_points_privacy network_speedtest=10|upload|no_wan_info")
|
||
os.remove(cfg.postfile)
|
||
done()
|
||
end
|
||
|
||
dot_datas[dot] = rt
|
||
print_rt(rt, rt_last)
|
||
end
|
||
|
||
|
||
--<2D><><EFBFBD><EFBFBD>ƽ<EFBFBD><C6BD>ֵ
|
||
function tx_avg_speed(dotdata)
|
||
local sum,j = 0,1
|
||
local dlt_time
|
||
|
||
if #dotdata < 2 then
|
||
return 0
|
||
end
|
||
|
||
while j + 1 <= #dotdata do
|
||
dlt_time = dotdata[j+1].uptime - dotdata[j].uptime
|
||
if (dotdata[j+1].t_bytes <= dotdata[j].t_bytes) then
|
||
return 0
|
||
end
|
||
sum = sum + (dotdata[j+1].t_bytes - dotdata[j].t_bytes) / dlt_time / 1024
|
||
j = j + 1
|
||
end
|
||
|
||
return sum/(#dotdata-1)
|
||
end
|
||
|
||
--ÿ<><C3BF><EFBFBD><EFBFBD>cfg.interval<61><6C><EFBFBD><EFBFBD>tx<74><78><EFBFBD>ݼ<EFBFBD><DDBC><EFBFBD><EFBFBD>ٶȣ<D9B6>ȡ<EFBFBD><C8A1><EFBFBD>е<EFBFBD><D0B5><EFBFBD><EFBFBD><EFBFBD>ֵ
|
||
function tx_max_avg_speed(dotdata, net_burst)
|
||
local tx_speed = 0
|
||
local i = 2 --i<><69>2<EFBFBD><32>ʼ<EFBFBD><CABC><EFBFBD>ܿ<EFBFBD><DCBF><EFBFBD>һ<EFBFBD><D2BB><EFBFBD><EFBFBD>burst<73><74><EFBFBD><EFBFBD>
|
||
|
||
while (i + cfg.interval) <= (cfg.timelimit + 1) do
|
||
local delta_time = dotdata[i+cfg.interval].uptime - dotdata[i].uptime
|
||
if (dotdata[i+cfg.interval].t_bytes <= dotdata[i].t_bytes) then
|
||
return 0
|
||
end
|
||
|
||
local tmp = (dotdata[i+cfg.interval].t_bytes - dotdata[i].t_bytes)/ delta_time / 1024
|
||
--print(string.format("sec[%d]%.2f sec[%d]%.2f delta_time %.2f, speed is %.2f",
|
||
-- i+cfg.interval, dotdata[i+cfg.interval].t_bytes, i,
|
||
-- dotdata[i].t_bytes, delta_time, tmp))
|
||
|
||
if (i == 1) and net_burst then
|
||
--print(string.format("sec[%d] weight %.2f burstrate %.2f : smooth from %.2f to %.2f",
|
||
-- i, cfg.weight, cfg.burstrate, tmp, tmp*cfg.weight))
|
||
tmp = tmp * cfg.weight
|
||
end
|
||
|
||
if tmp > tx_speed then
|
||
tx_speed = tmp
|
||
end
|
||
i = i + 1
|
||
end
|
||
|
||
return tx_speed
|
||
end
|
||
|
||
-- ȥ<><C8A5>һ<EFBFBD><D2BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֵ<EFBFBD><D6B5>ȥ<EFBFBD><C8A5>һ<EFBFBD><D2BB><EFBFBD><EFBFBD>Сֵ, <20><>ƽ<EFBFBD><C6BD>
|
||
function avg_remove_max_min(tdata)
|
||
local sum = 0
|
||
table.sort(tdata)
|
||
|
||
--print(string.format("table remove max %.2f min %.2f",
|
||
-- tdata[#tdata], tdata[1]))
|
||
|
||
table.remove(tdata,#tdata)
|
||
table.remove(tdata,1)
|
||
|
||
for key, value in pairs(tdata) do
|
||
sum = sum + value
|
||
--print(string.format("%.2f", value))
|
||
end
|
||
|
||
return sum/#tdata
|
||
end
|
||
|
||
--ÿ<><C3BF><EFBFBD><EFBFBD>cfg.interval<61><6C><EFBFBD><EFBFBD>tx<74><78><EFBFBD>ݼ<EFBFBD><DDBC><EFBFBD><EFBFBD>ٶȣ<D9B6>ȥ<EFBFBD><C8A5>һ<EFBFBD><D2BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֵ<EFBFBD><D6B5>ȥ<EFBFBD><C8A5>һ<EFBFBD><D2BB><EFBFBD><EFBFBD>Сֵ, <20><>ƽ<EFBFBD><C6BD>
|
||
function tx_avg_interval_speed(dotdata)
|
||
local sum = {}
|
||
local tx_speed = 0
|
||
local i = 1
|
||
|
||
while (i + cfg.interval) <= (cfg.timelimit + 1) do
|
||
if (dotdata[i+cfg.interval].t_bytes <= dotdata[i].t_bytes) then
|
||
print("t_x data "..(i+cfg.interval).." <= "..i)
|
||
logger(3, "stat_points_privacy network_speedtest=10|upload|data_invalid|"..i+cfg.interval.."|"..dotdata[i+cfg.interval].r_bytes.."|"..i.."|"..dotdata[i].r_bytes)
|
||
return 0
|
||
end
|
||
|
||
local delta_time = dotdata[i+cfg.interval].uptime - dotdata[i].uptime
|
||
local tmp = (dotdata[i+cfg.interval].t_bytes - dotdata[i].t_bytes)/ delta_time / 1024
|
||
--print(string.format("sec[%d]%.2f sec[%d]%.2f delta_time %.2f, speed is %.2f",
|
||
-- i+cfg.interval, dotdata[i+cfg.interval].t_bytes, i,
|
||
-- dotdata[i].t_bytes, delta_time, tmp))
|
||
|
||
table.insert(sum, tmp)
|
||
i = i + 1
|
||
end
|
||
|
||
tx_speed = avg_remove_max_min(sum)
|
||
if tx_speed*8 < 56 then
|
||
logger(3, "stat_points_privacy network_speedtest=10|upload|data_small_value|"..tx_speed*8)
|
||
end
|
||
return tx_speed
|
||
end
|
||
|
||
--<2D>жϵ<D0B6>һ<EFBFBD><D2BB><EFBFBD>Ƿ<EFBFBD><C7B7><EFBFBD>net burst
|
||
function if_net_burst(dotdata)
|
||
if ((dotdata[2].t_bytes < dotdata[1].t_bytes) or (dotdata[3].t_bytes < dotdata[2].t_bytes)) then
|
||
return false
|
||
end
|
||
|
||
local delta_time = dotdata[2].uptime - dotdata[1].uptime
|
||
local first_speed = (dotdata[2].t_bytes - dotdata[1].t_bytes) / delta_time / 1024
|
||
|
||
delta_time = dotdata[3].uptime - dotdata[2].uptime
|
||
local second_speed = (dotdata[3].t_bytes - dotdata[2].t_bytes) / delta_time / 1024
|
||
|
||
if first_speed > (cfg.burstrate * second_speed) then
|
||
return true
|
||
end
|
||
return false
|
||
end
|
||
|
||
--dump_dot_data(dot_datas)
|
||
local res_speed = tx_avg_speed(dot_datas)
|
||
print(string.format("avg t_x:%.2f %.2fMB %.2fMbit", res_speed*8, res_speed/1024, res_speed*8/1024))
|
||
|
||
res_speed = tx_avg_interval_speed(dot_datas)
|
||
|
||
print(string.format("qos weight %.3f: from %.2f to %.2f",
|
||
cfg.qos_weight, res_speed, res_speed*cfg.qos_weight))
|
||
|
||
res_speed = res_speed * cfg.qos_weight
|
||
print(string.format("avg tx:%.2f %.2fMB %.2fMbit", res_speed*8, res_speed/1024, res_speed*8/1024))
|
||
|
||
|
||
os.remove(cfg.postfile)
|
||
done()
|
||
|