エンジニアですよ!

頑張れ俺くん、巨匠と呼ばれるその日まで

Windows サービス関連のメモ

タスクスケジューラ

起動方法

起動時に実行したいなどイベントドリブンなタスクはタスクスケジューラから登録する(多分

タスクスケジューラはtaskschd.mscなので、ファイル名を指定して実行する

powershellスクリプトをタスクとして登録する

.batはわからないのでps1で書きたい(かけるものなら)が、タスクスケジューラの「プログラムの開始」で詳細にそのままps1のファイルを登録したら(デフォルトでは)メモ帳でps1ファイルが開くだけで実行されるわけではない。実行するには、詳細には「powershell」を指定し、引数の -File で実行したいファイルを指定する。

f:id:totem_3:20200509155518p:plain:w300

サービス

動かし続けたいものはサービスとして登録する(多分

これはタスクマネージャのサービスのところから起動できる

久々のwindows

Powershell7

インストール

GitHub - PowerShell/PowerShell: PowerShell for every system!

リリースからダウンロードしてインストール

keybindをlinuxっぽく

PSReadLine は最初から入ってたっぽいので↓実行する

Set-PSReadLineOption -EditMode Emacs

ってやってて気づいたけどこれだと永続化しないので, $PROFILEに書く。

新しいPowerShellで$PROFILEの場所が変わってたので作り直しておく

New-Item -Name (Get-Item $PROFILE).Directory -Type dir
New-Item -path $PROFILE -ItemType file
vim $PROFILE
cat $PROFILE
Import-Module PSReadLine
Set-PSReadLineOption -EditMode Emacs

Windows Terminal

とりあえず入れた

WSL2

WSL2対応版のDocker Desktopで必要だったのでUbuntu 18.04 入れた

WSL 2 のインストール | Microsoft Docs

この手順通りでいけたけど、

wsl --set-version <Distro> 2

をやらないと変換されないのに、

wsl --set-default-version 2

こっちをずっと打ってて変換されなくて頭抱えてた

Docker for Desktop

Docker Desktop for WSL2 を使い快適にWindowsでサーバ開発をしよう! - Qiita

これみてやった。(けど、後述だけど↑これはもう古かった。変なところからインストールしたせいでおかしな物が入ってた)

インストールは普通にできたが

PS C:\Users\totem> docker run hello-world
C:\Program Files\Docker\Docker\Resources\bin\docker.exe: error during connect: Post https://192.168.99.100:2376/v1.40/containers/create: dial tcp 192.168.99.100:2376: connectex: A connection attempt failed because the connected party did
not properly respond after a period of time, or established connection failed because connected host has failed to respond.

Windows側からはつながらない.

WSL2側では動いてる。そりゃそうだけど。

totem@xxx:/home/totem$ docker run hello-world

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/

defaultのほう見てたっぽい。とはいえこっちも起動してるはずなんだけどなぁ

PS C:\Users\totem> docker context ls
NAME                DESCRIPTION                               DOCKER ENDPOINT               KUBERNETES ENDPOINT   ORCHE
STRATOR
default *           Current DOCKER_HOST based configuration   tcp://192.168.99.100:2376                           swarm
wsl                 Docker daemon hosted in WSL 2             npipe:////./pipe/docker_wsl
Warning: DOCKER_HOST environment variable overrides the active context. To use a context, either set the global --conte
xt flag, or unset DOCKER_HOST environment variable.

f:id:totem_3:20200321165109p:plain
起動しているはず、の図

とまぁ色々悩んでたんだけど、これなんか古いバージョン入れてたせいでおかしくなってた。docker context の wsl ってのはもう使わないっぽい(?)

あと、古いdocker toolboxみたいなの入れてたから変に環境変数が設定されてて動かなかった。

最新のdocker desktopにアップデートして、環境変数消したら問題なくなった。

Visual Studio 2019

2017入ってたけど入れ直した。何が違うのか知らんけど。

クリップボード

前から使えたはずだけど知らなかったので windows + V で clipboard history 有効にしてclibor サヨナラ

そんなことにはまるんだってやつ C++編その1

久々にビビるレベルの内容で本当にわからなくて長時間ハマったので記念にメモ。本当にポインタがわか これがコンパイルエラーで小一時間ハマったんだけど、ismyarrayじゃなくてmyarray*なのでオーバーロードした[]が呼ばれてるわけじゃないので当然…ポインタに対して添字演算しているのでis[0]はそのままその位置を返すな。はい。

#include <iostream>

template<typename T>
struct myarray
{
    T *a;
    size_t length;

    myarray(size_t n) {
        a = new T[n];
        length = n;
    }

    T & operator[](size_t i)
    {
        return a[i];
    }

};

int main(int argc, char const* argv[])
{
    myarray<int> *is = new myarray<int>(5);
    is[0] = 1;
    std::cout << "i: " << is[0] << std::endl;
    return 0;
}

systemtap で udp の daddr, dport が 0.0.0.0 や 0 に見えることがあるのはなんでなのか

(udp だけど)tcpdump すれば当然宛先の ip address や port が見えるが、systemtap で daddr, dport を printf してみると daddr が 0.0.0.0, dport が 0 に見えることがある。

名前解決のトレースをしていて、通常名前解決をしているときは宛先ポートが 53 になので、 probe udp.sendmsgif (dport == 53) としておけば名前解決に関係する udp の送信だけトレースできるのだけど、たとえば dig で名前を引いたときにはこれだと引っかからなかったことで気付いた。

実際にトレースしたいプログラムで名前解決が実行される場合は普通に dport == 53 で引っかかるので特に問題はなさそうだが、どういうケースで 0 になってしまうのか確認した。

結論から言うと、(正確にはわかっていないが)

  • systemtap は socket 構造体の daddr, dportを見ているので、
  • データを送信(受信)している socket が -- connect していれば connect したアドレスが daddr, dport として見える -- connect していなければ daddr, dport は 0

ということのようだった。

確認した環境は

ーーー

systemtapudp.sendmsg の probe の daddr, dport の定義を見てみる。

systemtap自体の中身を追うのはかなりしんどいはずだが、probeについて調べるなら簡単だった。tapset 以下に stp ファイルがあり、そこに定義されている。tapset/linux/udp.stpを見てみると、udp.sendmsg はこのように定義されている。

sourceware.org Git - systemtap.git/blob - tapset/linux/udp.stp

  39 probe udp.sendmsg = kernel.function("udp_sendmsg") {
  40         name = "udp.sendmsg"
  41         sock    = $sk
  42         size    = $len
  43         %( systemtap_v >= "2.3" %?
  44         family  = __ip_sock_family($sk)
  45         saddr   = format_ipaddr(__ip_sock_saddr($sk), __ip_sock_family($sk))
  46         daddr   = format_ipaddr(__ip_sock_daddr($sk), __ip_sock_family($sk))
  47         sport   = __udp_sock_sport($sk)
  48         dport   = __udp_sock_dport($sk)
  49            %)
  50 }

確認したい dport__udp_sock_dport($sk) の部分。これはもっと上に定義されていて、

sourceware.org Git - systemtap.git/blob - tapset/linux/udp.stp

  15 @__private30 function __udp_sock_sport (sock) { return __tcp_sock_sport (sock) }
  16 @__private30 function __udp_sock_dport (sock) { return __tcp_sock_dport (sock) }

tcpのものを見ている。 tcp.stpを見てみると

sourceware.org Git - systemtap.git/blob - tapset/linux/tcp.stp

  82 /* return the TCP destination port for a given sock */
  83 function __tcp_sock_dport:long (sock:long)
  84 {
  85   port = @choose_defined(@inet_sock_cast(sock)->sk->__sk_common->skc_dport, # kernel >= 3.8
  86           @choose_defined(@inet_sock_cast(sock)->inet_dport, # kernel >= 2.6.33
  87            @choose_defined(@inet_sock_cast(sock)->dport, # kernel >= 2.6.11
  88                            @inet_sock_cast(sock)->inet->dport)))
  89   return ntohs(port)
  90 }

@hogehogeはよくわからないが、要するに skc_dport を見てるんだなとわかる。ような気がする。

これは大体 inet_dport と同じようなものだろう。

udp の場合、 connect で呼ばれるのは おそらく ip4_datagram_connect のはずで、

https://elixir.bootlin.com/linux/v4.0/source/net/ipv4/datagram.c#L23

その中でソケットに inet_daddrinet_dport を設定している。

https://elixir.bootlin.com/linux/v4.0/source/net/ipv4/datagram.c#L76

int ip4_datagram_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
{
    struct inet_sock *inet = inet_sk(sk);
    struct sockaddr_in *usin = (struct sockaddr_in *) uaddr;
...
    inet->inet_daddr = fl4->daddr;
    inet->inet_dport = usin->sin_port;
    sk->sk_state = TCP_ESTABLISHED;

これは、 udpsend で宛先を指定しなかった場合に宛先として利用される箇所と一致している、はず…。下記は udp_sendmsg の該当箇所。 msgsend にわたす宛先などの情報で、それがない場合は else の方に入り、 inet->inet_daddrinet->inet_dport をローカル変数の daddrdport に代入している。

https://elixir.bootlin.com/linux/v4.0/source/net/ipv4/udp.c#L932

int udp_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
        size_t len)
{
    struct inet_sock *inet = inet_sk(sk);
...
...
    /*
     *  Get and verify the address.
     */
    if (msg->msg_name) {
        DECLARE_SOCKADDR(struct sockaddr_in *, usin, msg->msg_name);
        if (msg->msg_namelen < sizeof(*usin))
            return -EINVAL;
        if (usin->sin_family != AF_INET) {
            if (usin->sin_family != AF_UNSPEC)
                return -EAFNOSUPPORT;
        }

        daddr = usin->sin_addr.s_addr;
        dport = usin->sin_port;
        if (dport == 0)
            return -EINVAL;
    } else {
        if (sk->sk_state != TCP_ESTABLISHED)
            return -EDESTADDRREQ;
        daddr = inet->inet_daddr;
        dport = inet->inet_dport;
        /* Open fast path for connected socket.
           Route will not be used, if at least one option is set.
         */
        connected = 1;
    }

ということで、

  • systemtapudp.sendmsg の probe 内の daddr, dport は実際にパケットを送信した宛先が見えているのではない
  • 使える変数は probe にもともと定義されている
  • udp.sendmsg の場合、socket から宛先の情報を取得している。
  • udpの場合、socket を connect して send のときには宛先を指定しないこともできるし、 connect せず send 時に宛先を指定することもできる
  • connect した場合は socket に宛先情報が設定されているので systemtap の probe 内から参照できる。
  • connect していない場合は socket の宛先情報とは関係なく送信するので、 systemtap の probe 内からは宛先を正しく参照することができない。

ということのようだ。

ちなみに glibcgetaddrinfo では connect しているっぽくて、実際 systemtap でトレースしていても dport がちゃんと取れている。 dig ではどうかと思ったが dig はソースコード読むのが難しかったので諦めた

stackprof の wall mode で rails のプロファイルをとろうとしたら mysql の接続エラーがたくさん出てホストが mysql にブロックされてしまう話

起きたこととしては

  • stackprofrack-stackprofで使って rails アプリのプロファイルを取ろうとした
    • stackprof は wall mode で動いてる
  • すると、Host 'x.x.x.x' is blocked because of many connection errors が発生した
  • 起動後しばらくは正常に動いているが、しばらくするとエラーが連続して発生してしまうようで mysql サーバにブロックされアプリが動かなくなる。

原因は

  • stackprof は profile を取るために setitimer で定期的にシグナルを送る
  • wall モードの場合、 setitimerITIMER_REAL で動くので、実時間で定期的にシグナルを送る。
  • libmysql は mysql に接続後 poll でデータを待ち受ける。
  • poll で待っている間にシグナルを受け取ると、 pollEINTR で失敗して connection を切ろうとする。
  • ここで、サーバが既に Server Greeting を送っているのに切断されてしまうと、サーバとしては接続が不正に中断されたとしてエラーだと判断する。
    • (このあたりの詳細はよくわかってない。)
  • mysql には max_connect_errors という値があり、それを超える回数連続で接続が失敗するとそのホストをブロックする。
  • ってことで連続で失敗してブロックされてしまうってことみたいだった。

原因はわかったものの、世の中の人は stackprof で rails アプリケーションのプロファイリングをしているのにこういうことで困ってないということはなにかが間違っているような気もするし、mysqllocalhost だとブロックされることはないとか、普通は mysqlskip-name-resolve を設定していて max_connect_errors は関係なくなるので気付かないとかそういう話なのかもしれない。(会社でもオンプレのときは skip-name-resolve は設定していたような気がする)

それぞれもう少し詳しく

stackprof

今回はじめて中身見た。 setitimer もはじめて知った。プロファイラってこうやってやるんだな〜という。

setitimer して定期的にシグナルを受け取って処理することで時間を測る。

計測モードにはいくつか種類があるが、README を見ると

four sampling modes are supported:

:wall (using ITIMER_REAL and SIGALRM)

:cpu (using ITIMER_PROF and SIGPROF) [default mode]

ということで、問題の wall モードでは ITIMER_REAL が使われている。 ITIMER_PROF だと poll の間は時間が進まないのでシグナルが送られることがなく、問題が起きないのだろう。きっと。

mysql2

問題の Mysql2::Client.new が実際に何をやってるかというと、initialize の最後で connect している

この connect メソッドは native extension の方で定義されている。実際には rb_mysql_connect

実際に接続しているのはここで、nogvl_connect というメソッド。

こいつが libmysql の mysql_real_connect を呼んで接続する。

クライアント側が接続失敗に気付かないのは、この rb_mysql_connect の中で、エラーが EINTR であればリトライしているから。↓この部分。

mysql2/client.c at 3b9a26708fa86aba23763626331eb317ed457cc1 · brianmario/mysql2 · GitHub

libmysql

バージョンは 5.7.24。(そういえば、地味に cmake のときに boost が見つからないっていう問題があり解決できなかったなぁ。メッセージに従って -DDDOWNLOAD_BOOST=1 したんだけど)

いろいろ見たけど、 poll しているのは vio/viosocket.c の vio_io_wait の中 786 行目から。print 入れまくってここでエラーが出ているのにたどり着いた。

  switch ((ret= poll(&pfd, 1, timeout)))
  {
  case -1:
    /* On error, -1 is returned. */
    break;
  case 0:
    /*
      Set errno to indicate a timeout error.
      (This is not compiled in on WIN32.)
    */
    errno= SOCKET_ETIMEDOUT;
    break;
  default:
    /* Ensure that the requested I/O event has completed. */
    DBUG_ASSERT(pfd.revents & revents);
    break;
  }

poll

Man page of POLL のエラーのところを見ると

EINTR 要求されたイベントのどれかが起こる前にシグナルが発生した。 signal(7) 参照。

エラーの原因はこいつです。多分

RSTが起きるのはなぜか

tcpdump の結果はこういう感じで、ハンドシェイク直後にクライアントが FIN を送っていて、サーバは Greeting を送っており、その後クライアントは RST を返す。これはなんでなのかよくわからなかった。

f:id:totem_3:20190107212554p:plain

どう解決すべきなのか?

本当はクライアント側のもう少し早いところで EINTR のリトライをすればいいのでは?という気がする。けどまぁものによるだろうから仕方ないんだろうなぁ。

libmysql の mysql 8 のソースコードを見ると、この部分には ppoll が使われるようになっていて、シグナルがマスクできるようになっている。

しかし、それを設定できるのはサーバ側だけで、クライアント側からは設定できない。残念。ここを設定できるようにすればいいよね。

実際に、 5.7 のソースコードppoll に切り替えて SIGALRM をマスクするようにしたところ問題は発生しなくなった。プロファイルの結果には微妙に影響があるだろうけど、エラーが出るよりは全然良い。

ただ、さすがに libmysql にパッチを当てるのは厳しいのでクライアント側で対応するのは難しい。

とするとサーバ側でなんとかするしかない。

で、↓を見た。 max_connect_errors 回連続した接続失敗でホストをブロックするのをやめよう。ほんとにエラーでなくするだけだけど………

MySQL 5.6のインストール後にチューニングすべき項目 | Yakst

max_connect_errors というのは skip-name-resolve を有効にしていると無効になるようだ。skip-name-resolve は名前解決を利用しない限り設定することが推奨される項目であり、この度エラーが出ていた環境では設定していなかったが経験上本番環境でも設定されていた。

つまり, max_connect_errors 回を超える接続失敗によって接続を拒否するなんてしなくてもいいんだ。そもそも mysql を外部に晒すなんてことはないわけで、攻撃を受けるとしたら内部から。内部に侵入されているのにこれで検知できるような攻撃されるってことはないだろう…多分。ってことでセキュリティ的にはあまり懸念はないはず。

あとはこういう微妙なケースで実は接続エラーが発生しているということに気付かないという可能性はあるが、めったにあることではないので、 SHOW GLOBAL STATUSAborted_connects でも監視しておけばいいでしょう。

時系列

ここからは調べた履歴。行き当たりばったりで調べて時間を費やしたので自戒

続きを読む

mac で dev_appserver.py を再起動しないと変更が反映されなかった

OSは mac, runtime は python27 のお話。 Google Cloud SDK のバージョンは 228.0.0。 components のバージョンは最後に書いた

結論から

起動してるGAEアプリケーションが . で始まるディレクトリ以下にあるとダメ

僕の場合は ghq を使って $HOME/.ghq/hoge/fuga 以下にアプリを clone して作業してたのでダメだった。

昔はいろいろあったようだが、現在の dev_appserver はファイルの変更を検知して変更を反映してくれるようになっている。

macの場合は inotify が使えないので、ファイルの mtime をポーリングして変更を検出している。ポーリング対象から除外する条件みたいなのが用意されており、恐らく意図としては .git とかを除外するために、 . から始まるディレクトリは除外するようになっている。

しかし、実際には working directory 以下の . 始まりのディレクトリを無視するのではなく、絶対パスで途中に . 始まりのディレクトリがあるとまるっとwatch対象から除外されてしまう。(inotify watcherでも同じ気はするが確認はしてない)

ということで ghq のルートディレクトリを $HOME/.ghq から $HOME/ghq にすることで問題は解決した。

おしまい

補足。 Google Cloud SDK の version

❯ gcloud --version
Google Cloud SDK 228.0.0
alpha 2018.11.09
app-engine-python 1.9.80
app-engine-python-extras 1.9.74
beta 2018.11.09
bq 2.0.39
cloud-datastore-emulator 2.0.2
core 2018.12.07
gsutil 4.34

facebookの自動テストツールの論文を読んだメモ

Facebook がバグの自動修正ツールを発表したという話があり、 code.fb.com

その紹介記事を見て、 Sapienz のほうは論文が発表されているという話だったので読んでみた。軽く。

バグの自動修正ツールのほうはSapFixというやつで、バグを修正するためにはまずバグを見つけなければいけないが、そのバグを検知するために使われているのがSapienz。

で、SapienzというのはAndroidアプリ向けの自動テストツール。

この論文を書いた一人のMark Harmanという方(とMcMinnさんという方)が2001年にSearch-based software engineering (SBSE) という手法を提案しており(参考: http://a-lifelong-tester.cocolog-nifty.com/publications/STM07_Notes_on_new_software_testing_techniques.pdf)、 曰くSBSEとは、

ソフトウェア工学における各種の課題をヒューリスティックサーチの技法を使って解決を図る

手法であり、

このサーチ技法をテストの分野に適用するのがSearch-based testingと呼ばれる技法

である。そのSearch-based testingの手法をAndroidのテストに応用して成果が出たよんということのようだ.

上で参考にあげた資料に書いてあるようにサーチ技法として用いられるアルゴリズムにはいくつかの種類があるが、Sapienzで用いられているのは Evolutionary Algorigthm (進化的アルゴリズム) である。 また、Androidのテストにおいては、多目的最適化を使ったsearch-based testingの初めてのアプローチだぜ、と言っているし、論文のタイトルからしてMulti-objective Automated Testing for Android Applicationsであるように、多目的最適化問題として扱っている。

進化的アルゴリズムでは、解きたい問題の解を個体として表現する(多分)。

個体の性質は染色体によって決まり、染色体は複数の遺伝子から成る。

そして個体同士で交叉して子を作ったり突然変異したり一部の個体は淘汰されたりして進化をして世代を重ねていくことで、よりいい感じの解を探す。

詳しくや正確にはもちろん他の資料を参考に。

で、Sapienzで具体的にどんな風に問題が表現されているか。

Sapienzで最適化したい問題というのは、

  • より多くの範囲をテストし(coverage)
  • より多くの不具合を(fault revelation)
  • より短い手順で(shorter test sequence)

発見するようなテストを生成したいということ。

この3つを同時に最適化するので、Multi-objectiveな最適化という話。

論文中でも言及されているが、多目的最適化においては一般にパレート最適な解を見つけることが目的になる。 この場合上記の3つの要素についてパレート最適な解を探す。

Sapienzでは、進化的アルゴリズムにおける個体などの表現は下記のようになっている。

  • 個体: テストスイート
  • 染色体:テストケース
  • 遺伝子:なんらかのイベント

で、遺伝子には2種類ある。Atomic genesとMotif genes.

Atomic genesのほうはその名の通りAtomicであってそれ以上分割することのできない単位のイベント。 たとえばなんかのキーを押すとかどこかをタップするとか音量を調節するとかそういうの。

Motif genesのほうはAtomic genesの組み合わせ。

なんで組み合わせを使うのかというと、UIというのは複雑なものなので、状態やその時のコンテクストの知識なしに単純にAtomic genesだけ組み合わせているとちゃんとした動作を組み立てるのが難しいからだそう。まぁそれはそうっぽい感じがする。しかしこの論文の時点では、Motif genesは汎用的なもの1種類しか使っていないようだ。Motif geneをたくさん使うということはそれだけ人間の手がかかるということで、自動化を妨げることになるからということ。

個体や遺伝子についてはそれで、個体を進化させるためのアルゴリズムの話

下記のいずれかを確率的に個体に適用する。

  • 交叉(crossover)
  • 変異(mutation)
  • 繁殖(reproduction)

個体間では一様交叉を行う。個体内では変異するが、変異は少し複雑。

個体はテストケースを複数含むテストスイートなので、まずテストケースの順序をランダムにシャッフルする。このシャッフルは交叉の際の多様性を増すことを目的にしている。シャッフルしたあとに2つの隣合うテストケース同士で, qの確率で1点交叉を行う。 更に、テストケース内のイベントをqの確率でシャッフルする。Atomic eventsにはパラメータがあるので、それを変異させることもできるが、操作を単純にするためにイベントの並び順を変えることにしたようだ。 繁殖は単にランダムに選ばれた個体をそのまま使うらしい。

淘汰(selection)にはNSGA-Ⅱというアルゴリズムを使う。雰囲気しか理解できてない。

個体の評価は、さっきも書いたが下記の組で行われる。

  • カバレッジ
  • テストシーケンスの短さ
  • 発見したクラッシュの多さ

これらが最も望ましいケースと個体との距離で個体がランク付けされ、ランクが良いものが選ばれる。同じランクではより混雑距離が大きいものが選ばれる。混雑距離というのはランク内において隣接する個体への距離の和のこと。より混雑していないもののほうがが多様性を確保することができるため良い。

ここまで読むとなんとなく進化的アルゴリズムを適用してある程度良いものができそうな気がしてくる。むしろ、実際多様なアプリケーションに対してどのようにテストのイベントを生成するのかとかが気になってくるが、そのへんについては特に細かくは触れられてない。多分他にもテストを生成するツールは既にあるのでそこはこの論文にとってはあんまり重要ではないだろう。

ちなみにこのSapienzはDEAPという, python製の進化的アルゴリズムのためのフレームワークを使って構築されていたが、このDEAPはすごく簡単に使えて便利。自分の場合使う予定は特にないけど。NSGA-Ⅱを用いた多目的最適化とかもできる様子。

参考にした資料など

基本的に検索してすぐ出てきたもの