Repository navigation
Expand file tree
/
Copy pathModule:Availability.lua
More file actions
308 lines (285 loc) · 8.39 KB
/
Copy pathModule:Availability.lua
File metadata and controls
308 lines (285 loc) · 8.39 KB
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
300
301
302
303
304
305
306
307
308
local p = {}
local site = '페미위키:대문'
local function load(name)
return mw.loadJsonData('Module:가용성/' .. name)
end
local function list(t)
local out = {}
for _, v in ipairs(t) do
out[#out + 1] = v
end
return out
end
-- Colour class for a fraction of probes that passed.
local function grade(x)
if x == nil then
return 'av-none'
elseif x >= 0.99999 then
return 'av-a'
elseif x >= 0.999 then
return 'av-b'
elseif x >= 0.99 then
return 'av-c'
elseif x >= 0.95 then
return 'av-d'
end
return 'av-e'
end
-- 100, 99.9, 99.63, 87.93: as many decimals as it takes to be honest, at most two.
local function percent(x)
if x == nil then
return '-'
end
local s = string.format('%.2f', x * 100):gsub('0+$', ''):gsub('%.$', '')
return s
end
local function mean(values)
local sum, n = 0, 0
for _, v in ipairs(values) do
if v ~= nil then
sum, n = sum + v, n + 1
end
end
if n == 0 then
return nil
end
return sum / n
end
local function daysIn(year, month)
return tonumber(os.date('%d', os.time({ year = year, month = month + 1, day = 0 })))
end
-- 2026-09 -> 가용성/2026년 9월, the page people write the month's story on.
local function pageOf(m)
return string.format('가용성/%s년 %d월', m:sub(1, 4), tonumber(m:sub(6, 7)))
end
local function styles(frame)
return frame:extensionTag('templatestyles', '', { src = 'Availability/styles.css' })
end
-- Stretches of hours in which probes failed and it was more than a blip: a run of
-- consecutive hours below 100%, kept when it lasted two hours or dipped below 99%.
local function stretches(hours)
local runs, run = {}, nil
for i, v in ipairs(hours) do
if v ~= nil and v < 0.99999 then
if run and run.last == i - 1 then
run.last, run.sum, run.n = i, run.sum + v, run.n + 1
run.worst = math.min(run.worst, v)
else
run = { first = i, last = i, sum = v, n = 1, worst = v }
runs[#runs + 1] = run
end
end
end
local notable = {}
for _, r in ipairs(runs) do
if r.worst < 0.99 or r.n >= 2 then
notable[#notable + 1] = r
end
end
return notable
end
local function describe(r, m)
local d1, h1 = math.floor((r.first - 1) / 24) + 1, (r.first - 1) % 24
local d2, h2 = math.floor((r.last - 1) / 24) + 1, (r.last - 1) % 24
return string.format(
'* %s-%02d %02d시부터 %s-%02d %02d시까지, %d시간, %s%% (%s%%)',
m,
d1,
h1,
m,
d2,
h2 + 1,
r.n,
percent(r.sum / r.n),
percent(r.worst)
)
end
-- The list of stretches, folded away when it is long enough to be noise.
local function stretchList(frame, items)
if #items == 0 then
return {}
end
local note =
'실패한 요청이 있었던 시간대입니다. 값은 그 동안 성공한 요청의 비율이고, 괄호는 가장 나빴던 한 시간입니다.'
if #items > 4 then
local summary = frame:extensionTag('summary', string.format('실패한 시간대 %d곳', #items))
return { '', frame:extensionTag('details', summary .. '\n' .. note .. '\n' .. table.concat(items, '\n')) }
end
local out = { '', note }
for _, item in ipairs(items) do
out[#out + 1] = item
end
return out
end
local function append(out, more)
for _, line in ipairs(more) do
out[#out + 1] = line
end
end
-- One check's months of a year as a row of cells, with the year's mean at the end.
local function yearRow(byMonth, y, linkYear)
local cells, values = {}, {}
for m = 1, 12 do
local key = string.format('%s-%02d', y, m)
local e = byMonth[key]
if e then
values[#values + 1] = e.uptime
cells[#cells + 1] = string.format('| class="%s" | [[%s|%s]]', grade(e.uptime), pageOf(key), percent(e.uptime))
else
cells[#cells + 1] = '| class="av-none" |'
end
end
local year = mean(values)
local text = percent(year)
if linkYear then
text = string.format('[[가용성/%s년|%s]]', y, text)
end
cells[#cells + 1] = string.format('| class="%s" | %s', grade(year), text)
return table.concat(cells, '\n')
end
local function checkNames(checks)
local names, others = {}, {}
for name in pairs(checks) do
if name ~= site then
others[#others + 1] = name
end
end
table.sort(others)
if checks[site] then
names[1] = site
end
for _, name in ipairs(others) do
names[#names + 1] = name
end
return names
end
-- Years by months, one table per check, from the monthly summaries.
function p.status(frame)
local ok, entries = pcall(load, 'months.json')
if not ok then
return '아직 수집된 달이 없습니다.'
end
local months = list(entries)
local all = {}
for _, e in ipairs(months) do
for name in pairs(e.checks) do
all[name] = true
end
end
local names = checkNames(all)
local out = { styles(frame) }
for _, name in ipairs(names) do
local byMonth, years, order = {}, {}, {}
for _, e in ipairs(months) do
local y = e.month:sub(1, 4)
if e.checks[name] and not years[y] then
years[y] = true
order[#order + 1] = y
end
byMonth[e.month] = e.checks[name]
end
table.sort(order, function(a, b)
return a > b
end)
out[#out + 1] = '=== ' .. name .. ' ==='
out[#out + 1] = '{| class="wikitable av-grid"'
out[#out + 1] = '! 연도 !! 1 !! 2 !! 3 !! 4 !! 5 !! 6 !! 7 !! 8 !! 9 !! 10 !! 11 !! 12 !! 연간'
for _, y in ipairs(order) do
out[#out + 1] = '|-\n| ' .. y .. '\n' .. yearRow(byMonth, y, true)
end
out[#out + 1] = '|}'
end
return '\n' .. table.concat(out, '\n')
end
-- One year: the months as a row, then every stretch of failures in the year.
function p.year(frame)
local y = frame.args[1]
local ok, entries = pcall(load, 'months.json')
if not ok then
return '아직 수집된 달이 없습니다.'
end
local months = list(entries)
local all = {}
for _, e in ipairs(months) do
for name in pairs(e.checks) do
all[name] = true
end
end
local names = checkNames(all)
local data = {}
for _, e in ipairs(months) do
if e.month:sub(1, 4) == y then
local found, d = pcall(load, e.month .. '.json')
if found then
data[#data + 1] = d
end
end
end
local out = { styles(frame) }
for _, name in ipairs(names) do
local byMonth = {}
for _, e in ipairs(months) do
byMonth[e.month] = e.checks[name]
end
out[#out + 1] = '== ' .. name .. ' =='
out[#out + 1] = '{| class="wikitable av-grid"'
out[#out + 1] = '! 1 !! 2 !! 3 !! 4 !! 5 !! 6 !! 7 !! 8 !! 9 !! 10 !! 11 !! 12 !! 연간'
out[#out + 1] = '|-\n' .. yearRow(byMonth, y, false)
out[#out + 1] = '|}'
local items = {}
for _, d in ipairs(data) do
if d.checks[name] then
for _, r in ipairs(stretches(list(d.checks[name]))) do
items[#items + 1] = describe(r, d.month)
end
end
end
append(out, stretchList(frame, items))
end
return '\n' .. table.concat(out, '\n')
end
-- Days by hours for one month, one table per check.
function p.month(frame)
local m = frame.args[1]
local ok, data = pcall(load, m .. '.json')
if not ok then
return m .. '에는 수집된 자료가 없습니다.'
end
local year, month = tonumber(m:sub(1, 4)), tonumber(m:sub(6, 7))
local days = daysIn(year, month)
local names = checkNames(data.checks)
local out = { styles(frame) }
for _, name in ipairs(names) do
local hours = list(data.checks[name])
local monthly = mean(hours)
out[#out + 1] = string.format('== %s ==', name)
out[#out + 1] = string.format('%s 가용성 %s%%.', m, percent(monthly))
out[#out + 1] = '{| class="wikitable av-grid"'
out[#out + 1] = '! 날짜 !! 가용성 !! 0시부터 23시까지'
for d = 1, days do
local slice, boxes = {}, {}
for h = 0, 23 do
local v = hours[(d - 1) * 24 + h + 1]
slice[#slice + 1] = v
boxes[#boxes + 1] = string.format('<span class="%s" title="%02d시 %s%%"></span>', grade(v), h, percent(v))
end
local day = mean(slice)
out[#out + 1] = string.format(
'|-\n| class="av-day" | %s-%02d\n| class="%s" | %s\n| <span class="av-hours">%s</span>',
m,
d,
grade(day),
percent(day),
table.concat(boxes)
)
end
out[#out + 1] = '|}'
local items = {}
for _, r in ipairs(stretches(hours)) do
items[#items + 1] = describe(r, m)
end
append(out, stretchList(frame, items))
end
return '\n' .. table.concat(out, '\n')
end
return p