yak shaving life

遠回りこそが最短の道

Pixel 6 を買った

買いました。

Pixel 3をおよそ3年前に買って以来ずっと使っていて、正直デバイスとしての不満は全然ありませんでした。今でもサクサク動くし、指紋認証使いやすいし、カメラは綺麗だし。カメラを起動するとメモリを使い果たして全てのアプリがキルされちゃうけどそこはご愛嬌。

ただ、充電が持たなくなってきたのと、電源ボタンが時々埋もれてしまい半分押しっぱなし状態(勝手に電源が切れたり、カメラ起動したりとめちゃくちゃな挙動をする)になってしまうことがあり、また充電ケーブルを挿しただけでは充電できない問題(サポートに問い合わせたけど解決せず)もあるのでそろそろ買い替えどきかなとは思っていた。

Pixel 4はなんか微妙だなと思い、4aも同様、5は良さそうだけどちょっとスペック低いな…?5aは言わずもがな…てな感じで様子見に様子見を重ね、6が来てやっと、これや!と思ったわけです。思ったんですが、Tensor SoCが果たしてどの程度の出来なのか、初おろしの製品だし重大なバグがないだろうか、それにお値段もそこそこするしハードウェアスペック的には正直そこまででもない…本当に買いなのか?ぶっちゃけPixel 3のままでいいのでは…?

等々の葛藤の末、Google様へのお布施だと思い、えいやで購入ボタンを押している私がいました。Big Techに世界を便利にしてもらうためにどんどんお布施していかないとね。つーわけで来たんですよ。やっと。悩んでいるうちにみんなどんどん注文していたっぽくて、Google Storeを見るたび配送予定日が遅くなっていたんですね。実際は表示されていた予定日よりだいぶ早く来たのでよかったです。

というわけで早速開けたかったけれども、子供が寝てからにしようと思っていたら子供を寝かしつけながら一緒に寝てしまうという失態を犯し、一日越しでついに開封しました。テンション上がってきた。

電源をつけて、指示に従いセットアップ。今時のAndroidは端末同士をケーブルで繋いで移行できるので便利。昔はどうやってたかなー。なんかよくわからんけど自力でアプリとか落としてきて全部手動でセットアップした気がする。思い出しただけで辛い。そのままサクサク進めて、数十分ほど待って完了。楽チンですなあ。

ちょっと触ってみた感想。でかい。ギリ許容できるレベルの大きさかな。個人的にはPixel 3くらいの画面サイズがちょうどいいんだけどなあ。デカすぎて右手で持ったとき左端まで親指が届かない…。iPhoneみたいに左端からスワイプして「戻る」ができるんだけど、ちょっと実用性がないのでは…と思ったら、右からスワイプでもいいらしい。ほう。こりゃ便利じゃあないか!というわけで従来の3ボタンナビゲーション(画面下部に「戻る」「ホーム」「アプリの切り替え」の3ボタンがある)ではなくてiPhone風のジェスチャーナビゲーションでいくことにしました。良さそう。

画面のヌルヌル感はiOSと遜色ない感じ。画面上の指紋センサはちょっと時間かかるけど今のところ精度は問題なさそう。保護フィルム貼ったときどうなるかが気になる。カメラはまだちゃんと使ってないけど画質いい感じ。Pixel 3にあった、本体横両側をぎゅっと握りしめるとGoogle Asistantが起動するやつはないのかー。地味に使ってたのになあ。まあ他に使ってる人いなさそうだけども。

そんな感じで、パッと見とても良さそうだなという印象。消しゴムマジックとか自動翻訳とかTensor SoCが本領発揮しそうな機能はまだ使ってないけど。発熱がきついという噂もあるし充電がどの程度持つかとかも気になるので、ちゃんとした使用感はまた後日書こうと思います。他にも買った人いると思うんで、是非感想書いてください。読みたいです。

Spring Data RedisでZPOPMIN(MAX)を使う

RedisにはSorted Setというデータ型があって、ユーザランキングみたいなものを作るときに便利だったりする。このSorted Setに対する操作はZADDとかZRANKのようにZから始まるものとなっている。

Sorted Setから一番スコアが低い(高い)ものを破壊的に取り出すZPOPMIN(ZPOPMAX)というコマンドがあり、Spring Data Redisでこれらを使いたかったのだがやり方がわからず、調べてもなかなか出てこないので苦労した。ので、解決までの道のりをメモ。やり方だけ知りたい人は最後の方のサンプルコードを見てください。検証に使っているSpring Data Redisのバージョンは2.5.6で、Javaは11。Redis ConnectorはLettuce。

まず、サポートされているコマンドは以下のように利用できる。

ZSetOperations<String, String> zSetOperations = redisTemplate.opsForZSet();
zSetOperations.add("ranking", "user1", 100);
zSetOperations.add("ranking", "user2", 200);
Set elements = zSetOperations.range("ranking", 0L, 1L);

System.out.println(elements);
// 出力 => [user1, user2]

zSetOperations.addZADDzSetOperations.rangeZRANGEコマンドを実行してます。簡単ですね。

ところがZSetOperationsクラスにはpopMinとかpopMaxみたいなメソッドがありません。こいつは困った。色々調べたけどよくわからない。そうこうしているうちに以下のissueに辿り着いた。

github.com

要は次くらいのバージョンで対応してZPOPMIN, BZPOPMIN, ZPOPMAX, BZPOPMAX, ZMSCOREが使えるようになるとのこと。へー。でも僕は!今やりたいんです!どうすりゃいいの!と思っていたら興味深いコメントが。

When using Lettuce, you need to provide a CommandOutput to capture the Redis response. For [B]ZPOP[MIN|MAX] that's new ScoredValueOutput<>(ByteArrayCodec.INSTANCE) (via execute(String command, @Nullable CommandOutput commandOutputTypeHint, byte[]... args)).

Spring Data Redis doesn't have yet command output hints for these commands.

なるほど、要はRedisTemplateではできないからLettuceのexecuteコマンドでやれってことね。ふむふむ。というわけでリファレンスを見る。

docs.spring.io

なるほどわからん…。数時間溶かしてなんとかできたのが以下。各行に何やってるかコメント書いてます。

 // ここはさっきとほぼ同じコード
ZSetOperations<String, String> zSetOperations = redisTemplate.opsForZSet();
zSetOperations.add("ranking", "user1", 100);
zSetOperations.add("ranking", "user2", 200);
Set elementsBefore = zSetOperations.range("ranking", 0L, 1L);
System.out.println("Ranking before ZPOPMIN : " + elementsBefore);
// => Ranking before ZPOPMIN : [user1, user2]

// LettuceConnectionを取得。キャストするのがミソ。キャストせずRedisConnectionのままだとexecuteが通らない
LettuceConnection conn = (LettuceConnection) redisTemplate.getRequiredConnectionFactory().getConnection();

// 実行。第2引数がミソ。戻り値はObject型
Object resultObject = conn.execute("ZPOPMIN", new ScoredValueOutput<>(ByteArrayCodec.INSTANCE), "ranking".getBytes());

// executeの戻り値はScoredValue型にキャストできる。ここの型を変えれば他のコマンドにも対応できそう
ScoredValue scoredValue = (ScoredValue) resultObject;

// getValueでvalueが取れるが、戻り値はObject型
Object valueObject = scoredValue.getValue();

// Stringを入れてるならStringに変換できる。直接はキャストできないのでbyte配列にしてから
String value = new String((byte[]) valueObject);
        
// scoreはdoubleで入ってるのでキャストとかは不要
double score = scoredValue.getScore();
        
// valueとscoreが取れてめでたしめでたし
System.out.println("Popped element : value = " + value + ", score = " + score);
// => Popped element : value = user1, score = 100.0

// POPしたのでSorted Setの中身が一つ減っていることを確認
Set elementsAfter = zSetOperations.range("ranking", 0L, 1L);
System.out.println("Ranking after ZPOPMIN : " + elementsAfter);
// => Ranking after ZPOPMIN : [user2]

conn.close();

このサンプルコードがググってさっと見つかればこんなに苦労することもなかったのになあ。。 ということで一応デモアプリをGithubにあげておいた。誰かの役に立てばいいけど。

github.com

追記:connectionをcloseしてなかったのでconn.close()を追加。

東京都現代美術館で GENKYO 横尾忠則 などを観た

この前 The Artists にも行ったのだが、GENKYO はもう規模が桁違いだった。とにかく横尾忠則作品がものすごい数展示されていた。活動期間がとても長い方なので、年代ごとに作風が全然違う。それがうまくカテゴリ分けされていて素晴らしい展示だった。

横尾忠則作品といえば、若い頃のアングラっぽい派手な色と女性が特徴的なポスターだったり、書き込み量が異常に多くて宗教や西洋美術のモチーフが大量に詰め込まれたような難解かつ複雑な絵画なんかは観たことがあったけれども、それ以外にもY字路シリーズとか「タマ、帰っておいで」とかまあ色んな作品があった。量だけはかなりたくさん観た気がするので、少しずつ横尾作品が分かるようになっていきたい。

その後はマーク・マンダースの特別展示を見たり、コレクション展を見たり。マーク・マンダースは初めてじっくり観たけど、どういうコンセプトなのか不思議だ。立体は表現方法が色々ありすぎてカオスで面白い。本人のインタビューとか解説をもうちょっと見たいなあ。コレクション展の方でも色々あって面白かったけど、平田実の写真展示でハイレッドセンターの「首都圏清掃整理促進運動」があり、あっ、本田パピヨン先生の漫画*1で見たやつだ!という進研ゼミ的体験をしたりして楽しかった。

1日でかなり多くの作品が観れて良かった。都現美いいとこみんなで行こう。

iPadは教科書代わりになるのか

息子が学校の教科書をPDF化してiPadだけ持って登下校しているという話を見た。

togetter.com

本当か嘘かみたいなところはどうでもよくて、はてブコメントが面白い。特に否定派のコメントはある意味参考になる。人は変化を嫌う生き物なんだなーというのがよくわかるというか。リツコさん風に言うとホメオスタシスというやつだろうか。全自動洗濯機が発売された当初、「洗濯は愛情を込めて手でするもの、機械がやるなんてあり得ない」と言われなかなか受け入れられなかったという話を聞いたことがあるが、どの時代も同じという感じがするなあ。

iPadがあれば(紙の)教科書はいらないのかというとどうだろう。個人的な経験からすると紙の方が便利そうに感じるけれども、初めからiPadしかなければその環境で十分に育っていくのが人間だという気もする。教育は結果が出るのに時間がかかるし効果測定が難しそうなのでなんとも言い難い。まあでもクソ重い教科書の持ち運びは意味ないからやめた方がいいでしょうね。

それはさておき、少し前に買ったキングジムのフリーノのことを思い出した。自分ではあまり使っていないのだが、奥さんが試験勉強にガッツリ活用していて面白いなと思った。PDFの問題集を端末に入れて、そこに書き込んでいくという感じ。いくらでもコピーできるし書き直しもできるし検索もできるというので紙より便利そう。いつでも持ち運べるし。もしかしたら、教育用にはiPadみたいな汎用タブレットより電子ペーパーのデジタルノート端末の方が向いているかもしれない。あーでも動画とか見れないのはちょっときついか…iPadで動画や資料を見て、フリーノでノートを取る。みたいな感じが一番いいのかもしれないけど、そこまでリッチな環境を全学生に提供するにはまだまだかかりそうだ。教育のIT化も難しそうだ。いつか携わってみたいところではあるけどまあ茨の道だろうな…。

HomebrewでJmeterをインストールしたらちゃんと動かなかった

諸事情によりJmeterGUIを動かしたかったのでMacでのインストール方法を調べると、Homebrewにあるとのことだったのでインストールした。が、ちゃんと動かなかったのでやったことをメモ。

環境

macOS 10.15.13(Catalina), JavaはOpenJDK 11.0.4, MacBook Pro Early 2015。M1 Macの新しいやつほしい。

事象

brew install jmeterJmeterをインストールして、jmeterコマンドでGUIを実行。WARNING: package sun.awt.X11 not in java.desktopとかいうのが出てるからなんとなく嫌な予感がしたが、一部の機能が動かない。具体的には File -> Open を押しても何も起こらない。(何もできねえ…)

ターミナルを見るとこんなエラーが出力されている。

Uncaught Exception java.lang.IllegalAccessError: class com.github.weisj.darklaf.ui.filechooser.DarkFilePaneUIBridge$DetailsTableModel (in unnamed module @0x4c40b76e) cannot access class sun.awt.shell.ShellFolder (in module java.desktop) because module java.desktop does not export sun.awt.shell to unnamed module @0x4c40b76e in thread Thread[AWT-EventQueue-0,6,main]. See log file for details.

なるほどわからん。ログファイルを見ろと言われているので、カレントディレクトリに生成されているjmeter.logを見る。

2021-10-26 01:27:40,693 ERROR o.a.j.JMeter: Uncaught exception in thread Thread[AWT-EventQueue-0,6,main]
java.lang.NoClassDefFoundError: Could not initialize class org.apache.jmeter.gui.util.FileDialoger
        at org.apache.jmeter.gui.action.Load.doActionAfterCheck(Load.java:75) ~[ApacheJMeter_core.jar:5.4.1]
        at org.apache.jmeter.gui.action.AbstractActionWithNoRunningTest.doAction(AbstractActionWithNoRunningTest.java:44) ~[ApacheJMeter_core.jar:5.4.1]
        at org.apache.jmeter.gui.action.ActionRouter.performAction(ActionRouter.java:87) ~[ApacheJMeter_core.jar:5.4.1]
        at org.apache.jmeter.gui.action.ActionRouter.lambda$actionPerformed$0(ActionRouter.java:69) ~[ApacheJMeter_core.jar:5.4.1]
        at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:318) ~[?:?]
        at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:771) ~[?:?]
        at java.awt.EventQueue$4.run(EventQueue.java:722) ~[?:?]
        at java.awt.EventQueue$4.run(EventQueue.java:716) ~[?:?]
        at java.security.AccessController.doPrivileged(AccessController.java:399) ~[?:?]
        at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:86) ~[?:?]
        at java.awt.EventQueue.dispatchEvent(EventQueue.java:741) ~[?:?]
        at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203) [?:?]
        at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124) [?:?]
        at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113) [?:?]
        at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109) [?:?]
        at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101) [?:?]
        at java.awt.EventDispatchThread.run(EventDispatchThread.java:90) [?:?]
2021-10-26 01:27:40,842 ERROR o.a.j.JMeter: Uncaught exception in thread Thread[AWT-EventQueue-0,6,main]
java.lang.NoClassDefFoundError: Could not initialize class org.apache.jmeter.gui.util.FileDialoger
        at org.apache.jmeter.gui.action.Load.doActionAfterCheck(Load.java:75) ~[ApacheJMeter_core.jar:5.4.1]
        at org.apache.jmeter.gui.action.AbstractActionWithNoRunningTest.doAction(AbstractActionWithNoRunningTest.java:44) ~[
ApacheJMeter_core.jar:5.4.1]
        at org.apache.jmeter.gui.action.ActionRouter.performAction(ActionRouter.java:87) ~[ApacheJMeter_core.jar:5.4.1]
        at org.apache.jmeter.gui.action.ActionRouter.lambda$actionPerformed$0(ActionRouter.java:69) ~[ApacheJMeter_core.jar:5.4.1]
        at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:318) ~[?:?]
        at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:771) ~[?:?]
        at java.awt.EventQueue$4.run(EventQueue.java:722) ~[?:?]
        at java.awt.EventQueue$4.run(EventQueue.java:716) ~[?:?]
        at java.security.AccessController.doPrivileged(AccessController.java:399) ~[?:?]
        at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:86) ~[?:?]
        at java.awt.EventQueue.dispatchEvent(EventQueue.java:741) ~[?:?]
        at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203) [?:?]
        at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124) [?:?]
        at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113) [?:?]
        at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109) [?:?]
        at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101) [?:?]
        at java.awt.EventDispatchThread.run(EventDispatchThread.java:90) [?:?]

なるほどわからん(2分ぶり2回目)。とりあえずorg.apache.jmeter.gui.util.FileDialogerがクラスパスにないんだろうけどどうすればいいやら。

解決策

エラーメッセージ等でしばらくググって色々試したけどどうも解決できず。Javaのバージョン下げろとか上げろとか色々書いてあったので、jenvを入れて8とか17とかも試してみたけど変わらず。

こいつはちょっとヤクすぎる(yak shavingすぎる)なと感じ始めたので、別のアプローチを試すことに。[Jmeter公式]に行くとバイナリをダウンロードできるので、これをダウンロードしてみることに。その間にhomebrew版は消しておく。

大体以下のようなコマンドを打った。

$ brew uninstall jmeter
Uninstalling /usr/local/Cellar/jmeter/5.4.1... (2,645 files, 124.4MB)

# 一応Checksumを計算してみる。ちゃんと合ってるっぽい。
$ shasum -a 512 apache-jmeter-5.4.1.tgz
bfc0faa84769b58c1fd498417b3a5c65749f52226bd6e3533f08ca7ea4a3798bb8d2cbd7091b443dd6837f3cbea5565c3c18e6497b40bec95616bf44dfdf590d  apache-jmeter-5.4.1.tgz

$ tar xzvf apache-jmeter-5.4.1.tgz
(出力略)

$ cd apache-jmeter-5.4.1/bin/

$ sh jmeter.sh
================================================================================
Don't use GUI mode for load testing !, only for Test creation and Test debugging.
For load testing, use CLI Mode (was NON GUI):
   jmeter -n -t [jmx file] -l [results file] -e -o [Path to web report folder]
& increase Java Heap to meet your test requirements:
   Modify current env variable HEAP="-Xms1g -Xmx1g -XX:MaxMetaspaceSize=256m" in the jmeter batch file
Check : https://jmeter.apache.org/usermanual/best-practices.html
================================================================================

お、Homebrew版で出ていたWARNINGが出てない!これは期待!

ということで、実際に立ち上がったGUIではFile -> Openも動きました。エラー出力もなし。めでたしめでたし。

Homebrew版のエラーを解消する方法は結局分からなかったけど、まあ動いたからよしとしよう。今日もyakをshavingしたので人生を生きているなあ。

世界史を変えた植物 を読んだ

なぜ読もうと思ったか

家族に勧められて。

どのような本か

Amazonの紹介文が面白いのでそのまま引用します。

私たちが知っている歴史の裏側で、植物が暗躍していたら……

古代の文明が植物によって生みだされたとしたら、どうだろう。 近代社会を生みだした産業革命の原動力となったものが、ある植物であったとしたらどうだろう。 世界一の大国であるアメリカの隆盛の陰に、ある植物があったとしたらどうだろう。 アメリカの南北戦争やイギリスと清とのアヘン戦争の陰に、植物の陰があったとしたらどうだろう。 人類の歴史は、植物の歴史でもある。 本書では、そんな植物から見た世界の歴史をのぞき見てみたいと思う。 (「はじめに」より)

書評というか感想

すごい面白かった。

一章につき一つの植物という章立てになっていて、歴史上重要な出来事と植物の関係性について解説がある。植物についての豆知識的な面白さもあるし、植物や農業が人類の発展においていかに重要なファクターなのかが丁寧に説明されていて勉強になる。

人間が植物を育てているのか、はたまた我々は植物に育てさせられているのか…?という若干オカルトめいた語り口もコミカルで面白い。

植物/農業が富を生み、国を作ったという視点は今までなかったけど言われてみれば確かにという感じ。肥沃な土地より痩せた土地で農業が発展するとか、農業と奴隷と戦争の関係、唐辛子と胡椒はなぜ両方ペッパーというのか、なぜヨーロッパで紅茶が流行ったのか、コーヒーを広めたのはイスラム教徒、ドイツ料理にジャガイモが多い理由、トウモロコシは宇宙から来た(?)等々、とにかく興味深い話題のオンパレードだった。

読みやすく、面白く、勉強にもなる非常にオススメな本です。

著者の稲垣栄洋氏は農学部の教授らしい。この人の他の書籍も読んでみたい。

KAWS TOKYO FIRST と 横尾忠則 The Artists に行った

順番的には先に21_21に行って横尾忠則 The Artistsを観てからKAWS TOKYO FIRSTの方へ行った。六本木はええところですなあ。

横尾忠則氏についてはあまりよく知らないのだけれども、若い頃のアングラ演劇ポスターみたいな印象が強い。毒々しい色の裸体の女みたいな。その程度しか知らずに観に行ったのだが、なかなか良かった。著名なアーティスト、写真家、デザイナーなどの肖像画がずらっと並んでいるのだけど、全て違った画風で書かれていて面白かった。北野武宮島達夫とかちょくちょく日本人も居た。ここにいる人たち全員わかったら相当アートに詳しいと言えるだろうな〜と思いながら眺めていた。僕は10分の1も分からなかった。無知。

KAWS TOKYO FIRSTは大変賑わっていて、長い入場列ができていた。当日券買えるかわかりませんとまで言われた。買えたけど。こちらは作品数も多いし大きな作品もあって分かりやすく凄かった。KAWSについてもあまりよく知らなかったのだけど、彼のキャラクターにはCompanion、BFF、Chumという名前があるようだ。BFFて。Best Friend Foreverて。個人的にはBackend For Frontendを真っ先に想像するので職業病だなと思った。

KAWSは大衆的キャラクターを多用しているので子供も喜んではしゃぎ回っていた。珍しく写真撮るときのポーズとかもしてくれたしキャラクターは偉大だなあ。アートと一般人(僕みたいな)の接点を増やしてくれるという意味でも良い。しかし、こういうのって権利関係とかどうなってるんだろう。分からんことだらけで楽しいなあ。