#!/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,'') if url then table.insert(resources, url) end line = pp:read("*line") end pp:close() --创建post文件 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 --计算平均值 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 --每间隔cfg.interval秒的tx数据计算速度,取其中的最大值 function tx_max_avg_speed(dotdata, net_burst) local tx_speed = 0 local i = 2 --i从2开始,避开第一秒的burst数据 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 -- 去掉一个最大值,去掉一个最小值, 求平均 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 --每间隔cfg.interval秒的tx数据计算速度,去掉一个最大值,去掉一个最小值, 求平均 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 --判断第一秒是否有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()