index 4076e202f03fd8c745031955ffae743e35697722..b311baf0f454130e1e34c4240d10e040749ba01c 100644 (file)
class stats
{
class stats
{
-
+ static protected $lastCpuLoad = "";
+ static protected $lastCpuLoadTimestamp = 0;
+ static protected $tableName = "stats";
- static function checkDatabase()
+ static protected $lastHandle = NULL;
+ static protected $statsEnabled = FALSE;
+
+
+ /*! \brief This method tries to connect the GOsa-stats database and
+ * then returns a database handle on success else NULL.
+ *
+ * (The GOsa-stats database has to be enabled : statsDatabaseEnabled/statsDatabaseDirectory)
+ *
+ * This database will then contain information about the use of GOsa,
+ * no customer data will be stored.
+ *
+ * @return handle Returns a sqlite database handle.
+ */
+ static function getDatabaseHandle($filename = '')
{
{
- $TABLE_NAME = 'stats';
-
- // Check for modules
- // php5-sqlite
+ // We cannot log while the path to store logs in is empty.
+ global $config;
+
+ // Check if Logging is enabled
+ if(!is_object($config) || ! $config instanceOf config){
+ return(NULL);
+ }
+
+ $path = $config->get_cfg_value('core', 'statsDatabaseDirectory');
+ if(empty($path)){
+ return(NULL);
+ }
+
+ // Check if path exists, if not try to create it
+ if(!is_dir($path) ){
+ $res = @mkdir($path);
+ if(!$res){
+ return(NULL);
+ }
+ }
+ // Append a date suffix to the database file, to prevent huge and unusable database files.
+ if($filename == ''){
+ $filename = date('Y-m-d');
+ }
+ $tableFile = $path.'/'.$filename;
+
+ // Try to return last valid handle.
+ if(file_exists($tableFile) &&
+ isset(stats::$lastHandle[$filename]) &&
+ stats::$lastHandle[$filename] != NULL &&
+ is_resource(stats::$lastHandle[$filename])){
+ return(stats::$lastHandle[$filename]);
+ }
+
+ // Get statsFile property
+ stats::$statsEnabled = $config->boolValueIsTrue('core', 'statsDatabaseEnabled');
+ if(!stats::$statsEnabled){
+ return(NULL);
+ }
+
+ // Check for SQLite extension
+ if(!stats::checkSQLiteExtension()){
+ return(NULL);
+ }
+
+ // Check if we are able to read/write the given database file.
+ if(!is_writeable($path) && !is_writeable(dirname($tableFile))){
+ return(NULL);
+ }
// Try to create database, if it exists just open it.
// Try to create database, if it exists just open it.
- $res = sqlite_open('/var/spool/gosa/stats', 0666, $error);
- if(!$res){
- return($res);
+ $handle = sqlite_open($tableFile, 0666, $error);
+ if($handle){
+ stats::createDatabaseOnDemand($handle);
}
}
+ stats::$lastHandle[$filename] = $handle;
+ return($handle);
+ }
+
+
+ /*! \brief Returns a list of all created stat files
+ * @return Array A list of all currently stored stat files.
+ */
+ static function getLocalStatFiles()
+ {
+ $res = array();
+
+ // Check if we're creating logs right now.
+ if(stats::getDatabaseHandle()){
+ global $config;
- // Delete Table
+ // Walk through all files found in the storage path
+ $path = $config->get_cfg_value('core', 'statsDatabaseDirectory');
+ $dir = opendir($path);
+ while($file = readdir($dir)){
+ if(is_file($path.'/'.$file)) $res[] = $file;
+ }
+ }
+ return($res);
+ }
+
+
+ /*! \brief Check whether the qlite extension is available or not.
+ * @return boolean TRUE on success else FALSE
+ */
+ static function checkSQLiteExtension()
+ {
+ return(function_exists('sqlite_popen'));
+ }
+
+
+ /*! \brief Drops the current stats table and thus enforces a recreation.
+ * @param handle The database handle to use.
+ */
+ static function dropTable($handle)
+ {
+ $TABLE_NAME = stats::$tableName;
$query = "DROP TABLE '{$TABLE_NAME}'";
$query = "DROP TABLE '{$TABLE_NAME}'";
-# $ret = sqlite_query($query, $res);
+ $ret = sqlite_query($query, $handle);
+ stats::$lastHandle = NULL;
+ stats::getDatabaseHandle();
+ }
+
+
+ /*! \brief Returns the currently used amount of memory form the PHP process.
+ * @return int The amount of bytes used for the PHP process.
+ */
+ static function get_memory_usage()
+ {
+ return(memory_get_usage());
+ }
- // List Tables an check if there is already everything we need.
+
+ /*! \brief Returns the current CPU load.
+ * The result will be cached and one updated every 5 seconds.
+ * @return float The current 'cpu_load'.
+ */
+ static function get_cpu_load()
+ {
+ $cur = time();
+ if(empty(stats::$lastCpuLoad) || (($cur - stats::$lastCpuLoadTimestamp) >= 5 )){
+ list($one, $five, $ten) =preg_split("/ /",shell_exec('cat /proc/loadavg'));
+ stats::$lastCpuLoad = $one;
+ stats::$lastCpuLoadTimestamp = $cur;
+ }
+ return(stats::$lastCpuLoad);
+ }
+
+
+ /*! \brief This method checks if the 'stats' table is already present,
+ * if it is not then it will be created.
+ * @param handle The sqlite database handle
+ */
+ static function createDatabaseOnDemand($handle)
+ {
+ $TABLE_NAME = stats::$tableName;
+
+ // List Tables an check if there is already everything we need,
+ // if not create it.
$query = "SELECT name FROM sqlite_master WHERE type='table' and name='{$TABLE_NAME}'";
$query = "SELECT name FROM sqlite_master WHERE type='table' and name='{$TABLE_NAME}'";
- $ret = sqlite_query($query, $res);
+ $ret = sqlite_query($query, $handle);
if(!count(sqlite_fetch_all($ret))){
if(!count(sqlite_fetch_all($ret))){
-
- // Check for table existance
$query = "
CREATE TABLE {$TABLE_NAME} (
$query = "
CREATE TABLE {$TABLE_NAME} (
- ID INTEGER PRIMARY KEY,
- ACTID INTEGER,
- TYPE TEXT,
- PLUGIN TEXT,
- ACTION TEXT,
- UUID TEXT,
- TIMESTAMP INTEGER,
- MTIMESTAMP INTEGER,
- AMOUNT INTEGER,
- DURATION INTEGER
+ ID INTEGER PRIMARY KEY,
+ ACTID INTEGER,
+ TYPE TEXT,
+ PLUGIN TEXT,
+ CATEGORY TEXT,
+ ACTION TEXT,
+ UUID TEXT,
+ TIMESTAMP INTEGER,
+ MTIMESTAMP REAL,
+ DURATION REAL,
+ RENDER_TIME REAL,
+ AMOUNT INTEGER,
+ MEMORY_USAGE INTEGER,
+ CPU_LOAD FLOAT,
+ INFO BLOB
)";
)";
- $ret = sqlite_query($query, $res);
+ $ret = sqlite_query($query, $handle);
}
}
-
- return($res);
}
}
-
- static function log($type, $plugin, $action, $amount = 1, $duration = 0)
+ /*! \brief Creates a new 'stats' table entry.
+ * -> Logs a GOsa action/activity in the sqlite stats table.
+ * @param string type The action type, e.g. ldap/plugin/management
+ * @param string plugin The plugin name, e.g. userManagement/user/posixAccount
+ * @param string category The plugin category e.g. users/servers/groups
+ * @param string action The action done e.g. edit/view/open/move
+ * @param int amount The amount, e.g. for multiple edit
+ * @param float duration The elapsed time.
+ * @param string info Some infos form the action, e.g. the used hashing mehtod for pwd changes.
+ */
+ static function log($type, $plugin, $category, $action, $amount = 1, $duration = 0, $info ='')
{
global $config;
global $clicks;
{
global $config;
global $clicks;
- $type = sqlite_escape_string($type);
- $plugin = sqlite_escape_string($plugin);
- $action = sqlite_escape_string($action);
- $timestamp = time();
- $mtimestamp = microtime(TRUE);
- $uuid = $config->getGOsaUUID();
- $amount = sqlite_escape_string($amount);
- $duration = sqlite_escape_string($duration);
- $clicks = sqlite_escape_string($clicks);
-
- $res = stats::checkDatabase();
- $TABLE_NAME = 'stats';
+ global $overallRenderTimer;
+
+ // Get database handle, if it is invalid (NULL) return without creating stats
+ $res = stats::getDatabaseHandle();
+ if(!$res) return;
+
+ // Ensure that 'clicks' and 'overallRenderTimer' are present and set correctly,
+ // if not simply create them with dummy values...
+ // -- 'clicks' is a counter wich is set in main.php -> Number of page reloads
+ // -- 'overallRenderTimer' is set in main.php -> timestamp of rendering start.
+ if(!isset($clicks) || empty($clicks)) $clicks = 0;
+ if(!isset($overallRenderTimer)){
+ $renderTime = 0;
+ }else{
+ $renderTime = microtime(TRUE) - $overallRenderTimer;
+
+ // Now set the overallRenderTimer to the current timestamp - else
+ // we will not be able to sum up the render time in a single SQL statement.
+ $overallRenderTimer = microtime(TRUE);
+ }
+
+ // Prepare values to be useable within a database
+ $uuid = $config->getGOsaUUID();
+ $type = sqlite_escape_string($type);
+ $plugin = sqlite_escape_string($plugin);
+ $action = sqlite_escape_string($action);
+ $timestamp = time();
+ $mtimestamp = number_format(microtime(TRUE), 4,'.','');
+ $amount = sqlite_escape_string($amount);
+ $duration = sqlite_escape_string(number_format($duration, 4,'.',''));
+ $renderTime = sqlite_escape_string(number_format($renderTime, 4,'.',''));
+ $info = sqlite_escape_string($info);
+ $clicks = sqlite_escape_string($clicks);
+ $memory_usage = sqlite_escape_string(stats::get_memory_usage());
+ $cpu_load = sqlite_escape_string(number_format(stats::get_cpu_load(),4,'.',''));
+
+ // Clean up category, which usally comes from acl_category and may still contain
+ // some special chars like /
+ $tmp = array();
+ foreach($category as $cat){
+ $tmp[] = trim($cat, '\/,; ');
+ }
+ $category = sqlite_escape_string(implode($tmp, ', '));
+
+ // Create insert statement.
+ $TABLE_NAME = stats::$tableName;
$query = "
INSERT INTO {$TABLE_NAME}
$query = "
INSERT INTO {$TABLE_NAME}
- (ACTID, TYPE, PLUGIN, ACTION, UUID, MTIMESTAMP, TIMESTAMP, AMOUNT, DURATION)
+ (ACTID, TYPE, PLUGIN, CATEGORY, ACTION, UUID, MTIMESTAMP, TIMESTAMP,
+ AMOUNT, DURATION, RENDER_TIME, MEMORY_USAGE, CPU_LOAD, INFO)
VALUES
VALUES
- ('{$clicks}','{$type}','{$plugin}','{$action}','{$uuid}','{$mtimestamp}','{$timestamp}','{$amount}','{$duration}')";
+ ('{$clicks}','{$type}','{$plugin}','{$category}','{$action}','{$uuid}',
+ '{$mtimestamp}','{$timestamp}','{$amount}','{$duration}','{$renderTime}',
+ '{$memory_usage}','{$cpu_load}','{$info}')";
sqlite_query($query, $res);
}
sqlite_query($query, $res);
}
- static function show()
+
+ /*! \brief Closes all sqlite handles opened by this class
+ */
+ static function closeHandles()
{
{
- $res = stats::checkDatabase();
- $TABLE_NAME = 'stats';
- $query = "SELECT * FROM {$TABLE_NAME} ORDER BY MTIMESTAMP";
- $query = "SELECT PLUGIN, ACTION, MAX(DURATION) as 'DURATION' FROM {$TABLE_NAME} WHERE ACTION='modify' GROUP BY PLUGIN,ACTION ";
- $query = "SELECT * FROM {$TABLE_NAME} ORDER BY MTIMESTAMP";
- $res = sqlite_query($query, $res);
- echo "<pre>";
- foreach(sqlite_fetch_all($res) as $entry){
- foreach($entry as $key => $str){
- if(is_numeric($key)) continue;
-
- if($key == "DURATION"){
- $str = sprintf("%0.4f", $str);
- echo str_pad($str,20,' ', STR_PAD_LEFT)."|";
- }else{
- echo str_pad($str,20,' ')."|";
- }
+ foreach(stats::lastHandle as $handle){
+ if($handle && is_resource($handle)){
+ sqlite_close($handle);
}
}
- echo "\n";
}
}
- echo "</pre>";
- echo sqlite_error_string($res);
}
}
-}
+ /*! \brief This method returns all entries of the GOsa-stats table.
+ * You can limit the result by setting the from/to parameter (timestamp).
+ * @param int from The timestamp to start the result from.
+ * @param int to The timestamp to end the request.
+ * @return array An array containing the requested entries.
+ */
+ static function dumpTables($filename)
+ {
+ // Get database connection
+ $TABLE_NAME = stats::$tableName;
+ $handle = stats::getDatabaseHandle($filename);
+ if(!$handle) return;
+
+ $query =
+ " SELECT ".
+ " TYPE, PLUGIN, CATEGORY, ACTION, ".
+ " UUID, DATE(TIMESTAMP, 'unixepoch') as date, ".
+ " AVG(DURATION), AVG(RENDER_TIME), SUM(AMOUNT), ".
+ " AVG(MEMORY_USAGE), AVG(CPU_LOAD), INFO ".
+ " FROM ".
+ " stats ".
+ " GROUP BY ".
+ " TYPE, PLUGIN, CATEGORY, ACTION, UUID, date, INFO ".
+ " ORDER BY ".
+ " ID ";
+
+ // Create Filter and start query
+ $ret = sqlite_array_query($query, $handle, SQLITE_ASSOC);
+ return($ret);
+ }
+
+
+ static function removeStatsFile($filename)
+ {
+ // Get statsFile property
+ global $config;
+ $path = $config->get_cfg_value('core', 'statsDatabaseDirectory');
+ stats::$statsEnabled = $config->boolValueIsTrue('core', 'statsDatabaseEnabled');
+ if(!stats::$statsEnabled){
+ return(NULL);
+ }
+
+ // We cannot log while the path to store logs in is empty.
+ if(empty($path)){
+ return(NULL);
+ }
+
+ // Check if file exists and then remove it
+ if(isset(stats::$lastHandle[$filename]) && is_resource(stats::$lastHandle[$filename])){
+ sqlite_close(stats::$lastHandle[$filename]);
+ }
+ if(file_exists($path.'/'.$filename)){
+ unlink($path.'/'.$filename);
+ }
+ }
+}
+
?>
?>