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;
29 $path = $config->get_cfg_value('core', 'statsDatabaseDirectory');
30 if(empty($path)){
31 return(NULL);
32 }
34 // Check if path exists, if not try to create it
35 if(!is_dir($path) ){
36 $res = @mkdir($path);
37 if(!$res){
38 return(NULL);
39 }
40 }
42 // Append a date suffix to the database file, to prevent huge and unusable database files.
43 if($filename == ''){
44 $filename = date('Y-m-d');
45 }
46 $tableFile = $path.'/'.$filename;
48 // Try to return last valid handle.
49 if(file_exists($tableFile) &&
50 isset(stats::$lastHandle[$filename]) &&
51 stats::$lastHandle[$filename] != NULL &&
52 is_resource(stats::$lastHandle[$filename])){
53 return(stats::$lastHandle[$filename]);
54 }
56 // Check if Logging is enabled
57 if(!is_object($config) || ! $config instanceOf config){
58 return(NULL);
59 }
61 // Get statsFile property
62 stats::$statsEnabled = $config->boolValueIsTrue('core', 'statsDatabaseEnabled');
63 if(!stats::$statsEnabled){
64 return(NULL);
65 }
67 // Check for SQLite extension
68 if(!stats::checkSQLiteExtension()){
69 return(NULL);
70 }
72 // Check if we are able to read/write the given database file.
73 if(!is_writeable($path) && !is_writeable(dirname($tableFile))){
74 return(NULL);
75 }
77 // Try to create database, if it exists just open it.
78 $handle = sqlite_open($tableFile, 0666, $error);
79 if($handle){
80 stats::createDatabaseOnDemand($handle);
81 }
82 stats::$lastHandle[$filename] = $handle;
83 return($handle);
84 }
87 /*! \brief Returns a list of all created stat files
88 * @return Array A list of all currently stored stat files.
89 */
90 static function getLocalStatFiles()
91 {
92 $res = array();
94 // Check if we're creating logs right now.
95 if(stats::getDatabaseHandle()){
96 global $config;
98 // Walk through all files found in the storage path
99 $path = $config->get_cfg_value('core', 'statsDatabaseDirectory');
100 $dir = opendir($path);
101 while($file = readdir($dir)){
102 if(is_file($path.'/'.$file)) $res[] = $file;
103 }
104 }
105 return($res);
106 }
109 /*! \brief Check whether the qlite extension is available or not.
110 * @return boolean TRUE on success else FALSE
111 */
112 static function checkSQLiteExtension()
113 {
114 return(function_exists('sqlite_popen'));
115 }
118 /*! \brief Drops the current stats table and thus enforces a recreation.
119 * @param handle The database handle to use.
120 */
121 static function dropTable($handle)
122 {
123 $TABLE_NAME = stats::$tableName;
124 $query = "DROP TABLE '{$TABLE_NAME}'";
125 $ret = sqlite_query($query, $handle);
126 stats::$lastHandle = NULL;
127 stats::getDatabaseHandle();
128 }
131 /*! \brief Returns the currently used amount of memory form the PHP process.
132 * @return int The amount of bytes used for the PHP process.
133 */
134 static function get_memory_usage()
135 {
136 return(memory_get_usage());
137 }
140 /*! \brief Returns the current CPU load.
141 * The result will be cached and one updated every 5 seconds.
142 * @return float The current 'cpu_load'.
143 */
144 static function get_cpu_load()
145 {
146 $cur = time();
147 if(empty(stats::$lastCpuLoad) || (($cur - stats::$lastCpuLoadTimestamp) >= 5 )){
148 list($one, $five, $ten) =preg_split("/ /",shell_exec('cat /proc/loadavg'));
149 stats::$lastCpuLoad = $one;
150 stats::$lastCpuLoadTimestamp = $cur;
151 }
152 return(stats::$lastCpuLoad);
153 }
156 /*! \brief This method checks if the 'stats' table is already present,
157 * if it is not then it will be created.
158 * @param handle The sqlite database handle
159 */
160 static function createDatabaseOnDemand($handle)
161 {
162 $TABLE_NAME = stats::$tableName;
164 // List Tables an check if there is already everything we need,
165 // if not create it.
166 $query = "SELECT name FROM sqlite_master WHERE type='table' and name='{$TABLE_NAME}'";
167 $ret = sqlite_query($query, $handle);
168 if(!count(sqlite_fetch_all($ret))){
169 $query = "
170 CREATE TABLE {$TABLE_NAME} (
171 ID INTEGER PRIMARY KEY,
172 ACTID INTEGER,
173 TYPE TEXT,
174 PLUGIN TEXT,
175 CATEGORY TEXT,
176 ACTION TEXT,
177 UUID TEXT,
178 TIMESTAMP INTEGER,
179 MTIMESTAMP REAL,
180 DURATION REAL,
181 RENDER_TIME REAL,
182 AMOUNT INTEGER,
183 MEMORY_USAGE INTEGER,
184 CPU_LOAD FLOAT,
185 INFO BLOB
186 )";
187 $ret = sqlite_query($query, $handle);
188 }
189 }
192 /*! \brief Creates a new 'stats' table entry.
193 * -> Logs a GOsa action/activity in the sqlite stats table.
194 * @param string type The action type, e.g. ldap/plugin/management
195 * @param string plugin The plugin name, e.g. userManagement/user/posixAccount
196 * @param string category The plugin category e.g. users/servers/groups
197 * @param string action The action done e.g. edit/view/open/move
198 * @param int amount The amount, e.g. for multiple edit
199 * @param float duration The elapsed time.
200 * @param string info Some infos form the action, e.g. the used hashing mehtod for pwd changes.
201 */
202 static function log($type, $plugin, $category, $action, $amount = 1, $duration = 0, $info ='')
203 {
204 global $config;
205 global $clicks;
206 global $overallRenderTimer;
208 // Get database handle, if it is invalid (NULL) return without creating stats
209 $res = stats::getDatabaseHandle();
210 if(!$res) return;
212 // Ensure that 'clicks' and 'overallRenderTimer' are present and set correctly,
213 // if not simply create them with dummy values...
214 // -- 'clicks' is a counter wich is set in main.php -> Number of page reloads
215 // -- 'overallRenderTimer' is set in main.php -> timestamp of rendering start.
216 if(!isset($clicks) || empty($clicks)) $clicks = 0;
217 if(!isset($overallRenderTimer)){
218 $renderTime = 0;
219 }else{
220 $renderTime = microtime(TRUE) - $overallRenderTimer;
222 // Now set the overallRenderTimer to the current timestamp - else
223 // we will not be able to sum up the render time in a single SQL statement.
224 $overallRenderTimer = microtime(TRUE);
225 }
227 // Prepare values to be useable within a database
228 $uuid = $config->getGOsaUUID();
229 $type = sqlite_escape_string($type);
230 $plugin = sqlite_escape_string($plugin);
231 $action = sqlite_escape_string($action);
232 $timestamp = time();
233 $mtimestamp = number_format(microtime(TRUE), 4,'.','');
234 $amount = sqlite_escape_string($amount);
235 $duration = sqlite_escape_string(number_format($duration, 4,'.',''));
236 $renderTime = sqlite_escape_string(number_format($renderTime, 4,'.',''));
237 $info = sqlite_escape_string($info);
238 $clicks = sqlite_escape_string($clicks);
239 $memory_usage = sqlite_escape_string(stats::get_memory_usage());
240 $cpu_load = sqlite_escape_string(number_format(stats::get_cpu_load(),4,'.',''));
242 // Clean up category, which usally comes from acl_category and may still contain
243 // some special chars like /
244 $tmp = array();
245 foreach($category as $cat){
246 $tmp[] = trim($cat, '\/,; ');
247 }
248 $category = sqlite_escape_string(implode($tmp, ', '));
250 // Create insert statement.
251 $TABLE_NAME = stats::$tableName;
252 $query = "
253 INSERT INTO {$TABLE_NAME}
254 (ACTID, TYPE, PLUGIN, CATEGORY, ACTION, UUID, MTIMESTAMP, TIMESTAMP,
255 AMOUNT, DURATION, RENDER_TIME, MEMORY_USAGE, CPU_LOAD, INFO)
256 VALUES
257 ('{$clicks}','{$type}','{$plugin}','{$category}','{$action}','{$uuid}',
258 '{$mtimestamp}','{$timestamp}','{$amount}','{$duration}','{$renderTime}',
259 '{$memory_usage}','{$cpu_load}','{$info}')";
260 sqlite_query($query, $res);
261 }
264 /*! \brief Closes all sqlite handles opened by this class
265 */
266 static function closeHandles()
267 {
268 foreach(stats::lastHandle as $handle){
269 if($handle && is_resource($handle)){
270 sqlite_close($handle);
271 }
272 }
273 }
276 /*! \brief This method returns all entries of the GOsa-stats table.
277 * You can limit the result by setting the from/to parameter (timestamp).
278 * @param int from The timestamp to start the result from.
279 * @param int to The timestamp to end the request.
280 * @return array An array containing the requested entries.
281 */
282 static function dumpTables($filename)
283 {
284 // Get database connection
285 $TABLE_NAME = stats::$tableName;
286 $handle = stats::getDatabaseHandle($filename);
287 if(!$handle) return;
289 // Create Filter and start query
290 $filter = "SELECT * FROM {$TABLE_NAME} ORDER BY ID";
291 $ret = sqlite_array_query($filter, $handle, SQLITE_ASSOC);
292 return($ret);
293 }
296 static function removeStatsFile($filename)
297 {
298 // Get statsFile property
299 global $config;
300 $path = $config->get_cfg_value('core', 'statsDatabaseDirectory');
301 stats::$statsEnabled = $config->boolValueIsTrue('core', 'statsDatabaseEnabled');
302 if(!stats::$statsEnabled){
303 return(NULL);
304 }
306 // We cannot log while the path to store logs in is empty.
307 if(empty($path)){
308 return(NULL);
309 }
311 // Check if file exists and then remove it
312 if(isset(stats::$lastHandle[$filename]) && is_resource(stats::$lastHandle[$filename])){
313 sqlite_close(stats::$lastHandle[$filename]);
314 }
315 if(file_exists($path.'/'.$filename)){
316 unlink($path.'/'.$filename);
317 }
318 }
319 }
321 ?>