pg: Add query tracing & prepare/execute time measurements

What I'd really like, in addition to this, is a way to extract a query
from an $st object that can be run in the psql CLI. VNDB has a debugging
feature for that, but it's less trivial to make that work with binary
query parameters.
This commit is contained in:
Yorhel 2025-02-22 15:14:51 +01:00
parent a5f9584b02
commit b2d676b1ed
6 changed files with 269 additions and 54 deletions

View file

@ -170,3 +170,9 @@ static SV *fustr_done_(pTHX_ fustr *s) {
#define fustr_writebeT(T, bits, s, in) fu_tobeT(T, bits, fustr_write_buf(s, bits>>3), in)
#define fustr_writebeI(bits, s, in) fustr_writebeT(I##bits, bits, s, in)
#define fustr_writebeU(bits, s, in) fustr_writebeT(U##bits, bits, s, in)
/* Return the difference between two struct timespecs as fractional seconds. */
static double fu_timediff(const struct timespec *a, const struct timespec *b) {
return ((double)(a->tv_sec - b->tv_sec)) + (double)(a->tv_nsec - b->tv_nsec) / 1000000000.0;
}

View file

@ -29,6 +29,7 @@ static void fupg_prep_destroy(fupg_prep *p) {
typedef struct {
SV *self;
PGconn *conn;
SV *trace;
UV prep_counter;
UV cookie_counter;
UV cookie; /* currently active transaction object; 0 = none active */
@ -159,6 +160,7 @@ static SV *fupg_connect(pTHX_ const char *str) {
fupg_conn *c = safemalloc(sizeof(fupg_conn));
c->conn = conn;
c->trace = NULL;
c->prep_counter = c->cookie_counter = c->cookie = 0;
c->stflags = FUPG_CACHE;
c->ntypes = 0;

View file

@ -12,6 +12,7 @@ typedef struct {
int prepared;
char name[32];
fupg_prep *prep;
double preptime;
PGresult *describe; /* shared with prep->describe if prep is set */
/* Set during execute */
@ -19,14 +20,36 @@ typedef struct {
const char **param_values; /* Points into conn->buf or st->bind SVs, may be invalid after exec */
int *param_lengths;
int *param_formats;
double exectime;
fupg_tio send;
fupg_tio *recv;
PGresult *result;
} fupg_st;
static void fupg_tracecb(pTHX_ fupg_st *st) {
if (!st->conn->trace) return;
dSP;
ENTER;
SAVETMPS;
PUSHMARK(SP);
mXPUSHs(sv_bless(newRV_inc(st->self), gv_stashpv("FU::Pg::st", GV_ADD)));
PUTBACK;
call_sv(st->conn->trace, G_DISCARD);
FREETMPS;
LEAVE;
}
static SV *fupg_exec(pTHX_ fupg_conn *c, const char *sql) {
struct timespec t_start;
clock_gettime(CLOCK_MONOTONIC, &t_start);
PGresult *r = PQexec(c->conn, sql);
struct timespec t_end;
clock_gettime(CLOCK_MONOTONIC, &t_end);
if (!r) fupg_conn_croak(c, "exec");
switch (PQresultStatus(r)) {
case PGRES_EMPTY_QUERY:
@ -34,8 +57,21 @@ static SV *fupg_exec(pTHX_ fupg_conn *c, const char *sql) {
case PGRES_TUPLES_OK: break;
default: fupg_result_croak(r, "exec", sql);
}
SV *ret = fupg_exec_result(aTHX_ r);
PQclear(r);
if (c->trace) {
fupg_st *st = safecalloc(1, sizeof(*st));
st->conn = c;
SvREFCNT_inc(c->self);
st->query = savepv(sql);
st->stflags = c->stflags;
st->result = r;
st->exectime = fu_timediff(&t_end, &t_start);
fu_selfobj(st, "FU::Pg::st");
fupg_tracecb(aTHX_ st);
} else {
PQclear(r);
}
return ret;
}
@ -87,6 +123,7 @@ static void fupg_st_destroy(pTHX_ fupg_st *st) {
static void fupg_st_prepare(pTHX_ fupg_st *st) {
if (st->describe) return;
if (st->prepared) fu_confess("invalid attempt to re-prepare invalid statement");
if (st->result) fu_confess("invalid attempt to prepare already executed statement");
if (st->stflags & FUPG_CACHE)
st->prep = fupg_prepared_ref(aTHX_ st->conn, st->query);
@ -101,6 +138,9 @@ static void fupg_st_prepare(pTHX_ fupg_st *st) {
if (st->prep) st->prep->name = st->conn->prep_counter;
snprintf(st->name, sizeof(st->name), "fupg%"UVuf, st->conn->prep_counter);
struct timespec t_start;
clock_gettime(CLOCK_MONOTONIC, &t_start);
/* Send prepare + describe in a pipeline to avoid a double round-trip with the server */
PQenterPipelineMode(st->conn->conn);
PQsendPrepare(st->conn->conn, st->name, st->query, 0, NULL);
@ -122,6 +162,10 @@ static void fupg_st_prepare(pTHX_ fupg_st *st) {
PQclear(prep);
st->prepared = 1;
struct timespec t_end;
clock_gettime(CLOCK_MONOTONIC, &t_end);
st->preptime = fu_timediff(&t_end, &t_start);
if (!desc) {
PQclear(sync);
fupg_conn_croak(st->conn , "prepare");
@ -140,6 +184,8 @@ static void fupg_st_prepare(pTHX_ fupg_st *st) {
}
static SV *fupg_st_param_types(pTHX_ fupg_st *st) {
if (st->result && !st->describe)
return sv_2mortal(newRV_noinc((SV *)newAV()));
fupg_st_prepare(aTHX_ st);
int i, nparams = PQnparams(st->describe);
AV *av = nparams == 0 ? newAV() : newAV_alloc_x(nparams);
@ -148,16 +194,28 @@ static SV *fupg_st_param_types(pTHX_ fupg_st *st) {
return sv_2mortal(newRV_noinc((SV *)av));
}
static SV *fupg_st_param_values(pTHX_ fupg_st *st) {
int i;
AV *av = st->nbind == 0 ? newAV() : newAV_alloc_x(st->nbind);
for (i=0; i<st->nbind; i++)
av_push_simple(av, SvREFCNT_inc(st->bind[i]));
return sv_2mortal(newRV_noinc((SV *)av));
}
static SV *fupg_st_columns(pTHX_ fupg_st *st) {
fupg_st_prepare(aTHX_ st);
int i, nfields = PQnfields(st->describe);
PGresult *r = st->result;
if (!r) {
fupg_st_prepare(aTHX_ st);
r = st->describe;
}
int i, nfields = PQnfields(r);
AV *av = nfields == 0 ? newAV() : newAV_alloc_x(nfields);
for (i=0; i<nfields; i++) {
HV *hv = newHV();
const char *name = PQfname(st->describe, i);
const char *name = PQfname(r, i);
hv_stores(hv, "name", newSVpvn_utf8(name, strlen(name), 1));
hv_stores(hv, "oid", newSViv(PQftype(st->describe, i)));
int tmod = PQfmod(st->describe, i);
hv_stores(hv, "oid", newSViv(PQftype(r, i)));
int tmod = PQfmod(r, i);
if (tmod >= 0) hv_stores(hv, "typemod", newSViv(tmod));
av_push_simple(av, newRV_noinc((SV *)hv));
}
@ -229,6 +287,8 @@ static void fupg_st_execute(pTHX_ fupg_st *st) {
* gather that just saves a bit of memory in exchange for more and smaller
* malloc()/free()'s. Performance-wise, it probably won't be much of an
* improvement */
struct timespec t_start;
clock_gettime(CLOCK_MONOTONIC, &t_start);
PGresult *r = PQexecPrepared(st->conn->conn,
st->name,
st->nbind,
@ -236,6 +296,10 @@ static void fupg_st_execute(pTHX_ fupg_st *st) {
st->param_lengths,
st->param_formats,
st->stflags & FUPG_TEXT_RESULTS ? 0 : 1);
struct timespec t_end;
clock_gettime(CLOCK_MONOTONIC, &t_end);
st->exectime = fu_timediff(&t_end, &t_start);
if (!r) fupg_conn_croak(st->conn , "exec");
switch (PQresultStatus(r)) {
case PGRES_COMMAND_OK:
@ -251,6 +315,8 @@ static void fupg_st_execute(pTHX_ fupg_st *st) {
fupg_tio_setup(aTHX_ st->conn, st->recv + i,
FUPGT_RECV | (st->stflags & FUPG_TEXT_RESULTS ? FUPGT_TEXT : 0),
PQftype(st->result, i), &refresh_done);
fupg_tracecb(aTHX_ st);
}
static SV *fupg_st_getval(pTHX_ fupg_st *st, int row, int col) {