Skip to content

Commit

Permalink
Add telemetry for access methods
Browse files Browse the repository at this point in the history
Add telemetry for tracking access methods used, number of pages for
each access method, and number of instances using each access method.

Also introduces a type-based function `ts_jsonb_set_value_by_type` that
can generate correct JSONB based on the PostgreSQL type. It will
generate "bare" values for numerics, and strings for anything else
using the output function for the type.

To test this for string values, we update `ts_jsonb_add_interval` to
use this new function, which is calling the output function for the
type, just like `ts_jsonb_set_value_by_type`.
  • Loading branch information
mkindahl committed Apr 10, 2024
1 parent 7ffdd07 commit ea22843
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 14 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/code_style.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ jobs:
- name: Run codespell
run: |
find . -type f \( -name "*.c" -or -name "*.h" -or -name "*.yaml" -or -name "*.sh" \) \
-exec codespell -L "inh,larg,inout" {} \+
-exec codespell -L "brin,inh,larg,inout" {} \+
cc_checks:
name: Check code formatting
Expand Down
1 change: 1 addition & 0 deletions .unreleased/pr_6810
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Implements: #6810 Add telemetry for access methods
66 changes: 54 additions & 12 deletions src/jsonb_utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -57,34 +57,76 @@ ts_jsonb_add_str(JsonbParseState *state, const char *key, const char *value)
ts_jsonb_add_value(state, key, &json_value);
}

static PGFunction
get_convert_func(Oid typeid)
{
switch (typeid)
{
case INT2OID:
return int2_numeric;
case INT4OID:
return int4_numeric;
case INT8OID:
return int8_numeric;
default:
return NULL;
}
}

void
ts_jsonb_add_int32(JsonbParseState *state, const char *key, const int32 int_value)
ts_jsonb_set_value_by_type(JsonbValue *value, Oid typeid, Datum datum)
{
Numeric value;
switch (typeid)
{
Oid typeOut;
bool isvarlena;
char *str;
PGFunction func;

case INT2OID:
case INT4OID:
case INT8OID:
case NUMERICOID:
func = get_convert_func(typeid);
value->type = jbvNumeric;
value->val.numeric = DatumGetNumeric(func ? DirectFunctionCall1(func, datum) : datum);
break;

default:
getTypeOutputInfo(typeid, &typeOut, &isvarlena);
str = OidOutputFunctionCall(typeOut, datum);
value->type = jbvString;
value->val.string.val = str;
value->val.string.len = strlen(str);
break;
}
}

value = DatumGetNumeric(DirectFunctionCall1(int4_numeric, Int32GetDatum(int_value)));
void
ts_jsonb_add_int32(JsonbParseState *state, const char *key, const int32 int_value)
{
JsonbValue json_value;

ts_jsonb_add_numeric(state, key, value);
ts_jsonb_set_value_by_type(&json_value, INT4OID, Int32GetDatum(int_value));
ts_jsonb_add_value(state, key, &json_value);
}

void
ts_jsonb_add_int64(JsonbParseState *state, const char *key, const int64 int_value)
{
Numeric value;

value = DatumGetNumeric(DirectFunctionCall1(int8_numeric, Int64GetDatum(int_value)));
JsonbValue json_value;

ts_jsonb_add_numeric(state, key, value);
ts_jsonb_set_value_by_type(&json_value, INT8OID, Int64GetDatum(int_value));
ts_jsonb_add_value(state, key, &json_value);
}

void
ts_jsonb_add_interval(JsonbParseState *state, const char *key, Interval *interval)
{
char *value;

value = DatumGetCString(DirectFunctionCall1(interval_out, IntervalPGetDatum(interval)));
JsonbValue json_value;

ts_jsonb_add_str(state, key, value);
ts_jsonb_set_value_by_type(&json_value, INTERVALOID, IntervalPGetDatum(interval));
ts_jsonb_add_value(state, key, &json_value);
}

void
Expand Down
1 change: 1 addition & 0 deletions src/jsonb_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ extern TSDLLEXPORT void ts_jsonb_add_int64(JsonbParseState *state, const char *k
const int64 value);
extern TSDLLEXPORT void ts_jsonb_add_numeric(JsonbParseState *state, const char *key,
const Numeric value);
extern TSDLLEXPORT void ts_jsonb_set_value_by_type(JsonbValue *value, Oid typeid, Datum datum);

extern void ts_jsonb_add_value(JsonbParseState *state, const char *key, JsonbValue *value);

Expand Down
90 changes: 90 additions & 0 deletions src/telemetry/telemetry.c
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,87 @@ add_replication_telemetry(JsonbParseState *state)
#define REQ_RELS_CONTINUOUS_AGGS "continuous_aggregates"
#define REQ_FUNCTIONS_USED "functions_used"
#define REQ_REPLICATION "replication"
#define REQ_ACCESS_METHODS "access_methods"

/*
* Add the result of a query as a sub-object to the JSONB.
*
* Each row from the query generates a separate object keyed by one of the
* columns. Each row will be represented as an object and stored under the
* "key" column. For example, with this query:
*
* select amname as name,
* sum(relpages) as pages,
* count(*) as instances
* from pg_class join pg_am on relam = pg_am.oid
* group by pg_am.oid;
*
* might generate the object
*
* {
* "brin" : {
* "instances" : 44,
* "pages" : 432
* },
* "btree" : {
* "instances" : 99,
* "pages" : 1234
* }
* }
*/
static void
add_query_result_dict(JsonbParseState *state, const char *query)
{
MemoryContext orig_context = CurrentMemoryContext;

int res;
if (SPI_connect() != SPI_OK_CONNECT)
elog(ERROR, "could not connect to SPI");

/* Lock down search_path */
res = SPI_execute("SET LOCAL search_path TO pg_catalog, pg_temp", false, 0);
Ensure(res >= 0, "could not set search path");

res = SPI_execute(query, true, 0);
Ensure(res >= 0, "could not execute query");

MemoryContext spi_context = MemoryContextSwitchTo(orig_context);

(void) pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
for (uint64 r = 0; r < SPI_processed; r++)
{
char *key_string = SPI_getvalue(SPI_tuptable->vals[r], SPI_tuptable->tupdesc, 1);
JsonbValue key = {
.type = jbvString,
.val.string.val = pstrdup(key_string),
.val.string.len = strlen(key_string),
};

(void) pushJsonbValue(&state, WJB_KEY, &key);

(void) pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL);
for (int c = 1; c < SPI_tuptable->tupdesc->natts; ++c)
{
bool isnull;
Datum val_datum =
SPI_getbinval(SPI_tuptable->vals[r], SPI_tuptable->tupdesc, c + 1, &isnull);
if (!isnull)
{
char *key_string = SPI_fname(SPI_tuptable->tupdesc, c + 1);
JsonbValue value;
ts_jsonb_set_value_by_type(&value,
SPI_gettypeid(SPI_tuptable->tupdesc, c + 1),
val_datum);
ts_jsonb_add_value(state, key_string, &value);
}
}
pushJsonbValue(&state, WJB_END_OBJECT, NULL);
}
MemoryContextSwitchTo(spi_context);
res = SPI_finish();
Assert(res == SPI_OK_FINISH);
(void) pushJsonbValue(&state, WJB_END_OBJECT, NULL);
}

static Jsonb *
build_telemetry_report()
Expand Down Expand Up @@ -937,6 +1018,15 @@ build_telemetry_report()
add_replication_telemetry(parse_state);
pushJsonbValue(&parse_state, WJB_END_OBJECT, NULL);

key.type = jbvString;
key.val.string.val = REQ_ACCESS_METHODS;
key.val.string.len = strlen(REQ_ACCESS_METHODS);
(void) pushJsonbValue(&parse_state, WJB_KEY, &key);
add_query_result_dict(parse_state,
"SELECT amname AS name, sum(relpages) AS pages, count(*) AS "
"instances FROM pg_class JOIN pg_am ON relam = pg_am.oid "
"GROUP BY amname");

/* end of telemetry object */
result = pushJsonbValue(&parse_state, WJB_END_OBJECT, NULL);

Expand Down
14 changes: 13 additions & 1 deletion test/expected/telemetry.out
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,7 @@ WHERE key != 'os_name_pretty';
db_metadata
replication
build_os_name
access_methods
functions_used
install_method
installed_time
Expand Down Expand Up @@ -377,7 +378,7 @@ WHERE key != 'os_name_pretty';
num_compression_policies_fixed
num_user_defined_actions_fixed
num_continuous_aggs_policies_fixed
(37 rows)
(38 rows)

CREATE MATERIALIZED VIEW telemetry_report AS
SELECT t FROM get_telemetry_report() t;
Expand All @@ -395,6 +396,17 @@ SELECT t -> 'instance_metadata' FROM telemetry_report;
{"cloud": "ci"}
(1 row)

-- Check access methods
SELECT t->'access_methods' ? 'btree',
t->'access_methods' ? 'heap',
CAST(t->'access_methods'->'btree'->'pages' AS int) > 0,
CAST(t->'access_methods'->'btree'->'instances' AS int) > 0
FROM telemetry_report;
?column? | ?column? | ?column? | ?column?
----------+----------+----------+----------
t | t | t | t
(1 row)

WITH t AS (
SELECT t -> 'relations' AS rels
FROM telemetry_report
Expand Down
7 changes: 7 additions & 0 deletions test/sql/telemetry.sql
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,13 @@ SELECT t -> 'db_metadata' FROM telemetry_report;
-- check timescaledb_telemetry.cloud
SELECT t -> 'instance_metadata' FROM telemetry_report;

-- Check access methods
SELECT t->'access_methods' ? 'btree',
t->'access_methods' ? 'heap',
CAST(t->'access_methods'->'btree'->'pages' AS int) > 0,
CAST(t->'access_methods'->'btree'->'instances' AS int) > 0
FROM telemetry_report;

WITH t AS (
SELECT t -> 'relations' AS rels
FROM telemetry_report
Expand Down

0 comments on commit ea22843

Please sign in to comment.