Diary of a Perpetual Student

Perpetual Student: A person who remains at university far beyond the normal period

OpenTelemetry MetricsのUnitに何を記述すべきか

OpenTelemetryにおいて、OTel CollectorのReceiverを開発するときやアプリケーションに計装をする際、MetricsのUnit(単位)にどんな文字列を指定すべきかよく分からなかったので調べてみました。

semconv

OpenTelemetryにはSemantic Conventions(semconv)と呼ばれる規約があります。あるデータに対して異なる実装間で共通の名前がついていると便利なので標準を取り決めましょうといった趣旨のものです。Metricsにおいてはメトリック名や属性、単位といったデータについて規約が定められています。

opentelemetry.io

semconvには一般的な規約と具体的な規約があります。具体というと、例えばHTTPに関する規約、AWSなどのクラウドサービスに関する規約、といった形です。「HTTPサーバのリクエストの継続時間を計装する際にはhttp.server.request.durationという名前にして、属性としてHTTPメソッドを必ず付与し、その属性名はhttp.request.methodにしましょう。またそのメトリックの単位はsとしましょう」といった内容がずらずらと並んでいる形です。

opentelemetry.io

一方で、一般的な規約というのは、具体的な規約を策定する際に指針となる、具体ケースに依存しない規約になります。この規約の単位に関する記述を読めば、今回の謎は解決しそうです。ということで該当箇所を読んでみると以下のような記述があります。

https://opentelemetry.io/docs/specs/semconv/general/metrics/#instrument-units

Units should follow the Unified Code for Units of Measure.

単位はUCUMと呼ばれる別のルールに従うべきだと書かれています。さて、新しい概念が出てきたので今度はそちらを追っていきましょう。

UCUM

UCUMは、数量とその単位を電子的な手段で正確に伝達するために生まれた符号システムです。先ほどの引用上のリンクにアクセスするとその仕様を閲覧することができますが、長大で理解するのが大変なので、簡単にここで紹介します。

電子的な伝達を目的としているため、UCUMにおいて単位はUS-ASCII文字だけで記述されます。残念ながら日本語文字や絵文字は使えません。

SI単位系のように原子単位とその組み合わせ(次元)で表現します。アノテーションといって、個数などの無次元の単位に意味を付与できる仕組みがあるのも特徴です。参考までに、Mackerelで現在使える単位をUCUMで表現してみました。

Mackerelの単位 OpenTelemetry(UCUM)の単位表記例
float 1
integer {request}
percentage %
seconds s
milliseconds ms
bytes By
bytes/sec By/s
bits/sec bit/s
iops {operation}/s

semconvで/sを見かけない件

ところで、/sとつくような単位はsemconvで見かけることがありません。従来IOPSとして監視していたようなメトリックも、OpenTelemetryのレイヤーではあくまでI/O Operationの数として扱う(単位は{operation})雰囲気になっています。

ある時間についての割合というのは計測値そのものではなく計算した結果であり、OpenTelemetryとしては生の計測値をできる限りそのまま取り扱おうという指針なのでしょう。SDKやCollector上で行われる集計(aggregation)を除いて、投稿したメトリックをユーザが扱いやすいように加工するのはExportする先のサービスの責務ということになります。OpenTelemetryの責任範囲(Backendに投稿するまで)やObservability(集計によってデータの解像度は不可逆に低下し、結果として可観測性も失われる)を鑑みると自然な価値観であると言えます。

監視サービスの作り手として、投稿したメトリックを自在に引き便利に取り扱うための、Observability Backendの機能設計や実装の難しさを日々痛感しています。

カンファレンス登壇者・スタッフにこそ知ってほしいマイクの使い方

オフラインの技術カンファレンス・イベントも徐々に復活し賑わいを見せつつある今日この頃、いかがお過ごしでしょうか。

多くの聴衆に等しく声を届けるための道具「マイク」はカンファレンスに欠かせないものとなっています。

普段はアプリケーションエンジニアとして働きつつ、休日にPAエンジニアやステージマネージャーをやっている身から、来場者が発表やコンテンツに集中できるようなマイクの扱い方を簡単にご紹介します。

叩かない

マイクを叩くと低い「ブォンブォン」という不愉快な音が発生するのはもちろん、スピーカーなどの機材が壊れる原因につながります。もちろん意図的に叩いている人はいないと思うのですが、以下のようなケースでつい叩いてしまう人を見かけます:

  • 拍手をする、拍手を煽る際にマイクを持ちながら手を叩く
  • マイクがONになっているか確認するために叩く

マイクを手に持っている際に拍手をする際には、手と手の代わりに前腕部を叩いて、マイクへの衝撃を軽減し風音が入らないようにするといった工夫ができます。マイクのスイッチを一瞬切ったりミュートにしたりするのも良いのですが、音響業者が準備している際には、利用者が勝手にスイッチを切る行為が推奨されないことがあるので確認しましょう*1

マイクがONになっているか調べるには、大人しく発声するのが一番です。またカンファレンススタッフ目線では、登壇者ときちんとコミュニケーションを取り、「こちらで登壇者紹介をした後に勝手にマイクがONになります」などと伝えるのも効果的です。

声の出る方向とマイクの位置・向きを合わせる

カンファレンスでの発表時に使うマイクには、多くの場合単一指向性と呼ばれる特性のマイクが選ばれます。これは、特定の方向の音だけを捉えやすいという性質です。発表者が喋りその他の人が聞くというスタイルが多いカンファレンスで、発表者の声だけを拾い多くの聴衆に届けるのにピッタリのマイクです。

そのため、マイクの軸から口が左右にずれてしまうと、一気に音量が小さくなってしまいます。マイクのグリル(先端の丸いあみあみの部分)の方向の延長に自分の口があるかを確認しましょう。また、位置だけでなく向きも重要です。手でマイクを持つ場合には声の出る方向に合わせてマイクの向きを調整しましょう。

小さな卓上スタンドにマイクを立てている形式の場合、立ちながら話している登壇者の口の位置に対してマイクが極端に低く、結果として音を拾えていないケースがよくあります。

マイクスタンドには高さと向きが調整できる機能が備わっているので、登壇前の時間に以下の動画のように、自分の身長や姿勢に合わせて調整しましょう。

www.youtube.com

グリル部分を掴まない

マイクのグリル(先端の丸いあみあみの部分)の中には、マイクが実際に収音し電気信号に変えるための部品が入っています。

この部分を握ってしまうと、音を拾えなかったり、声がこもってしまう原因になります。グリルのすぐ下の部分を持つと良いでしょう。

ちょうどこの前の勉強会での自分のマイクの持ち方の例があったので載せておきます

ある程度の声量で喋る

マイクの向き・位置・持ち方が良くても、肝心の声が小さすぎると多くの聴衆に聞きやすく発表を届けることはできません。はっきりと大きな声で話せるようにしておきましょう。むしろ、元々声が小さい人ほど、先ほど紹介した方法でなんとかマイクに伝わる信号の大きさを稼ぐべきです。

さて、これまで紹介してきた内容のうち、

  • 声の出る方向とマイクの位置・向きを合わせる
  • グリル部分を掴まない
  • ある程度の声量で喋る

これら3つはどれも、マイクが音をちゃんと拾って大きくクリアな音で聴衆に声を届けるための方法となります。しかし、マイクを通した声のボリュームを上げる方法が違った軸でもう1つあります。それは、スタッフがミキサーやアンプでボリュームを上げるという方法です。なぜこれではいけないのでしょうか。

マイクがとらえた信号を増幅させる度合いをどんどん高めていくと、発表者の声だけでなく今まで拾えていなかったような環境音などもマイクが拾って、増幅されてスピーカーから流れます。ここで、スピーカーから流れた音が再度マイクに拾われるとどうなるでしょうか。音がどんどん増幅されて「キーン」という大きな不快な音が発生してしまいます。ハウリングというやつですね。

適切なマイクの使い方をすることで、過度に音の信号を増幅しなくて済むようになり、結果としてハウリングを防ぐことができるのです。


せっかくのいい発表が聴衆に届かない、あるいは雑音が聞こえて集中できないのはもったいないことだと思っています。よろしければ以上の内容を意識して、より伝わりやすい発表にしてみてはいかがでしょうか。

*1:業者の責任範囲外で音が出なくなるのを防ぐためです。こういった現場では、PAエンジニアがマイクを叩くのを察知してシュッとボリュームを下げてくれることも多いですが、人間の手作業によるものですのでそこに頼りまくるのも良くないです。

登壇ふりかえり:Hatena Engineer Seminar 個人開発回 & Scalaわいわい勉強会 #2

2024年に入って2件登壇しましたので、こちらのエントリで振り返っていきます。

Hatena Engineer Seminar #28 「技術習得を支え続けた私の個人開発ヒストリー」

2024年1月30日に、「個人開発」をテーマとしたHatena Engineer Seminar #28にて登壇しました。

developer.hatenastaff.com

学生の頃からずっと続けてきた個人開発を通して、モノづくりを続けながら扱える技術を次々に増やしていった話と、最近の個人開発で自分の責務を減らすことを目的に進めているモダナイズの話をしました。さらに、企画・開発の両面にフルコミットする個人開発の経験がプロダクトエンジニアと呼ばれつつあるエンジニアの一つのあり方でいることに深く貢献している、だとか、個人開発がエンジニアリングの美学を学ぶ庭になる、というメッセージも伝えました。

話の構成として、自分のこれまでの個人開発の歴史や当時の自分・社会の状況を振り返りながら進めていき、その上で一般化した結論を最後に提示していきました。これに関して社内でも「キーノートっぽい」などという感想をいただいたのですが、まさに狙った効果でした。YAPC::Kyoto 2023の id:onishi さんのキーノートにだいぶ影響されてますね。

あと、発表の最後にMackerel DrinkUpというオフラインイベントの宣伝をしたのですが、これを見て来ましたと言ってくれた方がいて嬉しかったです。

動画はこちら:

www.youtube.com

資料はこちら:

speakerdeck.com

Scalaわいわい勉強会 #2「見せ算をScalaで実装してみた」

Scalaわいわい勉強会 #2にてLTをしました。前回のScalaわいわい勉強会でもLTをさせてもらいました。

scala-tokyo.connpass.com

今回は、M-1 2023の決勝で物議を醸したさや香の見せ算をScalaで実装したという話をしました。

この漫才を見た瞬間から、これScalaで実装しようと思っていました。同じようにプログラミングで実装したいと思った人はいたらしく、Pythonなどで実装した記事がいくつか上がっていますね。でもなぜ僕はScalaを選んだのかというと、以下の画像をご覧ください。

あたかも四則演算に加えてmiseという演算子が本当にあるかのように見えるではありませんか。これは5則目が追加されたという事実の表現として結構大事なことだと思うんですよね。Scalaでは演算子を独自に作成できるので、それを実現する2つの言語仕様を紹介しました。

これだけですとかなり初心者向けの内容になるので、来場者に楽しんで聞いてもらえるかというのがネックではありました。しかし、Scalaわいわい勉強会は初級者から上級者まで楽しめるようにと企画されているものですので、そこは割り切ることにしました。では実際どうだったかというと、以下の記事のように初心者の方もいらしていたようで、反応を見るにビギナー向けのトークができて良かったと強く思いました。

a5size.hatenablog.com

中級者向けにはScala 3でのアップデートに触れる・型レベルプログラミングでの実装を紹介するなどして、うまくバランスが取れたかなあと思っています。ただしScalaの人はM-1を見ないらしい。

資料はこちら:

speakerdeck.com


次の予定は特に決まっていないのですが、Go Conference 2024にはProposalを出す予定です。よろしくお願いします。

あんまり元気じゃないです

今更ですが2月中旬に新型コロナに罹って丸々1週間ほど寝込んでいました。高熱(39度以上が3日間)、咽頭痛、味覚・嗅覚障害あたりが主な症状でした。高熱で何も頭が働かないのが一番しんどかったです。テイクアウトで近所の美味しいラーメンを頼んだら、スープが塩水としか感じられなかったのはおもしろ体験でした。

実はこの期間にいろんな予定を入れてしまっていて、仕事のお休み以外にもいろいろご迷惑をおかけしました。そのうちの1つが従姉妹の結婚式でした。その人にとって人生で1回しかないであろうイベントに参加できなかったのはかなりショックだったのと、いろいろな予定の再調整がしんどくて、身体より精神的苦痛の方が大きかったです。SNSもしばらく見たり書いたりしないようにしていました。

そんな状況かつ、しばらく家に引きこもって誰とも交流していなかったこともあってか、家の外にいるだけで動悸や冷や汗がひどく、あんまりまともに行動できてないです。特に電車ではノイズキャンセリングイヤホンがあっても厳しくて、とにかく端っこにいないと落ち着かないです。見た目は若者なのでシートに座るのも躊躇われる故、居場所がないです。

最近は多少症状がマシになってきたかと思ってポケモンカードの店舗大会に出てみたんですが、手が震えてシャッフルに困りました。よそから見たらめちゃくちゃ挙動不審な感じだったと思います。ただ戦績はびっくりするほど良かったので不思議なものですね。

ということでフットワークの軽さだけが取り柄でしたが、しばらく外出の予定は控えめにさせてもらいます。少しずつリハビリしていければと思うのでよろしくお願いします。

と言ったところでこんな話をするのもアレですが、明日はScalaわいわい勉強会 #2(すでに満席です)のLTで物理登壇予定です。面白い話になるように絶賛仕込み中です。

scala-tokyo.connpass.com

arm64の場合/proc/cpuinfoでCPUのモデル名が取得できない件とその対策

arm64では/proc/cpuinfoからCPUのモデル名を取得できない

Linuxにはシステムの様子を知ることができる仮想ファイルがいくつかあります。そのうちの一つが/proc/cpuinfoで、その名の通りCPUの情報が以下のように閲覧できます。

$ cat /proc/cpuinfo
processor   : 0
vendor_id   : GenuineIntel
cpu family  : 6
model       : 63
model name  : Intel(R) Xeon(R) CPU E5-2650 v3 @ 2.30GHz
stepping    : 2
microcode   : 0x1
cpu MHz     : 2294.686
cache size  : 4096 KB
physical id : 0
siblings    : 1
core id     : 0
cpu cores   : 1
apicid      : 0
initial apicid  : 0
fpu     : yes
fpu_exception   : yes
cpuid level : 13
wp      : yes
flags       : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon rep_good nopl cpuid tsc_known_freq pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm invpcid_single pti fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid xsaveopt
bugs        : cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs itlb_multihit mmio_stale_data
bogomips    : 4589.37
clflush size    : 64
cache_alignment : 64
address sizes   : 46 bits physical, 48 bits virtual
power management:

(略、以下論理コアごと同じような出力が並ぶ)

「Intel(R) Xeon(R) CPU E5-2650 v3 @ 2.30GHz」のようなCPUの名前を見たい場合にはmodel nameの行を見れば良いわけです。しかし、このmodel nameは常にあるとは限りません。先ほどの実行例はx86_64のものでしたが、arm64での実行例を見ていきましょう。

$ cat /proc/cpuinfo
processor   : 0
BogoMIPS    : 108.00
Features    : fp asimd evtstrm crc32 cpuid
CPU implementer : 0x41
CPU architecture: 8
CPU variant : 0x0
CPU part    : 0xd08
CPU revision    : 3

(略、以下論理コアごと同じような出力が並ぶ)

先ほどと比べて情報量がかなり少なくなっており、model nameの行もないためCPUのモデル名が取得できなくなっています。

なぜこういった仕様になっているかは、Linuxカーネルのコード(下記参照)やパッチのディスカッションを見ることでわかります。

if (compat)
    seq_printf(m, "model name\t: ARMv8 Processor rev %d (%s)\n", MIDR_REVISION(midr), COMPAT_ELF_PLATFORM);

https://github.com/torvalds/linux/arch/arm64/kernel/cpuinfo.c#L193-L195

まとめると概ね以下の通りです:

  • 32bitではarmでもmodel nameが取得できたが64bitではできなくなっている(そういうカーネルのコードになっている)
  • arm64ではハードウェアのAPIからmodel nameを取得する術がないため上記実装になっている

lscpuではなぜarm64でもmodel nameが取得できるのか

lscpuというコマンドがあります。Ubuntuにはデフォルトで入っていそうですが、入っていないディストリビューションの場合にはutil-linuxをaptやらyumやらpacmanやらでインストールすると使えるはずです。

こちらのコマンドでもcat /proc/cpuinfo同様にCPUの情報を閲覧することができます。ここでは本題のModel nameにgrepで絞った結果をお見せします。

$ lscpu | grep "Model name"
Model name:                      Intel(R) Xeon(R) CPU E5-2650 v3 @ 2.30GHz

上記はx86_64での実行結果ですが、なんとlscpuではarm64の場合でもモデル名を取得することができます。

$ lscpu | grep "Model name"
Model name:                         Cortex-A72

これもソースコードから紐解いていきましょう。

lscpu_context_init_paths(cxt);

lscpu_read_cpulists(cxt);
lscpu_read_cpuinfo(cxt);
cxt->arch = lscpu_read_architecture(cxt);

lscpu_read_archext(cxt);
lscpu_read_vulnerabilities(cxt);
lscpu_read_numas(cxt);
lscpu_read_topology(cxt);

lscpu_decode_arm(cxt);

https://github.com/util-linux/util-linux/sys-utils/lscpu.c#L1359-L1370

lscpu_read_cpuinfoという関数がある通り、lscpuでも基本は/proc/cpuinfoファイルの内容を参照しているようです。しかし、一番下のlscpu_decode_arm関数の中身が気になりますね。

part = parse_model_id(ct);
if (part <= 0)
    goto done;

for (j = 0; parts[j].id != -1; j++) {
    if (parts[j].id == part) {
        free(ct->modelname);
        ct->modelname = xstrdup(parts[j].name);
        break;
    }
}

https://github.com/util-linux/util-linux/sys-utils/lscpu-arm.c#L366-L376

armの場合はlscpu_decode_arm関数から呼び出される上記の処理で、あらかじめなんらかの手段で得たモデルIDをモデル名に変換しているようです。以下のように変換テーブルを自前で管理していて味わい深いですね。

static const struct id_part arm_part[] = {
    { 0x810, "ARM810" },
    { 0x920, "ARM920" },
    { 0x922, "ARM922" },
    { 0x926, "ARM926" },
    ...
    { 0xd08, "Cortex-A72" },
    ...

https://github.com/util-linux/util-linux/sys-utils/lscpu-arm.c#L25-L97

そして、変換元のModel IDはどこから取得しているかというと、これは/proc/cpuinfoにちゃんと含まれていました。「CPU part」というカラムが対応するそうです。冒頭に挙げたarm64の出力例では「0xd08」という値になっているので、変換テーブルを引いた結果、モデル名として「Cortex-A72」が得られるという仕組みです。

Goのプログラムで/proc/cpuinfoを解析してモデル名を取得している際にどうするか

さて、お手持ちのGo製のアプリケーションで、/proc/cpuinfoを解析してCPUのモデル名を取得しているプログラムはありませんか?私はあります。

先ほどご覧いただいたlscpuの実装をgoでも真似すればarm64の対応ができるのですが、あの大量のidとmodel nameの対応マップを自前で用意したり場合分けしたりするのは大変ですよね。ここは巨人の肩に乗っかっていきたいところです。

shirou/gopsutilという、システムの情報を取得する際に便利なgoのライブラリがあり、この中にCPU情報を取得できる関数cpu.Info()があります。

pkg.go.dev

この実装を読むと、lscpu同様にテーブルを用意し、armの場合にはモデルIDをモデル名に変換しているようです。

case "model", "CPU part":
    c.Model = value
    // if CPU is arm based, model name is found via model number. refer to: arch/arm64/kernel/cpuinfo.c
    if c.VendorID == "ARM" {
        if v, err := strconv.ParseUint(c.Model, 0, 16); err == nil {
            modelName, exist := armModelToModelName[v]
            if exist {
                c.ModelName = modelName
            } else {
                c.ModelName = "Undefined"
            }
        }
    }

https://github.com/shirou/gopsutil/cpu/cpu_linux.go#L247-L259

var armModelToModelName = map[uint64]string{
    0x810: "ARM810",
    0x920: "ARM920",
    0x922: "ARM922",
    0x926: "ARM926",
    ...

https://github.com/shirou/gopsutil/cpu/cpu_linux.go#L21-L84

ついでにWindowsやFreeBSDなどの様々なOSの対応もできて嬉しいので、自前の実装をこういったメンテされているライブラリに移し替えるのを検討すると良いと思います。