open-source-search-engine/PageStatsdb.cpp
2014-09-17 16:58:24 -06:00

737 lines
18 KiB
C++

#include "gb-include.h"
#define X_DISPLAY_MISSING 1
//#include <plotter.h>
//#include <fstream.h>
#include <math.h>
//#include "CollectionRec.h"
#include "Pages.h"
#include "Statsdb.h"
#include "Hostdb.h"
#include "SafeBuf.h"
#include "SafeList.h"
#include "Threads.h"
class StateStatsdb {
public:
TcpSocket *m_socket;
HttpRequest m_request;
SafeBuf m_sb2;
// Original timestamp request data
time_t m_startDate;
time_t m_endDate;
long m_datePeriod;
long m_dateUnits;
// Timestamp data modified for the request
time_t m_startDateR;
time_t m_endDateR;
// For the auto-update AJAX script
bool m_autoUpdate;
long m_samples;
// Misc. request data
long m_hostId;
// Request & build flags
bool m_dateLimit;
bool m_dateCustom;
bool m_cacti;
bool m_now;
long m_niceness;
};
static time_t genDate( char *date, long dateLen ) ;
static void sendReply ( void *st ) ;
// . returns false if blocked, otherwise true
// . sets g_errno on error
bool sendPageGraph ( TcpSocket *s, HttpRequest *r ) {
char *cgi;
long cgiLen;
StateStatsdb *st;
try { st = new StateStatsdb; }
catch ( ... ) {
g_errno = ENOMEM;
log(LOG_INFO, "PageStatsdb: failed to allocate state memory.");
return true;
}
mnew( st, sizeof(StateStatsdb), "PageStatsdb" );
st->m_niceness = MAX_NICENESS;
st->m_socket = s;
//st->m_request = *r;
st->m_request.copy ( r );
// hostId must be one of the following:
// 0-n - a valid hostId
// -1 - a sample (subset) of the hosts
// -2 - all hosts
// -3 - this host
st->m_hostId = r->getLong( "host", -3 );
if ( st->m_hostId == -3 )
st->m_hostId = g_hostdb.getMyHostId();
// If we are pulling from multiple hosts, are we merging
// the data into a single graph?
// TODO:
// - Make sure this always happens. Now our only concern
// is how many stats we will be drawing.
//st->m_mergeResults = (bool )r->getLong( "merge_results" , 1 );
// get session parameters
st->m_cacti = (bool )r->getLong( "cacti" , 0 );
// get date parameters
cgi = r->getString( "sdate" , &cgiLen , NULL );
st->m_startDate = genDate( cgi, cgiLen );
cgi = r->getString( "edate" , &cgiLen , NULL );
st->m_endDate = genDate( cgi, cgiLen );
st->m_dateCustom = (bool)r->getLong( "custom", 0 );
// default to 10 hours, i would do 1 day except that there are
// some bugs that mess up the display a lot when i do that
st->m_datePeriod = r->getLong( "date_period" , 300 );//36000 );
st->m_dateUnits = r->getLong( "date_units" , 1 );//SECS_PER_MIN
st->m_now = (bool)r->getLong( "date_now" , 1 );
st->m_autoUpdate = (bool)r->getLong( "auto_update" , 0 );
// # samples in moving average
st->m_samples = r->getLong( "samples" , 300 );
//if ( st->m_columns < MIN_COLUMNS || st->m_columns > MAX_COLUMNS )
// st->m_columns = DEF_COLUMNS;
if ( st->m_now )
st->m_startDate = (time_t)getTimeGlobalNoCore();
st->m_startDateR = st->m_startDate;
st->m_endDateR = st->m_endDate;
if ( ! st->m_dateCustom ) {
st->m_endDateR = st->m_startDateR - ( st->m_datePeriod *
st->m_dateUnits );
st->m_endDate = st->m_endDateR;
}
//
// this is no longer a gif, but an html graph in g_statsdb.m_sb
//
if ( ! g_statsdb.makeGIF ( st->m_endDateR ,
st->m_startDateR ,
st->m_samples ,
&st->m_sb2 ,
st ,
sendReply ) )
return false;
// if we didn't block call it ourselves directly
sendReply ( st );
return true;
}
static void writeControls ( SafeBuf *buf, StateStatsdb *st ) ;
void sendReply ( void *state ) {
StateStatsdb *st = (StateStatsdb *)state;
if ( g_errno ) {
g_httpServer.sendErrorReply(st->m_socket,
500,mstrerror(g_errno));
return;
}
TcpSocket *s = st->m_socket;
SafeBuf buf( 1024*32 , "tmpbuf0" );
SafeBuf tmpBuf( 1024 , "tmpbuf1" );
//
// take these out until we need them!
//
/*
// print the top of the page
tmpBuf.safePrintf(
//"<style type=\"text/css\">"
//"@import url(/styles/statsdb.css);</style>\n"
"<script type=\"text/javascript\" "
"src=\"/scripts/statsdb.js\"></script>\n"
"<!-- DHTML Calendar -->"
"<style type=\"text/css\">"
"@import url(/jsc/calendar-win2k-1.css);"
"</style>\n"
"<script type=\"text/javascript\" "
"src=\"/jsc/calendar.js\"></script>\n"
"<script type=\"text/javascript\" "
"src=\"/jsc/lang/calendar-en.js\"></script>\n"
"<script type=\"text/javascript\" "
"src=\"/jsc/calendar-setup.js\"></script>\n"
);
*/
// make the query string
char qs[1024];
sprintf(qs,"&date_period=%li&date_units=%li&samples=%li",
st->m_datePeriod,
st->m_dateUnits,
st->m_samples);
// print standard header
g_pages.printAdminTop ( &buf , st->m_socket , &st->m_request ,
qs );
buf.cat ( tmpBuf );
//g_pages.printAdminTop2 ( &buf , st->m_socket , &st->m_request, NULL ,
// tmpBuf.getBufStart(), tmpBuf.length() );
// Debug print of CGI parameters and errors
char startTimeStr[30];
char endTimeStr[30];
strncpy( startTimeStr, ctime( &st->m_startDate ), 30 );
strncpy( endTimeStr, ctime( &st->m_endDate ), 30 );
buf.safePrintf(
"<b>Graph of various query performance statistics.</b>"
"<br>"
"<br>"
);
buf.safePrintf("<center>\n");
if ( ! g_conf.m_useStatsdb )
buf.safePrintf("<font color=red><b>Statsdb disabled. "
"Turn on in the master controls.</b>"
"</font>\n" );
buf.safePrintf("<table %s>\n",TABLE_STYLE);
buf.safePrintf("<tr><td bgcolor=#%s>"
"<center>",LIGHT_BLUE);
/////////////////////////
//
// insert the div graph here
//
/////////////////////////
buf.cat ( g_statsdb.m_gw );
// purge it
g_statsdb.m_gw.purge();
g_statsdb.m_dupTable.reset();
//"<img src=\"/stats%li.gif\" height=%li width=%li "
//"border=\"0px\">"
//st->m_hostId,
//g_statsdb.getImgHeight(),
//g_statsdb.getImgWidth());
buf.safePrintf("</center>"
//"class=\"statsdb_image\">"
"</td></tr>\n");
// the map key
buf.safePrintf("<tr><td>");
buf.cat ( st->m_sb2 );
buf.safePrintf("</td></tr>\n");
buf.safePrintf( "</table>\n" );
buf.safePrintf("</center>");
// write the controls section of the page
writeControls( &buf, st );
// print the bottom of the page
g_pages.printAdminBottom2( &buf );
g_errno = 0;
mdelete ( st, sizeof(StateStatsdb), "PageStatsdb" );
delete st;
g_httpServer.sendDynamicPage ( s, buf.getBufStart(), buf.length() );
}
void writeControls ( SafeBuf *buf, StateStatsdb *st ) {
// Print the controls.
struct tm *tmBuild;
buf->safePrintf (
"<div id=\"control\">\n"
"<div id=\"controls\" class=\"show\">\n"
);
g_pages.printFormTop( buf, &st->m_request );
// Print the start date (most recent date)
buf->safePrintf (
"<div id=\"time_limits\" class=\"section\">\n"
//"<div class=\"section_header\">"
//"<span class=\"section_title\">Time Selection"
//"</span>"
//"</div>\n"
"<table>\n"
"<tr>"
"<td>Moving Average Samples</td>"
"<td>"
"<input type=text name=samples length=20 value=\"%li\">"
"</td>"
"</tr>"
"<tr class=\"show\" id=\"e_date_start\">\n"
"<td> Start </td>\n"
"<td>\n",
st->m_samples
);
tmBuild = localtime( &st->m_startDate );
buf->safePrintf (
"<input type=\"text\" name=\"sdate\" id=\"s_date_field\" "
"value=\"%02d/%02d/%04d %02d:%02d\" />\n",
tmBuild->tm_mon + 1, tmBuild->tm_mday, tmBuild->tm_year + 1900,
tmBuild->tm_hour, tmBuild->tm_min
);
buf->safePrintf (
"<button type=\"reset\" name=\"strigger\" "
"id=\"s_date_trigger\">...</button>\n"
"<script type=\"text/javascript\">\n"
"Calendar.setup({\n"
"inputField : \"s_date_field\" ,\n"
"ifFormat : \"%%m/%%d/%%Y %%H:%%M\" ,\n"
"showsTime : true ,\n"
"button : \"s_date_trigger\" ,\n"
"singleClick : false ,\n"
"step : 1 ,\n"
"timeFormat : \"24\"\n"
"});\n"
"</script>\n"
"</td>\n"
"</tr>\n"
);
// "start at current time"
buf->safePrintf("<tr><td>");
buf->safePrintf("<input type=\"checkbox\" name=\"date_now\" "
"id=\"date_now_trigger\" value=\"1\" "
"onclick=\"javascript:st_date_now()\"");
if ( st->m_now ) buf->safePrintf( " checked" );
buf->safePrintf (
" /> Start at Current Time\n"
"</td>\n"
"</tr>\n");
buf->safePrintf(
"<tr class=\"show\" id=\"e_date_period\">\n"
"<td> Go back </td>\n"
"<td>\n"
"<input type=\"text\" name=\"date_period\" value=\"%i\" "
"size=\"3\" />\n"
"<select name=\"date_units\">\n"
"<option value=1" ,
(int)st->m_datePeriod);
if ( st->m_dateUnits == 1 ) // SECS_PER_MINUTE )
buf->safePrintf( " selected " );
buf->safePrintf(">seconds</option>\n");
buf->safePrintf("<option value=60");
// Print the time unit selection drop down menu.
// - gives the user to go back from the selected
// time by x number of minutes/hours/days
if ( st->m_dateUnits == 60 ) // SECS_PER_MINUTE )
buf->safePrintf( " selected " );
buf->safePrintf (
">minute(s)</option>\n"
"<option value=\"%d\"",
(int)3600);//SECS_PER_HOUR
if ( st->m_dateUnits == 3600 ) // SECS_PER_HOUR )
buf->safePrintf( " selected " );
buf->safePrintf (
">hour(s)</option>\n"
"<option value=\"%d\"",
3600*24);//(int)SECS_PER_DAY
if ( st->m_dateUnits == 3600*24 )// SECS_PER_DAY )
buf->safePrintf( " selected " );
// Print the end date selector.
buf->safePrintf (
">day(s)</option>\n"
"</select>\n"
"</td>\n"
"</tr>\n" );
buf->safePrintf(
"<tr class=\"show\" id=\"e_date_custom\">\n"
"<td> Go back to </td>\n"
"<td>\n"//,
//st->m_datePeriod
);
tmBuild = localtime( &st->m_endDate );
buf->safePrintf (
"<input type=\"text\" name=\"edate\" id=\"e_date_field\" "
"value=\"%02d/%02d/%04d %02d:%02d\" />\n",
tmBuild->tm_mon + 1, tmBuild->tm_mday, tmBuild->tm_year + 1900,
tmBuild->tm_hour, tmBuild->tm_min
);
buf->safePrintf (
"<button type=\"reset\" name=\"etrigger\" "
"id=\"e_date_trigger\">...</button>\n"
"<script type=\"text/javascript\">\n"
"Calendar.setup({\n"
"inputField : \"e_date_field\" ,\n"
"ifFormat : \"%%m/%%d/%%Y %%H:%%M\" ,\n"
"showsTime : true ,\n"
"button : \"e_date_trigger\" ,\n"
"singleClick : false ,\n"
"step : 1 ,\n"
"timeFormat : \"24\"\n"
"});\n"
"</script>\n"
"</td>\n"
"</tr>\n"
//"<tr class=\"show\" id=\"e_custom_trigger\">\n"
//"<td colspan=\"2\">\n"
//"<input type=\"checkbox\" name=\"custom\" "
//"id=\"date_custom_trigger\" value=\"1\" "
//"onclick=\"javascript:st_date_custom()\""
);
/*
// This checkbox enables the end date dialog, which will
// be used rather than the time period dialog.
if ( st->m_dateCustom ) buf->safePrintf( " checked" );
buf->safePrintf (
" /> Custom End Time\n"
"</td>\n"
"</tr>\n"
"<tr class=\"show\" id=\"e_show_trigger\">\n"
"<td colspan=\"2\">\n"
"<input type=\"checkbox\" name=\"date_now\" "
"id=\"date_now_trigger\" value=\"1\" "
"onclick=\"javascript:st_date_now()\""
);
if ( st->m_now ) buf->safePrintf( " checked" );
buf->safePrintf (
" /> Start at Current Time\n"
"</td>\n"
"</tr>\n");
*/
// This checkbox pulls the current time from the server,
// and uses it for the request. Can only use the time
// period dialog when this is selected.
buf->safePrintf (
"</table>\n"
);
buf->safePrintf ("</div>\n");
/*
buf->safePrintf (
"<div id=\"stat_selection\" class=\"section\">\n"
"<div class=\"section_header\">"
"<span class=\"section_title\">Stat Selection"
"</span></div>\n"
"<table>\n"
"<tr>\n"
"<td> Host(s) </td>\n"
"<td>\n"
"<select name=\"host\"> \n"
"<option value=\"-3\""
);
// Print the host selector.
if ( st->m_hostId == -3 ) buf->safePrintf ( " selected " );
buf->safePrintf ( ">This Host</option>\n<option value=\"-2\"");
if ( st->m_hostId == -2 ) buf->safePrintf ( " selected " );
buf->safePrintf ( ">All</option>\n<option value=\"-1\"");
if ( st->m_hostId == -1 ) buf->safePrintf ( " selected " );
buf->safePrintf ( ">Sample</option>\n");
for (long i = 0; i < g_hostdb.getNumHosts(); i++) {
buf->safePrintf ( "<option value=\"%li\"", i );
if ( st->m_hostId == i ) buf->safePrintf ( " selected " );
buf->safePrintf ( ">Host %li</option>\n", i );
}
// Print the statistic selector.
buf->safePrintf (
"</select>\n"
"</td>\n"
"</tr>\n"
//"<tr class=\"show\" id=\"e_stat_single\">\n"
//"<td> Statistic </td>\n"
//"<td>\n"
//"<select name=\"stat\"> \n"
//"<option value=\"-1\""
);
if ( st->m_statIndex == -1 ) buf->safePrintf( " selected " );
buf->safePrintf (
">List (one snapshot)</option>\n"
);
for (int32_t i = 0; (uint32_t)i < g_statsdb.m_numStats; i++) {
buf->safePrintf ( "<option value=\"%i\"", i );
if ( st->m_statIndex == i ) buf->safePrintf ( " selected " );
buf->safePrintf ( ">%s</option>\n",
g_statsdb.m_stats[i].m_title );
}
buf->safePrintf (
"</select>"
"</td>\n"
"</tr>\n"
"<tr class=\"show\" id=\"e_multi_trigger\">\n"
"<td colspan=\"2\">\n"
"<input type=\"checkbox\" name=\"multi_stat\" "
"id=\"multi_stat_trigger\" value=\"1\" "
"onclick=\"javascript:st_multi_stat()\""
);
if ( st->m_multiStat ) buf->safePrintf( " checked" );
buf->safePrintf (
" /> Select Multiple Stats\n"
"</td>\n"
"</tr>\n"
"<tr class=\"show\" id=\"e_stat_multi\">\n"
"<td colspan=\"2\">\n"
"<select name=\"stats\" multiple size=\"5\"> \n"
);
for (int32_t i = 0; (uint32_t)i < g_statsdb.m_numStats; i++) {
buf->safePrintf ( "<option value=\"%i\"", i );
if ( st->m_statListOrig.exists(i) )
buf->safePrintf ( " selected " );
buf->safePrintf ( ">%s</option>\n",
g_statsdb.m_stats[i].m_title );
}
buf->safePrintf (
"</select>"
"</td>\n"
"</tr>\n"
"</table>\n"
"</div>\n" );
// Print the graph type selection, can select one of:
// . Line - just a line drawn through each point
// . Fill - like the above, but fills under the line
// . Block - uses vertical bars to draw the graph
buf->safePrintf (
"<div id=\"appearance\" class=\"section\">\n"
"<div class=\"section_header\">"
"<span class=\"section_title\">Display Properties"
"</span></div>\n"
"<table>\n"
"<tr>\n"
"<td>Graph Style</td>\n"
"<td>\n"
"<input type=\"radio\" name=\"graph_style\" value=\"line\""
);
if ( st->m_linePlot ) buf->safePrintf( " checked" );
buf->safePrintf (
" />"
"Line"
"&nbsp;\n"
"<input type=\"radio\" name=\"graph_style\" value=\"fill\""
);
if ( st->m_lineFill ) buf->safePrintf( " checked" );
buf->safePrintf (
" />"
"Fill"
"&nbsp;\n"
"<input type=\"radio\" name=\"graph_style\" value=\"block\""
);
if ( st->m_lineBlock ) buf->safePrintf( " checked" );
buf->safePrintf (
" />"
"Block\n"
"</td>\n"
"</tr>\n"
"<tr>\n"
"<td>GIF Size</td>\n"
"<td>\n"
"<select name=\"graph_dim\">\n"
);
// Print out the graph dimesion selector.
uint16_t width = 0;
uint16_t height = 0;
bool dimPrinted = false;
for ( int i = 0; i < GIF_NUM_DIM; i++ ) {
width = s_widths[i];
height = s_heights[i];
if ( !dimPrinted && (st->m_gifWidth < width ||
( (st->m_gifWidth == width) &&
(st->m_gifHeight < height) )) ) {
buf->safePrintf(
"<option selected >%dx%d</option>\n",
st->m_gifWidth, st->m_gifHeight
);
dimPrinted = true;
}
buf->safePrintf ( "<option ");
if ( (st->m_gifHeight == height) &&
(st->m_gifWidth == width) ) {
buf->safePrintf ( " selected ");
dimPrinted = true;
}
buf->safePrintf (
">%dx%d</option>\n",
s_widths[i], s_heights[i]
);
}
buf->safePrintf (
"</select>\n"
"</td>\n"
"</tr>\n"
"<tr>\n"
"<td>Columns</td>\n"
"<td>\n"
"<select name=\"columns\">\n"
);
buf->safePrintf(
"</select>\n"
"</td>\n"
"</tr>\n" );
*/
//for ( uint32_t i=MIN_COLUMNS; i <= MAX_COLUMNS; i++ ) {
// buf->safePrintf( "<option" );
// if ( st->m_columns == i )
// buf->safePrintf ( " selected" );
// buf->safePrintf( ">%u</option>\n", i );
//}
buf->safePrintf(
"<tr class=\"show\" id=\"e_auto_trigger\">\n"
"<td colspan=\"2\">\n"
"<input type=\"checkbox\" name=\"auto_update\" "
"id=\"auto_update_trigger\" value=\"1\" "
"onclick=\"javascript:st_auto_update()\""
);
if ( st->m_autoUpdate ) buf->safePrintf( " checked" );
buf->safePrintf (
" /> Auto Update Stats\n"
);
buf->safePrintf (
"</td>\n"
"</tr>\n"
"</table>\n"
"<input type=\"hidden\" name=\"genstats\" value=\"1\" />\n"
);
g_pages.printFormData( buf, st->m_socket, &st->m_request );
buf->safePrintf (
"<input type=\"submit\" name=\"action\" value=\"submit\" />"
"</div>\n</form>\n</div>\n</div>\n"
);
}
time_t genDate( char *date, long dateLen ) {
time_t result = -1;
// the date string should always be the same length
if ( ! date || dateLen != 16 )
return result;
struct tm tmRef;
struct tm tmBuild;
//*
memset( (char *)&tmRef, 0, sizeof( tmRef ) );
time_t now = (time_t)getTimeGlobalNoCore();
localtime_r( &now, &tmRef );
now = mktime( &tmRef );
// */
char tmp[18];
char *p = tmp;
memcpy( p, date, dateLen );
p[2] = '\0';
p[5] = '\0';
p[10] = '\0';
p[13] = '\0';
p[16] = '\0';
memset( (char *)&tmBuild, 0, sizeof( tmBuild ) );
tmBuild.tm_mon = atoi( p ) - 1; p += 3;
tmBuild.tm_mday = atoi( p ); p += 3;
tmBuild.tm_year = atoi( p ) - 1900; p += 5;
tmBuild.tm_hour = atoi( p ); p += 3;
tmBuild.tm_min = atoi( p ); p += 3;
tmBuild.tm_isdst = tmRef.tm_isdst; p += 3;
// We must manually adjust for DST difference
// if the current state of DST does not match
// that of the date that was requested.
/*
struct tm nowDST;
struct tm resultDST;
localtime_r( &now, &nowDST );
localtime_r( &result, &resultDST );
if ( nowDST.tm_isdst && !resultDST.tm_isdst )
tmBuild.tm_hour++;
else if ( !nowDST.tm_isdst && resultDST.tm_isdst )
tmBuild.tm_hour--;
memcpy( p, date, dateLen );
p[16] = '\0';
log ( LOG_DEBUG, "stats: user string [%s]", p );
log ( LOG_DEBUG, "stats: user provided time [%s]", ctime( &result ) );
log ( LOG_DEBUG, "stats: our timestamp [%s]", ctime( &now ) );
// */
result = mktime( &tmBuild );
return result;
}