sysstatのバイナリファイルフォーマット

はじめに

sysstat のバイナリファイルのフォーマットを調べてみたメモです。

Documents のページを見てみましたが、ファイルフォーマットについての記述は見つけられませんでした。

FAQ の “2.5. Are sar daily data files fully compatible with Sun Solaris format sar files?” で Solaris と Linux では形式が違うことはわかりました。

portability of sa data files · Issue #135 · sysstat/sysstat というイシューの2016年のコメントで sqlite などを使うことも考えているとも書かれていましたが、 2020-08-09 時点では独自のバイナリファイル形式です。

ということでコードリーディングしてみました。 対象は Ubuntu 20.04 LTS の sysstat パッケージに合わせて 12.2.0 にします(ちなみに 2020-08-09 時点の最新リリースの 12.4.0 で、 Ubuntu 18.04 LTS の sysstat パッケージは 11.6.1 でした)。

ファイル全体の構成図

sa.h 内のコメントにファイル全体の構成図が書いてありました。

sa.h#L347-L445

全ての書き込みは write_all 関数で行う

ag 'write\(' で検索してみた感じでは、全ての書き込みは write_all 関数で行っているようです。

一部 pmiWrite (3) という関数を使っている箇所もありますでが、今回は対象外とします。

sadc.c 内の write_all 関数の呼び出し箇所

write_all 関数の呼び出し箇所は sa_conv.c にもありますが、ここでは sadc.c のみを見ていきます。 sadc.c 内での write_all 関数の呼び出し箇所は以下の9箇所です。

sadc.c
480:    if (write_all(fd, &file_magic, FILE_MAGIC_SIZE) != FILE_MAGIC_SIZE) {
530:    if (write_all(fd, &file_hdr, FILE_HEADER_SIZE) != FILE_HEADER_SIZE) {
561:                    if (write_all(fd, &file_act, FILE_ACTIVITY_SIZE) != FILE_ACTIVITY_SIZE) {
584:    if (write_all(ofd, &(act[p]->nr_ini), sizeof(__nr_t)) != sizeof(__nr_t)) {
625:    if (write_all(ofd, &record_hdr, RECORD_HEADER_SIZE) != RECORD_HEADER_SIZE) {
635:            if (write_all(ofd, comment, MAX_COMMENT_LEN) != MAX_COMMENT_LEN) {
664:    if (write_all(ofd, &record_hdr, RECORD_HEADER_SIZE) != RECORD_HEADER_SIZE) {
678:                            if (write_all(ofd, &(act[p]->_nr0), sizeof(__nr_t)) != sizeof(__nr_t)) {
682:                    if (write_all(ofd, act[p]->_buf0, act[p]->fsize * act[p]->_nr0 * act[p]->nr2) !=

1. setup_file_hdr 関数で file_magic 構造体を書く

setup_file_hdr 関数内の sadc.c#L480 (下記にインデント省略して引用)

if (write_all(fd, &file_magic, FILE_MAGIC_SIZE) != FILE_MAGIC_SIZE) {

でファイルのマジックヘッダーを書き込んでいます。

fill_magic_header 関数と struct file_magic 構造体などを見るとファイルのマジックヘッダーは以下のようになります。

struct file_magic 構造体76バイト。実際の例は下記のとおりです。

$ xxd -l 76 /var/log/sysstat/sa09
00000000: 96d5 7521 0c02 0000 5001 0000 0000 0000  ..u!....P.......
00000010: 0100 0000 0100 0000 0c00 0000 0000 0000  ................
00000020: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000030: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000040: 0000 0000 0000 0000 0000 0000            ............

2. setup_file_hdr 関数で file_header 構造体を書く

setup_file_hdr 関数内の sadc.c#L530

if (write_all(fd, &file_hdr, FILE_HEADER_SIZE) != FILE_HEADER_SIZE) {

struct file_header 構造体を書いています。

struct file_header 構造体336バイト。

$ xxd -s 76 -l 336 /var/log/sysstat/sa09
0000004c: 2949 2f5f 0000 0000 6400 0000 0000 0000  )I/_....d.......
0000005c: 0900 0000 1000 0000 7800 0000 0000 0000  ........x.......
0000006c: 0000 0000 0900 0000 0200 0000 0000 0000  ................
0000007c: 0100 0000 2400 0000 1800 0000 0000 0000  ....$...........
0000008c: 0907 084c 696e 7578 0000 0000 0000 0000  ...Linux........
0000009c: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000000ac: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000000bc: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000000cc: 0000 0000 7468 696e 6b63 656e 7472 6500  ....thinkcentre.
000000dc: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000000ec: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000000fc: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000010c: 0000 0000 0035 2e34 2e30 2d34 322d 6765  .....5.4.0-42-ge
0000011c: 6e65 7269 6300 0000 0000 0000 0000 0000  neric...........
0000012c: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000013c: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000014c: 0000 0000 0000 7838 365f 3634 0000 0000  ......x86_64....
0000015c: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000016c: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000017c: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000018c: 0000 0000 0000 004a 5354 0000 0000 0000  .......JST......

3. setup_file_hdr 関数で file_activity 構造体を書く

setup_file_hdr 関数内の sadc.c#L561

if (write_all(fd, &file_act, FILE_ACTIVITY_SIZE) != FILE_ACTIVITY_SIZE) {

struct file_activity 構造体を書いています。

sadc.c#L538for ループで最大 NR_ACT = 39 個の struct file_activity を書きます。

実際の個数は file_header 構造体の sa_act_nr に保持されていて今回の例では 16 です。

$ echo '76 + 336' | bc
412
$ echo '16 * 36' | bc
576

16個の file_activity

$ xxd -s 412 -l 576 /var/log/sysstat/sa09
0000019c: 0100 0000 8b00 0000 0900 0000 0100 0000  ................
000001ac: 0100 0000 5000 0000 0a00 0000 0000 0000  ....P...........
000001bc: 0000 0000 0200 0000 8b00 0000 0100 0000  ................
000001cc: 0100 0000 0000 0000 1000 0000 0100 0000  ................
000001dc: 0100 0000 0000 0000 0400 0000 8a00 0000  ................
000001ec: 0100 0000 0100 0000 0000 0000 1000 0000  ................
000001fc: 0000 0000 0200 0000 0000 0000 0500 0000  ................
0000020c: 8a00 0000 0100 0000 0100 0000 0000 0000  ................
0000021c: 4000 0000 0000 0000 0800 0000 0000 0000  @...............
0000022c: 0600 0000 8b00 0000 0100 0000 0100 0000  ................
0000023c: 0000 0000 3800 0000 0700 0000 0000 0000  ....8...........
0000024c: 0000 0000 0700 0000 8b00 0000 0100 0000  ................
0000025c: 0100 0000 0000 0000 8800 0000 1100 0000  ................
0000026c: 0000 0000 0000 0000 2200 0000 8b00 0000  ........".......
0000027c: 0100 0000 0100 0000 0000 0000 2000 0000  ............ ...
0000028c: 0400 0000 0000 0000 0000 0000 0800 0000  ................
0000029c: 8b00 0000 0100 0000 0100 0000 0000 0000  ................
000002ac: 2000 0000 0400 0000 0000 0000 0000 0000   ...............
000002bc: 0900 0000 8c00 0000 0100 0000 0100 0000  ................
000002cc: 0000 0000 2800 0000 0300 0000 0000 0000  ....(...........
000002dc: 0300 0000 0b00 0000 8c00 0000 0e00 0000  ................
000002ec: 0100 0000 0100 0000 5000 0000 0300 0000  ........P.......
000002fc: 0300 0000 0800 0000 0c00 0000 8d00 0000  ................
0000030c: 0300 0000 0100 0000 0100 0000 5000 0000  ............P...
0000031c: 0700 0000 0000 0000 0100 0000 0d00 0000  ................
0000032c: 8c00 0000 0300 0000 0100 0000 0100 0000  ................
0000033c: 5800 0000 0900 0000 0000 0000 0000 0000  X...............
0000034c: 0e00 0000 8a00 0000 0100 0000 0100 0000  ................
0000035c: 0000 0000 1800 0000 0000 0000 0000 0000  ................
0000036c: 0600 0000 0f00 0000 8a00 0000 0100 0000  ................
0000037c: 0100 0000 0000 0000 2c00 0000 0000 0000  ........,.......
0000038c: 0000 0000 0b00 0000 1000 0000 8a00 0000  ................
0000039c: 0100 0000 0100 0000 0000 0000 1800 0000  ................
000003ac: 0000 0000 0000 0000 0600 0000 2700 0000  ............'...
000003bc: 8a00 0000 0900 0000 0100 0000 0100 0000  ................
000003cc: 1400 0000 0000 0000 0000 0000 0500 0000  ................

最初の1個の file_activity

$ xxd -s 412 -l 36 /var/log/sysstat/sa09
0000019c: 0100 0000 8b00 0000 0900 0000 0100 0000  ................
000001ac: 0100 0000 5000 0000 0a00 0000 0000 0000  ....P...........
000001bc: 0000 0000                                ....

struct file_activity 構造体。

sadc.c#L540-L563unsigned int id_seq[NR_ACT]struct activity *act[NR_ACT] を参照して、 act の要素をローカル変数の struct file_activity file_act に移し替えてからファイルに書きます。

4. write_new_cpu_nr 関数内で CPU 番号を書く

write_new_cpu_nr 関数内の sadc.c#L584

if (write_all(ofd, &(act[p]->nr_ini), sizeof(__nr_t)) != sizeof(__nr_t)) {

で RESTART レコードの後に CPU の新しい番号を書きます。

5. write_special_record 関数で record_header 構造体を書く

write_special_record 関数内の sadc.c#L625

if (write_all(ofd, &(act[p]->_nr0), sizeof(__nr_t)) != sizeof(__nr_t)) {

struct record_header 構造体の グローバル変数 record_hdr を書きます。

struct record_header 構造体のサイズは24バイトです。

6. write_special_record 関数で comment を書く

write_special_record 関数内の sadc.c#L635

if (write_all(ofd, comment, MAX_COMMENT_LEN) != MAX_COMMENT_LEN) {

char comment[MAX_COMMENT_LEN] を書きます。

コメントの最大長 MAX_COMMENT_LEN は64です。

7. write_stats 関数で record_header 構造体を書く

write_stats 関数内の sadc.c#L664

if (write_all(ofd, &record_hdr, RECORD_HEADER_SIZE) != RECORD_HEADER_SIZE) {

struct record_header 構造体の グローバル変数 record_hdr を書きます。

8. write_stats 関数で activity 構造体の nr[0] を書く

write_stats 関数内の sadc.c#L678

if (write_all(ofd, &(act[p]->_nr0), sizeof(__nr_t)) != sizeof(__nr_t)) {

sa.h#L829

#define _nr0	nr[0]

struct activity 構造体の sa.h#L1020-L1024

/*
 * Number of items, as read and saved in corresponding buffer (@buf: See below).
 * The value may be zero for a particular sample if no items have been found.
 */
__nr_t nr[3];

nr[0] (4バイト) を書きます。

9. write_stats 関数で activity 構造体の buf[0] を書く

write_stats 関数内の sadc.c#L682 (下記にインデント省略して引用)の

if (write_all(ofd, act[p]->_buf0, act[p]->fsize * act[p]->_nr0 * act[p]->nr2) !=

sa.h#L828

#define _buf0	buf[0]

struct activity 構造体の sa.h#L1052-L1059

/*
 * Buffers that will contain the statistics read. Its size is @nr * @nr2 * @size each.
 * [0]: used by sadc.
 * [0] and [1]: current/previous statistics values (used by sar).
 * [2]: Used by sar to save first collected stats (used later to
 * compute average).
 */
void *buf[3];

buf[0] を書きます。 サイズは act[p]->fsize * act[p]->_nr0 * act[p]->nr2 です。

fsizesa.h#L1030-L1035

/*
 * Size of an item.
 * This is the size of the corresponding structure, as read from or written
 * to a file, or read from or written by the data collector.
 */
int fsize;

です。

_nr0 は前項に書いた activity 構造体の nr[0] です。

nr2sa.h#L996-L1013

/*
 * Number of sub-items on the system.
 * @nr2 is in fact the second dimension of a matrix of items, the first
 * one being @nr. @nr is the number of lines, and @nr2 the number of columns.
 * A negative value (-1) is the default value and indicates that this number
 * has still not been calculated by the f_count2() function.
 * A value of 0 means that this number has been calculated, but no sub-items have
 * been found.
 * A positive value (>0) has either been calculated or is a constant.
 * Rules:
 * 1) IF @nr2 = 0 THEN @nr = 0
 *    Note: If @nr = 0, then @nr2 is undetermined (may be -1, 0 or >0).
 * 2) IF @nr > 0 THEN @nr2 > 0.
 *    Note: If @nr2 > 0 then @nr is undetermined (may be -1, 0 or >0).
 * 3) IF @nr <= 0 THEN @nr2 = -1 (this is the default value for @nr2,
 * meaning that it has not been calculated).
 */
__nr_t nr2;

です。

つまり、サイズは (アイテムのサイズ * アイテム数 * サブアイテム数) です。

activity 構造体の buf[0] への値のセット

例えば CPU の統計情報の場合、 struct activity 型のグローバル変数 cpu_actactivity.c#L78

.f_read		= wrap_read_stat_cpu,

で設定している wrap_read_stat_cpu関数

/*
 ***************************************************************************
 * Read CPU statistics.
 *
 * IN:
 * @a	Activity structure.
 *
 * OUT:
 * @a	Activity structure with statistics.
 ***************************************************************************
 */
__read_funct_t wrap_read_stat_cpu(struct activity *a)
{
	struct stats_cpu *st_cpu
		= (struct stats_cpu *) a->_buf0;
	__nr_t nr_read = 0;

	/* Read CPU statistics */
	do {
		nr_read = read_stat_cpu(st_cpu, a->nr_allocated);

		if (nr_read < 0) {
			/* Buffer needs to be reallocated */
			st_cpu = (struct stats_cpu *) reallocate_buffer(a);
		}
	}
	while (nr_read < 0);

	a->_nr0 = nr_read;

	return;
}

activity 構造体の buf[0] へのポインタを struct stats_cpu * にキャストし、そこに read_stat_cpu 関数で common.h#L71 で定義されている

#define STAT			PRE "/proc/stat"

のファイルを読んで struct stats_cpu 構造体に読み込んだ値を設定しています。