dbxapp 4.1.3
CMS, Shop, Workflows und modulare Geschäftsanwendungen
Loading...
Searching...
No Matches
dbxPerformanceTimer.class.php
Go to the documentation of this file.
1<?php
2
3dbx()->use_system_class('dbxDD');
4
6
7 private const REQUEST_DD = 'dbx|dbxPerformanceRequest';
8 private const TIMER_DD = 'dbx|dbxPerformanceTimer';
9 private const SCHEMA_MARKER_VERSION = 'v1';
10
11 private array $configFileCache = array();
12
13 private function config_value(string $key, $default = '') {
14 $value = dbx()->get_config('dbx', $key);
15
16 if ($value !== 'undef') {
17 return $value;
18 }
19
20 $cfg = $this->config_file_values();
21 return $cfg[$key] ?? $default;
22 }
23
24 private function config_file_values(): array {
25 if ($this->configFileCache) {
26 return $this->configFileCache;
27 }
28
29 $file = dbx()->os_path(dbx()->get_base_dir() . 'dbx/modules/dbx/cfg/config.php');
30 if (!is_file($file)) {
31 return array();
32 }
33
34 $loader = static function ($file) {
35 $config = array();
36 include $file;
37 return is_array($config) ? $config : array();
38 };
39
40 $this->configFileCache = $loader($file);
41 return $this->configFileCache;
42 }
43
44 private function config_bool(string $key, $default = 0): bool {
45 $value = $this->config_value($key, $default);
46 return (int) $value === 1;
47 }
48
49 private function normalize_level($value): string {
50 $level = strtolower(trim((string) $value));
51 if ($level === 'details') {
52 $level = 'detail';
53 }
54
55 return in_array($level, array('off', 'main', 'detail'), true) ? $level : '';
56 }
57
58 private function performance_level(): string {
59 $level = $this->normalize_level($this->config_value('performance_timer_level', ''));
60 if ($level !== '') {
61 return $level;
62 }
63
64 $request = $this->config_bool('performance_timer_request', $this->config_value('performance_timer', 0));
65 $db = $this->config_bool('performance_timer_db', $this->config_value('performance_timer_detail', 0));
66
67 return ($request || $db) ? 'detail' : 'off';
68 }
69
70 private function is_enabled(): bool {
71 return $this->performance_level() !== 'off';
72 }
73
74 private function is_db_section(string $section): bool {
75 return $section === 'db-total'
76 || $section === 'db-select'
77 || $section === 'db-save'
78 || substr($section, 0, 10) === 'db-select-'
79 || substr($section, 0, 8) === 'db-save-';
80 }
81
82 private function is_main_section(string $section): bool {
83 return in_array($section, array('system', 'js-total', 'db-total'), true);
84 }
85
86 private function sample_rate(): int {
87 return max(1, (int) $this->config_value('performance_timer_sample_rate', 1));
88 }
89
90 private function should_sample(int $sampleRate): bool {
91 if ($sampleRate <= 1) {
92 return true;
93 }
94
95 try {
96 return random_int(1, $sampleRate) === 1;
97 } catch (\Throwable $e) {
98 return mt_rand(1, $sampleRate) === 1;
99 }
100 }
101
102 private function ensure_tables(): bool {
103 $marker = $this->runtime_dir() . '/schema-' . self::SCHEMA_MARKER_VERSION . '.ready';
104 if (is_file($marker) && (int)@filemtime($marker) >= time() - 86400) {
105 return true;
106 }
107
108 try {
109 $dd = dbx()->get_system_obj('dbxDD');
110 $ok = $dd->create_db_tab(self::REQUEST_DD) && $dd->create_db_tab(self::TIMER_DD);
111 if ($ok) @file_put_contents($marker, date('c'), LOCK_EX);
112 return $ok;
113 } catch (\Throwable $e) {
114 return false;
115 }
116 }
117
118 private function runtime_dir(): string {
119 $dir = rtrim((string)dbx()->get_file_dir(), '/\\') . '/cache/performance';
120 $dir = dbx()->os_path($dir);
121 if (!is_dir($dir)) @mkdir($dir, 0755, true);
122 return $dir;
123 }
124
125 private function ms($seconds): int {
126 return max(0, (int) round(((float) $seconds) * 1000));
127 }
128
129 private function kb($bytes): int {
130 return (int) round(((float) $bytes) / 1024);
131 }
132
133 private function timer_ms(array $timer): int {
134 if (isset($timer['time']) && is_numeric($timer['time']) && (float) $timer['time'] >= 0) {
135 return $this->ms($timer['time']);
136 }
137
138 if (isset($timer['start_time'], $timer['end_time']) && is_numeric($timer['start_time']) && is_numeric($timer['end_time'])) {
139 return $this->ms(max(0, (float) $timer['end_time'] - (float) $timer['start_time']));
140 }
141
142 return 0;
143 }
144
145 private function timer_memory_kb(array $timer): int {
146 if (isset($timer['memory']) && is_numeric($timer['memory']) && (float) $timer['memory'] >= 0) {
147 return $this->kb($timer['memory']);
148 }
149
150 if (isset($timer['start_memory'], $timer['end_memory']) && is_numeric($timer['start_memory']) && is_numeric($timer['end_memory'])) {
151 return $this->kb(max(0, (float) $timer['end_memory'] - (float) $timer['start_memory']));
152 }
153
154 return 0;
155 }
156
157 private function timer_end_memory_mb(array $timer): int {
158 if (isset($timer['end_memory']) && is_numeric($timer['end_memory']) && (float) $timer['end_memory'] >= 0) {
159 return max(0, (int) ceil(((float) $timer['end_memory']) / 1048576));
160 }
161
162 if (isset($timer['start_memory']) && is_numeric($timer['start_memory']) && (float) $timer['start_memory'] >= 0) {
163 return max(0, (int) ceil(((float) $timer['start_memory']) / 1048576));
164 }
165
166 return 0;
167 }
168
169 private function trim_text($value, int $length): string {
170 $value = str_replace(array("\r", "\n", "\t"), ' ', (string) $value);
171 return substr($value, 0, $length);
172 }
173
174 private function request_record(array $timers, int $sampleRate): array {
175 $system = $timers['system'] ?? array();
176 $uid = (int) dbx()->user();
177 $modul = (string) dbx()->get_system_var('dbx_modul', dbx()->get_system_var('dbx_activ_modul', 'dbx'));
178 $now = date('Y-m-d H:i:s');
179
180 return array(
181 'request_date' => $now,
182 'create_date' => $now,
183 'create_uid' => $uid,
184 'update_date' => $now,
185 'update_uid' => $uid,
186 'owner' => $uid,
187 'uid' => $uid,
188 'session_id' => $this->trim_text(session_id(), 128),
189 'modul' => $this->trim_text($modul, 80),
190 'run1' => $this->trim_text(dbx()->get_system_var('dbx_run1', ''), 80),
191 'run2' => $this->trim_text(dbx()->get_system_var('dbx_run2', ''), 80),
192 'ajax' => (int) dbx()->get_system_var('dbx_ajax', 0, 'int'),
193 'sync' => (int) dbx()->get_request_var('dbx_sync', 1, 'int'),
194 'method' => $this->trim_text($_SERVER['REQUEST_METHOD'] ?? '', 12),
195 'uri' => $this->trim_text($_SERVER['REQUEST_URI'] ?? '', 1200),
196 'total_time_ms' => $this->timer_ms($system),
197 'total_memory_kb' => $this->timer_memory_kb($system),
198 'peak_memory_mb' => $this->timer_end_memory_mb($system),
199 'timer_count' => count($timers),
200 'sample_rate' => $sampleRate,
201 );
202 }
203
204 private function timer_record(int $requestId, string $requestDate, string $section, array $timer, int $sortOrder): array {
205 $uid = (int) dbx()->user();
206 $now = date('Y-m-d H:i:s');
207
208 return array(
209 'request_id' => $requestId,
210 'request_date' => $requestDate,
211 'create_date' => $now,
212 'create_uid' => $uid,
213 'update_date' => $now,
214 'update_uid' => $uid,
215 'owner' => $uid,
216 'sort_order' => $sortOrder,
217 'section' => $this->trim_text($section, 80),
218 'info' => $this->trim_text($timer['info'] ?? '', 160),
219 'time_ms' => $this->timer_ms($timer),
220 'memory_kb' => $this->timer_memory_kb($timer),
221 'start_memory_kb' => isset($timer['start_memory']) && is_numeric($timer['start_memory']) ? $this->kb($timer['start_memory']) : 0,
222 'end_memory_kb' => isset($timer['end_memory']) && is_numeric($timer['end_memory']) ? $this->kb($timer['end_memory']) : 0,
223 );
224 }
225
226 private function cleanup_old_rows(): void {
227 $days = (int) $this->config_value('performance_timer_keep_days', 14);
228 if ($days <= 0) {
229 return;
230 }
231
232 $marker = $this->runtime_dir() . '/cleanup.timestamp';
233 $handle = @fopen($marker, 'c+b');
234 if (!is_resource($handle) || !@flock($handle, LOCK_EX | LOCK_NB)) {
235 if (is_resource($handle)) @fclose($handle);
236 return;
237 }
238
239 @rewind($handle);
240 $lastCleanup = (int)trim((string)stream_get_contents($handle));
241 if ($lastCleanup >= time() - 86400) {
242 @flock($handle, LOCK_UN);
243 @fclose($handle);
244 return;
245 }
246
247 $cutoff = date('Y-m-d H:i:s', time() - ($days * 86400));
248
249 try {
250 $db = dbx()->get_system_obj('dbxDB');
251 $db->delete(self::TIMER_DD, "request_date < '" . $cutoff . "'", 0, 0);
252 $db->delete(self::REQUEST_DD, "request_date < '" . $cutoff . "'", 0, 0);
253 @ftruncate($handle, 0);
254 @rewind($handle);
255 @fwrite($handle, (string)time());
256 @fflush($handle);
257 } catch (\Throwable $e) {
258 // Marker bleibt unveraendert; ein spaeterer Request darf erneut
259 // versuchen aufzuraeumen.
260 } finally {
261 @flock($handle, LOCK_UN);
262 @fclose($handle);
263 }
264 }
265
266 public function store(): int {
267 if ((int) dbx()->get_system_var('dbx_ajax', 0, 'int') === 1) {
268 return 0;
269 }
270
271 if ((int) dbx()->get_system_var('dbx_performance_timer_skip', 0, 'int') === 1) {
272 return 0;
273 }
274
275 if ((int) dbx()->get_request_var('dbx_sync', 1, 'int') === 0) {
276 return 0;
277 }
278
279 if (!$this->is_enabled()) {
280 return 0;
281 }
282
283 $sampleRate = $this->sample_rate();
284 if (!$this->should_sample($sampleRate)) {
285 return 0;
286 }
287
288 global $dbx_run_timer;
289 if (!isset($dbx_run_timer) || !is_array($dbx_run_timer) || !$dbx_run_timer) {
290 return 0;
291 }
292
293 dbx()->set_system_var('dbx_performance_timer_store', 1);
294 try {
295 if (!$this->ensure_tables()) {
296 return 0;
297 }
298
299 $performanceLevel = $this->performance_level();
300 $detailEnabled = $performanceLevel === 'detail';
301 $db = dbx()->get_system_obj('dbxDB');
302 $request = $this->request_record($dbx_run_timer, $sampleRate);
303 $requestId = 0;
304
305 if ($db->begin(self::REQUEST_DD) !== 1) {
306 return 0;
307 }
308
309 try {
310 $requestId = ($db->insert(self::REQUEST_DD, $request, 0, 1, 1, 0) === 1) ? $db->get_insert_id() : 0;
311
312 if ($requestId <= 0) {
313 throw new \RuntimeException('performance_request_insert_failed');
314 }
315
316 $sort = 0;
317 foreach ($dbx_run_timer as $section => $timer) {
318 if (!is_array($timer)) {
319 continue;
320 }
321
322 $section = (string) $section;
323 if (!$detailEnabled && !$this->is_main_section($section)) {
324 continue;
325 }
326
327 if ($db->insert(self::TIMER_DD, $this->timer_record($requestId, $request['request_date'], $section, $timer, $sort), 0, 1, 1, 0) !== 1) {
328 throw new \RuntimeException('performance_timer_insert_failed');
329 }
330 $sort++;
331 }
332
333 if ($db->commit(self::REQUEST_DD) !== 1) {
334 throw new \RuntimeException('performance_commit_failed');
335 }
336 } catch (\Throwable $e) {
337 $db->rollback(self::REQUEST_DD);
338 return 0;
339 }
340
341 $this->cleanup_old_rows();
342 return $requestId;
343 } catch (\Throwable $e) {
344 return 0;
345 } finally {
346 dbx()->set_system_var('dbx_performance_timer_store', 0);
347 }
348 }
349}
$cfg
dbx()
Liefert die zentrale dbXapp-API als Singleton.
Definition dbxApi.php:2805
get_base_dir(int $cutData=0)
Liefert das Basisverzeichnis der Installation.
Definition dbxApi.php:1507
get_file_dir()
Liefert das files/-Verzeichnis der Installation.
Definition dbxApi.php:1524
$_SERVER['REQUEST_METHOD']
if($resolved !==$expectedBase . 'files/test/') $config
if(!defined( 'IMG_WEBP')) define( 'IMG_WEBP'
DBX schema administration.