ILIAS  release_4-3 Revision
 All Data Structures Namespaces Files Functions Variables Groups Pages
class.ilSessionStatistics.php
Go to the documentation of this file.
1 <?php
2 /* Copyright (c) 1998-2009 ILIAS open source, Extended GPL, see docs/LICENSE */
3 
4 
12 {
13  const SLOT_SIZE = 15;
14 
20  public static function isActive()
21  {
22  global $ilSetting;
23 
24  return (bool)$ilSetting->get('session_statistics', 1);
25 
26  /* #13566 - includes somehow won't work this late in the request - doing it directly
27  include_once "Services/Tracking/classes/class.ilObjUserTracking.php";
28  return ilObjUserTracking::_enabledSessionStatistics();
29  */
30  }
31 
40  public static function createRawEntry($a_session_id, $a_session_type, $a_timestamp, $a_user_id)
41  {
42  global $ilDB;
43 
44  if(!$a_user_id || !$a_session_id || !self::isActive())
45  {
46  return;
47  }
48 
49  // #9669: if a session was destroyed and somehow the session id is still
50  // in use there will be a id-collision for the raw-entry
51 
52  $ilDB->replace(
53  "usr_session_stats_raw",
54  array(
55  "session_id" => array("text", $a_session_id)
56  ),
57  array(
58  "type" => array("integer", $a_session_type),
59  "start_time" => array("integer", $a_timestamp),
60  "user_id" => array("integer", $a_user_id)
61  )
62  );
63  }
64 
72  public static function closeRawEntry($a_session_id, $a_context = null, $a_expired_at = null)
73  {
74  global $ilDB;
75 
76  if(!self::isActive())
77  {
78  return;
79  }
80 
81  // single entry
82  if(!is_array($a_session_id))
83  {
84  if($a_expired_at)
85  {
86  $end_time = $a_expired_at;
87  }
88  else
89  {
90  $end_time = time();
91  }
92  $sql = "UPDATE usr_session_stats_raw".
93  " SET end_time = ".$ilDB->quote($end_time, "integer");
94  if($a_context)
95  {
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  else if(!$a_expired_at)
104  {
105  $sql = "UPDATE usr_session_stats_raw".
106  " SET end_time = ".$ilDB->quote(time(), "integer");
107  if($a_context)
108  {
109  $sql .= ",end_context = ".$ilDB->quote($a_context, "integer");
110  }
111  $sql .= " WHERE ".$ilDB->in("session_id", $a_session_id, false, "text").
112  " AND end_time IS NULL";
113  $ilDB->manipulate($sql);
114  }
115  // batch with individual timestamps
116  else
117  {
118  foreach($a_session_id as $id => $ts)
119  {
120  $sql = "UPDATE usr_session_stats_raw".
121  " SET end_time = ".$ilDB->quote($ts, "integer");
122  if($a_context)
123  {
124  $sql .= ",end_context = ".$ilDB->quote($a_context, "integer");
125  }
126  $sql .= " WHERE session_id = ".$ilDB->quote($id, "text").
127  " AND end_time IS NULL";
128  $ilDB->manipulate($sql);
129  }
130  }
131  }
132 
139  protected static function getCurrentSlot($a_now)
140  {
141  global $ilDB;
142 
143  // get latest slot in db
144  $sql = "SELECT MAX(slot_end) previous_slot_end".
145  " FROM usr_session_stats";
146  $res = $ilDB->query($sql);
147  $row = $ilDB->fetchAssoc($res);
148  $previous_slot_end = $row["previous_slot_end"];
149 
150  // no previous slot? calculate last complete slot
151  // should we use minimum session raw date instead? (problem: table lock)
152  if(!$previous_slot_end)
153  {
154  $slot = floor(date("i")/self::SLOT_SIZE);
155  // last slot of previous hour
156  if(!$slot)
157  {
158  $current_slot_begin = mktime(date("H", $a_now)-1, 60-self::SLOT_SIZE, 0);
159  }
160  // "normalize" to slot
161  else
162  {
163  $current_slot_begin = mktime(date("H", $a_now), ($slot-1)*self::SLOT_SIZE, 0);
164  }
165  }
166  else
167  {
168  $current_slot_begin = $previous_slot_end+1;
169  }
170 
171  $current_slot_end = $current_slot_begin+(60*self::SLOT_SIZE)-1;
172 
173  // no complete slot: nothing to do yet
174  if($current_slot_end < $a_now)
175  {
176  return array($current_slot_begin, $current_slot_end);
177  }
178  }
179 
186  protected static function getNumberOfActiveRawSessions($a_time)
187  {
188  global $ilDB;
189 
190  $sql = "SELECT COUNT(*) counter FROM usr_session_stats_raw".
191  " WHERE (end_time IS NULL OR end_time >= ".$ilDB->quote($a_time, "integer").")".
192  " AND start_time <= ".$ilDB->quote($a_time, "integer").
193  " AND ".$ilDB->in("type", ilSessionControl::$session_types_controlled, false, "integer");
194  $res = $ilDB->query($sql);
195  $row = $ilDB->fetchAssoc($res);
196  return $row["counter"];
197  }
198 
206  protected static function getRawData($a_begin, $a_end)
207  {
208  global $ilDB;
209 
210  $sql = "SELECT start_time,end_time,end_context FROM usr_session_stats_raw".
211  " WHERE start_time <= ".$ilDB->quote($a_end, "integer").
212  " AND (end_time IS NULL OR end_time >= ".$ilDB->quote($a_begin, "integer").")".
213  " AND ".$ilDB->in("type", ilSessionControl::$session_types_controlled, false, "integer").
214  " ORDER BY start_time";
215  $res = $ilDB->query($sql);
216  $all = array();
217  while($row = $ilDB->fetchAssoc($res))
218  {
219  $all[] = $row;
220  }
221  return $all;
222  }
223 
230  protected static function createNewAggregationSlot($a_now)
231  {
232  global $ilDB;
233 
234  // get exclusive lock
235  $ilDB->lockTables(array(array("name"=>"usr_session_stats", "type"=>ilDB::LOCK_WRITE)));
236 
237  // if we had to wait for the lock, no current slot should be returned here
238  $slot = self::getCurrentSlot($a_now);
239  if(!is_array($slot))
240  {
241  $ilDB->unlockTables();
242  return false;
243  }
244 
245  // save slot to mark as taken
246  $fields = array(
247  "slot_begin" => array("integer", $slot[0]),
248  "slot_end" => array("integer", $slot[1]),
249  );
250  $ilDB->insert("usr_session_stats", $fields);
251 
252  $ilDB->unlockTables();
253 
254  return $slot;
255  }
256 
262  public static function aggretateRaw($a_now)
263  {
264  if(!self::isActive())
265  {
266  return;
267  }
268 
269  $slot = self::createNewAggregationSlot($a_now);
270  while(is_array($slot))
271  {
272  self::aggregateRawHelper($slot[0], $slot[1]);
273  $slot = self::createNewAggregationSlot($a_now);
274  }
275 
276  // #12728
278  }
279 
286  public static function aggregateRawHelper($a_begin, $a_end)
287  {
288  global $ilDB, $ilSetting;
289 
290  // "relevant" closing types
291  $separate_closed = array(ilSession::SESSION_CLOSE_USER,
297 
298  // gather/process data (build event timeline)
299  $closed_counter = $events = array();
300  $opened_counter = 0;
301  foreach(self::getRawData($a_begin, $a_end) as $item)
302  {
303  // open/close counters are _not_ time related
304 
305  // we could filter for undefined/invalid closing contexts
306  // and ignore those items, but this would make any debugging
307  // close to impossible
308  // "closed_other" would have been a good idea...
309 
310  // session opened
311  if($item["start_time"] >= $a_begin)
312  {
313  $opened_counter++;
314  $events[$item["start_time"]][] = 1;
315  }
316  // session closed
317  if($item["end_time"] && $item["end_time"] <= $a_end)
318  {
319  if(in_array($item["end_context"], $separate_closed))
320  {
321  $closed_counter[$item["end_context"]]++;
322  }
323  else
324  {
325  $closed_counter[0]++;
326  }
327  $events[$item["end_time"]][] = -1;
328  }
329  }
330 
331  // initialising active statistical values
332  $active_begin = self::getNumberOfActiveRawSessions($a_begin-1);
333  $active_end = $active_min = $active_max = $active_avg = $active_begin;
334 
335  // parsing events / building avergages
336  if(sizeof($events))
337  {
338  $last_update_avg = $a_begin-1;
339  $slot_seconds = self::SLOT_SIZE*60;
340  $active_avg = 0;
341 
342  // parse all open/closing events
343  ksort($events);
344  foreach($events as $ts => $actions)
345  {
346  // actions which occur in the same second are "merged"
347  foreach($actions as $action)
348  {
349  // max
350  if($action > 0)
351  {
352  $active_end++;
353  }
354  // min
355  else
356  {
357  $active_end--;
358  }
359  }
360 
361  // max
362  if($active_end > $active_max)
363  {
364  $active_max = $active_end;
365  }
366 
367  // min
368  if($active_end < $active_min)
369  {
370  $active_min = $active_end;
371  }
372 
373  // avg
374  $diff = $ts-$last_update_avg;
375  $active_avg += $diff/$slot_seconds*$active_end;
376  $last_update_avg = $ts;
377  }
378  unset($actions);
379 
380  // add up to end of slot if needed
381  if($last_update_avg < $a_end)
382  {
383  $diff = $a_end-$last_update_avg;
384  $active_avg += $diff/$slot_seconds*$active_end;
385  }
386 
387  $active_avg = round($active_avg);
388  }
389  unset($events);
390 
391 
392  // do we (really) need a log here?
393  // $max_sessions = (int)$ilSetting->get("session_max_count", ilSessionControl::DEFAULT_MAX_COUNT);
394  $max_sessions = self::getLimitForSlot($a_begin);
395 
396  // save aggregated data
397  $fields = array(
398  "active_min" => array("integer", $active_min),
399  "active_max" => array("integer", $active_max),
400  "active_avg" => array("integer", $active_avg),
401  "active_end" => array("integer", $active_end),
402  "opened" => array("integer", $opened_counter),
403  "closed_manual" => array("integer", (int)$closed_counter[ilSession::SESSION_CLOSE_USER]),
404  "closed_expire" => array("integer", (int)$closed_counter[ilSession::SESSION_CLOSE_EXPIRE]),
405  "closed_idle" => array("integer", (int)$closed_counter[ilSession::SESSION_CLOSE_IDLE]),
406  "closed_idle_first" => array("integer", (int)$closed_counter[ilSession::SESSION_CLOSE_FIRST]),
407  "closed_limit" => array("integer", (int)$closed_counter[ilSession::SESSION_CLOSE_LIMIT]),
408  "closed_login" => array("integer", (int)$closed_counter[ilSession::SESSION_CLOSE_LOGIN]),
409  "closed_misc" => array("integer", (int)$closed_counter[0]),
410  "max_sessions" => array("integer", (int)$max_sessions)
411  );
412  $ilDB->update("usr_session_stats", $fields,
413  array("slot_begin" => array("integer", $a_begin),
414  "slot_end" => array("integer", $a_end)));
415  }
416 
422  protected static function deleteAggregatedRaw($a_now)
423  {
424  global $ilDB;
425 
426  // we are rather defensive here - 7 days BEFORE current aggregation
427  $cut = $a_now-(60*60*24*7);
428 
429  $ilDB->manipulate("DELETE FROM usr_session_stats_raw".
430  " WHERE start_time <= ".$ilDB->quote($cut, "integer"));
431  }
432 
438  public static function getLastMaxedOut()
439  {
440  global $ilDB;
441 
442  $sql = "SELECT max(slot_end) latest FROM usr_session_stats".
443  " WHERE active_max >= max_sessions".
444  " AND max_sessions > ".$ilDB->quote(0, "integer");
445  $res = $ilDB->query($sql);
446  $row = $ilDB->fetchAssoc($res);
447  if($row["latest"])
448  {
449  return $row["latest"];
450  }
451  }
452 
460  public static function getMaxedOutDuration($a_from, $a_to)
461  {
462  global $ilDB;
463 
464  $sql = "SELECT SUM(slot_end-slot_begin) dur FROM usr_session_stats".
465  " WHERE active_max >= max_sessions".
466  " AND max_sessions > ".$ilDB->quote(0, "integer").
467  " AND slot_end > ".$ilDB->quote($a_from, "integer").
468  " AND slot_begin < ".$ilDB->quote($a_to, "integer");
469  $res = $ilDB->query($sql);
470  $row = $ilDB->fetchAssoc($res);
471  if($row["dur"])
472  {
473  return $row["dur"];
474  }
475  }
476 
484  public static function getNumberOfSessionsByType($a_from, $a_to)
485  {
486  global $ilDB;
487 
488  $sql = "SELECT SUM(opened) opened, SUM(closed_manual) closed_manual,".
489  " SUM(closed_expire) closed_expire, SUM(closed_idle) closed_idle,".
490  " SUM(closed_idle_first) closed_idle_first, SUM(closed_limit) closed_limit,".
491  " SUM(closed_login) closed_login, SUM(closed_misc) closed_misc".
492  " FROM usr_session_stats".
493  " WHERE slot_end > ".$ilDB->quote($a_from, "integer").
494  " AND slot_begin < ".$ilDB->quote($a_to, "integer");
495  $res = $ilDB->query($sql);
496  return $ilDB->fetchAssoc($res);
497  }
498 
506  public static function getActiveSessions($a_from, $a_to)
507  {
508  global $ilDB;
509 
510  $sql = "SELECT slot_begin, slot_end, active_min, active_max, active_avg,".
511  " max_sessions".
512  " FROM usr_session_stats".
513  " WHERE slot_end > ".$ilDB->quote($a_from, "integer").
514  " AND slot_begin < ".$ilDB->quote($a_to, "integer").
515  " ORDER BY slot_begin";
516  $res = $ilDB->query($sql);
517  $all = array();
518  while($row = $ilDB->fetchAssoc($res))
519  {
520  $all[] = $row;
521  }
522  return $all;
523  }
524 
530  public static function getLastAggregation()
531  {
532  global $ilDB;
533 
534  $sql = "SELECT max(slot_end) latest FROM usr_session_stats";
535  $res = $ilDB->query($sql);
536  $row = $ilDB->fetchAssoc($res);
537  if($row["latest"])
538  {
539  return $row["latest"];
540  }
541  }
542 
549  public static function getLimitForSlot($a_timestamp)
550  {
551  global $ilDB, $ilSetting;
552 
553  $ilDB->setLimit(1);
554  $sql = "SELECT maxval FROM usr_session_log".
555  " WHERE tstamp <= ".$ilDB->quote($a_timestamp, "integer").
556  " ORDER BY tstamp DESC";
557  $res = $ilDB->query($sql);
558  $val = $ilDB->fetchAssoc($res);
559  if($val["maxval"])
560  {
561  return (int)$val["maxval"];
562  }
563  else
564  {
565  return (int)$ilSetting->get("session_max_count", ilSessionControl::DEFAULT_MAX_COUNT);
566  }
567  }
568 
574  public static function updateLimitLog($a_new_value)
575  {
576  global $ilDB, $ilSetting, $ilUser;
577 
578  $new_value = (int)$a_new_value;
579  $old_value = (int)$ilSetting->get("session_max_count", ilSessionControl::DEFAULT_MAX_COUNT);
580 
581  if($new_value != $old_value)
582  {
583  $fields = array(
584  "tstamp" => array("timestamp", time()),
585  "maxval" => array("integer", $new_value),
586  "user_id" => array("integer", $ilUser->getId())
587  );
588  $ilDB->insert("usr_session_log", $fields);
589  }
590  }
591 }
592 
593 ?>