Comparison

plugins/mod_http_file_share.lua @ 12179:5e68635cdc2c

mod_http_file_share: Always measure total disk usage for statistics! Metrics available or not depending on configuration is weird, even tho it might be expensive to calculate and it's only really needed when there is a global quota. Default quota is set to infinity, which is essentially what it was. Reports NaN if there is an error, which should count as over the infinite default quota.
author Kim Alvefur <zash@zash.se>
date Tue, 11 Jan 2022 04:15:29 +0100
parent 12008:c01532ae6a3b
child 12227:88958c0ecab3
comparison
equal deleted inserted replaced
12178:0aa99a6dfb3e 12179:5e68635cdc2c
18 local dt = require "util.datetime"; 18 local dt = require "util.datetime";
19 local hi = require "util.human.units"; 19 local hi = require "util.human.units";
20 local cache = require "util.cache"; 20 local cache = require "util.cache";
21 local lfs = require "lfs"; 21 local lfs = require "lfs";
22 22
23 local unknown = math.abs(0/0);
24 local unlimited = math.huge;
25
23 local namespace = "urn:xmpp:http:upload:0"; 26 local namespace = "urn:xmpp:http:upload:0";
24 27
25 module:depends("disco"); 28 module:depends("disco");
26 29
27 module:add_identity("store", "file", module:get_option_string("name", "HTTP File Upload")); 30 module:add_identity("store", "file", module:get_option_string("name", "HTTP File Upload"));
36 local file_size_limit = module:get_option_number(module.name .. "_size_limit", 10 * 1024 * 1024); -- 10 MB 39 local file_size_limit = module:get_option_number(module.name .. "_size_limit", 10 * 1024 * 1024); -- 10 MB
37 local file_types = module:get_option_set(module.name .. "_allowed_file_types", {}); 40 local file_types = module:get_option_set(module.name .. "_allowed_file_types", {});
38 local safe_types = module:get_option_set(module.name .. "_safe_file_types", {"image/*","video/*","audio/*","text/plain"}); 41 local safe_types = module:get_option_set(module.name .. "_safe_file_types", {"image/*","video/*","audio/*","text/plain"});
39 local expiry = module:get_option_number(module.name .. "_expires_after", 7 * 86400); 42 local expiry = module:get_option_number(module.name .. "_expires_after", 7 * 86400);
40 local daily_quota = module:get_option_number(module.name .. "_daily_quota", file_size_limit*10); -- 100 MB / day 43 local daily_quota = module:get_option_number(module.name .. "_daily_quota", file_size_limit*10); -- 100 MB / day
41 local total_storage_limit = module:get_option_number(module.name.."_global_quota", nil); 44 local total_storage_limit = module:get_option_number(module.name.."_global_quota", unlimited);
42 45
43 local access = module:get_option_set(module.name .. "_access", {}); 46 local access = module:get_option_set(module.name .. "_access", {});
44 47
45 if not external_base_url then 48 if not external_base_url then
46 module:depends("http"); 49 module:depends("http");
58 filesize = { type = "modify"; condition = "not-acceptable"; text = "File too large"; 61 filesize = { type = "modify"; condition = "not-acceptable"; text = "File too large";
59 extra = {tag = st.stanza("file-too-large", {xmlns = namespace}):tag("max-file-size"):text(tostring(file_size_limit)) }; 62 extra = {tag = st.stanza("file-too-large", {xmlns = namespace}):tag("max-file-size"):text(tostring(file_size_limit)) };
60 }; 63 };
61 filesizefmt = { type = "modify"; condition = "bad-request"; text = "File size must be positive integer"; }; 64 filesizefmt = { type = "modify"; condition = "bad-request"; text = "File size must be positive integer"; };
62 quota = { type = "wait"; condition = "resource-constraint"; text = "Daily quota reached"; }; 65 quota = { type = "wait"; condition = "resource-constraint"; text = "Daily quota reached"; };
63 unknowntotal = { type = "wait"; condition = "undefined-condition"; text = "Server storage usage not yet calculated" };
64 outofdisk = { type = "wait"; condition = "resource-constraint"; text = "Server global storage quota reached" }; 66 outofdisk = { type = "wait"; condition = "resource-constraint"; text = "Server global storage quota reached" };
65 }); 67 });
66 68
67 local upload_cache = cache.new(1024); 69 local upload_cache = cache.new(1024);
68 local quota_cache = cache.new(1024); 70 local quota_cache = cache.new(1024);
69 71
70 local total_storage_usage = nil; 72 local total_storage_usage = unknown;
71 73
72 local measure_upload_cache_size = module:measure("upload_cache", "amount"); 74 local measure_upload_cache_size = module:measure("upload_cache", "amount");
73 local measure_quota_cache_size = module:measure("quota_cache", "amount"); 75 local measure_quota_cache_size = module:measure("quota_cache", "amount");
74 local measure_total_storage_usage = nil; 76 local measure_total_storage_usage = module:measure("total_storage", "amount", { unit = "bytes" });
75 if total_storage_limit then 77
78 do
76 local total, err = persist_stats:get(nil, "total"); 79 local total, err = persist_stats:get(nil, "total");
77 if not err then total_storage_usage = tonumber(total) or 0; end 80 if not err then
78 measure_total_storage_usage = module:measure("total_storage", "amount", { unit = "bytes" }); 81 total_storage_usage = tonumber(total) or 0;
82 end
79 end 83 end
80 84
81 module:hook_global("stats-update", function () 85 module:hook_global("stats-update", function ()
82 measure_upload_cache_size(upload_cache:count()); 86 measure_upload_cache_size(upload_cache:count());
83 measure_quota_cache_size(quota_cache:count()); 87 measure_quota_cache_size(quota_cache:count());
84 if total_storage_limit and total_storage_usage then 88 measure_total_storage_usage(total_storage_usage);
85 measure_total_storage_usage(total_storage_usage);
86 end
87 end); 89 end);
88 90
89 local buckets = {}; 91 local buckets = {};
90 for n = 10, 40, 2 do 92 for n = 10, 40, 2 do
91 local exp = math.floor(2 ^ n); 93 local exp = math.floor(2 ^ n);
93 if exp >= file_size_limit then break end 95 if exp >= file_size_limit then break end
94 end 96 end
95 local measure_uploads = module:measure("upload", "sizes", {buckets = buckets}); 97 local measure_uploads = module:measure("upload", "sizes", {buckets = buckets});
96 98
97 -- Convenience wrapper for logging file sizes 99 -- Convenience wrapper for logging file sizes
98 local function B(bytes) return hi.format(bytes, "B", "b"); end 100 local function B(bytes)
101 if bytes ~= bytes then
102 return "unknown"
103 elseif bytes == unlimited then
104 return "unlimited";
105 end
106 return hi.format(bytes, "B", "b");
107 end
99 108
100 local function get_filename(slot, create) 109 local function get_filename(slot, create)
101 return dm.getpath(slot, module.host, module.name, "bin", create) 110 return dm.getpath(slot, module.host, module.name, "bin", create)
102 end 111 end
103 112
139 end 148 end
140 if filesize > file_size_limit then 149 if filesize > file_size_limit then
141 return false, upload_errors.new("filesize"); 150 return false, upload_errors.new("filesize");
142 end 151 end
143 152
144 if total_storage_limit then 153 if total_storage_usage + filesize > total_storage_limit then
145 if not total_storage_usage then 154 module:log("warn", "Global storage quota reached, at %s / %s!", B(total_storage_usage), B(total_storage_limit));
146 return false, upload_errors.new("unknowntotal"); 155 return false, upload_errors.new("outofdisk");
147 elseif total_storage_usage + filesize > total_storage_limit then
148 module:log("warn", "Global storage quota reached, at %s!", B(total_storage_usage));
149 return false, upload_errors.new("outofdisk");
150 end
151 end 156 end
152 157
153 local uploader_quota = get_daily_quota(uploader); 158 local uploader_quota = get_daily_quota(uploader);
154 if uploader_quota + filesize > daily_quota then 159 if uploader_quota + filesize > daily_quota then
155 return false, upload_errors.new("quota"); 160 return false, upload_errors.new("quota");
215 if not slot then 220 if not slot then
216 origin.send(st.error_reply(stanza, storage_err)); 221 origin.send(st.error_reply(stanza, storage_err));
217 return true; 222 return true;
218 end 223 end
219 224
220 if total_storage_usage then 225 total_storage_usage = total_storage_usage + filesize;
221 total_storage_usage = total_storage_usage + filesize; 226 module:log("debug", "Total storage usage: %s / %s", B(total_storage_usage), B(total_storage_limit));
222 module:log("debug", "Global quota %s / %s", B(total_storage_usage), B(total_storage_limit));
223 end
224 227
225 local cached_quota = quota_cache:get(uploader); 228 local cached_quota = quota_cache:get(uploader);
226 if cached_quota and cached_quota.time > os.time()-86400 then 229 if cached_quota and cached_quota.time > os.time()-86400 then
227 cached_quota.size = cached_quota.size + filesize; 230 cached_quota.size = cached_quota.size + filesize;
228 quota_cache:set(uploader, cached_quota); 231 quota_cache:set(uploader, cached_quota);
470 prune_done(); 473 prune_done();
471 return; 474 return;
472 end 475 end
473 476
474 module:log("info", "Pruning expired files uploaded earlier than %s", dt.datetime(boundary_time)); 477 module:log("info", "Pruning expired files uploaded earlier than %s", dt.datetime(boundary_time));
475 if total_storage_usage then 478 module:log("debug", "Total storage usage: %s / %s", B(total_storage_usage), B(total_storage_limit));
476 module:log("debug", "Global quota %s / %s", B(total_storage_usage), B(total_storage_limit));
477 elseif total_storage_limit then
478 module:log("debug", "Global quota %s / %s", "not yet calculated", B(total_storage_limit));
479 end
480 479
481 local obsolete_uploads = array(); 480 local obsolete_uploads = array();
482 local num_expired = 0; 481 local num_expired = 0;
483 local size_sum = 0; 482 local size_sum = 0;
484 local problem_deleting = false; 483 local problem_deleting = false;
511 -- eventually the admin ought to notice and fix the permissions or 510 -- eventually the admin ought to notice and fix the permissions or
512 -- whatever the problem is. 511 -- whatever the problem is.
513 deletion_query = {ids = obsolete_uploads}; 512 deletion_query = {ids = obsolete_uploads};
514 end 513 end
515 514
516 if total_storage_usage then 515 total_storage_usage = total_storage_usage - size_sum;
517 total_storage_usage = total_storage_usage - size_sum; 516 module:log("debug", "Total storage usage: %s / %s", B(total_storage_usage), B(total_storage_limit));
518 module:log("debug", "Global quota %s / %s", B(total_storage_usage), B(total_storage_limit)); 517 persist_stats:set(nil, "total", total_storage_usage);
519 persist_stats:set(nil, "total", total_storage_usage);
520 end
521 518
522 if #obsolete_uploads == 0 then 519 if #obsolete_uploads == 0 then
523 module:log("debug", "No metadata to remove"); 520 module:log("debug", "No metadata to remove");
524 else 521 else
525 local removed, err = uploads:delete(nil, deletion_query); 522 local removed, err = uploads:delete(nil, deletion_query);
533 530
534 prune_done(); 531 prune_done();
535 end); 532 end);
536 end 533 end
537 534
538 if total_storage_limit then 535 local summary_start = module:measure("summary", "times");
539 local summary_start = module:measure("summary", "times"); 536
540 537 module:weekly("Calculate total storage usage", function()
541 module:weekly("Global quota check", function() 538 local summary_done = summary_start();
542 local summary_done = summary_start(); 539 local iter = assert(uploads:find(nil));
543 local iter = assert(uploads:find(nil)); 540
544 541 local count, sum = 0, 0;
545 local count, sum = 0, 0; 542 for _, file in iter do
546 for _, file in iter do 543 sum = sum + tonumber(file.attr.size);
547 sum = sum + tonumber(file.attr.size); 544 count = count + 1;
548 count = count + 1; 545 end
549 end 546
550 547 module:log("info", "Uploaded files total: %s in %d files", B(sum), count);
551 module:log("info", "Uploaded files total: %s in %d files", B(sum), count); 548 if persist_stats:set(nil, "total", sum) then
552 total_storage_usage = sum; 549 total_storage_usage = sum;
553 module:log("debug", "Global quota %s / %s", B(total_storage_usage), B(total_storage_limit)); 550 else
554 persist_stats:set(nil, "total", sum); 551 total_storage_usage = unknown;
555 summary_done(); 552 end
556 end); 553 module:log("debug", "Total storage usage: %s / %s", B(total_storage_usage), B(total_storage_limit));
557 554 summary_done();
558 end 555 end);
559 556
560 -- Reachable from the console 557 -- Reachable from the console
561 function check_files(query) 558 function check_files(query)
562 local issues = {}; 559 local issues = {};
563 local iter = assert(uploads:find(nil, query)); 560 local iter = assert(uploads:find(nil, query));