免责声明: 如果你看完之后觉得太简单了,那你就是我的大佬,如果发现我的错误是请斧正! 如果你看完之后觉得太难了,那你就是我在其他领域的大佬,比如建模领域?我有好多建模方面的盲区需要请教你! 如果你看完之后感叹:“这不就是我想的那样吗!”,对,你就是我要找的那个人!跟我半斤八两的人~ 😃 我的邮箱是:[email protected],随时联系我! 文中...表示省略部分代码,但有的地方省略了也没有用...代替。😃

上结果

比如说,我要给show processlist加上连接创建的时间 CreateTime ,那它就是这样的:

截屏2024-06-15 14.45.07.png

processlist是怎么show出来的

要想加点东西,搞清楚原理是第一要务。除掉一些杂七杂八的过程,show processlist 的处理入口就是在Sql_cmd_show_processlist::execute_inner(THD *thd),直接来看这个函数(删节):

sql_show.cc:
bool Sql_cmd_show_processlist::execute_inner(THD *thd) {
  if (use_pfs()) {
    return Sql_cmd_show::execute_inner(thd);                                      <- 采用pfs的方式查询processlist,实际上查询的是performance_schema.processlist
  } else {
    mysqld_list_processes(thd,
                          thd->security_context()->check_access(PROCESS_ACL)
                              ? NullS
                              : thd->security_context()->priv_user().str,
                          m_verbose, true);                                       <- 传统的方式,实时搜集所有THD的信息
    return false;
  }
}

这部分先看传统的方式怎么搞的,然后试图修改一些东西达到我的目的。 显然 mysqld_list_processes(xxx) 是重点函数,要怎么搞需从此下手。其内容如下(删节):

sql_show.cc:
void mysqld_list_processes(THD *thd, const char *user, bool verbose,
                           bool has_cursor) {

  field_list.push_back(
      new Item_int(NAME_STRING("Id"), 0, MY_INT64_NUM_DECIMAL_DIGITS));           <- 这后面一排都是发送“表头”的,定义了每个字段的类型信息,因此这部分先将查询结构发送到客户端了
  field_list.push_back(new Item_empty_string("User", USERNAME_CHAR_LENGTH));
  field_list.push_back(new Item_empty_string("Host", HOSTNAME_LENGTH));
  field_list.push_back(field = new Item_empty_string("db", NAME_CHAR_LEN));
  field->set_nullable(true);
  field_list.push_back(new Item_empty_string("Command", 16));
  field_list.push_back(field = new Item_return_int("Time", 7, MYSQL_TYPE_LONG));
  field->unsigned_flag = false;
  field_list.push_back(field = new Item_empty_string("State", 30));
  field->set_nullable(true);
  field_list.push_back(field = new Item_empty_string("Info", max_query_length));
  field->set_nullable(true);

  if (thd->send_result_metadata(field_list,
                                Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))。  <- 将结构信息发送到客户端
    return;

  if (!thd->killed) {
    thread_infos.reserve(Global_THD_manager::get_instance()->get_thd_count());    <- 这段代码是拿到当前所有的线程上下文THD,拷贝内容到thread_infos结构中
    List_process_list list_process_list(user, &thread_infos, thd,
                                        max_query_length);                        <- List_process_list 是一个helper类,实现拷贝的动作
    Global_THD_manager::get_instance()->do_for_all_thd_copy(&list_process_list);  <- 拷贝THD内容到thread_infos,Global_THD_manager是管理THD的一个结构
  }

  const time_t now = time(nullptr);
  for (size_t ix = 0; ix < thread_infos.size(); ++ix) {                           <- 这里整了一套for循环,将thread_infos的内容(也就是thd给它的内容)发送到客户端
    thread_info *thd_info = thread_infos.at(ix);                                  <- 拿到单个thread_info,这个结构里已经包含了某个THD的信息
    protocol->start_row();
    protocol->store((ulonglong)thd_info->thread_id);
    protocol->store(thd_info->user, system_charset_info);
    protocol->store(thd_info->host, system_charset_info);
    protocol->store(thd_info->db, system_charset_info);
    if (thd_info->proc_info)
      protocol->store(thd_info->proc_info, system_charset_info);
    else
      protocol->store(Command_names::str_session(thd_info->command).c_str(),
                      system_charset_info);
    if (thd_info->start_time_in_secs)
      protocol->store_long((longlong)(now - thd_info->start_time_in_secs));
    else
      protocol->store_null();
    protocol->store(thd_info->state_info, system_charset_info);
    protocol->store(thd_info->query_string.str(),
                    thd_info->query_string.charset());
    
    if (protocol->end_row()) break; /* purecov: inspected */
  }
}

其实到这里就知道该怎么改了,但是下一步开始先看看 thread_info 是咋回事(删节):

sql_show.cc:
class thread_info {
 public:
  thread_info()
      : thread_id(0),
        start_time_in_secs(0),
        command(0),
        user(nullptr),
        host(nullptr),
        db(nullptr),
        proc_info(nullptr),
        state_info(nullptr) {}

  my_thread_id thread_id;
  time_t start_time_in_secs;
  uint command;
  const char *user, *host, *db, *proc_info, *state_info;
  CSET_STRING query_string;
};

无趣,这个类没啥东西,就是一堆结构,这些结构应该是用来保存显示的信息的。那再翻回去看看,发现一个类 List_process_list ,它有一个有趣的重载(删节):

sql_show.cc:
void operator()(THD *inspect_thd) override {

    thread_info *thd_info = nullptr;

    {
      Security_context *inspect_sctx = inspect_thd->security_context();       <- 从传入的thd中拿到上下文结构

      const LEX_CSTRING inspect_sctx_user = inspect_sctx->user();
      const LEX_CSTRING inspect_sctx_host = inspect_sctx->host();
      const LEX_CSTRING inspect_sctx_host_or_ip = inspect_sctx->host_or_ip();

      thd_info = new (m_client_thd->mem_root) thread_info;

      /* ID */
      thd_info->thread_id = inspect_thd->thread_id();                         <- 正式复制了,这里拷贝的是thread id

      /* USER */
      if (inspect_sctx_user.str)                                              <- 这里拷贝user,后面的代码差不多的意思,就不贴出来了
        thd_info->user = m_client_thd->mem_strdup(inspect_sctx_user.str);
      else if (inspect_thd->system_thread)
        thd_info->user = "system user";
      else
        thd_info->user = "unauthenticated user";

      /* HOST */
      ...
    }  // We've copied the security context, so release the lock.

    /* DB */
    ...

    /* COMMAND */
    ...

    /* STATE */
    ...

    /* INFO */
    ...

    /* MYSQL_TIME */
    ...

    m_thread_infos->push_back(thd_info);                                      <- 最后插入数组中
  }

到这里就清晰明了了,就是把THD的内容拷贝到一个thread_info的结构中,然后发送到客户端就完事儿了。

开始实现吧

首先我们得拿到线程创建的时间

有个前提,一个MySQL的线程是包含一个THD结构的。因此我们在THD创建时初始化一个时间就可以了,这就是线程创建的时间。

所以我们现在THD中加一个时间结构,并且加上配套的操作:

sql_class.h:
class THD {
	...
	// 1. 定义结构保存时间
  struct timeval m_create_time;
  
  // 4. 获取时间
  time_t create_time_in_secs() const { return m_create_time.tv_sec; }
  // 2. 设置时间函数
  void set_create_time();
	...
};

sql_class.cc:
// 2. 设置时间函数
void THD::set_create_time() {
  ulonglong login_utime = my_micro_time();
  my_micro_time_to_timeval(login_utime, &m_create_time);
}

现在结构有了,那就丢到THD的构造函数里去初始化一下时间吧: