debug_console.lua 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. local skynet = require "skynet"
  2. local codecache = require "skynet.codecache"
  3. local core = require "skynet.core"
  4. local socket = require "skynet.socket"
  5. local snax = require "skynet.snax"
  6. local memory = require "skynet.memory"
  7. local httpd = require "http.httpd"
  8. local sockethelper = require "http.sockethelper"
  9. local arg = table.pack(...)
  10. assert(arg.n <= 2)
  11. local ip = (arg.n == 2 and arg[1] or "127.0.0.1")
  12. local port = tonumber(arg[arg.n])
  13. local TIMEOUT = 300 -- 3 sec
  14. local COMMAND = {}
  15. local COMMANDX = {}
  16. local function format_table(t)
  17. local index = {}
  18. for k in pairs(t) do
  19. table.insert(index, k)
  20. end
  21. table.sort(index, function(a, b) return tostring(a) < tostring(b) end)
  22. local result = {}
  23. for _,v in ipairs(index) do
  24. table.insert(result, string.format("%s:%s",v,tostring(t[v])))
  25. end
  26. return table.concat(result,"\t")
  27. end
  28. local function dump_line(print, key, value)
  29. if type(value) == "table" then
  30. print(key, format_table(value))
  31. else
  32. print(key,tostring(value))
  33. end
  34. end
  35. local function dump_list(print, list)
  36. local index = {}
  37. for k in pairs(list) do
  38. table.insert(index, k)
  39. end
  40. table.sort(index, function(a, b) return tostring(a) < tostring(b) end)
  41. for _,v in ipairs(index) do
  42. dump_line(print, v, list[v])
  43. end
  44. end
  45. local function split_cmdline(cmdline)
  46. local split = {}
  47. for i in string.gmatch(cmdline, "%S+") do
  48. table.insert(split,i)
  49. end
  50. return split
  51. end
  52. local function docmd(cmdline, print, fd)
  53. local split = split_cmdline(cmdline)
  54. local command = split[1]
  55. local cmd = COMMAND[command]
  56. local ok, list
  57. if cmd then
  58. ok, list = pcall(cmd, table.unpack(split,2))
  59. else
  60. cmd = COMMANDX[command]
  61. if cmd then
  62. split.fd = fd
  63. split[1] = cmdline
  64. ok, list = pcall(cmd, split)
  65. else
  66. print("Invalid command, type help for command list")
  67. end
  68. end
  69. if ok then
  70. if list then
  71. if type(list) == "string" then
  72. print(list)
  73. else
  74. dump_list(print, list)
  75. end
  76. end
  77. print("<CMD OK>")
  78. else
  79. print(list)
  80. print("<CMD Error>")
  81. end
  82. end
  83. local function console_main_loop(stdin, print, addr)
  84. print("Welcome to skynet console")
  85. skynet.error(addr, "connected")
  86. local ok, err = pcall(function()
  87. while true do
  88. local cmdline = socket.readline(stdin, "\n")
  89. if not cmdline then
  90. break
  91. end
  92. if cmdline:sub(1,4) == "GET " then
  93. -- http
  94. local code, url = httpd.read_request(sockethelper.readfunc(stdin, cmdline.. "\n"), 8192)
  95. local cmdline = url:sub(2):gsub("/"," ")
  96. docmd(cmdline, print, stdin)
  97. break
  98. end
  99. if cmdline ~= "" then
  100. docmd(cmdline, print, stdin)
  101. end
  102. end
  103. end)
  104. if not ok then
  105. skynet.error(stdin, err)
  106. end
  107. skynet.error(addr, "disconnect")
  108. socket.close(stdin)
  109. end
  110. skynet.start(function()
  111. local listen_socket, ip, port = socket.listen (ip, port)
  112. skynet.error("Start debug console at " .. ip .. ":" .. port)
  113. socket.start(listen_socket , function(id, addr)
  114. local function print(...)
  115. local t = { ... }
  116. for k,v in ipairs(t) do
  117. t[k] = tostring(v)
  118. end
  119. socket.write(id, table.concat(t,"\t"))
  120. socket.write(id, "\n")
  121. end
  122. socket.start(id)
  123. skynet.fork(console_main_loop, id , print, addr)
  124. end)
  125. end)
  126. function COMMAND.help()
  127. return {
  128. help = "This help message",
  129. list = "List all the service",
  130. stat = "Dump all stats",
  131. info = "info address : get service infomation",
  132. exit = "exit address : kill a lua service",
  133. kill = "kill address : kill service",
  134. mem = "mem : show memory status",
  135. gc = "gc : force every lua service do garbage collect",
  136. start = "lanuch a new lua service",
  137. snax = "lanuch a new snax service",
  138. clearcache = "clear lua code cache",
  139. service = "List unique service",
  140. task = "task address : show service task detail",
  141. uniqtask = "task address : show service unique task detail",
  142. inject = "inject address luascript.lua",
  143. logon = "logon address",
  144. logoff = "logoff address",
  145. log = "launch a new lua service with log",
  146. debug = "debug address : debug a lua service",
  147. signal = "signal address sig",
  148. cmem = "Show C memory info",
  149. jmem = "Show jemalloc mem stats",
  150. ping = "ping address",
  151. call = "call address ...",
  152. trace = "trace address [proto] [on|off]",
  153. netstat = "netstat : show netstat",
  154. profactive = "profactive [on|off] : active/deactive jemalloc heap profilling",
  155. dumpheap = "dumpheap : dump heap profilling",
  156. killtask = "killtask address threadname : threadname listed by task",
  157. dbgcmd = "run address debug command",
  158. }
  159. end
  160. function COMMAND.clearcache()
  161. codecache.clear()
  162. end
  163. function COMMAND.start(...)
  164. local ok, addr = pcall(skynet.newservice, ...)
  165. if ok then
  166. if addr then
  167. return { [skynet.address(addr)] = ... }
  168. else
  169. return "Exit"
  170. end
  171. else
  172. return "Failed"
  173. end
  174. end
  175. function COMMAND.log(...)
  176. local ok, addr = pcall(skynet.call, ".launcher", "lua", "LOGLAUNCH", "snlua", ...)
  177. if ok then
  178. if addr then
  179. return { [skynet.address(addr)] = ... }
  180. else
  181. return "Failed"
  182. end
  183. else
  184. return "Failed"
  185. end
  186. end
  187. function COMMAND.snax(...)
  188. local ok, s = pcall(snax.newservice, ...)
  189. if ok then
  190. local addr = s.handle
  191. return { [skynet.address(addr)] = ... }
  192. else
  193. return "Failed"
  194. end
  195. end
  196. function COMMAND.service()
  197. return skynet.call("SERVICE", "lua", "LIST")
  198. end
  199. local function adjust_address(address)
  200. local prefix = address:sub(1,1)
  201. if prefix == '.' then
  202. return assert(skynet.localname(address), "Not a valid name")
  203. elseif prefix ~= ':' then
  204. address = assert(tonumber("0x" .. address), "Need an address") | (skynet.harbor(skynet.self()) << 24)
  205. end
  206. return address
  207. end
  208. function COMMAND.list()
  209. return skynet.call(".launcher", "lua", "LIST")
  210. end
  211. local function timeout(ti)
  212. if ti then
  213. ti = tonumber(ti)
  214. if ti <= 0 then
  215. ti = nil
  216. end
  217. else
  218. ti = TIMEOUT
  219. end
  220. return ti
  221. end
  222. function COMMAND.stat(ti)
  223. return skynet.call(".launcher", "lua", "STAT", timeout(ti))
  224. end
  225. function COMMAND.mem(ti)
  226. return skynet.call(".launcher", "lua", "MEM", timeout(ti))
  227. end
  228. function COMMAND.kill(address)
  229. return skynet.call(".launcher", "lua", "KILL", adjust_address(address))
  230. end
  231. function COMMAND.gc(ti)
  232. return skynet.call(".launcher", "lua", "GC", timeout(ti))
  233. end
  234. function COMMAND.exit(address)
  235. skynet.send(adjust_address(address), "debug", "EXIT")
  236. end
  237. function COMMAND.inject(address, filename, ...)
  238. address = adjust_address(address)
  239. local f = io.open(filename, "rb")
  240. if not f then
  241. return "Can't open " .. filename
  242. end
  243. local source = f:read "*a"
  244. f:close()
  245. local ok, output = skynet.call(address, "debug", "RUN", source, filename, ...)
  246. if ok == false then
  247. error(output)
  248. end
  249. return output
  250. end
  251. function COMMAND.dbgcmd(address, cmd, ...)
  252. address = adjust_address(address)
  253. return skynet.call(address, "debug", cmd, ...)
  254. end
  255. function COMMAND.task(address)
  256. return COMMAND.dbgcmd(address, "TASK")
  257. end
  258. function COMMAND.killtask(address, threadname)
  259. return COMMAND.dbgcmd(address, "KILLTASK", threadname)
  260. end
  261. function COMMAND.uniqtask(address)
  262. return COMMAND.dbgcmd(address, "UNIQTASK")
  263. end
  264. function COMMAND.info(address, ...)
  265. return COMMAND.dbgcmd(address, "INFO", ...)
  266. end
  267. function COMMANDX.debug(cmd)
  268. local address = adjust_address(cmd[2])
  269. local agent = skynet.newservice "debug_agent"
  270. local stop
  271. local term_co = coroutine.running()
  272. local function forward_cmd()
  273. repeat
  274. -- notice : It's a bad practice to call socket.readline from two threads (this one and console_main_loop), be careful.
  275. skynet.call(agent, "lua", "ping") -- detect agent alive, if agent exit, raise error
  276. local cmdline = socket.readline(cmd.fd, "\n")
  277. cmdline = cmdline and cmdline:gsub("(.*)\r$", "%1")
  278. if not cmdline then
  279. skynet.send(agent, "lua", "cmd", "cont")
  280. break
  281. end
  282. skynet.send(agent, "lua", "cmd", cmdline)
  283. until stop or cmdline == "cont"
  284. end
  285. skynet.fork(function()
  286. pcall(forward_cmd)
  287. if not stop then -- block at skynet.call "start"
  288. term_co = nil
  289. else
  290. skynet.wakeup(term_co)
  291. end
  292. end)
  293. local ok, err = skynet.call(agent, "lua", "start", address, cmd.fd)
  294. stop = true
  295. if term_co then
  296. -- wait for fork coroutine exit.
  297. skynet.wait(term_co)
  298. end
  299. if not ok then
  300. error(err)
  301. end
  302. end
  303. function COMMAND.logon(address)
  304. address = adjust_address(address)
  305. core.command("LOGON", skynet.address(address))
  306. end
  307. function COMMAND.logoff(address)
  308. address = adjust_address(address)
  309. core.command("LOGOFF", skynet.address(address))
  310. end
  311. function COMMAND.signal(address, sig)
  312. address = skynet.address(adjust_address(address))
  313. if sig then
  314. core.command("SIGNAL", string.format("%s %d",address,sig))
  315. else
  316. core.command("SIGNAL", address)
  317. end
  318. end
  319. function COMMAND.cmem()
  320. local info = memory.info()
  321. local tmp = {}
  322. for k,v in pairs(info) do
  323. tmp[skynet.address(k)] = v
  324. end
  325. tmp.total = memory.total()
  326. tmp.block = memory.block()
  327. return tmp
  328. end
  329. function COMMAND.jmem()
  330. local info = memory.jestat()
  331. local tmp = {}
  332. for k,v in pairs(info) do
  333. tmp[k] = string.format("%11d %8.2f Mb", v, v/1048576)
  334. end
  335. return tmp
  336. end
  337. function COMMAND.ping(address)
  338. address = adjust_address(address)
  339. local ti = skynet.now()
  340. skynet.call(address, "debug", "PING")
  341. ti = skynet.now() - ti
  342. return tostring(ti)
  343. end
  344. local function toboolean(x)
  345. return x and (x == "true" or x == "on")
  346. end
  347. function COMMAND.trace(address, proto, flag)
  348. address = adjust_address(address)
  349. if flag == nil then
  350. if proto == "on" or proto == "off" then
  351. proto = toboolean(proto)
  352. end
  353. else
  354. flag = toboolean(flag)
  355. end
  356. skynet.call(address, "debug", "TRACELOG", proto, flag)
  357. end
  358. function COMMANDX.call(cmd)
  359. local address = adjust_address(cmd[2])
  360. local cmdline = assert(cmd[1]:match("%S+%s+%S+%s(.+)") , "need arguments")
  361. local args_func = assert(load("return " .. cmdline, "debug console", "t", {}), "Invalid arguments")
  362. local args = table.pack(pcall(args_func))
  363. if not args[1] then
  364. error(args[2])
  365. end
  366. local rets = table.pack(skynet.call(address, "lua", table.unpack(args, 2, args.n)))
  367. return rets
  368. end
  369. local function bytes(size)
  370. if size == nil or size == 0 then
  371. return
  372. end
  373. if size < 1024 then
  374. return size
  375. end
  376. if size < 1024 * 1024 then
  377. return tostring(size/1024) .. "K"
  378. end
  379. return tostring(size/(1024*1024)) .. "M"
  380. end
  381. local function convert_stat(info)
  382. local now = skynet.now()
  383. local function time(t)
  384. if t == nil then
  385. return
  386. end
  387. t = now - t
  388. if t < 6000 then
  389. return tostring(t/100) .. "s"
  390. end
  391. local hour = t // (100*60*60)
  392. t = t - hour * 100 * 60 * 60
  393. local min = t // (100*60)
  394. t = t - min * 100 * 60
  395. local sec = t / 100
  396. return string.format("%s%d:%.2gs",hour == 0 and "" or (hour .. ":"),min,sec)
  397. end
  398. info.address = skynet.address(info.address)
  399. info.read = bytes(info.read)
  400. info.write = bytes(info.write)
  401. info.wbuffer = bytes(info.wbuffer)
  402. info.rtime = time(info.rtime)
  403. info.wtime = time(info.wtime)
  404. end
  405. function COMMAND.netstat()
  406. local stat = socket.netstat()
  407. for _, info in ipairs(stat) do
  408. convert_stat(info)
  409. end
  410. return stat
  411. end
  412. function COMMAND.dumpheap()
  413. memory.dumpheap()
  414. end
  415. function COMMAND.profactive(flag)
  416. if flag ~= nil then
  417. if flag == "on" or flag == "off" then
  418. flag = toboolean(flag)
  419. end
  420. memory.profactive(flag)
  421. end
  422. local active = memory.profactive()
  423. return "heap profilling is ".. (active and "active" or "deactive")
  424. end