ILIAS  release_9 Revision v9.13-25-g2c18ec4c24f
class.ilSessionStatistics.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
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  // if we had to wait for the lock, no current slot should be returned here
230  $slot = self::getCurrentSlot($a_now);
231  if (!is_array($slot)) {
232  $slot = null;
233  return;
234  }
235 
236  // save slot to mark as taken
237  $fields = array(
238  "slot_begin" => array("integer", $slot[0]),
239  "slot_end" => array("integer", $slot[1]),
240  );
241  $ilDB->insert("usr_session_stats", $fields);
242  });
243 
244  $ilAtomQuery->run();
245 
246  return $slot;
247  }
248 
254  public static function aggretateRaw(int $a_now): void
255  {
256  if (!self::isActive()) {
257  return;
258  }
259 
260  $slot = self::createNewAggregationSlot($a_now);
261  while (is_array($slot)) {
262  self::aggregateRawHelper($slot[0], $slot[1]);
263  $slot = self::createNewAggregationSlot($a_now);
264  }
265 
266  // #12728
267  self::deleteAggregatedRaw($a_now);
268  }
269 
274  public static function aggregateRawHelper(int $a_begin, int $a_end): void
275  {
276  global $DIC;
277 
278  $ilDB = $DIC['ilDB'];
279  $ilSetting = $DIC['ilSetting'];
280 
281  // "relevant" closing types
282  $separate_closed = array(ilSession::SESSION_CLOSE_USER,
288 
289  // gather/process data (build event timeline)
290  $closed_counter = $events = array();
291  $opened_counter = 0;
292  foreach (self::getRawData($a_begin, $a_end) as $item) {
293  // open/close counters are _not_ time related
294 
295  // we could filter for undefined/invalid closing contexts
296  // and ignore those items, but this would make any debugging
297  // close to impossible
298  // "closed_other" would have been a good idea...
299 
300  // session opened
301  if ($item["start_time"] >= $a_begin) {
302  $opened_counter++;
303  $events[$item["start_time"]][] = 1;
304  }
305  // session closed
306  if ($item["end_time"] && $item["end_time"] <= $a_end) {
307  if (in_array($item["end_context"], $separate_closed, true)) {
308  if (!isset($closed_counter[$item["end_context"]])) {
309  $closed_counter[$item["end_context"]] = 0;
310  }
311 
312  $closed_counter[$item["end_context"]]++;
313  } else {
314  $closed_counter[0] = ($closed_counter[0] ?? 0) + 1;
315  }
316  $events[$item["end_time"]][] = -1;
317  }
318  }
319 
320  // initialising active statistical values
321  $active_begin = self::getNumberOfActiveRawSessions($a_begin - 1);
322  $active_end = $active_min = $active_max = $active_avg = $active_begin;
323 
324  // parsing events / building avergages
325  if (count($events)) {
326  $last_update_avg = $a_begin - 1;
327  $slot_seconds = self::SLOT_SIZE * 60;
328  $active_avg = 0;
329 
330  // parse all open/closing events
331  ksort($events);
332  foreach ($events as $ts => $actions) {
333  // actions which occur in the same second are "merged"
334  foreach ($actions as $action) {
335  // max
336  if ($action > 0) {
337  $active_end++;
338  }
339  // min
340  else {
341  $active_end--;
342  }
343  }
344 
345  // max
346  if ($active_end > $active_max) {
347  $active_max = $active_end;
348  }
349 
350  // min
351  if ($active_end < $active_min) {
352  $active_min = $active_end;
353  }
354 
355  // avg
356  $diff = $ts - $last_update_avg;
357  $active_avg += $diff / $slot_seconds * $active_end;
358  $last_update_avg = $ts;
359  }
360 
361  // add up to end of slot if needed
362  if ($last_update_avg < $a_end) {
363  $diff = $a_end - $last_update_avg;
364  $active_avg += $diff / $slot_seconds * $active_end;
365  }
366 
367  $active_avg = round($active_avg);
368  }
369  unset($events);
370 
371 
372  // do we (really) need a log here?
373  // $max_sessions = (int)$ilSetting->get("session_max_count", ilSessionControl::DEFAULT_MAX_COUNT);
374  $max_sessions = self::getLimitForSlot($a_begin);
375 
376  // save aggregated data
377  $fields = array(
378  "active_min" => array("integer", $active_min),
379  "active_max" => array("integer", $active_max),
380  "active_avg" => array("integer", $active_avg),
381  "active_end" => array("integer", $active_end),
382  "opened" => array("integer", $opened_counter),
383  "closed_manual" => array("integer", (int) ($closed_counter[ilSession::SESSION_CLOSE_USER] ?? 0)),
384  "closed_expire" => array("integer", (int) ($closed_counter[ilSession::SESSION_CLOSE_EXPIRE] ?? 0)),
385  "closed_idle" => array("integer", (int) ($closed_counter[ilSession::SESSION_CLOSE_IDLE] ?? 0)),
386  "closed_idle_first" => array("integer", (int) ($closed_counter[ilSession::SESSION_CLOSE_FIRST] ?? 0)),
387  "closed_limit" => array("integer", (int) ($closed_counter[ilSession::SESSION_CLOSE_LIMIT] ?? 0)),
388  "closed_login" => array("integer", (int) ($closed_counter[ilSession::SESSION_CLOSE_LOGIN] ?? 0)),
389  "closed_misc" => array("integer", (int) ($closed_counter[0] ?? 0)),
390  "max_sessions" => array("integer", $max_sessions)
391  );
392  $ilDB->update(
393  "usr_session_stats",
394  $fields,
395  array("slot_begin" => array("integer", $a_begin),
396  "slot_end" => array("integer", $a_end))
397  );
398  }
399 
405  protected static function deleteAggregatedRaw($a_now): void
406  {
407  global $DIC;
408 
409  $ilDB = $DIC['ilDB'];
410 
411  // we are rather defensive here - 7 days BEFORE current aggregation
412  $cut = $a_now - (60 * 60 * 24 * 7);
413 
414  $ilDB->manipulate("DELETE FROM usr_session_stats_raw" .
415  " WHERE start_time <= " . $ilDB->quote($cut, "integer"));
416  }
417 
421  public static function getLastMaxedOut(): int
422  {
423  global $DIC;
424 
425  $ilDB = $DIC['ilDB'];
426 
427  $sql = "SELECT max(slot_end) latest FROM usr_session_stats" .
428  " WHERE active_max >= max_sessions" .
429  " AND max_sessions > " . $ilDB->quote(0, "integer");
430  $res = $ilDB->query($sql);
431  $row = $ilDB->fetchAssoc($res);
432  if ($row["latest"]) {
433  return (int) $row["latest"];
434  }
435  return 0;
436  }
437 
443  public static function getMaxedOutDuration(int $a_from, int $a_to): ?int
444  {
445  global $DIC;
446 
447  $ilDB = $DIC['ilDB'];
448 
449  $sql = "SELECT SUM(slot_end-slot_begin) dur FROM usr_session_stats" .
450  " WHERE active_max >= max_sessions" .
451  " AND max_sessions > " . $ilDB->quote(0, "integer") .
452  " AND slot_end > " . $ilDB->quote($a_from, "integer") .
453  " AND slot_begin < " . $ilDB->quote($a_to, "integer");
454  $res = $ilDB->query($sql);
455  $row = $ilDB->fetchAssoc($res);
456  if ($row["dur"]) {
457  return (int) $row["dur"];
458  }
459  //TODO check if return null as timestamp causes issues
460  return null;
461  }
462 
466  public static function getNumberOfSessionsByType(int $a_from, int $a_to): array
467  {
468  global $DIC;
469 
470  $ilDB = $DIC['ilDB'];
471 
472  $sql = "SELECT SUM(opened) opened, SUM(closed_manual) closed_manual," .
473  " SUM(closed_expire) closed_expire, SUM(closed_idle) closed_idle," .
474  " SUM(closed_idle_first) closed_idle_first, SUM(closed_limit) closed_limit," .
475  " SUM(closed_login) closed_login, SUM(closed_misc) closed_misc" .
476  " FROM usr_session_stats" .
477  " WHERE slot_end > " . $ilDB->quote($a_from, "integer") .
478  " AND slot_begin < " . $ilDB->quote($a_to, "integer");
479  $res = $ilDB->query($sql);
480  return $ilDB->fetchAssoc($res);
481  }
482 
486  public static function getActiveSessions(int $a_from, int $a_to): array
487  {
488  global $DIC;
489 
491  $ilDB = $DIC['ilDB'];
492 
493  $sql = "SELECT slot_begin, slot_end, active_min, active_max, active_avg," .
494  " max_sessions" .
495  " FROM usr_session_stats" .
496  " WHERE slot_end > " . $ilDB->quote($a_from, "integer") .
497  " AND slot_begin < " . $ilDB->quote($a_to, "integer") .
498  " ORDER BY slot_begin";
499  $res = $ilDB->query($sql);
500  $all = array();
501  while ($row = $ilDB->fetchAssoc($res)) {
502  $entry = [];
503  foreach ($row as $key => $value) {
504  $entry[$key] = (int) $value;
505  }
506  $all[] = $entry;
507  }
508  return $all;
509  }
510 
514  public static function getLastAggregation(): ?int
515  {
516  global $DIC;
517 
518  $ilDB = $DIC['ilDB'];
519 
520  $sql = "SELECT max(slot_end) latest FROM usr_session_stats";
521  $res = $ilDB->query($sql);
522  $row = $ilDB->fetchAssoc($res);
523  if ($row["latest"]) {
524  return (int) $row["latest"];
525  }
526  //TODO check if return null as timestamp causes issues
527  return null;
528  }
529 
534  public static function getLimitForSlot(int $a_timestamp): int
535  {
536  global $DIC;
537 
538  $ilDB = $DIC['ilDB'];
539  $ilSetting = $DIC['ilSetting'];
540 
541  $ilDB->setLimit(1);
542  $sql = "SELECT maxval FROM usr_session_log" .
543  " WHERE tstamp <= " . $ilDB->quote($a_timestamp, "integer") .
544  " ORDER BY tstamp DESC";
545  $res = $ilDB->query($sql);
546  $val = $ilDB->fetchAssoc($res);
547  if (isset($val["maxval"]) && $val["maxval"]) {
548  return (int) $val["maxval"];
549  }
550 
551  return (int) $ilSetting->get("session_max_count", (string) ilSessionControl::DEFAULT_MAX_COUNT);
552  }
553 
557  public static function updateLimitLog(int $a_new_value): void
558  {
559  global $DIC;
560 
561  $ilDB = $DIC['ilDB'];
562  $ilSetting = $DIC['ilSetting'];
563  $ilUser = $DIC['ilUser'];
564 
565  $new_value = $a_new_value;
566  $old_value = (int) $ilSetting->get("session_max_count", (string) ilSessionControl::DEFAULT_MAX_COUNT);
567 
568  if ($new_value !== $old_value) {
569  $fields = array(
570  "tstamp" => array("timestamp", time()),
571  "maxval" => array("integer", $new_value),
572  "user_id" => array("integer", $ilUser->getId())
573  );
574  $ilDB->insert("usr_session_log", $fields);
575  }
576  }
577 }
const SESSION_CLOSE_IDLE
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
const DEFAULT_MAX_COUNT
default value for settings that have not been defined in setup or administration yet ...
static updateLimitLog(int $a_new_value)
Log max session setting.
static getNumberOfActiveRawSessions(int $a_time)
Count number of active sessions at given time.
$res
Definition: ltiservices.php:69
static getLimitForSlot(int $a_timestamp)
Get max session setting for given timestamp.
static getNumberOfSessionsByType(int $a_from, int $a_to)
Get session counters by type (opened, closed)
static deleteAggregatedRaw($a_now)
Remove already aggregated raw data.
insert(string $table_name, array $values)
static createRawEntry(string $a_session_id, int $a_session_type, int $a_timestamp, int $a_user_id)
Create raw data entry.
const SESSION_CLOSE_LOGIN
static aggretateRaw(int $a_now)
Aggregate raw session data (older than given time)
const SESSION_CLOSE_EXPIRE
global $DIC
Definition: feed.php:28
static aggregateRawHelper(int $a_begin, int $a_end)
Aggregate statistics data for one slot.
static getLastAggregation()
Get timestamp of last aggregation.
static getCurrentSlot(int $a_now)
Get next slot to aggregate.
const SESSION_CLOSE_USER
string $key
Consumer key/client ID value.
Definition: System.php:193
static closeRawEntry($a_session_id, ?int $a_context=null, $a_expired_at=null)
Close raw data entry.
const SESSION_CLOSE_LIMIT
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.
static isActive()
Is session statistics active at all?
static array $session_types_controlled
global $ilSetting
Definition: privfeed.php:18
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
Definition: plugin.php:23
static createNewAggregationSlot(int $a_now)
Create new slot (using table lock)
static getLastMaxedOut()
Get latest slot during which sessions were maxed out.
const SESSION_CLOSE_FIRST