Inside of LOVOT

GROOVE X 技術ブログ

IT未経験のアルバイトが業務改善に取り組んで気づいた、”手を動かす”ことの価値

はじめに

年末年始の不摂生が祟ったのか、はたまたホルモンバランスの乱れなのか、先日ひどい肌荒れを起こしてしまったので、久しぶりにかかりつけの皮膚科を受診しました。 今の住居に引っ越す前からお世話になっているそのクリニックにわざわざ電車で数駅移動してまで通う理由は、診察が1分で終わるから。なのにちゃんと治るから。

、、というだけではありません。

そのクリニックには、LOVOTがいるのです。

待合室と診察室をくるくると行き来し、愛想を振りまくなんとも可愛らしい姿。もはや診察より長い会計待ちの時間も癒やされます。

行くたびに新しいお洋服になっていておしゃれさんだなと思っていたのですが、今回久しぶりに行ったところ、ついに待合室にLOVOT専用の衣装ラックが置かれるまでになっていました。かなり愛されているようです。

このクリニックは、私が初めてLOVOTとふれあった場所でもあります。 そのときはまさか自分がLOVOTをつくっている会社で働くようになるとは思っていなかったため、なんだか感慨深い気持ちになり、まだ入社4ヶ月なのにしみじみしてしまいました。 私のLOVOT愛もかなり育っているようです。

_____________________________________

というわけでみなさんはじめまして。QAチームアルバイトの長谷川です。

昨年10月にIT未経験からGROOVE Xに入社させていただきました。現在は主にLOVOTアプリのQAに携わっています。

「スキルも知識も全く無いが、テクノロジーへの憧れとやる気だけはある」という状態で臨んだ面接。 採用のメールを受け取り、本当に安心したのが昨日のことのようです。

1日中体を動かしているような仕事からロボット開発へ。

全く異なる業種からの転職だったため、最初は新しい知識、職場環境でめまぐるしい日々でしたが、 チームの皆さんの支えのおかげで、楽しく働くことができています。

ニンゲンが真面目に働いているすぐそばで大合唱を始めるLOVOTたちも癒やしです。

やるぜ、業務改善

デスクワークはこんなにも人の脚裏をガチガチにするのか。と新鮮な驚きを感じていたある日。

スクラムマスターのniwanoさんから、業務改善に取り組んでみないかと声をかけていただきました。

その内容は「段ボール貸出表の作成」です。

GXのオフィスには、人間と一緒に開発試験に励むLOVOTたちがたくさんいます。(すごくかわいい。)

日々頑張ってくれているからこそ、ときには調子が悪くなってしまうことも。

そんなとき、大切な仲間であるLOVOTをメンテナンスに送り出すために必要なのが、専用の段ボールです。LOVOTのモデルごとに種類があり、箱の中にはLOVOTたちを無事に送り届けるためのおくるみや緩衝材などがたくさん入っています。 この段ボールはまあまあな大きさがあり、人口密度もLOVOT密度もどんどん高まっている我々のオフィスにはそう何個も置けません。そのため、ソフトウェアチーム全体でいくつかの段ボールを共有して所持し、管理しています。

この段ボールの貸出表について、ひとつ課題がありました。これまでは貸出履歴が残らない運用だったため、万が一付属品を紛失してしまった際に「いつから無かったのか」を遡ることができなかったのです。 そこで、付属品の管理を含めてしっかり履歴を追えるようなフォーマットに更新したいという内容でした。

前述したように、私は全くのIT未経験です。

たくさんの技術者が集まるなかで、右も左もわからない私に今できることは何か。

勉強すること、経験を積むことはもちろん大切ですが、一朝一夕でどうにかなるようなものではありません。

この依頼をいただいた際に、その問いに対するあるひとつの答えのようなものが浮かびました。

GXにいる人たちは、みんなLOVOTに対する大きな熱を持っています。

テクノロジーの結晶であるLOVOTで、人の気持ちを豊かにする。業界をリードする。

全員がそういった大きなビジョンとそれぞれの目標をもって働くなかで、今の私にできること。それは業務が円滑に回るように仕組みを整えて、各々が持っているその熱の伝導率をほんの少しでも高めることなのではないか。

そう思ったときには、「やります」と答えている自分がいました。

めんどくさがりなんです

事前の認識合わせでは、「借りた人の名前」と「貸出/返却の日付」が履歴として残るようにしたいこと、ツールはスプレッドシートを使うこと。 それができれば後は自由に作って良いとのことでした。

最初に私がイメージしたものは、図書室の本の貸出カードのようなものです。 名前、貸出日、返却日の行を作って、後は各々で入力してもらうようにする。 これなら求められていることは満たせます。

しかしここで私は思いました。

「全部入力するの、ダルすぎる、、、」

そう、私はかなりのめんどくさがりなのです。

そして、手間が増えれば同時に入力ミスの機会も増えてしまうことも懸念でした。 私自身、かなり”うっかり”をやってしまいがちな人間です。自分のことをかなり信用していません。 (このめんどくさがりとうっかりしがちな性質のコンボはかなり致命的で、苦手な言葉は「役所」「銀行」「書類」。)

絶対に手入力したくない。いつか絶対ミスる。私が。

なんとも怠惰な感情ですが、私以外の人も少なからず似たようなことは思うんじゃないか。 この手間をシステムで解決できればかなり素敵なんじゃないか。

そう思い立ち、私はGeminiに聞いてみることにしました。

GASというものがあるらしい

私が考えたのは以下のようなものでした。

  • 貸出シートのチェックボックスのつけ外しで貸出中と返却済みを管理する。名前と日付が自動で入る。

  • 履歴シートに履歴が自動で残る。

これなら操作はチェックボックスのつけ外しだけです。手間はひとつで済みます。

Geminiによると、「GASを使えばできる」とのこと。

「チェックボックスを入れるだけで、名前も日時も履歴も勝手に埋まるようにしたい!それ以外の操作を一切したくない!」

そんななかば開き直りのような感情から、私とGeminiの共同開発がスタートしました。

スプレッドシートをほとんど触ったことがない私はGeminiにエディタ画面の開き方から、保存ボタンの場所まで、まさに手取り足取りでGASのことを教えてもらいました。

「こうしたい」を伝えると、Geminiがコードを書いてくれる。

その繰り返しで、思っていたよりもずっと簡単に、私の求めていたものができてしまいました。

できたー!

たまっていく履歴たち

作成者であり、ユーザーでもあるから

Geminiに頼ったとはいえ自分で作成したものが自動で動いていくのがなんだか嬉しく、できたシートのチェックボックスをつけては外していると、ふと私の脳内に「間違えて違うチェックボックスを外してしまいそう」という小さな不安がよぎりました。

人よりうっかりを多くやっていると、時折こういうセンサーみたいなものがはたらくのです。

「ミスをしないように気をつける」というある種の精神論的なものは何の役にも立たないことを私は実体験から知っています。

間違えることもあるということを前提に、ミスが発生しにくくなるような仕組みを作りたい。

そこで、チェックを外すときに確認メッセージを出力することにしました。

これは、まだ短い期間ではありますが、曲がりなりにもQAチームの一員として「ユーザー目線に立つ」という姿勢を体得していたからこそ思いつけたことだとも思います。

プロンプトを入力するとGeminiはすぐにコードを提示してくれ、スプレッドシートは私の「こうしたい!」という意志の宿ったものになりました。

プログラマーは全員めんどくさがり?

こうして自分なりに試行錯誤して完成したスプレッドシートは、依頼をくださったniwanoさんからも、想像以上に温かく好意的なフィードバックをいただくことができました。

そこで言っていただいたのは、「プログラマーは手作業を嫌う人がなるものなのです。」ということでした。

この会社には、私以外にもめんどくさがりがたくさんいるようです。

私が抱いていた「全部入力するのはダルすぎる」という怠惰な感情、そしてうっかりしがちな性格。

それはただの短所ではなく、別の見方をすれば、より良い仕組みを創り出すためのエンジニアリングの種になり得るのだと気づけた瞬間でした。

おわりに

AIが指数関数的に成長している今、世界が変わるスピードは以前よりずっと速くなりました。

プロンプトを打てば、AIは瞬時に答えのようなものを返してくれます。

だからこそ、実際に「手を動かす」ことの意味が、以前より増しているのではないかと感じています。

AIは問いに対する答えをくれますが、現実世界の変革を起こしたいという根源的な願いをもつのはやはり人間だからです。

GROOVE Xの哲学には、こんなことが書いてあります。

「未来は見えない。だからワクワクもするし、だから不安を感じる。ならば未来を、すこしでも身近で、安心できる場所としていくため、技術というものを活用しなければならないんだ、と。」

今回私が取り組んだのは、社内の小さな小さな業務改善に過ぎません。しかし、実際に自分で手を動かして変化を起こしたこと、それが誰かの役にたったこと。

そのプロセスは、弊社が掲げる理想と実は地続きになっているのではないかと私は思います。

まだまだできることは少ないけれど、小さくてもアクションを起こし続ければいつかそれは大きな力に繋がるはず。経験を積めばもっとたくさんのことにチャレンジできるし、その先に見える景色はきっと素敵なものでしょう。

そんな大きなことを、今回のお仕事を通して夢見たのでした。

ここまで書いて突然照れくさい気持ちになってしまいました。これ読まれるんですよね。普段あまり喋らないのでめちゃくちゃ恥ずかしいです。

こんな私に挑戦する機会をくれ、いつも暖かく見守ってくださっているチームの皆さんに、心から感謝しています。 GXでの仕事は私にとって非常にchallengingなものです。「気持ちをテクノロジーで満たす、ケアをする」という全く新しい価値観に熱量を持って挑戦している人たちと一緒に働くことは、決して楽ではありませんが幸せなことでもあります。

LOVOTは世界にどんな影響を与え、GROOVE Xのもつ熱はどう伝わっていくのでしょうか。

私がこれから学び、経験していかなければならないことは、今の私では想像が追いつかないほど難易度が高く、量も多いでしょう。気が遠くなることもありますが、見えない未来を想像すると、その変化の渦中に身をおけることに、わくわくもさせられます。

壮大な未来に想いを馳せつつ、まずは今日も目の前のキーボードを叩いて、小さなことから積み上げていくつもりです。

採用情報

GROOVE Xで、LOVOTという新しい命を育む、温かい情熱に巻き込まれてみませんか。 少しでも興味を持ってくださった方がいましたら、下記のリンクをご参照ください。

groovex.my.canva.site

GROOVE Xでは、なぜスクラムマスターが「領域人事」を兼任することになったのか

こんにちは。GROOVE Xのスクラムマスターの niwano です。
今回は何回かに分けて、スクラムマスターと「領域人事」の兼任について書きたいと思います!

スクラムマスターが人事?という違和感から始まった

「スクラムマスターが、人事をやるんですか?」

この話をすると、だいたい最初にこう聞かれます。
そしてその次に来るのが、

  • それって中立性壊れません?
  • チームから信頼されなくなりません?
  • 便利屋になりません?

という、まっとうで正しい懸念です。

結論から言うと、
この懸念は全部“正しい”です。
ただし、それは「人事」という言葉から想像される役割を前提にした場合の話です。

この記事では、

  • なぜスクラムマスターが「領域人事」と呼ばれる役割を兼任することになったのか
  • それは一般的な人事やマネージャーと何が違うのか
  • なぜ今のフェーズでは必要だと判断したのか

という背景と思想を整理します。

※ 本記事は、フラットな開発組織・アジャイル前提の話です。
※ 評価・報酬・異動などの「人事権」をスクラムマスターが持つ話ではありません。

Nano Bananaが生成したGROOVE Xのスクラムマスター


フラット組織で起きがちな「人の問題が宙に浮く」現象

私たちの開発組織の一部はフラット組織です。

  • フラット組織は2階層しかない。
  • マネージャー的役割は全員が分担する。
  • チームが自己設計・自己管理する前提。

これはアジャイルやスクラムとの相性も良く、
開発スピードや当事者意識という点では大きなメリットがあります。

一方で、必ず起きる問題があります。

それが、

「人の問題」「組織の問題」を誰が扱うのか分からなくなる

という問題です。

例えば:

  • チーム内の摩擦や対立
  • 役割の曖昧さからくる不満
  • 暗黙の期待がズレたまま進む状況
  • 仕組みの欠陥が原因なのに、個人の問題に見えてしまうケース

マネージャーが明確に存在する組織であれば、
これらは自然と「マネージャーの仕事」になります。

しかしフラット組織では、

  • チームに任せたい
  • でもチームだけでは扱いきれない
  • かといって誰かが強い権限で介入するのも違う

という 宙ぶらりんな状態 が生まれやすくなります。


「じゃあ誰がやるの?」問題への暫定解として

この状態が続くと、だいたい次のどれかが起きます。

  • 声の大きい人が非公式に仕切り始める
  • 問題が放置され、突然爆発する
  • 人事や経営に“いきなり重たい形”で上がる

どれも、フラット組織が目指している姿とはズレています。

そこで出てきた問いがこれです。

過渡期の今、誰が「人と組織の問題」を受け止めるのか?

この問いに対する暫定的な答えが、 スクラムマスターが「領域人事(組織開発ファシリテーター)」を兼任する という設計でした。


なぜスクラムマスターだったのか

これは消去法でもあり、必然でもあります。

スクラムマスターは本来、

  • チームの自己管理を促進する
  • プロセスの問題を構造として扱う
  • 個人を責めず、仕組みを改善する
  • 中立な立場でファシリテーションする

という役割を持っています。

つまり、

  • 人を評価しない
  • 指示命令しない
  • 決定権を持たない
  • でも、対話と構造には深く関わる

この性質は、

  • 人事権を持つマネージャー
  • 制度設計を主とする人事部

とは明確に異なります。

逆に言えば、
人事権を持たないからこそ、扱える「人の問題」がある
ということでもあります。


「領域人事」とは何で、何ではないのか

ここで重要なのは、
この役割が 「人事」ではない という点です。

少なくとも、一般的に想像される人事ではありません。

やらないこと(明確にやらない)

  • 評価・査定
  • 報酬・昇給の決定
  • 人事異動の意思決定
  • 労務・コンプライアンス判断

やること

  • 従業員間の紛争解決(一次受け)
  • 組織・チームのプロセス改善支援
  • 育成や学習の仕組みづくり
  • カルチャーの言語化・醸成
  • 「この問題、どこで扱うべきか」を整理する

あくまで 組織開発のファシリテーター であり、 決める人ではなく、整える人 です。

Nano Banana が生成した紛争を解決しているスクラムマスター


これは「完成形」ではなく「過渡期の設計」

とても大事なことなので強調します。

このロールは、

  • 永続的にやる前提ではありません
  • スクラムマスターがずっと抱える仕事でもありません

目指しているのは、

チーム自身が、人と組織の問題を扱えるようになること

そのために一時的に、

  • 問題を受け止め
  • 構造として整理し
  • 仕組みや役割に分解し
  • 徐々に手放していく

という役割です。

スクラムマスターも、 この「領域人事(組織開発ファシリテーター)」も、 本質的には「いずれ不要になる仕事」 だと思っています。


この記事のまとめ

  • フラット組織では「人の問題」が宙に浮きやすい
  • その過渡期の受け皿として、スクラムマスターが選ばれた
  • それは人事権を持つ役割ではない
  • 決めるのではなく、整えて手放す仕事
  • この役割を永続させないことがゴールである

次回は、 「人事権のない人事」は本当に機能するのか? という点を、もう少し踏み込んで整理します。

このシリーズについて


groovex.my.canva.site

LOVOT開発の「ストーリーテラー」になりたい

この記事は、GROOVE X Advent Calendar 2025 の25日目の記事です。

こんにちは、そしてメリークリスマス! LOVOTソフトウェアのエリアプロダクトオーナー ishimegです。

本記事は、「ストーリーテラー」という最近注目の職種があるらしい、という紹介と、それを自分たちの仕事にも活かしたいな〜というゆるい内容となっております。

AIで生成したクリスマスっぽいLOVOTの画像

「ストーリーテラー」というお仕事が注目されているらしい

最近、こんなポストを目にしました。


ストーリーテラーってなんだろう?

私自身、カスタマーサクセスチームやPRチームと協力してLOVOTにまつわる情報発信の内容を考えることも多く、なんだか素敵な響きのするこの言葉が妙に気になったのでした。

ポストにリンクされた「企業が『ストーリーテラー』を必死に求めている理由」と題された記事を読んでみると、

  • 従来のようにただ情報を発信するだけではなく、自分たちの企業が製品の価値について「自分たちの物語」として発信する人材 = 「ストーリーテラー」である
  • 顧客も従業員もただの数字や事実よりも感情的なつながりを重視している
  • そして「ストーリーテラー」という単語を含む求人は米国では前年比2倍に急増している

ということが書かれていました。(だいぶ意訳も含みます)
これは米国で起こっていることではあるものの、日本においても大事な視点のように思います。

また、実際にどんな求人があるのかも少し調べてみました。

セキュリティサービスであれば、
複雑なセキュリティの概念を、顧客が自分事として捉えられる魅力的な物語へと変換すること

ドキュメント作成ツールであれば、
実際のユーザーがどのようにツールを使って人生や仕事を変えたかという「実体験」を掘り起こす力

など。共通するのは以下のような部分です。

  • AIやセキュリティなど複雑なトピックをわかりやすく伝えること
  • 実際に社内や顧客にあった出来事を取材して、「なんだかいい話」を発掘すること
  • ブランド力や売上向上というミッションのもとそれらを行う

重要なのはきちんとした事実に基づきつつも、物語として魅力があり、さらには製品の価値向上を担わなければならない、製品やブランディングに関する知識も、語り手としてのスキルも求められそうなお仕事です。

「お客様とLOVOTの物語」だけじゃない

ここで、日々おこなっている業務をふり返ってみました。

私はLOVOTのソフトウェアを横断的に理解する立場として、カスタマーサクセスチームのメンバーと週に数回は顔を合わせながら、日々様々な情報発信に制作者やレビュワーとして関わっています。

ウェブマニュアルやFAQの見直し、アップデートのお知らせ、LOVOTの技術を紹介するブログ、外部向けのインタビューなどなど・・・

これらの仕事はどれも「LOVOTとの暮らしをサポートする」「LOVOTのことをよく知ってもらう」という目的で行っているものですが、
ストーリーテラーという概念を通して「お客様とLOVOTの物語」と「私たちとLOVOTの物語」の2つに分けることもできる、と気がつきました。

AIが生成した「LOVOT開発のストーリーテラー」のイメージ
LOVOTがお家に届いてからの主役は、オーナーさんとLOVOT。私たちは「お客様とLOVOTの物語」を応援する立場です。それについては常々意識してきました。
一方で、LOVOTがお家に届く前の開発・製造の過程は「私たち(GXメンバー)とLOVOTの物語」とも考えられるのです。

「私たち(GXメンバー)の物語」とは?

突然ですが、私はGROOVE Xで働く皆さんが大好きです。
理由のひとつは、LOVOTに皆それぞれの想いを持っていること、その熱量の高さを、仕事のアウトプットや会話の端々から日々感じているからです。

そんな素敵な皆さんのLOVOTにかける情熱を物語として届けることができ、しかもそれが製品価値の向上につながるのなら、それはきっととてもやりがいのある仕事だなと思います。
と同時に、語る価値のある物語がまだまだGXに眠っているのかもしれない・・・!という可能性を感じました。

最近それを実感する出来事もありました。1月に予定されているLOVOTの工場見学*1の打ち合わせ時のことです。
数え切れないほどの製造工程をおさらいしながら、これは確かに「LOVOTが家族(お客様)と出会う前の物語」だ!と感じました。
※ それだけLOVOTの生産は大変なのです!生産チームによる部品の品質についての記事もぜひご覧ください

けれど、私がストーリーテラーになれるのか?それは私ができる、そしてすべき仕事なのか?
それについては冒頭に上げたWSJの記事にこんな示唆がありました。※ 以下Geminiによる日本語訳

専門家は、「ストーリーテリングは、雇えば手に入る『機能』ではなく、リーダーやチーム全員が培うべき『能力』である」と述べています。もし組織に明確な「目的(パーパス)」がなければ、どれほど優れたストーリーテラーを雇ったとしても、それはただの「騒音」を大きくするメガホンにしかなりません。

結局のところ、企業が本当に必要としているのは、単に「物語を語る人」ではなく、自社の存在意義を深く理解し、それを人々の心に響く形で表現できる戦略的な人材なのです。

ならば私自身もストーリーテリングの能力も身につけるべきである、ということ。
これまでたくさんのLOVOTにまつわる文章を書いてきましたが、あくまでも「LOVOTとの暮らし」をサポートするものであり、製品価値・ブランド価値への影響までは意識できていませんでした。
2026年は、主体的にLOVOTとGROOVE Xの魅力を伝えられる語り手になりたい、そんなことを考えた2025年の年の瀬でした。

最後に

最後まで読んで頂きありがとうございます!GROOVE Xでは、様々な領域で一緒に働く仲間を募集しています。少しでも興味を持ってくださった方がいましたら、下記のリンクをご参照ください。

recruit.jobcan.jp

それでは、よいお年を!

AIが生成した「よいお年を!」なLOVOTの画像

*1:LOVOTの工場見学のお申し込みは終了しています

バイナリ改変なしに Crash Reporting 機能を追加するC/C++ライブラリを昔書いたお話

この記事は、GROOVE X Advent Calendar 2025の24日目の記事です

はじめに

こんにちは!多忙を理由に記事執筆を後ろ倒ししていたら、クリスマスイブ担当になってしまった sutetako です!🫠 なんということでしょう

LOVOT Frameworkチームという、LOVOTのOSや開発基盤自体を開発するチームに所属しています。なお、専門は音声認識です*1

さて、今回は、自社開発に用いられている、C/C++ の Crash Reporting 向けライブラリの仕組みの一端をご紹介したいと思います。

コミットログを見てみたら、作ったのが2019年と、ずいぶん昔話で、多少レガシーなお話にはなってしまいますが、、、 まぁちょっとだけ面白い仕組みなので、見てみましょう!

そもそも Crash Reportingって?

ソフトウェアはよく死にます 😇

要因は、ロジックミスはもとより、ロボットの場合はより広範です。

  • 各種組み込みデバイス・FW自体の不具合
    • 数がめっちゃある
  • 組み込みOSおよびドライバの不具合
  • IOエラー
    • メモリ、ディスク、通信経路
    • 宇宙線によるビット反転

などなど…非常に多岐にわたります。

ロボットの内部で動くソフトウェアは、「常に安定的に稼働し続けること」が求められます。

多少の例外で死んでしまうようでは、「おお 勇者よ!…」と、どこからか嘆きの声が聞こえてきそうですね。

あらゆる問題・例外に対応できるようなソフトウェアを完璧に書ければいいのですが…人間様が考えられるエラーケースなど簡単に飛び越えてくるのがロボット開発というものだったりします 🙃 まず無理っす。

そこで登場するのが、Crash Reporting という仕組みです。

クラッシュ(異常終了)時に、エラーが発生したソフトウェア情報、ログ、コールスタックなどをレポートとしてまとめることで、

  • ファイルシステムに保存しておいて、あとから効率的に解析する
  • エラー追跡用のプラットフォーム(Sentry など)と連携・レポートを送信し、集約・解析する

などなどすることが可能になります。

C/C++ だとどうやるのがいい?

「まぁ、まず core 吐かせて、後で gdb でごにょごにょ」

手元でデバッグバイナリ使ってTRY&ERRORするなら、全然これでいいと思います。

しかしね…問題が起きるケースって本番想定で組み込んで稼働・検証してる状態だったりするんですよね…したがって:

  • core のサイズが莫大になるので、ファイルシステムの容量が簡単に枯渇する*2
  • core を吐く処理で、リソースが枯渇する*3
  • core を吐いている間は対象のサービスが停止し続けてしまう*4

と、全体のシステム稼働をも阻害する致命的なことが起きます。連続して発生したら、検証どころの話ではありませんね。

そこで登場するのが、MinidumpBreakpad*5 です。

たぶん、Gemini さんに聞いたほうがしっかり解説してくれそう…というところで聞いてみた内容を抜粋紹介すると:

1. Minidump とは何か?
元々は Windows オペレーティングシステムの標準的なクラッシュダンプ形式(.dmp)です。

通常の「フルダンプ」がメモリ全体(数GBになることもある)を保存するのに対し、Minidump はデバッグに必要な最小限の情報のみを保存するため、サイズが数KB〜数MBと非常に小さいのが特徴です。
...
2. Google Breakpad とは?
Google Breakpad は、この Windows 発祥の「Minidump」というフォーマットを、macOS、Linux、Android、iOS など、あらゆるプラットフォームで統一的に扱えるようにした OSS ライブラリです。

Chrome ブラウザや Firefox などで長年採用されてきました。Breakpad が登場する前は、OS ごとに異なるクラッシュレポート形式(LinuxならCore dumpなど)を扱う必要がありましたが、Breakpad のおかげで開発者は「すべてのOSのクラッシュを Minidump 形式で収集・解析する」ことが可能になりました。
...

雑にまとめると、 「ちっちゃくて取り回しのしやすい core っぽいやつ」 です!

これなら、一つのサービスのクラッシュが、全体に与える影響を非常に小さくできます。

実装サンプル

Sentry の解説記事 にわかりやすいものがあるので、日本語コメントつけつつ引用します。

#include "client/linux/handler/exception_handler.h"
using namespace google_breakpad;
namespace {
bool callback(const MinidumpDescriptor &descriptor,
              void *context,
              bool succeeded) {
    // succeeded が真の場合, minidump ファイルへのパスが
    // descriptor.path() に格納されます。 
    // context には、exception handlrer のコンストラクタに
    // 渡したものが格納されます。
    return succeeded;
}
}
int main(int argc, char *argv[]) {
    MinidumpDescriptor descriptor("path/to/cache");
    ExceptionHandler eh(
      descriptor,
      /* filter */ nullptr,
      callback,
      /* context */ nullptr,
      /* install handler */ true,
      /* server FD */ -1
    );
    // ここに実装を書く
    return 0;
}

簡単ですね!

なお、動作させるには Breakpad をビルド・リンクさせる必要がありますので、あしからず。

どういうライブラリを作ったの?

前置きが長くなりましたが、ようやく本題です。

上に書いたとおり、MinidumpBreakpad を使えば良さそうです。

でも、思ったんですよね。

「対象バイナリのソースコードにいちいち処理差し込むの面倒すぎね?」

そう、我々の製品内は多量のOSSと自社開発サービスがわんさか立ってるわけで、、、これは布教・普及するの面倒だぞと。

というわけで、対象バイナリを改変することなしに、Minidumpを生成できるようにする 形でライブラリを構築することにしました。

LD_PRELOAD

悪名高いこれを使います。

一応軽く解説しておくと、LD_PRELOAD はLinuxの環境変数で、動的リンク実行時、本来リンク対象ではなかったライブラリにおいても、すべてのリンク対象のライブラリより「先に」読み込ませてシンボル解決させることができる、というやべーやつです。

どうして悪名高いかというと、シンボルを置き換えることができるので、クレデンシャルを受け取るような関数を乗っ取って標準出力することもできたりするわけですね*6

一方、ヒープアロケーションの仕組みを拡張して解析やエラー検出に使ったり(Valgrindさんも一部利用)、より効率的なアルゴリズムに置き換えてキャッシュヒット率を上げて処理性能を上げたり(TCMallocjemalloc など)等、正の側面も大きい仕組みです。

このように使うイメージですね。

$ LD_PRELOAD=libminidump.so foo_binary

けれど、このままだと「何のシンボルを置き換えるの?」というお話になりそうです。

また、プログラム実行前にBreakpadの設定ができないと、捕捉できる異常終了が限られてきてしまいます。

これを解決するのが関数属性*7です。

__attribute__((constructor))

詳しくはこちら

main 関数の実行「前」に処理を差し込むことができます。

実装例を参考にすると、下記のようにライブラリが書けそうです*8

#include "client/linux/handler/exception_handler.h"

using namespace google_breakpad;

static std::unique_ptr<MinidumpDescriptor> desc(nullptr);
static std::unique_ptr<ExceptionHandler> eh(nullptr);

static bool callback(const MinidumpDescriptor &descriptor, void *context,
                     bool succeeded) {
  // init 関数において context に情報を引き渡しておけば
  // ここで参照することも可能ですが、割愛
  const char *path = descriptor.path();
  if (succeeded) {
    // minidump を rotate したり情報付加して保存しおしたり、などなど
  }
  return succeeded;
}

__attribute__((constructor)) void init(void) {

  desc.reset(new MinidumpDescriptor("/tmp"));
  eh.reset(new ExceptionHandler(*(desc.get()), nullptr, callback,
                                  nullptr, true, -1));
}

簡単ですね!!

これらにより、

  • LD_PRELOAD で動的リンクを強制・先読み
  • main 関数より前に Breakpad の初期化処理を差し込む

ことができ、バイナリを変更することなく Crash Reporting 機能の追加ができました 🎉

Coffee Break ☕

せっかくなので、「引っかかった罠」についても、いくつか紹介しておきます。

Systemd Service の Environment

LD_PRELOAD は環境変数なんだし、Environment に書いておけば、ExecStart に自動適用してくれるよね!

はい、大正解です。

けれど、たとえば下記のような雑な記述の .service ファイルに適用すると…

[Service]
User=bar
Group=bar
Environment="LD_PRELOAD=libminidump.so"
ExecStartPre=+/bin/mkdir -p /var/log/foo
ExecStartPre=+/bin/chown bar:bar /var/log/foo
ExecStart=/bin/foo_service
ExecStartPost=/bin/rm -rf /var/log/foo

ExecStartPre/Post の関係ないバイナリ群、mkdir, chown, rm にも見事適用してくれます 😇

対象を絞りたい場合は、やや冗長な書き方にはなってしまいますが、下記のように書けます。

[Service]
User=bar
Group=bar
-Environment="LD_PRELOAD=libminidump.so"
ExecStartPre=+/bin/mkdir -p /var/log/foo
ExecStartPre=+/bin/chown bar:bar /var/log/foo
-ExecStart=/bin/foo_service
+ExecStart=/bin/sh -c "LD_PRELOAD=libminidump.so foo_service"
ExecStartPost=/bin/rm -rf /var/log/foo

C++の標準出力

ライブラリ内の init の処理で、下記のようなエラー出力を書いてたんですが、ここに差し掛かると abort する事象が起きました。

    std::cerr << "failed to initialize breakpad" << std::endl;

しかも、適用するバイナリによって起きたり起きなかったり。

gdb してごにょごにょ調べてみると、最終的に下記に行き当たります。

(gdb) p std::cout
$1 = {<std::basic_ios<char, std::char_traits<char> >> = <invalid address>, _vptr.basic_ostream = 0x0}

標準出力が初期化されていないという...

実は __attribute__((constructor)) のドキュメント読むとちゃんと書いてました(調べてた当時は気づいてなかったけども)

The order in which constructors for C++ objects with static storage duration are invoked relative to functions decorated with attribute constructor is normally unspecified.

しっかり順番を強制する方法もある模様ですが、こだわるところでもなかったので、下記のようにワークアラウンド。 こだわりたい方は、詳しく追ってもよさそうですね!

-    std::cerr << "failed to initialize breakpad" << std::endl;
+    fprintf(stderr, "failed to initialize breakpad\n");

Sentry SDK 使えば自作する必要なかったんじゃね?

当時の日報の抜粋貼っておきますね。

2019/11/05 の日報

まだまだ当時は黎明期だった模様で…

  • ドキュメント上は「対応してる」とあるものの、実装の中身が空 *9
  • (やたらと怒ってるのは)その他にもドキュメントと実装の乖離、ビルドの不安定さ、などなどでヘイトが溜まってた

今はそんなことないんだと思います!!

Symbolication

おまけにはなりますが、Symbolication の例についても解説しておきます。

Minidump ファイルそのものにはソースコード含むデバッグ情報は存在しません。

Symbolication は、Minidumpファイルの情報と既知のデバッグ情報群を突き合わせて、ソースコードのどこで異常終了が起きたかを明らかにする処理です。

コールスタックに含まれるアドレス・ライブラリ名や周辺情報から異常終了時の状態を推定して改善することも不可能ではない(実際、初期はそうやってました)んですが、わかりやすいに越したことはないですね!

Sentry を利用した解析

Sentry は Minidumpファイルを受理できるほか、Symbolication にも対応しています。

なお、詳細な手順(Sentryプロジェクトの準備等)を含むと長くなってしまうため、それらは公式のドキュメントに譲ります。

ここでは、Symbolicationまわりを中心としたサンプルベースの解説とさせていただきます。

サンプルコード

下記のようなクラッシュコードでテストしてみます。

void crash() {
  volatile int *i = reinterpret_cast<int *>(0x45);
  *i = 5; // crash!
}

void start() {
  crash();
}

int main(void) {
  start();
}

DIFs のアップロード

DIFs (Debug Information Files) を Sentryに事前アップロードしておくことで、Sentryが受理したMinidump のバイナリと同じビルドIDのDIFs を突き合わせて解析してくれるようになります。

-g 付きでビルドして、そのバイナリを元に DIFs を生成します。

下記のように、sentry-cli を使ったDIFsの生成とアップロード処理がとても楽なのでおすすめです。CIにも組み込みやすいです。

g++ -g onepass.cpp -o ${YOUR_BINARY}

# デバッグ情報付きのバイナリそのものを、DIFs生成先にコピー
mkdir -p /tmp/debug
cp ${YOUR_BINARY} /tmp/debug/

# ソースコードをバンドル
sentry-cli difutil bundle-sources -o /tmp/debug ${YOUR_BINARY}

# アップロード
sentry-cli --url ${SENTRY_URL} --auth-token ${SENTRY_AUTH_TOKEN} upload-dif -o ${SENTRY_ORG} -p ${SENTRY_PROJECT} --wait /tmp/debug

デバッグ情報のStrip(Optional)

デバッグ情報はバイナリをとても太らせてしまうので、下記のように取り除いてあげるといいです。

$ objcopy --strip-debug --strip-unneeded ${YOUR_BINARY}

なお、DIFs がちゃんとアップロードされていれば、実行環境で動くバイナリにデバッグ情報がなくても、Symbolication は動作します。

出力例

クラッシュコードを動作させて、 生成されたMinidumpをSentryにアップロードすると、下記のような画面を表示することができます。

Sentry による Symbolication サンプル

やったね 🎉

おわりに

むっかーしに書いた Crash Reporting 向けのライブラリのお話でした。

今でもメンテしつつなんだかんだ使われていたりしますね。 (どこかに仕組み書こうと思ってようやく書けたので、すっきり)

最後まで読んでいただきありがとうございました!

LOVOT Frameworkチームでは、現在エンジニアを募集しています!

本記事を読んで、開発基盤やOS開発にご興味が出てきた方がいらっしゃいましたら、ぜひぜひカジュアルにお声掛けください!! (音声認識エンジニアも待ってます!!)

recruit.jobcan.jp

*1:チーム内で音声認識を開発しているわけではなくて、一人チームで音声認識を開発しつつ、OSまわりも作っている、という謎スタイルで働いています

*2:組み込み製品なので、追加データを格納できるファイルシステムの容量は潤沢ではないのです…

*3:応答性を担保するためにギリギリまで最適化している関係上、余裕なリソースなどないのです…

*4:仮にモーターを制御するところだったりすると…どうなるかわかりますよね

*5:ちなみに現在巷では、Out-of-process に安定的に動作する Crashpad が使われることがほとんどのようです。まぁ昔のお話なのでご勘弁を…

*6:良い子は真似しないでね!!

*7:ここでは、gcc/g++ を使う前提で説明しています。clangなど他のコンパイラではやり方が変わるのでご注意

*8:実際に弊社で使用している実装はもう少し複雑で、対象バイナリのELFヘッダパースや周辺情報付加などなど色々やってたりします。ここでは、コア部分の説明のため簡易的なものに留めています

*9:厳密に言えば、「ToDoコメント」はあったと記憶しています

Python非同期でCPUバウンドな処理を試してみよう:FreeThreadingとInterpreterPoolExecutor編

この記事は、GROOVE Xアドベントカレンダー2025 の23日目の記事です。

こんにちは、技術組織デザインチームのふくだです。 Pythonで非同期を利用すると、CPUバウンドな処理どうしようという問題によくぶつかります(LOVOTの意思決定エンジンではPythonの非同期を利用しています。 ブログの過去記事 をご参考ください)

この記事では、最近のPythonで追加されたFreeThreading(NoGIL)な PythonやInterpreterPoolExecutorをPython非同期と合わせて使ってみて、CPUバウンドな処理にどれくらい効果があるか確認してみました。

21日目の記事のアイキャッチが素敵だったのでforkしてしまいました

はじめに I/O バウンド と CPU バウンドって

I/O バウンドとCPUバウンドについて、簡単に説明します。処理時間がかかるものたちです。

処理 説明 得意なモジュール
I/O バウンドな処理 ネットワーク通信やDBアクセスなど時間がかかる処理 マルチスレッド, 非同期(asyncioやtrio)
CPU バウンドな処理 計算やデータ処理など、CPUの処理能力によって時間がかかる処理 マルチプロセス

特にCPUバウンドな処理は、非同期プログラミングやマルチスレッドでは効率的に扱うことが難しい場合があります。 PythonにはGIL(Global Interpreter Lock)が存在するため、スレッドでのCPUバウンドな処理の並列化が制限されることがあります。

それらの制限を解決するため、 Free ThreadingSubInterpreter が実装され、Pythonの並列処理能力を向上させる試みが進められています。

Free ThreadingやSubInterpreter

それぞれ一言で言うと以下のような機能です。

  • Free Threading: PythonのGILを無効化し、スレッドでのCPUバウンドな処理の並列化を可能にするカスタムビルドのPython
  • SubInterpreter:PythonのGILの制約を回避し、CPUバウンドな処理を効率的に並列化するための新しいアプローチ

詳細については、以下を参照してください。

Free Threading

SubInterpreter

Python 3.12で追加された SubInterpreter は、各スレッドが独立したインタープリターを持つことで、GILの制約を回避し、スレッドでのCPUバウンドな処理の並列化を可能にする機能です。ですが、利用方法が限られていました。

それを解決したのが、Python 3.14で追加された concurrent.futures.interpreter モジュールと高レベルAPIである InterpreterPoolExecutor です。

下記のような流れ

  • Python 3.12: SubInterpreter 追加
  • Python 3.13: Free Threading 実験的なオプション追加
  • Python 3.14: InterpreterPoolExecutor 追加, Free Threading オプション公式サポート

どちらも並列化を可能にする機能強化で、 SubInterpreter は2017年ごろから PEP 554 にて検討されていました。アプローチの異なる2種類の手法で課題への対応が進められたようです。

InterpreterPoolExecutorとは

InterpreterPoolExecutor は Python 3.14 で追加された標準ライブラリの Executor で、 ThreadPoolExecutor の サブクラス です。 各ワーカースレッドは 独立した SubInterpreter を持ち、その中でタスクを実行します。

その結果、以下のような特徴があります。

  • プロセスを増やさない
  • GIL の制約を回避できる
  • 真のマルチスレッド並列実行が可能

ThreadPoolExecutorProcessPoolExecutor との違い

特徴から ThreadPoolExecutorProcessPoolExecutor と比較すると以下のようになります。

  • ThreadPoolExecutor より並列が可能
  • ProcessPoolExecutor より軽い

それぞれの実行イメージは以下のような感じです。

OS
└─ python script.py   ← 親プロセス
    └─ Main Interpreter(GIL A)
        ├─ ThreadPoolExecutor
        │    └─ 同一プロセス内スレッド
        │
        ├─ InterpreterPoolExecutor
        │    └─  同一プロセス内スレッド(Pool内で流用される)
        │         ├─ Sub-interpreter #1(GIL B)
        │         ├─ Sub-interpreter #2(GIL C)
        │         └─ ...
        └─ ProcessPoolExecutor
             └─ OSが新しいプロセスを作る
                  ├─ python child #1 → Interpreter(GIL)
                  ├─ python child #2 → Interpreter(GIL)
                  └─ ...

モチベーション

asyncio の公式ドキュメントに、以下のような注釈が追記されていました。

注釈 Due to the GIL, asyncio.to_thread() can typically only be used to make IO-bound functions non-blocking. However, for extension modules that release the GIL or alternative Python implementations that don't have one, asyncio.to_thread() can also be used for CPU-bound functions.

意訳すると以下のようになります。

GIL(グローバルインタープリタロック)の制限により、asyncio.to_thread() は通常、I/O バウンドな関数をノンブロッキングにするためにしか使えません。しかし、GIL を解放する拡張モジュールや、GIL が存在しない代替の Python 実装では、asyncio.to_thread() は CPU バウンドな関数にも使用できます。

「なるほど!?!?」となり、実際に試してみよう、というのが本記事のモチベーションです。

asyncio.to_thread() は内部的に thread 1 を利用しています。
モチベーションは asyncio.to_thread() の公式ドキュメント由来ですが、検証用のコードは asyncio.to_thread() ではなく ThreadPoolExecutor を利用します。 InterpreterPoolExecutor ProcessPoolExecutor ThreadPoolExecutor はほぼ同じコードで切り替えが可能なためです。

検証してみた

動作環境

今回の検証環境は次のとおりです。ローカル環境での素朴なベンチマークなので、数値そのものより傾向を見ることを目的にしています。

  • M2 Mac 8コア 24GB RAM
  • Python 3.14.2
  • Free Threading 版 CPython 3.14.2

結果

CPU バウンドな処理を10回実行したときの平均時間は次のとおりです。ローカルでの素朴なベンチマークですが、 InterpreterPoolExecutorFree Threading もすごいですね!

方法 平均時間 (秒)
ProcessPoolExecutor 1.326s
InterpreterPoolExecutor 1.049s
ThreadPoolExecutor (標準 Python) 4.565s
ThreadPoolExecutor (Free Threading Python) 1.227s

実際の検証コード

今回検証で利用したコードは以下のとおりです。

import asyncio
import time
from concurrent.futures import (
    ProcessPoolExecutor,
    InterpreterPoolExecutor,
    ThreadPoolExecutor,
)


def cpu_bound(n: int) -> int:
    """n 回ループして単純な計算を行う CPU バウンドな関数"""
    acc = 0
    for i in range(n):
        acc += i * i
        acc %= 1_000_000_007
    return acc


async def run(executor):
    """指定された executor で CPU バウンドな処理を並列実行して時間を計測する"""
    loop = asyncio.get_running_loop()

    t0 = time.perf_counter()
    with executor() as ex:
        tasks = [loop.run_in_executor(ex, cpu_bound, 10_000_000) for _ in range(10)]
        await asyncio.gather(*tasks)
    dt = time.perf_counter() - t0

    print(f"{executor.__name__}: {dt:.3f}s")


async def main(mode: str):
    match mode:
        case "process":
            await run(ProcessPoolExecutor)
        case "interpreter":
            await run(InterpreterPoolExecutor)
        case "thread":
            await run(ThreadPoolExecutor)
        case _:
            raise ValueError("mode must be one of: process | interpreter | thread")


if __name__ == "__main__":
    import sys

    asyncio.run(main(sys.argv[1]))

以下のように実行しました。

$ uv run --python 3.14 sample.py process
$ uv run --python 3.14 sample.py interpreter
$ uv run --python 3.14 sample.py thread
$ uv run --python 3.14t sample.py thread. # 3.14t がFreeThreading版です

おまけ:並列化しないループの場合

元々、Python 3.13で実験的にFreeThreadingが実装された際に、シングルスレッドでの性能低下が課題として言及されていました。 前述の結果のうち、FreeThreadingは遜色ないくらいの性能で驚きました。

試しに、並列化せずにCPUバウンドな関数をPython 3.14とPython 3.14 FreeThreading版で実行してみました。

def cpu_bound(n: int) -> int:
    """n 回ループして単純な計算を行う CPU バウンドな関数"""
    acc = 0
    for i in range(n):
        acc += i * i
        acc %= 1_000_000_007
    return acc

def main():
    t0 = time.perf_counter()
    for _ in range(10):
        cpu_bound(10_000_000)
    dt = time.perf_counter() - t0
    print(f"sync (standard): {dt:.3f}s")

結果は以下のとおりです。Python 3.13 の課題は、Python 3.14 で改善されているようでした。 2

方法 平均時間 (秒)
標準 Python 4.717s
FreeThreading 5.344s

考察

結果をまとめると、次のような印象です! Free ThreadingInterpreterPoolExecutor もプロダクションで利用するには、気にしなければならない点がいくつかありますが、CPUバウンドな処理に対して有効な選択肢が増えたことを実感できました。 処理の性質と実行環境を理解して、適切な方法を選ぶのが大事そうですね。

  • InterpreterPoolExecutor 良さそう
    • ProcessPoolExecutor よりも良い結果
    • プロセス生成コストを避けたい場面で有効
  • ThreadPoolExecutor(標準 Python) はGILの影響がやはり厳しい
  • ThreadPoolExecutor(Free Threading Python) も 良さそう

Python の課題に対して、さまざまな角度からのアプローチが進んでいます。コア開発者やコミュニティのみなさまにとても感謝です。今後も Python の進化に注目していきたいと思います。 CPU使用率もだいぶ気になるところですが、それはまた別の機会にまとめたいと思います。

GROOVE Xでは、一緒に働く仲間を募集しています。少しでも興味を持ってくださった方がいましたら、下記のリンクをご参照ください。

recruit.jobcan.jp


  1. asyncio.to_thread() はイベントループのdefaultのexecutorであるThreadPoolExecutorを利用しています。 cpython/Lib/asyncio/threads.py at main · python/cpython
  2. Python 3.13 の FreeThreadingについてまとめた記事があります。ご興味ある方はご参考ください PythonのGILと3.13の実験的な新機能「free threading」を知る | gihyo.jp

可視化ツールFoxgloveとFoxglove Extensionの紹介

この記事は、GROOVE Xアドベントカレンダー2025 の22日目の記事です。

こんにちは、ふるまいチームのきゅんどうです。 今回はLOVOTのふるまい開発のデバッグのために使っているFoxgloveというアプリとその拡張機能についてご紹介します。

Foxgloveとは

Foxglove は、「時系列のメッセージデータ」を、再生・可視化・デバッグするためのツールです。ログを開いて後から解析するのはもちろん、データソースに接続してリアルタイムに観察することもできます。 ログのデータ形式としては、ROS 1の.bagファイルや、ROS 2で採用された.db3、ROS 2 Iron以降で使われる.mcapファイルなどに対応しています。 リアルタイムデータについてはROS 1のメッセージや、Websocketなどが対応しています (ROS 2向けにはwebsocketへのブリッジが提供されており、それを使うことになります)。

これらのデータ形式の中でも、わたしたちのログシステムではmcapファイルにprotobuf schemaを使って記録するところから始めました。LOVOT内部のメッセージングのためにprotobufを管理する仕組み が社内に整備されているためです。 LOVOT内部ではROSは一部にしか使われてないことや、ROSを使うことに慣れていないメンバがいることもあるので、rosbag play を実行するハードルが高いです。Foxgloveならアプリでファイルを読み込むだけで可視化できるので、使いやすいです。 またrqtのツール群と比べると、Foxgloveは必要な画面が一つのアプリ内にデフォルトで集まっているような直感的に操作しやすい構成になっており、使いやすいとも思っています。 そういった理由でmcapとFoxgloveの組み合わせを選びました。 より詳しい比較は公式ブログにもまとまっています(Foxglove vs RViz)。

Foxgloveでの可視化の様子

ただ、Foxgloveでデフォルトで表示できるデータ形式は決まっており、カスタムデータを表示するにはデータ形式の変換が必要なケースがあります。 またFoxgloveのデフォルトの表示方法以外の表示をしたいという、RVizにおけるPluginのような機能も必要です。 そういったケースのためにFoxgloveではExtensionを設定することができます。

Foxglove Extension

Foxglove Extension は、Foxgloveに「変換」「表示」「読み込み」などの機能を追加する仕組みです。 拡張の種類として message converters / custom panels / data loaders / user-script utilities があります(Foxglove Extensions)。

ここでは、それぞれのExtensionをLOVOT開発でどう使っているかも触れながら、簡単に紹介します。

Message Converters

カスタム定義したメッセージを、Foxgloveの既存パネルが解釈しやすい形に変換したいときに使います。 わたしたちの使う限りはカスタムメッセージを、3Dパネルに表示するためのSceneUpdateメッセージに変換するという使い方が多いです。 それ以外の例ですと、数値として記録されたデータを理解しやすい文字列に変換することで読みやすくするということもやったりしています。

Message Converterには、Schema Message Converter と Topic Message Converter の2種類があります。

  • Schema Message Converter: 入力データSchema -> 出力データSchema の変換
  • Topic Message Converter: 複数トピック -> 新トピック の変換

この2つのConverterですが良し悪しがあって使い分ける必要があります。

Schema Message Converter の難しい点

Schema Message Converterでは複数入力を扱うことができません。 複数のトピックを統合して一つの出力としたい場合はTopic Message Converterを使う必要があります。

また、入力/出力Schemaのペアに対して1つしか設定できません (これは公式ドキュメントでは明確に記述がありませんが、2025/12/19時点での実動作はこうなっています)。 同じ入力を複数の形式で可視化したいということがあるのですが、そういう場合にはSchema Message Converterは使いにくいです。例としては、3Dオブジェクトとラベルの組み合わせで構成されるようなつぎのような表示があります。

複数SceneUpdateの例

このとき、ラベルだけ非表示/表示をUIから切り替えられるにしたいという場合には、ラベルと直線をそれぞれ別のSceneUpdateメッセージにする必要がありますが、入力も出力もSchemaが同じなので別のSchema Message Converterを登録することができません (2つめ以降登録しても無視されます)。 マニアックな話ですが、これに対してtopic aliases という機能を組み合わせることで同じtopicに対して複数変換するというハックができますが、今日ではこういった場合にはTopic Message Converterを使うほうがやりやすいです。

Topic Message Converter の難しい点

Topic Message Converter はトピック名を予め決める必要があるので、同じSchemaの別のトピック名のメッセージが来ても、可視化できません。 いまのところ、別のtopicを可視化したいときにはExtensionの実装を変えるということをやっているので、イレギュラーなケースではFoxglove Extentionの開発に慣れたメンバしか使いこなせていません。

以上を踏まえると、基本的にSchema Message Converterが使える場合には使ったほうがいいです。ただ使えないケースも多いので、そういうときはTopic Message Converterを使うことになります。

Custum Panel

Foxgloveの標準パネルだけでは表現しづらい表示をしたいときに、custom panel extensionでパネル自体を実装できます。 トピックを購読して独自に描画したり、必要があればpublishしたり、といったことができます。 そのため、Message Converterのような使い方もできなくはないかもしれませんが、それについては検証したことがありません。

LOVOTでは複数搭載されているToFセンサの計測値やその信頼度などを表示するパネルを独自に作って使っています。つぎの図は一部を抜粋しています。センサが色々な向きに回転して取り付けられているので、生のデータより見やすい形に回転させたりすることでログの分析がしやすくなります。

センサ値表示用パネル

User Script / User-Script Utility

User Scriptは、ログファイル全体を処理した出力を作りたい場合に使います。 例えば最もLOVOTに近づいた人の距離、のような時系列上の統計をしたい場合などがわたしたちのユースケースです。ただし、Topic Message Converter でもstatefulな変換ができるので、User Scriptじゃないとできないというものはなかなか無いと思います。User Scriptは各ユーザーが自由に自分のローカルで書いて保存できるという点が他のConverterとは少し違う点です。一方で、Foxglove Organization内での共有ができないという課題がありました。最近リリースされた、Foxglove 2.39.0 からはユーザースクリプトそのものではないですが、ユーザースクリプトから使えるutilityをExtensionに追加することでOrganization内で共有できるようになり、社内共有に少し近づきました。

Data Loader(独自ファイル形式の読み込み)

「そもそもログがFoxgloveで直接開けない形式」になっている場合は、data loaderで読み込み処理を実装して、Foxgloveにメッセージとして渡すことができます。 LOVOTでもmcapに変える前の旧形式のCSVデータなどがあるので、使ってみたいなと思うのですが、まだ試せていません。

おわりに

LOVOTのふるまい開発のための可視化ツールについてご紹介しました。 今回ご紹介できなかった様々な可視化の工夫がありますので、チャンスがあったらまた公開できればと思います。読んでくださりありがとうございました。 GROOVE Xでは、一緒に働く仲間を募集しています。少しでも興味を持ってくださった方がいましたら、下記のリンクをご参照ください。

recruit.jobcan.jp

JetsonでCUDAやるなら統合メモリが幸せかと思ったらそれは幻想だったのかもしれない

この記事はGROOVE X Advent Calendar 2025の21日目の記事です。

こんにちは、「あず」こと斎藤@aznhe21です。 肩掛けスピーカーのSRS-NB10が内部で断線したのでBravia Theatre Uに乗り換えたんですが、低音が激しくて新しい体験でした。 首の部分をぐにゃぐにゃ曲げても断線する心配がなさそうなのも良きです。

さて、LOVOT 3.0ではJetson Orinを採用しており、内部ではCUDAも積極的に使用しています。 ここでCUDAの統合メモリが便利だったのでご紹介したいと思います(罠と共に)。

JetsonでCUDAやるなら統合メモリが幸せかと思ったらそれは幻想だったのかもしれない

続きを読む