GraalVMを試してみました

遅まきながら、GraalVMのサイトのGetting Startを試してみました。

Getting started with GraalVM

試してみるようdockerイメージが用意されています。Docker Desktopをインストール済みであればこれを使うのが簡単です。

サイトには、Node、RubyPythonを使ったサンプルもありますがまずはJavaで試してみました。

手順

# docker image取得
$ docker pull oracle/graalvm-ce:19.1.1

# docker imageに接続
$ docker run -it oracle/graalvm-ce:19.1.1 bash
bash-4.2# 

# 気持ち的にrootにファイルは作成しにくい
bash-4.2# cd home

# "Hello Word!"を出力するjavaファイル作成
bash-4.2# vi HelloWorld.java

bash-4.2# cat HelloWorld.java
public class HelloWorld {
  public static void main(String[] args) {
    System.out.println("Hello, World!");
  }
}

bash-4.2# javac HelloWorld.java
bash-4.2# java HelloWorld
Hello, World!

# native-imageを作成
# デフォルトでは入っていないのでダウンロードする
bash-4.2# install native-image
bash-4.2# native-image HelloWorld
bash-4.2# ./helloWorld
Hello, World!

# バイナリをhexdumpする
# elfファイルになっている。
bash-4.2# hexdump -C helloworld | head
00000000  7f 45 4c 46 02 01 01 00  00 00 00 00 00 00 00 00  |.ELF............|
00000010  02 00 3e 00 01 00 00 00  00 10 40 00 00 00 00 00  |..>.......@.....|
00000020  40 00 00 00 00 00 00 00  d0 0c 23 00 00 00 00 00  |@.........#.....|
00000030  00 00 00 00 40 00 38 00  09 00 40 00 26 00 25 00  |....@.8...@.&.%.|
00000040  06 00 00 00 05 00 00 00  40 00 00 00 00 00 00 00  |........@.......|
00000050  40 00 40 00 00 00 00 00  40 00 40 00 00 00 00 00  |@.@.....@.@.....|
00000060  f8 01 00 00 00 00 00 00  f8 01 00 00 00 00 00 00  |................|
00000070  08 00 00 00 00 00 00 00  03 00 00 00 04 00 00 00  |................|
00000080  38 02 00 00 00 00 00 00  38 02 40 00 00 00 00 00  |8.......8.@.....|
00000090  38 02 40 00 00 00 00 00  1c 00 00 00 00 00 00 00  |8.@.............|

# ファイルサイズ
bash-4.2# ls -l
total 2256
-rwxr-xr-x 1 root root 2299472 Jul 21 01:23 helloworld
-rw-r--r-- 1 root root     427 Jul 21 01:20 HelloWorld.class
-rw-r--r-- 1 root root     116 Jul 21 01:20 HelloWorld.java

# 実行時間
bash-4.2# time ./helloworld
Hello, World!

real    0m0.002s
user    0m0.000s
sys 0m0.000s
bash-4.2# java HelloWorld
Hello, World!
bash-4.2# time java HelloWorld
Hello, World!

real    0m0.076s
user    0m0.060s
sys 0m0.000s

以下の記事はJavaフレームワークをnative-imageで起動した結果です。今後の動きから目が離せません。

きしだのHatena

あと、同じ著者が書いたこの記事も参考になります。 native-imageとJITは同程度だと思っていたのですが、JITの方がこんなに早いとは思いませんでした。

GraalVMはどれだけ遅いか - きしだのHatena

pyOxidizerを試しててみました

以下のページを参考にしてpyOxidizerを試してみました。 PyOxidizer を試してみた - Qiita

python環境を持ち運ぶ(repl)として使うとか、アプリをpythonをインストールしていない環境で実行するなど使い道はありそうです。

環境

macOS Mojave 10.14.5

事前準備

rust環境をインストールする必要があります。 homebrewでインストールした場合pyOxidizerに必要なモジュールがインストールされないので、rustセットアップシェルを実行しました。

$ curl https://sh.rustup.rs -sSf | sh

アプリ環境作成

$ pyoxidizer init pyapp

pythonファイル作成

パッケージのルートディレクトに作成します。

$ cat helloworld.py
print("hello world")

設定ファイル()編集

$ cat pyoxidizer.toml
# Package .py files discovered in a local directory.
[[packaging_rule]]
type = "package-root"
path = "."
packages = ["helloworld"]

[[embedded_python_run]]
# Run an interactive Python interpreter.
#mode = "repl"     <-- init時にはここが有効になっています 

# Import a Python module and run it.
mode = "module"
module = "helloworld"
#module = "mypackage.__main__"

ビルド & 実行

$  pyoxidizer build
$  pyoxidizer run
packaging application into /.../pyapp/build/apps/pyapp/debug
purging /.../pyapp/build/apps/pyapp/debug
copying /.../pyapp/build/target/x86_64-apple-darwin/debug/pyapp to /.../pyapp/build/apps/pyapp/debug/pyapp
resolving packaging state...
pyapp packaged into /.../pyapp/build/apps/pyapp/debug
hello world

ファイルサイズは約27M。

$ ls -l build/target/x86_64-apple-darwin/debug/pyapp
-rwxr-xr-x  2 unokun  staff  27291660  7 14 09:23 build/target/x86_64-apple-darwin/debug/pyapp

リンク

SwiftUIチュートリアル(Landmarks)を試してみました

遅ればせながら、SwiftUIチュートリアル(Landmarks)を試してみました。

Creating and Combining Views — SwiftUI Tutorials | Apple Developer Documentation

MacOS 10.15(beta)を使っていれば、XCode project内のpreviewを見ながら開発できるので便利そうです。 MacOS 10.15が待ち遠しいです。

チュートリアルもよく出来ておりわかりやすいです。

f:id:unokun3:20190623112721p:plain

参考

Windows版Leelaを使って見ました

ニューラルネットベースの囲碁AI梱包のLeelaを使って見ました。 簡単にインストールできるので興味がある人は使ってみると良いと思います。

設定

ニューラルネットワークを使うがチェックされています。 人間 vs Leela、Leela vs Leelaが選択できます。

f:id:unokun3:20190526095500p:plain

棋譜読み込み

SGFファイル形式のファイルを読み込むことができます。 自分の棋譜はもちろん、公開されている棋譜も読み込むことができます。 f:id:unokun3:20190526095422p:plain

最善手選択

メニューから「Show Best Move」を選択する(あるいはF4をクリックする)と最善手(候補一覧)が表示されます。 f:id:unokun3:20190526100052p:plain

リンク

AWS Cloud9に移行しました

Cloud9が終了するということでAWS Cloud9に移行しました。 とても親切な移行手順だったので特に困ることはありませんでした。

12月まで使えると思っていましたので、6月30日に使えなくなるのに驚きました。 f:id:unokun3:20190519134515p:plain

メールはちゃんと読まないといけませんね....

we plan to discontinue the ability to create new or to use existing workspaces on c9.io on June 30, 2019 and to discontinue all access on December 31, 2019. We would love it if you would join us on AWS Cloud9.

同時ログイン禁止(後勝ち)

実装

Spring Web Applicationで同時ログイン禁止(後勝ち)を実装するためには、WebSecurityConfigurerAdapterで以下の記述をする。

Spring Security 使い方メモ セッション管理 - Qiita

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests().antMatchers("/", "/home").permitAll().anyRequest().authenticated()
            .and().formLogin()
                .loginPage("/login").permitAll()
            .and()
                .logout().permitAll()
            .and()
                .sessionManagement().maximumSessions(1)
                .maxSessionsPreventsLogin(false)
                .expiredSessionStrategy(new MySessionInformationExpiredStrategy());
    }
}
public class MySessionInformationExpiredStrategy implements SessionInformationExpiredStrategy {

    @Override
    public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {
        DefaultRedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
        redirectStrategy.sendRedirect(event.getRequest(), event.getResponse(), "/login");
    }
}

動作

  1. ブラウザAでログイン
  2. ブラウザBでログイン
  3. ブラウザAでログイン後ページを更新 ブラウザAはログイン画面に遷移する。

ただし、ブラウザのタブで同時ログインした場合は、同じセッションIDを使い回すので、この方法ではできない。

java – Spring Securityで複数のブラウザタブを管理できますか? - コードログ

また、UsernamePassword認証をカスタマイズしている場合、UsernamePassword認証のFilterの方が先に実施されるため、この方法(セッション管理)は期待通りの動きにならない。

UsernamePassword認証Filterより前に実行されるようにできれば良いのだが...。

以下はMySessionInformationExpiredStrategyが実行されている場合のスタックトレースf:id:unokun3:20190519090523p:plain

Headless-Chrome on AWS Lambda

以下の記事を参考にAWS Lambda上でHeadless-Chromeを動かしてみました。

注) Serverlessは事前にインストールしておく必要があります。

AWS Lambda上でのWebクローリング実行に少し近づいてきました。

ファイル構成

適当なディレクトリ以下に二つのディレクトリ(lambda、selenium-layer)を作成していきます。

mkdir crowler(任意)
cd crowler

$ tree -L 2
.
├── lambda
│   ├── handler.py
│   └── serverless.yml
├── selenium-layer
│   ├── chromedriver
│   ├── selenium
│   └── serverless.yml

selenium-layer

selenim(pythonモジュール)とchromedriver(chromedriver, headless-chrome)をLayerとして登録します。

参照した記事は、chromedriverディレクトリ以下の構成が少しわかりにくかったです。

chromedriver

$ cd selenium-layer
$ mkdir chromedriver
$ cd chromedriver

# headless-chromiumダウンロード
$ curl -SL https://github.com/adieuadieu/serverless-chrome/releases/download/v1.0.0-41/stable-headless-chromium-amazonlinux-2017-03.zip > headless-chromium.zip
$ unzip headless-chromium.zip
$ rm headless-chromium.zip

# chromedriverダウンロード
$ curl -SL https://chromedriver.storage.googleapis.com/2.37/chromedriver_linux64.zip > chromedriver.zip
$ unzip chromedriver.zip
$ rm chromedriver.zip

$ tree
.
├── chromedriver(実行モジュール)
└── headless-chromium(実行モジュール)

selemium

pythonモジュールを追加します。

$ cd selenium-layer
$ mkdir selenium
$ cd selenium
$ pip install -t selenium/python/lib/python3.6/site-packages selenium
$ tree -L 3
.
└── python
    └── lib
        └── python3.6

serverless.yml

chromedriverとseleniumをLayer定義します。

$ cat serverless.yml
service: selenium-layer

provider:
  name: aws
  runtime: python3.6
  stage: dev
  region: ap-northeast-1

layers:
  selenium:
    path: selenium
    description: selenium layer
    CompatibleRuntimes:
      - python3.6
  chromedriver:
    path: chromedriver
    description: chrome driver layer
    CompatibleRuntimes:
      - python3.6

resources:
  Outputs:
    SeleniumLayerExport:
      Value:
        Ref: SeleniumLambdaLayer
      Export:
        Name: SeleniumLambdaLayer
    ChromedriverLayerExport:
      Value:
        Ref: ChromedriverLambdaLayer
      Export:
        Name: ChromedriverLambdaLayer

deploy

deployします。awscliのインストールおよび設定は事前に済ませておく必要があります。

$ serverless deploy

AWSコンソールで登録内容を確認することができます。

lambda

登録したselenium-layerを使う関数をLambdaに登録します。

handler.py

Lambda登録したモジュールは/opt以下に配置されます。

$ cat handler.py
from selenium import webdriver
from selenium.webdriver.chrome.options import Options

def hello(event, context):
    options = Options()
    options.binary_location = '/opt/headless-chromium'
    options.add_argument('--headless')
    options.add_argument('--no-sandbox')
    options.add_argument('--single-process')
    options.add_argument('--disable-dev-shm-usage')

    driver = webdriver.Chrome('/opt/chromedriver', chrome_options=options)

    driver.get('https://qiita.com/')
    body = f"Blog title is: {driver.title}"

    driver.close();
    driver.quit();

    response = {
        "statusCode": 200,
        "body": body
    }

    return response

serverless.yml

service: crawler-with-selenium

provider:
  name: aws
  runtime: python3.6
  stage: dev
  region: ap-northeast-1
  timeout: 900
  environment:
    SELENIUM_LAYER_SERVICE: selenium-layer

functions:
  hello:
    handler: handler.hello
    layers:
      - ${cf:${self:provider.environment.SELENIUM_LAYER_SERVICE}-${opt:stage, self:provider.stage}.SeleniumLayerExport}
      - ${cf:${self:provider.environment.SELENIUM_LAYER_SERVICE}-${opt:stage, self:provider.stage}.ChromedriverLayerExport}

deploy

$ serverless deploy

実行

$ seleverless invoke -f hello
{
    "statusCode": 200,
    "body": "Blog title is: Qiita"
}

実行した時に以下のエラーが発生しましたが、しばらく時間をおいたら正常に動作しました。deployには少し時間がかかるのかも。

  Serverless Error ---------------------------------------

  The role defined for the function cannot be assumed by Lambda.