This is an automated email from the ASF dual-hosted git repository.

twice pushed a commit to branch unstable
in repository https://gitbox.apache.org/repos/asf/kvrocks.git


The following commit(s) were added to refs/heads/unstable by this push:
     new 5e9d61da feat(info): support to specify multiple sections for INFO 
(#2772)
5e9d61da is described below

commit 5e9d61da41e1c73960eb5054db48b2c9a9d61568
Author: Twice <[email protected]>
AuthorDate: Thu Feb 6 22:36:04 2025 +0800

    feat(info): support to specify multiple sections for INFO (#2772)
---
 src/commands/cmd_server.cc          |  10 +-
 src/server/server.cc                | 221 +++++++++++++++++-------------------
 src/server/server.h                 |   5 +-
 tests/gocase/unit/info/info_test.go |   7 ++
 4 files changed, 117 insertions(+), 126 deletions(-)

diff --git a/src/commands/cmd_server.cc b/src/commands/cmd_server.cc
index a8512936..17815cbf 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<std::string> 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 0c15abef..07a66cf9 100644
--- a/src/server/server.cc
+++ b/src/server/server.cc
@@ -27,6 +27,7 @@
 #include <sys/statvfs.h>
 #include <sys/utsname.h>
 
+#include <algorithm>
 #include <atomic>
 #include <cstdint>
 #include <functional>
@@ -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 &section, 
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<std::mutex> 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<float>(self_ru.ru_stime.tv_sec) + 
static_cast<float>(self_ru.ru_stime.tv_usec / 1000000)
+                << "\r\n";
+  string_stream << "used_cpu_user:"
+                << static_cast<float>(self_ru.ru_utime.tv_sec) + 
static_cast<float>(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<std::mutex> 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<float>(self_ru.ru_stime.tv_sec) +
-                         static_cast<float>(self_ru.ru_stime.tv_usec / 1000000)
-                  << "\r\n";
-    string_stream << "used_cpu_user:"
-                  << static_cast<float>(self_ru.ru_utime.tv_sec) +
-                         static_cast<float>(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<time_t>(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<double>(storage->GetTotalSize() * 100) /
+                                                   
static_cast<double>(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<double>(used_disk_size * 100) / 
static_cast<double>(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<time_t>(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<std::string> 
&sections, 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<double>(storage->GetTotalSize() * 100) /
-                                                     
static_cast<double>(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<double>(used_disk_size * 100) / 
static_cast<double>(disk_capacity);
-      string_stream << "used_disk_percent: " << used_disk_percent << "%\r\n";
+  std::vector<std::pair<std::string, std::function<void(Server *, std::string 
*)>>> 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 55059d97..c82c1019 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 &section, 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<std::string> 
&sections, 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 128e5774..aace8279 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) {

Reply via email to