7 private const REQUEST_DD =
'dbx|dbxPerformanceRequest';
8 private const TIMER_DD =
'dbx|dbxPerformanceTimer';
9 private const SCHEMA_MARKER_VERSION =
'v1';
11 private array $configFileCache = array();
13 private function config_value(
string $key, $default =
'') {
20 $cfg = $this->config_file_values();
21 return $cfg[$key] ?? $default;
24 private function config_file_values(): array {
25 if ($this->configFileCache) {
26 return $this->configFileCache;
30 if (!is_file(
$file)) {
34 $loader =
static function (
$file) {
40 $this->configFileCache = $loader(
$file);
41 return $this->configFileCache;
44 private function config_bool(
string $key, $default = 0):
bool {
45 $value = $this->config_value($key, $default);
49 private function normalize_level(
$value):
string {
50 $level = strtolower(trim((
string)
$value));
51 if ($level ===
'details') {
55 return in_array($level, array(
'off',
'main',
'detail'),
true) ? $level :
'';
58 private function performance_level():
string {
59 $level = $this->normalize_level($this->config_value(
'performance_timer_level',
''));
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));
67 return ($request ||
$db) ?
'detail' :
'off';
70 private function is_enabled():
bool {
71 return $this->performance_level() !==
'off';
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-';
82 private function is_main_section(
string $section):
bool {
83 return in_array($section, array(
'system',
'js-total',
'db-total'),
true);
86 private function sample_rate():
int {
87 return max(1, (
int) $this->config_value(
'performance_timer_sample_rate', 1));
90 private function should_sample(
int $sampleRate):
bool {
91 if ($sampleRate <= 1) {
96 return random_int(1, $sampleRate) === 1;
97 }
catch (\Throwable $e) {
98 return mt_rand(1, $sampleRate) === 1;
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) {
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);
113 }
catch (\Throwable $e) {
118 private function runtime_dir():
string {
121 if (!is_dir(
$dir)) @mkdir(
$dir, 0755,
true);
125 private function ms($seconds):
int {
126 return max(0, (
int) round(((
float) $seconds) * 1000));
129 private function kb($bytes):
int {
130 return (
int) round(((
float) $bytes) / 1024);
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']);
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']));
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']);
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']));
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));
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));
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);
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');
181 'request_date' =>
$now,
182 'create_date' =>
$now,
183 'create_uid' => $uid,
184 'update_date' =>
$now,
185 'update_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,
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');
209 'request_id' => $requestId,
210 'request_date' => $requestDate,
211 'create_date' =>
$now,
212 'create_uid' => $uid,
213 'update_date' =>
$now,
214 'update_uid' => $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,
226 private function cleanup_old_rows():
void {
227 $days = (int) $this->config_value(
'performance_timer_keep_days', 14);
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);
240 $lastCleanup = (int)trim((
string)stream_get_contents($handle));
241 if ($lastCleanup >= time() - 86400) {
242 @flock($handle, LOCK_UN);
247 $cutoff = date(
'Y-m-d H:i:s', time() - ($days * 86400));
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);
255 @fwrite($handle, (
string)time());
257 }
catch (\Throwable $e) {
261 @flock($handle, LOCK_UN);
267 if ((int)
dbx()->get_system_var(
'dbx_ajax', 0,
'int') === 1) {
271 if ((
int)
dbx()->get_system_var(
'dbx_performance_timer_skip', 0,
'int') === 1) {
275 if ((
int)
dbx()->get_request_var(
'dbx_sync', 1,
'int') === 0) {
279 if (!$this->is_enabled()) {
283 $sampleRate = $this->sample_rate();
284 if (!$this->should_sample($sampleRate)) {
288 global $dbx_run_timer;
289 if (!isset($dbx_run_timer) || !is_array($dbx_run_timer) || !$dbx_run_timer) {
293 dbx()->set_system_var(
'dbx_performance_timer_store', 1);
295 if (!$this->ensure_tables()) {
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);
305 if (
$db->begin(self::REQUEST_DD) !== 1) {
310 $requestId = (
$db->insert(self::REQUEST_DD, $request, 0, 1, 1, 0) === 1) ?
$db->get_insert_id() : 0;
312 if ($requestId <= 0) {
313 throw new \RuntimeException(
'performance_request_insert_failed');
317 foreach ($dbx_run_timer as $section => $timer) {
318 if (!is_array($timer)) {
322 $section = (string) $section;
323 if (!$detailEnabled && !$this->is_main_section($section)) {
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');
333 if (
$db->commit(self::REQUEST_DD) !== 1) {
334 throw new \RuntimeException(
'performance_commit_failed');
336 }
catch (\Throwable $e) {
337 $db->rollback(self::REQUEST_DD);
341 $this->cleanup_old_rows();
343 }
catch (\Throwable $e) {
346 dbx()->set_system_var(
'dbx_performance_timer_store', 0);