- 誰かがQtアプリケーションを作成しました。
- ユーザーは、プログラムが動作中に大量のCPUを消費していると不満を言います。
- プロファイラーとstraceは、データベースがレイプされていることを示します。
そのような状況では、どのような種類のリクエスト、その数、実行時間を確認したいです。 PostgreSQLにpg_stat_statementsとpgBadgerがある場合、SQLiteの場合は自分でスクーターを作成する必要がありました。 スクーターは、接続ごとにsqlite3_profile呼び出しであり、受信した情報をログに記録します。
sqlite3_profileハンドラーをインストールする
QSqlDriverはハンドルを提供し、そこから
sqlite3*
ハンドラーを取得できます。 Qt5では、次のことを行う必要があります
Q_DECLARE_OPAQUE_POINTER(sqlite3*); Q_DECLARE_METATYPE(sqlite3*); //.... QVariant v = driver->handle(); const QString tpName = v.typeName(); if (v.isValid() && (tpName == "sqlite3*")) { sqlite3* handle = v.value<sqlite3*>(); if (handle != nullptr) { sqlite3_profile(handle, profile, this); } }
タイプ
sqlite3*
登録用のマクロ
Q_DECLARE_*
は、 QVariantから取り出されます。
ハンドラー
プロセッサの署名の形式は
void profile(void* userData, const char* sql, sqlite3_uint64 time)
パラメータ:
-
sqlite3_profile
に設定されたuserDataユーザーデータ。私の場合、これはthis
です。 - UTF-8のsqlクエリテキスト。
- クエリの実行時間はナノ秒ですが、現在の実装では最大ミリ秒の精度が提供されます。
ログクラスのソースコードは200行未満です
#pragma once #include <memory> #include <QtCore/QString> class QSqlDriver; namespace QSqliteProfiler { struct LogRecord { quint64 timestamp; quint64 duration; QString query; }; class Log { public: static Log& instance(); void setProfile(QSqlDriver* driver); void setLogFile(const QString& file); void write(const LogRecord& record); private: Log(); ~Log(); private: struct LogImpl; std::unique_ptr<LogImpl> m_impl; }; } inline QDataStream & operator<< (QDataStream& stream, const QSqliteProfiler::LogRecord record) { stream<<record.timestamp<<record.duration<<record.query; } inline QDataStream & operator>> (QDataStream& stream, QSqliteProfiler::LogRecord& record) { stream>>record.timestamp>>record.duration>>record.query; }
#include <mutex> #include <sqlite3.h> #include <QtCore/QFile> #include <QtCore/QDebug> #include <QtCore/QVariant> #include <QtCore/QDateTime> #include <QtCore/QMetaType> #include <QtCore/QDataStream> #include <QtSql/QSqlDriver> Q_DECLARE_OPAQUE_POINTER(sqlite3*); Q_DECLARE_METATYPE(sqlite3*); #include "Log.h" typedef std::lock_guard<std::mutex> LockGuard; namespace { void profile(void* userData, const char* sql, sqlite3_uint64 time) { using namespace QSqliteProfiler; const quint64 timestamp = QDateTime::currentMSecsSinceEpoch(); LogRecord record{timestamp, time, sql}; auto log = static_cast<Log*>(userData); log->write(record); } } namespace QSqliteProfiler { Log& Log::instance() { static Log log; return log; } struct Log::LogImpl { ~LogImpl() { LockGuard lock(fileMutex); if(file.isOpen()) { file.flush(); file.close(); } } QFile file; QDataStream stream; std::mutex fileMutex; }; Log::Log() : m_impl(new LogImpl) { } Log::~Log() = default; void Log::setProfile(QSqlDriver* driver) { QVariant v = driver->handle(); const QString tpName = v.typeName(); if (v.isValid() && (tpName == "sqlite3*")) { sqlite3* handle = v.value<sqlite3*>(); if (handle != nullptr) { sqlite3_profile(handle, profile, this); } } } void Log::setLogFile(const QString& file) { LockGuard lock(m_impl->fileMutex); if(m_impl->file.isOpen()) { m_impl->file.close(); } m_impl->file.setFileName(file); auto isOpen = m_impl->file.open(QIODevice::WriteOnly); if(isOpen) { m_impl->stream.setDevice(&m_impl->file); } else { qCritical()<<"Can not open file for writing, file"<<file; qDebug()<<m_impl->file.errorString(); exit(1); } } void Log::write(const LogRecord& record) { LockGuard lock(m_impl->fileMutex); if(m_impl->file.isOpen()) { m_impl->stream<<record; } } }
使用例
無意味で愚かな例
#include <QtCore/QDebug> #include <QtCore/QCoreApplication> #include <QtSql/QSqlQuery> #include <QtSql/QSqlDriver> #include <QtSql/QSqlDatabase> #include "Log.h" int main(int argc, char *argv[]) { using namespace QSqliteProfiler; QCoreApplication app(argc, argv); Log::instance().setLogFile("sqlite.profile"); QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE"); db.setDatabaseName("db.sqlite"); if (!db.open()) { qDebug()<<"Test data base not open"; return 1; } Log::instance().setProfile(db.driver()); QSqlQuery query; query.exec( "CREATE TABLE my_table (" "number integer PRIMARY KEY NOT NULL, " "address VARCHAR(255), " "age integer" ");"); QString str; //db.transaction(); for(int i = 0; i < 100; ++i) { QSqlQuery query1(db); query1.prepare("INSERT INTO my_table(number, address, age) VALUES (?, ?, ?)"); query1.bindValue(0, i); query1.bindValue(1, "hello world str."); query1.bindValue(2, 37); query1.exec(); } //db.commit(); return 0; }
ベースを開いた後、
Log::instance().setProfile(db.driver());
インストールし
Log::instance().setProfile(db.driver());
補助ユーティリティを使用して、ログをSQLiteデータベースに変換し、SQLの能力を使用します。
リクエスト
SELECT count(time) AS CNT, avg(time)/1000000 AS AVG , sum(time)/1000000 AS TIME, query FROM query GROUP BY query
結果を出します
CNT | 平均 | 時間 | クエリ |
---|---|---|---|
1 | 155.0 | 155 | CREATE TABLE my_table(数値整数PRIMARY KEY NOT NULL、アドレスVARCHAR(255)、年齢整数); |
100 | 149.55 | 14955 | INSERT INTO my_table(number、address、age)VALUES(?、?、?) |
トランザクションが使用されない場合の期待される結果。 平均挿入時間は150ミリ秒かかり、すべてのレコードの挿入時間は15秒です。 トランザクションのコメントを外すと、もう少し興味深いことがわかります。
CNT | 平均 | 時間 | クエリ |
---|---|---|---|
1 | 0.0 | 0 | 開始 |
1 | 129.0 | 129 | コミット |
1 | 132.0 | 132 | CREATE TABLE my_table(数値整数PRIMARY KEY NOT NULL、アドレスVARCHAR(255)、年齢整数); |
100 | 0.02 | 2 | INSERT INTO my_table(number、address、age)VALUES(?、?、?) |
すべてのコードはGitHubで入手できます 。 コンパイル時に、Qtで使用され、アプリケーションにリンクされた(sqlite3_profileに必要な)SQLiteのバージョンに互換性があることを確認してください。互換性がない場合、クラッシュする可能性があります。