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 }
337 static function getLdapObjectCount($config, $statisticConformResult = FALSE, $date = "")
338 {
339 $ldap = $config->get_ldap_link();
340 $ldap->cd($config->current['BASE']);
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 }
426 }
428 ?>