修改ceph源码——增加函数级别日志输出功能

ceph中原有日志功能介绍

当前ceph中的日志只能做到某一子系统(subsystem)的某一级别日志,比如给ceph_subsys_osd子系统设置日志级别为5,则表示任何级别小于等于5的osd子系统日志都可以输出到日志文件。关于子系统日志级别设置有两种方法
(1)在配置文件中指定
比如

debug_osd 5 

就是将osd子系统的日志输出级别设置为5
(2)利用admin_socket动态设置
ceph实现了admin_socket(即unix socket)来用于用户和ceph的组件进行通信。我们可以利用admin_socket来动态设置日志输出级别,如下:

ceph daemon /var/run/ceph/ceph-osd.0.asok congfig set debug_osd 5

在调试问题时一般会用到第二种方法进行运行时更改日志输出级别,但是这个方法有一个问题就是会输出大量不相关的日志信息,这会增加问题排查的难度,因此本人修改ceph日志模块,增加了可以具体到某一函数的日志输出功能,并可以利用admin_socket动态修改.

函数级别日志输出

增加函数级别日志的动态设置、查看、删除等功能,具体实现如下
(1)定义相关类与方法
在src/log/SubsystemMap.h中定义相关的类和方法

class ZyLog
{

private:
  std::map<unsigned, std::set<std::string>> zy_log_funs;

public:
  void zy_set_fun_log(unsigned subsys, std::string fun_name)
  {
    zy_log_funs[subsys].insert(fun_name);
  }

  void zy_unset_fun_log(unsigned subsys, std::string fun_name)
  {
    zy_log_funs[subsys].erase(fun_name); 
    if(zy_log_funs[subsys].empty())
    {
      zy_log_funs.erase(subsys);
    }
  }

  bool zy_should_gather(unsigned subsys, std::string fun_name)
  {
    return (zy_log_funs[subsys].find(fun_name) == zy_log_funs[subsys].end());
  }

  int zy_subsys_string_to_unsigned(std::string subsys_str)
  {
    constexpr auto t = ceph_subsys_get_as_array();
    for(unsigned i = 0; i < t.size(); i++)
    {
      if(std::string(t[i].name) == subsys_str)
        return i;
    }
    return -1;
  }

  void get_all_subsys(Formatter *f)
  {
    auto t = ceph_subsys_get_as_array();
    for(unsigned i = 0; i < t.size(); i++)
    {
      f->dump_stream(t[i].name);
    }
  }

  void get_all_setted_funs(Formatter *f)
  {
    for(auto subsys_it = zy_log_funs.begin(); subsys_it != zy_log_funs.end(); subsys_it++)
    {
      if(subsys_it->first >= ceph_subsys_get_num())
        continue;

      const auto subsys_name = ceph_subsys_get_as_array()[subsys_it->first].name; 
      for(auto fun_it = subsys_it->second.begin(); fun_it != subsys_it->second.end(); fun_it++)
        f->dump_string(subsys_name, *fun_it);
    } 
  }

(2)注册相关命令
在src/common/ceph_context.cc的CephContext类的构造函数中,增加如下几行

  _admin_socket->register_command("zylog set", "zylog set name=subsys,type=CephString name=fun,type=CephString", _admin_hook, "");
  _admin_socket->register_command("zylog unset", "zylog unset name=subsys,type=CephString name=fun,type=CephString", _admin_hook, "");
  _admin_socket->register_command("zylog showsubsys", "zylog showsubsys", _admin_hook, "");
  _admin_socket->register_command("zylog showsets", "zylog showsets", _admin_hook, "");

这里分别注册了set unset showsubsys showsets四个命令,其中showsubsys是查看ceph中有哪些子系统可以设置
set命令的用法就是ceph daemon /var/run/ceph/ceph-osd.0.asok zylog set osd heartbeat ,表示设置osd子系统中的heartbeat函数中的日志为输出。
(3)处理相关命令
在src/common/ceph_context.cc的do_command函数中增加如下代码

    else if (command == "zylog set") {
      std::string zy_subsys;
      std::string zy_fun;

      if (!(cmd_getval(this, cmdmap, "subsys", zy_subsys)) ||
          !(cmd_getval(this, cmdmap, "fun", zy_fun))) {
        f->dump_string("error", "syntax error: 'config set <subsys> <fun>'");
      } else {
          int subsys_id = _conf->funlog.zy_subsys_string_to_unsigned(zy_subsys);
          if(-1 == subsys_id)
            f->dump_stream("error") << zy_subsys << " is not a subsys";
          else
          {
            _conf->funlog.zy_set_fun_log((unsigned)subsys_id, zy_fun);
            f->dump_stream("success") << " set " << zy_subsys << "(" << subsys_id << ") " << zy_fun;
          }
      }
    }
    else if (command == "zylog unset") {
      std::string zy_subsys;
      std::string zy_fun;

      if (!(cmd_getval(this, cmdmap, "subsys", zy_subsys)) ||
          !(cmd_getval(this, cmdmap, "fun", zy_fun))) {
        f->dump_string("error", "syntax error: 'config set <subsys> <fun>'");
      } else {
          int subsys_id = _conf->funlog.zy_subsys_string_to_unsigned(zy_subsys);
          if(-1 == subsys_id)
            f->dump_stream("error ") << zy_subsys << "is not a subsys";
          else
          {
            _conf->funlog.zy_unset_fun_log((unsigned)subsys_id, zy_fun);
            f->dump_stream("success ") << "unset" << zy_subsys << "(" << subsys_id << ") " << zy_fun;
          }
      }
    }
    else if (command == "zylog showsubsys") {
      _conf->funlog.get_all_subsys(f); 
    }
    else if (command == "zylog showsets") {
      _conf->funlog.get_all_setted_funs(f);
    }

(4)修改dout.h
修改后的内容如下

#define dout_impl(cct, sub, v)            \
  do {                  \
    unsigned funsub = sub;  \
    const string funname = __func__;  \
    bool funlog_out = false;  \
  const bool should_gather = [&](const auto cctX) {     \
    if constexpr (ceph::dout::is_dynamic<decltype(sub)>::value || \
      ceph::dout::is_dynamic<decltype(v)>::value) {   \
      return cctX->_conf->subsys.should_gather(sub, v);     \
    } else {                \
      /* The parentheses are **essential** because commas in angle  \
       * brackets are NOT ignored on macro expansion! A language's  \
       * limitation, sorry. */            \
      return (cctX->_conf->subsys.template should_gather<sub, v>());  \
    }                 \
  }(cct);               \
                  \
  if (!should_gather){    \
    funlog_out = !(cct->_conf->funlog.zy_should_gather(funsub, funname)); \
  } \             //如果日志在原有判断中不能输出,则继续判断函数级别日志是否允许输出
  if (should_gather || funlog_out) {              \
    static size_t _log_exp_length = 80;         \
    ceph::logging::Entry *_dout_e = NULL;           \
    if(!funlog_out) {                             \
      _dout_e =           \
        cct->_log->create_entry(v, sub, &_log_exp_length);    \
    }                   \
    else {              \
     _dout_e =           \
        cct->_log->create_entry(-1, sub, &_log_exp_length);    \  //如果函数级别日志允许输出,则需要将改条日志的level改为-1,这样是为了防止在submit_entry日志时再次被过滤掉。
    }                   \
    static_assert(std::is_convertible<decltype(&*cct),      \
              CephContext* >::value,    \
      "provided cct must be compatible with CephContext*"); \
    auto _dout_cct = cct;           \
    std::ostream* _dout = &_dout_e->get_ostream();

最后附上代码修改记录
https://github.com/jasonlinuxzhang/ceph/commit/c6b5a14c8ea0bfd485fd30bc0afdf74f1f2f137e

需要改进的地方

(1)在设置函数级别日志输出时,我没有判断该函数是否存在于子系统内,目前我还没有找到方法获得某个子系统内或者某个文件内的函数表。
(2)可以设置命令格式为file:function而不是subsys:function,这样就可以具体到某一个文件中的某个函数,而不是某一个某一个子系统,目前也没有找到修改方法。

Ceph是一个分布式存储系统,包括多个组件,其中包括Librbd块存储库。在Ceph中,Librbd提供了一种将RBD(块设备)映射到客户端的方法,并使客户端能够读写这些设备。在本文中,我们将分析Librbd块存储库的源代码以及RBD的读写过程。 1. Librbd源码分析 Librbd块存储库的源代码位于src/librbd目录下。其中,包括librbd.cc、ImageCtx.cc、ImageWatcher.cc、Journal.cc等多个源代码文件。这些源代码文件组成了Librbd块存储库的核心。 其中,librbd.cc是Librbd块存储库的主要源代码文件。在这个文件中,包括了Librbd的初始化、映射、卸载等方法。ImageCtx.cc则是Image上下文,用于管理Image的状态、锁定、映射等信息。ImageWatcher.cc用于监控Image的状态变化,Journal.cc则是Librbd的Journal日志管理。 2. RBD读写流程源码分析 在Ceph中,RBD由client和server两个部分组成。client在客户端上运行,提供了将RBD映射到客户端的方法。server在存储集群中运行,提供了RBD的存储和管理。 RBD的读写流程如下: 1)客户端向Ceph Monitor请求RBD映射信息,Monitor返回Image ID和Image特性; 2)客户端向Ceph OSD请求RBD数据块,OSD返回数据块内容; 3)客户端将数据写入或读取到本地块设备; 4)客户端向Ceph OSD写入或读取数据块,OSD返回操作结果; 5)客户端向Ceph Monitor请求解除RBD映射,Monitor返回解除结果。 在上述过程中,涉及到的源代码文件有:librbd.cc、ImageCtx.cc、ImageWatcher.cc、Journal.cc等。 总结 Librbd块存储库和RBD读写流程是Ceph存储系统的核心组件之一,通过分析源代码可以更加深入地了解Ceph存储系统的实现原理。同时,对于开发者来说,也有助于在Ceph存储系统上构建更加高效、稳定的存储应用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值