Code

Fixed proposal pre-selection
[gosa.git] / gosa-core / include / class_stats.inc
1 <?php
3 class stats 
4 {
6     static protected $lastCpuLoad = "";
7     static protected $lastCpuLoadTimestamp = 0;
9     static protected $tableName = "stats";
11     static protected $lastHandle = NULL;
12     static protected $statsEnabled = FALSE;
15     /*! \brief     This method tries to connect the GOsa-stats database and 
16      *              then returns a database handle on success else NULL.
17      *
18      *             (The GOsa-stats database has to be enabled : statsDatabaseEnabled/statsDatabaseDirectory)
19      *
20      *             This database will then contain information about the use of GOsa,
21      *              no customer data will be stored.
22      *
23      *  @return     handle      Returns a sqlite database handle.
24      */
25     static function getDatabaseHandle($filename = '')
26     {
27         // We cannot log while the path to store logs in is empty.
28         global $config;
30         // Check if Logging is enabled
31         if(!is_object($config) || ! $config instanceOf config){
32             return(NULL);
33         }
35         $path = $config->get_cfg_value('core', 'statsDatabaseDirectory');
36         if(empty($path)){
37             return(NULL);
38         }
40         // Check if path exists, if not try to create it
41         if(!is_dir($path) ){
42             $res = @mkdir($path);
43             if(!$res){
44                 return(NULL);
45             }
46         }
48         // Append a date suffix to the database file, to prevent huge and unusable database files.
49         if($filename == ''){
50             $filename = date('Y-m-d');
51         }
52         $tableFile = $path.'/'.$filename;
54         // Try to return last valid handle.
55         if(file_exists($tableFile) && 
56                 isset(stats::$lastHandle[$filename]) && 
57                 stats::$lastHandle[$filename] != NULL &&
58                 is_resource(stats::$lastHandle[$filename])){
59             return(stats::$lastHandle[$filename]);
60         }
62         // Get statsFile property 
63         stats::$statsEnabled = $config->boolValueIsTrue('core', 'statsDatabaseEnabled');
64         if(!stats::$statsEnabled){
65             return(NULL);
66         }
68         // Check for SQLite extension
69         if(!stats::checkSQLiteExtension()){
70             return(NULL);
71         }
73         // Check if we are able to read/write the given database file.
74         if(!is_writeable($path) && !is_writeable(dirname($tableFile))){
75             return(NULL);
76         }
78         // Try to create database, if it exists just open it.
79         $handle = sqlite_open($tableFile, 0666, $error);
80         if($handle){
81             stats::createDatabaseOnDemand($handle);
82         }
83         stats::$lastHandle[$filename] = $handle;
84         return($handle);
85     }
88     /*! \brief      Returns a list of all created stat files
89      *  @return     Array   A list of all currently stored stat files.
90      */
91     static function getLocalStatFiles()
92     {
93         $res = array();
95         // Check if we're creating logs right now.
96         if(stats::getDatabaseHandle()){
97             global $config;
99             // Walk through all files found in the storage path
100             $path = $config->get_cfg_value('core', 'statsDatabaseDirectory');
101             $dir = opendir($path);
102             while($file = readdir($dir)){
103                 if(is_file($path.'/'.$file) && !preg_match('/.old$/', $file)) {
104                     $res[] = $file;
105                 }
106             }
107         } 
108         return($res);   
109     }
112     /*! \brief      Check whether the qlite extension is available or not.
113      *  @return     boolean     TRUE on success else FALSE
114      */  
115     static function checkSQLiteExtension()
116     {
117         return(function_exists('sqlite_popen'));
118     }
121     /*! \brief      Drops the current stats table and thus enforces a recreation.
122      *  @param      handle      The database handle to use.
123      */  
124     static function dropTable($handle)
125     {
126         $TABLE_NAME = stats::$tableName;
127         $query = "DROP TABLE '{$TABLE_NAME}'";
128         $ret = sqlite_query($query, $handle);
129         stats::$lastHandle = NULL;
130         stats::getDatabaseHandle();
131     }
134     /*! \brief      Returns the currently used amount of memory form the PHP process.
135      *  @return     int     The amount of bytes used for the PHP process.
136      */  
137     static function get_memory_usage()
138     {
139         return(memory_get_usage());
140     }
143     /*! \brief      Returns the current CPU load. 
144      *              The result will be cached and one updated every 5 seconds.
145      *  @return     float       The current 'cpu_load'.
146      */  
147     static function get_cpu_load()
148     {
149         $cur = time();
150         if(empty(stats::$lastCpuLoad) || (($cur - stats::$lastCpuLoadTimestamp) >= 5 )){
151             list($one, $five, $ten) =preg_split("/ /",shell_exec('cat /proc/loadavg'));
152             stats::$lastCpuLoad = $one;
153             stats::$lastCpuLoadTimestamp = $cur;
154         }
155         return(stats::$lastCpuLoad);
156     }
159     /*! \brief      This method checks if the 'stats' table is already present,
160      *               if it is not then it will be created.
161      *  @param      handle      The sqlite database handle
162      */  
163     static function createDatabaseOnDemand($handle)
164     {
165         $TABLE_NAME = stats::$tableName;
167         // List Tables an check if there is already everything we need,
168         //  if not create it.
169         $query = "SELECT name FROM sqlite_master WHERE type='table' and name='{$TABLE_NAME}'";
170         $ret = sqlite_query($query, $handle);
171         if(!count(sqlite_fetch_all($ret))){
172             $query = "
173                 CREATE TABLE {$TABLE_NAME} (
174                         ID              INTEGER PRIMARY KEY,
175                         ACTID           INTEGER,
176                         TYPE            TEXT,
177                         PLUGIN          TEXT,
178                         CATEGORY        TEXT,
179                         ACTION          TEXT,
180                         UUID            TEXT,
181                         TIMESTAMP       INTEGER,
182                         MTIMESTAMP      REAL,
183                         DURATION        REAL,
184                         RENDER_TIME     REAL,
185                         AMOUNT          INTEGER,
186                         MEMORY_USAGE    INTEGER,
187                         CPU_LOAD        FLOAT,
188                         INFO            BLOB
189                         )";
190             $ret = sqlite_query($query, $handle);
191         }
192     }
195     /*! \brief      Creates a new 'stats' table entry.
196      *              -> Logs a GOsa action/activity in the sqlite stats table.
197      *  @param      string  type        The action type, e.g. ldap/plugin/management
198      *  @param      string  plugin      The plugin name, e.g. userManagement/user/posixAccount
199      *  @param      string  category    The plugin category e.g. users/servers/groups
200      *  @param      string  action      The action done e.g. edit/view/open/move
201      *  @param      int     amount      The amount, e.g. for multiple edit
202      *  @param      float   duration    The elapsed time.
203      *  @param      string  info        Some infos form the action, e.g. the used hashing mehtod for pwd changes.
204      */  
205     static function log($type, $plugin, $category, $action, $amount = 1, $duration = 0, $info ='')
206     {
207         global $config;
208         global $clicks;
209         global $overallRenderTimer;
211         // Get database handle, if it is invalid (NULL) return without creating stats
212         $res = stats::getDatabaseHandle();
213         if(!$res) return;
215         // Ensure that 'clicks' and 'overallRenderTimer' are present and set correctly, 
216         //  if not simply create them with dummy values... 
217         //   -- 'clicks' is a counter wich is set in main.php -> Number of page reloads 
218         //   -- 'overallRenderTimer' is set in main.php -> timestamp of rendering start.
219         if(!isset($clicks) || empty($clicks)) $clicks = 0;
220         if(!isset($overallRenderTimer)){
221             $renderTime = 0;
222         }else{
223             $renderTime = microtime(TRUE) - $overallRenderTimer;
225             // Now set the overallRenderTimer to the current timestamp - else 
226             //  we will not be able to sum up the render time in a single SQL statement.
227             $overallRenderTimer = microtime(TRUE);
228         }
230         // Prepare values to be useable within a database
231         $uuid = $config->getGOsaUUID();
232         $type           = sqlite_escape_string($type);
233         $plugin         = sqlite_escape_string($plugin);
234         $action         = sqlite_escape_string($action);
235         $timestamp      = time();
236         $mtimestamp     = number_format(microtime(TRUE), 4,'.','');
237         $amount         = sqlite_escape_string($amount);
238         $duration       = sqlite_escape_string(number_format($duration, 4,'.',''));
239         $renderTime     = sqlite_escape_string(number_format($renderTime, 4,'.',''));
240         $info           = sqlite_escape_string($info);
241         $clicks         = sqlite_escape_string($clicks);
242         $memory_usage   = sqlite_escape_string(stats::get_memory_usage());
243         $cpu_load       = sqlite_escape_string(number_format(stats::get_cpu_load(),4,'.',''));
245         // Clean up category, which usally comes from acl_category and may still contain 
246         //  some special chars like /
247         $tmp = array();
248         foreach($category as $cat){
249             $tmp[] = trim($cat, '\/,; ');
250         }
251         $category = sqlite_escape_string(implode($tmp, ', '));
253         // Create insert statement.
254         $TABLE_NAME = stats::$tableName;
255         $query = "
256             INSERT INTO {$TABLE_NAME}
257         (ACTID, TYPE, PLUGIN, CATEGORY, ACTION, UUID, MTIMESTAMP, TIMESTAMP, 
258          AMOUNT, DURATION, RENDER_TIME, MEMORY_USAGE, CPU_LOAD, INFO) 
259             VALUES 
260             ('{$clicks}','{$type}','{$plugin}','{$category}','{$action}','{$uuid}',
261              '{$mtimestamp}','{$timestamp}','{$amount}','{$duration}','{$renderTime}',
262              '{$memory_usage}','{$cpu_load}','{$info}')";
263         sqlite_query($query, $res);
264     }
267     /*! \brief      Closes all sqlite handles opened by this class
268      */ 
269     static function closeHandles()
270     {
271         foreach(stats::lastHandle as $handle){
272             if($handle && is_resource($handle)){
273                 sqlite_close($handle);
274             }
275         }
276     }
279     /*! \brief      This method returns all entries of the GOsa-stats table.
280      *              You can limit the result by setting the from/to parameter (timestamp).
281      *  @param      int     from    The timestamp to start the result from. 
282      *  @param      int     to      The timestamp to end the request.
283      *  @return     array           An array containing the requested entries.
284      */  
285     static function generateStatisticDump($filename)
286     {
287         // Get database connection
288         $TABLE_NAME = stats::$tableName;
289         $handle = stats::getDatabaseHandle($filename);
290         if(!$handle) return;        
292         $query = 
293             "  SELECT ".
294             "      TYPE, PLUGIN, CATEGORY, ACTION, ".
295             "      UUID, DATE(TIMESTAMP, 'unixepoch') as date, ".
296             "      AVG(DURATION), AVG(RENDER_TIME), SUM(AMOUNT), ".
297             "      AVG(MEMORY_USAGE), AVG(CPU_LOAD), INFO ".
298             "  FROM ".
299             "      stats ".
300             "  GROUP BY ".
301             "      TYPE, PLUGIN, CATEGORY, ACTION, UUID, date, INFO ".
302             "  ORDER BY ".
303             "      ID ";
305         // Create Filter and start query
306         $ret = sqlite_array_query($query, $handle, SQLITE_ASSOC);
307         return($ret);
308     }
311     static function removeStatsFile($filename)
312     {
313         // Get statsFile property 
314         global $config;
315         $path = $config->get_cfg_value('core', 'statsDatabaseDirectory');
316         stats::$statsEnabled = $config->boolValueIsTrue('core', 'statsDatabaseEnabled');
317         if(!stats::$statsEnabled){
318             return(NULL);
319         }
321         // We cannot log while the path to store logs in is empty.
322         if(empty($path)){
323             return(NULL);
324         }
326         // Check if file exists and then remove it
327         if(isset(stats::$lastHandle[$filename]) && is_resource(stats::$lastHandle[$filename])){
328             sqlite_close(stats::$lastHandle[$filename]);
329         }
330         if(file_exists($path.'/'.$filename)){
331             #unlink($path.'/'.$filename);
332             rename($path.'/'.$filename,  $path.'/'.$filename.".old");
333         }
334     }
336     
337     static function getLdapObjectCount($config, $statisticConformResult = FALSE, $date = "")    
338     {
339         $ldap = $config->get_ldap_link();
340         $ldap->cd($config->current['BASE']);
341         
342         // A list of objectClasses to search for, indexed by their 
343         //  object-category
344         $ocsToSearchFor = array(
345                 "department" => array("gosaDepartment"),
346                 "devices" => array("gotoDevice"),
347                 "fai" => array("FAIobject"),
348                 "gofaxlist" => array("goFaxRBlock","goFaxSBlock"),
349                 "gofonconference" => array("goFonConference"),
350                 "phone" => array("goFonHardware"),
351                 "gofonmacro" => array("goFonMacro"),
352                 "users" => array("gosaAccount"),
353                 "acls" => array("gosaAcl","gosaRole"),
354                 "application" => array("gosaApplication"),
355                 "ogroups" => array("gosaGroupOfNames"),
356                 "roles" => array("organizationalRole"),
357                 "server" => array("goServer"),
358                 "printer" => array("gotoPrinter"),
359                 "terminal" => array("gotoTerminal"),
360                 "workstation" => array("gotoWorkstation"),
361                 "winworkstation" => array("sambaSamAccount"),
362                 "incoming" => array("goHard"),
363                 "component" => array("ieee802Device"),
364                 "mimetypes" => array("gotoMimeType"),
365                 "groups" => array("posixGroup"),
366                 "sudo" => array("sudoRole"));
368         // Build up a filter which contains all objectClass combined by OR.
369         // We will later sum up the results using PHP.
370         $filter = "";
371         $categoryCounter = array();
372         foreach($ocsToSearchFor as $category => $ocs){
373             foreach($ocs as $oc){
374                 $filter.= "(objectClass={$oc})";
375             }
376             $categoryCounter[$category] = 0;
377         }
378         $filter = "(|{$filter})";
380         // Initiate the ldap query
381         $res = $ldap->search($filter, array('objectClass'));
382         if($ldap->success()) {
384             // Count number of results per category
385             while($entry = $ldap->fetch()){
386                 foreach($ocsToSearchFor as $category => $ocs){
387                     if(count(array_intersect($ocs, $entry['objectClass']))){
388                         $categoryCounter[$category] ++;
389                         break; 
390                     }
391                 }
392             }
393         }
394         arsort($categoryCounter);
396         // Do we have to return the result as SQL INSERT statement?
397         if($statisticConformResult){ 
398             $uuid           = $config->getGOsaUUID();
399             $type           = 'objectCount';
400             $plugin         = '';
401             $action         = '';
402             $date           = (empty($date))?date('Y-m-d'):$date;
403             $memory_usage   = sqlite_escape_string(stats::get_memory_usage());
404             $cpu_load       = sqlite_escape_string(number_format(stats::get_cpu_load(),4,'.',''));
405             $sql = array();
406             foreach($categoryCounter as $category => $amount){
407                 $sql[] = array(
408                         "type" => $type,
409                         "plugin" => $plugin,
410                         "category" => $category,
411                         "action" => $action,
412                         "uuid" => $uuid,
413                         "date" => $date,
414                         "duration" => 0,
415                         "render_time" => 0,
416                         "amount" => $amount,
417                         "mem_usage" => $memory_usage,
418                         "load" => $cpu_load,
419                         "info" => '');
420             }
421             return($sql);
422         }else{
423             return($categoryCounter); 
424         }
425     }
428 ?>