もしかして頻尿で Mashup Battle 1stStage in 北陸敗退しました
トイレに行く回数が気になったので、トイレに行った回数を記録するためのデバイスを作ってみました。
磁石とリードスイッチを使ってチャックの開閉を検出し、データをサーバに蓄積する仕組みです。
で、これを持って Mashup Awards 2017 の Mashup Battle 1stStage in 北陸 に出てみました。
【北陸予選】#MA_2017
— MashupAwards@決勝は12/16 (@mashupaward) 2017年11月23日
⑨作品名:もしかして頻尿/チーム名:そろそろアラフォー
もしかして、俺って頻尿?そんな気になる疑問に答えるアイテムです
1日に8回行ったら貧乳! pic.twitter.com/xAbqiDhWlg
【北陸予選】#MA_2017
— MashupAwards@決勝は12/16 (@mashupaward) 2017年11月23日
⑨作品名:もしかして頻尿/チーム名:そろそろアラフォー
もしかして、俺って頻尿?そんな気になる疑問に答えるアイテムです
チャック写真 pic.twitter.com/Ky1Xu1tTcb
【北陸予選】#MA_2017
— MashupAwards@決勝は12/16 (@mashupaward) 2017年11月23日
⑨作品名:もしかして頻尿/チーム名:そろそろアラフォー
もしかして、俺って頻尿?そんな気になる疑問に答えるアイテムです
仕組みと尿の出具合。
チャックを触りながらキントーンを褒める。 pic.twitter.com/BPQTbcTOZg
【北陸予選】#MA_2017
— MashupAwards@決勝は12/16 (@mashupaward) 2017年11月23日
⑨作品名:もしかして頻尿/チーム名:そろそろアラフォー
もしかして、俺って頻尿?そんな気になる疑問に答えるアイテムです
チャックを上げ下ろしする前代未聞のデモ! pic.twitter.com/9Bk8NLPhnA
【北陸予選】#MA_2017
— MashupAwards@決勝は12/16 (@mashupaward) 2017年11月23日
頻尿のモノ pic.twitter.com/O8diBgy9Hf
@運営さん たくさんの写真、動画をありがとうございました!
昨年の Internet of Tairyoku では惜しくも2位だったので、今年こそ、と思って挑みましたが、得票は1票。無念の敗退でした。
この作品について、コンセプト自体はだいたい実現できたので満足したのですが、実装に改善の余地がありありだなと感じています。ということで、ここでひとつふりかえり。
よかった事:
- ESP32 というおもちゃを手に入れた
- これをきっかけに工具箱、工具、材料などを買い揃えることができた
- kintone に毎分データを送って可視化するといい感じだった
- 頻尿だけじゃなくて尿道結石にも使えるんじゃないのという意見をもらった
今後の課題:
- 電池で動かすつもりが、消費電力に不安があって PC から USB 給電でのデモとなった
- 電池で1日動くようにしたい。いや、1日と言わず一週間は動かしたい
- リチウムイオン電池を使って小型化してみたい
- deep sleep とやらを使ってみたい
- 消費電力をちゃんと測りたい
- ブラウザから操作できたら素敵なのでは
- 小型化したい
- リードスイッチに固定に難あり
- チャックに磁石をくっつけるのは、セロテープでぐるぐる巻きにすれば大丈夫だった
- リードスイッチは、よく外れた。デモでも外れた
このように課題山積です。個人的にほしいデバイスなので(現時点で頻尿ってわけじゃないです)、今後も作り込んでいきたいなと思います。
ISUCON 7 予選敗退しました
ISUCON 5 のときと同じチーム「へしこず」で、2年ぶりの本戦出場を狙いましたが、本戦出場ラインには倍ぐらい届かず敗退しました。また来年会いましょう。
今回の主な装備
- vim
- kataribe
- pt-query-digest (percona-toolkit), mysqldumpslow
- beer (alcohol free)
KENZEN #isucon pic.twitter.com/kJpWD5ZQyM
— こっしー@みいつけた! (@macoshita) 2017年10月22日
序盤
予習で golang も検討したんですが、これまでの ISUCON で慣れてた ruby でやることにしました。
bundle install
できないぞってことで sudo apt-get install -y ruby-bundler
とか適当に叩いたのがアダ。 ~/xbuild
眺めたりして気づいたんですが ~/local/ruby/bin/bundle
を使うのが正解だったようです。
まず POST /login
のクエリがイケてない感じだったので、LIMIT 1
つけたり * を必要なものだけに書き換えたりして 10007 点になりました。その後何回か微調整してベンチマークして 15191 点あたりまで伸びました。このあたりはとりあえず API 1台だけでベンチマーク走らせてました。
この時点で2時45分ごろ。開始から2時間弱といったところでした。
中盤:icons をどうにかする
どうみても GET /icons/*.png
なんとかしないとだめだよね。ってことで、なんとかすることに。画像を mysql から取り出して redis に入れる班と、nginx の設定いじってキャッシュさせる班のふた手に分かれて行動しました。俺たちを苦しめた icons の一部。
アイコン画像が数百KBとか、仕事だったらありえないサイズだよねとか話してました。nginx 側は
location ~ /(css|js|fonts|icons)/ { proxy_set_header Host $http_host; proxy_pass http://puma_app; proxy_ignore_headers Cache-Control; proxy_cache zone1; proxy_cache_valid any 1m; expires 1m; etag on; add_header Cache-Control "public"; }
と書いてみたり、ruby 側では
configure do # 略 set :static_cache_control, [:public, :max_age => 90] end
とか
get '/icons/:file_name' do # 略 etag file_name # 略 end
とか書いて、kataribe で見た icons の状況はこのようになりました。
Top 20 Sort By Total Count Total Mean Stddev Min P50.0 P90.0 P95.0 P99.0 Max 2xx 3xx 4xx 5xx Request 3097 6351.049 2.050710 3.120888 0.000 0.007 7.722 10.000 10.001 10.007 1646 1451 0 0 icons
100Mbps 使い切ってたネットワーク帯域にも少し余裕が出てきました。
あとは GET /fetch
での SELECT COUNT(*) as cnt FROM message ...
のコストが高いと考えて、件数を redis に入れるなどしました。
18:43 時点で 45124 点。他のチームが数十万点を出してて、だいぶ焦りが出てきました。
終盤:積み重ねでフィニッシュ
- nginx → ruby (puma) をソケット経由にする
/etc/systemd/system/isubata.ruby.service
でExecStart = /home/isucon/local/ruby/bin/bundle exec puma -b unix:///tmp/puma.sock
/etc/nginx/sites-enabled/nginx.conf
でupstream puma_app { server unix:/tmp/puma.sock; }
,proxy_pass http://puma_app;
- puma のプロセス数、スレッド数を増やす
/etc/systemd/system/isubata.ruby.service
でExecStart = /home/isucon/local/ruby/bin/bundle exec puma -w 2 -t 25 -b unix:///tmp/puma.sock
- 既読メッセージIDを redis で管理する。
- mysqld の max_connection を 1024 に。
などといった工夫を重ねて、終了直前には 125550 点まで伸ばせました。
感想
開始時間が遅れはしましたが、ベンチマークとダッシュボードは ISUCON 5 からの中で(自分が参加した中で)一番良かったと思います。
- ベンチマークキューが詰まる気配がなかった
- 「負荷レベルが上昇しました。」がエキサイティング!
レスポンスが遅いため負荷レベルを上げられませんでした。/message?channel_id=7466&last_message_id=0
やエラーが発生したため負荷レベルを上げられませんでした。2017-10-22 20:57:58.647006618 +0900 JST m=+31.152077525 リクエストがタイムアウトしました (POST /profile )
といった具体的なアドバイス
問題についても、icons をクリアしたら、さらに次の課題が出てくるような形で、なかなかのスルメゲーでした。これまでの ruby 実装とは違って unicorn ではなく puma、mysql2-cs-bind
ではなく mysql2
だったので、調べ物に少し時間がかかってしまいました。
また、参加者連絡が discord になったわけですが、蓋を開けてみれば isubata ってのもクスリと来ました。
運営、出題の方々、ありがとうございました。残念ながら予選落ちしてしまいましたが、来年も参加したいです。いや、参加します。
反省会 #isucon pic.twitter.com/B5W42nPhHB
— りちゃ (@rch850) 2017年10月22日
ESP WROOM 32 でブザするまでのメモ
ちょっと作りたいおもちゃを思いついたので、@kimikato先生に何で作ったらいいかおすすめを聞いたところ ESP 32 とか 02 あたりがいいよと聞いたので、さっそく買ってみました。秋月の ESP32-DevKitC ESP-WROOM-32 開発ボードです。
といったところが売りらしいです。
さっそくマイクロ USB でつないで Arduino から書き込もうとしたのですが、「ツール」>「ボード」の選択肢に ESP らしきものが見当たりませんでした。GitHub の arduino-esp32 のドキュメントを見たところインストール手順が書いてあったので、それに従ったところボードに ESP32 Dev Module などが出てきました。
これで書き込めると思ったのですが、
ボード/dev/cu.usbserial-A9007Ldzは利用できません
などというエラーが出て書き込めませんでした。どうも USB ドライバが必要そうです。あ、開発環境は macOS Sierra です。
この開発ボードについている USB 変換チップは CP2102 なので、「CP2102 ドライバ」と検索してヒットしたメーカーのサイトからドライバをダウンロードしてインストール。OS を再起動すると、無事に書き込めました。
Lチカで動作確認するのが普通なんでしょうが、手近にあったのがブザーだったので、刺して音がするのを確認しました。
ESP 32 手軽で楽しそうです!
生産管理部業務効率課進捗課長で MA 2nd 進出決めました
Mashup Award の福井ハッカソン予選で「生産管理部業務効率課 進捗課長」という作品を作り、最優秀賞を取り、2nd Stage 進出が決まりました。やった!先にソフトバンクロボティクス賞で名前が呼ばれたときは、あ、これ最優秀逃したかなーと思ったのですが、まさかのダブル受賞でした。
イベントの状況は公式のレポートに譲るとして、ここには自分がやったことなどを書いておきます。
自分が担当したのは、Pepper が録画、録音したデータを結合して、ハイライト動画、音声を作るところです。発表スライドの役割分担書く時に「自分は映像クリエイターで」って答えたら、他のメンバーもそれっぽい役割名で書く流れになってしまいました。
Pepper での録画、録音
Record Video ボックスで録画、Record Sound ボックスで録音しました。実際のコーディング、というかコレグラフでのボックスの配置は @pittanko_pta にやってもらいました。
動画と音声の結合
3人分の動画、音声を 1.avi、2.avi、3.avi といった名前で保存したので、それを結合しました。動画、音声の操作といえば ffmpeg。Pepper の内部で ffmpeg を叩けることが分かったので、別サーバに投げたりすることなく処理ができました。動画も音声も、結合には ffmpeg -f concat を使いました。なお出力形式は動画は mp4、音声は wav にしました。
結合した音声には ffmpeg -y -i sound_tmp.wav -i bgm.wav -filter_complex amerge -ac 2 -q:a 4 sound.wav
といったコマンドで BGM もつけました。
映像と音声をくっつけず別々にしてる理由はすぐあとで書きます。
Pepper での動画、音声の再生
Play Video ボックスで動画を、Play Sound ボックスで音声を再生しました。最初は音声がついた動画を Play Video で再生するだけでいいと考えたのですが「Pepper 動画 音声」で調べたところ動画に音声を乗せるとノイズが入ることがあるという話を見かけたので、デモでの成功率を考えて個別に扱うことにしました。こちらもボックスの配置は @pittanko_pta の担当だったのですが、中身の調査は自分がやりました。
中身の調査というのは、作成した動画をどうやったら Play Video で再生できるのかという話です。ボックスのコードを読んだところ、ローカルのファイルは "http://%s/apps/%s" % (self.tabletService.robotIp(), subpath.replace(os.path.sep, "/"))
といった形の URL で読み込んでいるようで、この apps は一体どこなのかを探す必要がありました。Pepper の中身を ps コマンドで見たところ、http サーバは nginx だったので、/etc/nginx/nginx.conf
を見て apps が /opt/aldebaran/www/apps
を指していることがわかりました。というわけで再生する動画はそのパスに置くことにしました。たしか nao ユーザーで書き込める場所だったはずです。Pepper の中に ssh できるのは便利ですね。
Play Sound のほうは普通にパスを指定すれば再生できたので、特に困りませんでした。
Play Video と Play Sound を同時に動かすと、Play Video のほうが数秒遅れて再生が始まるので、絶妙なウェイトを入れて同時に再生されるような工夫もしています。
まとめ
以上が映像クリエイターの仕事でした。ハイライト動画にイントロを付けたいとか、テロップを入れたいとか、いろいろやりたいことがあるので、今後もネタに困ることはなさそうです。
Angular の routerLink でクエリパラメータを指定する
Angular 4.2.2 の話です。
<a routerLink="/foo?bar=10">link</a>
のように routerLink
にクエリパラメータを直接指定すると Error: Uncaught (in promise): Error: Cannot match any routes. URL Segment: 'foo%3Fbar%3D10'
といったエラーが出ます。
RouterLink のリファレンスにあるように [queryParams]
にオブジェクトを指定すれば動作するようになります。
<a routerLink="/foo" [queryParams]='{ bar: 10 }'>link</a>
routerLink
がダメなら href
で……というのも試してみたのですが、これだと遷移するにはするのですが、ページ全体の再読込がかかってしまいました。
<a href="/foo?bar=10">link</a>
基本的にはちゃんと動く書き方をすればいいのですが、ウェブ API から受け取ったリンク先に遷移させたい場合は、API から受け取った文字列をパースして routerLink
と queryParams
に設定するといった実装が必要になってきます。これは手間です。このあたりを賢くやってくれる方法があればいいのですが。
Chrome 59 で window.open の挙動が変わった
JavaScript で新しいウィンドウを開くため、このようなコードを書いていたのですが、Chrome 59 になってから新しいタブで開くようになってしまいました。
window.open('http://example.com/', '_blank', 'width=640, height=480, location=yes')
window.open の第3引数
feature から、アドレスバーを表示するためのオプション location=yes
を取り除くことで、タブではなくウィンドウで開くようになりました。
window.open('http://example.com/', '_blank', 'width=640, height=480')
こうなった原因を探るため Chrome 59 のコミットログ を window.open
で検索したところ window.open() should gate new tab/new popup based on toolbar visibility. (e507bb3) が関係してそうでした。
window.open() should gate new tab/new popup based on toolbar visibility.
Previously, Chrome required that toolbar, menubar, scrollbars, status, resizable were all set to enabled to open a window as a new tab rather than a new popup. However, this causes developer frustration if one of window features is accidentally omitted (as it then defaults to disabled).
Instead, just use toolbar visibility to determine whether or not window.open() creates a new popup or a new tab, which matches Firefox.
なるほど。toolbar についても調べたところ、確かに設定次第でポップアップかどうかが変わりました。
// 新しいタブになる window.open('http://example.com/', '_blank', 'width=640, height=480, toolbar=yes') // 新しいウィンドウ(ポップアップ)になる window.open('http://example.com/', '_blank', 'width=640, height=480, toolbar=no') window.open('http://example.com/', '_blank', 'width=640, height=480')
Chrome 59 のソースを調べたことのメモ
feature をパースしているのは WindowFeatures.cpp のようです。
どこかに location を toolbar として見るコードがあるはずですが、見つけられませんでした。
(6月20日追記)
location=yes
の有無でどうなるか、IE 11, Edge, 14, Chrome 59, Firefox 54 で動作確認しました。
結果はリンク先を見ての通りですが、特に挙動が違ったところとして IE 11 だけ location=yes の時にアドレスバーの中身を編集できました。
oEmbed の height null について
きっかけは mastodon の URL 貼り付けを確認してたときにびろーんと伸びてしまうのに気づいたこと。
引用ここまで。めっちゃ改行入れてるわけじゃなくて、ここまでびろーんって伸びちゃってるんです。
逆に、長いトゥートは途切れてしまう。
なぜこうなるか mastodon のコードを追ってみた。対象はタグ v1.3.2 のもの。
oEmbed の実装は oembed_controller.rb にあって、特別な指定がなければ height が 640 となる。
def show @stream_entry = stream_entry_from_url(params[:url]) @width = params[:maxwidth].present? ? params[:maxwidth].to_i : 400 @height = params[:maxheight].present? ? params[:maxheight].to_i : 600 end
https://github.com/tootsuite/mastodon/blob/v1.3.2/app/controllers/api/oembed_controller.rb#L9
height というのは oEmbed の仕様にあって rich タイプでは必須となっている。
height (required)
The height in pixels required to display the HTML.
この高さが 640 と固定されているので、びろーんと伸びてしまったり、途切れてしまったりするわけだ。
Twitter がどうしているか調べてみたら、height には null が入っていた。instagram も同様に null だった。
じゃぁ mastodon でも null 返せばいいのか?ってことで、自分の mastodon インスタンスで show メソッドを書き換えて null になるようにしたら、いい感じにフィットするようになった。
なるにはなったけど required って明記されているものに対して null を返すのはちょっと抵抗あるなー。本家に PR 出しても苦い顔されそうだし、自分がその立場なら苦い顔しそう。
なお今回の調査では iframely のデバッガにとてもお世話になりました。
(5/9 追記) 出すだけ出してみようってことで PR 出したらすぐマージされましたとさ。ウワサには聞いてたけど対応速かった!