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)) $res[] = $file;
104 }
105 }
106 return($res);
107 }
110 /*! \brief Check whether the qlite extension is available or not.
111 * @return boolean TRUE on success else FALSE
112 */
113 static function checkSQLiteExtension()
114 {
115 return(function_exists('sqlite_popen'));
116 }
119 /*! \brief Drops the current stats table and thus enforces a recreation.
120 * @param handle The database handle to use.
121 */
122 static function dropTable($handle)
123 {
124 $TABLE_NAME = stats::$tableName;
125 $query = "DROP TABLE '{$TABLE_NAME}'";
126 $ret = sqlite_query($query, $handle);
127 stats::$lastHandle = NULL;
128 stats::getDatabaseHandle();
129 }
132 /*! \brief Returns the currently used amount of memory form the PHP process.
133 * @return int The amount of bytes used for the PHP process.
134 */
135 static function get_memory_usage()
136 {
137 return(memory_get_usage());
138 }
141 /*! \brief Returns the current CPU load.
142 * The result will be cached and one updated every 5 seconds.
143 * @return float The current 'cpu_load'.
144 */
145 static function get_cpu_load()
146 {
147 $cur = time();
148 if(empty(stats::$lastCpuLoad) || (($cur - stats::$lastCpuLoadTimestamp) >= 5 )){
149 list($one, $five, $ten) =preg_split("/ /",shell_exec('cat /proc/loadavg'));
150 stats::$lastCpuLoad = $one;
151 stats::$lastCpuLoadTimestamp = $cur;
152 }
153 return(stats::$lastCpuLoad);
154 }
157 /*! \brief This method checks if the 'stats' table is already present,
158 * if it is not then it will be created.
159 * @param handle The sqlite database handle
160 */
161 static function createDatabaseOnDemand($handle)
162 {
163 $TABLE_NAME = stats::$tableName;
165 // List Tables an check if there is already everything we need,
166 // if not create it.
167 $query = "SELECT name FROM sqlite_master WHERE type='table' and name='{$TABLE_NAME}'";
168 $ret = sqlite_query($query, $handle);
169 if(!count(sqlite_fetch_all($ret))){
170 $query = "
171 CREATE TABLE {$TABLE_NAME} (
172 ID INTEGER PRIMARY KEY,
173 ACTID INTEGER,
174 TYPE TEXT,
175 PLUGIN TEXT,
176 CATEGORY TEXT,
177 ACTION TEXT,
178 UUID TEXT,
179 TIMESTAMP INTEGER,
180 MTIMESTAMP REAL,
181 DURATION REAL,
182 RENDER_TIME REAL,
183 AMOUNT INTEGER,
184 MEMORY_USAGE INTEGER,
185 CPU_LOAD FLOAT,
186 INFO BLOB
187 )";
188 $ret = sqlite_query($query, $handle);
189 }
190 }
193 /*! \brief Creates a new 'stats' table entry.
194 * -> Logs a GOsa action/activity in the sqlite stats table.
195 * @param string type The action type, e.g. ldap/plugin/management
196 * @param string plugin The plugin name, e.g. userManagement/user/posixAccount
197 * @param string category The plugin category e.g. users/servers/groups
198 * @param string action The action done e.g. edit/view/open/move
199 * @param int amount The amount, e.g. for multiple edit
200 * @param float duration The elapsed time.
201 * @param string info Some infos form the action, e.g. the used hashing mehtod for pwd changes.
202 */
203 static function log($type, $plugin, $category, $action, $amount = 1, $duration = 0, $info ='')
204 {
205 global $config;
206 global $clicks;
207 global $overallRenderTimer;
209 // Get database handle, if it is invalid (NULL) return without creating stats
210 $res = stats::getDatabaseHandle();
211 if(!$res) return;
213 // Ensure that 'clicks' and 'overallRenderTimer' are present and set correctly,
214 // if not simply create them with dummy values...
215 // -- 'clicks' is a counter wich is set in main.php -> Number of page reloads
216 // -- 'overallRenderTimer' is set in main.php -> timestamp of rendering start.
217 if(!isset($clicks) || empty($clicks)) $clicks = 0;
218 if(!isset($overallRenderTimer)){
219 $renderTime = 0;
220 }else{
221 $renderTime = microtime(TRUE) - $overallRenderTimer;
223 // Now set the overallRenderTimer to the current timestamp - else
224 // we will not be able to sum up the render time in a single SQL statement.
225 $overallRenderTimer = microtime(TRUE);
226 }
228 // Prepare values to be useable within a database
229 $uuid = $config->getGOsaUUID();
230 $type = sqlite_escape_string($type);
231 $plugin = sqlite_escape_string($plugin);
232 $action = sqlite_escape_string($action);
233 $timestamp = time();
234 $mtimestamp = number_format(microtime(TRUE), 4,'.','');
235 $amount = sqlite_escape_string($amount);
236 $duration = sqlite_escape_string(number_format($duration, 4,'.',''));
237 $renderTime = sqlite_escape_string(number_format($renderTime, 4,'.',''));
238 $info = sqlite_escape_string($info);
239 $clicks = sqlite_escape_string($clicks);
240 $memory_usage = sqlite_escape_string(stats::get_memory_usage());
241 $cpu_load = sqlite_escape_string(number_format(stats::get_cpu_load(),4,'.',''));
243 // Clean up category, which usally comes from acl_category and may still contain
244 // some special chars like /
245 $tmp = array();
246 foreach($category as $cat){
247 $tmp[] = trim($cat, '\/,; ');
248 }
249 $category = sqlite_escape_string(implode($tmp, ', '));
251 // Create insert statement.
252 $TABLE_NAME = stats::$tableName;
253 $query = "
254 INSERT INTO {$TABLE_NAME}
255 (ACTID, TYPE, PLUGIN, CATEGORY, ACTION, UUID, MTIMESTAMP, TIMESTAMP,
256 AMOUNT, DURATION, RENDER_TIME, MEMORY_USAGE, CPU_LOAD, INFO)
257 VALUES
258 ('{$clicks}','{$type}','{$plugin}','{$category}','{$action}','{$uuid}',
259 '{$mtimestamp}','{$timestamp}','{$amount}','{$duration}','{$renderTime}',
260 '{$memory_usage}','{$cpu_load}','{$info}')";
261 sqlite_query($query, $res);
262 }
265 /*! \brief Closes all sqlite handles opened by this class
266 */
267 static function closeHandles()
268 {
269 foreach(stats::lastHandle as $handle){
270 if($handle && is_resource($handle)){
271 sqlite_close($handle);
272 }
273 }
274 }
277 /*! \brief This method returns all entries of the GOsa-stats table.
278 * You can limit the result by setting the from/to parameter (timestamp).
279 * @param int from The timestamp to start the result from.
280 * @param int to The timestamp to end the request.
281 * @return array An array containing the requested entries.
282 */
283 static function dumpTables($filename)
284 {
285 // Get database connection
286 $TABLE_NAME = stats::$tableName;
287 $handle = stats::getDatabaseHandle($filename);
288 if(!$handle) return;
290 $query =
291 " SELECT ".
292 " TYPE, PLUGIN, CATEGORY, ACTION, ".
293 " UUID, DATE(TIMESTAMP, 'unixepoch') as date, ".
294 " AVG(DURATION), AVG(RENDER_TIME), SUM(AMOUNT), ".
295 " AVG(MEMORY_USAGE), AVG(CPU_LOAD), INFO ".
296 " FROM ".
297 " stats ".
298 " GROUP BY ".
299 " TYPE, PLUGIN, CATEGORY, ACTION, UUID, date, INFO ".
300 " ORDER BY ".
301 " ID ";
303 // Create Filter and start query
304 $ret = sqlite_array_query($query, $handle, SQLITE_ASSOC);
305 return($ret);
306 }
309 static function removeStatsFile($filename)
310 {
311 // Get statsFile property
312 global $config;
313 $path = $config->get_cfg_value('core', 'statsDatabaseDirectory');
314 stats::$statsEnabled = $config->boolValueIsTrue('core', 'statsDatabaseEnabled');
315 if(!stats::$statsEnabled){
316 return(NULL);
317 }
319 // We cannot log while the path to store logs in is empty.
320 if(empty($path)){
321 return(NULL);
322 }
324 // Check if file exists and then remove it
325 if(isset(stats::$lastHandle[$filename]) && is_resource(stats::$lastHandle[$filename])){
326 sqlite_close(stats::$lastHandle[$filename]);
327 }
328 if(file_exists($path.'/'.$filename)){
329 unlink($path.'/'.$filename);
330 }
331 }
332 }
334 ?>