astamuse Lab

astamuse Labとは、アスタミューゼのエンジニアとデザイナーのブログです。アスタミューゼの事業・サービスを支えている知識と舞台裏の今を発信しています。

Scalaから始めるRust入門

お久しぶりでございます。Scalaでバックエンドを開発しているaxtstar(@axtstart)です。

みなさまゴールデンウィークはいかがお過ごしだったでしょうか?

我が家はあまり旅行に行くということもなく、近場のドライブや、ちょい大き目の公園などで過ごすことが多かったです。

さて、そのおかげというわけではありませんが、この連休を利用して、 新たにRustとWebAssemblyに入門してきたので今回はそのあたりの話を、書きたいと思います。

f:id:astamuse:20190508020532j:plain

Image by prettysleepy1 from Pixabay

前書き

遅ればせながら、前々から気になっていた、Rust Programming Languageの勉強をGWを利用して初めて見ました。

随分昔ですが、Visual C++でDLLを作ってそれをフロントのVisual Basicで呼び出すのが最強と思っていた時があります。

それと似た世界が、JavascriptとRust(WebAssembly)なのではないかと、最近考えていました。

少々遅い気もしますが、何事もはじめないよりは、はじめた方が面白いし何かの役に立つことがあればいいなと。

普段はScalaで開発する事が多いので、そのScalaとの比較として書いていきたいと思います。

Rust、Scala比較

さて、早速RustとScalaを比較してみます。

◎RustはScalaとは違ってガーベージコレクションを持たない

→自分でメモリ管理をする必要がある*1

とは言え、強力な言語システムのサポートがあるため、昔のように、メモリの解放もれを気にすることはかなり減っているようです。

Scopeを抜けると確保したメモリは解放される*2などなど

→OwnershipメカニズムやScopeメカニズムによって、極力バグをcompile時に発見できる仕組みがある。

 ・stackとheapを常に意識できるような書き方になる

例えば、これはダメ

//Stringはstructであるため、その実体はheap領域に確保される
let s1 = String::from("hello");
//s2はs1をディープコピーするのではなく、参照をコピーしている(シャローコピー)
let s2 = s1;
//memory safetyの観点から、Rustはこの時点でs1をOut of Scopeにする

println!("{}, world!", s1);//<---コンパイルエラー(既にOut of Scopeのため)
println!("{}, world!", s2);//OK

関数に渡しても、Out of Scopeになる

fn bollow(c:String){
}
let s1 = String::from("hello");
bollow(s1);
println!("{}, world!", s1);//<---コンパイルエラー(既にOut of Scopeのため)

これはOK

// 固定文字列はstack上に確保される
let s1 = "hello!";
//これはスタック上の操作
let s2 = s1;

println!("{}, world!", s1);

これもOK

fn bollow(c:&str) {
}
// 固定文字列はstack上に確保される
let s1 = "hello!";
bollow(s1);

println!("{}, world!", s1);

参照渡しにした場合はこの限りではありません。

fn calculate_length(s: &String) -> usize { 
    s.len()
}

let s1 = String::from("hello");

let len = calculate_length(&s1);
// 参照渡しの場合、ここでOwnereshipの変更は無い

println!("{}", s1); //OK

もちろん上記のようなことはScalaではおこらず、コンパイルは通ります*3

◎StatementとExpressionが異なる概念として扱われる

Scalaだとあまり意識しない*4セミコロン(「;」)の有る無しによって、StatementなのかExpressionなのかが決まります。

これはダメ

fn add(x:i32, y:i32) -> i32 {
  // これはStatement
  x + y; // <--i32を戻り値として指定しているので、戻り値=()は、コンパイルエラー
}

これはOK

fn add(x:i32, y:i32) -> i32 {
  // これはExpression
  x + y //ここが戻り値になる
}

◎immutable、mutableはScalaと非常に似ている

immutable

let x = 5;
x = 10; // <-- コンパイルエラー
let x = 6; // <-- これはOK(シャードーイング)※ScalaはScopeで実現

mutable

let mut x = 5;
x = 10; // OK
let x = 6; // <-- OK(シャードーイング)※ScalaはScopeで実現

◎enumが非常に強力

 ・Optionがenumとして実装できる*5

enum Option<T> {
    Some(T),
    None,
}

◎nullが無い

その発明者自身*6が10億ドルの過ちといった「null」がRustには存在しません。

Scalaはnullそのものを消去することはできていません。極力使わないようにプログラミングすることはもちろん可能ではあります。

◎match文はScalaと非常に似ている

Rust

fn fib(n: u32) -> u32 {
    match n {
        0 => 0,
        1 => 1,
        _ => fib(n-2) + fib(n-1),
    }
}

順序と case、「,」以外同じように見える

Scala

def fib(n:Int):Int= {
  n match {
    case 0 => 0
    case 1 => 1
    case _ => fib(n-2) + fib(n-1)
  }
}

◎文字の取り扱いはそこそこ大変

let h = String::from("hello");
let w = String::from(", world!");

let hw = h + &w;
println!("{}", w);// OK
println!("{}", h);// コンパイルエラー、hはStringのadd関数、演算子オーバロード(+)により、Out of Scopeになっている

◎Cargoという、パッケージ管理ツールがある

→Scalaのsbt、gradle、mavenのような存在。

tomlという形式で、設定ファイルを記述します。

ここで紹介した機能以外にも、似た機能や全然似てない機能はたくさんあります。

私も今回はこの*7チュートリアルで学習しました。

WebAssembly

MDN Web Docsによると、WebAssemblyとは、

以前ではできなかったようなウェブ上で動作するクライアントアプリケーションのために、複数の言語で記述されたコードをウェブ上でネイティブに近いスピードで実行する方法を提供します。

というもので、IEを除くだいたいの最新ブラウザなら動き、今回Hello, world!した、Rustとも相性の良いものです。

Hello, world! for WebAssembly

RustでWebAssemblyでHello, world!するには今は、wasm-bindgenを使用するのが一番簡単なようですね。

Cargo.toml

[package]
name = "hello-world"
version = "0.1.0"
authors = ["axt <axt_star@hotmail.com>"]
edition = "2018"

[lib]
path = "src/lib.rs"
crate-type = ["cdylib", "rlib"]

[dependencies]
wasm-bindgen = "0.2"
wee_alloc = { version = "0.4.2", optional = true }

src/lib.rs

use wasm_bindgen::prelude::*;

// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
// allocator.
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;

// Rust側からJavascriptのalertを呼べるようにする。
#[wasm_bindgen]
extern {
    fn alert(s: &str);
}

// JavascriptからRustの関数を呼び出せるようにする。
#[wasm_bindgen]
pub fn greet() {
    alert("Hello, hello-world!");
}

上記の2ファイルの状態で、下記を実行すると、しばらくかかりますが、target、pkgというフォルダができて、そこにJavascript*8からRust側を呼び出す、glueスクリプト*9みたいなものを吐き出してくれます。

wasm-pack build

さらに下記で、wwwディレクトリを作成してそこに、webの開発に必要なwebpackなど一式を作成してくれます。

npm init wasm-app www

www配下でnpm installして必要なライブラリを取得します。

cd www && npm install & cd ..

pkgをlinkします。

cd pkg/ && npm link && cd ..

pkgを利用したい側でリンクを呼び出します。

cd www && npm link hello-world && cd ..

ローカルのwebサーバを起動します。

cd www && npm run start ; cd ..

ブラウザでhttp://127.0.0.1:8080/にアクセスすると、alertのダイアログでhello, world!が表示されます。

ここまでここの内容だいたい、そのままです。

RustからのDomアクセスなど

WebのDocumentやElementなどにアクセスしたい場合は、Cargo.tomlにfeatureを追加する必要があります。

[dependencies.web-sys]
version = "0.3.4"
features = [
  'console',
  'CanvasRenderingContext2d',
  'Document',
  'Element',
  'HtmlElement',
  'HtmlBodyElement',
  'HtmlButtonElement',
  'HtmlCanvasElement',
  'EventTarget',
  'Node',
  'Window',
]

bodyの最後にpタグでHello from Rust!の追加

#[wasm_bindgen]
pub fn hello_p() -> Result<(), JsValue> {
    let window = web_sys::window().expect("no global `window` exists");
    let document = window.document().expect("should have a document on window");
    let body = document.body().expect("document should have a body");

    // Manufacture the element we're gonna append
    let val = document.create_element("p")?;
    val.set_inner_html("Hello from Rust!");

    body.append_child(&val)?;

    Ok(())
}

速度を比較してみる

あまりいい例ではないのですが、(スタックオーバフローになるような何も考えてない方法の)フィボナッチ数列による速度比較をして見ました。

Javascript版

function fib(n) {
    return n < 2 ? n : fib(n - 2) + fib(n - 1);
}

WebAssembly(Rust)版

#[wasm_bindgen]
pub fn fib(n: u32) -> u32 {
    match n {
        0 => 0,
        1 => 1,
        _ => fib(n-2) + fib(n-1),
    }
}

3回実施した平均です。

fib(n)

n Javascript(ms) Rust(ms) 割合(R/J)
10 0 0 -
30 17 14 1.2
40 1138 910 1.25
41 1842 1463 1.26
42 2995 2388 1.25
43 4821 3802 1.27
43 7868 6134 1.27

Rust版の方が2割増し程度の性能が出ました。

アルゴリズムが大変悪いので比較的低いnでPageがクラッシュしました。

ただこれだけなのに、速度差を体感できます。

画像を受け渡してみる

Rust側に、canvasのイメージバイナリを渡して透過度をあげてみます。

↓こちらに置いておきます。

github.com

Rust側は比較的簡単に実装できます。

ただバイナリは直列データとして扱う必要があるようです。

線の描画などはもっと別の方法が良いかもです。

Rust側

#[wasm_bindgen]
pub fn to_tranparent(screen: & mut Screen, bytes: & mut [u8]) {
    for i in 0..screen.height {
        for j in 0..screen.width {
            let offset = 4 * (screen.width * i + j);

            bytes[offset] = bytes[offset];
            bytes[offset + 1] = bytes[offset + 1];
            bytes[offset + 2] = bytes[offset + 2];
            bytes[offset + 3] = 25;//透明度を上げる
        }
    }
}

htmlはVue.jsの読み込みと、canvas、inputを置いています。

html

    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="./bootstrap.js"></script>
        <canvas
          id="canvas"
          height="600"
          width="400">
     </canvas>
     <br />
     <input @change="uploadFile" name="image" type="file" />
      <button v-on:click="to_transparent">to_transparent</button>

glue以外の記述部分です。

javascript

var app = new Vue({
    el: '#app',
    data: {
      text: '',
      left_val: '',
      right_val: '',
      number:10,
      message: '',
      canvas: null,
      duration: ''
    },
    methods: {
      to_transparent: function() {
        // canvas取得
        const canvas = document.querySelector('canvas');

        // Rust上のScreen構造体取得
        const screen = new sample.Screen(canvas.width, canvas.height);
        // context取得
        const ctx = canvas.getContext('2d');
        // canvas
        var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
        // Rustの透過処理を呼び出し
        sample.to_transparent(screen, imageData.data);
        // 透過処理後のバイナリをimageに変換
        const image = new ImageData(imageData.data, screen.width, screen.height);
        // canvasに書き戻す
        ctx.putImageData(image, 0, 0);
      },
      uploadFile: function(e){
        // ローカルのファイルアップロード部分
        var canvas = document.querySelector('canvas');
        var ctx = canvas.getContext("2d");
    
        let files = e.target.files;
        var file = files[0];
    
        var image = new Image();
        var reader = new FileReader();
    
        reader.onload = function(evt) {    
          image.onload = function() {
            // canvasにイメージを書き込む
            ctx.drawImage(image, 0, 0);
          }
          image.src = evt.target.result;
        }
        reader.readAsDataURL(file);
      },
    }
})

上記でアップロードした画像の透明化ができます。

まとめ

Rustは非常に面白い特徴を持った言語ですね。WebAssemblyへの対応も進んでおり、比較的楽に開発を進めて行く事ができそうです。

全ての環境では動かない部分もあるので、まずは社内的なプロジェクトのフロントに採用するとか、 限定した環境をうたえるなら結構アリだなと思わせるものでした。

最後になりましたが、アスタミューゼでは現在、エンジニア・デザイナーを絶賛大大大募集中です! 興味のある方はぜひ下記バナーからご応募ください!!

*1:Scalaが全くしなくて良いという意味でも無いですが

*2:C++に見るRAIIに近いが、さらにエレガントな方法

*3:Rustのstructに近い概念はcase classでしょうか?

*4:Scalaではほぼ記述しない

*5:ScalaはOptionとEnumは別のもの

*6:https://ja.wikipedia.org/wiki/%E3%82%A2%E3%83%B3%E3%83%88%E3%83%8B%E3%83%BC%E3%83%BB%E3%83%9B%E3%83%BC%E3%82%A2

*7:日本語版:https://doc.rust-jp.rs/book/second-edition/

*8:Typescriptのglueも!

*9:なんというべきなのか?

アスタミューゼに入社して1年が経った

こんにちは、開発部のomiです。
4月といえば、出会いと別れの季節ですね。節目の月ということで新たな環境へチャレンジする方も多いのではないでしょうか。
かく言う私も昨年2018年の4月にアスタミューゼに入社してからちょうど1年が経ちました。
そこで今回はこの1年を振り返り、この1年で感じたこと・転職してよかったことなどを書き出してみようと思います。

そうだ、転職しよう

f:id:astamuse:20190420182211p:plain
私が前職から転職を考え始めたのは一昨年2017年の冬でした。
新卒で入社したSIer2年目の冬。入社してからというもの、プログラミングがしたいという思いはずっと心にありながらも開発業務に携わることはほぼなく。
内製化を進めていると言っていたような気がするけれどこの会社では開発業務はできないんだろうな、というのをじわじわ理解してきたタイミングでした。
開発を委託しているベンダーのおじさんたち(仲良し)が羨ましくて堪らなかったんです。
このままじゃ後悔する!!!そう感じて転職を思い立ちました。

そこからは衝動的に行動しだし、あっという間に転職活動を終えました。
普段の業務後に走って退社し、面接・面接・面接 ・・・
f:id:astamuse:20190420182253p:plain
全体を通し4週間で転職活動を終え、11社に出向き面接は計15回受けました。
この期間は自分にとってとても大切な時期だったと思います。自分を見つめ直す時期でした。
自分にとって何が大事なのか、何が楽しいのか、何をしたかったのか、、。
考えれば考えるほど今の環境ではだめだという思いと共に、未来へのワクワクが増すばかりでした。
そして同時に、たくさんの企業に出向き覗いてみてはじめて、外の世界にはこんなことをやっている企業があって、こんな働き方があって、こんな人たちが集まっているんだ、と世界の広さを知りました。(新卒の時には3社くらいしか行かなかった)
自分が知っている世界は本当に本当にちっぽけな一部分でしかなかった、自分に合う・自分が求めている世界は誰にでも必ずある、そう感じました。

アスタミューゼとの出会い

面接を受け始めて2回目でアスタミューゼの一次面接にあたりましたが、この時点から最後までずっと変わらずアスタミューゼが第一志望でした。
今の私の上司である、ぶちょーとの1対1でしたが、めちゃめちゃデキる人なんだろうな・・と感じさせるスマートさと同時に、相手を安心させる自然体さ・こちらの話を親身に聞いてくれる姿勢に感動したのを覚えています。
正直やっている事業の話はよくわかっていなかったのですが(ぶちょーごめんなさい)、この人が選んだ人たちは絶対良い人に決まってる!と思いました。
二次面接ではさらに上のボスと1対1でお話ししましたが、こちらも更に完璧すぎるスマートさと絶対的な安心感(お二人とも、スマートなのに柔らかい雰囲気)を感じて、自分もこの会社の一員になりたい!と思いました。
結局、内定を2社もらいその他も5社程選考が進んでおり返事を急かされている状況でしたが、アスタミューゼにお食事会に誘われた時点で、ほぼ内定デショ!(^_−)−☆とエージェントに言われたので全て辞退しました。(今考えるとやばい)

アスタミューゼに入社した

それからの1年はあっという間に過ぎて行きました。
やはりわからないことだらけでした、むしろ分からないことしかありませんでした。
でもそれが嬉しかった、これを求めていた!と感じました。
分からない = 新たな学びのチャンスであるので、壁にぶつかりまくったこの1年は前職の2年間よりは遙かに自分を成長させてくれたと感じました。

そして想像した通り、皆スーパーエンジニアではあるけれど、暖かく優しくユーモアのあるいい人しかいませんでした。
SIerからベンチャーであるアスタミューゼに転職した私にとってははじめてのことばかりでした。
はじめての自分のデスク(前職はフリーアドレスだった)、はじめてのモニタ(前職はノートonly)
はじめてのSlack、はじめてのgit、その他諸々はじめてのアプリ・ツール
はじめてのリモート会議
はじめてのフレックス勤務
はじめての外部カンファレンス参加
はじめての新規開発、はじめてのScala
はじめてのブログ、はじめての勉強会
はじめてのオフィス引っ越し
・・・
f:id:astamuse:20190420182315p:plain
どれも今となっては当たり前になりましたが、日々受ける新しい刺激は多く、楽しみながら仕事が出来ています。
開発部以外の部署の方も仲良くしてくれる方が多く、毎日を楽しく過ごさせていただいています、感謝でいっぱいです。

転職してみて

転職してみて今の気持ちとしては、
いっぱい開発できて嬉しい!学ぶべき人が周りにたくさんいて嬉しい!
周りの人はみんな優しいし、面白い!とにかくいろいろ自由で嬉しい!
勤務形態や外見(すみません私金髪にしました)もそうですが、自分で試してみたい技術などに寛容な点や、各人のやりたいこと・興味あることなどをぶちょーやボス達がとても尊重して下さっていて本当にいい会社だなと思います。
そして今まさに会社がどんどん大きくなっていっているのが見ていて嬉しいし、面白い!ワクワクする!!!!!
引き続き、開発部の皆さんと共に学びを忘れず楽しくお仕事して会社に貢献できるように励んで参ろうと思います。

そんな弊社ではエンジニア・デザイナーを募集中です!興味を持っていただけましたらバナーからよろしくお願いします!
面白楽しく一緒に働いてくださる仲間が増えるのを楽しみにしております!
以上です。読んでいただきありがとうございました。

無線LANアクセスポイントのリソース状況を可視化した話

こんにちは。開発部のtorigakiです。 弊社では無線LANアクセスポイントにYamaha WLXシリーズを使用しているのですが、最近社内のwifi環境が不調になることがあります。

そこで何が原因なのか探るためアクセスポイントのリソース使用状況を可視化することを試みました。

WLXシリーズにはWebUIが用意されており、CPUやメモリ使用状況などブラウザから確認することもできるのですが、このWebUI上からでは過去の状況を把握することができず、不調になったときの状況がわからないため、定期的にリソース情報を取得し、グラフ化して時系列で状況を把握することができるようにしましたので、今回はそのお話をさせていただければと思います。

リソース情報を取得する

無線アクセスポイントからリソース情報を取得する方法は色々あるかと思いますが、今回はtelnetとexpectを組み合わせて、定期的にアクセスポイントにログインして情報を収集する方法にしました。

以下は情報を収集するスクリプトの例です。 「IP」にはリソース取得対象のアクセスポイントのIPアドレスを指定します。

#!/bin/bash

expect -c "
set timeout 5
spawn telnet ${IP}
expect Password:\  ; send \"XXXXX\n\"
expect \"> \" ; send \"show environment\n\"
expect \"> \" ; send \"exit\n\"
" > ${IP}_log

WLXシリーズは、showコマンドにて様々な情報をとることができます。 「show environment」を実行すると、以下のような結果が得られます。

CPU:  20%(5sec)  31%(1min)  36%(5min)    メモリ: 40% used
ファームウェア: internal  設定ファイル: 0
起動時刻: 2019/04/11 13:33:03 +09:00
現在の時刻: 2019/04/12 16:47:14 +09:00
起動からの経過時間: 1日 03:14:11
セキュリティクラス レベル: 1, FORGET: ON, TELNET: OFF
筐体内温度(℃): 47
電源: PoE
連携状態: RT連携

上記の結果をテキストファイルに保存し、必要な情報を抜き出します。 今回の場合、CPU、メモリ、筐体温度の情報を抜き出しています。

抜き出した情報を可視化する方法

使用したツール

リソース状況を可視化するツールとして、今回はInfluxDBGrafanaを使用しました。

ツールをインストールしたOSは「Ubuntu 18.04.2 LTS」です。

InfluxDB

InfluxDBはオープンソースの時系列データベースで、ログなどの時間を軸とした連続したデータを蓄積、検索することを得意としているデータベースです。

以下はインストール例です。 設定はデフォルトのままです。

wget https://dl.influxdata.com/influxdb/releases/influxdb_1.7.4_amd64.deb
sudo dpkg -i influxdb_1.7.4_amd64.deb

systemctl start influxdb

Grafana

Grafanaはデータ時系列の分析用ダッシュボードのツールです。

以下はインストール例です。 設定はデフォルトのままです。

wget https://dl.grafana.com/oss/release/grafana_6.0.2_amd64.deb 
sudo dpkg -i grafana_6.0.2_amd64.deb 

systemctl start grafana-server

InfluxDBにデータをインサートする

無線アクセスポイントから取得した情報(CPU、メモリ、筐体温度)をInfulxDBにインサートしていきます。 インサート方法はcurlコマンドを使用しました。

以下はインサートスクリプトの例です。 「TAG」に無線アクセスポイントのIPアドレス、「TIME」に情報を取得した時間をunixtimeに変換して入れています。 「DB」はInfluxDBに予め作成したDB名が入ります。

# CPU
curl -o /dev/null -s -i -XPOST "http://localhost:8086/write?db=${DB}&precision=s" 
 --data-binary "${TAG}_cpu,ip=${TAG} cpu=${CPU} ${TIME}"

# MEM
curl -o /dev/null -s -i -XPOST "http://localhost:8086/write?db=${DB}&precision=s" 
 --data-binary "${TAG}_mem,ip=${TAG} mem=${MEM} ${TIME}"

# Temperature
curl -o /dev/null -s -i -XPOST "http://localhost:8086/write?db=${DB}&precision=s"  
 --data-binary "${TAG}_temp,ip=${TAG} temperature=${TEMP} ${TIME}"

上記を実行すると、InfluxDBには以下のようにインサートされます。

# テーブル一覧
> show measurements;
name: measurements
name
----
192.168.11.101_cpu
192.168.11.101_mem
192.168.11.101_temp

# データ内容
> select * from "192.168.11.101_cpu" limit 10;
name: 192.168.11.101_cpu
time                cpu ip
----                --- --
1553585400000000000 57  192.168.11.101
1553585700000000000 52  192.168.11.101
1553586000000000000 60  192.168.11.101
1553586300000000000 52  192.168.11.101
1553586600000000000 54  192.168.11.101
1553586900000000000 61  192.168.11.101
1553587200000000000 54  192.168.11.101
1553587500000000000 68  192.168.11.101
1553587800000000000 61  192.168.11.101
1553588100000000000 53  192.168.11.101
>

1分ごとに取得しているので、DBには1分ごとの情報がインサートされていきます。 各IPアドレスごとに、CPU、MEM、TEMPのテーブルが作成されデータがインサートされていきます。

Grafnaで可視化する

Grafanaにブラウザでアクセスし、グラフを作成します。

InfluxDBからデータを取得するクエリですが、以下のように指定します。 以下はCPUステータスを取得する場合の例です。

select cpu  from /.*_cpu.*/ where $timeFilter order by asc

上記のように、ワイルドカードを指定することにより、IPアドレスごとに作成したテーブル情報を一括で取得することが可能です。 このクエリを実行できるようにするために、InfluxDBにはテーブル名にIPアドレスを入れて、CPUステータスだけを入れるテーブル、メモリステータスだけを入れるテーブルという感じでインサートしていくことがポイントとなります。

実際にGrafanaで作成したグラフが以下になります。

f:id:astamuse:20190415124459p:plain

munin等の場合、1つグラフに1の機器のグラフしか作成できませんが、Grafanaでは複数機器の値を一括で取得すると、1つのグラフに複数機器の状態を重ねて表示できるところができるので便利です。

まとめ

今回は無線LANアクセスポイントのリソース状況を可視化するあたり工夫した点について紹介させていただきました。 少しでも無線LANアクセスポイントの運用されている方のお役に立てれば幸いです。

弊社では引き続きエンジニア・デザイナーを募集中ですので、ご興味のある方は下からご応募いただければと思います。

Copyright © astamuse company, ltd. all rights reserved.