1
0
Fork 0
mirror of https://github.com/Oreolek/ifhub.club.git synced 2024-05-19 17:28:23 +03:00

Модуль профилирования: только основной функционал ведения лога, импорта лога в базу данных и выборке данных из базы.

This commit is contained in:
Alexey Kachayev 2009-12-16 17:22:29 +00:00
parent d27e4429ff
commit fd0339b17e
13 changed files with 627 additions and 8 deletions

View file

@ -0,0 +1,154 @@
<?php
/*-------------------------------------------------------
*
* LiveStreet Engine Social Networking
* Copyright © 2008 Mzhelskiy Maxim
*
*--------------------------------------------------------
*
* Official site: www.livestreet.ru
* Contact e-mail: rus.engine@gmail.com
*
* GNU General Public License, version 2:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
---------------------------------------------------------
*/
/**
* Обрабатывает вывод отчетов профилирования
*
*/
class ActionProfiler extends Action {
/**
* Текущий юзер
*
* @var unknown_type
*/
protected $oUserCurrent=null;
/**
* Инициализация
*
* @return unknown
*/
public function Init() {
/**
* Проверяем авторизован ли юзер
*/
if (!$this->User_IsAuthorization()) {
$this->Message_AddErrorSingle($this->Lang_Get('not_access'));
return Router::Action('error');
}
/**
* Получаем текущего юзера
*/
$this->oUserCurrent=$this->User_GetUserCurrent();
/**
* Проверяем является ли юзер администратором
*/
if (!$this->oUserCurrent->isAdministrator()) {
$this->Message_AddErrorSingle($this->Lang_Get('not_access'));
return Router::Action('error');
}
$this->SetDefaultEvent('report');
}
protected function RegisterEvent() {
$this->AddEvent('report','EventReport');
}
/**********************************************************************************
************************ РЕАЛИЗАЦИЯ ЭКШЕНА ***************************************
**********************************************************************************
*/
protected function EventReport() {
/**
* Обработка удаления отчетов профайлера
*/
if (isPost('submit_report_delete')) {
$this->Security_ValidateSendForm();
$aReportsId=getRequest('report_del');
if (is_array($aReportsId)) {
if($this->Profiler_DeleteEntryByRequestId(array_keys($aReportsId))) {
$this->Message_AddNotice($this->Lang_Get('profiler_report_delete_success'), $this->Lang_Get('attention'));
} else {
$this->Message_AddError($this->Lang_Get('profiler_report_delete_error'), $this->Lang_Get('error'));
}
}
}
/**
* Если вызвана обработка upload`а логов в базу данных
*/
if(getRequest('submit_profiler_import') and getRequest('profiler_date_import')) {
$iCount = @$this->Profiler_UploadLog(date('Y-m-d H:i:s',strtotime(getRequest('profiler_date_import'))));
if(!is_null($iCount)) {
$this->Message_AddNotice($this->Lang_Get('profiler_import_report_success',array('count'=>$iCount)), $this->Lang_Get('attention'));
} else {
$this->Message_AddError($this->Lang_Get('profiler_import_report_error'), $this->Lang_Get('error'));
}
}
/**
* Составляем фильтр для просмотра отчетов
*/
$aFilter=$this->BuildFilter();
/**
* Передан ли номер страницы
*/
$iPage=preg_match("/^page(\d+)$/i",$this->getParam(0),$aMatch) ? $aMatch[1] : 1;
/**
* Получаем список отчетов
*/
$aResult=$this->Profiler_GetReportsByFilter($aFilter,$iPage,Config::Get('module.profiler.per_page'));
$aReports=$aResult['collection'];
/**
* Формируем постраничность
*/
$aPaging=$this->Viewer_MakePaging(
$aResult['count'],$iPage,Config::Get('module.profiler.per_page'),4,
Router::GetPath('profiler').$this->sCurrentEvent,
array_intersect_key(
$_REQUEST,
array_fill_keys(
array('start','end','request_id','time_full'),
''
)
)
);
/**
* Загружаем переменные в шаблон
*/
$this->Viewer_Assign('aPaging',$aPaging);
$this->Viewer_Assign('aReports',$aReports);
$this->Viewer_Assign('aDatabaseStat',($aData=$this->Profiler_GetDatabaseStat())?$aData:array('max_date'=>'','count'=>''));
$this->Viewer_AddBlock('right','actions/ActionProfiler/sidebar.tpl');
$this->Viewer_AddHtmlTitle($this->Lang_Get('profiler_report_page_title'));
}
/**
* Формирует из REQUEST массива фильтр для отбора отчетов
*
* @return array
*/
protected function BuildFilter() {
return array();
}
/**
* Завершение работы Action`a
*
*/
public function EventShutdown() {
}
}
?>

View file

@ -0,0 +1,162 @@
<?php
/*-------------------------------------------------------
*
* LiveStreet Engine Social Networking
* Copyright © 2008 Mzhelskiy Maxim
*
*--------------------------------------------------------
*
* Official site: www.livestreet.ru
* Contact e-mail: rus.engine@gmail.com
*
* GNU General Public License, version 2:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
---------------------------------------------------------
*/
set_include_path(get_include_path().PATH_SEPARATOR.dirname(__FILE__));
require_once('mapper/Profiler.mapper.class.php');
/**
* Модуль статических страниц
*
*/
class LsProfiler extends Module {
/**
* Меппер для сохранения логов в базу данных и формирования выборок по данным из базы
*
* @var Mapper_Profiler
*/
protected $oMapper;
/**
* Хендлер открытого файла лога
*
* @var resource
*/
protected $hLog;
/**
* @var string
*/
protected $sDataDelimiter = "\t";
/**
* Инициализация модуля
*/
public function Init() {
$this->oMapper=new Mapper_Profiler($this->Database_GetConnect());
$this->hLog = @fopen(Config::Get('path.root.server').'/logs/'.Config::Get('sys.logs.profiler_file'),'r+');
}
/**
* Добавить новую запись в базу данных
*
* @param ProfilerEntity_Entry $oEntry
* @return bool
*/
public function AddEntry(ProfilerEntity_Entry $oEntry) {
return $this->oMapper->AddEntry($oEntry);
}
/**
* Читает из лог-файла записи
*
* @param string $sPath
* @return ProfilerEntity_Entry
*/
public function ReadEntry() {
/**
* Если хендлер не определен, или лог закончен, вовращаем null
*/
if(!$this->hLog or feof($this->hLog)) return null;
/**
* Читаем следующую строку и формируем объект Entry
*/
$sLine=fgets($this->hLog);
if(!$sLine) return null;
$aTime = array();
list(
$aTime['request_date'],$aTime['request_id'],$aTime['time_full'],
$aTime['time_start'],$aTime['time_stop'],$aTime['time_id'],
$aTime['time_pid'],$aTime['time_name'],$aTime['time_comment']
)=explode($this->sDataDelimiter,$sLine,9);
return Engine::GetEntity('Profiler_Entry',$aTime);
}
/**
* Выгружает записи из лога в базу данных
*
* @param string $sDateStart
* @param string $sPath
* @return bool|int
*/
public function UploadLog($sDateStart,$sPath=null) {
if($sPath) $this->hLog = @fopen($sPath,'r+');
if(!$this->hLog) return null;
rewind($this->hLog);
$iCount=0;
while($oEntry=$this->ReadEntry()) {
if(strtotime($oEntry->getDate())>strtotime($sDateStart)){
$this->AddEntry($oEntry);
$iCount++;
}
unset($oEntry);
}
return $iCount;
}
/**
* Получает дату последней записи профайлера в базе данных
*
* @return string
*/
public function GetDatabaseStat() {
return $this->oMapper->GetDatabaseStat();
}
/**
* Очищает файл лога
*
* @return bool
*/
public function EraseLog() {
}
/**
* Получает записи профайлера из базы данных, группированных по уровню "Report"
* TODO: Реализовать кеширование данных
*
* @param array $aFilter
* @param int $iPage
* @param int $iPerPage
* @return array
*/
public function GetReportsByFilter($aFilter,$iPage,$iPerPage) {
$data=array(
'collection'=>$this->oMapper->GetReportsByFilter($aFilter,$iCount,$iPage,$iPerPage),
'count'=>$iCount
);
return $data;
}
/**
* Удаление отчетов из базы данных
* TODO: Добавить обработку кеша данных
*
* @param array|int $aIds
* @return bool
*/
public function DeleteEntryByRequestId($aIds) {
if(!is_array($aIds)) $aIds = array($aIds);
return $this->oMapper->DeleteEntryByRequestId($aIds);
}
}
?>

View file

@ -0,0 +1,77 @@
<?php
/*-------------------------------------------------------
*
* LiveStreet Engine Social Networking
* Copyright © 2008 Mzhelskiy Maxim
*
*--------------------------------------------------------
*
* Official site: www.livestreet.ru
* Contact e-mail: rus.engine@gmail.com
*
* GNU General Public License, version 2:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
---------------------------------------------------------
*/
class ProfilerEntity_Entry extends Entity
{
public function getRequestId() {
return $this->_aData['request_id'];
}
public function getDate() {
return $this->_aData['request_date'];
}
public function getTimeFull() {
return $this->_aData['time_full'];
}
public function getTimeStart() {
return $this->_aData['time_start'];
}
public function getTimeStop() {
return $this->_aData['time_stop'];
}
public function getId() {
return $this->_aData['time_id'];
}
public function getPid() {
return $this->_aData['time_pid'];
}
public function getName() {
return $this->_aData['time_name'];
}
public function getComment() {
return $this->_aData['time_comment'];
}
public function setRequestId($data) {
$this->_aData['request_id']=$data;
}
public function setDate($data) {
$this->_aData['request_date']=$data;
}
public function setTimeFull($data) {
$this->_aData['time_full']=$data;
}
public function setTimeStart($data) {
$this->_aData['time_start']=$data;
}
public function setTimeStop($data) {
$this->_aData['time_stop']=$data;
}
public function setId($data) {
$this->_aData['time_id']=$data;
}
public function setPid($data) {
$this->_aData['time_pid']=$data;
}
public function setName($data) {
$this->_aData['time_name']=$data;
}
public function setComment($data) {
$this->_aData['time_comment']=$data;
}
}
?>

View file

@ -0,0 +1,101 @@
<?php
/*-------------------------------------------------------
*
* LiveStreet Engine Social Networking
* Copyright © 2008 Mzhelskiy Maxim
*
*--------------------------------------------------------
*
* Official site: www.livestreet.ru
* Contact e-mail: rus.engine@gmail.com
*
* GNU General Public License, version 2:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
---------------------------------------------------------
*/
class Mapper_Profiler extends Mapper {
public function AddEntry(ProfilerEntity_Entry $oEntry) {
$sql = "INSERT IGNORE INTO ".Config::Get('db.table.profiler')."
(request_date,
request_id,
time_full,
time_start,
time_stop,
time_id,
time_pid,
time_name,
time_comment)
VALUES(?, ?, ?f, ?f, ?f, ?d, ?d, ?, ?)
";
return $this->oDb->query($sql,$oEntry->getDate(),$oEntry->getRequestId(),$oEntry->getTimeFull(),$oEntry->getTimeStart(),$oEntry->getTimeStop(),$oEntry->getId(),$oEntry->getPid(),$oEntry->getName(),$oEntry->getComment());
}
public function GetDatabaseStat() {
$sql = "
SELECT
MAX(request_date) as max_date,
COUNT(*) as count
FROM ".Config::Get('db.table.profiler') ."
";
if($aData = $this->oDb->selectRow($sql)) {
return $aData;
}
return null;
}
/**
* Возвращает список отчетов профайлера, сгруппированных по идентификатору вызова request_id
*
* @param array $aFilter
* @param int $iCount
* @param int $iCurrPage
* @param int $iPerPage
* @return array
*/
public function GetReportsByFilter($aFilter,&$iCount,$iCurrPage,$iPerPage) {
$sql = "
SELECT
DISTINCT request_id,
SUM(time_full) as time_full,
COUNT(time_id) as count_time_id,
MIN(request_date) as request_date
FROM ".Config::Get('db.table.profiler')."
WHERE 1
GROUP BY request_id
ORDER BY request_date asc
LIMIT ?d, ?d
";
if (
$aRows=$this->oDb->selectPage(
$iCount,
$sql,
($iCurrPage-1)*$iPerPage,
$iPerPage
)
) {
return $aRows;
}
return null;
}
/**
* Удаление записей из базы данных по уникальному ключу отчетов
*
* @param array|int $aIds
* @return bool
*/
public function DeleteEntryByRequestId($aIds) {
$sql = "
DELETE FROM ".Config::Get('db.table.profiler')."
WHERE request_id IN(?a)
";
return $this->oDb->query($sql,$aIds);
}
}
?>

View file

@ -106,6 +106,8 @@ $config['sys']['logs']['sql_query_file'] = 'sql_query.log'; // файл лога
$config['sys']['logs']['sql_error'] = true; // логировать или нет ошибки SQl
$config['sys']['logs']['sql_error_file'] = 'sql_error.log'; // файл лога ошибок SQL
$config['sys']['logs']['cron_file'] = 'cron.log'; // файл лога запуска крон-процессов
$config['sys']['logs']['profiler'] = false; // логировать или нет профилирование процессов
$config['sys']['logs']['profiler_file'] = 'profiler.log'; // файл лога профилирования процессов
/**
* Общие настройки
*/
@ -198,6 +200,8 @@ $config['module']['image']['topic']['round_corner'] = false;
// Модуль Security
$config['module']['security']['key'] = "livestreet_security_key"; // ключ сессии для хранения security-кода
$config['module']['security']['hash'] = "livestreet_security_key"; // "примесь" к строке, хешируемой в качестве security-кода
// Модуль Profiler
$config['module']['profiler']['per_page'] = 15; // Число profiler-отчетов на одну страницу
// Какие модули должны быть загружены на старте
$config['module']['autoLoad'] = array('Cache','Security','Session','Lang','User','Message');
@ -245,6 +249,7 @@ $config['db']['table']['country_user'] = '___db.table.prefix___country_us
$config['db']['table']['reminder'] = '___db.table.prefix___reminder';
$config['db']['table']['session'] = '___db.table.prefix___session';
$config['db']['table']['notify_task'] = '___db.table.prefix___notify_task';
$config['db']['table']['profiler'] = '___db.table.prefix___profiler';
$config['db']['tables']['engine'] = 'InnoDB'; // InnoDB или MyISAM
/**
@ -285,6 +290,7 @@ $config['router']['page']['link'] = 'ActionLink';
$config['router']['page']['question'] = 'ActionQuestion';
$config['router']['page']['blogs'] = 'ActionBlogs';
$config['router']['page']['search'] = 'ActionSearch';
$config['router']['page']['profiler'] = 'ActionProfiler';
// Глобальные настройки роутинга
$config['router']['config']['action_default'] = 'index';
$config['router']['config']['action_not_found'] = 'error';

View file

@ -64,8 +64,8 @@ class Router extends Object {
$this->ParseUrl();
$this->ExecAction();
$this->AssignVars();
$this->oEngine->Shutdown();
$this->Viewer_Display($this->oAction->GetTemplate());
$this->oEngine->Shutdown();
$this->Viewer_Display($this->oAction->GetTemplate());
}
/**
* Парсим URL

View file

@ -82,10 +82,14 @@ class ProfilerSimple {
}
if ($fp=fopen($this->sFileName,"a")) {
foreach ($this->aTimes as $aTime) {
if(!isset($aTime['time_pid'])) $aTime['time_pid']=0;
if(isset($aTime['time_comment']) and $aTime['time_comment']!='') {
$aTime['time_comment'] = preg_replace('/\s{1,}/',' ',$aTime['time_comment']);
}
$s=date('Y-m-d H:i:s')."\t{$aTime['request_id']}\t{$aTime['time_full']}\t{$aTime['time_start']}\t{$aTime['time_stop']}\t{$aTime['time_id']}\t{$aTime['time_pid']}\t{$aTime['time_name']}\t{$aTime['time_comment']}\r\n";
fwrite($fp,$s);
}
fclose($fp);
}
fclose($fp);
}
}

View file

@ -29,7 +29,7 @@ chdir(dirname(__FILE__));
require_once("./config/loader.php");
require_once(Config::Get('path.root.engine')."/classes/Engine.class.php");
$oProfiler=ProfilerSimple::getInstance(Config::Get('path.server.root').'/logs/profiler.log',false);
$oProfiler=ProfilerSimple::getInstance(Config::Get('path.root.server').'/logs/'.Config::Get('sys.logs.profiler_file'),Config::Get('sys.logs.profiler'));
$iTimeId=$oProfiler->Start('full_time');
$oRouter=Router::getInstance();
@ -82,4 +82,5 @@ if (Router::GetIsShowStats() and $oUser and $oUser->isAdministrator()) {
</tr>
</table>
</fieldset>
<p align="center">Profiler: <?php print (Config::Get('sys.logs.profiler')) ? 'On' : 'Off'; ?> | <a href="<?php echo Router::GetPath('profiler');?>">Profiler reports</a></p>
<?php }?>

View file

@ -135,4 +135,17 @@ ALTER TABLE `prefix_user` ADD `user_date_topic_last` DATETIME AFTER `user_dat
ALTER TABLE `prefix_user` DROP `user_date_topic_last`
ALTER TABLE `prefix_comment` ADD `target_parent_id` INT DEFAULT '0' NOT NULL AFTER `target_type` ;
ALTER TABLE `prefix_comment_online` ADD `target_parent_id` INT DEFAULT '0' NOT NULL AFTER `target_type` ;
ALTER TABLE `prefix_comment_online` ADD `target_parent_id` INT DEFAULT '0' NOT NULL AFTER `target_type` ;
CREATE TABLE `prefix_profiler` (
`request_date` DATETIME NOT NULL ,
`request_id` VARCHAR( 32 ) NOT NULL ,
`time_full` FLOAT( 9,6 ) NOT NULL ,
`time_start` FLOAT( 17,7 ) NOT NULL ,
`time_stop` FLOAT( 17,7 ) NOT NULL ,
`time_id` INT NOT NULL ,
`time_pid` INT NOT NULL ,
`time_name` VARCHAR( 250 ) NOT NULL ,
`time_comment` VARCHAR( 250 ) NOT NULL
);
ALTER TABLE `prefix_profiler` ADD PRIMARY KEY ( `request_id` , `time_id` ) ;

View file

@ -707,7 +707,29 @@ return array(
'talk_speaker_add_self' => 'Нельзя добавлять в участники себя',
'talk_not_found' => 'Разговор не найден',
'profiler_report_page_title' => 'Профилирование',
'profiler_reports_title' => 'Отчеты о профилировании',
'profiler_table_date' => 'Дата начала работы',
'profiler_table_time_full' => 'Время работы',
'profiler_table_count_id' => 'Количество профилей',
'profiler_report_delete' => 'Удалить',
'profiler_report_delete_confirm' => 'Вы уверены, что хотите удалить выбранные отчеты?',
'profiler_dbstat_title' => 'Информацию о базе данных',
'profiler_dbstat_count' => 'Всего записей',
'profiler_dbstat_max_date' => 'Последняя дата',
'profiler_import_label' => 'Импортировать начиная с',
'profiler_import_notice' => 'В любом читаемом формате, напрмиер, Y-m-d H:i',
'profiler_import_submit' => 'Импортировать в БД',
'profiler_filter_title' => 'Фильтр',
'profiler_report_delete_success' => 'Отчеты успешно удалены из базы данных',
'profiler_report_delete_error' => 'При удалении отчетов произошла ошибка',
'profiler_import_report_success' => 'Импортировано записей: %%count%%',
'profiler_import_report_error' => 'При импорте отчетов в базу данных произошла ошибка',
/**
* Рейтинг TOP
*/

View file

@ -0,0 +1,35 @@
{include file='header.tpl' noShowSystemMessage=false}
<div class="topic people top-blogs talk-table">
<h1>{$aLang.profiler_reports_title}</h1>
<form action="{router page='profiler'}" method="post" id="form_report_list">
<input type="hidden" name="security_ls_key" value="{$LIVESTREET_SECURITY_KEY}" />
<table>
<thead>
<tr>
<td width="20px"><input type="checkbox" name="" onclick="checkAllReport(this);"></td>
<td></td>
<td>{$aLang.profiler_table_date}</td>
<td align="center">{$aLang.profiler_table_time_full}</td>
<td align="center">{$aLang.profiler_table_count_id}</td>
</tr>
</thead>
<tbody>
{foreach from=$aReports item=oReport}
<tr>
<td><input type="checkbox" name="report_del[{$oReport.request_id}]" class="form_reports_checkbox"></td>
<td>+</td>
<td>{date_format date=$oReport.request_date}</td>
<td align="center">{$oReport.time_full}</td>
<td align="center">{$oReport.count_time_id}</td>
</tr>
{/foreach}
</tbody>
</table>
<input type="submit" name="submit_report_delete" value="{$aLang.profiler_report_delete}" onclick="return ($$('.form_reports_checkbox').length==0)?false:confirm('{$aLang.profiler_report_delete_confirm}');">
</form>
</div>
{include file='paging.tpl' aPaging=`$aPaging`}
{include file='footer.tpl'}

View file

@ -0,0 +1,35 @@
<div class="block blogs">
<div class="tl"><div class="tr"></div></div>
<div class="cl"><div class="cr">
<h1>{$aLang.profiler_dbstat_title}</h1>
<form action="{router page='profiler'}" method="POST" name="profiler_import_form">
<p>{$aLang.profiler_dbstat_count}: {$aDatabaseStat.count}<br />
{$aLang.profiler_dbstat_max_date}: {$aDatabaseStat.max_date}</p>
<p>
<label for="profiler_date_import">{$aLang.profiler_import_label}:</label><br />
<input type="text" id="profiler_date_import" name="profiler_date_import" value="{if $_aRequest.date_import}{$_aRequest.date_import}{else}{$aDatabaseStat.max_date}{/if}" class="w100p" /><br />
<span class="form_note">{$aLang.profiler_import_notice}</span>
</p>
<p class="buttons">
<input type="submit" name="submit_profiler_import" value="{$aLang.profiler_import_submit}"/>
</p>
</form>
<br/>
</div></div>
<div class="bl"><div class="br"></div></div>
</div>
<div class="block blogs">
<div class="tl"><div class="tr"></div></div>
<div class="cl"><div class="cr">
<h1>{$aLang.profiler_filter_title}</h1>
Функционал в разработке
</div></div>
<div class="bl"><div class="br"></div></div>
</div>

View file

@ -60,10 +60,19 @@ function checkAllTalk(checkbox) {
chk.checked=true;
} else {
chk.checked=false;
}
}
});
}
function checkAllReport(checkbox) {
$$('.form_reports_checkbox').each(function(chk){
if (checkbox.checked) {
chk.checked=true;
} else {
chk.checked=false;
}
});
}
function showImgUploadForm() {
if (Browser.Engine.trident) {