CubicLouve

Spring_MTの技術ブログ

BazelでC++をビルドする

以前Bazelの入門の記事をかきましたが、今回はC++をビルドする環境を作成してみます。 spring-mt.hatenablog.com

下記のチュートリアル通りにやっていきます。 Build Tutorial - C++ - Bazel

まずはexampleをcloneしてやっていくのですが、前回やった残りのプロジェクトもあるのでサクッと自分でやっていきます。

【1】単一パッケージ、単一ターゲットでビルド

workspaceのセットアップを行います。

workspaceにはソースファイルとビルド成果物が含まれます。

Bazelのowrkspaceとして、トップディレクトリにWORKSPACEファイルを置きます。

今回は依存関係がないので、空ファイルで置いておきます。

プロジェクトをビルドするために、全てのインプットと依存関係を同一workspaceに含める必要があります。

異なるワークスペースに存在するファイルは、リンクしていなければ互いに独立しています。

BUILDファイルはソースコードなどビルドに必要なファイルやビルド方法、出力されるファイルなどの設定を記述するファイルです。

BUILDファイルを含むワークスペース内のディレクトリはパッケージという単位になります。

BUILDファイルにはBazelのさまざまな種類の設定を記述しますが、一番重要な設定はビルドルールです。

ビルドルールは、実行可能なバイナリやライブラリなど、どのような成果物を作成するかを記述します。

BUILDファイル内のビルドルールのそれぞれの実体はターゲットと呼ばれ、ソース・ファイルと依存関係の特定のセットを指します。ターゲットは他のターゲット郡を指すこともできます。

最初に下記構成にしてみました。

.
├── WORKSPACE
└── main
    ├── BUILD
    └── hello-world.cc

まずは単純なBUILDファイルを見ていきます。

cc_binary(
    name = "hello-world",
    srcs = ["hello-world.cc"],
)

nameで指定したパラメータがターゲット名になります。(nameの指定は必須です。)

hello-worldターゲットはcc_binaryルールをインスタンス化します。 このルールでは依存関係のないhello-world.ccソースファイルから自己完結型の実行可能バイナリをビルドします。

ではビルドしてみます。

ビルドコマンドは

bazel build //main:hello-world

//main: はworkspaseのルートディレクトリからのBUILDファイルが配置されてる相対パスを示しています。hello-worldBUILDファイル内で指定したnameです。

% bazel build //main:hello-world
.....................
INFO: Found 1 target...
Target //main:hello-world up-to-date:
  bazel-bin/main/hello-world
INFO: Elapsed time: 12.816s, Critical Path: 3.27s
% bazel build //main:hello-world
INFO: Found 1 target...
Target //main:hello-world up-to-date:
  bazel-bin/main/hello-world
INFO: Elapsed time: 0.165s, Critical Path: 0.00s

(二回目はキャッシュが使われて高速にビルドできています)

ビルドが完了すると、workspaceのルート直下のbazel-binに成果物ができています。

 % bazel-bin/main/hello-world
Hello world
Fri Aug  4 01:11:51 2017

依存性の確認

BUILDファイルにはビルドの依存関係がすべて明示的に記述されています。

Bazelはこれらのステートメントを使用してプロジェクトの依存関係グラフを作成し、正確なインクリメンタルビルドを可能にします。

前述のプロジェクトの依存性のグラフは下記コマンドで生成できます。

% bazel query --nohost_deps --noimplicit_deps 'deps(//main:hello-world)' --output graph
digraph mygraph {
  node [shape=box];
"//main:hello-world"
"//main:hello-world" -> "//main:hello-world.cc"
"//main:hello-world.cc"
}

グラフ構造はGraphViz で可視化できます。

【2】単一のパッケージ、複数のターゲット

大きなプロジェクトを複数のターゲットとパッケージに分割することで、変更した部分の再ビルドだけの高速なインクリメンタルビルドができ、分割されたプロジェクトを一度にビルドするとことでビルド時間の短縮ができます。

【1】プロジェクトを2つのターゲットに分けます。

diff --git a/CppExamples/main/BUILD b/CppExamples/main/BUILD
index 20c6f47..43b22a4 100644
--- a/CppExamples/main/BUILD
+++ b/CppExamples/main/BUILD
@@ -1,4 +1,13 @@
+cc_library(
+    name = "hello-greet",
+    srcs = ["hello-greet.cc"],
+    hdrs = ["hello-greet.h"],
+)
+
 cc_binary(
     name = "hello-world",
     srcs = ["hello-world.cc"],
+    deps = [
+        ":hello-greet",
+    ],
 )

hello-worldのリンクしたいライブラリのラベルをdepsに指定します。

こうすると、hello-worldをビルドする前に、hello-greetのビルドが実行されます。

% bazel build //main:hello-world
INFO: Found 1 target...
Target //main:hello-world up-to-date:
  bazel-bin/main/hello-world
INFO: Elapsed time: 5.813s, Critical Path: 1.16s

% bazel-bin/main/hello-world
Hello world
Sun Aug  6 15:23:47 2017

hello-greet.ccが修正されたら、hello-greet.ccのみコンパイルが行われます。

【3】複数のパッケージ、複数のターゲット

BUILDファイルを含む新しいディレクトリを作成します。 (BUILDファイルを含むワークスペース内のディレクトリはパッケージという単位)

cc_library(
    name = "hello-time",
    srcs = ["hello-time.cc"],
    hdrs = ["hello-time.h"],
    visibility = ["//main:__pkg__"],
)

srcsでソースファイルを、hdrsでヘッダーファイルを指定します。

lib/BUILDのなかで指定した// lib:hello-timeターゲットをmain/ BUILDから明示的に見えるようにするためにvisibility//main:__pkg__を指定しています。

デフォルトでは、ターゲットは同じBUILDファイル内の他のターゲットに対してのみだけに公開されています。

Bazelは、ターゲットの可視性を利用して、ライブラリの実装の詳細がパブリックAPIに漏れることを防いでいます。

Common Definitions - Bazel

ビルドをしてみます。

% bazel build //main:hello-world
INFO: Found 1 target...
Target //main:hello-world up-to-date:
  bazel-bin/main/hello-world
INFO: Elapsed time: 0.365s, Critical Path: 0.00s
% bazel-bin/main/hello-world
Hello world
Sun Aug  6 22:01:01 2017

ラベルの使い方

BUILDファイルやコマンドライン上では、ラベルからターゲットを参照します。

シンタックスは下記の通り。

//path/to/package:target-name

ターゲットがruleのターゲットの場合、path/to/packageはBUILDファイルを含むディレクトリへのパスで、target-nameはBUILDファイルのターゲット(name)の名前です。

ターゲットがファイルである場合、path/to/packageはパッケージのrootからのパスであり、target-nameはターゲットファイル名です。

同じパッケージ内のターゲットを参照するときは、パッケージパスを省略して//:target-nameとだけで使えます。

同じBUILDファイル内のターゲットを参照する場合は、//のworkspaceのルート識別子をスキップして、:target-nameだけを使用することができます。

Q4MのConditional Subscriptionのベンチマーク

Q4MのConditional Subscriptionを使う前にベンチマークを取ってみました。

Q4M - Tutorial

マシン

Model Name: MacBook Pro
Model Identifier: MacBookPro13,2
Processor Name: Intel Core i5
Processor Speed: 2.9 GHz
Number of Processors: 1
Total Number of Cores: 2
L2 Cache (per Core): 256 KB
L3 Cache: 4 MB
Memory: 16 GB

テーブルスキーマ

CREATE TABLE `q4m_test` (
  `id` varchar(255) COLLATE utf8mb4_bin NOT NULL,
  `payload` text COLLATE utf8mb4_bin NOT NULL,
  `enqueued_at` int(10) unsigned NOT NULL
) ENGINE=QUEUE;  

enqueued_atをConditional Subscriptionで使います。

enqueued_atにはキューを入れた時間を入れています。

Conditional Subscriptionにはintegral values(整数値)なので、unixtimestamp形式で保存しています。

ベンチの条件

予め6000件のデータを作成しました。 payloadには1000バイトの文字列を入れています。

【1】6000件のデータのうち、最後の1000件のキューを取り出して処理するパフォーマンス

こんなスクリプトで計測しました。

require 'mysql2'
require 'benchmark'

client = Mysql2::Client.new(host: "localhost", username: "root", database: 'test')

1000.times do
  result = Benchmark.realtime do
    result = client.query("SELECT queue_wait('q4m_test:enqueued_at>=1501686146', 5)")                                                                   
    client.query('select queue_end()')
  end
  puts "#{result}"
end

結果

f:id:Spring_MT:20170803002413p:plain

縦軸が秒になります。 横軸が時間軸になります。

特に劣化する様子はありません。

【2】5000件のデータを処理するパフォーマンス

上で5000件になったデータを全て順に処理していくようにして処理速度を計測してみました。

require 'mysql2'
require 'benchmark'

client = Mysql2::Client.new(host: "localhost", username: "root", database: 'test')

1000.times do
  result = Benchmark.realtime do
    result = client.query("SELECT queue_wait('q4m_test:enqueued_at<1501686146', 5)")                                                                   
    client.query('select queue_end()')
  end
  puts "#{result}"
end

結果

f:id:Spring_MT:20170803002854p:plain

縦軸が秒になります。 横軸が時間軸になります。

1とくらべてもそこまで大きな変化はありませんでした。

キューを入れる処理

キューを入れるときも処理速度を計測してみました。

require 'mysql2'
require 'benchmark'
require 'securerandom'

client = Mysql2::Client.new(host: "localhost", username: "root", database: 'test')
payload = 'a' * 1000

stmt = client.prepare('INSERT INTO q4m_test VALUES (?, ?, ?)')
                                                                                                                                                        
4000.times do
  result = Benchmark.realtime do
    stmt.execute(SecureRandom.uuid, payload, Time.now.to_i + 1000)
  end
  puts "#{result}"
end

結果

f:id:Spring_MT:20170803004509p:plain

○でくくったパフォーマンスが劣化した部分はおそらく、compactionが走った影響かと思われます。

参考にした記事

lestrrat.ldblog.jp

Bazel入門

Bazelとは

Googleが内部で利用していたビルドツールのオープンソース版として提供されているものです。

Bazel - a fast, scalable, multi-language and extensible build system" - Bazel

FAQ - Bazel

ビルドやテストの高速化を目指して作られています。

C++Javaだけでなく、iOSAndroidアプリのビルドにも対応しています。

今後のロードマップにはAndroid Studioとの統合も入っています。

Bazelを導入する理由

  • Gradleよりも構造的な設計になっていて、各アクションが何をするかが正確に理解しやすい構成になっている(らしい)。
  • すでに、ローカルでのキャッシュ、並列実行、依存性解析の最適化などが行われており高速に動作する。 さらに、分散キャッシュの仕組みも今絶賛入ろうとしていることろのなので、ビルドの高速化という意味では期待ができそう。

Bazelの公式ページも参照ください。

FAQ - Bazel

Bazelのinstall

Macであればhomebrew経由でインストールできます。

Installing Bazel on macOS - Bazel

homebrewでinstallすると最後に補完スクリプトのパスが表示されるのでそれを利用してzshの補完の設定をします。

Bash completion has been installed to:
  /usr/local/etc/bash_completion.d

zsh completions have been installed to:
  /usr/local/share/zsh/site-functions

Installing Bazel - Bazel

fpath[1,0]=~/.zsh/completion/
mkdir -p ~/.zsh/completion/
cp scripts/zsh_completion/_bazel ~/.zsh/completion

rm -f ~/.zcompdump; compinit

# This way the completion script does not have to parse Bazel's options
# repeatedly.  The directory in cache-path must be created manually.
zstyle ':completion:*' use-cache on
zstyle ':completion:*' cache-path ~/.zsh/cache

Bazelでビルド

まずは簡単なチュートリアルを試してみます。

Getting Started - Bazel

WORKSPACEファイル

Bazelは、ビルドしたいソースコードとビルド成果物を含むworkspaceというディレクトリでビルドを行います。

必要に応じて、1つのworkspaceを複数のプロジェクトで共有することができます。

workspaceはどこでもよいのですが、トップディレクトリにWORKSPACEファイルを必ず置く必要があります。

このWORKSPACEファイルに外部の依存性の定義を行います。

依存がなければ空ファイルでも大丈夫です。

touch WORKSPACE

BUILDファイル

Bazelは、プロジェクト内でのビルドターゲットを知るために、BUILDファイルを使います。

BUILDファイルはPythonに似たBazelのビルド用言語で記述します。

BUILDファイルにいろいろなルールを書いていきます。

各ルールは、入力、出力、および入力からの出力を計算する方法を指定します。

genruleについて

シェルコマンドを呼び出すgenruleは、Makefileを使っている人たちにおなじみのルールです。

genruleの詳細は下記ページにまとまっています。

General Rules - Bazel

今回は単純にechoコマンドで標準出力に出した内容をリダイレクトしてファイルに書き込むルールを作ってみます。

genruleでは、 cmdで指定したコマンドが実行されます。

genrule(
  name = "hello",
  outs = ["hello_world.txt"],
  cmd = "echo Hello World > $@",
)

今回、cmdの中で使われている$@はBazelの中で使える変数で、outsが一つの場合において、outsの内容が展開されます。

利用可能な変数の一覧は Make variables にまとまっています。

ターゲットは、ルールの中で設定しているnameによって指定されるlabelになります。

また、Bazelの成果物はソースツリーを汚染しないようにするために、ソースツリーとは別のbazel-genfilesディレクトリに格納されます。

1回目

% bazel build :hello
....................
INFO: Found 1 target...
Target //:hello up-to-date:
  bazel-genfiles/hello_world.txt
INFO: Elapsed time: 7.322s, Critical Path: 0.01s

2回目

% bazel build :hello
INFO: Found 1 target...
Target //:hello up-to-date:
  bazel-genfiles/hello_world.txt
INFO: Elapsed time: 0.155s, Critical Path: 0.00s

% cat bazel-genfiles/hello_world.txt
Hello World

BUILDファイルにoutsを複数して$@を利用するとビルドエラーになります。

genrule(
  name = "hello",
  outs = ["hello_world.txt", "hello_world2.txt"],
  cmd = "echo Hello World > $@",
)
% bazel build :hello
ERROR: /Users/hoge/BazelTest/SampleEcho/BUILD:4:9: in cmd attribute of genrule rule //:hello: variable '$@' : more than one output file.
ERROR: Analysis of target '//:hello' failed; build aborted.
INFO: Elapsed time: 0.161s

ルールは、他のルールの出力を入力として利用できます。

生成したsrcはlabelから参照できます。

$<srcのファイルの変数です。

genrule(
  name = "hello",
  outs = ["hello_world.txt"],
  cmd = "echo Hello World > $@",
)

genrule(
  name = "double",
  srcs = [":hello"],
  outs = ["double_hello.txt"],
  cmd = "cat $< $< > $@",                                                                                                                               
)
% bazel build :double
INFO: Found 1 target...
Target //:double up-to-date:
  bazel-genfiles/double_hello.txt
INFO: Elapsed time: 0.396s, Critical Path: 0.04s
% cat bazel-genfiles/double_hello.txt
Hello World
Hello World

genruleですが、通常利用するためのものではないです。

それぞれの言語に特化したルールがあるので、それを使ったほうがよいです。

Build Encyclopedia - Bazel

参考にした記事

knowledge.sakura.ad.jp

blog.matsuokah.jp

Common C++ Build Use Cases - Bazel

github.com

github.com

ログについてのまとめ

ログとは

時系列順になっているデータのこと。

全てが過去に発生する。

データログ - Wikipedia

ログ取得の目的

  • 記録・履歴(点)
    • 事象単体が意味を持つ場合に、時刻を含めてそのときの状況のデータを記録する
      • エラーログ
      • クラッシュログ
  • 変化量を捉える(線)
    • 時系列順に連続して観察したい場合に、時刻を含めてその時の状況のデータを記録し、その後に連続性を確認する
    • 連続性を確認するために、グラフなどで可視化する必要がある場合が多い

ログの設計

ログは出そうと思えばいくらでもだせる。

(それこそメソッド呼び出し単位で)

全部を出すと必要なデータが埋もれてしまう。

なので、ログの目的を明確化し、必要なログだけを出すようにする。

name Who 誰が Whom 誰に対して Where どこに 最終的な出力場所 What 何を When いつ 出力するタイミング Why なぜ HOW to どのように 出力方法 How many どれくらいの量 How much コスト感
アクセスログ Web サーバー 開発者 Hadoop アクセスログ アクセスするたびに ユーザー調査のため fluentd 100 logs / sec
アプリログ
分析ログ

ログの内容

ログの内容の基本的なもの。

情報は出力しすぎるとコストになるので出力する情報が必要か検討する。

名前 説明 詳細
When いつ 出力日時 yyyy-MM-ddTHH:mm:ss.ffffff、単位、出力時刻のタイムゾーン等を決めておく
Where どこで 出力された場所 アプリ名 ホスト、サーバーのIPアドレス プロセスID ログを出力したコードのパスや行数
Who 誰が クライアントのIPアドレス、ユーザーID、ユーザーエージェント、MySQLIPアドレス
What 何を ログレベル メッセージ 理由とか 実行コマンド

ログレベル

多くのアプリケーションで、アプリケーションの状態や処理内容を開発者に通知するためのログに重要度(ログレベル)を設定する。

(分析用のログにはないこともある)

ここでは代表的なログレベルについて記載する。

ログレベル 概要 説明 出力先 運用時の対応
fatal 致命的なエラー コンソール, ファイル 即時対応が必要
error エラー コンソール, ファイル 営業時間内のみ対応
warn 警告 コンソール, ファイル 次回リリースまでに対応
info 情報 コンソール, ファイル 対応不要
debug デバッグ情報 ファイル 出力しない

設定されたログレベルによって、ログの出力をコントロールできるようにしておく。(開発環境ではdebugまで出力するが、本番環境ではinfoまでしか出力しないようにするとか)

ログのフォーマット

ログの管理

ローテーションと保存期間

一つのファイルにログを書き続けると、一ファイルあたりの容量が増えていくのであとで加工しにくい(ログの肥大化)。

一定の容量や期間でログをまとめて別ファイルにして管理するようにする。

また一定期間を過ぎたログを削除もしくバックアップサーバー等に転送することで、サーバーのディスク容量があふれるのを防ぐ

logrotateなどがその役割を担ってくれている。

ログの活用

流量の監視

ログの送信方法

大量に送る場合の負荷

参照

dev.classmethod.jp

techblog.yahoo.co.jp

8.2.1 ログの運用

www.ipa.go.jp

Ruby2.4に上げたら"ArgumentError: key must be %d bytes" のエラーが出るようになったことの調査と暗号化の復習

Rubyの2.4以上から、共通鍵暗号を扱うOpenSSL::Cipherにおいて、暗号化鍵と初期化ベクトル(IV)を設定する際に、指定した暗号化方式の鍵長のビット数を超えて指定した場合、ArgumentErrorを返すようになりました。

irb(main):001:0> require 'openssl'
=> true
irb(main):002:0> c = OpenSSL::Cipher.new('aes-256-cbc')
=> #<OpenSSL::Cipher:0x007fc3c389da30>
irb(main):003:0> c.encrypt
=> #<OpenSSL::Cipher:0x007fc3c389da30>
irb(main):004:0> c.key = "1" * 33
ArgumentError: key must be 32 bytes
    from (irb):4:in `key='
    from (irb):4
    from /Users/hoge/.rbenv/versions/2.4.1/bin/irb:11:in `<main>'
irb(main):018:0> c.iv = "1" * 17
ArgumentError: iv must be 16 bytes
    from (irb):18:in `iv='
    from (irb):18
    from /Users/hoge/.rbenv/versions/2.4.1/bin/irb:11:in `<main>'

2.3までは短い場合のみエラーになっており、長い場合は切り捨てて使っていました。

https://github.com/ruby/ruby/blob/v2_4_0/doc/ChangeLog-2.4.0#L4332

github.com

class OpenSSL::Cipher (Ruby 2.4.0)

切り捨ててしまうのは、そもそもOpenSSLの仕様です。

なぜ切り捨てるようにできるのか?というのを暗号化の復習も兼ねて調べてみました。

暗号化

暗号化とは、意味の分かる情報(平文)を、意味の分からない情報(暗号文)に変換することを言います。

また、意味の分からない情報を意味の分かる情報に戻すことを復号(化)と言います。

暗号 - Wikipedia

暗号化する際に使う変換のアルゴリズムとかの歴史を追っていくと終わらないので、こちらを是非読んで貰えれば。

暗号化周りをわかりやすく読みやすく書いている本なので一読することをおすすめします。

AESとは

今回は暗号化方式の一つのAESを取り上げます。 AESはブロック暗号という暗号アルゴリズムに当たります。

ブロック暗号は、特定の固定長のビット数のまとまり(ブロック)に分けて、ブロックごとに暗号化していく暗号アルゴリズムのことです。

ブロックのビット数はブロック長と呼ばれます。

AESは128ビット(16バイト)のブロック長のみです。

暗号化する対象は、この固定長のまとまりであるブロック一つより大きいことがほとんどなので、 ブロック長ごとにブロック暗号アルゴリズムを繰り返し使って全体を暗号化する事になります。

この繰り返しの方法をブロック暗号のモードと呼びます。(複数の方法があります)

また、AESは暗号化と復号に同一の(共通の)鍵を使う対称鍵暗号化方式(共通鍵暗号方式)です。

AESの暗号化について

AESでは暗号化の鍵のビット長(鍵長)は128ビット、192ビット、256ビットが選択可能です。

この鍵を使って、ブロック長の128ビットと同じ長さのサブ鍵を作成して、それを利用して暗号化を行います。

なので、ブロック長と同じ長さである必要はありません。

鍵長がながければ長いほど、暗号強度は上がります。

AESの暗号化・復号ロジックの詳細は

の3章や

www.atmarkit.co.jp

などを参照ください。

ブロック暗号のモード CBC(Cipher Block Chaining)について

一つ前の暗号ブロックと平文ブロックのXOR(排他的論理和)を取ってから暗号化を行うモードです。

初期化ベクトル(IV)

最初の平文を暗号化するときには一つ前のの暗号化ブロックがないので、1ブロック分のビット列を用意する必要があります。 このビット列を初期化ベクトル(IV)といいます。

CBCモードの暗号化、復号化のフロー

CBCモードのフローは下記の通りです。

CFB encryption 作者 Gwenda (PNG version), WhiteTimberwolf (SVG version) (PNG version) [Public domain], ウィキメディア・コモンズ経由で

CFB decryption

暗号利用モード - Wikipedia

長い鍵と初期化ベクトル(IV)は切り捨てても問題ない?

そもそもAESの場合だと、鍵長を長くしても扱えませんし、IVを長くしてもブロック長を超えて与えても使えないから切り捨てるしかないですね。

でも、意図せずそういう設定にしてしまった可能性があるので、Rubyではあえてエラーにしているということかなと思います。

鍵のとIVを生成

ビット列として十分のランダムであることが求められます。

例えば鍵長が128ビットだと、2の128乗で340282366920938463463374607431768211456通り組み合わせがあります。

ただし、a-z A-Z 0-9 のasciiだけ鍵を作ろうとすると、62 の 16乗(1byte = 8bit)で、47672401706823533450263330816通りの組み合わせに減ってしまいます。

そこで、Rubyであれば、 module SecureRandom (Ruby 2.4.0) を使って、安全な鍵を生成するのがよいかと思います。

irb(main):001:0> require 'securerandom'
=> true
irb(main):002:0> SecureRandom.base64(16)
=> "NkIG8GcEL9fYTH+BXKYjQg=="

参考

理解してるつもりの SSL/TLS でも、もっと理解したら面白かった話 · けんごのお屋敷

thinkit.co.jp

The AES-CBC Cipher Algorithm and Its Use with IPsec

http://www.risk.tsukuba.ac.jp/pdf/group-work2005/2005group-4-resume.pdf

鍵 (暗号) - Wikipedia

openssl/evp_enc.c at 64846096b18340b9a39ddd29a7a0e23c56f22959 · openssl/openssl · GitHub

この記事は、クリエイティブ・コモンズ・表示・継承ライセンス3.0のもとで公表されたウィキペディアの項目「暗号利用モード」を素材として二次利用しています。

壊れたgzipを調べたときのメモ

壊れたgzipがあり、展開できないと言われて調査したときにどうやったかのメモ

gzipとは

www.gzip.org

www.futomi.com

zcatで展開できるところまでしてみる

zcatは破損した位置までのデータを修復できる。

展開できたデータの最後を確認して修復する。

バイナリを見る

壊れているgzipの内容

$ od -xc data.json.gz | tail
        342 213 322 264   @ 370   D 024 365 221 243 003   )   u   3   >
2572260    346a    d38e    1aea    bcb8    7f2f    5a69    d8f8    0865
          j   4 216 323 352 032 270 274   / 177   i   Z 370 330   e  \b
2572300    0f84    f0a9    9d00    f404    03af    33e1    f451    e212
        204 017 251 360  \0 235 004 364 257 003 341   3   Q 364 022 342
2572320    e79b    818a    b9f8    6228    36a0    91d9    7edb    173c
        233 347 212 201 370 271   (   b 240   6 331 221 333   ~   < 027
2572340    20d0    f29c    ac0f    732d    b57b    4cb7    0a01
        320     234 362 017 254   -   s   { 265 267   L 001  \n
2572356

最後に改行文字が入っていた。

gzipを書き出すときに Kernel.#puts module function Kernel.#puts (Ruby 2.4.0) とかを使うと起きそう。

直してみる

Vim 上で :%!xxdをして、hexdumpする

:%!xxd -r でバイナリで書き戻して完了

$ od -xc data_zcat_json.gz | tail
        213 322 264   @ 370   D 024 365 221 243 003   )   u   3   >   j
2572300    8e34    ead3    b81a    2fbc    697f    f85a    65d8    8408
          4 216 323 352 032 270 274   / 177   i   Z 370 330   e  \b 204
2572320    a90f    00f0    049d    aff4    e103    5133    12f4    9be2
        017 251 360  \0 235 004 364 257 003 341   3   Q 364 022 342 233
2572340    8ae7    f881    28b9    a062    d936    db91    3c7e    d017
        347 212 201 370 271   (   b 240   6 331 221 333   ~   < 027 320
2572360    9c20    0ff2    2dac    7b73    b7b5    014c
            234 362 017 254   -   s   { 265 267   L 001
2572374

参照

技術/歴史/zip,gzip,zlib,bzip2 - Glamenv-Septzen.net

http://www.gzip.org/algorithm.txt

Man page of GZIP

qiita.com

CloudWatch Alarms の Treats Missing Dataの意味

よくわからかなかったので整理

docs.aws.amazon.com

configure(コンソール) configure(api) 意味 英文
Missing missing 過去に遡ってデータを見に行く the alarm looks back farther in time to find additional data points
Good notBreaching 欠落データポイントで、しきい値内であることを示す(アラート収まる) treated as a data point that is within the threshold
Bad breaching 欠落データポイントで、しきい値を超えていることを示す(アラート飛ぶ) treated as a data point that is breaching the threshold
Ignored ignore 欠落データポイントでアラーム状態の変更がトリガーされない the current alarm state is maintained