ILIAS  release_8 Revision v8.24
class.ilSessionStatistics.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
25{
26 private const SLOT_SIZE = 15;
27
31 public static function isActive(): bool
32 {
33 global $DIC;
34
35 $ilSetting = $DIC['ilSetting'];
36
37 return (bool) $ilSetting->get('session_statistics', "1");
38 }
39
43 public static function createRawEntry(string $a_session_id, int $a_session_type, int $a_timestamp, int $a_user_id): void
44 {
45 global $DIC;
46
47 $ilDB = $DIC['ilDB'];
48
49 if (!$a_user_id || !$a_session_id || !self::isActive()) {
50 return;
51 }
52
53 // #9669: if a session was destroyed and somehow the session id is still
54 // in use there will be a id-collision for the raw-entry
55
56 $ilDB->replace(
57 "usr_session_stats_raw",
58 array(
59 "session_id" => array("text", $a_session_id)
60 ),
61 array(
62 "type" => array("integer", $a_session_type),
63 "start_time" => array("integer", $a_timestamp),
64 "user_id" => array("integer", $a_user_id)
65 )
66 );
67 }
68
76 public static function closeRawEntry($a_session_id, ?int $a_context = null, $a_expired_at = null): void
77 {
78 global $DIC;
79
80 $ilDB = $DIC['ilDB'];
81
82 if (!self::isActive()) {
83 return;
84 }
85
86 // single entry
87 if (!is_array($a_session_id)) {
88 if ($a_expired_at) {
89 $end_time = $a_expired_at;
90 } else {
91 $end_time = time();
92 }
93 $sql = "UPDATE usr_session_stats_raw" .
94 " SET end_time = " . $ilDB->quote($end_time, "integer");
95 if ($a_context) {
96 $sql .= ",end_context = " . $ilDB->quote($a_context, "integer");
97 }
98 $sql .= " WHERE session_id = " . $ilDB->quote($a_session_id, "text") .
99 " AND end_time IS NULL";
100 $ilDB->manipulate($sql);
101 }
102 // batch closing
103 elseif (!$a_expired_at) {
104 $sql = "UPDATE usr_session_stats_raw" .
105 " SET end_time = " . $ilDB->quote(time(), "integer");
106 if ($a_context) {
107 $sql .= ",end_context = " . $ilDB->quote($a_context, "integer");
108 }
109 $sql .= " WHERE " . $ilDB->in("session_id", $a_session_id, false, "text") .
110 " AND end_time IS NULL";
111 $ilDB->manipulate($sql);
112 }
113 // batch with individual timestamps
114 else {
115 foreach ($a_session_id as $id => $ts) {
116 $sql = "UPDATE usr_session_stats_raw" .
117 " SET end_time = " . $ilDB->quote($ts, "integer");
118 if ($a_context) {
119 $sql .= ",end_context = " . $ilDB->quote($a_context, "integer");
120 }
121 $sql .= " WHERE session_id = " . $ilDB->quote($id, "text") .
122 " AND end_time IS NULL";
123 $ilDB->manipulate($sql);
124 }
125 }
126 }
127
133 protected static function getCurrentSlot(int $a_now): ?array
134 {
135 global $DIC;
136
137 $ilDB = $DIC['ilDB'];
138
139 // get latest slot in db
140 $sql = "SELECT MAX(slot_end) previous_slot_end" .
141 " FROM usr_session_stats";
142 $res = $ilDB->query($sql);
143 $row = $ilDB->fetchAssoc($res);
144 $previous_slot_end = $row["previous_slot_end"];
145
146 // no previous slot? calculate last complete slot
147 // should we use minimum session raw date instead? (problem: table lock)
148 if (!$previous_slot_end) {
149 $slot = (int) (floor(date("i") / self::SLOT_SIZE));
150 // last slot of previous hour
151 if (!$slot) {
152 $current_slot_begin = mktime((int) date("H", $a_now) - 1, 60 - self::SLOT_SIZE, 0);
153 }
154 // "normalize" to slot
155 else {
156 $current_slot_begin = mktime((int) date("H", $a_now), ($slot - 1) * self::SLOT_SIZE, 0);
157 }
158 } else {
159 $current_slot_begin = $previous_slot_end + 1;
160 }
161
162 $current_slot_end = $current_slot_begin + (60 * self::SLOT_SIZE) - 1;
163
164 // no complete slot: nothing to do yet
165 if ($current_slot_end < $a_now) {
166 return array($current_slot_begin, $current_slot_end);
167 }
168 return null;
169 }
170
177 protected static function getNumberOfActiveRawSessions(int $a_time): int
178 {
179 global $DIC;
180
181 $ilDB = $DIC['ilDB'];
182
183 $sql = "SELECT COUNT(*) counter FROM usr_session_stats_raw" .
184 " WHERE (end_time IS NULL OR end_time >= " . $ilDB->quote($a_time, "integer") . ")" .
185 " AND start_time <= " . $ilDB->quote($a_time, "integer") .
186 " AND " . $ilDB->in("type", ilSessionControl::$session_types_controlled, false, "integer");
187 $res = $ilDB->query($sql);
188 $row = $ilDB->fetchAssoc($res);
189 return (int) $row["counter"];
190 }
191
195 protected static function getRawData(int $a_begin, int $a_end): array
196 {
197 global $DIC;
198
199 $ilDB = $DIC['ilDB'];
200
201 $sql = "SELECT start_time,end_time,end_context FROM usr_session_stats_raw" .
202 " WHERE start_time <= " . $ilDB->quote($a_end, "integer") .
203 " AND (end_time IS NULL OR end_time >= " . $ilDB->quote($a_begin, "integer") . ")" .
204 " AND " . $ilDB->in("type", ilSessionControl::$session_types_controlled, false, "integer") .
205 " ORDER BY start_time";
206 $res = $ilDB->query($sql);
207 $all = array();
208 while ($row = $ilDB->fetchAssoc($res)) {
209 $all[] = $row;
210 }
211 return $all;
212 }
213
219 protected static function createNewAggregationSlot(int $a_now): ?array
220 {
221 global $DIC;
222
223 $ilDB = $DIC['ilDB'];
224
225 $ilAtomQuery = $ilDB->buildAtomQuery();
226 $ilAtomQuery->addTableLock("usr_session_stats");
227
228 $ilAtomQuery->addQueryCallable(function (ilDBInterface $ilDB) use ($a_now, &$slot) {
229
230 // if we had to wait for the lock, no current slot should be returned here
231 $slot = self::getCurrentSlot($a_now);
232 if (!is_array($slot)) {
233 $slot = null;
234 return;
235 }
236
237 // save slot to mark as taken
238 $fields = array(
239 "slot_begin" => array("integer", $slot[0]),
240 "slot_end" => array("integer", $slot[1]),
241 );
242 $ilDB->insert("usr_session_stats", $fields);
243 });
244
245 $ilAtomQuery->run();
246
247 return $slot;
248 }
249
255 public static function aggretateRaw(int $a_now): void
256 {
257 if (!self::isActive()) {
258 return;
259 }
260
261 $slot = self::createNewAggregationSlot($a_now);
262 while (is_array($slot)) {
263 self::aggregateRawHelper($slot[0], $slot[1]);
264 $slot = self::createNewAggregationSlot($a_now);
265 }
266
267 // #12728
269 }
270
275 public static function aggregateRawHelper(int $a_begin, int $a_end): void
276 {
277 global $DIC;
278
279 $ilDB = $DIC['ilDB'];
280 $ilSetting = $DIC['ilSetting'];
281
282 // "relevant" closing types
283 $separate_closed = array(ilSession::SESSION_CLOSE_USER,
289
290 // gather/process data (build event timeline)
291 $closed_counter = $events = array();
292 $opened_counter = 0;
293 foreach (self::getRawData($a_begin, $a_end) as $item) {
294 // open/close counters are _not_ time related
295
296 // we could filter for undefined/invalid closing contexts
297 // and ignore those items, but this would make any debugging
298 // close to impossible
299 // "closed_other" would have been a good idea...
300
301 // session opened
302 if ($item["start_time"] >= $a_begin) {
303 $opened_counter++;
304 $events[$item["start_time"]][] = 1;
305 }
306 // session closed
307 if ($item["end_time"] && $item["end_time"] <= $a_end) {
308 if (in_array($item["end_context"], $separate_closed, true)) {
309 if (!isset($closed_counter[$item["end_context"]])) {
310 $closed_counter[$item["end_context"]] = 0;
311 }
312
313 $closed_counter[$item["end_context"]]++;
314 } else {
315 $closed_counter[0] = ($closed_counter[0] ?? 0) + 1;
316 }
317 $events[$item["end_time"]][] = -1;
318 }
319 }
320
321 // initialising active statistical values
322 $active_begin = self::getNumberOfActiveRawSessions($a_begin - 1);
323 $active_end = $active_min = $active_max = $active_avg = $active_begin;
324
325 // parsing events / building avergages
326 if (count($events)) {
327 $last_update_avg = $a_begin - 1;
328 $slot_seconds = self::SLOT_SIZE * 60;
329 $active_avg = 0;
330
331 // parse all open/closing events
332 ksort($events);
333 foreach ($events as $ts => $actions) {
334 // actions which occur in the same second are "merged"
335 foreach ($actions as $action) {
336 // max
337 if ($action > 0) {
338 $active_end++;
339 }
340 // min
341 else {
342 $active_end--;
343 }
344 }
345
346 // max
347 if ($active_end > $active_max) {
348 $active_max = $active_end;
349 }
350
351 // min
352 if ($active_end < $active_min) {
353 $active_min = $active_end;
354 }
355
356 // avg
357 $diff = $ts - $last_update_avg;
358 $active_avg += $diff / $slot_seconds * $active_end;
359 $last_update_avg = $ts;
360 }
361
362 // add up to end of slot if needed
363 if ($last_update_avg < $a_end) {
364 $diff = $a_end - $last_update_avg;
365 $active_avg += $diff / $slot_seconds * $active_end;
366 }
367
368 $active_avg = round($active_avg);
369 }
370 unset($events);
371
372
373 // do we (really) need a log here?
374 // $max_sessions = (int)$ilSetting->get("session_max_count", ilSessionControl::DEFAULT_MAX_COUNT);
375 $max_sessions = self::getLimitForSlot($a_begin);
376
377 // save aggregated data
378 $fields = array(
379 "active_min" => array("integer", $active_min),
380 "active_max" => array("integer", $active_max),
381 "active_avg" => array("integer", $active_avg),
382 "active_end" => array("integer", $active_end),
383 "opened" => array("integer", $opened_counter),
384 "closed_manual" => array("integer", (int) ($closed_counter[ilSession::SESSION_CLOSE_USER] ?? 0)),
385 "closed_expire" => array("integer", (int) ($closed_counter[ilSession::SESSION_CLOSE_EXPIRE] ?? 0)),
386 "closed_idle" => array("integer", (int) ($closed_counter[ilSession::SESSION_CLOSE_IDLE] ?? 0)),
387 "closed_idle_first" => array("integer", (int) ($closed_counter[ilSession::SESSION_CLOSE_FIRST] ?? 0)),
388 "closed_limit" => array("integer", (int) ($closed_counter[ilSession::SESSION_CLOSE_LIMIT] ?? 0)),
389 "closed_login" => array("integer", (int) ($closed_counter[ilSession::SESSION_CLOSE_LOGIN] ?? 0)),
390 "closed_misc" => array("integer", (int) ($closed_counter[0] ?? 0)),
391 "max_sessions" => array("integer", $max_sessions)
392 );
393 $ilDB->update(
394 "usr_session_stats",
395 $fields,
396 array("slot_begin" => array("integer", $a_begin),
397 "slot_end" => array("integer", $a_end))
398 );
399 }
400
406 protected static function deleteAggregatedRaw($a_now): void
407 {
408 global $DIC;
409
410 $ilDB = $DIC['ilDB'];
411
412 // we are rather defensive here - 7 days BEFORE current aggregation
413 $cut = $a_now - (60 * 60 * 24 * 7);
414
415 $ilDB->manipulate("DELETE FROM usr_session_stats_raw" .
416 " WHERE start_time <= " . $ilDB->quote($cut, "integer"));
417 }
418
422 public static function getLastMaxedOut(): int
423 {
424 global $DIC;
425
426 $ilDB = $DIC['ilDB'];
427
428 $sql = "SELECT max(slot_end) latest FROM usr_session_stats" .
429 " WHERE active_max >= max_sessions" .
430 " AND max_sessions > " . $ilDB->quote(0, "integer");
431 $res = $ilDB->query($sql);
432 $row = $ilDB->fetchAssoc($res);
433 if ($row["latest"]) {
434 return (int) $row["latest"];
435 }
436 return 0;
437 }
438
444 public static function getMaxedOutDuration(int $a_from, int $a_to): ?int
445 {
446 global $DIC;
447
448 $ilDB = $DIC['ilDB'];
449
450 $sql = "SELECT SUM(slot_end-slot_begin) dur FROM usr_session_stats" .
451 " WHERE active_max >= max_sessions" .
452 " AND max_sessions > " . $ilDB->quote(0, "integer") .
453 " AND slot_end > " . $ilDB->quote($a_from, "integer") .
454 " AND slot_begin < " . $ilDB->quote($a_to, "integer");
455 $res = $ilDB->query($sql);
456 $row = $ilDB->fetchAssoc($res);
457 if ($row["dur"]) {
458 return (int) $row["dur"];
459 }
460 //TODO check if return null as timestamp causes issues
461 return null;
462 }
463
467 public static function getNumberOfSessionsByType(int $a_from, int $a_to): array
468 {
469 global $DIC;
470
471 $ilDB = $DIC['ilDB'];
472
473 $sql = "SELECT SUM(opened) opened, SUM(closed_manual) closed_manual," .
474 " SUM(closed_expire) closed_expire, SUM(closed_idle) closed_idle," .
475 " SUM(closed_idle_first) closed_idle_first, SUM(closed_limit) closed_limit," .
476 " SUM(closed_login) closed_login, SUM(closed_misc) closed_misc" .
477 " FROM usr_session_stats" .
478 " WHERE slot_end > " . $ilDB->quote($a_from, "integer") .
479 " AND slot_begin < " . $ilDB->quote($a_to, "integer");
480 $res = $ilDB->query($sql);
481 return $ilDB->fetchAssoc($res);
482 }
483
487 public static function getActiveSessions(int $a_from, int $a_to): array
488 {
489 global $DIC;
490
492 $ilDB = $DIC['ilDB'];
493
494 $sql = "SELECT slot_begin, slot_end, active_min, active_max, active_avg," .
495 " max_sessions" .
496 " FROM usr_session_stats" .
497 " WHERE slot_end > " . $ilDB->quote($a_from, "integer") .
498 " AND slot_begin < " . $ilDB->quote($a_to, "integer") .
499 " ORDER BY slot_begin";
500 $res = $ilDB->query($sql);
501 $all = array();
502 while ($row = $ilDB->fetchAssoc($res)) {
503 $entry = [];
504 foreach ($row as $key => $value) {
505 $entry[$key] = (int) $value;
506 }
507 $all[] = $entry;
508 }
509 return $all;
510 }
511
515 public static function getLastAggregation(): ?int
516 {
517 global $DIC;
518
519 $ilDB = $DIC['ilDB'];
520
521 $sql = "SELECT max(slot_end) latest FROM usr_session_stats";
522 $res = $ilDB->query($sql);
523 $row = $ilDB->fetchAssoc($res);
524 if ($row["latest"]) {
525 return (int) $row["latest"];
526 }
527 //TODO check if return null as timestamp causes issues
528 return null;
529 }
530
535 public static function getLimitForSlot(int $a_timestamp): int
536 {
537 global $DIC;
538
539 $ilDB = $DIC['ilDB'];
540 $ilSetting = $DIC['ilSetting'];
541
542 $ilDB->setLimit(1);
543 $sql = "SELECT maxval FROM usr_session_log" .
544 " WHERE tstamp <= " . $ilDB->quote($a_timestamp, "integer") .
545 " ORDER BY tstamp DESC";
546 $res = $ilDB->query($sql);
547 $val = $ilDB->fetchAssoc($res);
548 if (isset($val["maxval"]) && $val["maxval"]) {
549 return (int) $val["maxval"];
550 }
551
552 return (int) $ilSetting->get("session_max_count", (string) ilSessionControl::DEFAULT_MAX_COUNT);
553 }
554
558 public static function updateLimitLog(int $a_new_value): void
559 {
560 global $DIC;
561
562 $ilDB = $DIC['ilDB'];
563 $ilSetting = $DIC['ilSetting'];
564 $ilUser = $DIC['ilUser'];
565
566 $new_value = $a_new_value;
567 $old_value = (int) $ilSetting->get("session_max_count", (string) ilSessionControl::DEFAULT_MAX_COUNT);
568
569 if ($new_value !== $old_value) {
570 $fields = array(
571 "tstamp" => array("timestamp", time()),
572 "maxval" => array("integer", $new_value),
573 "user_id" => array("integer", $ilUser->getId())
574 );
575 $ilDB->insert("usr_session_log", $fields);
576 }
577 }
578}
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
Definition: plugin.php:23
const DEFAULT_MAX_COUNT
default value for settings that have not been defined in setup or administration yet
static array $session_types_controlled
static getLastAggregation()
Get timestamp of last aggregation.
static getCurrentSlot(int $a_now)
Get next slot to aggregate.
static getNumberOfActiveRawSessions(int $a_time)
Count number of active sessions at given time.
static getNumberOfSessionsByType(int $a_from, int $a_to)
Get session counters by type (opened, closed)
static updateLimitLog(int $a_new_value)
Log max session setting.
static aggregateRawHelper(int $a_begin, int $a_end)
Aggregate statistics data for one slot.
static getLastMaxedOut()
Get latest slot during which sessions were maxed out.
static deleteAggregatedRaw($a_now)
Remove already aggregated raw data.
static closeRawEntry($a_session_id, ?int $a_context=null, $a_expired_at=null)
Close raw data entry.
static aggretateRaw(int $a_now)
Aggregate raw session data (older than given time)
static createRawEntry(string $a_session_id, int $a_session_type, int $a_timestamp, int $a_user_id)
Create raw data entry.
static isActive()
Is session statistics active at all?
static getLimitForSlot(int $a_timestamp)
Get max session setting for given timestamp.
static createNewAggregationSlot(int $a_now)
Create new slot (using table lock)
static getRawData(int $a_begin, int $a_end)
Read raw data for timespan.
static getMaxedOutDuration(int $a_from, int $a_to)
Get maxed out duration in given timeframe.
const SESSION_CLOSE_USER
const SESSION_CLOSE_LOGIN
const SESSION_CLOSE_FIRST
const SESSION_CLOSE_IDLE
const SESSION_CLOSE_LIMIT
const SESSION_CLOSE_EXPIRE
global $DIC
Definition: feed.php:28
$ilUser
Definition: imgupload.php:34
Interface ilDBInterface.
$res
Definition: ltiservices.php:69
string $key
Consumer key/client ID value.
Definition: System.php:193
global $ilSetting
Definition: privfeed.php:17