技術的な話題 に関する投稿を表示しています

視認しにくいテキストを探す | 最終回 puppeteer を使って任意の web サイト上のテキストに対してコントラストレートを計算する

前回までは色差をいい感じに計算して、じゃあどれくらい近いんですか!を感覚で設定していました。
視認しにくいテキストを探す | その 2 puppeteer を使って任意の web サイト上のテキストに対して色差を計算する | ごみばこいん Blog

でもそういうのって誰かが研究した成果だったり指針が一定あるはずで、それがアクセシビリティという形でまとまっているのです。

Web Content Accessibility Guidelines (WCAG) 2.0

コントラストレート

ところで、いま Chrome の DevTools を見ると、文字色のカラーピッカーにコントラストレートという情報が増えていることに気づきました。

このコントラストレートは、アクセシビリティの話の中でも出てきます。

Web Content Accessibility Guidelines (WCAG) 2.0 - 1.4.3 Contrast (Minimum)

WebAIM: WebAIM's WCAG 2 Checklist

おおざっぱにいうと、背景と文字とのコントラスト比が 3:1 とか 4.5:1 を超えないと見にくいテキストですよね、という判断のようです。

というわけで、今回はコントラストレートを使って、見にくいテキストを探してみます。

Chrome 上の実装

ところで Chrome ではコントラストレートの計算はどのように実装されているのでしょうか。
透明度もいい感じになっているのでしょうか。
その謎を知るためにソースコードへ飛び立ちます。

Code Search

ソースの検索が提供されているので、ここで、画面上にでている文言をさがして追いかけていきます。
検索してみるとそれっぽいソースが出てきました。

https://cs.chromium.org/chromium/src/third_party/blink/renderer/devtools/front_end/color_picker/ContrastDetails.js?q=contrast+ratio+lang:%5Ejavascript$&sq=package:chromium&dr=CSs&l=5

ここからコントラストレート AA の判定を追ってみます。

https://cs.chromium.org/chromium/src/third_party/blink/renderer/devtools/front_end/color_picker/ContrastDetails.js?sq=package:chromium&dr=CSs&g=0&l=121

const aa = this._contrastInfo.contrastRatioThreshold('aa');
this._passesAA = this._contrastInfo.contrastRatio() >= aa;

this._contrastInfo.contrastRatioThreshold('aa') が AA のコントラストレート基準値を持ってきているようです。
this._contrastInfo.contrastRatio() は指定された要素のコントラストレートを計算していそうです。

まずは this._contrastInfo.contrastRatioThreshold から追ってみます。

https://cs.chromium.org/chromium/src/third_party/blink/renderer/devtools/front_end/color_picker/ContrastInfo.js?sq=package:chromium&dr=CSs&g=0&l=147

contrastRatioThreshold(level) {
  if (!this._contrastRatioThresholds)
    return null;
  return this._contrastRatioThresholds[level];
}

this._contrastRatioThresholds という配列から値を取っているようです。
level は'aa'が入ってきます。

this._contrastRatioThresholds が設定されるところを見てみます。

https://cs.chromium.org/chromium/src/third_party/blink/renderer/devtools/front_end/color_picker/ContrastInfo.js?sq=package:chromium&dr=CSs&g=0&l=32

/**
 * @param {?SDK.CSSModel.ContrastInfo} contrastInfo
 */
update(contrastInfo) {
  ...

  if (contrastInfo.computedFontSize && contrastInfo.computedFontWeight && contrastInfo.computedBodyFontSize) {
    this._isNull = false;
    const isLargeFont = ColorPicker.ContrastInfo.computeIsLargeFont(
        contrastInfo.computedFontSize, contrastInfo.computedFontWeight, contrastInfo.computedBodyFontSize);

    this._contrastRatioThresholds =
        ColorPicker.ContrastInfo._ContrastThresholds[(isLargeFont ? 'largeFont' : 'normalFont')];
  }

  ...
}

... で一部省略していますが、キモは残しています。

この update というメソッドは外から定期的に呼ばれるようです。どこから呼ばれるのかちょっとわからず。。察するに要素が更新されたり、値が更新されるときに呼ばれるものだと思います。

肝心の this._contrastRatioThresholds は ColorPicker.ContrastInfo._ContrastThresholds から取得して、
そのために isLargeFont の判定をしています。

isLargeFont の判定に必要な、引数で渡ってくる contrastInfo はSDK.CSSModel.ContrastInfo という型です。

そこを追ってみます。

https://cs.chromium.org/chromium/src/third_party/blink/renderer/devtools/front_end/sdk/CSSModel.js?sq=package:chromium&dr=CSs&g=0&l=644

/** @typedef {{backgroundColors: ?Array<string>, computedFontSize: string, computedFontWeights: string, computedBodyFontSize: string}} */
SDK.CSSModel.ContrastInfo;

んー、ちょっとよく分からないですね。
devtools の js サイドではなく、もっとネイティブ側でもっている値なのでしょうか。

ある要素に対するすべての背景色と、フォントサイズ、フォントの太さ、本文フォントサイズが入っているオブジェクトのようです。

続いて isLargeFont の計算をしている箇所です。

https://cs.chromium.org/chromium/src/third_party/blink/renderer/devtools/front_end/color_picker/ContrastInfo.js?sq=package:chromium&dr=CSs&g=0&l=159

static computeIsLargeFont(fontSize, fontWeight, bodyFontSize) {
  const boldWeights = ['bold', 'bolder', '600', '700', '800', '900'];

  const fontSizePx = parseFloat(fontSize.replace('px', ''));
  const isBold = (boldWeights.indexOf(fontWeight) !== -1);

  const fontSizePt = fontSizePx * 72 / 96;
  if (isBold)
    return fontSizePt >= 14;
  else
    return fontSizePt >= 18;
}

先程の SDK.CSSModelContrastInfo から渡されるフォントサイズと太さの情報を使って、大きいフォント判定をします。
これはアクセシビリティのサイトに書いてあった通りのものです。

Web Content Accessibility Guidelines (WCAG) 2.0 - large scale (text)

そして ColorPicker.ContrastInfo._ContrastThresholds です。

https://cs.chromium.org/chromium/src/third_party/blink/renderer/devtools/front_end/color_picker/ContrastInfo.js?sq=package:chromium&dr=CSs&g=0&l=178

ColorPicker.ContrastInfo._ContrastThresholds = {
  largeFont: {aa: 3.0, aaa: 4.5},
  normalFont: {aa: 4.5, aaa: 7.0}
};

ラージフォントか通常かによって aa と aaa の基準値が設定されています。
こちらもアクセシビリティのサイトに書いてあったものです。

> The visual presentation of text and images of text has a contrast ratio of at least 4.5:1 except for the following: (Level AA)
> Large Text: Large-scale text and images of large-scale text have a contrast ratio of at least 3:1;

 

ここまでスレッショルドの取得は追いかけ終わりました。

次に this._contrastInfo.contrastRatio() を追います。

https://cs.chromium.org/chromium/src/third_party/blink/renderer/devtools/front_end/color_picker/ContrastInfo.js?sq=package:chromium&dr=CSs&g=0&l=81

contrastRatio() {
  return this._contrastRatio;
}

メソッドは単に getter になっているだけですね。

https://cs.chromium.org/chromium/src/third_party/blink/renderer/devtools/front_end/color_picker/ContrastInfo.js?sq=package:chromium&dr=CSs&g=0&l=140

this._contrastRatio = Common.Color.calculateContrastRatio(this._fgColor.rgba(), this._bgColor.rgba());

計算された値が設定されていそうです。
_fgColor と_bgColor は要素の color と background-color に当たるものでしょうか。

https://cs.chromium.org/chromium/src/third_party/blink/renderer/devtools/front_end/color_picker/ContrastInfo.js?sq=package:chromium&dr=CSs&g=0&l=12

/** @type {?Common.Color} */
this._fgColor = null;

/** @type {?Common.Color} */
this._bgColor = null;

コントラストレート計算の処理を追ってみます。

https://cs.chromium.org/chromium/src/third_party/blink/renderer/devtools/front_end/common/Color.js?dr=CSs&q=Common.Color&sq=package:chromium&g=0&l=364

/**
 * Calculate the contrast ratio between a foreground and a background color.
 * Returns the ratio to 1, for example for two two colors with a contrast ratio of 21:1, this function will return 21.
 * See http://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef
 * @param {!Array<number>} fgRGBA
 * @param {!Array<number>} bgRGBA
 * @return {number}
 */
static calculateContrastRatio(fgRGBA, bgRGBA) {
  Common.Color.blendColors(fgRGBA, bgRGBA, Common.Color.calculateContrastRatio._blendedFg);

  const fgLuminance = Common.Color.luminance(Common.Color.calculateContrastRatio._blendedFg);
  const bgLuminance = Common.Color.luminance(bgRGBA);
  const contrastRatio = (Math.max(fgLuminance, bgLuminance) + 0.05) / (Math.min(fgLuminance, bgLuminance) + 0.05);

  for (let i = 0; i < Common.Color.calculateContrastRatio._blendedFg.length; i++)
    Common.Color.calculateContrastRatio._blendedFg[i] = 0;

  return contrastRatio;
}

重たいロジックが出てきましたが、見るとリンクが付いています。
それを実装しているだけのようです。

Web Content Accessibility Guidelines (WCAG) 2.0 - contrast ratio

というところで、ここから先は追わなくてもいいかなと思ったので止めておきます。
ロジックのざっくりとした理解では、背景色と文字色をブレンドして、明るさを計算して、いい感じの係数を合わせたら出来上がり!という具合でしょうか。

 

ここまでで AA の判定をすることができます。
大きくやっていることは 2 つで、コントラストレートのしきい値を計算することと、実際のコントラストレートを計算すること、です。

実装してみる

該当のソースは devtool の中にいるので puppeteer からウマいこと利用する手立てはないと思います。
ので、前回までと同様に、開いたページに対してスクリプトを実行する形でやってみます。

前回もお世話になった chroma を使うと2つの色に対してコントラストレートを計算することができます。

chroma.js api docs! - chroma.contrast(color1 color2)

でもってソースはこんな感じで try puppeteer してみます。
(前回とほぼ同じソース)

// ブラウザの起動
const browser = await puppeteer.launch();
const page = await browser.newPage();
page.setViewport({
    width: 1600,
    height: 900,
})

// デバッグ用に console.log を nodejs 側に渡す
page.on('console', msg => console.log(msg.text()));

// サイトにアクセスする
await page.goto('https://gomiba.co.in/test/color_difference.html');

// 色差を計算するために chroma をページ内で読み込む
await page.addScriptTag({
    url: 'https://cdnjs.cloudflare.com/ajax/libs/chroma-js/1.3.6/chroma.min.js',
});

// 色差が一定以上のものを探す
await page.evaluate(() => {
    // 全要素を探索していく
    document.querySelectorAll('*').forEach((element) => {
        // テキストノード、または SVG を子に持っている要素を探す
        const foundChildNode = Array.prototype.filter.call(element.childNodes, (e) => {
            let status = false;
            status = status || (e.nodeType === Node.TEXT_NODE && e.textContent.trim().length > 0);
            status = status || e.nodeName.toLowerCase() === 'svg';
            return status;
        });
        if (foundChildNode.length === 0) {
            return;
        }

        // 計算されたスタイルから色を取得
        const elementStyle = window.getComputedStyle(element);
        const fontColor = elementStyle.color;
        const backgroundColor = elementStyle.backgroundColor;

        // コントラストレートを計算する
        const contrastRatio = chroma.contrast(fontColor, backgroundColor);

        // フォントサイズからコントラストレートのしきい値を決める
        const fontSizePx = parseInt(elementStyle.fontSize);
        const fontSizePt = fontSizePx * 72 / 96;
        const isLargeFont = fontSizePt >= 18;
        const contrastRatioThreshold =
              isLargeFont ? 3.0 : 4.5
      
        // しきい値を超えたもの = 見やすいものを色付けする
        console.log(element.nodeName + " : " + contrastRatio)
        if (contrastRatio > contrastRatioThreshold) {
          element.style.cssText = element.style.cssText + 'border: 5px dashed red !important;';
        }
    });
});


// スクリーンショットを撮ってみる
await page.screenshot({path: 'example.png'});

// ブラウザを終了する
await browser.close();

isLargeFont のところで太字判定をサボっていたりしますが、とりあえずのところは。
前回の記事では見にくい!を出していましたが、わからんので逆にして、見やすいものだけをマークするようにしました。

これを試すとこんな感じになります。

が、これがどうなのかわからない…、ので、前回の結果と見比べられるようにしました。

要素の左側に点線があるのが今回、要素の右側に点線があるのが前回の検出した見やすいものです。

こうしてみると、前回のときにチョット見やすいとはいいにくいなあというものも今回のでいい感じに検出されているような気がします。
とはいえ、前回の方法でもしきい値がかなりガバガバなので、もうちょっと詰めていくと、良い結果がでるんじゃないかなあと。

 

とりあえず、コントラストレートを計算してあげるのがアクセシビリティの確認方法として挙げられているので
手動でいい感じに検出するなら今回の方法でやるほうがよさそうです。

その他

lighthouse を使うとアクセシビリティ以外にも様々な観点について自動チェックできておすすめです。

GoogleChrome/lighthouse: Auditing performance metrics and best practices for Progressive Web Apps

今回はとりあえず色の様子だけシュッと見たかったので、単品で調査する方法がないかなあと取り組んでみたものです。

mapから指定したキーを検索するやつ

map[string]interface{} が入れ子になったものを扱うときがあって
たぶん、こういうなんだかわからないけど、いい感じに区分けされたデータ列があったときに使うんじゃないかな、きっと。
(前段階のアプローチがそもそも悪いかも)

{
  "case-1": {
    "a" : {
      "id" : 1
    },
    "b" : {
      "id" : 2
    }
  },
  "case-2": {
    "c" : {
      "id" : 3
    }
  }
}

その中から特定のキーをいい感じに取り出したいなあってなったので作ってみた。
GitHub - sters/maputils

 

余談になるけど
どこかで再帰呼び出しとループだと、ループのほうが速いよって書いてあったので、再帰的にキーを探すところはループでやってみた。

それでもって、ベンチも取ってみた。
gist:521e2be8b3aeed0c87fe75f7335f259e

謎な map の作り方してるけど、これくらいの規模感でやらないと、手元だとまったくもって差がでなかった。
逆にいえば、これくらいの規模感を取り扱う or これくらい負荷がかかるようになって初めて効果あるくらいなのかあという気持ち。


書いてて思ったけど []struct から特定のフィールドだけ取り出すほうが需要高そうだしもうありそう。

GoLand でカバレッジを表示した

結論 Java のそれなどと同じようにやれば良い

スクリーンショットが多くてわかりやすかった Q&A
Intellij Idea : view test coverage on a maven project - Stack Overflow

 

Cmd + Shift + A したときのコマンドパレットに Show Code Coverage Data というものがあり、これを開くとあたかもファイルを追加できそうにも見えるが go test -cover したときの結果を使うことはできないっぽい。

JetBrains 系エディタを通してカバレッジ計測した結果を表示するよ、というものみたい。

.proto を整形する

.proto ってどうやって整形するんだろう?って思ったら Issue がヒット。

proto file formatter · Issue #4113 · google/protobuf

clang-format を使えってことみたいなので、やってみる。


Mac なら brew で入る。

$ brew install clang-format
$ clang-format -i hoge.proto

JetBrains 系エディタなら clang-format プラグインがあって Reformat Code with clang-format というコマンドが増えるので、これを実行すると OK
内部的に clang-format を実行するので、実行できる状態になっている必要がある。

vscode にも同様の動きをするような Clang-Format という拡張機能があるのでこれを使うと整形が出来る。
と思ったが .proto には紐付いてないみたいなのでダメっぽい…?

--

とはいえ環境を汚したくないとか、何かしらの都合で入れられないとかあると思うので
Docker 使って make なりのタスクランナーから実行出来るようにしてあげるのがいいかな。

Dockerfile はこんな様子で。

FROM alpine:3.8

RUN apk add clang findutils

ENTRYPOINT find . -type f | xargs clang-format -i

run はこんなふうにやる。

$ docker run --rm -t -w /work -v "$(pwd)":/work clang-format

というのを試したリポジトリはココ。

sters/docker-clang-format-example: testrepo

ガタガタな proto も整えてくれる。

Before

syntax = "proto3";

package example;

import "google/protobuf/timestamp.proto";




service Example {
    rpc Get(GetRequest) returns (GetResponse) {};
}

message GetRequest {
    string fieldA;
            int fieldB;
google.protobuf.Timestamp fieldC;
}

message GetRequest {
string a;
    repeated google.protobuf.Timestamp b;
}

After

syntax = "proto3";

package example;

import "google/protobuf/timestamp.proto";

service Example {
  rpc Get(GetRequest) returns (GetResponse) {};
}

message GetRequest {
  string fieldA;
  int fieldB;
  google.protobuf.Timestamp fieldC;
}

message GetRequest {
  string a;
  repeated google.protobuf.Timestamp b;
}

Go 言語の練習に markdown のテーブルだけいい感じに調整するやつを作ってみた

作ったよ

sters/markdown-table-formatter: markdown-table-formatter

|hoge            |huga|
|--------------|------|
|its|confusing              |markdown|
|table   |so|crazy|table|.|

みたいなガタガタテーブルが書かれているテキストをキレイに揃えてくれるやつ。

|hoge |huga     |        |     | |
|-----|---------|--------|-----|-|
|its  |confusing|markdown|     | |
|table|so       |crazy   |table|.|

 

何も考えずにこんな要素が必要では?とテストを書いていって、それに合わせて実装をしていったので、同じことを何度もやっているような部分もあって、
アーキテクチャ的には再考の余地あり。

struct に中間データ的なものを閉じ込めてバケツリレーするのが簡単かなあ。
テストがいるので、ここからリファクタしがいありますね、って感じで。

 

書いてみて、ポインタとかまったく気にしなくて良いってことがわかってきた。
出番はほぼないのと、あっても波線出たときにどうにかする、ということだけを、とりあえず、それでよさそう。

リリースの配布

タグを設定すると Circle CI が動いてリリースが作られるようにしてみた。
(go get に必要なのかな?と思ったけどそういうわけではなかったみたい)

goreleaser を使ったリリースというタスクを作って、ワークフローで制御している。

goreleaser/goreleaser: Deliver Go binaries as fast and easily as possible

設定はここみて。

markdown-table-formatter/config.yml at master · sters/markdown-table-formatter

詰まったポイント

テスト時の assert を見やすくするために gopwt を使ってみたんですが、-cover したときにカバレッジが出なくなる問題があり、そこで無駄に詰まった。
gopwt のソースを調査するのはまたの機会にして、カバレッジ測定では gopwt を無効にした。

markdown-table-formatter/Makefile at master · sters/markdown-table-formatter

あと go get したときにそのままバイナリができる方法がわからなかった。ググってもそれっぽい回答が見つからない。。公式ドキュメントのどこかにいるのかなあ…

わからなかったので、リリースを作ってみたが違った ./main.go にして見たらバイナリが作られるようになった。
ディレクトリ切るとだめなんかな。いや、./formatter.go がいたからダメだったのかな、、
ドキュメントどこーー

そのほか

カバレッジ出ると楽しい。
100%にするのが完璧なテストというわけではないけど、そもそもココのテストできてなくね?とかが可視化されるの便利。

Go 言語に入門する

やるかやるか〜〜〜って思ってたけど、やっとやった。

A Tour of Go をやってみる

Go 言語の入門するにはとりあえず A Tour of Go をやると良いって噂を聞いたのでやってみる。

A Tour of Go

ブラウザ上で Go 言語のプログラムを書いて、そのまま実行できるスゴイやつ。
課題も途中で出てくるので、実際に動かしながら各種の理解を進められる。

とりあえずやってみての感想は interface と struct そして ポインタが絡むとよくわからんとなった。
もっとコード書いたらわかってくると思うので一旦雰囲気を理解するだけで。

あと標準パッケージがワカラン。このへんもやっていくしかない。

ただなんとなく、超便利有能関数群みたいなのが無いあたり、俺達のプログラムに必要なコードは俺達で組むんだ、みたいな意思を勝手に感じた。
パッケージは Github から直に入るのでどういうのがよく使われているのかまったくわかんない… awesome-go を見るのがとりあえずは良いかもしれない。

avelino/awesome-go: A curated list of awesome Go frameworks libraries and software

Go 言語のランタイムを入れる

Downloads - The Go Programming Language

Mac なら brew install go で入る。

GOPATH は設定してもしなくても良くて、設定しないと ~/go/ になる。特に移動する必要も感じなかったのでそのまま。
GOROOT は設定必要だよ!とか書いてある記事もあるが、現在に置いては不要らしい。

ghq をいれる

motemen/ghq: Remote repository management made easy

~/.gitconfig に ghq の設定を追記する。


[ghq]
root = $GOPATH/src/

ざっくり説明すると、リポジトリの管理をお手軽簡単にやるツール。

Go 言語のリポジトリをこねくりまわすとき GOPATH/src 以下で作業をする必要があるので、これを使って GOPATH/src に配置されるようにしておくと困らなくて良い。
(git clone して作業しようとしたら GOPATH/src におくんやで!みたいなエラーが出てちょっと困った)

Visual Studio Code を入れる

Visual Studio Code - Code Editing. Redefined

エディタ atom でも良かったんだけど、せっかくなので別のやつを試してみる。
個人でポチポチやってみるのに Gogland ほどのウルトラハイパフォーマンス IDE は一旦いらないんじゃなかろうかという気持ち。

拡張機能はとりあえずこの2つを。

  • Go
    • Go 言語サポートを追加する
  • Dracula Official
    • おすすめテーマ

Go を入れるだけで、裏で go ほげほげなコマンドを使った定義元ジャンプや整形や lint やデバッグまで色々多数用意される。
初めて Go を入れたときに必要な Go パッケージが go get されているかを自動で確認してくれて、無い場合はよろしくやってくれる。

GOPATH 以下が見れる GOLANG というタブ?がエクスプローラーの中に出ているので、ここからこのまま作業をしてもいいし、ディレクトリを開いて置いても良いと思う。

ディレクトリを開いたほうがターミナル起動時に、カレントディレクトリに移動してからやってくれるので便利。
GOPATH のもので見ると GOPATH を最初に開くので移動する手間あり。

----

とりあえずはこんなとこで。

MySQL (MariaDB) のスローログを出す設定をする

インデックスつかってる?やばない?みたいなクエリを調べたいので、スローログで出せた気がするなあ、ってメモ。

スローログに関する設定の確認

> show variables like '%slow%';
+---------------------+--------------------------------------------------------------------------------------------------------------+
| Variable_name       | Value                                                                                                        |
+---------------------+--------------------------------------------------------------------------------------------------------------+
| log_slow_filter     | admin,filesort,filesort_on_disk,full_join,full_scan,query_cache,query_cache_miss,tmp_table,tmp_table_on_disk |
| log_slow_queries    | OFF                                                                                                          |
| log_slow_rate_limit | 1                                                                                                            |
| log_slow_verbosity  |                                                                                                              |
| slow_launch_time    | 2                                                                                                            |
| slow_query_log      | OFF                                                                                                          |
| slow_query_log_file | 522d4fac608d-slow.log                                                                                        |
+---------------------+--------------------------------------------------------------------------------------------------------------+
7 rows in set (0.00 sec)
> show variables like '%long%';
+---------------------------------------------------+-----------+
| Variable_name                                     | Value     |
+---------------------------------------------------+-----------+
| deadlock_search_depth_long                        | 15        |
| deadlock_timeout_long                             | 50000000  |
| long_query_time                                   | 10.000000 |
| max_long_data_size                                | 1048576   |
| performance_schema_events_waits_history_long_size | 10000     |
+---------------------------------------------------+-----------+
5 rows in set (0.00 sec)
> show variables like '%log_queries%';
+-------------------------------+-------+
| Variable_name                 | Value |
+-------------------------------+-------+
| log_queries_not_using_indexes | OFF    |
+-------------------------------+-------+
1 row in set (0.00 sec)
> show variables like '%output%';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| log_output    | FILE  |
+---------------+-------+
1 row in set (0.00 sec)

log_slow_queries と slow_query_log がいてオッ?と思ったが slow_query_log を使うのが正しいらしい。

profiling - Mysql: What is the difference between "slow_query_log" vs "log_slow_queries" - Stack Overflow

 

その他の設定値はフィーリングで大丈夫(ダメ)
このあたりで説明は補完できる。

MySQL :: MySQL 5.7 Reference Manual :: 5.1.7 Server System Variables

スローログの設定と動作確認

set global slow_query_log = ON;
set global long_query_time = 1;
set global log_queries_not_using_indexes = ON;
set global log_output = 'TABLE';

log_output を TABLE にすると mysql.slow_log テーブルに出る FILE にするとファイルに出る。

まずは TABLE にした場合

> use hoge;
> select * from users where nanika_id = 1;

> use mysql;
> show tables;
+---------------------------+
| Tables_in_mysql           |
+---------------------------+
| columns_priv              |
| db                        |
| event                     |
| func                      |
| general_log               |
| help_category             |
| help_keyword              |
| help_relation             |
| help_topic                |
| host                      |
| ndb_binlog_index          |
| plugin                    |
| proc                      |
| procs_priv                |
| proxies_priv              |
| servers                   |
| slow_log                  |
| tables_priv               |
| time_zone                 |
| time_zone_leap_second     |
| time_zone_name            |
| time_zone_transition      |
| time_zone_transition_type |
| user                      |
+---------------------------+
24 rows in set (0.00 sec)

> select * from slow_log;
+----------------------------+---------------------------+-----------------+-----------------+-----------+---------------+------+----------------+-----------+-----------+-----------------------------------------+
| start_time                 | user_host                 | query_time      | lock_time       | rows_sent | rows_examined | db   | last_insert_id | insert_id | server_id | sql_text                                |
+----------------------------+---------------------------+-----------------+-----------------+-----------+---------------+------+----------------+-----------+-----------+-----------------------------------------+
| 2018-07-19 05:08:05.999111 | root[root] @ localhost [] | 00:00:00.003893 | 00:00:00.002550 |         0 |             0 | hoge |              0 |         0 |         0 | select * from users where nanika_id = 1 |
+----------------------------+---------------------------+-----------------+-----------------+-----------+---------------+------+----------------+-----------+-----------+-----------------------------------------+
1 row in set (0.00 sec)

> use hoge;
> explain select * from users where nanika_id = 1;
+------+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id   | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra       |
+------+-------------+-------+------+---------------+------+---------+------+------+-------------+
|    1 | SIMPLE      | users | ALL  | NULL          | NULL | NULL    | NULL |    1 | Using where |
+------+-------------+-------+------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)

次に FILE の場合。

$ pwd

/var/lib/mysql


$ tail 522d4fac608d-slow.log

# Time: 180719  5:33:26
# User@Host: root[root] @ localhost []
# Thread_id: 3  Schema: hoge  QC_hit: No
# Query_time: 0.000337  Lock_time: 0.000141  Rows_sent: 0  Rows_examined: 0
SET timestamp=1531978406;
select * from users where nanika_id = 1;

slow_query_log_file で指定した場所に出る /var/log/mysql/... みたいな指定をする必要がありそう。

インデックスを使用していないクエリも出してくれる、便利。

そのほか

set global しただけだと mysql サーバーを終了したときに設定が消えてしまうので、必要に応じて my.cnf に記載。

[mysqld]
slow_query_log=ON
long_query_time=1
log_queries_not_using_indexes=ON
log_output=FILE
slow_query_log_file=hogehoge-slow.log
$ echo 'show variables like "%slow%";' | mysql

Variable_name	Value
log_slow_filter	admin,filesort,filesort_on_disk,full_join,full_scan,query_cache,query_cache_miss,tmp_table,tmp_table_on_disk
log_slow_queries	ON
log_slow_rate_limit	1
log_slow_verbosity
slow_launch_time	2
slow_query_log	ON
slow_query_log_file	hogehoge-slow.log

大丈夫そう。

日本語の折り返しを正規表現で解決する mikan.js を PHP に書き換えた

できたものがこちらです

作った背景

日本語の折り返しが中途半端になってつらい!機械学習で改善するぞ!という話が過去にありました。
google/budou: Budou is an automatic organizer tool for beautiful line breaking in CJK (Chinese Japanese and Korean).

それからしばらくして、いやいや機械学習じゃなくてもいいのでは?というものが出てきました。
mikan.js : 機械学習なしで、日本語の単語の改行を処理するライブラリを書いた

これって別に JS で表示するときにアレコレしなくても、普段のサーバサイドでいい感じにしてもいいのでは??と思い PHP に移植してみた次第です。

移植する流れ

元のコードを眺めて、同じような処理に書き換えていく簡単なおしごと。
幸いにも、そこまで難しいロジックでは無いので、動作をみながら書き換えていきました。

正規表現のあたりだけ、言語の違いでちょっと詰まったので、ドキュメントを見ながら動作を見ながら随時書き換えていくようなコツコツ作業でした。

言語の書き換え、双方の言語について理解が深まるのでオススメです。

git で用済みのブランチをまるっと消す

普段の開発はもちろん、確認用!作業用!などとやったり放っておくと増えていくブランチを一掃する。

基本的には master をチェックアウトしてからやったほうがいい。
別のブランチから見たときに、あるブランチがマージされていないように見えることがあるので、基本的には master ブランチをチェックアウトしてからやったほうがよさそう。
それも組み込めないかなあ、、シェルスクリプトだなー

ローカル用

git branch --merged | grep -v '*' | grep -v 'master' | xargs git branch -D

リモート用

git branch -a --merged | grep remotes | grep -v HEAD | grep -v master | sed -e "s/remotes\/origin\///" | xargs git push --delete origin

マウスホバーでAjaxしてコンテンツを先読みキャッシュ

なんとなく思いついて、このブログに入れてみて機能の紹介。

link rel=prefetch

HTML の機能として、指定した URL の内容を先読みして、内容をキャッシュしておいてくれるものがあります。
詳しいことは MDN を見るとよいかとー。

rel="preload" によるコンテンツの先読み - HTML: HyperText Markup Language | MDN
Link prefetching FAQ - HTTP | MDN

preload と prefetch って??という場合にはこちらの記事もおすすめです。

Preload を用いたリソースプリローディングの最適化 | blog.jxck.io

なぜ onHover で prefetch ?

ちゃんと計測したことないのでわからないですけど、リンクをセカセカ、サササッ!とクリックすること、あまり無いですよね?読み物系ならとくに。

わずかにホバーする時間があってからクリックするのかなあと思ったので
じゃあそのタイミングで先読みしたら爆速 Web サイトが体験できるんじゃ?と思った次第です。

コード

// prefetch link
let prefetchTimers = {};
let prefetcedLinks = {};
document.querySelectorAll('a').forEach((element) => {
    element.addEventListener('mouseenter', (event) => {
        if (element.href.indexOf('#') !== -1 || element.href.indexOf('javascript:') !== -1) {
            return;
        }
        if (element.target === '_blank') {
            return;
        }
        if (prefetcedLinks[element.href] === true) {
            return;
        }
        prefetchTimers[element] = setTimeout(() => {
            const prefetchTag = document.createElement('link');
            prefetchTag.rel = 'prefetch';
            prefetchTag.href = element.href;
            document.head.appendChild(prefetchTag);
            prefetcedLinks[element.href] = true;
        }, 300);
    });

    element.addEventListener('mouseleave', (event) => {
        if (prefetchTimers[element] !== undefined) {
            clearTimeout(prefetchTimers[element]);
            delete prefetchTimers[element];
        }
    });
});

遷移先別に、ホバー用タイマーと読み込み有無を記録しています。
ホバーから外れるとタイマーを削除し、ホバーが外れずにタイマーが経過すると prefetch が登録されます。

爆速 web サイトは体験できるのか?

prefetch 無し (disable cache をつけてすべて読み込むようにしています)

prefetch あり (disable cache を外して、読み込む様子)

すべてキャッシュから読み込んでいるのがわかります。
そのおかげで DOMContentLoadedまで 1 秒ちょっとかかっていたのが 300ms ほどに収まるようになりました!すごい!

まとめ

prefetch で爆速 web 体験ができるかも。
結局作ってみた満足度が高いことが主になっているので、ガチプロダクトでやってくなら本当に効果あるの?その機能使われている?は別途ちゃんと計測しないとねーーー。
あとは prefetch すればギガ減るよね、というかスマホ対応できないなこれ。スマホ対応ってどうすんだろ。

1 2 3 4 5 6 7 8 9 10 11 12 13