LogDoctor/logdoctor/modules/crapview/modules/query.cpp

1355 lines
46 KiB
C++

#include "query.h"
#include "modules/database/database.h"
#include "modules/dialogs.h"
#include "modules/exceptions.h"
#include "utilities/printables.h"
#include "utilities/strings.h"
#include <QDateTime>
#include <map>
#include <vector>
#include <ranges>
int toInt( const QString& str )
{
bool ok;
const int result{ str.toInt( &ok ) };
if ( ! ok ) {
DialogSec::errConvertingData(
QStringLiteral("QString"),
QStringLiteral("int"),
str );
throw LogDoctorException{};
}
return result;
}
int toInt( QStringView str )
{
bool ok;
const int result{ str.toInt( &ok ) };
if ( ! ok ) {
DialogSec::errConvertingData(
QStringLiteral("QStringView"),
QStringLiteral("int"),
str.toString() );
throw LogDoctorException{};
}
return result;
}
int toInt( const QVariant& v )
{
if ( ! v.canConvert( QMetaType(QMetaType::Int) ) ) {
DialogSec::errConvertingData(
QStringLiteral("QVariant"),
QStringLiteral("int"),
v.toString() );
throw LogDoctorException{};
}
return v.toInt();
}
QString toString( const QVariant& v )
{
if ( ! v.canConvert( QMetaType(QMetaType::QString) ) ) {
DialogSec::errConvertingData(
QStringLiteral("QVariant"),
QStringLiteral("QString"),
v.toString() );
throw LogDoctorException{};
}
return v.toString();
}
void DbQuery::setDialogLevel( const DialogsLevel new_level ) noexcept
{
this->dialog_level = new_level;
}
void DbQuery::setDbPath( std::string&& path ) noexcept
{
this->db_path = std::move(path);
this->db_name = QString::fromStdString( this->db_path.substr( this->db_path.find_last_of( '/' ) + 1ul ) );
}
int DbQuery::getMinuteGap( const int minute, const int gap )
{
int m{ -1 };
if ( minute < 0 || minute >= 60 ) {
// unexpected value
throw DateTimeException( "Unexpected Minute: " + std::to_string( minute ) );
}
int n{ 0 };
for ( int g{0}; g<60; g+=gap ) {
if ( minute >= g && minute < g+gap ) {
m = gap * n;
break;
}
++n;
}
return m;
}
int DbQuery::getMonthDays( const int year, const int month )
{
int n_days;
switch (month) {
case 1: n_days = 31; break;
case 2: n_days = ( year%4 == 0 ) ? 29 : 28 ; break;
case 3: n_days = 31; break;
case 4: n_days = 30; break;
case 5: n_days = 31; break;
case 6: n_days = 30; break;
case 7: n_days = 31; break;
case 8: n_days = 31; break;
case 9: n_days = 30; break;
case 10: n_days = 31; break;
case 11: n_days = 30; break;
case 12: n_days = 31; break;
default:
// unexpected month
throw DateTimeException( "Unexpected Month number: " + std::to_string( month ) );
}
return n_days;
}
int DbQuery::getMonthNumber( QStringView month_str ) const
{
for ( const auto& [num,str] : this->MONTHS ) {
if ( TR::tr(str.c_str()) == month_str ) {
return num;
}
}
throw DateTimeException( "Unexpected Month name: " + month_str.toString().toStdString() );
}
int DbQuery::countDays( const int from_year, const int from_month, const int from_day, const int to_year, const int to_month, const int to_day )
{
int n_days{ 1 };
if ( from_year == to_year ) {
// 1 year
if ( from_month == to_month ) {
// 1 month
n_days += to_day - from_day + 1;
} else {
n_days += getMonthDays( from_year, from_month ) - from_day; // first month's days
for ( int month=from_month+1; month<to_month; ++month ) {
n_days += getMonthDays( from_year, month );
}
n_days += to_day; // last month's days
}
} else {
n_days += getMonthDays( from_year, from_month ) - from_day;
if ( from_month < 12 ) {
for ( int month{from_month+1}; month<=12; ++month ) {
n_days += getMonthDays( from_year, month );
}
}
for ( int year{from_year+1}; year<=to_year; ++year ) {
int last_month{ 12 };
if ( year == to_year ) {
last_month = to_month-1;
n_days += to_day; // last month's days, added in advance
}
for ( int month{1}; month<=last_month; ++month ) {
n_days += getMonthDays( year, month );
}
}
}
return n_days;
}
int DbQuery::countMonths( const int from_year, const int from_month, const int to_year, const int to_month ) noexcept
{
int n_months{ 0 };
if ( from_year == to_year ) {
// same year
if ( from_month == to_month ) {
// same month
n_months = 1;
} else {
// different months
n_months = to_month - from_month + 1;
}
} else {
// different years
n_months += 13 - from_month; // months to the end of the first year
n_months += to_month; // months from the beginning of the last year
n_months += 12 * ( to_year - from_year - 1 ); // 12 months for every middle year (0 if none)
}
return n_months;
}
int DbQuery::countMonths( QStringView from_year, QStringView from_month, QStringView to_year, QStringView to_month ) const
{
const int from_year_{ toInt( from_year ) },
from_month_{ this->getMonthNumber( from_month ) };
return this->countMonths(
toInt( from_year ),
this->getMonthNumber( from_month ),
to_year.isEmpty() ? from_year_ : toInt( to_year ),
to_month.isEmpty() ? from_month_ : this->getMonthNumber( to_month )
);
}
const QString& DbQuery::getDbField( const LogField fld ) const
{
return this->LogFields_to_DbFields.at( fld );
}
const QString& DbQuery::getDbField( QStringView tr_fld ) const
{
for ( const auto& [id,str] : this->FIELDS ) {
if ( TR::tr(str.c_str()) == tr_fld ) {
return this->LogFields_to_DbFields.at( id );
}
}
throw DatabaseException( std::move(QStringLiteral("Unexpected DbField: ").append(tr_fld)) );
}
// get a fresh map of available dates
void DbQuery::refreshDates( std::optional<database_dates_t>& result ) noexcept
{
database_dates_t dates{ // std::unordered_map<int, std::unordered_map<int, std::unordered_map<int, std::vector<int>>>>
{11, {}}, {12, {}}, {13, {}}
};
DatabaseWrapper db{ DatabaseHandler::get( DatabaseType::Data, true ) };
db.open( this->db_path, this->dialog_level==DL_EXPLANATORY );
// recursively query years, months and days for every WebServer
static const std::vector<std::tuple<int, QString>> tables{
std::make_tuple(11,"apache"),
std::make_tuple(12,"nginx"),
std::make_tuple(13,"iis") };
for ( const auto& [ws,tbl] : tables ) {
QueryWrapper query{ db.getQuery() };
query( QStringLiteral(R"(SELECT DISTINCT "year", "month", "day" FROM "%1" ORDER BY "year", "month", "day" ASC;)").arg(tbl) );
auto& years = dates.at( ws );
while ( query->next() ) {
years.try_emplace( toInt( query[0] ), std::map<int, std::vector<int>>{} )
.first->second
.try_emplace( toInt( query[1] ), std::vector<int>{} )
.first->second
.emplace_back( toInt( query[2] ) );
}
}
result.emplace( std::move(dates) );
}
// get daytime values for the warnings
void DbQuery::getWarningsData( std::optional<stats_warn_items_t>& result, QStringView web_server, QStringView year_, QStringView month_, QStringView day_, QStringView hour_ ) const
{
stats_warn_items_t items; // std::vector<std::vector<std::vector<std::array<QString,18>>>>
DatabaseWrapper db{ DatabaseHandler::get( DatabaseType::Data, true ) };
db.open( this->db_path, this->dialog_level==DL_EXPLANATORY );
// setup period limits
const int year{ toInt( year_ ) };
const int month{ this->getMonthNumber( month_ ) };
const int day{ toInt( day_ ) };
const int hour{ hour_.isEmpty() ? -1 : toInt( hour_ ) };
const auto from_query_data{
[](const QueryWrapper& query)->std::array<QString,18>
{
return {
toString(query[0]), toString(query[1]), toString(query[2]), // year, month, day
toString(query[3]), toString(query[4]), toString(query[5]), // hour, minute, second
toString(query[6]), toString(query[7]), // protocol, method
toString(query[8]), toString(query[9]), // uri, query
toString(query[10]), // response
toString(query[15]), // user agent
toString(query[14]), // client
toString(query[16]), // cookie
toString(query[17]), // referer
toString(query[13]), // bytes received
toString(query[12]), // bytes sent
toString(query[11]) // time taken
};
}
};
QueryWrapper query{ db.getQuery() };
query << QStringLiteral(R"(SELECT * FROM "%1" WHERE "year"=%2 AND "month"=%3 AND "day"=%4)")
.arg( web_server )
.arg( year ).arg( month ).arg( day );
if ( hour == -1 ) {
// entire day
items.reserve( 24ul );
for ( size_t h{0ul}; h<24ul; ++h ) {
items.emplace_back( std::vector<std::vector<std::array<QString,18>>>{} );
auto& aux{ items.at( h ) };
aux.reserve( 6ul );
for ( int m{0}; m<60; m+=10 ) {
aux.emplace_back( std::vector<std::array<QString,18>>{} );
}
}
query << R"( ORDER BY "hour","minute","second" ASC;)";
query();
while ( query->next() ) {
// append the line
items.at( static_cast<size_t>( toInt( query[3] ) ) )
.at( static_cast<size_t>( this->getMinuteGap( toInt( query[4] ) )/10 ) )
.push_back( from_query_data( query ) );
}
} else {
// 1 hour
items.reserve( 6ul );
for ( size_t g{0ul}; g<6ul; ++g ) {
items.emplace_back( std::vector<std::vector<std::array<QString,18>>>{} );
auto& aux{ items.at( g ) };
aux.reserve( 10ul );
for ( int m{0}; m<10; ++m ) {
aux.emplace_back( std::vector<std::array<QString,18>>{} );
}
}
query << QStringLiteral(R"( AND "hour"=%5 ORDER BY "minute","second" ASC;)")
.arg( hour );
query();
while ( query->next() ) {
// append the line
const int min{ toInt( query[4] ) };
items.at( static_cast<size_t>( this->getMinuteGap( min )/10 ) )
.at( static_cast<size_t>( min % 10 ) )
.push_back( from_query_data( query ) );
}
}
result.emplace( std::move(items) );
}
// get day-time values for the time-taken field
void DbQuery::getSpeedData( std::optional<stats_speed_items_t>& result, QStringView web_server, QStringView year_, QStringView month_, QStringView day_, QStringView protocol_f, QStringView method_f, QStringView uri_f, QStringView query_f, QStringView response_f ) const
{
stats_speed_items_t data; // std::vector<std::tuple<long long, std::array<QString,6>>>
DatabaseWrapper db{ DatabaseHandler::get( DatabaseType::Data, true ) };
db.open( this->db_path, this->dialog_level==DL_EXPLANATORY );
const int year{ toInt( year_ ) };
const int month{ this->getMonthNumber( month_ ) };
const int day{ toInt( day_ ) };
QueryWrapper query{ db.getQuery() };
query << QStringLiteral(R"(SELECT "hour","minute","second","time_taken","uri","query","method","protocol","response" FROM "%1" WHERE "year"=%2 AND "month"=%3 AND "day"=%4 AND "time_taken" IS NOT NULL)")
.arg( web_server )
.arg( year ).arg( month ).arg( day );
if ( ! protocol_f.isEmpty() ) {
query << QStringLiteral(R"( AND "protocol")").append( protocol_f );
}
if ( ! method_f.isEmpty() ) {
query << QStringLiteral(R"( AND "method")").append( method_f );
}
if ( ! uri_f.isEmpty() ) {
query << QStringLiteral(R"( AND "uri")").append( uri_f );
}
if ( ! query_f.isEmpty() ) {
query << QStringLiteral(R"( AND "query")").append( query_f );
}
if ( ! response_f.isEmpty() ) {
query << QStringLiteral(R"( AND "response")").append( response_f );
}
query << R"( ORDER BY "hour","minute","second" ASC;)";
query();
if ( const size_t size{ query.size() }; size > 0ul ) {
data.reserve( size * 3 );
} else {
return;
}
int h, m, s;
const auto prev_instant{
[&h,&m,&s](const int hour, const int minute, const int second)
{
h=hour; m=minute; s=second;
if ( --s < 0 ) { s=59;
if ( --m < 0 ) { m=59;
if ( --h < 0 ) { h=m=s=0; }}}
}
};
const auto next_instant{
[&h,&m,&s](const int hour, const int minute, const int second)
{
h=hour; m=minute; s=second;
if ( ++s > 59 ) { s=0;
if ( ++m > 59 ) { m=0;
if ( ++h > 23 ) { h=23;m=59;s=59; }}}
}
};
using data_t = std::array<QString,6>;
QDateTime time{
QDate( year, month, day ),
QTime( 0, 0, 0 )
};
const auto push_empty{
[&data,&time]()
{
data.emplace_back( time.toMSecsSinceEpoch(), data_t{} );
}
};
const auto push_data{
[&data,&time](const auto ...args)
{
data.emplace_back( time.toMSecsSinceEpoch(), data_t{args...} );
}
};
const auto set_time{
[&time](const auto ...args)
{
time.setTime( QTime(args...) );
}
};
int hour{-1}, next_hour, prev_hour{0},
minute{0}, next_minute, prev_minute{0},
second{0}, next_second, prev_second{0};
QString tt, ur, qr, mt, pt, rs;
// append the first ficticious count
time.setMSecsSinceEpoch( time.toMSecsSinceEpoch() - 1000 ); // -1s
push_empty();
time.setMSecsSinceEpoch( time.toMSecsSinceEpoch() + 1000 ); // +1s
while ( query->next() ) {
next_hour = toInt( query[0] );
next_minute = toInt( query[1] );
next_second = toInt( query[2] );
if ( next_hour == hour && next_minute == minute && next_second == second ) {
set_time( hour, minute, second );
push_data( tt,ur,qr,mt,pt,rs );
} else {
if ( next_hour == hour ) {
prev_instant( hour, minute, second );
// append the second before the last one found, if it is not equal to the prev
if ( prev_hour < h || prev_minute < m || prev_second < s ) {
set_time( h, m, s );
push_empty();
}
// same hour new minute/second, append the last count
set_time( hour, minute, second );
push_data( tt,ur,qr,mt,pt,rs );
// append the second after the last one found, if it is not equal to the next
next_instant( hour, minute, second );
if ( next_hour > h || next_minute > m || next_second > s ) {
set_time( h, m, s );
push_empty();
}
prev_hour = hour; // update now to avoid getting next_hour's value
} else {
// minute & second are always different when the hour is different
if ( hour >= 0 ) {
// append the prev as zero
prev_instant( hour, minute, second );
if ( prev_hour < h || prev_minute < m || prev_second < s ) {
set_time( h, m, s );
push_empty();
}
// apend the last p count if not in the first round of the loop
set_time( hour, minute, second );
push_data( tt,ur,qr,mt,pt,rs );
// append the next as zero
next_instant( hour, minute, second );
if ( next_hour > h || next_minute > m || next_second > s ) {
set_time( h, m, s );
push_empty();
}
} else {
// hout < 0 only in the first round of the loop
// append the second 0 of the day, if it is not the one found
if ( next_hour > 0 || next_minute > 0 || next_second > 0 ) {
push_empty();
// append the second before the first found
prev_instant( next_hour, next_minute, next_second );
if ( h > 0 || m > 0 || s > 0 ) {
set_time( h, m, s );
push_empty();
}
}
}
prev_hour = hour;
hour = next_hour;
}
prev_minute = minute;
minute = next_minute;
prev_second = second;
second = next_second;
}
tt = toString( query[3] ); // time taken
ur = toString( query[4] ); // uri
qr = toString( query[5] ); // query
mt = toString( query[6] ); // method
pt = toString( query[7] ); // protocol
rs = toString( query[8] ); // response
}
// last one, append the prev count
prev_instant( hour, minute, second );
if ( prev_hour < h || prev_minute < m || prev_second < s ) {
set_time( h, m, s );
push_empty();
}
// append the last count
set_time( hour, minute, second );
push_data( tt,ur,qr,mt,pt,rs );
if ( hour < 23 && minute < 59 && second < 59 ) {
// append 1 second after the last
next_instant( hour, minute, second );
set_time( h, m, s );
push_empty();
}
// append the last fictitious count
time.setTime( QTime( 23, 59, 59 ) );
time.setMSecsSinceEpoch( time.toMSecsSinceEpoch() + 1000 ); // +1s
push_empty();
if ( data.capacity() > data.size() ) {
data.shrink_to_fit();
}
result.emplace( std::move(data) );
}
// get, group and count identical items of a specific field in a date
void DbQuery::getItemsCount( std::optional<stats_count_items_t>& result, QStringView web_server, QStringView year, QStringView month, QStringView day, QStringView log_field ) const
{
QHash<QString, unsigned> aux_items;
stats_count_items_t items; // std::map<QString, unsigned int>>
DatabaseWrapper db{ DatabaseHandler::get( DatabaseType::Data, true ) };
db.open( this->db_path, this->dialog_level==DL_EXPLANATORY );
QueryWrapper query{ db.getQuery() };
query << QStringLiteral(R"(SELECT "%1" FROM "%2" WHERE "%3" IS NOT NULL AND "year"=%4 AND "month"=%5 AND "day"=%6;)")
.arg( this->getDbField( log_field ),
web_server,
this->getDbField( log_field ),
year,
QString::number( this->getMonthNumber( month ) ),
day )
.replace(QChar('\''),QLatin1String("''"));
query();
if ( const size_t size{ query.size() }; size > 0ul ) {
aux_items.reserve( size * 3 );
} else {
return;
}
while ( query->next() ) {
const QString item{ toString( query[0] ) };
if ( ! item.isEmpty() ) {
++ aux_items[ item ];
}
}
// morph tha QHash into an ordered map
QHashIterator iter{ aux_items };
while ( iter.hasNext() ) {
iter.next();
items.emplace( iter.value(), iter.key() );
}
result.emplace( std::move(items) );
}
// get and count items with a 10 minutes gap for every hour of the day
void DbQuery::getDaytimeCounts( std::optional<stats_day_items_t>& result, QStringView web_server, QStringView from_year_, QStringView from_month_, QStringView from_day_, QStringView to_year_, QStringView to_month_, QStringView to_day_, const LogField log_field_, QStringView field_filter ) const
{
stats_day_items_t data{ // std::unordered_map<int, std::unordered_map<int, int>>
{0, {{0,0},{10,0},{20,0},{30,0},{40,0},{50,0}}}, {1, {{0,0},{10,0},{20,0},{30,0},{40,0},{50,0}}},
{2, {{0,0},{10,0},{20,0},{30,0},{40,0},{50,0}}}, {3, {{0,0},{10,0},{20,0},{30,0},{40,0},{50,0}}},
{4, {{0,0},{10,0},{20,0},{30,0},{40,0},{50,0}}}, {5, {{0,0},{10,0},{20,0},{30,0},{40,0},{50,0}}},
{6, {{0,0},{10,0},{20,0},{30,0},{40,0},{50,0}}}, {7, {{0,0},{10,0},{20,0},{30,0},{40,0},{50,0}}},
{8, {{0,0},{10,0},{20,0},{30,0},{40,0},{50,0}}}, {9, {{0,0},{10,0},{20,0},{30,0},{40,0},{50,0}}},
{10, {{0,0},{10,0},{20,0},{30,0},{40,0},{50,0}}}, {11, {{0,0},{10,0},{20,0},{30,0},{40,0},{50,0}}},
{12, {{0,0},{10,0},{20,0},{30,0},{40,0},{50,0}}}, {13, {{0,0},{10,0},{20,0},{30,0},{40,0},{50,0}}},
{14, {{0,0},{10,0},{20,0},{30,0},{40,0},{50,0}}}, {15, {{0,0},{10,0},{20,0},{30,0},{40,0},{50,0}}},
{16, {{0,0},{10,0},{20,0},{30,0},{40,0},{50,0}}}, {17, {{0,0},{10,0},{20,0},{30,0},{40,0},{50,0}}},
{18, {{0,0},{10,0},{20,0},{30,0},{40,0},{50,0}}}, {19, {{0,0},{10,0},{20,0},{30,0},{40,0},{50,0}}},
{20, {{0,0},{10,0},{20,0},{30,0},{40,0},{50,0}}}, {21, {{0,0},{10,0},{20,0},{30,0},{40,0},{50,0}}},
{22, {{0,0},{10,0},{20,0},{30,0},{40,0},{50,0}}}, {23, {{0,0},{10,0},{20,0},{30,0},{40,0},{50,0}}},
};
DatabaseWrapper db{ DatabaseHandler::get( DatabaseType::Data, true ) };
db.open( this->db_path, this->dialog_level==DL_EXPLANATORY );
const int from_year{ toInt( from_year_ ) };
const int from_month{ this->getMonthNumber( from_month_ ) };
const int from_day{ toInt( from_day_ ) };
const int to_year{ to_year_.isEmpty() ? from_year : toInt( to_year_ ) };
const int to_month{ to_month_.isEmpty() ? from_month : this->getMonthNumber( to_month_ ) };
const int to_day{ to_day_.isEmpty() ? from_day : toInt( to_day_ ) };
const QString& log_field{ this->getDbField( log_field_ ) };
int n_days { 0 },
n_months { this->countMonths( from_year, from_month, to_year, to_month ) };
int year { from_year },
month { from_month };
std::unordered_map<int,int> days_l;
days_l.reserve( 31ul );
if ( n_months == 1 ) {
// 1 month, no need to loop
QueryWrapper query{ db.getQuery() };
query << QStringLiteral(R"(SELECT "day", "hour", "minute" FROM "%1" WHERE "year"=%2 AND "month"=%3 AND "day">=%4 AND "day"<=%5)")
.arg( web_server )
.arg( year ).arg( month )
.arg( from_day ).arg( to_day );
if ( ! field_filter.isEmpty() ) {
query << QStringLiteral(R"( AND "%1"%2)").arg( log_field, field_filter );
}
query << ";";
query();
while ( query->next() ) {
const int day{ toInt( query[0] ) };
const int hour{ toInt( query[1] ) };
const int minute{ toInt( query[2] ) };
// increase the count
++ data.at( hour ).at( this->getMinuteGap( minute ) );
// append the day as newly found if not found yet
++ days_l[ day ];
}
n_days += static_cast<int>(days_l.size());
} else {
for ( int m{1}; m<=n_months; ++m ) {
QueryWrapper query{ db.getQuery() };
query << QStringLiteral(R"(SELECT "day", "hour", "minute" FROM "%1" WHERE "year"=%2 AND "month"=%3)")
.arg( web_server )
.arg( year ).arg( month );
if ( m == 1 ) {
// first month, only get the days starting from the beginning day
query << QStringLiteral(R"( AND "day">=%1)").arg( from_day );
} else if ( m == n_months ) {
// last month, only get the days until the ending day
query << QStringLiteral(R"( AND "day"<=%1)").arg( to_day );
}
if ( ! field_filter.isEmpty() ) {
query << QStringLiteral(R"( AND "%1"%2)").arg( log_field, field_filter );
}
query << ";";
query();
while ( query->next() ) {
const int day{ toInt( query[0] ) };
const int hour{ toInt( query[1] ) };
const int minute{ toInt( query[2] ) };
// increase the count
++ data.at( hour ).at( this->getMinuteGap( minute ) );
// append the day as newly found if not found yet
++ days_l[ day ];
}
n_days += static_cast<int>(days_l.size());
++ month;
if ( month > 12 ) {
month = 1;
++ year;
}
}
}
if ( n_days == 0 ) {
// no data
return;
}
// divide the count by the number of days to get the mean value
for ( const auto& [h,data_] : data ) {
for ( const auto& [m,c] : data_ ) {
int& count{ data.at( h ).at( m ) };
if ( count > 0 ) {
count /= n_days;
if ( count == 0 ) {
++ count;
}
}
}
}
result.emplace( std::move(data) );
}
// get and count how many times a specific item value brought to another
void DbQuery::getRelationalCountsDay( std::optional<stats_relat_items_t>& result, QStringView web_server, QStringView year_, QStringView month_, QStringView day_, const LogField log_field_1_, QStringView field_filter_1, const LogField log_field_2_, QStringView field_filter_2 ) const
{
stats_relat_items_t data; // std::vector<std::tuple<qint64, int>>
int gap = 20;
DatabaseWrapper db{ DatabaseHandler::get( DatabaseType::Data, true ) };
db.open( this->db_path, this->dialog_level==DL_EXPLANATORY );
const int year{ toInt( year_ ) };
const int month{ this->getMonthNumber( month_ ) };
const int day{ toInt( day_ ) };
const QString& log_field_1{ this->getDbField( log_field_1_ ) };
const QString& log_field_2{ this->getDbField( log_field_2_ ) };
QueryWrapper query{ db.getQuery() };
query << QStringLiteral(R"(SELECT "hour", "minute" FROM "%1" WHERE "year"=%2 AND "month"=%3 AND "day"=%4)")
.arg( web_server )
.arg( year ).arg( month ).arg( day );
if ( ! field_filter_1.isEmpty() ) {
query << QStringLiteral(R"( AND "%1"%2)").arg( log_field_1, field_filter_1 );
}
if ( ! field_filter_2.isEmpty() ) {
query << QStringLiteral(R"( AND "%1"%2)").arg( log_field_2, field_filter_2 );
}
query << QStringLiteral(R"( ORDER BY "hour","minute" ASC;)");
query();
if ( query.size() == 0ul ) {
return;
}
QDateTime time{
QDate( year, month, day ),
QTime( 0, 0, 0 )
};
data.reserve( static_cast<size_t>( 24*(60/gap) ) );
const auto push_data{
[&data,&time](const int count)
{
data.emplace_back( time.toMSecsSinceEpoch(), count );
}
};
const auto set_time{
[&time](const auto ...args)
{
time.setTime( QTime(args...) );
}
};
int hour{-1}, next_hour,
minute{0}, next_minute,
count{0};
while ( query->next() ) {
next_hour = toInt( query[0] );
next_minute = this->getMinuteGap( toInt( query[1] ), gap );
if ( next_hour == hour && next_minute == minute ) {
++ count;
} else {
if ( next_hour == hour ) {
// same hour new minute gap, append the last count
set_time( hour, minute );
push_data( count );
// and any missing gap
for ( int m{minute+gap}; m<next_minute; m+=gap ) {
set_time( hour, m );
push_data( 0 );
}
} else {
// minute is always different when the hour is different
if ( hour >= 0 ) {
// apend the last minute-gap count if not in the first round of the loop
set_time( hour, minute );
push_data( count );
// append any missing gap in the current hour
for ( int m{minute+gap}; m<60; m+=gap ) {
set_time( hour, m );
push_data( 0 );
}
++ hour;
} else {
// prepare to add missing gaps from 00:00 (+gap will be added to the minute)
hour = 0;
}
// append any missing gap in every hour between the current and the next found (aux)
for ( int h{hour}; h<next_hour; ++h ) {
for ( int m{0}; m<60; m+=gap ) {
set_time( h, m );
push_data( 0 );
}
}
// append any missing gap in the netx found hour
for ( int m{0}; m<next_minute; m+=gap ) {
set_time( next_hour, m );
push_data( 0 );
}
hour = next_hour;
}
minute = next_minute;
count = 1;
}
}
// append the last count
set_time( hour, minute );
push_data( count );
// append any missing gap in the last hour
for ( int m{minute+gap}; m<60; m+=gap ) {
set_time( hour, m );
push_data( 0 );
}
// append any missing data up to the end of the day
for ( int h{hour+1}; h<24; ++h ) {
for ( int m{0}; m<60; m+=gap ) {
set_time( h, m );
push_data( 0 );
}
}
// append the real last fictitious count
int d{ day }, m{ month }, y{ year };
if ( ++d > this->getMonthDays( year, month ) ) {
if ( ++m > 12 ) {
m = 1;
++y;
}
d = 1;
}
time.setDate( QDate( y, m , d ) );
time.setTime( QTime( 0, 0, 0 ) );
push_data( 0 );
result.emplace( std::move(data) );
}
void DbQuery::getRelationalCountsPeriod( std::optional<stats_relat_items_t>& result, QStringView web_server, QStringView from_year_, QStringView from_month_, QStringView from_day_, QStringView to_year_, QStringView to_month_, QStringView to_day_, const LogField log_field_1_, QStringView field_filter_1, const LogField log_field_2_, QStringView field_filter_2 ) const
{
stats_relat_items_t data; // std::vector<std::tuple<qint64, int>>
DatabaseWrapper db{ DatabaseHandler::get( DatabaseType::Data, true ) };
db.open( this->db_path, this->dialog_level==DL_EXPLANATORY );
const int from_year{ toInt( from_year_ ) };
const int from_month{ this->getMonthNumber( from_month_ ) };
const int from_day{ toInt( from_day_ ) };
const int to_year{ to_year_.isEmpty() ? from_year : toInt( to_year_ ) };
const int to_month{ to_month_.isEmpty() ? from_month : this->getMonthNumber( to_month_ ) };
const int to_day{ to_day_.isEmpty() ? from_day : toInt( to_day_ ) };
const QString& log_field_1{ this->getDbField( log_field_1_ ) };
const QString& log_field_2{ this->getDbField( log_field_2_ ) };
const int n_months{ this->countMonths( from_year, from_month, to_year, to_month ) };
QDateTime time;
const auto set_date{
[&time](const auto ...args)
{
time.setDate( QDate(args...) );
}
};
const auto push_data{
[&data,&time](const int count)
{
data.emplace_back( time.toMSecsSinceEpoch(), count );
}
};
const auto prev_instant{
[this](int& y, int& m, int& d)
{
if ( --d < 1 ) {
if ( --m < 1 ) {
m=12; --y;
}
d = this->getMonthDays( y, m );
}
}
};
const auto next_instant{
[this](int& y, int& m, int& d)
{
if ( ++d > this->getMonthDays( y, m ) ) {
if ( ++m > 12 ) {
m=1; ++y;
}
d = 1;
}
}
};
int year { from_year },
month { from_month };
if ( n_months == 1 ) {
// 1 month, no need to loop
QueryWrapper query{ db.getQuery() };
query << QStringLiteral(R"(SELECT "day" FROM "%1" WHERE "year"=%2 AND "month"=%3 AND "day">=%4 AND "day"<=%5)")
.arg( web_server )
.arg( year ).arg( month )
.arg( from_day ).arg( to_day );
if ( ! field_filter_1.isEmpty() ) {
query << QStringLiteral(R"( AND "%1"%2)").arg( log_field_1, field_filter_1 );
}
if ( ! field_filter_2.isEmpty() ) {
query << QStringLiteral(R"( AND "%1"%2)").arg( log_field_2, field_filter_2 );
}
query << R"( ORDER BY "day" ASC;)";
query();
if ( query.size() == 0ul ) {
return;
}
data.reserve( to_day - from_day );
int day{0}, count{0};
while ( query->next() ) {
const int next_day{ toInt( query[0] ) };
if ( next_day == day ) {
++ count;
continue; // avoids resetting the count at the end
} else if ( day > 0 ) {
// any loop-round except the first
set_date( year, month , day );
push_data( count );
for ( int d{day+1}; d<next_day; ++d ) {
// append any missing day with a zero value
set_date( year, month , d );
push_data( 0 );
}
} else {
// day == 0 only in the first round of the loop
// append any missing day from 1 day before the first until the next found
int d{ from_day }, m{ month }, y{ year };
prev_instant( y, m, d );
while ( d!=next_day ) {
set_date( y, m , d );
push_data( 0 );
next_instant( y, m, d );
}
}
day = next_day;
count = 1;
}
// append the last count
set_date( year, month , day );
push_data( count );
// append any missing day from the last found until 1 day after the last one
next_instant( year, month, day );
int max_day{ to_day }, max_month{ to_month }, max_year{ to_year };
next_instant( max_year, max_month, max_day );
while ( year<=max_year && month<=max_month && day<=max_day ) {
set_date( year, month , day );
push_data( 0 );
next_instant( year, month, day );
}
} else {
data.reserve( this->countDays( from_year, from_month, from_day, to_year, to_month, to_day ) );
bool no_data{ true };
const QString query_filters{ QStringLiteral("%1%2").arg(
!field_filter_1.isEmpty() ? QStringLiteral(R"( AND "%1"%2)").arg( log_field_1, field_filter_1 ) : QString(),
!field_filter_2.isEmpty() ? QStringLiteral(R"( AND "%1"%2)").arg( log_field_2, field_filter_2 ) : QString())
};
for ( int m{1}; m<=n_months; ++m ) {
QueryWrapper query{ db.getQuery() };
query << QStringLiteral(R"(SELECT "day" FROM "%1" WHERE "year"=%2 AND "month"=%3)")
.arg( web_server )
.arg( year ).arg( month );
if ( m == 1 ) {
// first month, only get the day from the beginning day
query << QStringLiteral(R"( AND "day">=%1)").arg( from_day );
} else if ( m == n_months ) {
// last month, only get the days until the ending day
query << QStringLiteral(R"( AND "day"<=%1)").arg( to_day );
}
if ( ! query_filters.isEmpty() ) {
query << query_filters;
}
query << R"( ORDER BY "day" ASC;)";
query();
if ( query.size() == 0ul ) {
// no data found for this month, append missing days with 0 value
const int max_d{ m==n_months ? to_day : this->getMonthDays( year, month ) };
int d{ m==1 ? from_day : 1 };
for ( ; d<=max_d; ++d ) {
set_date( year, month , d );
push_data( 0 );
}
} else {
no_data &= false;
int day{0}, count{0};
while ( query->next() ) {
const int next_day{ toInt( query[0] ) };
if ( next_day == day ) {
++ count;
continue; // avoids resetting the count at the end
} else if ( day > 0 ) {
// any loop-round except the first
set_date( year, month, day++ );
push_data( count );
while ( day<next_day ) {
// append any missing day with a zero value
set_date( year, month, day++ );
push_data( 0 );
}
} else {
// day == 0 only in the first round of the loop
// append any missing day until one before the next day with a zero value
int d{ m==1 ? from_day : 2 }, m{ month }, y{ year };
prev_instant( y, m, d );
while ( y<=year && m<=month && d<next_day ) {
set_date( y, m, d );
push_data( 0 );
next_instant( y, m, d );
}
}
day = next_day;
count = 1;
}
// append the last count
if ( day > 0 ) {
set_date( year, month , day );
push_data( count );
}
// append any missing day up to the last one with a zero value
const int max_d{ m==n_months ? to_day : this->getMonthDays( year, month ) };
int d{ day>0 ? day : 1 }, m{ month }, y{ year };
while ( y<=year && m<=month && d<=max_d ) {
set_date( y, m, d );
push_data( 0 );
next_instant( y, m, d );
}
}
// increase the month
if ( ++month > 12 ) {
month = 1;
++year;
}
}
if ( no_data ) {
return;
}
// append one day after the last one
int day{ to_day };
next_instant( year, month, day );
set_date( year, month , day );
push_data( 0 );
}
if ( data.capacity() > data.size() ) {
data.shrink_to_fit();
}
result.emplace( std::move(data) );
}
void DbQuery::getGlobalCounts( std::optional<GlobalsData>& result, QStringView web_server, const stats_dates_t& dates ) const
{
DatabaseWrapper db{ DatabaseHandler::get( DatabaseType::Data, true ) };
db.open( this->db_path, this->dialog_level==DL_EXPLANATORY );
bool no_data{ true };
int max_date_year, max_date_month, max_date_day;
double n_days{0.0};
size_t max_date_count{0};
std::array<double, 7> week_days_count{ 0, 0, 0, 0, 0, 0, 0 };
GlobalsData data;
const auto week_day_from{
[](const int y, const int m, const int d)->size_t
{ return static_cast<size_t>( QDate(y,m,d).dayOfWeek()-1 ); }
};
const auto update_perf{
[](Perfs& perf, const int val)
{
if ( val >= 0 ) {
if ( const size_t v{static_cast<size_t>(val)}; v > 0ul) [[likely]] {
if ( v > perf.max ) {
perf.max = v;
}
perf.total += v;
}
++ perf.count;
}
}
};
for ( const auto& [year, dates_] : dates ) {
for ( const auto& [month, dates__] : dates_ ) {
int d{-1}, h{-1}, tt{-1}, bs{-1}, br{-1},
day{-1}, hour{-1};
double hour_count{0};
size_t day_count{0};
QString protocol, method, uri, user_agent;
QueryWrapper query{ db.getQuery() };
query( QStringLiteral(R"(SELECT "day","hour","protocol","method","uri","user_agent","time_taken","bytes_sent","bytes_received" FROM "%1" WHERE "year"=%2 AND "month"=%3 ORDER BY "day","hour" ASC;)")
.arg( web_server )
.arg( year ).arg( month ) );
if ( query.size() == 0ul ) {
// no data in this month
continue;
}
no_data &= false;
while ( query->next() ) {
// day
if ( ! query[0].isNull() ) {
d = toInt( query[0] );
}
// hour
if ( ! query[1].isNull() ) {
h = toInt( query[1] );
}
// protocol
if ( ! query[2].isNull() ) {
protocol = toString( query[2] );
}
// method
if ( ! query[3].isNull() ) {
method = toString( query[3] );
}
// uri
if ( ! query[4].isNull() ) {
uri = toString( query[4] );
}
// user agent
if ( ! query[5].isNull() ) {
user_agent = toString( query[5] );
}
// time taken
if ( ! query[6].isNull() ) {
tt = toInt( query[6] );
}
// bytes sent
if ( ! query[7].isNull() ) {
bs = toInt( query[7] );
}
// bytes received
if ( ! query[8].isNull() ) {
br = toInt( query[8] );
}
// process the day count
if ( d > 0 ) {
if ( day == -1 ) {
day = d;
}
if ( d == day ) {
++ day_count;
} else {
++ n_days;
// sum the day count to the total count
data.req_count += day_count;
// sum the day count to the relative day of the week count
const size_t week_day{ week_day_from(year,month,day) };
data.traf.day[ week_day ] += static_cast<double>(day_count);
++ week_days_count[ week_day ];
// check the max date count
if ( day_count > max_date_count ) {
max_date_count = day_count;
max_date_year = year;
max_date_month = month;
max_date_day = day;
}
day_count = 1;
day = d;
}
}
// process the hour count
if ( h >= 0 ) {
if ( hour == -1 ) {
hour = h;
}
if ( h == hour ) {
++ hour_count;
} else {
data.traf.hour[ hour ] += hour_count;
hour_count = 1;
hour = h;
}
}
// sum the time taken
update_perf( data.perf.time_taken, tt );
// sum the bytes sent
update_perf( data.perf.bytes_sent, bs );
// sum the bytes received
update_perf( data.perf.bytes_recv, br );
// process the protocol
if ( ! protocol.isEmpty() ) {
++ data.recurs.protocol[ protocol ];
}
// process the method
if ( ! method.isEmpty() ) {
++ data.recurs.method[ method ];
}
// process the uri
if ( ! uri.isEmpty() ) {
++ data.recurs.uri[ uri ];
}
// process the user-agent
if ( ! user_agent.isEmpty() ) {
++ data.recurs.user_agent[ user_agent ];
}
}
// complete the remaining stats
// append the last hour
if ( hour >= 0 ) {
data.traf.hour[ hour ] += hour_count;
}
// sum the day count to the total count
data.req_count += day_count;
// sum the day count to the relative day of the week count
const size_t week_day{ week_day_from(year,month,day) };
data.traf.day[ week_day ] += static_cast<double>(day_count);
++ week_days_count[ week_day ];
// check the max date count
if ( day_count > max_date_count ) {
max_date_count = day_count;
max_date_year = year;
max_date_month = month;
max_date_day = day;
}
}
}
if ( no_data ) {
return;
}
// finally process some of the values
// process the hours of the day
if ( n_days > 0.0 ) {
std::transform( data.traf.hour.cbegin(), data.traf.hour.cend(), data.traf.hour.begin(),
[n_days](const double count){ return count / n_days; } );
}
// process the day of the week
for ( auto [total,count] : std::views::zip( data.traf.day, week_days_count ) ) {
if ( count > 0.0 ) {
total /= count;
}
}
// make the max-date tuple
data.traf.date = std::make_tuple(
PrintSec::printableDate( max_date_year, max_date_month, max_date_day ),
QString::number( max_date_count ) );
result.emplace( std::move(data) );
}