diff --git a/api/ui/evaluation.php b/api/ui/evaluation.php index a9ae4c8cc7..d8d75bf398 100644 --- a/api/ui/evaluation.php +++ b/api/ui/evaluation.php @@ -77,6 +77,14 @@ public function get() { if (!empty($this->get['action'])) { switch ($this->get['action']) { + case 'getLogalyzerResponses': + $qF = new QueryFilter(Job::EVALUATION_ID, $evaluation->getId(), "="); + $jobs = Factory::getJobFactory()->filter([$qF]); + //foreach ($jobs as $job) { + // + //} + $array = []; + $this->addData('response', $array); case 'countFinishedJobs': // retrieve how many jobs are finished $qF1 = new QueryFilter(Job::STATUS, Define::JOB_STATUS_FINISHED, "="); diff --git a/api/ui/job.php b/api/ui/job.php index efa105a83a..61023e3beb 100644 --- a/api/ui/job.php +++ b/api/ui/job.php @@ -37,6 +37,13 @@ public function patch() { if (isset($this->request['progress'])) { $job->setProgress($this->request['progress']); } + if(isset($this->request['getLogalyzerResponse'])) { + $warning = -1; + $error = -1; + $mandatory = -1; + $string = json_encode('{"warning": ' . $warning .',"error": '. $error . ',"mandatory": '. $mandatory .'}'); + $this->addData('response', $string); + } Factory::getJobFactory()->update($job); } } diff --git a/api/v1/job.php b/api/v1/job.php index 064973e4af..13a5fe9d70 100644 --- a/api/v1/job.php +++ b/api/v1/job.php @@ -109,6 +109,22 @@ public function get() { } else { $data->log = $log; } + // Dynamically fetch Logalyzer results + $data->logErrorCount = Factory::getJobFactory()->getJobCountForLogLevel($job, 'error', 'negative'); + $data->logWarningCount = Factory::getJobFactory()->getJobCountForLogLevel($job, 'warn', 'negative'); + if($job->getStatus()==Define::JOB_STATUS_FINISHED && !Factory::getJobFactory()->checkAllPositiveJobPatterns($job)) { + $data->logContainsMandatory=0; + } + $logalyzer = new Logalyzer_Library(); + $logalyzer->setSystemAndLoadPattern($system); + $hash = $logalyzer->calculateSystemHash(); + $json = $job->getLogalyzerResults(); + if($json != null) { + $results = json_decode($json, true); + if (!isset($results['hash']) || $results['hash'] != $hash) { + $data->usedOutdatedPattern = true; + } + } } $this->add($data); } @@ -163,7 +179,6 @@ public function patch() { if (empty($this->get['id'])) { throw new Exception('No id provided'); } - $auth = Auth_Library::getInstance(); $job = Factory::getJobFactory()->get($this->get['id']); $evaluation = Factory::getEvaluationFactory()->get($job->getEvaluationId()); @@ -298,5 +313,7 @@ private function appendLog($id) { mkdir(UPLOADED_DATA_PATH . 'log'); } file_put_contents(UPLOADED_DATA_PATH . 'log/' . $id . '.log', $this->request['log'], FILE_APPEND); + $logalyzer = new Logalyzer_Library($job); + $logalyzer->examineLogLine($this->request['log']); } } diff --git a/chronos.sql b/chronos.sql index 9f38cc92f5..cef93e2af3 100644 --- a/chronos.sql +++ b/chronos.sql @@ -70,7 +70,8 @@ CREATE TABLE `Job` ( `finished` datetime DEFAULT NULL, `evaluationId` int(11) NOT NULL, `internalId` int(11) NOT NULL, - `configurationIdentifier` varchar(256) COLLATE utf8_unicode_ci NOT NULL + `configurationIdentifier` varchar(256) COLLATE utf8_unicode_ci NOT NULL, + `logalyzerResults` json ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; @@ -130,7 +131,8 @@ CREATE TABLE `System` ( `vcsPassword` varchar(256) COLLATE utf8_unicode_ci NOT NULL, `created` datetime NOT NULL, `lastEdit` datetime NOT NULL, - `isArchived` int(11) NOT NULL + `isArchived` int(11) NOT NULL, + `logalyzerPatterns` json ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; @@ -215,6 +217,7 @@ CREATE INDEX job_idx_2 ON `Job`(systemId); CREATE INDEX job_idx_3 ON `Job`(status); CREATE INDEX job_idx_4 ON `Job`(evaluationId); CREATE INDEX job_idx_5 ON `Job`(internalId); +CREATE INDEX job_idx_6 ON `Job`(logalyzerHash); CREATE INDEX project_idx_1 ON `Project`(userId); CREATE INDEX project_idx_2 ON `Project`(systemId); diff --git a/constants.php b/constants.php index e95c487aff..6b882c11ef 100644 --- a/constants.php +++ b/constants.php @@ -1,2 +1,3 @@ view->includeAsset("gitgraph"); - if (!empty($this->get['id'])) { $system = new System($this->get['id']); $system = $system->getModel(); @@ -339,7 +338,6 @@ public function system() { if ($system->getUserId() != $auth->getUserID() && !$auth->isAdmin()) { throw new Exception("Not enough privileges to view this system!"); } - if (!empty($this->post['id'])) { if ($this->post['group'] == 'general') { $data = $this->post; @@ -391,8 +389,70 @@ public function system() { throw new Exception("Key already used!"); } } + } else if (!empty($this->post['group'] =='newError')) { + $key = $this->post['newErrorPattern']; + if ($key != "") { + $system = Factory::getSystemFactory()->get($this->post['id']); + $logalyzer = new Logalyzer_Library(); + $logalyzer->setSystemAndLoadPattern($system); + if(!empty($this->post['regexError'])&&$this->post['regexError']=='on') { + $logalyzer->addKey('error', $key, 'regex', 'negative'); + } else { + $logalyzer->addKey('error', $key, 'string', 'negative'); + } + } + } else if (!empty($this->post['group'] == 'newWarning')) { + $key = $this->post['newWarningPattern']; + if ($key != "") { + $system = Factory::getSystemFactory()->get($this->post['id']); + $logalyzer = new Logalyzer_Library(); + $logalyzer->setSystemAndLoadPattern($system); + if(!empty($this->post['regexWarning'])&&$this->post['regexWarning']=='on') { + $logalyzer->addKey('warn', $key, 'regex', 'negative'); + } else { + $logalyzer->addKey('warn', $key, 'string', 'negative'); + } + } + } else if (!empty($this->post['group'] == 'newMandatory')) { + $key = $this->post['newMandatoryPattern']; + if ($key != "") { + $system = Factory::getSystemFactory()->get($this->post['id']); + $logalyzer = new Logalyzer_Library(); + $logalyzer->setSystemAndLoadPattern($system); + if(!empty($this->post['regexMandatory'])&&$this->post['regexMandatory']=='on') { + $logalyzer->addKey('error', $key, 'regex', 'positive'); + } else { + $logalyzer->addKey('error', $key, 'string', 'positive'); + } + } + } + } + else if (!empty($this->get['deleteWarningPattern'])) { + $key = $this->get['deleteWarningPattern']; + if ($key != "") { + $system = Factory::getSystemFactory()->get($this->get['id']); + $logalyzer = new Logalyzer_Library(); + $logalyzer->setSystemAndLoadPattern($system); + $logalyzer->removeKey('warn', $key, 'negative'); } - } else if (!empty($this->get['delete'])) { + } else if (!empty($this->get['deleteErrorPattern'])) { + $key = $this->get['deleteErrorPattern']; + if ($key != "") { + $system = Factory::getSystemFactory()->get($this->get['id']); + $logalyzer = new Logalyzer_Library(); + $logalyzer->setSystemAndLoadPattern($system); + $logalyzer->removeKey('error', $key, 'negative'); + } + } else if (!empty($this->get['deleteMandatoryPattern'])) { + $key = $this->get['deleteMandatoryPattern']; + if ($key != "") { + $system = Factory::getSystemFactory()->get($this->get['id']); + $logalyzer = new Logalyzer_Library(); + $logalyzer->setSystemAndLoadPattern($system); + $logalyzer->removeKey('error', $key, 'positive'); + } + } + else if (!empty($this->get['delete'])) { $settings = Settings_Library::getInstance($system->getId()); if (!empty($this->get['delete'])) { $key = urldecode($this->get['delete']); @@ -497,6 +557,15 @@ public function system() { $this->view->assign('branches', Systems_Library::getBranches($system->getId())); $this->view->assign('history', Systems_Library::getHistory($system->getId())); $this->view->assign('auth', Auth_Library::getInstance()); + + $logalyzer = new Logalyzer_Library(); + $logalyzer->setSystemAndLoadPattern($system); + $errors = $logalyzer->getPatterns('error', 'negative'); + $warnings = $logalyzer->getPatterns('warn', 'negative'); + $mandatory = $logalyzer->getPatterns('error', "positive"); + $this->view->assign('errorPatterns', $errors); + $this->view->assign('warningPatterns', $warnings); + $this->view->assign('mandatoryPatterns', $mandatory); } else { throw new Exception("No id provided!"); } diff --git a/controllers/evaluation.php b/controllers/evaluation.php index 333ece9f9d..551bebbd47 100644 --- a/controllers/evaluation.php +++ b/controllers/evaluation.php @@ -130,7 +130,22 @@ public function detail() { $evaluation->setIsStarred(0); Factory::getEvaluationFactory()->update($evaluation); } - + // Button press to reexamine entire log + // Button shows up if the job was inspected using an outdated pattern + if (!empty($this->post['recheck'])) { + $job = Factory::getJobFactory()->get($this->post['jobId']); + $logalyzer = new Logalyzer_Library($job); + $logalyzer->examineEntireLog(); + } + // Recheck all Jobs contained in an Evaluation + if (!empty($this->post['recheckAll'])) { + $qF = new QueryFilter(Job::EVALUATION_ID, $evaluation->getId(), "="); + $jobs = Factory::getJobFactory()->filter([Factory::FILTER => $qF]); + foreach ($jobs as $subJob) { + $logalyzer = new Logalyzer_Library($subJob); + $logalyzer->examineEntireLog(); + } + } $experiment = Factory::getExperimentFactory()->get($evaluation->getExperimentId()); // Check if the user has enough privileges to access this evaluation @@ -151,6 +166,14 @@ public function detail() { $this->view->assign('subjobs', $jobs); $sys = new System($evaluation->getSystemId()); $this->view->assign('supportsShowResults', $sys->supportsFullResults()); + $system = Factory::getSystemFactory()->get($experiment->getSystemId()); + + $logalyzer = new Logalyzer_Library(); + $logalyzer->setSystemAndLoadPattern($system); + $systemHash = $logalyzer->calculateSystemHash(); + $this->view->assign('systemHash', $systemHash); + $usedOutdatedPattern = false; + // check if all jobs have finished $isFinished = true; $resultsAvailable = false; @@ -160,10 +183,16 @@ public function detail() { } else { $resultsAvailable = true; } + if(Factory::getJobFactory()->getJobHash($subJob) != $systemHash && Factory::getJobFactory()->getJobHash($subJob) != "") { + $usedOutdatedPattern = true; + } } if (sizeof($jobs) == 0) { $isFinished = false; } + if ($usedOutdatedPattern) { + $this->view->assign('usedOutdatedPattern', true); + } $this->view->assign('isFinished', $isFinished); $this->view->assign('resultsAvailable', $resultsAvailable); } else { diff --git a/controllers/job.php b/controllers/job.php index 4dd7d74921..f384478c54 100644 --- a/controllers/job.php +++ b/controllers/job.php @@ -143,12 +143,39 @@ public function detail() { $this->view->assign('evaluation', $evaluation); $this->view->assign('experiment', Factory::getExperimentFactory()->get($evaluation->getExperimentId())); + if (!empty($this->post['recheck'])) { + $logalyzer = new Logalyzer_Library($job); + $logalyzer->examineEntireLog(); + } + + $this->view->assign('logWarningCount', Factory::getJobFactory()->getJobCountForLogLevel($job, 'warn', 'negative')); + $this->view->assign('logErrorCount', Factory::getJobFactory()->getJobCountForLogLevel($job, 'error', 'negative')); + if($job->getStatus()==Define::JOB_STATUS_FINISHED && !Factory::getJobFactory()->checkAllPositiveJobPatterns($job)) { + $this->view->assign('logContainsMandatory', 0); + } + $system = Factory::getSystemFactory()->get($job->getSystemId()); + + // Fetch a Job's Logalyzer results to check if it is up-to-date. + $logalyzer = new Logalyzer_Library(); + $logalyzer->setSystemAndLoadPattern($system); + $hash = $logalyzer->calculateSystemHash(); + $json = $job->getLogalyzerResults(); + if($json != null) { + $results = json_decode($json, true); + if(!isset($results['hash'])||$results['hash'] != $hash) { + $this->view->assign('usedOutdatedPattern', true); + } + } + + $events = Util::eventFilter(['job' => $job]); $this->view->assign('events', $events); } else { throw new Exception("No job with id: " . $this->get['id']); } - } else { + + } + else { throw new Exception("No job id provided!"); } } diff --git a/libraries/dba/AbstractModelFactory.class.php b/libraries/dba/AbstractModelFactory.class.php index 862d0ec191..c48c3b97ca 100755 --- a/libraries/dba/AbstractModelFactory.class.php +++ b/libraries/dba/AbstractModelFactory.class.php @@ -775,5 +775,186 @@ public function getDB($test = false) { die("Fatal Error! Database connection failed. Message: " . $e->getMessage()); } } + + /** + * Creates two queries to identify the right value to increment + * $stmt1 will search for the index of the array's entry + * $incrementQuery will look in the specified :index of the array and replace the value of the key + * @param $jobId + * @param $pattern + * @param $amount + * @return void + */ + public function incrementJobCountAtomically($jobId, $resultCollection) + { + $dbh = self::getDB(); + try { + foreach ($resultCollection as $pattern) { + $stmt1 = $dbh->prepare("SELECT + JSON_UNQUOTE( + REPLACE(JSON_EXTRACT( + JSON_SEARCH(logalyzerResults, 'one', :pattern), '$[0]'), '].pattern', '].count')) + INTO @index + FROM Job + WHERE jobId = :jobId;"); + $stmt1->bindParam(':pattern', $pattern['pattern'], PDO::PARAM_STR); + $stmt1->bindParam(':jobId', $jobId, PDO::PARAM_INT); + + if (!$stmt1->execute()) { + file_put_contents(UPLOADED_DATA_PATH . 'log/' . $jobId . '.log', "\nError in execute() for Query 1\n", FILE_APPEND); + } + $helper = $dbh->query("SELECT @index"); + $index = $helper->fetch(PDO::FETCH_ASSOC); + + $stmt2 = $dbh->prepare("UPDATE Job + SET logalyzerResults = JSON_SET( + logalyzerResults, + :index, + CAST(CAST( + JSON_UNQUOTE( + JSON_EXTRACT(logalyzerResults, :index) + ) AS UNSIGNED) + :amount AS CHAR)) + WHERE jobId = :jobId AND JSON_SEARCH(logalyzerResults, 'one', :pattern) is not null;"); + $stmt2->bindParam(':index', $index['@index'], PDO::PARAM_STR); + $stmt2->bindParam(':pattern', $pattern['pattern'], PDO::PARAM_STR); + $stmt2->bindParam(':amount', $pattern['count'], PDO::PARAM_INT); + $stmt2->bindParam(':jobId', $jobId, PDO::PARAM_INT); + if (!$stmt2->execute()) { + file_put_contents(UPLOADED_DATA_PATH . 'log/' . $jobId . '.log', "\nError in execute()\n", FILE_APPEND); + } + + # Used to query the database entry and append it to a log for debugging + #$checker2 = $dbh->prepare("SELECT * FROM Job WHERE jobId = :jobId AND JSON_SEARCH(logalyzerResults, 'one', :pattern) is not null;"); + #$checker2->bindParam(':jobId', $jobId, PDO::PARAM_INT); + #$checker2->bindParam(':pattern', $pattern['pattern'], PDO::PARAM_STR); + #$checker2->execute(); + #$fetch2 = $checker2->fetch(PDO::FETCH_ASSOC); + #file_put_contents(UPLOADED_DATA_PATH . 'log/' . $jobId . '.log', print_r($fetch2, true)."\n", FILE_APPEND); + + } + } + catch (PDOException $e) { + #$dbh->rollback(); + file_put_contents(UPLOADED_DATA_PATH . 'log/' . $jobId . '.log', $e->getMessage(), FILE_APPEND); + } + } + + /** + * currently not in use + * @param $jobId + * @param $logLevel + * @param $pattern + * @param $regex + * @param $type + * @param $hash + * @param $amount + * @return bool + */ + public function logalyzerAppendNewResult($jobId, $logLevel, $pattern, $regex, $type, $hash, $amount=0) { + $dbh = self::getDB(); + $dbh->beginTransaction(); + $lockQuery = "SELECT logalyzerResults FROM Job WHERE jobId=? FOR UPDATE"; + $stmt = $dbh->prepare($lockQuery); + $stmt->execute([$jobId]); + + $query = "UPDATE Job SET logalyzerResults = JSON_ARRAY_APPEND(logalyzerResults, '$.results', JSON_OBJECT('logLevel', ".$logLevel.", 'pattern', '".$pattern."', 'regex', '".$regex."', 'type', '".$type."', 'count', ".$amount.")) WHERE jobId=?"; + $stmt2 = $dbh->prepare($query); + $result = $stmt2->execute([$jobId]); + + $hashUpdate = "UPDATE Job SET logalyzerResults = JSON_SET(logalyzerResults, '$.hash', ?) WHERE jobId=?"; + $stmt3 = $dbh->prepare($hashUpdate); + $result = $stmt3->execute([$hash, $jobId]); + $dbh->commit(); + return $result; + } + public function logalyzerUpdateHash($jobId, $hash) { + $dbh = self::getDB(); + $dbh->beginTransaction(); + $lockQuery = "SELECT logalyzerResults FROM Job WHERE jobId = ? FOR UPDATE"; + $stmt = $dbh->prepare($lockQuery); + $stmt->execute([$jobId]); + + $hashQuery = "UPDATE Job SET logalyzerResults = JSON_SET(logalyzerResults, '$.hash', ?) WHERE jobId=?"; + + + $stmt2 = $dbh->prepare($hashQuery); + $result = $stmt2->execute([$hash, $jobId]); + $dbh->commit(); + return $result; + } + /** + * Goes over all results and aggregates the counts for keywords of the same logLevel + * Returns the accumulated number of all patterns with the specified $logLevel + * @param $job + * @param $logLevel + * @param $type + * @return int + */ + public function getJobCountForLogLevel($job, $logLevel, $type) { + if($job->getLogalyzerResults() != null) { + $json = json_decode($job->getLogalyzerResults(), true); + if(isset($json['result'])) { + $resultArray = $json['result']; + } + # An early version of this feature used an array key 'pattern', which was changed before release. This is just a fallback + elseif(isset($json['pattern'])) { + $resultArray = $json['pattern']; + } + else { + return 0; + } + $count = 0; + foreach ($resultArray as $element) { + if ($type === "negative" && $element["type"] === "negative" && $element["logLevel"] === $logLevel) { + $count += $element["count"]; + } + } + return $count; + } + else { + return 0; + } + } + public function getJobHash($job) { + $json = $job->getLogalyzerResults(); + if ($json != null) { + $json = json_decode($json, true); + //echo "JobHash: " . $json['jobHash'] . " returned.\n"; + return $json['hash']; + } + else { + return ""; + } + } + + /** + * Returns true if all positive patterns are present, or if no positive pattern exists for this system + * @param $job + * @return bool + */ + public function checkAllPositiveJobPatterns($job) { + $json = $job->getLogalyzerResults(); + if ($json != null) { + $data = json_decode($job->getLogalyzerResults(), true); + if(isset($data['result'])) { + foreach ($data['result'] as $element) { + if ($element['type'] === 'positive' && $element['count'] <= 0) { + return false; + } + } + return true; + } + # An early version used the key 'pattern' instead of 'results' this is only a fallback + elseif(isset($data['pattern'])) { + foreach ($data['pattern'] as $element) { + if ($element['type'] === 'positive' && $element['count'] <= 0) { + return false; + } + } + return true; + } + } + return true; + } } diff --git a/libraries/dba/models/Job.class.php b/libraries/dba/models/Job.class.php index a64f8f5bbc..fe2b36970c 100644 --- a/libraries/dba/models/Job.class.php +++ b/libraries/dba/models/Job.class.php @@ -43,8 +43,9 @@ class Job extends AbstractModel { private $evaluationId; private $internalId; private $configurationIdentifier; + private $logalyzerResults; - function __construct($jobId, $userId, $description, $systemId, $environment, $phases, $configuration, $status, $progress, $result, $created, $started, $finished, $evaluationId, $internalId, $configurationIdentifier) { + function __construct($jobId, $userId, $description, $systemId, $environment, $phases, $configuration, $status, $progress, $result, $created, $started, $finished, $evaluationId, $internalId, $configurationIdentifier, $logalyzerResults=null) { $this->jobId = $jobId; $this->userId = $userId; $this->description = $description; @@ -61,6 +62,7 @@ function __construct($jobId, $userId, $description, $systemId, $environment, $ph $this->evaluationId = $evaluationId; $this->internalId = $internalId; $this->configurationIdentifier = $configurationIdentifier; + $this->logalyzerResults = $logalyzerResults; } function getKeyValueDict() { @@ -81,6 +83,7 @@ function getKeyValueDict() { $dict['evaluationId'] = $this->evaluationId; $dict['internalId'] = $this->internalId; $dict['configurationIdentifier'] = $this->configurationIdentifier; + $dict['logalyzerResults'] = $this->logalyzerResults; return $dict; } @@ -220,6 +223,14 @@ function getConfigurationIdentifier(){ function setConfigurationIdentifier($configurationIdentifier){ $this->configurationIdentifier = $configurationIdentifier; } + + function getLogalyzerResults(){ + return $this->logalyzerResults; + } + + function setLogalyzerResults($logalyzerResults){ + $this->logalyzerResults = $logalyzerResults; + } const JOB_ID = "jobId"; const USER_ID = "userId"; @@ -237,4 +248,5 @@ function setConfigurationIdentifier($configurationIdentifier){ const EVALUATION_ID = "evaluationId"; const INTERNAL_ID = "internalId"; const CONFIGURATION_IDENTIFIER = "configurationIdentifier"; + const LOGALYZER_RESULTS = "logalyzerResults"; } diff --git a/libraries/dba/models/JobFactory.class.php b/libraries/dba/models/JobFactory.class.php index 7a7e85ebb4..04b1f2f03c 100644 --- a/libraries/dba/models/JobFactory.class.php +++ b/libraries/dba/models/JobFactory.class.php @@ -47,7 +47,7 @@ function getCacheValidTime() { * @return Job */ function getNullObject() { - $o = new Job(-1, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null); + $o = new Job(-1, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null); return $o; } @@ -57,7 +57,7 @@ function getNullObject() { * @return Job */ function createObjectFromDict($pk, $dict) { - $o = new Job($dict['jobId'], $dict['userId'], $dict['description'], $dict['systemId'], $dict['environment'], $dict['phases'], $dict['configuration'], $dict['status'], $dict['progress'], $dict['result'], $dict['created'], $dict['started'], $dict['finished'], $dict['evaluationId'], $dict['internalId'], $dict['configurationIdentifier']); + $o = new Job($dict['jobId'], $dict['userId'], $dict['description'], $dict['systemId'], $dict['environment'], $dict['phases'], $dict['configuration'], $dict['status'], $dict['progress'], $dict['result'], $dict['created'], $dict['started'], $dict['finished'], $dict['evaluationId'], $dict['internalId'], $dict['configurationIdentifier'], $dict['logalyzerResults']); return $o; } diff --git a/libraries/dba/models/JobView.class.php b/libraries/dba/models/JobView.class.php index c6a9943131..8b86a28e5b 100644 --- a/libraries/dba/models/JobView.class.php +++ b/libraries/dba/models/JobView.class.php @@ -44,8 +44,9 @@ class JobView extends AbstractModel { private $internalId; private $configurationIdentifier; private $projectUserId; + private $logalyzerResults; - function __construct($jobId, $userId, $description, $systemId, $environment, $phases, $configuration, $status, $progress, $result, $created, $started, $finished, $evaluationId, $internalId, $configurationIdentifier, $projectUserId) { + function __construct($jobId, $userId, $description, $systemId, $environment, $phases, $configuration, $status, $progress, $result, $created, $started, $finished, $evaluationId, $internalId, $configurationIdentifier, $projectUserId, $logalyzerResults=null) { $this->jobId = $jobId; $this->userId = $userId; $this->description = $description; @@ -63,6 +64,7 @@ function __construct($jobId, $userId, $description, $systemId, $environment, $ph $this->internalId = $internalId; $this->configurationIdentifier = $configurationIdentifier; $this->projectUserId = $projectUserId; + $this->logalyzerResults = $logalyzerResults; } function getKeyValueDict() { @@ -84,6 +86,7 @@ function getKeyValueDict() { $dict['internalId'] = $this->internalId; $dict['configurationIdentifier'] = $this->configurationIdentifier; $dict['projectUserId'] = $this->projectUserId; + $dict['logalyzerResults'] = $this->logalyzerResults; return $dict; } @@ -231,6 +234,14 @@ function getProjectUserId(){ function setProjectUserId($projectUserId){ $this->projectUserId = $projectUserId; } + + function getLogalyzerResults(){ + return $this->logalyzerResults; + } + + function setLogalyzerResults($logalyzerResults){ + $this->logalyzerResults = $logalyzerResults; + } const JOB_ID = "jobId"; const USER_ID = "userId"; @@ -249,4 +260,5 @@ function setProjectUserId($projectUserId){ const INTERNAL_ID = "internalId"; const CONFIGURATION_IDENTIFIER = "configurationIdentifier"; const PROJECT_USER_ID = "projectUserId"; + const LOGALYZER_RESULTS = "logalyzerResults"; } diff --git a/libraries/dba/models/JobViewFactory.class.php b/libraries/dba/models/JobViewFactory.class.php index 9413ea188f..06cf5cd15f 100644 --- a/libraries/dba/models/JobViewFactory.class.php +++ b/libraries/dba/models/JobViewFactory.class.php @@ -47,7 +47,7 @@ function getCacheValidTime() { * @return JobView */ function getNullObject() { - $o = new JobView(-1, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null); + $o = new JobView(-1, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null); return $o; } @@ -57,7 +57,7 @@ function getNullObject() { * @return JobView */ function createObjectFromDict($pk, $dict) { - $o = new JobView($dict['jobId'], $dict['userId'], $dict['description'], $dict['systemId'], $dict['environment'], $dict['phases'], $dict['configuration'], $dict['status'], $dict['progress'], $dict['result'], $dict['created'], $dict['started'], $dict['finished'], $dict['evaluationId'], $dict['internalId'], $dict['configurationIdentifier'], $dict['projectUserId']); + $o = new JobView($dict['jobId'], $dict['userId'], $dict['description'], $dict['systemId'], $dict['environment'], $dict['phases'], $dict['configuration'], $dict['status'], $dict['progress'], $dict['result'], $dict['created'], $dict['started'], $dict['finished'], $dict['evaluationId'], $dict['internalId'], $dict['configurationIdentifier'], $dict['projectUserId'], $dict['logalyzerResults']); return $o; } diff --git a/libraries/dba/models/System.class.php b/libraries/dba/models/System.class.php index 0fdf9b67ce..6056a291bf 100644 --- a/libraries/dba/models/System.class.php +++ b/libraries/dba/models/System.class.php @@ -39,8 +39,9 @@ class System extends AbstractModel { private $created; private $lastEdit; private $isArchived; + private $logalyzerPatterns; - function __construct($systemId, $name, $description, $userId, $vcsUrl, $vcsBranch, $vcsType, $vcsUser, $vcsPassword, $created, $lastEdit, $isArchived) { + function __construct($systemId, $name, $description, $userId, $vcsUrl, $vcsBranch, $vcsType, $vcsUser, $vcsPassword, $created, $lastEdit, $isArchived, $logalyzerPatterns=null) { $this->systemId = $systemId; $this->name = $name; $this->description = $description; @@ -53,6 +54,7 @@ function __construct($systemId, $name, $description, $userId, $vcsUrl, $vcsBranc $this->created = $created; $this->lastEdit = $lastEdit; $this->isArchived = $isArchived; + $this->logalyzerPatterns = $logalyzerPatterns; } function getKeyValueDict() { @@ -69,6 +71,7 @@ function getKeyValueDict() { $dict['created'] = $this->created; $dict['lastEdit'] = $this->lastEdit; $dict['isArchived'] = $this->isArchived; + $dict['logalyzerPatterns'] = $this->logalyzerPatterns; return $dict; } @@ -176,6 +179,14 @@ function getIsArchived(){ function setIsArchived($isArchived){ $this->isArchived = $isArchived; } + + function getLogalyzerPatterns(){ + return $this->logalyzerPatterns; + } + + function setLogalyzerPatterns($logalyzerPatterns){ + $this->logalyzerPatterns = $logalyzerPatterns; + } const SYSTEM_ID = "systemId"; const NAME = "name"; @@ -189,4 +200,5 @@ function setIsArchived($isArchived){ const CREATED = "created"; const LAST_EDIT = "lastEdit"; const IS_ARCHIVED = "isArchived"; + const LOGALYZER_PATTERNS = "logalyzerPatterns"; } diff --git a/libraries/dba/models/SystemFactory.class.php b/libraries/dba/models/SystemFactory.class.php index e49fe3355a..e9fd90fe95 100644 --- a/libraries/dba/models/SystemFactory.class.php +++ b/libraries/dba/models/SystemFactory.class.php @@ -47,7 +47,7 @@ function getCacheValidTime() { * @return System */ function getNullObject() { - $o = new System(-1, null, null, null, null, null, null, null, null, null, null, null); + $o = new System(-1, null, null, null, null, null, null, null, null, null, null, null, null); return $o; } @@ -57,7 +57,7 @@ function getNullObject() { * @return System */ function createObjectFromDict($pk, $dict) { - $o = new System($dict['systemId'], $dict['name'], $dict['description'], $dict['userId'], $dict['vcsUrl'], $dict['vcsBranch'], $dict['vcsType'], $dict['vcsUser'], $dict['vcsPassword'], $dict['created'], $dict['lastEdit'], $dict['isArchived']); + $o = new System($dict['systemId'], $dict['name'], $dict['description'], $dict['userId'], $dict['vcsUrl'], $dict['vcsBranch'], $dict['vcsType'], $dict['vcsUser'], $dict['vcsPassword'], $dict['created'], $dict['lastEdit'], $dict['isArchived'], $dict['logalyzerPatterns']); return $o; } diff --git a/libraries/dba/models/generator.php b/libraries/dba/models/generator.php index c55b9a9845..839836ddb4 100644 --- a/libraries/dba/models/generator.php +++ b/libraries/dba/models/generator.php @@ -41,7 +41,8 @@ 'vcsPassword', 'created', 'lastEdit', - 'isArchived' + 'isArchived', + 'logalyzerPatterns' ]; $CONF['Project'] = [ 'projectId', @@ -94,7 +95,8 @@ 'finished', 'evaluationId', 'internalId', - 'configurationIdentifier' + 'configurationIdentifier', + 'logalyzerResults' ]; $CONF['Result'] = [ 'resultId', @@ -201,7 +203,8 @@ 'evaluationId', 'internalId', 'configurationIdentifier', - 'projectUserId' + 'projectUserId', + 'logalyzerResults' ]; foreach ($CONF as $NAME => $COLUMNS) { diff --git a/libraries/logalyzer.php b/libraries/logalyzer.php new file mode 100644 index 0000000000..746fcb0526 --- /dev/null +++ b/libraries/logalyzer.php @@ -0,0 +1,338 @@ +job = $job; + if($this->job != null) { + $this->system = Factory::getSystemFactory()->get($this->job->getSystemId()); + $this->loadPatterns(); + + } + } + + /** + * Returns the Job of this Logalyzer Instance, might return Null if no job has been defined + * @return Job|null + */ + public function getJob() { + return $this->job; + } + /** + * Returns the System of this Logalyzer Instance + * @return DBA\System + */ + public function getSystem() { + return $this->system; + } + + /** + * Select the desired system and fetch the stored patterns from the database + * @param DBA\System $system The system to fetch patterns from + * @return void + */ + public function setSystemAndLoadPattern($system) { + $this->system = $system; + $this->loadPatterns(); + } + + /** + * Allows retroactively setting a Job for an existing Logalyzer object. + * @param DBA\Job $job The Job to analyze + * @return void + */ + public function setJob($job) { + $this->job = $job; + } + /** + * Searches the $keyword in the $target using an appropriate search function for the type defined by $regex + * @param string $keyword Pattern to be searched + * @param string $target Target text, such as a log file or log line + * @param string $regex Takes the form of 'string' or 'regex'. + * @return int Returns the amount of occurrences of $keyword in $target as an integer. + */ + public function countLogOccurances(string $keyword, string $target, string $regex) { + if ($regex === 'regex') { + return preg_match_all($keyword, $target); + } elseif ($regex === 'string') { + return substr_count($target, $keyword); + } + else { + return 0; + } + } + + /** + * Checks the hash of a system's patterns and the patterns used to analyze a Job. + * Returns true if the hash values differ. + * @return bool + */ + private function checkHashDifference() { + $results = json_decode($this->job->getLogalyzerResults(), true); + return !($results['hash'] === hash('sha1', json_encode($this->system_pattern))); + } + + /** + * Load and read the entire logfile counting the occurrences of the pattern, saving the result in the database + * @return void + */ + public function examineEntireLog() { + $path = UPLOADED_DATA_PATH . '/log/' . $this->job->getId() . '.log'; + $log = Util::readFileContents($path); + if ($log === false) { + $this->log = ""; + } else { + $this->log = $log; + } + $this->createEmptyJobLogalyzerResults(); + $hash = $this->calculateSystemHash(); + + foreach($this->system_pattern['result'] as $pattern) { + $number = $this->countLogOccurances($pattern['pattern'], $this->log, $pattern['regex']); + $found = false; + foreach($this->job_pattern['result'] as $result) { + if(isset($result['logLevel'],$result['pattern'],$result['regex'],$result['type']) && $pattern['logLevel'] === $result['logLevel'] && $pattern['pattern'] === $result['pattern'] && $pattern['regex'] === $result['regex'] && $pattern['type'] === $result['type']) { + $result['count'] += $number; + $found = true; + } + } + if(!$found) { + $pattern['count'] = $number; + $this->job_pattern['result'][] = $pattern; + } + } + $this->job_pattern['hash'] = $hash; + $this->job->setLogalyzerResults(json_encode($this->job_pattern)); + Factory::getJobFactory()->update($this->job); + } + + /** + * Reads a submitted log line and updates the database object with a new result json object + * @param $logLine + * @return void + */ + public function examineLogLine($logLine) { + // Load existing result set + $json = $this->system->getLogalyzerPatterns(); + if($json === null) { + return; + } + $json = $this->job->getLogalyzerResults(); + if($json === null) { + $this->createEmptyJobLogalyzerResults(); + } + else { + $this->job_pattern = json_decode($json, true); + } + $hash = $this->calculateSystemHash(); + $resultCollection = []; + $LOG_ERRORS_MAX = 50; // TODO change to constant from constants.php + foreach($this->system_pattern['result'] as $index => $pattern) { + $number = $this->countLogOccurances($pattern['pattern'], $logLine, $pattern['regex']); + $isInResultSet = false; + foreach($this->job_pattern['result'] as $result) { + // Check if the result has been previously set in the job's result + if (isset($result['logLevel'], $result['pattern'], $result['regex'], $result['type']) && $pattern['logLevel'] === $result['logLevel'] && $pattern['pattern'] === $result['pattern'] && $pattern['regex'] === $result['regex'] && $pattern['type'] === $result['type'] && $result['count'] < $LOG_ERRORS_MAX) { + $isInResultSet = true; + if ($number >= 1) { + $pattern['count'] = $number; + $resultCollection[] = $pattern; + } + } + } + if(!$isInResultSet) { + $pattern['count'] = $number; + $this->job_pattern['result'][] = $pattern; + if($this->job_pattern['hash'] === "" || $this->job_pattern['hash'] === null) { + $this->job_pattern['hash'] = $hash; + } + + $this->job->setLogalyzerResults(json_encode($this->job_pattern)); + Factory::getJobFactory()->update($this->job); + } + } + if(!empty($resultCollection)) { + Factory::getJobFactory()->incrementJobCountAtomically($this->job->getId(), $resultCollection); + } + } + + /** + * Creates empty pattern for a system + * @return void + */ + private function createBasicPatterns() { + $this->system_pattern['hash'] = ""; + $this->system_pattern['result'] = array(); + } + + /** + * Fetch a systems' pattern as an array + * $identifier can be 'all', or the desired logLevel such as 'warn' or 'error' + * @param string $logLevel + * @param string $type 'regex' or 'string' + * @return array + */ + public function getPatterns(string $logLevel, string $type) { + if ($this->system_pattern == null) { + $this->createBasicPatterns(); + } + if ($logLevel === 'all') { + return $this->system_pattern['result']; + } else { + $temp = []; + foreach ($this->system_pattern['result'] as $pattern) { + if ($pattern['logLevel'] == $logLevel && $pattern['type'] == $type) { + $temp[] = $pattern; + } + } + return $temp; + } + } + + /** + * Fetches the json object containing the systems pattern from the database. + * Decodes the json object and populates local variables or creates an empty pattern if database returns null object + * @return void + */ + public function loadPatterns() { + $patterns = $this->system->getLogalyzerPatterns(); + if (isset($patterns) && $patterns != null) { + $this->system_pattern = json_decode($patterns, true); + } + else { + // No patterns have been created for this system yet + $this->createBasicPatterns(); + } + } + + /** + * Saves a modified pattern set to the systems database table + * Is called whenever patterns changed + * Hash value is updated + * @return void + */ + private function savePatterns() { + $this->system_pattern['hash'] = hash('sha1', json_encode($this->system_pattern['result'])); + $this->system->setLogalyzerPatterns(json_encode($this->system_pattern)); + Factory::getSystemFactory()->update($this->system); + } + /** + * Adds a new pattern defined in the System UI and saves it to the System database table if it is no duplicate + * @param string $logLevel + * @param string $pattern 'the pattern string' + * @param string $regex 'string' or 'regex' + * @param string $type 'positive' or 'negative' + * @return void + */ + public function addKey(string $logLevel, string $pattern, string $regex, string $type) { + if($this->system == null) { + echo 'System not defined\n'; + } + else { + $array = array('logLevel' => $logLevel, 'pattern' => $pattern, 'regex' => $regex, 'type' => $type); + // Duplicate check + if(!in_array($array, $this->system_pattern['result'])) { + $this->system_pattern['result'][] = $array; + $this->savePatterns(); + } + } + } + + /** + * Search and drop the corresponding pattern for a System + * @param string $logLevel currently supports 'warn' and 'error' + * @param string $pattern 'the pattern string' + * @param string $type 'positive' or 'negative' + * @return void + */ + public function removeKey(string $logLevel, string $pattern, string $type) { + if($this->system == null) { + echo 'System not defined\n'; + } + else { + $array = array('logLevel' => $logLevel, 'pattern' => $pattern, 'regex' => 'string', 'type' => $type); + // Check if it is a normal string + if(in_array($array, $this->system_pattern['result'])) { + $index = array_search($array, $this->system_pattern['result']); + unset($this->system_pattern['result'][$index]); + $this->savePatterns(); + } + // Check if it is a regex + else { + $array['regex'] = 'regex'; + if(in_array($array, $this->system_pattern['result'])) { + $index = array_search($array, $this->system_pattern['result']); + unset($this->system_pattern['result'][$index]); + $this->savePatterns(); + } + } + } + } + + /** + * Populates the local variables for a new Job Result + * @return void + */ + private function createEmptyJobLogalyzerResults() { + $this->job_pattern['hash'] = ""; + $this->job_pattern['result'] = array(); + } + + /** + * Calculates the System hash on the fly + * @return string + */ + function calculateSystemHash() { + return hash('sha1', json_encode($this->system_pattern['result'])); + } +} diff --git a/views/admin/system.php b/views/admin/system.php index 76ff2de939..3f1429def4 100644 --- a/views/admin/system.php +++ b/views/admin/system.php @@ -454,6 +454,120 @@ + +
+
+

Log Analysis

+

+

All patterns are case-sensitive, unless it's a regex that considers case-insensitivity.

+
+
+
+
+

Errors

+
+
+ + +
+ + + Regex + +
+
+
+ + +
+
+ + + + + + Delete + + +
+
+ +
+
+
+
+
+

Warnings

+
+
+ + + +
+ + + Regex + +
+
+
+ + +
+
+ + + + + Delete + + +
+
+ +
+
+
+
+
+

Positive Patterns

+ All positive patterns must be present in a log for it to be considered valid. +
+
+ + +
+ + + Regex + +
+
+
+ + +
+
+ + + + + + Delete + + +
+
+ +
+
+ diff --git a/views/evaluation/detail.php b/views/evaluation/detail.php index 86a609a7f3..4b50fe46a5 100644 --- a/views/evaluation/detail.php +++ b/views/evaluation/detail.php @@ -26,6 +26,7 @@ */ use DBA\Job; +use DBA\Factory; $this->includeInlineJS(" function validateForm() { @@ -61,7 +62,23 @@ function submitData() { } }); } - + function getLogalyzerResponses(evalId) { + $.ajax({ + url: '/api/ui/evaluation/id=' + evalId, + data : { action: 'getLogalyzerResponses' }, + type: 'GET', + success: function(response) { + if (response.jobsToHighlight) { + $('#jobCompleteMessage').show(); + } else { + setTimeout(checkJobStatus, 5000); + } + }, + error: function() { + console.error('Error checking job status.'); + } + }); + } jQuery(document).ready(function($) { $(\".clickable-row\").click(function() { window.document.location = $(this).data(\"href\"); @@ -191,6 +208,11 @@ function submitData() {

Jobs

+ +
+ +
+
@@ -198,8 +220,11 @@ function submitData() { + + + @@ -207,6 +232,17 @@ function submitData() { + + - - + +
# Description Status
getInternalId(); ?> getDescription(); ?> + getJobCountForLogLevel($job, 'error', 'negative')>=1) { ?> + + getJobCountForLogLevel($job, 'warn', 'negative')>=1) { ?> + + + getStatus() == Define::JOB_STATUS_FINISHED || $job->getStatus() == Define::JOB_STATUS_FAILED) && !Factory::getJobFactory()->checkAllPositiveJobPatterns($job)) { ?> + + + getStatus() == Define::JOB_STATUS_SCHEDULED) { ?> scheduled @@ -223,13 +259,21 @@ function submitData() { getStatus() == Define::JOB_STATUS_FINISHED) { ?> + getJobHash($job) != $data['systemHash'] && Factory::getJobFactory()->getJobHash($job) != "" ) {?> + +
+ + +
+
+ +
-
diff --git a/views/job/detail.php b/views/job/detail.php index 40e23cb5aa..f15e22e13e 100644 --- a/views/job/detail.php +++ b/views/job/detail.php @@ -96,6 +96,21 @@ function updateAll() { $('#progress').width(obj.response.progress + '%'); $('#log').html(ansi_up.ansi_to_html(obj.response.log).replace(/\\r\\n/g, '\\n').replace(/\\n/g, '
')); $('#log').scrollTop($('#log')[0].scrollHeight); + $('#errors').text(obj.response.logErrorCount); + $('#warnings').text(obj.response.logWarningCount); + + if (obj.response.logWarningCount >= 1 && obj.response.logErrorCount < 1) { + document.getElementById('logWarningBanner').style.display = 'block'; + } + if (obj.response.logErrorCount >= 1) { + document.getElementById('logWarningBanner').style.display = 'none'; + document.getElementById('logErrorBanner').style.display = 'block'; + } + if (obj.response.logContainsMandatory == 0) { + document.getElementById('logMandatoryBanner').style.display = 'block'; + } else { + document.getElementById('logMandatoryBanner').style.display = 'none'; + } }); } @@ -118,7 +133,6 @@ function updateProgress() { }, 2000); $('#log').scrollTop($('#log')[0].scrollHeight); }); - "); $this->includeInlineCSS(" @@ -130,7 +144,7 @@ function updateProgress() { "); ?>
-
+

Job details @@ -214,28 +228,62 @@ function updateProgress() {

-
+
-
+
+
+
+
+ + + +
+
+ Errors: +
+
+
+
+ Warnings: +
+
+
+
+ +
+ × +

Outdated pattern used, use 'Recheck'

+
+ + + +
-
-