Merge pull request #137 from TheSerapher/worker-monitor

Worker monitor
This commit is contained in:
Sebastian Grewe 2013-06-07 06:41:55 -07:00
commit d986ed93b8
12 changed files with 296 additions and 16 deletions

61
cronjobs/notifications.php Executable file
View File

@ -0,0 +1,61 @@
#!/usr/bin/php
<?php
/*
Copyright:: 2013, Sebastian Grewe
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Include all settings and classes
require_once('shared.inc.php');
// Find all IDLE workers
$aWorkers = $worker->getAllIdleWorkers();
if (empty($aWorkers)) {
verbose("No idle workers found\n");
} else {
foreach ($aWorkers as $aWorker) {
$aData = $aWorker;
$aData['username'] = $user->getUserName($aWorker['account_id']);
$aData['subject'] = 'IDLE Worker : ' . $aWorker['username'];
$aData['email'] = $user->getUserEmail($aData['username']);
if ( $notification->isNotified($aData) ) {
verbose("Worker already notified\n");
continue;
}
if ($notification->addNotification('idle_worker', $aData) && $notification->sendMail($aData['email'], 'idle_worker', $aData)) {
verbose ("Notified " . $aData['email'] . " for IDLE worker " . $aWorker['username'] . "\n");
} else {
verbose("Unable to send notification: " . $notification->getError() . "\n");
}
}
}
// We notified, lets check which recovered
$aNotifications = $notification->getAllActive();
foreach ($aNotifications as $aNotification) {
$aData = json_decode($aNotification['data'], true);
$aWorker = $worker->getWorker($aData['id']);
if ($aWorker['active'] == 1) {
if ($notification->setInactive($aNotification['id'])) {
verbose("Marked notification " . $aNotification['id'] . " as inactive\n");
} else {
verbose("Failed to set notification inactive for " . $aWorker['username'] . "\n");
}
}
}
?>

View File

@ -16,7 +16,7 @@ PIDFILE='/tmp/mmcfe-ng-cron.pid'
CRONHOME='.'
# List of cruns to execute
CRONS="findblock.php proportional_payout.php blockupdate.php auto_payout.php tickerupdate.php"
CRONS="findblock.php proportional_payout.php blockupdate.php auto_payout.php tickerupdate.php notifications.php"
# Additional arguments to pass to cronjobs
CRONARGS="-v"

View File

@ -14,3 +14,5 @@ require_once(CLASS_DIR . '/worker.class.php');
require_once(CLASS_DIR . '/statistics.class.php');
require_once(CLASS_DIR . '/transaction.class.php');
require_once(CLASS_DIR . '/setting.class.php');
require_once(CLASS_DIR . '/mail.class.php');
require_once(CLASS_DIR . '/notification.class.php');

View File

@ -0,0 +1,65 @@
<?php
// Make sure we are called from index.php
if (!defined('SECURITY'))
die('Hacking attempt');
class Mail {
private $sError = '';
public function setDebug($debug) {
$this->debug = $debug;
}
public function setMysql($mysqli) {
$this->mysqli = $mysqli;
}
public function setSmarty($smarty) {
$this->smarty = $smarty;
}
public function setConfig($config) {
$this->config = $config;
}
public function setErrorMessage($msg) {
$this->sError = $msg;
}
public function getError() {
return $this->sError;
}
function checkStmt($bState) {
$this->debug->append("STA " . __METHOD__, 4);
if ($bState ===! true) {
$this->debug->append("Failed to prepare statement: " . $this->mysqli->error);
$this->setErrorMessage('Internal application Error');
return false;
}
return true;
}
public function sendMail($email, $template, $aData) {
$this->smarty->assign('WEBSITENAME', $this->config['website']['name']);
$this->smarty->assign('SUBJECT', $aData['subject']);
$this->smarty->assign('DATA', $aData);
$headers = 'From: Website Administration <' . $this->config['website']['email'] . ">\n";
$headers .= "MIME-Version: 1.0\n";
$headers .= "Content-Type: text/html; charset=ISO-8859-1\r\n";
if (mail($email,
$this->smarty->fetch(BASEPATH . 'templates/mail/subject.tpl'),
$this->smarty->fetch(BASEPATH . 'templates/mail/' . $template . '.tpl'),
$headers)) {
return true;
} else {
$this->setErrorMessage("Unable to send mail");
return false;
}
return false;
}
}
// Make our class available automatically
$mail = new Mail ();
$mail->setDebug($debug);
$mail->setMysql($mysqli);
$mail->setSmarty($smarty);
$mail->setConfig($config);
?>

View File

@ -0,0 +1,80 @@
<?php
// Make sure we are called from index.php
if (!defined('SECURITY'))
die('Hacking attempt');
class Notification extends Mail {
var $table = 'notifications';
public function setInactive($id) {
$field = array(
'name' => 'active',
'type' => 'i',
'value' => 0
);
return $this->updateSingle($id, $field);
}
/**
* Update a single row in a table
* @param userID int Account ID
* @param field string Field to update
* @return bool
**/
private function updateSingle($id, $field) {
$this->debug->append("STA " . __METHOD__, 4);
$stmt = $this->mysqli->prepare("UPDATE $this->table SET " . $field['name'] . " = ? WHERE id = ? LIMIT 1");
if ($this->checkStmt($stmt) && $stmt->bind_param($field['type'].'i', $field['value'], $id) && $stmt->execute())
return true;
$this->debug->append("Unable to update " . $field['name'] . " with " . $field['value'] . " for ID $id");
return false;
}
/**
* We check our notification table for existing data
* so we can avoid duplicate entries
**/
public function isNotified($aData) {
$data = json_encode($aData);
$stmt = $this->mysqli->prepare("SELECT id FROM $this->table WHERE data = ? AND active = 1 LIMIT 1");
if ($stmt && $stmt->bind_param('s', $data) && $stmt->execute() && $stmt->store_result() && $stmt->num_rows == 1)
return true;
// Catchall
// Does not seem to have a notification set
$this->setErrorMessage("Unable to run query: " . $this->mysqli->error);
return false;
}
/**
* Get all active notifications
**/
public function getAllActive() {
$stmt =$this->mysqli->prepare("SELECT id, data FROM $this->table WHERE active = 1 LIMIT 1");
if ($stmt && $stmt->execute() && $result = $stmt->get_result())
return $result->fetch_all(MYSQLI_ASSOC);
// Catchall
return false;
}
/**
* Add a new notification to the table
* @param type string Type of the notification
* @return bool
**/
public function addNotification($type, $data) {
// Store notification data as json
$data = json_encode($data);
$stmt = $this->mysqli->prepare("INSERT INTO $this->table (type, data, active) VALUES (?,?,1)");
if ($stmt && $stmt->bind_param('ss', $type, $data) && $stmt->execute())
return true;
$this->debug->append("Failed to add notification for $type with $data: " . $this->mysqli->error);
$this->setErrorMessage("Unable to add new notification");
return false;
}
}
$notification = new Notification();
$notification->setDebug($debug);
$notification->setMysql($mysqli);
$notification->setSmarty($smarty);
$notification->setConfig($config);

View File

@ -486,6 +486,7 @@ class User {
}
$smarty->assign('TOKEN', $token);
$smarty->assign('USERNAME', $username);
$smarty->assign('SUBJECT', 'Password Reset Request');
$smarty->assign('WEBSITENAME', $this->config['website']['name']);
$headers = 'From: Website Administration <' . $this->config['website']['email'] . ">\n";
$headers .= "MIME-Version: 1.0\n";

View File

@ -43,17 +43,60 @@ class Worker {
public function updateWorkers($account_id, $data) {
$this->debug->append("STA " . __METHOD__, 4);
$username = $this->user->getUserName($account_id);
$iFailed = 0;
foreach ($data as $key => $value) {
// Prefix the WebUser to Worker name
$value['username'] = "$username." . $value['username'];
$stmt = $this->mysqli->prepare("UPDATE $this->table SET password = ?, username = ? WHERE account_id = ? AND id = ?");
if ($this->checkStmt($stmt)) {
if (!$stmt->bind_param('ssii', $value['password'], $value['username'], $account_id, $key)) return false;
if (!$stmt->execute()) return false;
$stmt->close();
}
$stmt = $this->mysqli->prepare("UPDATE $this->table SET password = ?, username = ?, monitor = ? WHERE account_id = ? AND id = ?");
if ( ! ( $this->checkStmt($stmt) && $stmt->bind_param('ssiii', $value['password'], $value['username'], $value['monitor'], $account_id, $key) && $stmt->execute()) )
$iFailed++;
}
return true;
if ($iFailed == 0)
return true;
// Catchall
$this->setErrorMessage('Failed to update ' . $iFailed . ' worker.');
return false;
}
/**
* Fetch all IDLE workers that have monitoring enabled
* @param none
* @return data array Workers in IDLE state and monitoring enabled
**/
public function getAllIdleWorkers() {
$this->debug->append("STA " . __METHOD__, 4);
$stmt = $this->mysqli->prepare("
SELECT account_id, id, username
FROM " . $this->table . "
WHERE monitor = 1 AND ( SELECT SIGN(COUNT(id)) FROM " . $this->share->getTableName() . " WHERE username = $this->table.username AND time > DATE_SUB(now(), INTERVAL 10 MINUTE)) = 0");
if ($this->checkStmt($stmt) && $stmt->execute() && $result = $stmt->get_result())
return $result->fetch_all(MYSQLI_ASSOC);
// Catchall
$this->setErrorMessage("Unable to fetch IDLE, monitored workers");
echo $this->mysqli->error;
return false;
}
/**
* Fetch a specific worker and its status
* @param id int Worker ID
* @return mixed array Worker details
**/
public function getWorker($id) {
$this->debug->append("STA " . __METHOD__, 4);
$stmt = $this->mysqli->prepare("
SELECT id, username, password, monitor,
( SELECT SIGN(COUNT(id)) FROM " . $this->share->getTableName() . " WHERE username = $this->table.username AND time > DATE_SUB(now(), INTERVAL 10 MINUTE)) AS active,
( SELECT ROUND(COUNT(id) * POW(2, " . $this->config['difficulty'] . ")/600/1000) FROM " . $this->share->getTableName() . " WHERE username = $this->table.username AND time > DATE_SUB(now(), INTERVAL 10 MINUTE)) AS hashrate
FROM $this->table
WHERE id = ?
");
if ($this->checkStmt($stmt) && $stmt->bind_param('i', $id) && $stmt->execute() && $result = $stmt->get_result())
return $result->fetch_assoc();
// Catchall
echo $this->mysqli->error;
return false;
}
/**
@ -64,7 +107,7 @@ class Worker {
public function getWorkers($account_id) {
$this->debug->append("STA " . __METHOD__, 4);
$stmt = $this->mysqli->prepare("
SELECT id, username, password,
SELECT id, username, password, monitor,
( SELECT SIGN(COUNT(id)) FROM " . $this->share->getTableName() . " WHERE username = $this->table.username AND time > DATE_SUB(now(), INTERVAL 10 MINUTE)) AS active,
( SELECT ROUND(COUNT(id) * POW(2, " . $this->config['difficulty'] . ")/600/1000) FROM " . $this->share->getTableName() . " WHERE username = $this->table.username AND time > DATE_SUB(now(), INTERVAL 10 MINUTE)) AS hashrate
FROM $this->table

View File

@ -16,10 +16,10 @@ $smarty = new Smarty;
// Assign our local paths
$debug->append('Define Smarty Paths', 3);
$smarty->template_dir = 'templates/' . THEME . '/';
$smarty->compile_dir = 'templates/compile/';
$smarty->template_dir = BASEPATH . 'templates/' . THEME . '/';
$smarty->compile_dir = BASEPATH . 'templates/compile/';
// Optional smarty caching, check Smarty documentation for details
$smarty->caching = $config['cache'];
$smarty->cache_dir = "templates/cache";
$smarty->cache_dir = BASEPATH . "templates/cache";
?>

View File

@ -0,0 +1,9 @@
<html>
<body>
<p>One of your workers is currently IDLE: {$DATA.username}</p>
<p>Since monitoring is enabled for this worker, this notification was sent.</p>
<p>Please check your workers!</p>
<br/>
<br/>
</body>
</html>

View File

@ -1 +1 @@
[ {$WEBSITENAME} ] Password Reset Request
[ {$WEBSITENAME} ] {$SUBJECT}

View File

@ -10,6 +10,7 @@
<td>Worker Name</td>
<td>Password</td>
<td class="center">Active</td>
<td class="center">Monitor</td>
<td class="right">Khash/s</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
@ -20,6 +21,7 @@
<td{if $WORKERS[worker].active} style="color: orange"{/if}>{$username.0|escape}.<input name="data[{$WORKERS[worker].id}][username]" value="{$username.1|escape}" size="10" /></td>
<td><input type="text" name="data[{$WORKERS[worker].id}][password]" value="{$WORKERS[worker].password|escape}" size="10"></td>
<td class="center"><img src="{$PATH}/images/{if $WORKERS[worker].active}success{else}error{/if}.gif" /></td>
<td class="center"><input type="checkbox" name="data[{$WORKERS[worker].id}][monitor]" value="1" {if $WORKERS[worker].monitor}checked{/if} /></td>
<td class="right">{$WORKERS[worker].hashrate|number_format}</td>
<td align="right"><a href="{$smarty.server.PHP_SELF}?page={$smarty.request.page|escape}&action={$smarty.request.action|escape}&do=delete&id={$WORKERS[worker].id|escape}"><button style="padding:5px" type="button">Delete</button></a></td>
</tr>

View File

@ -3,7 +3,7 @@
-- http://www.phpmyadmin.net
--
-- Host: localhost
-- Generation Time: Jun 06, 2013 at 09:01 PM
-- Generation Time: Jun 07, 2013 at 03:39 PM
-- Server version: 5.5.31-0ubuntu0.13.04.1
-- PHP Version: 5.4.9-4ubuntu2
@ -65,7 +65,24 @@ CREATE TABLE IF NOT EXISTS `blocks` (
PRIMARY KEY (`id`),
UNIQUE KEY `height` (`height`,`blockhash`),
KEY `time` (`time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Discovered blocks persisted from Litecoin Service';
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Discovered blocks persisted from Litecoin Service';
-- --------------------------------------------------------
--
-- Table structure for table `notifications`
--
CREATE TABLE IF NOT EXISTS `notifications` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`type` varchar(25) NOT NULL,
`data` varchar(255) NOT NULL,
`active` tinyint(1) NOT NULL DEFAULT '1',
`time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `active` (`active`),
KEY `data` (`data`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- --------------------------------------------------------
@ -173,7 +190,7 @@ CREATE TABLE IF NOT EXISTS `transactions` (
KEY `block_id` (`block_id`),
KEY `account_id` (`account_id`),
KEY `type` (`type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;