diff --git a/src/commands/cmd_server.cc b/src/commands/cmd_server.cc index a8512936052..17815cbf91b 100644 --- a/src/commands/cmd_server.cc +++ b/src/commands/cmd_server.cc @@ -235,14 +235,12 @@ class CommandConfig : public Commander { class CommandInfo : public Commander { public: Status Execute([[maybe_unused]] engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override { - std::string section = "all"; - if (args_.size() == 2) { - section = util::ToLower(args_[1]); - } else if (args_.size() > 2) { - return {Status::RedisParseErr, errInvalidSyntax}; + std::vector sections; + for (size_t i = 1; i < args_.size(); ++i) { + sections.push_back(util::ToLower(args_[i])); } std::string info; - srv->GetInfo(conn->GetNamespace(), section, &info); + srv->GetInfo(conn->GetNamespace(), sections, &info); *output = conn->VerbatimString("txt", info); return Status::OK(); } diff --git a/src/server/server.cc b/src/server/server.cc index 0c15abef8d5..07a66cf936a 100644 --- a/src/server/server.cc +++ b/src/server/server.cc @@ -27,6 +27,7 @@ #include #include +#include #include #include #include @@ -853,6 +854,8 @@ void Server::cron() { } void Server::GetRocksDBInfo(std::string *info) { + if (is_loading_) return; + std::ostringstream string_stream; rocksdb::DB *db = storage->GetDB(); @@ -1028,6 +1031,8 @@ void Server::GetMemoryInfo(std::string *info) { } void Server::GetReplicationInfo(std::string *info) { + if (is_loading_) return; + std::ostringstream string_stream; string_stream << "# Replication\r\n"; string_stream << "role:" << (IsSlave() ? "slave" : "master") << "\r\n"; @@ -1207,141 +1212,119 @@ void Server::GetClusterInfo(std::string *info) { *info = string_stream.str(); } -// WARNING: we must not access DB(i.e. RocksDB) when server is loading since -// DB is closed and the pointer is invalid. Server may crash if we access DB during loading. -// If you add new fields which access DB into INFO command output, make sure -// this section can't be shown when loading(i.e. !is_loading_). -void Server::GetInfo(const std::string &ns, const std::string §ion, std::string *info) { - info->clear(); +void Server::GetPersistenceInfo(std::string *info) { + std::ostringstream string_stream; + + string_stream << "# Persistence\r\n"; + string_stream << "loading:" << is_loading_ << "\r\n"; + + std::lock_guard lg(db_job_mu_); + string_stream << "bgsave_in_progress:" << (is_bgsave_in_progress_ ? 1 : 0) << "\r\n"; + string_stream << "last_bgsave_time:" + << (last_bgsave_timestamp_secs_ == -1 ? start_time_secs_ : last_bgsave_timestamp_secs_) << "\r\n"; + string_stream << "last_bgsave_status:" << last_bgsave_status_ << "\r\n"; + string_stream << "last_bgsave_time_sec:" << last_bgsave_duration_secs_ << "\r\n"; + *info = string_stream.str(); +} + +void Server::GetCpuInfo(std::string *info) { // NOLINT(readability-convert-member-functions-to-static) std::ostringstream string_stream; - bool all = section == "all"; - int section_cnt = 0; - if (all || section == "server") { - std::string server_info; - GetServerInfo(&server_info); - if (section_cnt++) string_stream << "\r\n"; - string_stream << server_info; - } + rusage self_ru; + getrusage(RUSAGE_SELF, &self_ru); + string_stream << "# CPU\r\n"; + string_stream << "used_cpu_sys:" + << static_cast(self_ru.ru_stime.tv_sec) + static_cast(self_ru.ru_stime.tv_usec / 1000000) + << "\r\n"; + string_stream << "used_cpu_user:" + << static_cast(self_ru.ru_utime.tv_sec) + static_cast(self_ru.ru_utime.tv_usec / 1000000) + << "\r\n"; - if (all || section == "clients") { - std::string clients_info; - GetClientsInfo(&clients_info); - if (section_cnt++) string_stream << "\r\n"; - string_stream << clients_info; - } + *info = string_stream.str(); +} - if (all || section == "memory") { - std::string memory_info; - GetMemoryInfo(&memory_info); - if (section_cnt++) string_stream << "\r\n"; - string_stream << memory_info; - } +void Server::GetKeyspaceInfo(const std::string &ns, std::string *info) { + if (is_loading_) return; - if (all || section == "persistence") { - if (section_cnt++) string_stream << "\r\n"; - string_stream << "# Persistence\r\n"; - string_stream << "loading:" << is_loading_ << "\r\n"; + std::ostringstream string_stream; - std::lock_guard lg(db_job_mu_); - string_stream << "bgsave_in_progress:" << (is_bgsave_in_progress_ ? 1 : 0) << "\r\n"; - string_stream << "last_bgsave_time:" - << (last_bgsave_timestamp_secs_ == -1 ? start_time_secs_ : last_bgsave_timestamp_secs_) << "\r\n"; - string_stream << "last_bgsave_status:" << last_bgsave_status_ << "\r\n"; - string_stream << "last_bgsave_time_sec:" << last_bgsave_duration_secs_ << "\r\n"; - } - - if (all || section == "stats") { - std::string stats_info; - GetStatsInfo(&stats_info); - if (section_cnt++) string_stream << "\r\n"; - string_stream << stats_info; - } - - // In replication section, we access DB, so we can't do that when loading - if (!is_loading_ && (all || section == "replication")) { - std::string replication_info; - GetReplicationInfo(&replication_info); - if (section_cnt++) string_stream << "\r\n"; - string_stream << replication_info; - } - - if (all || section == "cpu") { - rusage self_ru; - getrusage(RUSAGE_SELF, &self_ru); - if (section_cnt++) string_stream << "\r\n"; - string_stream << "# CPU\r\n"; - string_stream << "used_cpu_sys:" - << static_cast(self_ru.ru_stime.tv_sec) + - static_cast(self_ru.ru_stime.tv_usec / 1000000) - << "\r\n"; - string_stream << "used_cpu_user:" - << static_cast(self_ru.ru_utime.tv_sec) + - static_cast(self_ru.ru_utime.tv_usec / 1000000) - << "\r\n"; - } + KeyNumStats stats; + GetLatestKeyNumStats(ns, &stats); - if (all || section == "commandstats") { - std::string commands_stats_info; - GetCommandsStatsInfo(&commands_stats_info); - if (section_cnt++) string_stream << "\r\n"; - string_stream << commands_stats_info; - } + // FIXME(mwish): output still requires std::tm. + auto last_scan_time = static_cast(GetLastScanTime(ns)); + std::tm last_scan_tm{}; + localtime_r(&last_scan_time, &last_scan_tm); - if (all || section == "cluster") { - std::string cluster_info; - GetClusterInfo(&cluster_info); - if (section_cnt++) string_stream << "\r\n"; - string_stream << cluster_info; + string_stream << "# Keyspace\r\n"; + if (last_scan_time == 0) { + string_stream << "# WARN: DBSIZE SCAN never performed yet\r\n"; + } else { + string_stream << "# Last DBSIZE SCAN time: " << std::put_time(&last_scan_tm, "%a %b %e %H:%M:%S %Y") << "\r\n"; + } + string_stream << "db0:keys=" << stats.n_key << ",expires=" << stats.n_expires << ",avg_ttl=" << stats.avg_ttl + << ",expired=" << stats.n_expired << "\r\n"; + string_stream << "sequence:" << storage->GetDB()->GetLatestSequenceNumber() << "\r\n"; + string_stream << "used_db_size:" << storage->GetTotalSize(ns) << "\r\n"; + string_stream << "max_db_size:" << config_->max_db_size * GiB << "\r\n"; + double used_percent = config_->max_db_size ? static_cast(storage->GetTotalSize() * 100) / + static_cast(config_->max_db_size * GiB) + : 0; + string_stream << "used_percent: " << used_percent << "%\r\n"; + + struct statvfs stat; + if (statvfs(config_->db_dir.c_str(), &stat) == 0) { + auto disk_capacity = stat.f_blocks * stat.f_frsize; + auto used_disk_size = (stat.f_blocks - stat.f_bavail) * stat.f_frsize; + string_stream << "disk_capacity:" << disk_capacity << "\r\n"; + string_stream << "used_disk_size:" << used_disk_size << "\r\n"; + double used_disk_percent = static_cast(used_disk_size * 100) / static_cast(disk_capacity); + string_stream << "used_disk_percent: " << used_disk_percent << "%\r\n"; } - // In keyspace section, we access DB, so we can't do that when loading - if (!is_loading_ && (all || section == "keyspace")) { - KeyNumStats stats; - GetLatestKeyNumStats(ns, &stats); + *info = string_stream.str(); +} - // FIXME(mwish): output still requires std::tm. - auto last_scan_time = static_cast(GetLastScanTime(ns)); - std::tm last_scan_tm{}; - localtime_r(&last_scan_time, &last_scan_tm); +// WARNING: we must not access DB(i.e. RocksDB) when server is loading since +// DB is closed and the pointer is invalid. Server may crash if we access DB during loading. +// If you add new fields which access DB into INFO command output, make sure +// this section can't be shown when loading(i.e. !is_loading_). +void Server::GetInfo(const std::string &ns, const std::vector §ions, std::string *info) { + info->clear(); - if (section_cnt++) string_stream << "\r\n"; - string_stream << "# Keyspace\r\n"; - if (last_scan_time == 0) { - string_stream << "# WARN: DBSIZE SCAN never performed yet\r\n"; - } else { - string_stream << "# Last DBSIZE SCAN time: " << std::put_time(&last_scan_tm, "%a %b %e %H:%M:%S %Y") << "\r\n"; - } - string_stream << "db0:keys=" << stats.n_key << ",expires=" << stats.n_expires << ",avg_ttl=" << stats.avg_ttl - << ",expired=" << stats.n_expired << "\r\n"; - string_stream << "sequence:" << storage->GetDB()->GetLatestSequenceNumber() << "\r\n"; - string_stream << "used_db_size:" << storage->GetTotalSize(ns) << "\r\n"; - string_stream << "max_db_size:" << config_->max_db_size * GiB << "\r\n"; - double used_percent = config_->max_db_size ? static_cast(storage->GetTotalSize() * 100) / - static_cast(config_->max_db_size * GiB) - : 0; - string_stream << "used_percent: " << used_percent << "%\r\n"; - - struct statvfs stat; - if (statvfs(config_->db_dir.c_str(), &stat) == 0) { - auto disk_capacity = stat.f_blocks * stat.f_frsize; - auto used_disk_size = (stat.f_blocks - stat.f_bavail) * stat.f_frsize; - string_stream << "disk_capacity:" << disk_capacity << "\r\n"; - string_stream << "used_disk_size:" << used_disk_size << "\r\n"; - double used_disk_percent = static_cast(used_disk_size * 100) / static_cast(disk_capacity); - string_stream << "used_disk_percent: " << used_disk_percent << "%\r\n"; + std::vector>> info_funcs = { + {"server", &Server::GetServerInfo}, + {"clients", &Server::GetClientsInfo}, + {"memory", &Server::GetMemoryInfo}, + {"persistence", &Server::GetPersistenceInfo}, + {"stats", &Server::GetStatsInfo}, + {"replication", &Server::GetReplicationInfo}, + {"cpu", &Server::GetCpuInfo}, + {"commandstats", &Server::GetCommandsStatsInfo}, + {"cluster", &Server::GetClusterInfo}, + {"keyspace", [&ns](Server *srv, std::string *info) { srv->GetKeyspaceInfo(ns, info); }}, + {"rocksdb", &Server::GetRocksDBInfo}, + }; + + std::stringstream string_stream; + + bool all = sections.empty() || std::find(sections.begin(), sections.end(), "all") != sections.end(); + + bool first = true; + for (const auto &[sec, fn] : info_funcs) { + if (all || std::find(sections.begin(), sections.end(), sec) != sections.end()) { + if (first) + first = false; + else + string_stream << "\r\n"; + + std::string out; + fn(this, &out); + string_stream << out; } } - // In rocksdb section, we access DB, so we can't do that when loading - if (!is_loading_ && (all || section == "rocksdb")) { - std::string rocksdb_info; - GetRocksDBInfo(&rocksdb_info); - if (section_cnt++) string_stream << "\r\n"; - string_stream << rocksdb_info; - } - *info = string_stream.str(); } diff --git a/src/server/server.h b/src/server/server.h index 55059d97cfe..c82c101962b 100644 --- a/src/server/server.h +++ b/src/server/server.h @@ -241,7 +241,10 @@ class Server { void GetRoleInfo(std::string *info); void GetCommandsStatsInfo(std::string *info); void GetClusterInfo(std::string *info); - void GetInfo(const std::string &ns, const std::string §ion, std::string *info); + void GetPersistenceInfo(std::string *info); + void GetCpuInfo(std::string *info); + void GetKeyspaceInfo(const std::string &ns, std::string *info); + void GetInfo(const std::string &ns, const std::vector §ions, std::string *info); std::string GetRocksDBStatsJson() const; ReplState GetReplicationState(); diff --git a/tests/gocase/unit/info/info_test.go b/tests/gocase/unit/info/info_test.go index 128e57748f3..aace82792a1 100644 --- a/tests/gocase/unit/info/info_test.go +++ b/tests/gocase/unit/info/info_test.go @@ -119,6 +119,13 @@ func TestInfo(t *testing.T) { require.Contains(t, splitValues, "count") require.Contains(t, splitValues, "inf") }) + + t.Run("multiple sections", func(t *testing.T) { + info := rdb.Info(ctx, "server", "cpu") + require.NoError(t, info.Err()) + require.Contains(t, info.Val(), "# Server") + require.Contains(t, info.Val(), "# CPU") + }) } func TestKeyspaceHitMiss(t *testing.T) {