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:
parent
a5f9584b02
commit
b2d676b1ed
6 changed files with 269 additions and 54 deletions
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
78
c/pgst.c
78
c/pgst.c
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue