Skip to content
Browse files

Initial version of the histogram (works only within a single connecti…

…on).
  • Loading branch information...
1 parent 9dbb6d7 commit aabec368352d0f98d12715a7778c8a4aa45301c6 @tvondra committed
Showing with 519 additions and 0 deletions.
  1. +20 −0 Makefile
  2. +4 −0 query_histogram--1.0.sql
  3. +128 −0 query_histogram.c
  4. +6 −0 query_histogram.control
  5. +337 −0 queryhist.c
  6. +24 −0 queryhist.h
View
20 Makefile
@@ -0,0 +1,20 @@
+MODULE_big = query_histogram
+OBJS = query_histogram.o queryhist.o
+
+EXTENSION = query_histogram
+DATA = query_histogram--1.0.sql
+MODULES = query_histogram
+
+CFLAGS=`pg_config --includedir-server`
+
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+
+all: query_histogram.so
+
+query_histogram.so: query_histogram.o queryhist.o
+
+queryhist.o : queryhist.c
+
+query_histogram.o: query_histogram.c
View
4 query_histogram--1.0.sql
@@ -0,0 +1,4 @@
+CREATE OR REPLACE FUNCTION query_histogram(OUT bin_from INT, OUT bin_to INT, OUT bin_count INT, OUT bin_count_pct REAL, OUT bin_time INT, OUT bin_time_pct REAL)
+ RETURNS SETOF record
+ AS 'MODULE_PATHNAME', 'query_histogram'
+ LANGUAGE C IMMUTABLE;
View
128 query_histogram.c
@@ -0,0 +1,128 @@
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#include "queryhist.h"
+#include "postgres.h"
+#include "fmgr.h"
+
+#include "funcapi.h"
+
+#ifdef PG_MODULE_MAGIC
+PG_MODULE_MAGIC;
+#endif
+
+PG_FUNCTION_INFO_V1(query_histogram);
+
+Datum query_histogram(PG_FUNCTION_ARGS);
+
+Datum
+query_histogram(PG_FUNCTION_ARGS)
+{
+ FuncCallContext *funcctx;
+ TupleDesc tupdesc;
+ AttInMetadata *attinmeta;
+ histogram_data* data;
+
+ /* init on the first call */
+ if (SRF_IS_FIRSTCALL()) {
+
+ MemoryContext oldcontext;
+
+ funcctx = SRF_FIRSTCALL_INIT();
+ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+ data = query_hist_get_data();
+
+ /* init (open file, etc.), maybe read all the data in memory
+ * so that the file is not kept open for a long time */
+ funcctx->user_fctx = data;
+ funcctx->max_calls = data->bins_count;
+
+ if (data->bins_count > 0) {
+ funcctx->max_calls = data->bins_count + 1;
+ }
+
+ /* Build a tuple descriptor for our result type */
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("function returning record called in context "
+ "that cannot accept type record")));
+
+ /*
+ * generate attribute metadata needed later to produce tuples from raw
+ * C strings
+ */
+ attinmeta = TupleDescGetAttInMetadata(tupdesc);
+ funcctx->attinmeta = attinmeta;
+ funcctx->tuple_desc = tupdesc;
+
+ /* switch back to the old context */
+ MemoryContextSwitchTo(oldcontext);
+
+ }
+
+ /* init the context */
+ funcctx = SRF_PERCALL_SETUP();
+
+ /* check if we have more data */
+ if (funcctx->max_calls > funcctx->call_cntr)
+ {
+ HeapTuple tuple;
+ Datum result;
+ Datum values[6];
+ bool nulls[6];
+
+ int binIdx;
+
+ binIdx = funcctx->call_cntr;
+
+ data = (histogram_data*)funcctx->user_fctx;
+
+ memset(nulls, 0, sizeof(nulls));
+
+ values[0] = UInt32GetDatum(binIdx * data->bins_width);
+
+ if (funcctx->max_calls - 1 == funcctx->call_cntr) {
+ values[1] = UInt32GetDatum(0);
+ nulls[1] = TRUE;
+ } else {
+ values[1] = UInt32GetDatum((binIdx+1)* data->bins_width);
+ }
+
+ values[2] = UInt32GetDatum(data->count_data[binIdx]);
+
+ if (data->total_count > 0) {
+ values[3] = Float4GetDatum(100*data->count_data[binIdx] / data->total_count);
+ } else {
+ values[3] = Float4GetDatum(0);
+ }
+
+ values[4] = UInt32GetDatum(data->time_data[binIdx]);
+
+ if (data->total_time > 0) {
+ values[5] = Float4GetDatum(100*data->time_data[binIdx] / data->total_time);
+ } else {
+ values[5] = Float4GetDatum(0);
+ }
+
+ /* Build and return the tuple. */
+ tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
+
+ /* make the tuple into a datum */
+ result = HeapTupleGetDatum(tuple);
+
+ /* Here we want to return another item: */
+ SRF_RETURN_NEXT(funcctx, result);
+
+ }
+ else
+ {
+ /* Here we are done returning items and just need to clean up: */
+ SRF_RETURN_DONE(funcctx);
+ }
+
+}
View
6 query_histogram.control
@@ -0,0 +1,6 @@
+# query histogram
+comment = 'Provides access to query histogram.'
+default_version = '1.0'
+relocatable = true
+
+module_pathname = '$libdir/query_histogram'
View
337 queryhist.c
@@ -0,0 +1,337 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "postgres.h"
+#include "queryhist.h"
+
+#include "commands/explain.h"
+#include "executor/instrument.h"
+#include "utils/guc.h"
+
+/* How are the histogram bins scaled? */
+typedef enum {
+ HISTOGRAM_LINEAR,
+ HISTOGRAM_LOG
+} histogram_type;
+
+static const struct config_enum_entry histogram_type_options[] = {
+ {"linear", HISTOGRAM_LINEAR, false},
+ {"log", HISTOGRAM_LOG, false}
+};
+
+#define HIST_BINS_MAX 1000
+
+static int query_histogram_bins_count = 0; /* number of bins (0 - disable) */
+static int query_histogram_bins_width = 10; /* msec (bin width) */
+static int query_histogram_sample_pct = 5; /* portion of queries to sample */
+static int query_histogram_type = HISTOGRAM_LINEAR; /* histogram type */
+
+static int nesting_level = 0;
+
+static unsigned long histogram_count_bins[HIST_BINS_MAX+1];
+static unsigned long histogram_time_bins[HIST_BINS_MAX+1];
+
+#define query_histogram_enabled() \
+ ((query_histogram_bins_count > 0) && (nesting_level == 0))
+
+/* Saved hook values in case of unload */
+static ExecutorFinish_hook_type prev_ExecutorFinish = NULL;
+static ExecutorEnd_hook_type prev_ExecutorEnd = NULL;
+static ExecutorStart_hook_type prev_ExecutorStart = NULL;
+static ExecutorRun_hook_type prev_ExecutorRun = NULL;
+
+static void set_histogram_bins_count_hook(int newval, void *extra);
+static void set_histogram_bins_width_hook(int newval, void *extra);
+static void set_histogram_sample_hook(int newval, void *extra);
+
+void _PG_init(void);
+void _PG_fini(void);
+
+static void explain_ExecutorStart(QueryDesc *queryDesc, int eflags);
+static void explain_ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, long count);
+static void explain_ExecutorFinish(QueryDesc *queryDesc);
+static void explain_ExecutorEnd(QueryDesc *queryDesc);
+
+/*
+ * Module load callback
+ */
+void
+_PG_init(void)
+{
+ /* Define custom GUC variables. */
+ DefineCustomIntVariable("query_histogram.bin_count",
+ "Sets the number of bins of the histogram.",
+ "Zero disables collecting the histogram.",
+ &query_histogram_bins_count,
+ 0,
+ 0, 1000,
+ PGC_SUSET,
+ GUC_UNIT_MS,
+ NULL,
+ &set_histogram_bins_count_hook,
+ NULL);
+
+ DefineCustomIntVariable("query_histogram.bin_width",
+ "Sets the width of the histogram bin.",
+ NULL,
+ &query_histogram_bins_width,
+ 10,
+ 1, 1000,
+ PGC_SUSET,
+ GUC_UNIT_MS,
+ NULL,
+ &set_histogram_bins_width_hook,
+ NULL);
+
+ DefineCustomIntVariable("query_histogram.sample_pct",
+ "What portion of the queries should be sampled (in percent).",
+ NULL,
+ &query_histogram_sample_pct,
+ 5,
+ 1, 100,
+ PGC_SUSET,
+ GUC_UNIT_MS,
+ NULL,
+ &set_histogram_sample_hook,
+ NULL);
+
+ DefineCustomEnumVariable("query_histogram.histogram_type",
+ "Type of the histogram (how the bin width is computed).",
+ NULL,
+ &query_histogram_type,
+ HISTOGRAM_LINEAR,
+ histogram_type_options,
+ PGC_SUSET,
+ 0,
+ NULL,
+ NULL,
+ NULL);
+
+ EmitWarningsOnPlaceholders("query_histogram");
+
+ /* Install hooks. */
+ prev_ExecutorStart = ExecutorStart_hook;
+ ExecutorStart_hook = explain_ExecutorStart;
+ prev_ExecutorRun = ExecutorRun_hook;
+ ExecutorRun_hook = explain_ExecutorRun;
+ prev_ExecutorFinish = ExecutorFinish_hook;
+ ExecutorFinish_hook = explain_ExecutorFinish;
+ prev_ExecutorEnd = ExecutorEnd_hook;
+ ExecutorEnd_hook = explain_ExecutorEnd;
+
+ query_hist_init(query_histogram_bins_count, query_histogram_bins_width);
+
+}
+
+
+/*
+ * Module unload callback
+ */
+void
+_PG_fini(void)
+{
+ /* Uninstall hooks. */
+ ExecutorStart_hook = prev_ExecutorStart;
+ ExecutorRun_hook = prev_ExecutorRun;
+ ExecutorFinish_hook = prev_ExecutorFinish;
+ ExecutorEnd_hook = prev_ExecutorEnd;
+}
+
+/*
+ * ExecutorStart hook: start up logging if needed
+ */
+static void
+explain_ExecutorStart(QueryDesc *queryDesc, int eflags)
+{
+
+ if (prev_ExecutorStart)
+ prev_ExecutorStart(queryDesc, eflags);
+ else
+ standard_ExecutorStart(queryDesc, eflags);
+
+ if (query_histogram_enabled())
+ {
+ /*
+ * Set up to track total elapsed time in ExecutorRun. Make sure the
+ * space is allocated in the per-query context so it will go away at
+ * ExecutorEnd.
+ */
+ if (queryDesc->totaltime == NULL)
+ {
+ MemoryContext oldcxt;
+
+ oldcxt = MemoryContextSwitchTo(queryDesc->estate->es_query_cxt);
+ queryDesc->totaltime = InstrAlloc(1, INSTRUMENT_ALL);
+ MemoryContextSwitchTo(oldcxt);
+ }
+ }
+}
+
+/*
+ * ExecutorRun hook: all we need do is track nesting depth
+ */
+static void
+explain_ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, long count)
+{
+ nesting_level++;
+ PG_TRY();
+ {
+ if (prev_ExecutorRun)
+ prev_ExecutorRun(queryDesc, direction, count);
+ else
+ standard_ExecutorRun(queryDesc, direction, count);
+ nesting_level--;
+ }
+ PG_CATCH();
+ {
+ nesting_level--;
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+}
+
+/*
+ * ExecutorFinish hook: all we need do is track nesting depth
+ */
+static void
+explain_ExecutorFinish(QueryDesc *queryDesc)
+{
+ nesting_level++;
+ PG_TRY();
+ {
+ if (prev_ExecutorFinish)
+ prev_ExecutorFinish(queryDesc);
+ else
+ standard_ExecutorFinish(queryDesc);
+ nesting_level--;
+ }
+ PG_CATCH();
+ {
+ nesting_level--;
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+}
+
+/*
+ * ExecutorEnd hook: log results if needed
+ */
+static void
+explain_ExecutorEnd(QueryDesc *queryDesc)
+{
+ if (queryDesc->totaltime && query_histogram_enabled())
+ {
+ int msec;
+
+ /*
+ * Make sure stats accumulation is done. (Note: it's okay if several
+ * levels of hook all do this.)
+ */
+ InstrEndLoop(queryDesc->totaltime);
+
+ /* Log plan if duration is exceeded. */
+ msec = (int)round(queryDesc->totaltime->total * 1000.0);
+
+ if (rand() % 100 < query_histogram_sample_pct) {
+ query_hist_add_query((int)round(msec));
+ }
+
+ }
+
+ if (prev_ExecutorEnd)
+ prev_ExecutorEnd(queryDesc);
+ else
+ standard_ExecutorEnd(queryDesc);
+}
+
+void query_hist_free() {
+
+ /* free the original histogram (if needed) */
+ if (histogram_count_bins != NULL) {
+ pfree(histogram_count_bins);
+ pfree(histogram_time_bins);
+ }
+
+}
+
+void query_hist_init(int bins, int step) {
+
+ srand(bins + step);
+
+ query_hist_reset(bins);
+
+}
+
+void query_hist_reset(int bins) {
+
+ memset(histogram_count_bins, 0, HIST_BINS_MAX+1);
+ memset(histogram_time_bins, 0, HIST_BINS_MAX+1);
+}
+
+void query_hist_add_query(int duration) {
+
+ int bin = duration / query_histogram_bins_width;
+
+ /* queries that take longer than the last bin should go to
+ * the (HIST_BINS_MAX+1) bin */
+ if (bin >= HIST_BINS_MAX) {
+ bin = HIST_BINS_MAX;
+ }
+
+ histogram_count_bins[bin] += 1;
+ histogram_time_bins[bin] += duration;
+
+}
+
+histogram_data * query_hist_get_data() {
+
+ int i = 0;
+
+ histogram_data * tmp = (histogram_data *)palloc(sizeof(histogram_data));
+
+ memset(tmp, 0, sizeof(histogram_data));
+
+ tmp->bins_count = query_histogram_bins_count;
+ tmp->bins_width = query_histogram_bins_width;
+
+ if (query_histogram_bins_count > 0) {
+
+ tmp->count_data = (unsigned long *) palloc(sizeof(unsigned long) * (query_histogram_bins_count+1));
+ tmp->time_data = (unsigned long *) palloc(sizeof(unsigned long) * (query_histogram_bins_count+1));
+
+ memcpy(tmp->count_data, histogram_count_bins, sizeof(unsigned long) * (query_histogram_bins_count+1));
+ memcpy(tmp->time_data, histogram_time_bins, sizeof(unsigned long) * (query_histogram_bins_count+1));
+
+ for (i = 0; i < query_histogram_bins_count+1; i++) {
+ tmp->total_count += tmp->count_data[i];
+ tmp->total_time += tmp->time_data[i];
+ }
+
+ }
+
+ return tmp;
+
+}
+
+int query_hist_get_bins() {
+ return query_histogram_bins_count;
+}
+
+int query_hist_get_step() {
+ return query_histogram_bins_width;
+}
+
+static
+void set_histogram_bins_count_hook(int newval, void *extra) {
+ query_hist_init(newval, query_histogram_bins_width);
+}
+
+static
+void set_histogram_bins_width_hook(int newval, void *extra) {
+ query_hist_init(query_histogram_bins_count, newval);
+}
+
+static
+void set_histogram_sample_hook(int newval, void *extra) {
+ query_hist_init(query_histogram_bins_count, query_histogram_bins_width);
+}
View
24 queryhist.h
@@ -0,0 +1,24 @@
+
+typedef struct histogram_data {
+
+ unsigned int bins_count;
+ unsigned int bins_width;
+
+ unsigned long total_count;
+ unsigned long total_time;
+
+ unsigned long * count_data;
+ unsigned long * time_data;
+
+} histogram_data;
+
+void query_hist_init(int bins, int step);
+void query_hist_reset(int bins);
+void query_hist_free();
+
+void query_hist_add_query(int duration);
+
+int query_hist_get_bins();
+int query_hist_get_step();
+
+histogram_data * query_hist_get_data();

0 comments on commit aabec36

Please sign in to comment.
Something went wrong with that request. Please try again.