ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
class.ilSessionStatistics.php
Go to the documentation of this file.
1<?php
2
19declare(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
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}
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
Definition: plugin.php:23
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)
static getNumberOfSessionsByType(int $a_from, int $a_to)
Get session counters by type (opened, closed)
static aggregateRawHelper(int $a_begin, int $a_end)
Aggregate statistics data for one slot.
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 createNewAggregationSlot(int $a_now)
Create new slot (using table lock)
static getRawData(int $a_begin, int $a_end)
Read raw data for timespan.
static deleteAggregatedRaw(int $a_now)
Remove already aggregated raw data.
const int SESSION_CLOSE_LOGIN
const int SESSION_CLOSE_EXPIRE
const int SESSION_CLOSE_USER
Interface ilDBInterface.
$res
Definition: ltiservices.php:69
global $ilSetting
Definition: privfeed.php:31
global $DIC
Definition: shib_login.php:26