.
*
* You can contact KnowledgeTree Inc., PO Box 7775 #87847, San Francisco,
* California 94120-7775, or email info@knowledgetree.com.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "Powered by
* KnowledgeTree" logo and retain the original copyright notice. If the display of the
* logo is not reasonably feasible for technical reasons, the Appropriate Legal Notices
* must display the words "Powered by KnowledgeTree" and retain the original
* copyright notice.
* Contributor( s): ______________________________________
*
*/
/**
* TODO: refactor into seperate comparison object
*
*/
class MD5SourceTree
{
private $rootDir;
private $logFilename;
private $logFile;
private $numDirectories;
private $numFiles;
private $comparisonFailure;
private $exclusions;
public function __construct($exclusions = array())
{
$this->numDirectories = 0;
$this->numFiles = 0;
$this->exclusions = $exclusions;
}
/**
* Helper function to traverse the directories. Called initially by scan()
*
* @param string $dir
*/
private function _scan($dir)
{
if (in_array($dir, $this->exclusions))
{
return;
}
if (is_dir($dir))
{
if ($dh = opendir($dir))
{
while (($filename = readdir($dh)) !== false)
{
if (substr($filename,0,1) == '.')
{
continue;
}
$path = $dir . '/' . $filename;
if (is_dir($path))
{
$this->numDirectories++;
$this->_scan($path);
}
else
{
$this->numFiles++;
if (is_readable($path))
{
$md5 = md5_file($path);
$path = substr($path, strlen($this->rootDir) + 1);
fwrite($this->logFile, "$md5:$path\n");
}
}
}
closedir($dh);
}
}
}
/**
* This does the scan of the directory.
*
* @param string $rootDir
* @param string $reportFile
*/
public function scan($rootDir, $reportFile)
{
$this->rootDir = $rootDir;
$this->logFilename = $reportFile;
$this->logFile = fopen($reportFile,'wt');
$this->_scan($rootDir);
fclose($this->logFile);
}
/**
* Used by the compare function, to load a md5 file
*
* @param string $path
* @return array
*/
private function _loadDirectory($path)
{
$dirs = array();
$numFiles = 0;
$numDirectories = 0;
$fp = fopen($path, 'rt');
while (!feof($fp))
{
$line = fgets($fp, 10240);
list($md5, $path) = explode(':',$line);
$dirname = dirname($path);
$filename = basename($path);
$numFiles++;
$dirs[$dirname][$filename] = $md5;
}
fclose($fp);
return array('numFiles'=>$numFiles, 'numDirectories'=>$numDirectories, 'dirs'=>$dirs);
}
/**
* Internal function used to compare two md5 directory structures.
*
* @param array $prev
* @param array $cur
* @param string $msg
*/
private function _compare($prev, $cur, $msg)
{
foreach($prev['dirs'] as $prevDir=>$prevDirFiles)
{
if (!array_key_exists($prevDir, $cur['dirs']))
{
print "$msg: $prevDir does not exist in target.\n";
}
else
{
foreach($prevDirFiles as $prevFilename=>$prevMD5)
{
if (!array_key_exists($prevFilename, $cur['dirs'][$prevDir]))
{
$prevFilename = substr($prevFilename,0,-1);
print "$msg: $prevFilename does not exist in $prevDir.\n";
}
else
{
if (in_array($prevDir . '/' . $prevFilename, $this->comparisonFailure))
{
continue;
}
$newMD5 = $cur['dirs'][$prevDir][$prevFilename];
if ($prevMD5 != $newMD5)
{
$this->comparisonFailure[] = $prevDir . '/' . $prevFilename;
$prevFilename = substr($prevFilename,0,-1);
print "$msg: $prevFilename does not match md5; $prevMD5 != $newMD5.\n";
}
}
}
}
}
}
/**
* Compare to md5 report files
*
* @param string $reportA
* @param string $reportB
*/
public function compare($reportA, $reportB)
{
if (is_null($reportB))
{
$reportB = $this->logFilename;
}
$this->comparisonFailure = array();
$prev = $this->_loadDirectory($reportA);
$cur = $this->_loadDirectory($reportB);
if ($prev['numDirectories'] != $cur['numDirectories'])
{
print "Folder count mismatch!\n";
}
if ($prev['numFiles'] != $cur['numFiles'])
{
print "File count mismatch!\n";
}
$this->_compare($prev, $cur,'>');
$this->_compare($cur,$prev,'<');
}
}
class SupportUtil
{
private $path;
private $innodb;
private $noninnodb;
/**
* Constructor for SupportUtil. Creates a folder with format support-YYYY-MM-DD_HH-mm-ss
*
*/
function __construct()
{
$config = KTConfig::getSingleton();
$tempdir = $config->get('urls/tmpDirectory');
$this->path = $tempdir . "/support-" . date('Y-m-d_H-i-s');
mkdir($this->path);
}
/**
* Main function to capture as much info that is reasonable.
*
*/
public function capture()
{
// get php info
$this->capture_phpinfo($this->path . '/phpinfo.htm');
// get db schema
$tables = $this->capture_db_schema($this->path);
// get zseq counters from taables
$this->capture_zseqs($tables, $this->path . '/zseqreport.htm');
// get md5 on table
$exclusions = array(
KT_DIR . '/var',
realpath(KT_DIR . '/../var')
);
$tree = new MD5SourceTree($exclusions);
$config = KTConfig::getSingleton();
$sourcePath = $config->get('KnowledgeTree/fileSystemRoot');
$tree->scan($sourcePath, $this->path . '/md5report.txt');
// get plugins
$this->capture_plugins($this->path . '/plugins.htm');
// get logs
$this->capture_logs($this->path);
// get sys info
$this->get_sysinfo($this->path);
// get storage engine list
$this->create_storage_engine($this->path);
// get disk space listing
$this->capture_df($this->path);
// get process listing
$this->capture_ps($this->path);
// get version files
$this->capture_version_files($this->path);
// get system settings
$this->capture_system_settings($this->path);
// create out index file
$this->create_index($this->path);
}
/**
* Main helper function to cleanup after creating zip file
*
* @param stirng $path
*/
private function _cleanup($path)
{
$dh = opendir($path);
while (($filename = readdir($dh)) !== false)
{
if (substr($filename,0,1) == '.') continue;
$fullname = $path . '/' . $filename;
if (is_dir($fullname))
{
$this->_cleanup($fullname);
}
else
{
unlink($fullname);
}
}
closedir($dh);
rmdir($path);
}
/**
* Main cleanup function
*
*/
public function cleanup()
{
$this->_cleanup($this->path);
}
/**
* Creates an archive file
*
* @return string
*/
public function archive()
{
$zip = KTUtil::findCommand('export/zip', 'zip');
chdir(dirname($this->path));
$subdir = basename($this->path);
$archivename = $this->path . '.zip';
$cmd = "\"$zip\" -r \"$archivename\" \"$subdir\"";
KTUtil::pexec($cmd);
return $archivename;
}
/**
* Tries to get list of running processes
*
* @param string $path
*/
private function capture_ps($path)
{
$ps = KTUtil::findCommand('externalBinary/ps', 'ps');
if (!file_exists($ps) || !is_executable($ps))
{
return;
}
$cmd = "'$ps' waux";
// TODO: refactor to use KTUtil::pexec
$ps = popen($cmd, 'r');
$content = fread($ps , 10240);
pclose($ps);
file_put_contents($path . '/ps.txt', $content);
}
/**
* Get list of KnowledgeTree version files
*
* @param string $path
*/
private function capture_version_files($path)
{
$path = $path . '/versions';
mkdir($path);
$ver_path = KT_DIR . '/docs';
$dh = opendir($ver_path);
while (($filename = readdir($dh)) !== false)
{
if (substr($filename, 0, 7) == 'VERSION')
{
copy($ver_path . '/' . $filename, $path . '/' . $filename);
}
}
closedir($dh);
}
/**
* Dump the system_settings table, except for dashboard-state entries.
*
* @param string $path
*/
private function capture_system_settings($path)
{
$sql = "SELECT id, name, value FROM system_settings";
$rs = DBUtil::getResultArray($sql);
$html = "
System Settings
";
$html .= '
';
foreach($rs as $rec)
{
$id = $rec['id'];
$name = $rec['name'];
$value = $rec['value'];
if (substr($name, 0, 15) == 'dashboard-state') continue;
$html .= "| $id | $name | $value\r\n";
}
$html .= ' |
';
file_put_contents($path . '/systemsettings.htm', $html);
}
/**
* Get disk usage
*
* @param string $path
*/
private function capture_df($path)
{
$df = KTUtil::findCommand('externalBinary/df', 'df');
if (!file_exists($df) || !is_executable($df))
{
return;
}
$df = popen($df, 'r');
$content = fread($df, 10240);
pclose($df);
file_put_contents($path . '/df.txt', $content);
}
/**
* Get php info
*
* @param string $filename
*/
private function capture_phpinfo($filename)
{
ob_start();
phpinfo();
$phpinfo = ob_get_clean();
file_put_contents($filename, $phpinfo);
}
/**
* Helper table to get schema
*
* @param string $folder
* @return string
*/
private function capture_table_schema($folder)
{
$tables = array();
$sql = 'show tables';
$results = DBUtil::getResultArray($sql);
foreach($results as $rec)
{
$rec = array_values($rec);
$tablename = $rec[0];
$sql = "show create table $tablename";
$sql = DBUtil::getOneResultKey($sql,'Create Table');
file_put_contents($folder . '/' . $tablename . '.sql.txt', $sql);
$sql = strtolower($sql);
if (strpos($sql, 'innodb') === false)
$this->noninnodb[] = $tablename;
else
$this->innodb[] = $tablename;
$tables[] = $tablename;
}
return $tables;
}
/**
* Get database schema
*
* @param string $folder
* @param string $suffix
* @return array
*/
private function capture_db_schema($folder, $suffix='')
{
$schema_folder = $folder . '/' . $suffix . 'schema';
mkdir($schema_folder);
return $this->capture_table_schema($schema_folder);
}
/**
* Get list of plugins
*
* @param string $filename
*/
private function capture_plugins($filename)
{
$sql = 'select namespace,path, disabled, unavailable,friendly_name from plugins';
$result = DBUtil::getResultArray($sql);
$plugins = "Plugin Status Report
";
$plugins .= '';
$plugins .= '| Display Name | Availability | Namespace | Path';
foreach($result as $rec)
{
$fileexists = file_exists(KT_DIR . '/' . $rec['path'])?'':'';
$status = ($rec['disabled'] == 0)?'':'';
$unavailable = ($rec['unavailable'] == 0)?'available':'unavailable';
$plugins .= '';
$plugins .= '| ' . $status . $rec['friendly_name'];
$plugins .= ' | ' . $unavailable;
$plugins .= ' | ' . $rec['namespace'];
$plugins .= ' | ' . $fileexists . $rec['path'] . "\r\n";
}
$plugins .= ' | |
|---|
';
$plugins .= '
Plugin name is green if enabled and orange if disabled .';
$plugins .= '
Availability indicates that KnowledgeTree has detected the plugin not to be available.';
$plugins .= '
Path is coloured red if the plugin file cannot be resolved. If the path is not resolved, it should be flagged unavailable.';
file_put_contents($filename, $plugins);
}
/**
* Make a zseq report
*
* @param string $tables
* @param string $filename
*/
private function capture_zseqs($tables, $filename)
{
$zseqs = 'Table Counter Report
';
$zseqs .= '';
$zseqs .= '| Table | Max ID | ZSEQ | Status';
foreach($tables as $ztablename)
{
if (substr($ztablename, 0, 5) != 'zseq_')
{
continue;
}
$tablename = substr($ztablename, 5);
$sql = "SELECT max(id) as maxid FROM $tablename";
$maxid = DBUtil::getOneResultKey($sql, 'maxid');
$sql = "SELECT id FROM $ztablename";
$zseqid = DBUtil::getOneResultKey($sql, 'id');
$note = (is_null($maxid) || $maxid <= $zseqid)?'OK':'FAIL';
if ($note == 'FAIL' && $maxid > $zseqid)
{
$note = 'COUNTER PROBLEM! maxid should be less than or equal to zseq';
}
if (PEAR::isError($maxid))
{
$maxid = '??';
$note = "STRANGE - DB ERROR ON $tablename";
}
if (PEAR::isError($zseqid))
{
$zseqid = '??';
$note = "STRANGE - DB ERROR ON $ztablename";
}
if (is_null($maxid))
{
$maxid='empty';
}
if (is_null($zseqid))
{
$zseqid='empty';
$note = "STRANGE - ZSEQ SHOULD NOT BE EMPTY ON $ztablename";
}
$zseqs .= " |
| $tablename | $maxid | $zseqid | $note\r\n";
}
$zseqs .= " |
";
file_put_contents($filename, $zseqs);
}
/**
* Get log files
*
* @param string $path
*/
private function capture_logs($path)
{
$path = $path . '/logs';
mkdir($path);
$this->capture_kt_log($path);
$this->capture_apache_log($path);
$this->capture_php_log($path);
$this->capture_mysql_log($path);
}
/**
* Get Php log file. KT makes a php_error_log when tweak setting is enabled.
*
* @param string $path
*/
private function capture_php_log($path)
{
$config = KTConfig::getSingleton();
$logdir = $config->get('urls/logDirectory');
$logfile = $logdir . '/php_error_log';
if (file_exists($logfile))
{
copy($logfile, $path . '/php-error_log.txt');
}
}
/**
* Get mysql log from stack. It is difficult to resolve otherwise.
*
* @param string $path
*/
private function capture_mysql_log($path)
{
$stack_path = realpath(KT_DIR . '/../mysql/data');
if ($stack_path === false || !is_dir($stack_path))
{
return;
}
$dh = opendir($stack_path);
while (($filename = readdir($dh)) !== false)
{
if (substr($filename, -4) == '.log' && strpos($filename, 'err') !== false)
{
copy($stack_path . '/' . $filename, $path . '/mysql-' . $filename);
}
}
closedir($dh);
}
/**
* Get Apache log file from stack. It is difficult to resolve otherwise.
*
* @param string $path
*/
private function capture_apache_log($path)
{
$stack_path = realpath(KT_DIR . '/../apache2/logs');
if ($stack_path === false || !is_dir($stack_path))
{
return;
}
$dh = opendir($stack_path);
while (($filename = readdir($dh)) !== false)
{
if (substr($filename, -4) == '.log' && strpos($filename, 'err') !== false)
{
copy($stack_path . '/' . $filename, $path . '/apache-' . $filename);
}
}
closedir($dh);
}
/**
* Get KT log file.
*
* @param string $path
*/
private function capture_kt_log($path)
{
$date = date('Y-m-d');
$config = KTConfig::getSingleton();
$logdir = $config->get('urls/logDirectory');
$dh = opendir($logdir);
while (($filename = readdir($dh)) !== false)
{
if (substr($filename,0,14) != 'log-' . $date)
{
continue;
}
copy($logdir . '/' . $filename, $path . '/kt-' . $filename);
}
closedir($dh);
}
/**
* Get some basic info on Linux if possible. Get cpuinfo, loadavg, meminfo
*
* @param string $path
*/
private function get_sysinfo($path)
{
if (!OS_UNIX && !is_dir('/proc'))
{
return;
}
$path .= '/sysinfo';
mkdir($path);
$this->get_sysinfo_file('cpuinfo', $path);
$this->get_sysinfo_file('loadavg', $path);
$this->get_sysinfo_file('meminfo', $path);
}
/**
* Helper to get linux sysinfo
*
* @param string $filename
* @param string $path
*/
private function get_sysinfo_file($filename, $path)
{
if (!is_readable('/proc/' . $filename))
{
return;
}
$content = file_get_contents('/proc/' . $filename);
file_put_contents($path . '/' . $filename . '.txt', $content);
}
/**
* Helper to create the index file for the support archive.
*
* @param string $title
* @param string $path
* @param boolean $relative
* @return string
*/
private function get_index_contents($title, $path, $relative = true)
{
if (!is_dir($path))
{
return '';
}
$contents = array();
$dh = opendir($path);
while (($filename = readdir($dh)) !== false)
{
if (substr($filename,0,1) == '.') continue;
$fullname = $path . '/' . $filename;
if (!file_exists($fullname) || is_dir($fullname))
{
continue;
}
$contents[] = $fullname;
}
closedir($dh);
sort($contents);
$html = $title;
if (empty($contents))
{
$html .= 'There is no content for this section.';
return $html;
}
$dir = '';
if ($relative) $dir = basename($path) . '/';
foreach($contents as $filename)
{
$corename = basename($filename);
$ext = pathinfo($corename, PATHINFO_EXTENSION);
$basename = substr($corename, 0, -strlen($ext)-1);
$html .= "$basename
";
}
return $html;
}
/**
* Create the support archvie index.htm
*
* @param string $path
*/
private function create_index($path)
{
$contents = $this->get_index_contents('Support Info
', $path, false);
$contents .= $this->get_index_contents('System Info
', $path . '/sysinfo');
$contents .= $this->get_index_contents('Logs
', $path . '/logs');
$contents .= $this->get_index_contents('Schema
', $path . '/schema');
file_put_contents($path . '/index.htm', $contents);
}
/**
* Get list of tables based on InnoDB
*
* @param string $path
*/
private function create_storage_engine($path)
{
$html = 'Table Storage Engines';
$html .= '
';
$html .= '';
$html .= 'InnoDB';
foreach($this->innodb as $tablename)
{
$html .= "$tablename ";
}
$html .= ' | ';
$html .= 'Non-InnoDB';
foreach($this->noninnodb as $tablename)
{
$html .= "$tablename ";
}
$html .= ' |
';
file_put_contents($path . '/tablestorage.htm', $html);
}
}
?>