Nginx OpenTelemetry モジュールにスパン属性を追加する

ngx_otel_module モジュール を使うと Nginx のテレメトリーデータを取得できます。

otel_span_attr ディレクディブを使って Nginx の変数をスパン属性に追加し、取得する方法を紹介します。

デモ

以下に紹介する内容を使ったデモを作りました。Docker Compose で簡単に起動できるので、ぜひ試してみてください。

github.com

取得するスパン属性

ngx_otel_module モジュールはデフォルトで次のスパン属性を取得します。

  • http.method
  • http.target
  • http.route
  • http.scheme
  • http.flavor
  • http.user_agent
  • http.request_content_length
  • http.response_content_length
  • http.status_code
  • net.host.name
  • net.host.port
  • net.sock.peer.addr
  • net.sock.peer.port

上記に加えて、この記事では次のスパン属性を取得してみようと思います。

  • http.request
  • http.request_time

otel_span_attr ディレクディブ

ngx_otel_module モジュールでは、 otel_span_attr ディレクディブでスパン属性を追加できます。

otel_span_attr name value;

参考: https://nginx.org/en/docs/ngx_otel_module.html#otel_span_attr

変数 $request から http.request 属性を作成

$request はリクエスト全体 (メソッド・パス・クエリ・フラグメント・プロトコル) を格納する ngx_http_core_module の埋め込み変数です。例えば、GET /favicon.ico HTTP/1.1 のような値を取得できます。

デフォルトの属性を参考にして http という名前空間を付けました。

otel_span_attr http.request  $request;

変数 $request_time から http.request_time 属性を作成

$request_time はリクエストの処理時間 (ミリ秒) を格納する ngx_http_core_module の埋め込み変数です。

http.request と同じように http を名前空間とします。

otel_span_attr http.request_time  $request_time;

しかし、 otel_span_attr ディレクディブを使って取得した値は文字列 (string) 型になります。変数 $request_time は小数であり、OpenTelemetry でも小数として扱いたいです。

http.request_time 属性を文字列型から倍精度浮動小数点数型に変換する

OpenTelemetry では小数を表す型として倍精度浮動小数点数 (double-precision floating-point) 型があります。そこで、http.request_time 属性の値を倍精度浮動小数点数型に変換します。

Attributes Processor で型変換

指定した属性の型を変換できる Attriutes Processor という Processor があります。

この Processor を使うと OpenTelemetry の config は次のようになります。

...
processors:
  batch:
  attributes:
    actions:
      - key: http.request_time
        action: convert
        converted_type: double
...

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch, attributes]
      exporters: [otlp]

参考: https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/processor/attributesprocessor/README.md

結果

http.request と http.request_time が送れました。

Jaeger UI からスパン属性を見る

まとめ

Nginx のテレメトリーデータに任意のスパン属性を追加して取得する方法を紹介しました。また、そのときに課題となる型変換を Attributes Processor で行う方法を紹介しました。

参考

nginx.org

github.com

opentelemetry.io

opentelemetry.io

www.cncf.io

Intellij IDEA で効率的にコーディングするためにやっていること

まとめ

Shift を2回押してどこでも検索を使おう。

まえがき

一般的に効率的にコーディングするためにはキーボードショートカット (以下、ショートカット) を使い、キーボードから極力手を離さないという方法があります。

しかし Intellij の場合、たくさんあるショートカットを覚えるのは大変ですし、操作によってはショートカットの割当設定が必要になるかもしれません。

また、Key Promoter X などのショートカットを教えてくれるプラグインがありますが、通知がたくさん出るのが私には合いませんでした。

どこでも検索

そこで私が使っているのがどこでも検索という機能です。

pleiades.io

機能の詳細は Intellij のドキュメントに任せますが、これによりショートカットを1つ1つ覚えたり設定する必要はなくなります。加えてショートカットを教えてくれるので、私はよく使う操作のショートカットを覚えることができました。

使い方

Shift + Shift (Shift キーを2回) で呼び出すことができます。

以下に2つ使用例を載せました。

例1: Git のブランチ切り替え

よく行う操作に Git のブランチ切り替えがあります。これをどこでも検索でやります。

まず、どこでも検索の検索ウィンドウを開き、「git branch」で検索したのが次の画像です。

Branches... の横に書かれている Ctrl+Shift+` は割り当てられているショートカットです。何度も実行しているうちに目に入って覚えられると思います。

「git branch」で検索

実行するとブランチを管理するウィンドウが開きます。

ブランチ操作のウィンドウ

例2: タブを全て閉じる

次は現在開いているタブをすべて閉じます。

どこでも検索で「close」と入力すると候補に Close All Tabs が出てきました。これを実行するとタブを全て閉じることができます。

「close」で検索

この操作にはショートカットがデフォルトで割り当てられていません。もちろん自分で割当設定をしてもいいのですが、設定が面倒であればこのように実行できます。

おわり

いちいち検索するのが面倒という意見がありそうですが、個人的には Emacs で関数を実行する感覚で使っていて気になりません。

GitHubのREST APIを使って名前に '/' の入ったEnvironmentを作る

要点: %2F で / をエスケープしてあげればいい

Create or update an environment

GitHub は Environment を作成する REST API を提供しています。

docs.github.com

使い方はリファレンスに詳しく書いてありますが、curl なら次のような感じ。適切な権限を付与したアクセストークン (GITHUB_PAT) を用いて、リポジトリ (OWNER/REPO) に ENVIRONMENT_NAME という名前で Environment を作るコマンドです。

export GITHUB_PAT=<YOUR_TOKEN>
export OWNER=<YOUR_OWNER>
export REPO=<YOUR_REPO>
export ENVIRONMENT_NAME=<YOUR_ENVIRONMENT_NAME>

curl -L \
  -X PUT \
  -H "Accept: application/vnd.github+json" \
  -H "Authorization: Bearer "${GITHUB_PAT}"" \
  -H "X-GitHub-Api-Version: 2022-11-28" \
  https://api.github.com/repos/"${OWNER}"/"${REPO}"/environments/"${ENVIRONMENT_NAME}"

トークンの作り方:

docs.github.com

Environment 名に '/' を使いたいとき

Environment 名に / を使って、例えば、production/backend というように構造化したいかもしれません。

その場合は、/ を %2F にパーセントエンコーディングする必要があります。

コマンド例 (ENVIRONMENT_NAME に注目):

export GITHUB_PAT=<YOUR_TOKEN>
export OWNER=<YOUR_OWNER>
export REPO=<YOUR_REPO>
# / を %2F に変換
export ENVIRONMENT_NAME=production%2Fbackend

curl -L \
  -X PUT \
  -H "Accept: application/vnd.github+json" \
  -H "Authorization: Bearer "${GITHUB_PAT}"" \
  -H "X-GitHub-Api-Version: 2022-11-28" \
  https://api.github.com/repos/"${OWNER}"/"${REPO}"/environments/"${ENVIRONMENT_NAME}"

エンコードしないとエラーレスポンスが返ってきます。

プライベートリポジトリだと具体的なエラーメッセージは無く、Not Found とだけ返ってくるので注意です。

{
  "message": "Not Found",
  "documentation_url": "https://docs.github.com/rest"
}

docs.github.com

補足1. ラベルの場合

Environment と同じように、Issue や Pull Request に付けるラベルも REST API を通じて作れます。

しかし、ラベルの場合は / が名前に入っていてもエスケープ処理が不要でした。

docs.github.com

補足2. プラン

GitHub Free プランでは、プライベートリポジトリで Environment を使えません。実際に試すときはリポジトリの可視性やプランを確認してください。

docs.github.com

2023年の振り返り

とりあえず書いた。

仕事

学生生活が終わり社会人になった。

chaya2z.hatenablog.jp

AWS でインフラ構築するのが主な仕事で、Terraform で IaC したり、TypeScript でツール書いたりしている。

よかった技術書

システム設計の面接試験

本のタイトルには「面接試験」とあるけれど普段の業務でも役立つ。内容がわかりやすいのはもちろん、参考文献がたくさん載っているのがよかった。

www.socym.co.jp

入門モダン Linux

短くまとまっているのと、図がわかりやすいのがよかった。

www.oreilly.co.jp

ロードバイクを買った

リモートワークで家に籠りがちなので運動のためロードバイクを買った。週末に80kmくらい乗ってる。

運動ならロードバイクである必要はないけれど、ずっとやりたいと思っていたので買った。やりたいと思っていたことが叶ったのは喜ばしいことなのだけど、このままやらなかったら一生やらずに死ぬんだろうと後ろ向きな感覚が芽生えてしまった。悲しい。

今は25km/hで数kmのペースなので、来年は30km/hで100km走れるようになりたい。.

来年

来年はもっとブログ書いたりコード書いたりアウトプットしたい。

来年もよろしくおねがいします。

Linuxカーネルの赤黒木のおもしろい最適化

この記事ははてなエンジニア Advent Calendar 2023 の27日目の記事です。はてなのアドベントカレンダーはまだまだ続きます!

26日目の記事は id:masayosu さんの EKS Pod Identity を活用するメリットでした。

赤黒木とは

赤黒木は平衡二分探索木の1種で、各ノードが赤か黒の色に塗り分けられていることが特徴の1つです。

(Wikipedia の赤黒木のページの画像)

この記事では赤黒木に詳しい必要はないので、説明は wikipedia の赤黒木のページに任せます。

Linux カーネルでは、CFS スケジューラで用いられたりしています。

Linux の赤黒木のノード

Linux カーネルの赤黒木の実装を見ていきます。この記事では特に赤黒木のノードに注目します。

include/linux/rbtree_types.h (v6.6) にある rb_node が赤黒木のノードを表す構造体です。

struct rb_node {
    unsigned long  __rb_parent_color;
    struct rb_node *rb_right;
    struct rb_node *rb_left;
} __attribute__((aligned(sizeof(long))));
/* The alignment might seem pointless, but allegedly CRIS needs it */

まず、rb_node 構造体は3つのフィールドを持っています。

  1. __rb_parent_color
    • 木の親と自身の色 (赤か黒) の情報
    • 下記で詳しく見ていきます。
  2. *rb_right
    • 右側の子ノードへのポインタ
  3. *rb_left
    • 右側の子ノードへのポインタ

次に、 __attribute__((aligned(sizeof(long)))) という部分はコンパイラにアライメント境界を伝えるキーワードです。

下記で詳しく見ていきます。

コミットログを追う

コミットログを読んで rb_node 構造体の理解を深めます。

v6.6 では rb_node 構造体は include/linux/rbtree_types.h に定義されていますが、v5.15 まで include/linux/rbtree.h ファイルに定義されていました1。

なので、include/linux/rbtree.h の Git のコミットログを追います。

v2.6.12-rc2

Linux カーネルのソースコードが Git で管理されるのは v2.6.12-rc2 からです。

そこで v2.6.12-rc2 から見ていきます。

include/linux/rbtree.h (v2.6.12-rc2)

struct rb_node
{
    struct rb_node *rb_parent;
    int rb_color;
#define    RB_RED      0
#define    RB_BLACK    1
    struct rb_node *rb_right;
    struct rb_node *rb_left;
};

v6.6 と大きく異なるのは、v2.6.12-rc2 の時点では __rb_parent_color フィールドは存在せず、 *rb_parent と rb_color にフィールドが別れている点です。

v2.6.18

v2.6.18 で v6.6 とほとんど同じコードになります。

include/linux/rbtree.h (v2.6.18)

struct rb_node
{
    unsigned long  rb_parent_color;
#define    RB_RED      0
#define    RB_BLACK    1
    struct rb_node *rb_right;
    struct rb_node *rb_left;
} __attribute__((aligned(sizeof(long))));
    /* The alignment might seem pointless, but allegedly CRIS needs it */

赤黒木に関するコミットはいくつかありますが、その中でも2つのコミットを見てみます。

[RBTREE] Merge colour and parent fields of struct rb_node.

We only used a single bit for colour information, so having a whole machine word of space allocated for it was a bit wasteful. Instead, store it in the lowest bit of the 'parent' pointer, since that was always going to be aligned anyway.

https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?h=v2.6.18&id=55a981027fc393c86de2c4e7836c9515088a9a58

このコミットで *rb_parent と rb_color が rb_parent_colour というフィールドにまとまります。

struct rb_node
 {
-   struct rb_node *rb_parent;
-   int rb_color;
+   unsigned long  rb_parent_colour;
 #define   RB_RED      0
 #define   RB_BLACK    1
    struct rb_node *rb_right;
    struct rb_node *rb_left;
 };

親へのポインタ ( *rb_parent ) の最下位1ビット (LSB) に色情報 ( rb_color ) を格納することでメモリの無駄遣いを防ぐことができるということがコミットメッセージに書いてあります。

なぜ、ポインタの LSB に色情報を格納できるのでしょうか。

それは、親へのポインタは偶数アドレスに配置されるので、LSB は必ず0になるからです。ポインタが偶数アドレスになる理由は、rb_node 構造体がアライメントされてメモリに配置されるからです。

このようなポインタの使い方を Tagged pointer と呼ぶらしいです。

ちなみに、colour というのは typo ではなくイギリス英語のスペルです。v2.6.18 のリリースまでにアメリカ英語の color に変更されました2。

[RBTREE] Add explicit alignment to sizeof(long) for struct rb_node.

Seems like a strange requirement, but allegedly it was necessary for struct address_space on CRIS, because it otherwise ended up being only byte-aligned. It's harmless enough, and easier to just do it than to prove it isn't necessary... although I really ought to dig out my etrax board and test it some time.

https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?h=v2.6.18&id=e977145aeaad23d443686f2a2d5b32800d1607c5

__attribute__ というコンパイラにアライメント境界を伝えるキーワードが追加されます。

+} __attribute__((aligned(sizeof(long))));
+    /* The alignment might seem pointless, but allegedly CRIS needs it */

通常、コンパイラはこのコードが無くてもアライメントするはずですが、 CRIS ではそれがうまくいかないらしいということがコミットメッセージでわかります。

__attribute__ は GCC の構文3で、構造体と共用体に属性を付与できます。aligned は属性の1つで、アラインメントの境界を指定できます。64ビットアーキテクチャでは long 型は8バイトなので、8バイトアライメントされるはずです。

CRIS というのは Axis Communications 社の開発しているアーキテクチャのことだと思います4。

まとめ

Linux カーネルの赤黒木の実装に見る Tagged Pointer を用いた最適化の話でした。


この記事ははてなエンジニア Advent Calendar 2023 の27日目の記事でした。

明日の記事は id:SlashNephy さんです。

参考

Jetbrains AI Assistant を使ってみた

2023年12月6日、ついに Jetbrains AI の提供が始まりました。

blog.jetbrains.com

この AI 機能は、2023年7月ごろにテクニカルプレビューとして登場しました。私もそれを試用して、その機能に魅力を感じていました。そのため今回リリースされてすぐにライセンスを購入し、さっそく利用してみました。

この記事では、個人的に特に魅力を感じた Explain Code 機能を紹介します。

Explain Code 機能がすごい

AI チャットの機能の1つに Explain Code という機能があります。これはその名の通り、コードの意味を説明してくれるものです。

例えば、 Linux カーネルの CPU スケジューラの初期化関数を解説してもらうとこんな感じです。

コード: https://elixir.bootlin.com/linux/latest/source/kernel/sched/core.c#L9912

AIにコードを解説してもらう

まず、1行目で、「これは Linux カーネルのコードでタスクスケジューラの初期化部分です」と説明されます。

This is a part of the Linux kernel code which is responsible for initializing the task scheduler.

こちらからこの関数が Linux カーネルのコードの一部であることは伝えていないので、AI がコードを見て推測しているということでしょう。

次に、 __init が Linux カーネル独自のものであることを教えてくれます。

This function is named sched_init and is tagged with __init which in Linux kernel programming means that this function is executed only once at startup and its memory can be freed up after this.

もう少し説明がほしいと思ったので、AI Assistant に聞いてみました。

AI Assitant に質問する

先程より詳しい解説が返ってきました。

要約すると、「これは Linux カーネルのマクロで、カーネルがメモリにロードされるときに1度呼び出され、メモリを効率的に扱えるようにするものです」と答えてくれています。

感想

まだすべての機能を試していませんが、AI チャットだけを見ても Jetbrains AI に期待できるのではないでしょうか。

関連

www.jetbrains.com

www.youtube.com