⌨️

自作キーボードのキーマップ最適化のためにキー入力分析基盤を作ってみた(後編)

2024/12/27に公開

この記事は、tacoms Advent Calendar 2024の22日目です!
他メンバーのAdvent Calendarはこちらからご覧ください!👇
https://qiita.com/advent-calendar/2024/tacoms

前回の続き

自作キーボード Keyball44 のキーマップの最適化のために、キー入力データを収集・分析する基盤を構築します。

前編ではデータの収集のための Go 言語でキーロガーを実装し、LaunchAgents を利用してバックグラウンドで起動させ、キー入力がログファイルに出力されることまで確認できました。
https://zenn.dev/tacoms/articles/7c35fdb78ee16e

今回の後編では、ELKスタックを構築し、ログファイルを取り込んで Kibana で可視化していきます!

ELK スタックとは

Elasticsearch、Logstash、Kibana の3つを組み合わせて構築するログ分析のプラットフォームのことです。
最近ではここに Beats を加えて ELK-B やその他プロダクトも加えて Elastic Stack と呼ばれているようです。[1]

今回は Beats は利用していないので、引き続き ELK スタックと呼びます。

  • Elasticsearch
    • 言わずと知れた分散型の検索・分析エンジン。
    • データの保存と検索を行う。
  • Logstash
    • データの収集・変換・送信を行うツール。
    • 今回はログファイルを Elasticsearch に送信する役割。
  • Kibana
    • データの可視化・分析を行うツール。
    • 今回は Elasticsearch に接続し収集したログを元に可視化を行う。

ELK スタックの構築

Elasticsearch, Kibana は docker で、 Logstash は macos に直接インストールします。

当初 Logstash についてもコンテナで動作させていたのですが、ログファイルがコンテナに同期されるのが遅くリアルタイムで反映できなかったため、macos上で直接起動するようにしました。[2]

Elastcsearch, Kibana

公式ドキュメントの通りに docker-compose.yml を作成すれば簡単に起動できます。
ただし、今回は以下の点を変更しています。

  • ローカルで動作させるだけなのでクラスタではなくシングルノード
  • Logstash がリアルタイムにログを読み込めないので、コンテナではなくホストマシンで動作させるためにサービスから除外
docker-compose.yml

XPack のセキュリティの関係で長々としていますが、証明書やユーザーの作成処理です。

docker-compose.yml
services:
  setup:
    image: docker.elastic.co/elasticsearch/elasticsearch:${STACK_VERSION}
    volumes:
      - "./certs:/usr/share/elasticsearch/config/certs"
    user: "0"
    command: >
      bash -c '
        if [ x${ELASTIC_PASSWORD} == x ]; then
          echo "Set the ELASTIC_PASSWORD environment variable in the .env file";
          exit 1;
        elif [ x${KIBANA_PASSWORD} == x ]; then
          echo "Set the KIBANA_PASSWORD environment variable in the .env file";
          exit 1;
        fi;
        if [ ! -f config/certs/ca.zip ]; then
          echo "Creating CA";
          bin/elasticsearch-certutil ca --silent --pem -out config/certs/ca.zip;
          unzip config/certs/ca.zip -d config/certs;
        fi;
        if [ ! -f config/certs/certs.zip ]; then
          echo "Creating certs";
          echo -ne \
          "instances:\n"\
          "  - name: es01\n"\
          "    dns:\n"\
          "      - es01\n"\
          "      - localhost\n"\
          "    ip:\n"\
          "      - 127.0.0.1\n"\
          > config/certs/instances.yml;
          bin/elasticsearch-certutil cert --silent --pem -out config/certs/certs.zip --in config/certs/instances.yml --ca-cert config/certs/ca/ca.crt --ca-key config/certs/ca/ca.key;
          unzip config/certs/certs.zip -d config/certs;
        fi;
        echo "Setting file permissions"
        chown -R root:root config/certs;
        find . -type d -exec chmod 750 \{\} \;;
        find . -type f -exec chmod 640 \{\} \;;
        echo "Waiting for Elasticsearch availability";
        until curl -s --cacert config/certs/ca/ca.crt https://es01:9200 | grep -q "missing authentication credentials"; do sleep 30; done;
        echo "Setting kibana_system password";
        until curl -s -X POST --cacert config/certs/ca/ca.crt -u "elastic:${ELASTIC_PASSWORD}" -H "Content-Type: application/json" https://es01:9200/_security/user/kibana_system/_password -d "{\"password\":\"${KIBANA_PASSWORD}\"}" | grep -q "^{}"; do sleep 10; done;
        echo "All done!";
      '
    healthcheck:
      test: ["CMD-SHELL", "[ -f config/certs/es01/es01.crt ]"]
      interval: 1s
      timeout: 5s
      retries: 120

  es01:
    depends_on:
      setup:
        condition: service_healthy
    image: docker.elastic.co/elasticsearch/elasticsearch:${STACK_VERSION}
    volumes:
      - "./certs:/usr/share/elasticsearch/config/certs"
      - esdata01:/usr/share/elasticsearch/data
    ports:
      - ${ES_PORT}:9200
    environment:
      - node.name=es01
      - cluster.name=${CLUSTER_NAME}
      - discovery.type=single-node
      - ELASTIC_PASSWORD=${ELASTIC_PASSWORD}
      - bootstrap.memory_lock=true
      - xpack.security.enabled=true
      - xpack.security.http.ssl.enabled=true
      - xpack.security.http.ssl.key=certs/es01/es01.key
      - xpack.security.http.ssl.certificate=certs/es01/es01.crt
      - xpack.security.http.ssl.certificate_authorities=certs/ca/ca.crt
      - xpack.security.transport.ssl.enabled=true
      - xpack.security.transport.ssl.key=certs/es01/es01.key
      - xpack.security.transport.ssl.certificate=certs/es01/es01.crt
      - xpack.security.transport.ssl.certificate_authorities=certs/ca/ca.crt
      - xpack.security.transport.ssl.verification_mode=certificate
      - xpack.license.self_generated.type=${LICENSE}
    mem_limit: ${MEM_LIMIT}
    ulimits:
      memlock:
        soft: -1
        hard: -1
    healthcheck:
      test:
        [
          "CMD-SHELL",
          "curl -s --cacert config/certs/ca/ca.crt https://localhost:9200 | grep -q 'missing authentication credentials'",
        ]
      interval: 10s
      timeout: 10s
      retries: 120
  kibana:
    depends_on:
      es01:
        condition: service_healthy
    image: docker.elastic.co/kibana/kibana:${STACK_VERSION}
    volumes:
      - "./certs:/usr/share/kibana/config/certs"
      - kibanadata:/usr/share/kibana/data
    ports:
      - ${KIBANA_PORT}:5601
    environment:
      - SERVERNAME=kibana
      - ELASTICSEARCH_HOSTS=https://es01:9200
      - ELASTICSEARCH_USERNAME=kibana_system
      - ELASTICSEARCH_PASSWORD=${KIBANA_PASSWORD}
      - ELASTICSEARCH_SSL_CERTIFICATEAUTHORITIES=config/certs/ca/ca.crt
    mem_limit: ${MEM_LIMIT}
    healthcheck:
      test:
        [
          "CMD-SHELL",
          "curl -s -I http://localhost:5601 | grep -q 'HTTP/1.1 302 Found'",
        ]
      interval: 10s
      timeout: 10s
      retries: 120
  # ログファイルがリアルタイムで読み込めなかったため、コンテナで起動せずホストマシンに直接インストールしました
  # logstash01:
  #   depends_on:
  #     es01:
  #       condition: service_healthy
  #     kibana:
  #       condition: service_healthy
  #   image: docker.elastic.co/logstash/logstash:${STACK_VERSION}
  #   labels:
  #     co.elastic.logs/module: logstash
  #   user: root
  #   volumes:
  #     - certs:/usr/share/logstash/certs
  #     - logstashdata01:/usr/share/logstash/data
  #     - "./keylogger/log/:/usr/share/logstash/ingest_data/"
  #     - "./logstash/logstash.conf:/usr/share/logstash/pipeline/logstash.conf:ro"
  #   environment:
  #     - xpack.monitoring.enabled=false
  #     - ELASTIC_USER=elastic
  #     - ELASTIC_PASSWORD=${ELASTIC_PASSWORD}
  #     - ELASTIC_HOSTS=https://es01:9200
volumes:
  # certs:
  #   driver: local
  esdata01:
    driver: local
  kibanadata:
    driver: local
  # logstashdata01:
  #   driver: local

また、 docker-compose.yml で利用する環境変数を .env に記載しておきます。

.env
ELASTIC_PASSWORD=password
KIBANA_PASSWORD=password
STACK_VERSION=8.16.1
CLUSTER_NAME=docker-cluster
LICENSE=basic
ES_PORT=9200
KIBANA_PORT=5601
MEM_LIMIT=1073741824

docker-compose.yml, .env を作成したら docker compose up で起動します。
http://localhost:5601 にアクセスすると Kibana のログイン画面が確認できます!

ログイン情報は Username が elastic, パスワードが .env に設定した ELASTIC_PASSWORD の値になります。
ちょっとまぎらわしいのですが、 kibanaKIBANA_PASSWORD は Kibana が Elasticsearch に接続する際の情報なので Kibana へのログインはできないので注意が必要です。

無事ログインできてもデータがまだないので Logstash を設定して keylogger で収集したキー入力を Elastcsearch に送信し、Kibana から閲覧できるようにします。

Logstash のインストール

こちらは先述の通り Mac にインストールします。

公式サイトから一式をダウンロードしてもよいですし、 Homebrew でもインストールできます。
今回は一式をダウンロードしてパスを通しました。

Logstash の設定

次にログファイルを読み込んで Elasticsearch に送信する設定ファイルを作成します。

input, filter, output など各項目は公式ドキュメントを参考に設定します。

logstash.conf
input {
  file {
    path => "/Users/xxx/dev/github.com/momiom/keylogger-visualization/keylogger/log/key.log"
    start_position => "end"
    sincedb_path => "/dev/null"
    codec => json
  }
}

filter {
  date {
    match => [ "timestamp", "ISO8601" ]
    target => "@timestamp"
  }
}

output {
  elasticsearch {
    hosts => ["https://localhost:9200"]
    user => "elastic"
    password => "password"
    index => "keylogger-%{+YYYY.MM.dd}"
    ssl => true
    ssl_certificate_verification => true
    cacert => "/Users/xxx/dev/github.com/momiom/keylogger-visualization/certs/ca/ca.crt"
  }
  stdout { codec => rubydebug }
}

今回は基本的にログをそのまま Elasticsearch の keylogger-{日時} のインデックスに出力しています。

timestamp だけはこちらで設定しておきました。
Elasticsearch 側で変更することもできますが、この時点で変換してしまったほうが簡単だと思います。

設定ファイルを作成したら以下のコマンドで起動します。

> logstash -f ./logstash.conf

output の設定に stdout も追加しているので、keylogger を起動していればキーを押下するたびに送信されるログが画面に流れます。

前編で keylogger をバックグラウンドで起動するために作成・設定した plist と同様に Logstash を起動するようにしておくと便利でしょう。

データの確認

実際に Elasticsearch に収集されたデータを確認してみます。

改めて整理すると以下のプログラムやサービスが起動している必要があります。

  • 前編で実装した keylogger を起動
  • Elasticsearch, Kibana のサービスを起動
  • Logstash を実行

Elasticsearch の API を実行してもよいのですが、簡単のため Kibana から確認します。

Kibana にログインしメニューから Discover を選択します。
ここではクエリを発行してデータを確認ができます。

サイドメニューからか Discover を選択

まずは Data View を作成します。
左上の Data View から Create a data view を選択。

Create a data view を選択

Logstash の設定でインデックス名に keylogger-%{+YYYY.MM.dd} を指定したので keylogger-2024.12.14 のようなインデックスが作成されています。
インデックスは日毎に作成されるので、Index pattern には keylogger-* のようにワイルドカードで指定します。
Timestamp は Index pattern を指定してからフォーカスすると自動で選択されます。
Name に任意の値をいれて保存します。

さきほど作成した Data View を選択し、右上のカレンダーから取得期間を設定します。
試しに1時間前から現在までを指定してみると以下のように表示されました!

Kibana による可視化

データが揃ったので可視化していきます。
Kibana で可視化を行う方法はたくさんあるのですが、その代表的な入口が Dashboard と Canvas です。

Dashboard

Dashboard では、複数の可視化やデータを1つの画面にまとめて表示できます。アプリケーションの監視やBIツールなどでよく目にすると思います。

Dashboards | Kibana Guide [8.17] | Elastic より引用

Canvas

Canvas は、よりカスタマイズ性の高いプレゼンテーション向けの可視化ツールです。
以下は走行中のマシンからのデータをリアルタイムで可視化している例です。

Canvas ― インフォグラフィックスタイルのライブプレゼンテーション | Elastic より引用

Dashboard で可視化

まずは Dashbord を作成してみました。

この記事を書いている最中のキー入力。タグクラウドでは不動の一位であろう backspace がわかりやすく鎮座しています。

簡単な可視化ならクエリを書くこともなく画面に従ってポチポチしていくだけで作成できます。

Canvas で可視化

次に Canvas を利用してみます。
メニューから Canvas を選択し、新たに Workpad を作成します。
まっさらな画面が表示されるので、要素を追加してみます。

Add element の Chart から Metric を選択しました。

左に表示されるパネルから設定していきます。
まずは Data タブで値を取得します。今回は Elastic SQL を選択しました。
以下のクエリで Preview data を実行するとデータを取得できます。

SELECT * FROM "keylogger-*"

公式ドキュメントに細かな仕様はありますが、RDB で利用する SQL とだいたい同じです。
例えば以下のようにすれば、すべての期間を対象にレコード数を取得できます。

SELECT count(event.keyname) FROM "keylogger-*"

クエリを保存し、Dispaly タブに移動して、Measure の Value を SELECT したフィールドに設定し、"Totla Typed" という Label を設定しました。
これですべてのキー入力の合計が表示できました。

同じ要領でいくつか Metric を追加してみます。

Typed / minutes : 1分間あたりのキー入力数

SELECT 
  DATE_TRUNC('minute', "@timestamp") as minute, 
  COUNT(*) as keys_per_minute 
FROM 
  "keylogger-*" 
GROUP BY 
  DATE_TRUNC('minute', "@timestamp")

Effective Typed / minutes : backspace, delete を除いた1分間あたりのキー入力数

SELECT 
  DATE_TRUNC('minute', "@timestamp") as minute, 
  COUNT(*) as keys_per_minute 
FROM 
  "keylogger-*" 
WHERE
  event.keyname != 'backspace' 
  and event.keyname != 'delete' 
GROUP BY 
  DATE_TRUNC('minute', "@timestamp")

それぞれ作成して配置すると以下のようになりました。

このままでは期間の指定ができないので、Filter を追加します。
Add element の Filter から Time filter を選択し配置しました。
ここで期間を指定するとすべてのクエリで期間が絞り込まれます。

この調子でさらに要素を追加していくとこんな感じになりました!

キーごとのカウントを取得する
装飾キーや制御キーは文字数が多いためクエリ内で記号に置き換えています。

SELECT CASE
         WHEN event.keyname IN ( 'shift-left', 'shift-right' ) THEN '⇧'
         WHEN event.keyname = 'tab' THEN '⇥'
         WHEN event.keyname = 'control' THEN '⌃'
         WHEN event.keyname = 'option' THEN '⌥'
         WHEN event.keyname IN ( 'command-left', 'command-right' ) THEN '⌘'
         WHEN event.keyname = 'backspace' THEN '⌫'
         WHEN event.keyname = 'enter' THEN '↵'
         WHEN event.keyname = 'arrow-left' THEN '←'
         WHEN event.keyname = 'arrow-right' THEN '→'
         WHEN event.keyname = 'arrow-up' THEN '↑'
         WHEN event.keyname = 'arrow-down' THEN '↓'
         WHEN event.keyname = 'space' THEN '□'
         WHEN event.keyname = 'esc' THEN '⎋'
         WHEN event.keyname = 'eisu' THEN '英'
         WHEN event.keyname = 'kana' THEN 'か'
         ELSE event.keyname
       END      AS display_key,
       Count(*) AS count
FROM   "keylogger-*"
GROUP  BY event.keyname
ORDER  BY count DESC 

Vega による複雑な可視化

ここまでである程度可視化できてきましたが、現状ではキー入力の頻度しかわかりません。
キーマップの最適化においては、高頻度で利用されているのに押しづらい位置にあるキーを知りたいところです。

そこで、ヒートマップのようにキー入力の頻度をカラースケールで表し、それらを実際のキー配列に合せて表示してみます。
Canvas 上でキーひとつひとつのカラースケールを配置するのは、作成もメンテも大変になるので、ここでは Vega を使って可視化し、Canvas に配置したいと思います。

Vega とは

Vega は JSON 形式で宣言的にインタラクティブなデザインを定義できる言語仕様とツールセットです。
公式サイトのExampleを見ていただくとわかる通り表現力はかなり高いです。

パックマンもできる。恐ろしく長い JSON を書けば。。

また、Vega-Light という仕様も存在します。
高い表現力ゆえに複雑になりがちな JSON の定義をより簡潔に記載することができます。

これらは独立して存在していて、Kibana をはじめ Elastic 社のプロダクトではありません。
JavaScript runtime API を使ってWebサイトや Node プロジェクトで利用することができます。

Vega-Light によるキー配列の可視化

今回は Vega-Light を使おうと思います。それでも表現力に欠ける場合は Vega の利用を考えればよいでしょう。

Canvas から Vega で可視化を行うには Select type から Custom visualization を選択します。

この画面で JSON の定義を記載していきます。

いきなりですが、完成したものがこちらです。

JSON の定義はこちらです。

JSON の定義
{J
  "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
  "width": 1040,
  "height": 400,
  "autosize": "none",
  "title": "Keyboard Input Heatmap",
  "data": {
    "url": {
      "%context%": true,
      "%timefield%": "event.timestamp",
      "index": "keylogger-*",
      "body": {
        "aggs": {
          "keys": {
            "terms": {
              "field": "event.keyname.keyword",
              "size": 999
            }
          }
        },
        "size": 0
      }
    },
    "format": {"property": "aggregations.keys.buckets"}
  },
  "transform": [
    {
      "calculate": "datum.key == 'shift-left' || datum.key == 'shift-right' ? '⇧' : datum.key == 'tab' ? '⇥' : datum.key == 'control' ? '⌃' : datum.key == 'option' ? '⌥' : datum.key == 'command-left' || datum.key == 'command-right' ? '⌘' : datum.key == 'backspace' ? '⌫' : datum.key == 'enter' ? '↵' : datum.key == 'arrow-left' ? '←' : datum.key == 'arrow-right' ? '→' : datum.key == 'arrow-up' ? '↑' : datum.key == 'arrow-down' ? '↓' : datum.key",
      "as": "display_key"
    },
    {
      "lookup": "key",
      "from": {
        "data": {
          "values": [
            // Function key row (y: 400)
            {"key": "esc", "x": 0, "y": 400},
            {"key": "f1", "x": 80, "y": 400},
            {"key": "f2", "x": 160, "y": 400},
            {"key": "f3", "x": 240, "y": 400},
            {"key": "f4", "x": 320, "y": 400},
            {"key": "f5", "x": 400, "y": 400},
            {"key": "f6", "x": 480, "y": 400},
            {"key": "f7", "x": 560, "y": 400},
            {"key": "f8", "x": 640, "y": 400},
            {"key": "f9", "x": 720, "y": 400},
            {"key": "f10", "x": 800, "y": 400},
            {"key": "f11", "x": 880, "y": 400},
            {"key": "f12", "x": 960, "y": 400},

            // Number row (y: 320)
            {"key": "1", "x": 0, "y": 320},
            {"key": "2", "x": 80, "y": 320},
            {"key": "3", "x": 160, "y": 320},
            {"key": "4", "x": 240, "y": 320},
            {"key": "5", "x": 320, "y": 320},
            {"key": "6", "x": 400, "y": 320},
            {"key": "7", "x": 480, "y": 320},
            {"key": "8", "x": 560, "y": 320},
            {"key": "9", "x": 640, "y": 320},
            {"key": "0", "x": 720, "y": 320},
            {"key": "-", "x": 800, "y": 320},
            {"key": "^", "x": 880, "y": 320},
            {"key": "¥", "x": 960, "y": 320},
            {"key": "backspace", "x": 1040, "y": 320},

            // QWERTY row (y: 240)
            {"key": "tab", "x": 0, "y": 240},
            {"key": "q", "x": 80, "y": 240},
            {"key": "w", "x": 160, "y": 240},
            {"key": "e", "x": 240, "y": 240},
            {"key": "r", "x": 320, "y": 240},
            {"key": "t", "x": 400, "y": 240},
            {"key": "y", "x": 480, "y": 240},
            {"key": "u", "x": 560, "y": 240},
            {"key": "i", "x": 640, "y": 240},
            {"key": "o", "x": 720, "y": 240},
            {"key": "p", "x": 800, "y": 240},
            {"key": "@", "x": 880, "y": 240},
            {"key": "[", "x": 960, "y": 240},
            {"key": "enter", "x": 1040, "y": 240},

            // Home row (y: 160)
            {"key": "control", "x": 0, "y": 160},
            {"key": "a", "x": 80, "y": 160},
            {"key": "s", "x": 160, "y": 160},
            {"key": "d", "x": 240, "y": 160},
            {"key": "f", "x": 320, "y": 160},
            {"key": "g", "x": 400, "y": 160},
            {"key": "h", "x": 480, "y": 160},
            {"key": "j", "x": 560, "y": 160},
            {"key": "k", "x": 640, "y": 160},
            {"key": "l", "x": 720, "y": 160},
            {"key": ";", "x": 800, "y": 160},
            {"key": ":", "x": 880, "y": 160},
            {"key": "]", "x": 960, "y": 160},

            // Bottom row (y: 80)
            {"key": "shift-left", "x": 0, "y": 80},
            {"key": "z", "x": 100, "y": 80},
            {"key": "x", "x": 180, "y": 80},
            {"key": "c", "x": 260, "y": 80},
            {"key": "v", "x": 340, "y": 80},
            {"key": "b", "x": 420, "y": 80},
            {"key": "n", "x": 500, "y": 80},
            {"key": "m", "x": 580, "y": 80},
            {"key": ",", "x": 660, "y": 80},
            {"key": ".", "x": 740, "y": 80},
            {"key": "/", "x": 820, "y": 80},
            {"key": "shift-right", "x": 1040, "y": 80},

            // Space bar row (y: 0)
            {"key": "option", "x": 80, "y": 0},
            {"key": "command-left", "x": 160, "y": 0},
            {"key": "eisu", "x": 240, "y": 0},
            {"key": "space", "x": 400, "y": 0},
            {"key": "kana", "x": 560, "y": 0},
            {"key": "command-right", "x": 700, "y": 0},
            {"key": "fn", "x": 0, "y": 0},
            {"key": "arrow-left", "x": 820, "y": 0},
            {"key": "arrow-up", "x": 900, "y": 80},
            {"key": "arrow-down", "x": 900, "y": 0},
            {"key": "arrow-right", "x": 980, "y": 0}
          ]
        },
        "key": "key",
        "fields": ["x", "y"]
      }
    }
  ],
  "layer": [
    {
      "mark": {"type": "circle", "size": 2000},
      "encoding": {
        "x": {
          "field": "x",
          "type": "quantitative",
          "scale": {"domain": [-20, 1060]}
        },
        "y": {
          "field": "y",
          "type": "quantitative",
          "scale": {"domain": [-20, 420]}
        },
        "color": {
          "field": "doc_count",
          "type": "quantitative",
          "scale": {
            "type": "linear",
            "scheme": "lightgreyred",
            "domain": [0, 1500]
          }
        },
        "tooltip": [
          {"field": "key", "type": "nominal", "title": "Key"},
          {"field": "doc_count", "type": "quantitative", "title": "Count"}
        ]
      }
    },
    {
      "mark": {
        "type": "text",
        "baseline": "middle",
        "align": "center",
        "fontSize": 12
      },
      "encoding": {
        "x": {"field": "x", "type": "quantitative"},
        "y": {"field": "y", "type": "quantitative"},
        "text": {"field": "display_key", "type": "nominal"},
        "color": {
          "condition": {
            "test": "datum.doc_count > 750",
            "value": "white"
          },
          "value": "black"
        }
      }
    }
  ],
  "config": {
    "view": {"stroke": null},
    "axis": {"grid": false, "labels": true, "ticks": true, "domain": false}
  }
}

Vega-Light とはいえ中々の長さになります。。
詳細な解説は公式サイトのチュートリアルドキュメントを参照いただければと思いますが、ざっくりと以下のような設定をしています。

  • data
    • Elasticsearch のインデックスを指定
  • transform
    • 装飾キーと制御キーを記号に置き替え
    • 各キーの座標設定
  • layer
    • 表示のシェイプ形状
    • カラースケールの指定

キーの座標設定の部分は気合でがんばるか AI 様に作っていただきましょう。
私は Sonet3.5 様に依頼して出てきたファイルを微調整して作成しました。[3]
正規表現で置換等できるので、ドラッグ・アンド・ドロップで40個以上の要素を配置するよりはいいですね。

完成したもの


過去1年間のデータを表示

無事キー入力を取得して Elasticsearch に蓄積し、Kibana で可視化することができました!

ぱっと見ても backspace の頻度が尋常ではないですね。
私の利用しているキーボードには親指キーが存在するため、多用している割に遠い位置にある backspace は親指キーに移したほうが良さそうです。

また、最も押しやすいキーのひとつである F キーが活用できていないことも分ります。
F キーにはタップダンス(長押し)でレイヤーの切り変えなどを割り当てると効率的かもしれません。

一度実行してしまえば大量のデータが簡単に手に入るので、直接データを使った分析にも利用できそうです!

みなさんもこれを機会に今一度キーマップを見直してみてはいかがでしょうか??
効率的なキーマップで快適なエンジニアライフを送れることを願っています!😊

脚注
  1. https://www.elastic.co/jp/elastic-stack ↩︎

  2. ボリューム・マウント(共有ファイルシステム)のためのパフォーマンス・チューニング — Docker-docs-ja 19.03 ドキュメントなどを参考に volume のオプションに delegated を指定するなどしましたが、そもそも数十MBのテキストファイルなのでしかたないのかもしれません。そもそもちゃんとローテーションすべきですね。。 ↩︎

  3. キーボードの画像を渡してこの通りに設定ファイルを作ってくれとお願いしてみましたがうまくいきませんでした。一般的なQWERTY配列のキーボードのように、と頼んだらある程度それっぽいものが作成されたのでそれをベースに修正しました。 ↩︎

tacomsテックブログ

Discussion