如何将 performance_schema 中的 TIMER 字段转换为日期时间

这篇具有很好参考价值的文章主要介绍了如何将 performance_schema 中的 TIMER 字段转换为日期时间。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

问题

最近有好几个朋友问,如何将 performance_schema.events_statements_xxx 中的 TIMER 字段(主要是TIMER_START和TIMER_END)转换为日期时间。

因为 TIMER 字段的单位是皮秒(picosecond),所以很多童鞋会尝试直接转换,但转换后的结果并不对,看下面这个示例。

mysql> select * from performance_schema.events_statements_current limit 1\G
*************************** 1. row ***************************
              THREAD_ID: 57
               EVENT_ID: 13
           END_EVENT_ID: 13
             EVENT_NAME: statement/sql/commit
                 SOURCE: log_event.cc:4825
            TIMER_START: 3304047000000
              TIMER_END: 3305287000000
             TIMER_WAIT: 1240000000
             ...
       EXECUTION_ENGINE: PRIMARY
1 row in set (0.00 sec)

# 因为1秒等于10^12皮秒,所以需要先除以 1000000000000。
mysql> select from_unixtime(3304047000000/1000000000000);
+--------------------------------------------+
| from_unixtime(3304047000000/1000000000000) |
+--------------------------------------------+
| 1970-01-01 08:00:03.3040                   |
+--------------------------------------------+
1 row in set (0.00 sec)

下面会从源码角度分析 TIMER 字段的生成逻辑。

对源码分析不感兴趣的童鞋,可直接跳到后面的案例部分看结论。

TIMER 字段的生成逻辑

当我们查询 events_statements_xxx 表时,会调用对应的 make_row() 函数来生成行数据。

如 events_statements_current 表,对应的生成函数是 table_events_statements_current::make_row()

make_row 会调用 make_row_part_1 和 make_row_part_2 来生成数据。

TIMER_START、TIMER_END 实际上就是table_events_statements_common::make_row_part_1调用to_pico来生成的。

int table_events_statements_common::make_row_part_1(
    PFS_events_statements *statement, sql_digest_storage *digest) {
  ulonglong timer_end;
  ...
  m_normalizer->to_pico(statement->m_timer_start, timer_end,
                        &m_row.m_timer_start, &m_row.m_timer_end,
                        &m_row.m_timer_wait);
  m_row.m_lock_time = statement->m_lock_time * MICROSEC_TO_PICOSEC;

  m_row.m_name = klass->m_name.str();
  m_row.m_name_length = klass->m_name.length();
  ...
  return 0;
}

void time_normalizer::to_pico(ulonglong start, ulonglong end,
                              ulonglong *pico_start, ulonglong *pico_end,
                              ulonglong *pico_wait) {
  if (start == 0) {
    *pico_start = 0;
    *pico_end = 0;
    *pico_wait = 0;
  } else {
    *pico_start = (start - m_v0) * m_factor;
    if (end == 0) {
      *pico_end = 0;
      *pico_wait = 0;
    } else {
      *pico_end = (end - m_v0) * m_factor;
      *pico_wait = (end - start) * m_factor;
    }
  }
}

函数中的 start 和 end 分别对应语句的开始时间(m_timer_start)和结束时间(m_timer_end)。

如果 start,end 不为 0,则 pico_start = (start - m_v0) * m_factor,pico_end = (end - m_v0) * m_factor。

pico_start、pico_end 即我们在 events_statements_current 中看到的 TIMER_START 和 TIMER_END。

m_timer_start 和 m_timer_end 的实现逻辑

如果 performance_schema.setup_instruments 中 statement 相关的采集项开启了(默认开启),则语句在开始和结束时会分别调用pfs_start_statement_vc() 和pfs_end_statement_vc()这两个函数。

m_timer_start 和 m_timer_end 实际上就是在这两个函数中被赋值的。

void pfs_start_statement_vc(PSI_statement_locker *locker, const char *db,
                            uint db_len, const char *src_file, uint src_line) {
  ...
  if (flags & STATE_FLAG_TIMED) {
    timer_start = get_statement_timer();
    state->m_timer_start = timer_start;
  }
  ...
    pfs->m_timer_start = timer_start;
  ...
}

void pfs_end_statement_vc(PSI_statement_locker *locker, void *stmt_da) {
  ...
  if (flags & STATE_FLAG_TIMED) {
    timer_end = get_statement_timer();
    wait_time = timer_end - state->m_timer_start;
  }
  ...
    pfs->m_timer_end = timer_end;
  ...
}

可以看到,无论是语句开始时间(timer_start)还是结束时间(timer_end),调用的都是get_statement_timer()

接下来,我们看看get_statement_timer()的具体实现。

ulonglong inline get_statement_timer() { return USED_TIMER(); }

# 如果有其它的计数器实现,只需更新宏定义即可。
#define USED_TIMER my_timer_nanoseconds

ulonglong my_timer_nanoseconds(void) {
 ...
#elif defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_REALTIME)
  {
    struct timespec tp;
    clock_gettime(CLOCK_REALTIME, &tp);
    return (ulonglong)tp.tv_sec * 1000000000 + (ulonglong)tp.tv_nsec;
  }
 ...
#else
  return 0;
#endif
}

get_statement_timer()调用的是 USED_TIMER(),而 USED_TIMER 只不过是个宏定义,实际调用的还是my_timer_nanoseconds

my_timer_nanoseconds是一个计时器函数,用于获取系统当前时间,并将其转换为纳秒级别的时间戳。不同的系统,会使用不同的方法来获取。

对于 linux 系统,它会首先调用clock_gettime函数获取系统当前时间,然后再将其转换为纳秒。

所以,语句的开始时间(m_timer_start)和结束时间(m_timer_end)取的都是系统当前时间。

m_v0 和 m_factor 的实现逻辑

m_v0和m_factor是结构体 time_normalizer 中的两个变量。其中,

  • m_v0:实例的启动时间(计数器值)。
  • m_factor:将计数器值转换为皮秒的转换因子。

这两个变量是在实例启动时被赋值的。

void init_timers(void) {
  double pico_frequency = 1.0e12;
  ...
  my_timer_init(&pfs_timer_info);
  ...
  cycle_v0 = my_timer_cycles();
  nanosec_v0 = my_timer_nanoseconds(); # 获取系统当前时间,以纳秒表示。
  ...
  if (pfs_timer_info.nanoseconds.frequency > 0) {
    nanosec_to_pico =
        lrint(pico_frequency / (double)pfs_timer_info.nanoseconds.frequency);
  } else {
    nanosec_to_pico = 0;
  }
  ...
  to_pico_data[TIMER_NAME_NANOSEC].m_v0 = nanosec_v0;
  to_pico_data[TIMER_NAME_NANOSEC].m_factor = nanosec_to_pico;
  ...
}

可以看到,nanosec_v0 调用的函数,实际上同 m_timer_start、m_timer_end 一样,都是my_timer_nanoseconds

nanosec_to_pico 是将纳秒转换为皮秒的转换因子,等于 1.0e12/1.0e9 = 1000。

案例

基于上面的分析,我们总结下 TIMER_START 的计算公式。

TIMER_START = (语句执行时的系统时间(单位纳秒)- 实例启动时的系统时间(单位纳秒))* 1000

所以,如果要获取语句执行时的系统时间,可将 TIMER_START 除以 1000,然后再加上实例启动时的系统时间。

而实例启动时的系统时间,可通过当前时间(now)减去Uptime这个状态变量来实现。

下面我们通过一个具体的案例来验证下。

mysql> create database test;
Query OK, 1 row affected (0.01 sec)

mysql> create table test.t1(id int primary key, c1 datetime(6));
Query OK, 0 rows affected (0.05 sec)

mysql> insert into test.t1 values(1, now(6));
Query OK, 1 row affected (0.02 sec)

mysql> select * from test.t1;
+----+----------------------------+
| id | c1                         |
+----+----------------------------+
|  1 | 2023-12-05 23:57:01.892242 |
+----+----------------------------+
1 row in set (0.01 sec)

mysql> select * from performance_schema.events_statements_history where digest_text like '%insert%'\G
*************************** 1. row ***************************
              THREAD_ID: 69
               EVENT_ID: 8
           END_EVENT_ID: 9
             EVENT_NAME: statement/sql/insert
                 SOURCE: init_net_server_extension.cc:97
            TIMER_START: 24182166000000
              TIMER_END: 24208896000000
             TIMER_WAIT: 26730000000
              LOCK_TIME: 254000000
               SQL_TEXT: insert into test.t1 values(1, now(6))
                 DIGEST: b2e0770f7505d35d2894321783fe92b7ebfbb908f687b98966efdc58d3386b3c
            DIGEST_TEXT: INSERT INTO `test` . `t1` VALUES ( ? , NOW (?) )
            ...
       EXECUTION_ENGINE: PRIMARY
1 row in set (0.04 sec)

mysql> select (unix_timestamp(now(6)) - variable_value) * 1000000000 into @mysql_start_time from performance_schema.global_status where variable_name = 'uptime';
Query OK, 1 row affected (0.02 sec)

mysql> select sql_text, timer_start, from_unixtime((timer_start/1000 + @mysql_start_time)/1000000000) as formatted_time from performance_schema.events_statements_history where digest_text like '%insert%';
+---------------------------------------+----------------+----------------------------+
| sql_text                              | timer_start    | formatted_time             |
+---------------------------------------+----------------+----------------------------+
| insert into test.t1 values(1, now(6)) | 24182166000000 | 2023-12-05 23:57:02.356767 |
+---------------------------------------+----------------+----------------------------+
1 row in set (0.01 sec)

插入时间(2023-12-05 23:57:01.892242)和 formatted_time(2023-12-05 23:57:02.356767)基本吻合,相差不到 0.5s。

为什么会有误差呢?

  1. Uptime这个状态变量的单位是秒。
  2. 语句的开始时间(m_timer_start)要比语句中的 now(6) 这个时间早。

细节补充

为了可读性,上面其实忽略了很多细节,这里简单记录下。

1. to_pico_data

to_pico_data是个数组,这个数组包含了多个 time_normalizer 类型的元素。

实例启动,在调用init_timers函数时,实际上还会将以微秒、毫秒为单位的系统时间分别赋值给to_pico_data[TIMER_NAME_MICROSEC].m_v0to_pico_data[TIMER_NAME_MILLISEC].m_v0

to_pico_data[TIMER_NAME_CYCLE].m_v0 = cycle_v0;
to_pico_data[TIMER_NAME_CYCLE].m_factor = cycle_to_pico;

to_pico_data[TIMER_NAME_NANOSEC].m_v0 = nanosec_v0;
to_pico_data[TIMER_NAME_NANOSEC].m_factor = nanosec_to_pico;

to_pico_data[TIMER_NAME_MICROSEC].m_v0 = microsec_v0;
to_pico_data[TIMER_NAME_MICROSEC].m_factor = microsec_to_pico;

to_pico_data[TIMER_NAME_MILLISEC].m_v0 = millisec_v0;
to_pico_data[TIMER_NAME_MILLISEC].m_factor = millisec_to_pico;

既然有这么多个 m_v0,怎么知道time_normalizer::to_pico函数取的是哪一个呢?

实际上,events_statements_xxx 系列表的实现中,有个基类table_events_statements_common

该类的构造函数里面会基于time_normalizer::get_statement()来初始化 m_normalizer,

time_normalizer::get_statement()实际上返回的就是to_pico_data[TIMER_NAME_NANOSEC]

table_events_statements_common::table_events_statements_common(
    const PFS_engine_table_share *share, void *pos)
    : PFS_engine_table(share, pos) {
  m_normalizer = time_normalizer::get_statement();
}

time_normalizer *time_normalizer::get_statement() {
  return &to_pico_data[USED_TIMER_NAME];
}

#define USED_TIMER_NAME TIMER_NAME_NANOSEC

2. performance_schema 表的实现注释

storage/perfschema/pfs.cc文件中有一段注释。

这段注释非常重要,它介绍了 performance_schema 中的表是如何实现的。

以下是 events_statements_xxx 相关的注释。

 ... 
 Implemented as:
  - [1] #pfs_start_statement_vc(), #pfs_end_statement_vc()
       (1a, 1b) is an aggregation by EVENT_NAME,
        (1c, 1d, 1e) is an aggregation by TIME,
        (1f) is an aggregation by DIGEST
        all of these are orthogonal,
        and implemented in #pfs_end_statement_vc().
  - [2] #pfs_delete_thread_v1(), #aggregate_thread_statements()
  - [3] @c PFS_account::aggregate_statements()
  - [4] @c PFS_host::aggregate_statements()
  - [A] EVENTS_STATEMENTS_SUMMARY_BY_THREAD_BY_EVENT_NAME,
        @c table_esms_by_thread_by_event_name::make_row()
  ...
  - [H] EVENTS_STATEMENTS_HISTORY_LONG,
        @c table_events_statements_history_long::make_row()
  - [I] EVENTS_STATEMENTS_SUMMARY_BY_DIGEST
        @c table_esms_by_digest::make_row()

3. 如何知道 TIMER 字段对应 m_row 中的哪些变量?

两者的对应关系实际上是在table_events_statements_common::read_row_values中定义的。文章来源地址https://www.toymoban.com/news/detail-750706.html

int table_events_statements_common::read_row_values(TABLE *table,
                                                    unsigned char *buf,
                                                    Field **fields,
                                                    bool read_all) {
  Field *f;
  uint len;

  /* Set the null bits */
  assert(table->s->null_bytes == 3);
  buf[0] = 0;
  buf[1] = 0;
  buf[2] = 0;

  for (; (f = *fields); fields++) {
    if (read_all || bitmap_is_set(table->read_set, f->field_index())) {
      switch (f->field_index()) {
        case 0: /* THREAD_ID */
          set_field_ulonglong(f, m_row.m_thread_internal_id);
          break;
        ...
        case 5: /* TIMER_START */
          if (m_row.m_timer_start != 0) {
            set_field_ulonglong(f, m_row.m_timer_start);
          } else {
            f->set_null();
          }
          break;
        case 6: /* TIMER_END */
          if (m_row.m_timer_end != 0) {
            set_field_ulonglong(f, m_row.m_timer_end);
          } else {
            f->set_null();
          }
          break;
          ...

到了这里,关于如何将 performance_schema 中的 TIMER 字段转换为日期时间的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • pg mysql oracle 中的schema

    pg中的schema 表示当前db中数据库对象的命名空间(namespace),数据库对象包括但不限于表、函数、视图、索引等。 对于熟悉mysql的人来说,在第一次看到pg中的schema的概念时,可能会疑惑,schema不是表示database的吗? 注: mysql中schema和 database是一个概念。create database 和create sche

    2024年02月08日
    浏览(42)
  • Python中的定时器用法:Timer定时器和schedule库

    目录 一、引言 二、Timer定时器 1、Timer定时器的原理 2、Timer定时器的使用方法 3、Timer定时器的实际应用案例 三、schedule库 1、schedule库的原理 2、schedule库的使用方法 3、schedule库的实际应用案例 四、Timer定时器和schedule库的比较 1、功能差异 2、适用场景 五、实际应用案例 六、

    2024年01月16日
    浏览(74)
  • MySQL必知必会:MySQL中的Schema与DataBase

    涉及到数据库的模式有很多疑惑,问题经常出现在模式和数据库之间是否有区别,如果有,区别在哪里。 取决于数据库供应商 对schema(模式)产生疑惑的一部分原因是数据库系统倾向于以自己的方式处理模式 (1)MySQL的文档中指出,在物理上,模式与数据库是同义的,所以

    2023年04月27日
    浏览(44)
  • Django rest_framework Serializer中的create、Views中的create/perform_create的区别

    对于后端来说,前后端分离的方式能让前后端的开发都爽。和所有的爽一样,每爽一次都要付出一定的代价。而前后端分离的代价,就是后端要面对巨量的模块化的功能组件以及这些组件的常规用法与重写复用。有一点经验,关于[Django rest_framework ] Serializer 中的create()、 Vie

    2024年02月13日
    浏览(35)
  • 如何安全地变更数据库 Schema

    最近 Reddit 的 r/golang 下有人问了一个如何做数据库 schema 变更的问题,不到一天,就有了超过 40 条回复。 数据库 schema 变更一直是让程序员头疼的问题,但又不得不面对,毕竟业务要发展,产品要迭代,添加新的功能往往需要去修改数据库的结构,比如添加一个新的字段来保

    2024年02月10日
    浏览(49)
  • 小程序如何修改缓存中的某一个字段的值;小程序中如何应用vant组件 如:van-dropdown-item、van-field

    将数据放入指定缓存中。 这里缓存块的名称叫‘mydata’,你可以根据自己的需求,取合适的名字。代码如下 效果:这里是存了个对象当示例。也可以存数组,字符串等。 使用 wx.getStorageSync(‘myData’),即可取出‘myData’中缓存的数据。以下代码打印结果为 hello 先取出数据,

    2024年02月09日
    浏览(62)
  • Java接口中的字段

    在Java接口中,可以定义字段(field)。接口中的字段隐含着是public、static、和final的。在字段的前面可以加public、static、和final这样的一个或多个修饰符,但加也是多余的,因为加不加的效果相同。 代码示例: 运行输出:

    2024年02月16日
    浏览(36)
  • Elasticsearch:ES|QL 查询中的元数据字段及多值字段

    在今天的文章里,我来介绍一下 ES|QL 里的元数据字段以及多值字段。我们可以利用这些元数据字段以及多值字段来针对我们的查询进行定制。这里例子的数据集,请参考文章 “Elasticsearch:ES|QL 快速入门”。 ES|QL 可以访问元数据字段。 目前支持的有: _index :文档所属的索引

    2024年02月04日
    浏览(61)
  • Mysql替换字段中的指定文本

    最近有个需求,不同的环境的ip和端口都不一样,所以就要修改表里面字段的值 但是手动修改比较麻烦而且很慢,所以在网上搜了下相关的方法。经过手动实践确实可行,下面分享给大家 为了方便演示,准备一个表和几个字段,随便给点值: 可以看到,我有三个字段分别为

    2024年02月09日
    浏览(39)
  • Django中数据库模型中的DecimalField字段和IntegerField字段有何区别?

    在Django的数据库模型中, DecimalField 和 IntegerField 是两种不同的字段类型,用于存储数字数据。它们的主要区别在于支持的数据范围和精度。 IntegerField 是用于存储整数值的字段类型。它可以存储包含正数、负数和零在内的整数值。 IntegerField 的取值范围是由所使用的数据库系

    2024年02月16日
    浏览(38)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包