Skip to content

Commit 39e5505

Browse files
kamil-holubickiinikep
authored andcommitted
PS-10576 [8.4] Percona telemetry — safe non-default sysvars, global status
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).
1 parent 0aaeed2 commit 39e5505

7 files changed

Lines changed: 572 additions & 3 deletions

File tree

components/percona_telemetry/data_provider.cc

Lines changed: 126 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,25 @@
1414
along with this program; if not, write to the Free Software
1515
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
1616

17-
#include <mysqld_error.h>
17+
#include <algorithm>
1818
#include <sstream>
1919

2020
#include "data_provider.h"
2121
#include "logger.h"
22+
#include "telemetry_status_allowlist.h"
23+
#include "telemetry_sysvars_allowlist.h"
2224

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

28+
/* Reject values that look like paths (defense in depth: the allow list is
29+
already curated, but we double-check at collection time in case a value
30+
shape changes upstream). */
31+
inline bool is_value_safe_for_export(const std::string &value) {
32+
return std::all_of(value.begin(), value.end(),
33+
[](unsigned char ch) { return ch != '/' && ch != '\\'; });
34+
}
35+
2636
/*
2737
percona.telemetry user is created when server starts with telemetry
2838
enabled. The user is deleted, when the server is started with telemetry
@@ -67,6 +77,11 @@ const char *size = "size";
6777
// server configuration variables
6878
const char *server_config_info = "server_config_info";
6979
const char *thread_handling = "thread_handling";
80+
const char *nondefault_allowlisted_sysvars = "nondefault_allowlisted_sysvars";
81+
const char *variable_source = "variable_source";
82+
const char *variable_value = "variable_value";
83+
const char *server_status_info = "server_status_info";
84+
const char *allowlisted_global_status = "allowlisted_global_status";
7085
} // namespace JSONKey
7186
} // namespace
7287

@@ -659,12 +674,121 @@ bool DataProvider::collect_server_config(rapidjson::Document *document) {
659674
thread_handling, allocator);
660675
}
661676

677+
/*
678+
Non-default globals: provenance from performance_schema.variables_info,
679+
restricted to allowlisted names via SQL IN (...) (see
680+
telemetry_sysvars_allowlist.h) and value shape via
681+
is_value_safe_for_export() above.
682+
*/
683+
if (!kSysvarsAllowlistCsv.empty()) {
684+
std::ostringstream oss;
685+
oss << "SELECT vi.VARIABLE_NAME, gv.VARIABLE_VALUE, vi.VARIABLE_SOURCE "
686+
"FROM performance_schema.variables_info vi "
687+
"INNER JOIN performance_schema.global_variables gv "
688+
"USING (VARIABLE_NAME) "
689+
"WHERE vi.VARIABLE_SOURCE <> 'COMPILED' "
690+
"AND vi.VARIABLE_NAME IN ("
691+
<< kSysvarsAllowlistCsv << ')';
692+
693+
QueryResult rows;
694+
if (!do_query(oss.str(), &rows, nullptr, true)) {
695+
rapidjson::Value sysvars_array(rapidjson::Type::kArrayType);
696+
for (const Row &row : rows) {
697+
if (row.size() < 3) {
698+
continue;
699+
}
700+
const std::string &vname = row[0];
701+
const std::string &vval = row[1];
702+
const std::string &vsrc = row[2];
703+
if (!is_value_safe_for_export(vval)) {
704+
continue;
705+
}
706+
rapidjson::Value one_status(rapidjson::Type::kObjectType);
707+
rapidjson::Value name_json;
708+
name_json.SetString(vname.c_str(),
709+
static_cast<rapidjson::SizeType>(vname.length()),
710+
allocator);
711+
one_status.AddMember(rapidjson::StringRef(JSONKey::name), name_json,
712+
allocator);
713+
rapidjson::Value val_json;
714+
val_json.SetString(vval.c_str(),
715+
static_cast<rapidjson::SizeType>(vval.length()),
716+
allocator);
717+
one_status.AddMember(rapidjson::StringRef(JSONKey::variable_value),
718+
val_json, allocator);
719+
rapidjson::Value src_json;
720+
src_json.SetString(vsrc.c_str(),
721+
static_cast<rapidjson::SizeType>(vsrc.length()),
722+
allocator);
723+
one_status.AddMember(rapidjson::StringRef(JSONKey::variable_source),
724+
src_json, allocator);
725+
sysvars_array.PushBack(one_status, allocator);
726+
}
727+
728+
if (!sysvars_array.Empty()) {
729+
server_config_json.AddMember(
730+
rapidjson::StringRef(JSONKey::nondefault_allowlisted_sysvars),
731+
sysvars_array, allocator);
732+
}
733+
}
734+
}
735+
662736
document->AddMember(rapidjson::StringRef(JSONKey::server_config_info),
663737
server_config_json, allocator);
664738

665739
return false;
666740
}
667741

742+
bool DataProvider::collect_server_status(rapidjson::Document *document) {
743+
if (!kStatusAllowlistCsv.empty()) {
744+
std::ostringstream oss;
745+
oss << "SELECT VARIABLE_NAME, VARIABLE_VALUE FROM "
746+
"performance_schema.global_status WHERE VARIABLE_NAME IN ("
747+
<< kStatusAllowlistCsv << ')';
748+
749+
QueryResult rows;
750+
if (!do_query(oss.str(), &rows, nullptr, true)) {
751+
rapidjson::Document::AllocatorType &allocator = document->GetAllocator();
752+
rapidjson::Value status_array(rapidjson::Type::kArrayType);
753+
for (const Row &row : rows) {
754+
if (row.size() < 2) {
755+
continue;
756+
}
757+
const std::string &vname = row[0];
758+
const std::string &vval = row[1];
759+
if (!is_value_safe_for_export(vval)) {
760+
continue;
761+
}
762+
rapidjson::Value one_status(rapidjson::Type::kObjectType);
763+
rapidjson::Value name_json;
764+
name_json.SetString(vname.c_str(),
765+
static_cast<rapidjson::SizeType>(vname.length()),
766+
allocator);
767+
one_status.AddMember(rapidjson::StringRef(JSONKey::name), name_json,
768+
allocator);
769+
rapidjson::Value val_json;
770+
val_json.SetString(vval.c_str(),
771+
static_cast<rapidjson::SizeType>(vval.length()),
772+
allocator);
773+
one_status.AddMember(rapidjson::StringRef(JSONKey::variable_value),
774+
val_json, allocator);
775+
status_array.PushBack(one_status, allocator);
776+
}
777+
778+
if (!status_array.Empty()) {
779+
rapidjson::Value server_status_json(rapidjson::Type::kObjectType);
780+
server_status_json.AddMember(
781+
rapidjson::StringRef(JSONKey::allowlisted_global_status),
782+
status_array, allocator);
783+
document->AddMember(rapidjson::StringRef(JSONKey::server_status_info),
784+
server_status_json, allocator);
785+
}
786+
}
787+
}
788+
789+
return false;
790+
}
791+
668792
bool DataProvider::collect_metrics(rapidjson::Document *document) {
669793
/* The configuration of this instance might have changed, so we need to colect
670794
it every time. */
@@ -692,6 +816,7 @@ bool DataProvider::collect_metrics(rapidjson::Document *document) {
692816
res |= collect_group_replication_info(document);
693817
res |= collect_async_replication_info(document);
694818
res |= collect_server_config(document);
819+
res |= collect_server_status(document);
695820

696821
/* The requirement is to have db_replication_id key at the top of JSON
697822
structure. But it may originate from the different places. The above

components/percona_telemetry/data_provider.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ class DataProvider {
8989
bool collect_group_replication_info(rapidjson::Document *document);
9090
bool collect_async_replication_info(rapidjson::Document *document);
9191
bool collect_server_config(rapidjson::Document *document);
92+
bool collect_server_status(rapidjson::Document *document);
9293
bool collect_db_replication_id(rapidjson::Document *document);
9394
bool collect_metrics(rapidjson::Document *document);
9495

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/* Copyright (c) 2026 Percona LLC and/or its affiliates. All rights reserved.
2+
3+
This program is free software; you can redistribute it and/or
4+
modify it under the terms of the GNU General Public License
5+
as published by the Free Software Foundation; version 2 of
6+
the License.
7+
8+
This program is distributed in the hope that it will be useful,
9+
but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
GNU General Public License for more details.
12+
13+
You should have received a copy of the GNU General Public License
14+
along with this program; if not, write to the Free Software
15+
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
16+
17+
#ifndef TELEMETRY_STATUS_ALLOWLIST_H
18+
#define TELEMETRY_STATUS_ALLOWLIST_H
19+
20+
#include <string_view>
21+
22+
/*
23+
Allow list for performance_schema.global_status names collected by the
24+
telemetry component. The names below are quoted, comma-joined and ready
25+
to drop into SQL `IN (...)` (without surrounding parentheses). Adjacent
26+
string literals concatenate at translation time, so this is one
27+
contiguous read-only blob in `.rodata`; no allocation, no runtime
28+
construction.
29+
30+
Maintenance:
31+
* keep entries lexicographically sorted;
32+
* every line ends with a trailing comma except the last;
33+
* when adding/removing the last entry, fix up the comma on its neighbor.
34+
35+
Counters and numeric aggregates only; no SSL strings, paths, hostnames,
36+
or InnoDB human-readable status strings. Values are still filtered
37+
against `/` and `\` characters at collection time.
38+
*/
39+
inline constexpr std::string_view kStatusAllowlistCsv =
40+
"'Aborted_clients',"
41+
"'Aborted_connects',"
42+
"'Acl_cache_items_count',"
43+
"'Binlog_cache_disk_use',"
44+
"'Binlog_cache_use',"
45+
"'Binlog_stmt_cache_disk_use',"
46+
"'Binlog_stmt_cache_use',"
47+
"'Bytes_received',"
48+
"'Bytes_sent',"
49+
"'Com_delete',"
50+
"'Com_insert',"
51+
"'Com_select',"
52+
"'Com_update',"
53+
"'Connection_errors_internal',"
54+
"'Connection_errors_max_connections',"
55+
"'Connection_errors_peer_address',"
56+
"'Connections',"
57+
"'Created_tmp_disk_tables',"
58+
"'Created_tmp_tables',"
59+
"'Handler_commit',"
60+
"'Handler_delete',"
61+
"'Handler_read_first',"
62+
"'Handler_read_key',"
63+
"'Handler_read_next',"
64+
"'Handler_read_prev',"
65+
"'Handler_read_rnd',"
66+
"'Handler_read_rnd_next',"
67+
"'Handler_update',"
68+
"'Handler_write',"
69+
"'Innodb_buffer_pool_read_requests',"
70+
"'Innodb_buffer_pool_reads',"
71+
"'Innodb_data_read',"
72+
"'Innodb_data_reads',"
73+
"'Innodb_data_writes',"
74+
"'Innodb_data_written',"
75+
"'Innodb_dblwr_pages_written',"
76+
"'Innodb_dblwr_writes',"
77+
"'Innodb_log_waits',"
78+
"'Innodb_log_write_requests',"
79+
"'Innodb_log_writes',"
80+
"'Innodb_os_log_written',"
81+
"'Innodb_pages_created',"
82+
"'Innodb_pages_read',"
83+
"'Innodb_pages_written',"
84+
"'Innodb_redo_log_read_only',"
85+
"'Innodb_row_lock_time',"
86+
"'Innodb_row_lock_time_avg',"
87+
"'Innodb_row_lock_time_max',"
88+
"'Innodb_row_lock_waits',"
89+
"'Innodb_rows_deleted',"
90+
"'Innodb_rows_inserted',"
91+
"'Innodb_rows_read',"
92+
"'Innodb_rows_updated',"
93+
"'Libcoredumper_enabled',"
94+
"'Max_used_connections',"
95+
"'Open_files',"
96+
"'Open_table_definitions',"
97+
"'Open_tables',"
98+
"'Prepared_stmt_count',"
99+
"'Queries',"
100+
"'Questions',"
101+
"'Select_full_join',"
102+
"'Select_full_range_join',"
103+
"'Select_range',"
104+
"'Select_range_check',"
105+
"'Select_scan',"
106+
"'Slow_queries',"
107+
"'Sort_merge_passes',"
108+
"'Sort_range',"
109+
"'Sort_rows',"
110+
"'Sort_scan',"
111+
"'Table_locks_immediate',"
112+
"'Table_locks_waited',"
113+
"'Threadpool_idle_threads',"
114+
"'Threadpool_threads',"
115+
"'Threads_cached',"
116+
"'Threads_connected',"
117+
"'Threads_created',"
118+
"'Threads_running'";
119+
120+
#endif /* TELEMETRY_STATUS_ALLOWLIST_H */

0 commit comments

Comments
 (0)