Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
PS-10576 [8.4] Percona telemetry — safe non-default sysvars, global s…
…tatus

https://perconadev.atlassian.net/browse/PS-10576

Extend the percona_telemetry component so periodic reports include how
the server is configured and exercise

Configuration (server_config_info)
Report non-default global system variables whose
provenance is not COMPILED, using performance_schema.variables_info
joined to global_variables. Only explicitly allowlisted names are
collected; values that look like paths (containing / or \) are dropped.

Global status (server_status_info)
Add a separate JSON subtree for allowlisted
performance_schema.global_status rows (name + value), again with
path-like value filtering. This keeps counters and usage signals
(including Threads_running and Libcoredumper_enabled) out of the config
blob.

Server: libcoredumper introspection
Expose a read-only global status Libcoredumper_enabled (ON when built
with libcoredumper and the feature is enabled, otherwise OFF).
  • Loading branch information
kamil-holubicki committed May 4, 2026
commit e8ad12a12f4de17bbcd91e5dcc8c5d9b319ac0d6
127 changes: 126 additions & 1 deletion components/percona_telemetry/data_provider.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,25 @@
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */

#include <mysqld_error.h>
#include <algorithm>
#include <sstream>

#include "data_provider.h"
#include "logger.h"
#include "telemetry_status_allowlist.h"
#include "telemetry_sysvars_allowlist.h"

namespace {
inline const char *b2s(bool val) { return val ? "1" : "0"; }

/* Reject values that look like paths (defense in depth: the allow list is
already curated, but we double-check at collection time in case a value
shape changes upstream). */
inline bool is_value_safe_for_export(const std::string &value) {
return std::all_of(value.begin(), value.end(),
[](unsigned char ch) { return ch != '/' && ch != '\\'; });
}

/*
percona.telemetry user is created when server starts with telemetry
enabled. The user is deleted, when the server is started with telemetry
Expand Down Expand Up @@ -67,6 +77,11 @@ const char *size = "size";
// server configuration variables
const char *server_config_info = "server_config_info";
const char *thread_handling = "thread_handling";
const char *nondefault_allowlisted_sysvars = "nondefault_allowlisted_sysvars";
const char *variable_source = "variable_source";
const char *variable_value = "variable_value";
const char *server_status_info = "server_status_info";
const char *allowlisted_global_status = "allowlisted_global_status";
} // namespace JSONKey
} // namespace

Expand Down Expand Up @@ -659,12 +674,121 @@ bool DataProvider::collect_server_config(rapidjson::Document *document) {
thread_handling, allocator);
}

/*
Non-default globals: provenance from performance_schema.variables_info,
restricted to allowlisted names via SQL IN (...) (see
telemetry_sysvars_allowlist.h) and value shape via
is_value_safe_for_export() above.
*/
if (!kSysvarsAllowlistCsv.empty()) {
std::ostringstream oss;
oss << "SELECT vi.VARIABLE_NAME, gv.VARIABLE_VALUE, vi.VARIABLE_SOURCE "
"FROM performance_schema.variables_info vi "
"INNER JOIN performance_schema.global_variables gv "
"USING (VARIABLE_NAME) "
"WHERE vi.VARIABLE_SOURCE <> 'COMPILED' "
"AND vi.VARIABLE_NAME IN ("
<< kSysvarsAllowlistCsv << ')';

QueryResult rows;
if (!do_query(oss.str(), &rows, nullptr, true)) {
rapidjson::Value sysvars_array(rapidjson::Type::kArrayType);
for (const Row &row : rows) {
if (row.size() < 3) {
continue;
}
const std::string &vname = row[0];
const std::string &vval = row[1];
const std::string &vsrc = row[2];
if (!is_value_safe_for_export(vval)) {
continue;
}
rapidjson::Value one_status(rapidjson::Type::kObjectType);
rapidjson::Value name_json;
name_json.SetString(vname.c_str(),
static_cast<rapidjson::SizeType>(vname.length()),
allocator);
one_status.AddMember(rapidjson::StringRef(JSONKey::name), name_json,
allocator);
rapidjson::Value val_json;
val_json.SetString(vval.c_str(),
static_cast<rapidjson::SizeType>(vval.length()),
allocator);
one_status.AddMember(rapidjson::StringRef(JSONKey::variable_value),
val_json, allocator);
rapidjson::Value src_json;
src_json.SetString(vsrc.c_str(),
static_cast<rapidjson::SizeType>(vsrc.length()),
allocator);
one_status.AddMember(rapidjson::StringRef(JSONKey::variable_source),
src_json, allocator);
sysvars_array.PushBack(one_status, allocator);
}

if (!sysvars_array.Empty()) {
server_config_json.AddMember(
rapidjson::StringRef(JSONKey::nondefault_allowlisted_sysvars),
sysvars_array, allocator);
}
}
}

document->AddMember(rapidjson::StringRef(JSONKey::server_config_info),
server_config_json, allocator);

return false;
}

bool DataProvider::collect_server_status(rapidjson::Document *document) {
if (!kStatusAllowlistCsv.empty()) {
std::ostringstream oss;
oss << "SELECT VARIABLE_NAME, VARIABLE_VALUE FROM "
"performance_schema.global_status WHERE VARIABLE_NAME IN ("
<< kStatusAllowlistCsv << ')';

QueryResult rows;
if (!do_query(oss.str(), &rows, nullptr, true)) {
rapidjson::Document::AllocatorType &allocator = document->GetAllocator();
rapidjson::Value status_array(rapidjson::Type::kArrayType);
for (const Row &row : rows) {
if (row.size() < 2) {
continue;
}
const std::string &vname = row[0];
const std::string &vval = row[1];
if (!is_value_safe_for_export(vval)) {
continue;
}
rapidjson::Value one_status(rapidjson::Type::kObjectType);
rapidjson::Value name_json;
name_json.SetString(vname.c_str(),
static_cast<rapidjson::SizeType>(vname.length()),
allocator);
one_status.AddMember(rapidjson::StringRef(JSONKey::name), name_json,
allocator);
rapidjson::Value val_json;
val_json.SetString(vval.c_str(),
static_cast<rapidjson::SizeType>(vval.length()),
allocator);
one_status.AddMember(rapidjson::StringRef(JSONKey::variable_value),
val_json, allocator);
status_array.PushBack(one_status, allocator);
}

if (!status_array.Empty()) {
rapidjson::Value server_status_json(rapidjson::Type::kObjectType);
server_status_json.AddMember(
rapidjson::StringRef(JSONKey::allowlisted_global_status),
status_array, allocator);
document->AddMember(rapidjson::StringRef(JSONKey::server_status_info),
server_status_json, allocator);
}
}
}

return false;
}

bool DataProvider::collect_metrics(rapidjson::Document *document) {
/* The configuration of this instance might have changed, so we need to colect
it every time. */
Expand Down Expand Up @@ -692,6 +816,7 @@ bool DataProvider::collect_metrics(rapidjson::Document *document) {
res |= collect_group_replication_info(document);
res |= collect_async_replication_info(document);
res |= collect_server_config(document);
res |= collect_server_status(document);

/* The requirement is to have db_replication_id key at the top of JSON
structure. But it may originate from the different places. The above
Expand Down
1 change: 1 addition & 0 deletions components/percona_telemetry/data_provider.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ class DataProvider {
bool collect_group_replication_info(rapidjson::Document *document);
bool collect_async_replication_info(rapidjson::Document *document);
bool collect_server_config(rapidjson::Document *document);
bool collect_server_status(rapidjson::Document *document);
bool collect_db_replication_id(rapidjson::Document *document);
bool collect_metrics(rapidjson::Document *document);

Expand Down
120 changes: 120 additions & 0 deletions components/percona_telemetry/telemetry_status_allowlist.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/* Copyright (c) 2026 Percona LLC and/or its affiliates. All rights reserved.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; version 2 of
the License.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */

#ifndef TELEMETRY_STATUS_ALLOWLIST_H
#define TELEMETRY_STATUS_ALLOWLIST_H

#include <string_view>

/*
Allow list for performance_schema.global_status names collected by the
telemetry component. The names below are quoted, comma-joined and ready
to drop into SQL `IN (...)` (without surrounding parentheses). Adjacent
string literals concatenate at translation time, so this is one
contiguous read-only blob in `.rodata`; no allocation, no runtime
construction.

Maintenance:
* keep entries lexicographically sorted;
* every line ends with a trailing comma except the last;
* when adding/removing the last entry, fix up the comma on its neighbor.

Counters and numeric aggregates only; no SSL strings, paths, hostnames,
or InnoDB human-readable status strings. Values are still filtered
against `/` and `\` characters at collection time.
*/
inline constexpr std::string_view kStatusAllowlistCsv =
"'Aborted_clients',"
"'Aborted_connects',"
"'Acl_cache_items_count',"
"'Binlog_cache_disk_use',"
"'Binlog_cache_use',"
"'Binlog_stmt_cache_disk_use',"
"'Binlog_stmt_cache_use',"
"'Bytes_received',"
"'Bytes_sent',"
"'Com_delete',"
"'Com_insert',"
"'Com_select',"
"'Com_update',"
"'Connection_errors_internal',"
"'Connection_errors_max_connections',"
"'Connection_errors_peer_address',"
"'Connections',"
"'Created_tmp_disk_tables',"
"'Created_tmp_tables',"
"'Handler_commit',"
"'Handler_delete',"
"'Handler_read_first',"
"'Handler_read_key',"
"'Handler_read_next',"
"'Handler_read_prev',"
"'Handler_read_rnd',"
"'Handler_read_rnd_next',"
"'Handler_update',"
"'Handler_write',"
"'Innodb_buffer_pool_read_requests',"
"'Innodb_buffer_pool_reads',"
"'Innodb_data_read',"
"'Innodb_data_reads',"
"'Innodb_data_writes',"
"'Innodb_data_written',"
"'Innodb_dblwr_pages_written',"
"'Innodb_dblwr_writes',"
"'Innodb_log_waits',"
"'Innodb_log_write_requests',"
"'Innodb_log_writes',"
"'Innodb_os_log_written',"
"'Innodb_pages_created',"
"'Innodb_pages_read',"
"'Innodb_pages_written',"
"'Innodb_redo_log_read_only',"
"'Innodb_row_lock_time',"
"'Innodb_row_lock_time_avg',"
"'Innodb_row_lock_time_max',"
"'Innodb_row_lock_waits',"
"'Innodb_rows_deleted',"
"'Innodb_rows_inserted',"
"'Innodb_rows_read',"
"'Innodb_rows_updated',"
"'Libcoredumper_enabled',"
"'Max_used_connections',"
"'Open_files',"
"'Open_table_definitions',"
"'Open_tables',"
"'Prepared_stmt_count',"
"'Queries',"
"'Questions',"
"'Select_full_join',"
"'Select_full_range_join',"
"'Select_range',"
"'Select_range_check',"
"'Select_scan',"
"'Slow_queries',"
"'Sort_merge_passes',"
"'Sort_range',"
"'Sort_rows',"
"'Sort_scan',"
"'Table_locks_immediate',"
"'Table_locks_waited',"
"'Threadpool_idle_threads',"
"'Threadpool_threads',"
"'Threads_cached',"
"'Threads_connected',"
"'Threads_created',"
"'Threads_running'";

#endif /* TELEMETRY_STATUS_ALLOWLIST_H */
Loading
Loading