summaryrefslogtreecommitdiffstats
path: root/lualib/rspamadm/fuzzy_stat.lua
blob: 38202be20798bdd3390a258104ae937a6330ccf0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
local rspamd_util = require "rspamd_util"
local lua_util = require "lua_util"
local opts = {}

local argparse = require "argparse"
local parser = argparse()
    :name "rspamadm confighelp"
    :description "Shows help for the specified configuration options"
    :help_description_margin(32)
parser:flag "--no-ips"
      :description "No IPs stats"
parser:flag "--no-keys"
      :description "No keys stats"
parser:flag "--short"
      :description "Short output mode"
parser:flag "-n --number"
      :description "Disable numbers humanization"
parser:option "-s --sort"
      :description "Sort order"
      :convert {
        matched = "matched",
        errors = "errors",
        ip = "ip"
      }

local function add_data(target, src)
  for k,v in pairs(src) do
    if k ~= 'ips' then
      if target[k] then
        target[k] = target[k] + v
      else
        target[k] = v
      end
    else
      if not target['ips'] then target['ips'] = {} end
      -- Iterate over IPs
      for ip,st in pairs(v) do
        if not target['ips'][ip] then target['ips'][ip] = {} end
        add_data(target['ips'][ip], st)
      end
    end
  end
end

local function print_num(num)
  if opts['n'] or opts['number'] then
    return tostring(num)
  else
    return rspamd_util.humanize_number(num)
  end
end

local function print_stat(st, tabs)
  if st['checked'] then
    print(string.format('%sChecked: %s', tabs, print_num(st['checked'])))
  end
  if st['matched'] then
    print(string.format('%sMatched: %s', tabs, print_num(st['matched'])))
  end
  if st['errors'] then
    print(string.format('%sErrors: %s', tabs, print_num(st['errors'])))
  end
  if st['added'] then
    print(string.format('%sAdded: %s', tabs, print_num(st['added'])))
  end
  if st['deleted'] then
    print(string.format('%sDeleted: %s', tabs, print_num(st['deleted'])))
  end
end

-- Sort by checked
local function sort_ips(tbl, sort_opts)
  local res = {}
  for k,v in pairs(tbl) do
    table.insert(res, {ip = k, data = v})
  end

  local function sort_order(elt)
    local key = 'checked'
    local _res = 0

    if sort_opts['sort'] then
      if sort_opts['sort'] == 'matched' then
        key = 'matched'
      elseif sort_opts['sort'] == 'errors' then
        key = 'errors'
      elseif sort_opts['sort'] == 'ip' then
        return elt['ip']
      end
    end

    if elt['data'][key] then
      _res = elt['data'][key]
    end

    return _res
  end

  table.sort(res, function(a, b)
    return sort_order(a) > sort_order(b)
  end)

  return res
end

local function add_result(dst, src, k)
  if type(src) == 'table' then
    if type(dst) == 'number' then
      -- Convert dst to table
      dst = {dst}
    elseif type(dst) == 'nil' then
      dst = {}
    end

    for i,v in ipairs(src) do
      if dst[i] and k ~= 'fuzzy_stored' then
        dst[i] = dst[i] + v
      else
        dst[i] = v
      end
    end
  else
    if type(dst) == 'table' then
      if k ~= 'fuzzy_stored' then
        dst[1] = dst[1] + src
      else
        dst[1] = src
      end
    else
      if dst and k ~= 'fuzzy_stored' then
        dst = dst + src
      else
        dst = src
      end
    end
  end

  return dst
end

local function print_result(r)
  local function num_to_epoch(num)
    if num == 1 then
      return 'v0.6'
    elseif num == 2 then
      return 'v0.8'
    elseif num == 3 then
      return 'v0.9'
    elseif num == 4 then
      return 'v1.0+'
    elseif num == 5 then
      return 'v1.7+'
    end
    return '???'
  end
  if type(r) == 'table' then
    local res = {}
    for i,num in ipairs(r) do
      res[i] = string.format('(%s: %s)', num_to_epoch(i), print_num(num))
    end

    return table.concat(res, ', ')
  end

  return print_num(r)
end

return function(args, res)
  local res_ips = {}
  local res_databases = {}
  local wrk = res['workers']
  opts = parser:parse(args)

  if wrk then
    for _,pr in pairs(wrk) do
      -- processes cycle
      if pr['data'] then
        local id = pr['id']

        if id then
          local res_db = res_databases[id]
          if not res_db then
            res_db = {
              keys = {}
            }
            res_databases[id] = res_db
          end

          -- General stats
          for k,v in pairs(pr['data']) do
            if k ~= 'keys' and k ~= 'errors_ips' then
              res_db[k] = add_result(res_db[k], v, k)
            elseif k == 'errors_ips' then
              -- Errors ips
              if not res_db['errors_ips'] then
                res_db['errors_ips'] = {}
              end
              for ip,nerrors in pairs(v) do
                if not res_db['errors_ips'][ip] then
                  res_db['errors_ips'][ip] = nerrors
                else
                  res_db['errors_ips'][ip] = nerrors + res_db['errors_ips'][ip]
                end
              end
            end
          end

          if pr['data']['keys'] then
            local res_keys = res_db['keys']
            if not res_keys then
              res_keys = {}
              res_db['keys'] = res_keys
            end
            -- Go through keys in input
            for k,elts in pairs(pr['data']['keys']) do
              -- keys cycle
              if not res_keys[k] then
                res_keys[k] = {}
              end

              add_data(res_keys[k], elts)

              if elts['ips'] then
                for ip,v in pairs(elts['ips']) do
                  if not res_ips[ip] then
                    res_ips[ip] = {}
                  end
                  add_data(res_ips[ip], v)
                end
              end
            end
          end
        end
      end
    end
  end

  -- General stats
  for db,st in pairs(res_databases) do
    print(string.format('Statistics for storage %s', db))

    for k,v in pairs(st) do
      if k ~= 'keys' and k ~= 'errors_ips' then
        print(string.format('%s: %s', k, print_result(v)))
      end
    end
    print('')

    local res_keys = st['keys']
    if res_keys and not opts['no-keys'] and not opts['short'] then
      print('Keys statistics:')
      for k,_st in pairs(res_keys) do
        print(string.format('Key id: %s', k))
        print_stat(_st, '\t')

        if _st['ips'] and not opts['no-ips'] then
          print('')
          print('\tIPs stat:')
          local sorted_ips = sort_ips(_st['ips'], opts)

          for _,v in ipairs(sorted_ips) do
            print(string.format('\t%s', v['ip']))
            print_stat(v['data'], '\t\t')
            print('')
          end
        end

        print('')
      end
    end
    if st['errors_ips'] and not opts['no-ips'] and not opts['short'] then
      print('')
      print('Errors IPs statistics:')
      local ip_stat = st['errors_ips']
      local ips = lua_util.keys(ip_stat)
      -- Reverse sort by number of errors
      table.sort(ips, function(a, b)
        return ip_stat[a] > ip_stat[b]
      end)
      for _, ip in ipairs(ips) do
        print(string.format('%s: %s', ip, print_result(ip_stat[ip])))
      end
      print('')
    end
  end

  if not opts['no-ips'] and not opts['short'] then
    print('')
    print('IPs statistics:')

    local sorted_ips = sort_ips(res_ips, opts)
    for _, v in ipairs(sorted_ips) do
      print(string.format('%s', v['ip']))
      print_stat(v['data'], '\t')
      print('')
    end
  end
end