1: | <?php |
2: | declare(strict_types=1); |
3: | |
4: | |
5: | |
6: | |
7: | |
8: | |
9: | |
10: | |
11: | |
12: | |
13: | |
14: | |
15: | |
16: | |
17: | |
18: | |
19: | |
20: | class Test_Module extends Module_Skeleton |
21: | { |
22: | public $exportedFunctions = array( |
23: | '*' => PRIVILEGE_ALL, |
24: | ); |
25: | |
26: | public function __construct() |
27: | { |
28: | parent::__construct(); |
29: | if (!is_debug()) { |
30: | $this->exportedFunctions = array('*' => PRIVILEGE_NONE); |
31: | } |
32: | } |
33: | |
34: | |
35: | |
36: | |
37: | |
38: | |
39: | |
40: | |
41: | public function benchmark_all($iterations = 1000, $testclass = null) |
42: | { |
43: | if (!is_debug()) { |
44: | return error('benchmark only enabled in dev'); |
45: | } |
46: | $methods = array(); |
47: | if (is_null($testclass)) { |
48: | $my_class = __CLASS__; |
49: | } else { |
50: | $my_class = $testclass; |
51: | } |
52: | if (!class_exists($my_class)) { |
53: | return error("Unknown class `%s'", $my_class); |
54: | } |
55: | $my_method = __FUNCTION__; |
56: | $rfxn = new ReflectionClass($my_class); |
57: | $maxlen = 0; |
58: | foreach ($rfxn->getMethods(ReflectionMethod::IS_PUBLIC) as $m) { |
59: | $method = $m->name; |
60: | $class = $m->class; |
61: | if ($class === $my_class && $method !== $my_method) { |
62: | $methods[] = $method; |
63: | $maxlen = max($maxlen, strlen($method)); |
64: | } |
65: | } |
66: | $header = 'Module ' . $my_class; |
67: | print $header . "\n" . |
68: | str_repeat('=', strlen($header)) . "\n"; |
69: | foreach ($methods as $m) { |
70: | printf('%-' . $maxlen . 's: ', $m); |
71: | $start = microtime(true); |
72: | for ($i = 0, $n = $iterations; $i < $n; $i++) { |
73: | assert($ret = $this->$m()); |
74: | } |
75: | $end = microtime(true); |
76: | $diff = ($end - $start); |
77: | printf("%.4fs (%.6fs)\n", $diff, $diff / $iterations); |
78: | } |
79: | print "\n"; |
80: | |
81: | return true; |
82: | } |
83: | |
84: | |
85: | |
86: | |
87: | |
88: | |
89: | public function exec_named_args() |
90: | { |
91: | $args = array( |
92: | 'program' => 'echo', |
93: | 'args' => 'Hello World!' |
94: | ); |
95: | $proc = Util_Process_Safe::exec('%(program)s %(args)s %(args)s', |
96: | $args, array(1, 0) |
97: | ); |
98: | |
99: | return $proc['success']; |
100: | } |
101: | |
102: | |
103: | |
104: | |
105: | |
106: | |
107: | public function exec_no_args() |
108: | { |
109: | $proc = Util_Process::exec('echo "Hello"', |
110: | array(0) |
111: | ); |
112: | |
113: | return $proc['success']; |
114: | } |
115: | |
116: | public function exec_fail() |
117: | { |
118: | $proc = Util_Process::exec('/bin/true', array(1)); |
119: | |
120: | return $proc['success'] == false; |
121: | } |
122: | |
123: | |
124: | |
125: | |
126: | |
127: | |
128: | |
129: | |
130: | |
131: | public function exec_additional_args() |
132: | { |
133: | $args = array('echo', "'Hello World!'", 'test'); |
134: | |
135: | $proc = Util_Process::exec('%s %s', 'echo', "'Hello World!'", 'Test'); |
136: | |
137: | return $proc['success']; |
138: | } |
139: | |
140: | public function exec_quotes() |
141: | { |
142: | $args = array("'Hello World!'", 'test'); |
143: | |
144: | $proc = Util_Process_Safe::exec('echo %s %s', $args); |
145: | print $proc['stdout']; |
146: | |
147: | return $proc['success']; |
148: | } |
149: | |
150: | |
151: | |
152: | |
153: | |
154: | public function backend_performance($n = 10000) |
155: | { |
156: | |
157: | |
158: | |
159: | |
160: | |
161: | |
162: | |
163: | |
164: | |
165: | |
166: | |
167: | |
168: | |
169: | |
170: | |
171: | |
172: | |
173: | |
174: | return $this->benchmark('test_backend_emitter', $n); |
175: | } |
176: | |
177: | |
178: | |
179: | |
180: | |
181: | |
182: | |
183: | |
184: | public function benchmark($func, int $iterations = 1000) |
185: | { |
186: | if (!is_debug()) { |
187: | return error('benchmark only enabled in dev'); |
188: | } |
189: | if ($func instanceof \Closure) { |
190: | $fname = (new ReflectionFunction($func))->getName(); |
191: | } else if (is_callable(array($this, $func))) { |
192: | $fname = $func; |
193: | $func = [$this, $func]; |
194: | } |
195: | if (!is_callable($func)) { |
196: | return error("function `%s' is not callable", $fname); |
197: | } |
198: | gc_collect_cycles(); |
199: | gc_mem_caches(); |
200: | $bm = static function (Callable $func, string $fname) use ($iterations) { |
201: | $mem = memory_get_usage(); |
202: | print 'benchmark ' . $fname . "\r\n"; |
203: | $start = microtime(true); |
204: | for ($i = 0; $i < $iterations; $i++) { |
205: | |
206: | $func(); |
207: | } |
208: | $end = microtime(true); |
209: | $delta = $end - $start; |
210: | printf("time: %.2f sec (%d rounds; %.4f ms each; %.2f per second)\n\n", |
211: | $delta, |
212: | $iterations, |
213: | $delta / $iterations * 1000, |
214: | $iterations / $delta |
215: | ); |
216: | printf("Mem usage: %.2f bytes\n", memory_get_usage() - $mem); |
217: | |
218: | return $delta; |
219: | }; |
220: | $bmf = $bm($func, $fname); |
221: | $bm = null; |
222: | |
223: | return $bmf; |
224: | } |
225: | |
226: | |
227: | |
228: | |
229: | |
230: | |
231: | |
232: | public function backend_emitter($args = '') |
233: | { |
234: | |
235: | |
236: | |
237: | |
238: | |
239: | return $this->query('test_backend_collector', $args); |
240: | } |
241: | |
242: | |
243: | |
244: | |
245: | |
246: | |
247: | |
248: | public function backend_collector($args = '') |
249: | { |
250: | return $args; |
251: | } |
252: | |
253: | public function config_bm() |
254: | { |
255: | |
256: | $this->compare('test_config', 'test_config2'); |
257: | } |
258: | |
259: | |
260: | |
261: | |
262: | |
263: | |
264: | |
265: | |
266: | |
267: | public function compare($func1, $func2, $iterations = 1000) |
268: | { |
269: | if (!is_debug()) { |
270: | return error('benchmark only enabled in dev'); |
271: | } |
272: | |
273: | if (!$func1 || !$func2) { |
274: | return error('need 2 functions to compare'); |
275: | } |
276: | $mem = memory_get_usage(); |
277: | $bmf1 = $this->benchmark($func1, $iterations); |
278: | $bmf2 = $this->benchmark($func2, $iterations); |
279: | $ret = 0; |
280: | if ($bmf1 < $bmf2) { |
281: | printf('%s is quicker than %s ', $func1, $func2); |
282: | $diff = abs($bmf1 - $bmf2); |
283: | $diffp = $diff / $bmf2; |
284: | $ret = 1; |
285: | } else { |
286: | printf('%s is quicker than %s ', $func2, $func1); |
287: | $diff = abs($bmf2 - $bmf1); |
288: | $diffp = $diff / $bmf1; |
289: | $ret = -1; |
290: | } |
291: | printf("by %.2f%%\n\n", $diffp * 100); |
292: | printf("Mem usage: %.2f bytes\n", memory_get_usage() - $mem); |
293: | |
294: | return; |
295: | } |
296: | |
297: | public function config($conf = null) |
298: | { |
299: | $conf = $this->getAuthContext()->conf('ipinfo'); |
300: | |
301: | |
302: | return "[DEFAULT]\n" . Util_Conf::build_ini($conf); |
303: | } |
304: | |
305: | public function config2($conf = null) |
306: | { |
307: | $conf = $this->getAuthContext()->conf('ipinfo'); |
308: | $data = '[DEFAULT]' . "\n"; |
309: | foreach ($conf as $srvc_var => $srvc_val) { |
310: | $data .= $srvc_var . ' = ' . (!is_array($srvc_val) ? $srvc_val : (!$srvc_val ? '[]' : '[\'' . implode('\', \'', |
311: | array_unique($srvc_val)) . '\']')) . "\n"; |
312: | } |
313: | |
314: | return $data; |
315: | } |
316: | |
317: | public function sudo() |
318: | { |
319: | if (!IS_CLI) { |
320: | return $this->query('test_sudo'); |
321: | } |
322: | $args = array('user' => 'debug'); |
323: | if ($this->permission_level & PRIVILEGE_ADMIN) { |
324: | $args['domain'] = 'debug.com'; |
325: | } |
326: | $ret = Util_Process_Sudo::exec('id', |
327: | $args); |
328: | |
329: | return $ret; |
330: | } |
331: | |
332: | public function fn_decompose($cmd) |
333: | { |
334: | return Util_Process::decompose($cmd); |
335: | } |
336: | |
337: | public function mail() |
338: | { |
339: | $address = $this->common_get_email() ?? Crm_Module::COPY_ADMIN; |
340: | $template = \BladeLite::factory('views/email'); |
341: | $html = $template->make('simple', |
342: | [ |
343: | 'msg' => 'This is a test email from your panel!' . |
344: | "\n\nPanel login source: " . \Auth_Redirect::getPreferredUri() |
345: | ] |
346: | )->render(); |
347: | |
348: | $opts = array( |
349: | 'html_charset' => 'utf-8', |
350: | 'text_charset' => 'utf-8' |
351: | ); |
352: | $from = \Crm_Module::FROM_NAME . ' <' . \Crm_Module::FROM_ADDRESS . '>'; |
353: | $headers = array( |
354: | 'Sender' => $from, |
355: | 'From' => $from |
356: | ); |
357: | $mime = new Mail_Mime($opts); |
358: | |
359: | $mime->setHTMLBody($html); |
360: | $mime->setTXTBody(strip_tags($html)); |
361: | $headers = $mime->txtHeaders($headers); |
362: | $msg = $mime->get(); |
363: | |
364: | return Mail::send( |
365: | $address, |
366: | PANEL_BRAND . ' test', |
367: | $msg, |
368: | $headers |
369: | ); |
370: | |
371: | return info("Sent test email to `%s'", $address); |
372: | } |
373: | |
374: | |
375: | |
376: | |
377: | |
378: | |
379: | |
380: | |
381: | public function message_class(string $class, string $confirmation = 'Hello!') { |
382: | if (!\in_array($class, ['fatal', 'error', 'warning', 'info'], true)) { |
383: | return error("Unknown message class `%s'", $class); |
384: | } |
385: | return $class($confirmation); |
386: | } |
387: | |
388: | |
389: | |
390: | |
391: | |
392: | |
393: | |
394: | public function sleep(int $time = 10) |
395: | { |
396: | if (!IS_CLI) { |
397: | return $this->query('test_sleep', $time); |
398: | } |
399: | sleep($time); |
400: | |
401: | return true; |
402: | } |
403: | |
404: | |
405: | |
406: | |
407: | |
408: | |
409: | public function now() |
410: | { |
411: | return date('r'); |
412: | } |
413: | |
414: | public function context_performance() |
415: | { |
416: | |
417: | |
418: | |
419: | if (!$this->permission_level & PRIVILEGE_SITE) { |
420: | return error('Context requires site admin privileges'); |
421: | } |
422: | |
423: | $oldrep = \Error_Reporter::set_verbose(0); |
424: | $user = \Opcenter\Auth\Password::generate(8, 'a-z'); |
425: | if (!$this->user_add($user, 'randompassword12345')) { |
426: | return error('Failed to create user'); |
427: | } |
428: | \assert(spl_object_hash(\Auth::context($user, $this->site)) !== spl_object_hash(\Auth::context($user, $this->site)), 'Context uniqueness'); |
429: | $this->benchmark(function () use ($user) { |
430: | \Auth::context($user, $this->site); |
431: | }); |
432: | $this->user_delete($user); |
433: | \Error_Reporter::clear_buffer(); |
434: | \Error_Reporter::set_verbose($oldrep); |
435: | } |
436: | |
437: | public function metrics(string $attr = null) |
438: | { |
439: | if (!TELEMETRY_ENABLED) { |
440: | return error('[telemetry] => enabled is off in config.ini'); |
441: | } |
442: | |
443: | $pg = PostgreSQL::pdo(); |
444: | $query = "SELECT TIME_BUCKET('5 minute', ts), name, label, value FROM metrics " . |
445: | 'JOIN metric_attributes USING (attr_id) WHERE site_id = ' . $this->site_id . " AND ts >= NOW() - '1 day'::INTERVAL"; |
446: | if ($attr) { |
447: | $query .= ' AND name = ' . $pg->quote($attr); |
448: | } |
449: | $rs = $pg->query($query); |
450: | return $rs->fetchAll(PDO::FETCH_ASSOC); |
451: | } |
452: | |
453: | public function sigchld_exit(int $code, bool $backend = true): int |
454: | { |
455: | if ($backend && !IS_CLI) { |
456: | return $this->query('test_sigchld_exit', $code, $backend); |
457: | } |
458: | |
459: | $ret = \Util_Process_Safe::exec('exit %d', [$code]); |
460: | return $ret['return']; |
461: | } |
462: | } |