You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

fuzzy_stat.lua 8.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. local rspamd_util = require "rspamd_util"
  2. local lua_util = require "lua_util"
  3. local opts = {}
  4. local argparse = require "argparse"
  5. local parser = argparse()
  6. :name "rspamadm control fuzzystat"
  7. :description "Shows help for the specified configuration options"
  8. :help_description_margin(32)
  9. parser:flag "--no-ips"
  10. :description "No IPs stats"
  11. parser:flag "--no-keys"
  12. :description "No keys stats"
  13. parser:flag "--short"
  14. :description "Short output mode"
  15. parser:flag "-n --number"
  16. :description "Disable numbers humanization"
  17. parser:option "--sort"
  18. :description "Sort order"
  19. :convert {
  20. checked = "checked",
  21. matched = "matched",
  22. errors = "errors",
  23. name = "name"
  24. }
  25. local function add_data(target, src)
  26. for k,v in pairs(src) do
  27. if type(v) == 'number' then
  28. if target[k] then
  29. target[k] = target[k] + v
  30. else
  31. target[k] = v
  32. end
  33. elseif k == 'ips' then
  34. if not target['ips'] then target['ips'] = {} end
  35. -- Iterate over IPs
  36. for ip,st in pairs(v) do
  37. if not target['ips'][ip] then target['ips'][ip] = {} end
  38. add_data(target['ips'][ip], st)
  39. end
  40. elseif k == 'keypair' then
  41. if type(v.extensions) == 'table' then
  42. if type(v.extensions.name) == 'string' then
  43. target.name = v.extensions.name
  44. end
  45. end
  46. end
  47. end
  48. end
  49. local function print_num(num)
  50. if opts['n'] or opts['number'] then
  51. return tostring(num)
  52. else
  53. return rspamd_util.humanize_number(num)
  54. end
  55. end
  56. local function print_stat(st, tabs)
  57. if st['checked'] then
  58. if st.checked_per_hour then
  59. print(string.format('%sChecked: %s (%s per hour in average)', tabs,
  60. print_num(st['checked']), print_num(st['checked_per_hour'])))
  61. else
  62. print(string.format('%sChecked: %s', tabs, print_num(st['checked'])))
  63. end
  64. end
  65. if st['matched'] then
  66. if st.matched_per_hour then
  67. print(string.format('%sMatched: %s (%s per hour in average)', tabs,
  68. print_num(st['matched']), print_num(st['matched_per_hour'])))
  69. else
  70. print(string.format('%sMatched: %s', tabs, print_num(st['matched'])))
  71. end
  72. end
  73. if st['errors'] then
  74. print(string.format('%sErrors: %s', tabs, print_num(st['errors'])))
  75. end
  76. if st['added'] then
  77. print(string.format('%sAdded: %s', tabs, print_num(st['added'])))
  78. end
  79. if st['deleted'] then
  80. print(string.format('%sDeleted: %s', tabs, print_num(st['deleted'])))
  81. end
  82. end
  83. -- Sort by checked
  84. local function sort_hash_table(tbl, sort_opts, key_key)
  85. local res = {}
  86. for k,v in pairs(tbl) do
  87. table.insert(res, {[key_key] = k, data = v})
  88. end
  89. local function sort_order(elt)
  90. local key = 'checked'
  91. local sort_res = 0
  92. if sort_opts['sort'] then
  93. if sort_opts['sort'] == 'matched' then
  94. key = 'matched'
  95. elseif sort_opts['sort'] == 'errors' then
  96. key = 'errors'
  97. elseif sort_opts['sort'] == 'name' then
  98. return elt[key_key]
  99. end
  100. end
  101. if elt.data[key] then
  102. sort_res = elt.data[key]
  103. end
  104. return sort_res
  105. end
  106. table.sort(res, function(a, b)
  107. return sort_order(a) > sort_order(b)
  108. end)
  109. return res
  110. end
  111. local function add_result(dst, src, k)
  112. if type(src) == 'table' then
  113. if type(dst) == 'number' then
  114. -- Convert dst to table
  115. dst = {dst}
  116. elseif type(dst) == 'nil' then
  117. dst = {}
  118. end
  119. for i,v in ipairs(src) do
  120. if dst[i] and k ~= 'fuzzy_stored' then
  121. dst[i] = dst[i] + v
  122. else
  123. dst[i] = v
  124. end
  125. end
  126. else
  127. if type(dst) == 'table' then
  128. if k ~= 'fuzzy_stored' then
  129. dst[1] = dst[1] + src
  130. else
  131. dst[1] = src
  132. end
  133. else
  134. if dst and k ~= 'fuzzy_stored' then
  135. dst = dst + src
  136. else
  137. dst = src
  138. end
  139. end
  140. end
  141. return dst
  142. end
  143. local function print_result(r)
  144. local function num_to_epoch(num)
  145. if num == 1 then
  146. return 'v0.6'
  147. elseif num == 2 then
  148. return 'v0.8'
  149. elseif num == 3 then
  150. return 'v0.9'
  151. elseif num == 4 then
  152. return 'v1.0+'
  153. elseif num == 5 then
  154. return 'v1.7+'
  155. end
  156. return '???'
  157. end
  158. if type(r) == 'table' then
  159. local res = {}
  160. for i,num in ipairs(r) do
  161. res[i] = string.format('(%s: %s)', num_to_epoch(i), print_num(num))
  162. end
  163. return table.concat(res, ', ')
  164. end
  165. return print_num(r)
  166. end
  167. return function(args, res)
  168. local res_ips = {}
  169. local res_databases = {}
  170. local wrk = res['workers']
  171. opts = parser:parse(args)
  172. if wrk then
  173. for _,pr in pairs(wrk) do
  174. -- processes cycle
  175. if pr['data'] then
  176. local id = pr['id']
  177. if id then
  178. local res_db = res_databases[id]
  179. if not res_db then
  180. res_db = {
  181. keys = {}
  182. }
  183. res_databases[id] = res_db
  184. end
  185. -- General stats
  186. for k,v in pairs(pr['data']) do
  187. if k ~= 'keys' and k ~= 'errors_ips' then
  188. res_db[k] = add_result(res_db[k], v, k)
  189. elseif k == 'errors_ips' then
  190. -- Errors ips
  191. if not res_db['errors_ips'] then
  192. res_db['errors_ips'] = {}
  193. end
  194. for ip,nerrors in pairs(v) do
  195. if not res_db['errors_ips'][ip] then
  196. res_db['errors_ips'][ip] = nerrors
  197. else
  198. res_db['errors_ips'][ip] = nerrors + res_db['errors_ips'][ip]
  199. end
  200. end
  201. end
  202. end
  203. if pr['data']['keys'] then
  204. local res_keys = res_db['keys']
  205. if not res_keys then
  206. res_keys = {}
  207. res_db['keys'] = res_keys
  208. end
  209. -- Go through keys in input
  210. for k,elts in pairs(pr['data']['keys']) do
  211. -- keys cycle
  212. if not res_keys[k] then
  213. res_keys[k] = {}
  214. end
  215. add_data(res_keys[k], elts)
  216. if elts['ips'] then
  217. for ip,v in pairs(elts['ips']) do
  218. if not res_ips[ip] then
  219. res_ips[ip] = {}
  220. end
  221. add_data(res_ips[ip], v)
  222. end
  223. end
  224. end
  225. end
  226. end
  227. end
  228. end
  229. end
  230. -- General stats
  231. for db,st in pairs(res_databases) do
  232. print(string.format('Statistics for storage %s', db))
  233. for k,v in pairs(st) do
  234. if k ~= 'keys' and k ~= 'errors_ips' then
  235. print(string.format('%s: %s', k, print_result(v)))
  236. end
  237. end
  238. print('')
  239. local res_keys = st['keys']
  240. if res_keys and not opts['no_keys'] and not opts['short'] then
  241. print('Keys statistics:')
  242. -- Convert into an array to allow sorting
  243. local sorted_keys = sort_hash_table(res_keys, opts, 'key')
  244. for _, key in ipairs(sorted_keys) do
  245. local key_stat = key.data
  246. if key_stat.name then
  247. print(string.format('Key id: %s, name: %s', key.key, key_stat.name))
  248. else
  249. print(string.format('Key id: %s', key.key))
  250. end
  251. print_stat(key_stat, '\t')
  252. if key_stat['ips'] and not opts['no_ips'] then
  253. print('')
  254. print('\tIPs stat:')
  255. local sorted_ips = sort_hash_table(key_stat['ips'], opts, 'ip')
  256. for _,v in ipairs(sorted_ips) do
  257. print(string.format('\t%s', v['ip']))
  258. print_stat(v['data'], '\t\t')
  259. print('')
  260. end
  261. end
  262. print('')
  263. end
  264. end
  265. if st['errors_ips'] and not opts['no_ips'] and not opts['short'] then
  266. print('')
  267. print('Errors IPs statistics:')
  268. local ip_stat = st['errors_ips']
  269. local ips = lua_util.keys(ip_stat)
  270. -- Reverse sort by number of errors
  271. table.sort(ips, function(a, b)
  272. return ip_stat[a] > ip_stat[b]
  273. end)
  274. for _, ip in ipairs(ips) do
  275. print(string.format('%s: %s', ip, print_result(ip_stat[ip])))
  276. end
  277. print('')
  278. end
  279. end
  280. if not opts['no_ips'] and not opts['short'] then
  281. print('')
  282. print('IPs statistics:')
  283. local sorted_ips = sort_hash_table(res_ips, opts)
  284. for _, v in ipairs(sorted_ips) do
  285. print(string.format('%s', v['ip']))
  286. print_stat(v['data'], '\t')
  287. print('')
  288. end
  289. end
  290. end