ILIAS  trunk Revision v11.0_alpha-2638-g80c1d007f79
class.ilSessionStatistics.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
22 {
23  private const int SLOT_SIZE = 15;
24 
28  public static function isActive(): bool
29  {
30  global $DIC;
31 
32  $ilSetting = $DIC['ilSetting'];
33 
34  return (bool) $ilSetting->get('session_statistics', '1');
35  }
36 
40  public static function createRawEntry(string $a_session_id, int $a_session_type, int $a_timestamp, int $a_user_id): void
41  {
42  global $DIC;
43 
44  $ilDB = $DIC['ilDB'];
45 
46  if (!$a_user_id || !$a_session_id || !self::isActive()) {
47  return;
48  }
49 
50  // #9669: if a session was destroyed and somehow the session id is still
51  // in use there will be a id-collision for the raw-entry
52 
53  $ilDB->replace(
54  'usr_session_stats_raw',
55  [
56  'session_id' => ['text', $a_session_id]
57  ],
58  [
59  'type' => ['integer', $a_session_type],
60  'start_time' => ['integer', $a_timestamp],
61  'user_id' => ['integer', $a_user_id]
62  ]
63  );
64  }
65 
73  public static function closeRawEntry($a_session_id, ?int $a_context = null, $a_expired_at = null): void
74  {
75  global $DIC;
76 
77  $ilDB = $DIC['ilDB'];
78 
79  if (!self::isActive()) {
80  return;
81  }
82 
83  // single entry
84  if (!is_array($a_session_id)) {
85  if ($a_expired_at) {
86  $end_time = $a_expired_at;
87  } else {
88  $end_time = time();
89  }
90  $sql = 'UPDATE usr_session_stats_raw' .
91  ' SET end_time = ' . $ilDB->quote($end_time, 'integer');
92  if ($a_context) {
93  $sql .= ',end_context = ' . $ilDB->quote($a_context, 'integer');
94  }
95  $sql .= ' WHERE session_id = ' . $ilDB->quote($a_session_id, 'text') .
96  ' AND end_time IS NULL';
97  $ilDB->manipulate($sql);
98  }
99  // batch closing
100  elseif (!$a_expired_at) {
101  $sql = 'UPDATE usr_session_stats_raw' .
102  ' SET end_time = ' . $ilDB->quote(time(), 'integer');
103  if ($a_context) {
104  $sql .= ',end_context = ' . $ilDB->quote($a_context, 'integer');
105  }
106  $sql .= ' WHERE ' . $ilDB->in('session_id', $a_session_id, false, 'text') .
107  ' AND end_time IS NULL';
108  $ilDB->manipulate($sql);
109  }
110  // batch with individual timestamps
111  else {
112  foreach ($a_session_id as $id => $ts) {
113  $sql = 'UPDATE usr_session_stats_raw' .
114  ' SET end_time = ' . $ilDB->quote($ts, 'integer');
115  if ($a_context) {
116  $sql .= ',end_context = ' . $ilDB->quote($a_context, 'integer');
117  }
118  $sql .= ' WHERE session_id = ' . $ilDB->quote($id, 'text') .
119  ' AND end_time IS NULL';
120  $ilDB->manipulate($sql);
121  }
122  }
123  }
124 
130  protected static function getCurrentSlot(int $a_now): ?array
131  {
132  global $DIC;
133 
134  $ilDB = $DIC['ilDB'];
135 
136  // get latest slot in db
137  $sql = 'SELECT MAX(slot_end) previous_slot_end' .
138  ' FROM usr_session_stats';
139  $res = $ilDB->query($sql);
140  $row = $ilDB->fetchAssoc($res);
141  $previous_slot_end = $row['previous_slot_end'];
142 
143  // no previous slot? calculate last complete slot
144  // should we use minimum session raw date instead? (problem: table lock)
145  if (!$previous_slot_end) {
146  $slot = (int) (floor(date('i') / self::SLOT_SIZE));
147  // last slot of previous hour
148  if (!$slot) {
149  $current_slot_begin = mktime((int) date('H', $a_now) - 1, 60 - self::SLOT_SIZE, 0);
150  }
151  // "normalize" to slot
152  else {
153  $current_slot_begin = mktime((int) date('H', $a_now), ($slot - 1) * self::SLOT_SIZE, 0);
154  }
155  } else {
156  $current_slot_begin = $previous_slot_end + 1;
157  }
158 
159  $current_slot_end = $current_slot_begin + (60 * self::SLOT_SIZE) - 1;
160 
161  // no complete slot: nothing to do yet
162  if ($current_slot_end < $a_now) {
163  return [$current_slot_begin, $current_slot_end];
164  }
165  return null;
166  }
167 
168  protected static function getNumberOfActiveRawSessions(int $a_time): int
169  {
170  global $DIC;
171 
172  $ilDB = $DIC['ilDB'];
173 
174  $sql = 'SELECT COUNT(*) counter FROM usr_session_stats_raw' .
175  ' WHERE (end_time IS NULL OR end_time >= ' . $ilDB->quote($a_time, 'integer') . ')' .
176  ' AND start_time <= ' . $ilDB->quote($a_time, 'integer') .
177  ' AND ' . $ilDB->in('type', ilSessionControl::$session_types_controlled, false, 'integer');
178  $res = $ilDB->query($sql);
179  $row = $ilDB->fetchAssoc($res);
180  return (int) $row['counter'];
181  }
182 
186  protected static function getRawData(int $a_begin, int $a_end): array
187  {
188  global $DIC;
189 
190  $ilDB = $DIC['ilDB'];
191 
192  $sql = 'SELECT start_time,end_time,end_context FROM usr_session_stats_raw' .
193  ' WHERE start_time <= ' . $ilDB->quote($a_end, 'integer') .
194  ' AND (end_time IS NULL OR end_time >= ' . $ilDB->quote($a_begin, 'integer') . ')' .
195  ' AND ' . $ilDB->in('type', ilSessionControl::$session_types_controlled, false, 'integer') .
196  ' ORDER BY start_time';
197  $res = $ilDB->query($sql);
198  $all = [];
199  while ($row = $ilDB->fetchAssoc($res)) {
200  $all[] = $row;
201  }
202  return $all;
203  }
204 
210  protected static function createNewAggregationSlot(int $a_now): ?array
211  {
212  global $DIC;
213 
214  $ilDB = $DIC['ilDB'];
215 
216  $ilAtomQuery = $ilDB->buildAtomQuery();
217  $ilAtomQuery->addTableLock('usr_session_stats');
218 
219  $ilAtomQuery->addQueryCallable(function (ilDBInterface $ilDB) use ($a_now, &$slot) {
220  // if we had to wait for the lock, no current slot should be returned here
221  $slot = self::getCurrentSlot($a_now);
222  if (!is_array($slot)) {
223  $slot = null;
224  return;
225  }
226 
227  // save slot to mark as taken
228  $fields = [
229  'slot_begin' => ['integer', $slot[0]],
230  'slot_end' => ['integer', $slot[1]],
231  ];
232  $ilDB->insert('usr_session_stats', $fields);
233  });
234 
235  $ilAtomQuery->run();
236 
237  return $slot;
238  }
239 
243  public static function aggretateRaw(int $a_now): void
244  {
245  if (!self::isActive()) {
246  return;
247  }
248 
249  $slot = self::createNewAggregationSlot($a_now);
250  while (is_array($slot)) {
251  self::aggregateRawHelper($slot[0], $slot[1]);
252  $slot = self::createNewAggregationSlot($a_now);
253  }
254 
255  // #12728
256  self::deleteAggregatedRaw($a_now);
257  }
258 
263  public static function aggregateRawHelper(int $a_begin, int $a_end): void
264  {
265  global $DIC;
266 
267  $ilDB = $DIC['ilDB'];
268 
269  // "relevant" closing types
270  $separate_closed = [
274  ];
275 
276  // gather/process data (build event timeline)
277  $closed_counter = $events = [];
278  $opened_counter = 0;
279  foreach (self::getRawData($a_begin, $a_end) as $item) {
280  // open/close counters are _not_ time related
281 
282  // we could filter for undefined/invalid closing contexts
283  // and ignore those items, but this would make any debugging
284  // close to impossible
285  // "closed_other" would have been a good idea...
286 
287  // session opened
288  if ($item['start_time'] >= $a_begin) {
289  $opened_counter++;
290  $events[$item['start_time']][] = 1;
291  }
292  // session closed
293  if ($item['end_time'] && $item['end_time'] <= $a_end) {
294  if (in_array($item['end_context'], $separate_closed, true)) {
295  if (!isset($closed_counter[$item['end_context']])) {
296  $closed_counter[$item['end_context']] = 0;
297  }
298 
299  $closed_counter[$item['end_context']]++;
300  } else {
301  $closed_counter[0] = ($closed_counter[0] ?? 0) + 1;
302  }
303  $events[$item['end_time']][] = -1;
304  }
305  }
306 
307  // initialising active statistical values
308  $active_begin = self::getNumberOfActiveRawSessions($a_begin - 1);
309  $active_end = $active_min = $active_max = $active_avg = $active_begin;
310 
311  // parsing events / building avergages
312  if (count($events)) {
313  $last_update_avg = $a_begin - 1;
314  $slot_seconds = self::SLOT_SIZE * 60;
315  $active_avg = 0;
316 
317  // parse all open/closing events
318  ksort($events);
319  foreach ($events as $ts => $actions) {
320  // actions which occur in the same second are "merged"
321  foreach ($actions as $action) {
322  // max
323  if ($action > 0) {
324  $active_end++;
325  }
326  // min
327  else {
328  $active_end--;
329  }
330  }
331 
332  // max
333  if ($active_end > $active_max) {
334  $active_max = $active_end;
335  }
336 
337  // min
338  if ($active_end < $active_min) {
339  $active_min = $active_end;
340  }
341 
342  // avg
343  $diff = $ts - $last_update_avg;
344  $active_avg += $diff / $slot_seconds * $active_end;
345  $last_update_avg = $ts;
346  }
347 
348  // add up to end of slot if needed
349  if ($last_update_avg < $a_end) {
350  $diff = $a_end - $last_update_avg;
351  $active_avg += $diff / $slot_seconds * $active_end;
352  }
353 
354  $active_avg = round($active_avg);
355  }
356  unset($events);
357 
358  // save aggregated data
359  $fields = [
360  'active_min' => ['integer', $active_min],
361  'active_max' => ['integer', $active_max],
362  'active_avg' => ['integer', $active_avg],
363  'active_end' => ['integer', $active_end],
364  'opened' => ['integer', $opened_counter],
365  'closed_manual' => ['integer', (int) ($closed_counter[ilSession::SESSION_CLOSE_USER] ?? 0)],
366  'closed_expire' => ['integer', (int) ($closed_counter[ilSession::SESSION_CLOSE_EXPIRE] ?? 0)],
367  'closed_login' => ['integer', (int) ($closed_counter[ilSession::SESSION_CLOSE_LOGIN] ?? 0)],
368  'closed_misc' => ['integer', (int) ($closed_counter[0] ?? 0)],
369  ];
370  $ilDB->update(
371  'usr_session_stats',
372  $fields,
373  [
374  'slot_begin' => ['integer', $a_begin],
375  'slot_end' => ['integer', $a_end]
376  ]
377  );
378  }
379 
383  protected static function deleteAggregatedRaw(int $a_now): void
384  {
385  global $DIC;
386 
387  $ilDB = $DIC['ilDB'];
388 
389  // we are rather defensive here - 7 days BEFORE current aggregation
390  $cut = $a_now - (60 * 60 * 24 * 7);
391 
392  $ilDB->manipulate(
393  'DELETE FROM usr_session_stats_raw' .
394  ' WHERE start_time <= ' . $ilDB->quote($cut, 'integer')
395  );
396  }
397 
401  public static function getNumberOfSessionsByType(int $a_from, int $a_to): array
402  {
403  global $DIC;
404 
405  $ilDB = $DIC['ilDB'];
406 
407  $sql = 'SELECT SUM(opened) opened, SUM(closed_manual) closed_manual,' .
408  ' SUM(closed_expire) closed_expire,' .
409  ' SUM(closed_login) closed_login, SUM(closed_misc) closed_misc' .
410  ' FROM usr_session_stats' .
411  ' WHERE slot_end > ' . $ilDB->quote($a_from, 'integer') .
412  ' AND slot_begin < ' . $ilDB->quote($a_to, 'integer');
413  $res = $ilDB->query($sql);
414  return $ilDB->fetchAssoc($res);
415  }
416 
420  public static function getActiveSessions(int $a_from, int $a_to): array
421  {
422  global $DIC;
423 
425  $ilDB = $DIC['ilDB'];
426 
427  $sql = 'SELECT slot_begin, slot_end, active_min, active_max, active_avg' .
428  ' FROM usr_session_stats' .
429  ' WHERE slot_end > ' . $ilDB->quote($a_from, 'integer') .
430  ' AND slot_begin < ' . $ilDB->quote($a_to, 'integer') .
431  ' ORDER BY slot_begin';
432  $res = $ilDB->query($sql);
433  $all = [];
434  while ($row = $ilDB->fetchAssoc($res)) {
435  $entry = [];
436  foreach ($row as $key => $value) {
437  $entry[$key] = (int) $value;
438  }
439  $all[] = $entry;
440  }
441  return $all;
442  }
443 
447  public static function getLastAggregation(): ?int
448  {
449  global $DIC;
450 
451  $ilDB = $DIC['ilDB'];
452 
453  $sql = 'SELECT max(slot_end) latest FROM usr_session_stats';
454  $res = $ilDB->query($sql);
455  $row = $ilDB->fetchAssoc($res);
456  if ($row['latest']) {
457  return (int) $row['latest'];
458  }
459  //TODO check if return null as timestamp causes issues
460  return null;
461  }
462 }
static getNumberOfActiveRawSessions(int $a_time)
$res
Definition: ltiservices.php:66
static getNumberOfSessionsByType(int $a_from, int $a_to)
Get session counters by type (opened, closed)
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 int SESSION_CLOSE_LOGIN
static deleteAggregatedRaw(int $a_now)
Remove already aggregated raw data.
static aggretateRaw(int $a_now)
Aggregate raw session data (older than given time)
while($session_entry=$r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) return null
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 int SESSION_CLOSE_USER
global $DIC
Definition: shib_login.php:26
static closeRawEntry($a_session_id, ?int $a_context=null, $a_expired_at=null)
Close raw data entry.
static getRawData(int $a_begin, int $a_end)
Read raw data for timespan.
static isActive()
Is session statistics active at all?
static array $session_types_controlled
const int SESSION_CLOSE_EXPIRE
global $ilSetting
Definition: privfeed.php:31
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
Definition: plugin.php:23
static createNewAggregationSlot(int $a_now)
Create new slot (using table lock)