TECH PLAY

株式会社mediba

株式会社mediba の技術ブログ

167

こんにちは、最近 WAF WAF しているインフラストラクチャー部の沼沢です。 WAF(Web Application Firewall) といえば、SQL インジェクションなどの Web アプリケーションへの攻撃をブロックするためのファイアウォールです。 そんな用途とは少し異なり、AWS WAF を使って CloudFront や Application Load Balancer へのアクセス制限をする機会が多いのですが、自分自身、WAF というものにあまり縁が無かったからなのか、AWS WAF の設定が少々ややこしくてわかりにくかったので、自分なりに if 文に例えてみました。 なお、あくまで理解するためだけの例えとなりますので、正確には以下の公式ドキュメント等を参考にしていただければと思います。 ルールの使用 - AWS WAF と AWS Shield アドバンスド ウェブ ACL の使用 - AWS WAF と AWS Shield アドバンスド Condition まずは最小単位の Condition からです。 Condition は if 文の1つの条件と捉える if (ipaddress == "xxx.xxx.xxx.xxx"){ // アクセス元の IP Address が "xxx.xxx.xxx.xxx" だったら OK! return "Access OK"; } 上記の if 文で言うと、 ipaddress == "xxx.xxx.xxx.xxx" が Condition(IP addresses 相当) にあたります。 Rule Rule は if 文の条件 (Condition) の塊と捉える if (ipaddress == "xxx.xxx.xxx.xxx" && method == "POST") { /* アクセス元の IP Address が "xxx.xxx.xxx.xxx" かつ * * HTTP Method が "POST" だったら OK! */ return "Access OK"; } 上記の if 文を Rule と仮定するとして、 以下の2つの Condition の塊が Rule ipaddress == "xxx.xxx.xxx.xxx" (IP addresses 相当) hostHeader == "hoge.com" (String matching 相当) このような、 1つ以上の Condition の組み合わせが Rule です。 1つの Rule 内では Condition は 全て AND で繋がります 。 1つでも異なるとその Rule ではマッチしません。 Web ACL Web ACL は if 文本体と捉える if (!url.startsWith("/hoge/") && userAgent == "hogehoge") { /* URL の Path が "/hoge/" で前方一致 かつ * * User-Agent が "hogehoge" だったら OK! */ return "Access OK"; } else if (ipaddress == "xxx.xxx.xxx.xxx") { // アクセス元の IP Address が "xxx.xxx.xxx.xxx" だったら OK! return "Access OK"; } else { // それ以外は NG! return "Access NG"; } 上記の if 文全体を Web ACL と仮定するとして、上から 以下の2つの Condition を含む Rule が 優先順位 1 の Rule (最初の if 文) !url.startsWith("/hoge/") (String matching 相当) userAgent == "hogehoge" (String matching 相当) 上記2つとも一致すれば許可 以下の1つの Condition を含む Rule が 優先順位 2 の Rule (次の else if 文) ipaddress == "xxx.xxx.xxx.xxx" (IP addresses 相当) 上記に一致すれば許可 全ての Rule にマッチしなかった(else ブロックに入った)ものの挙動を定義(Web ACL のデフォルト動作相当) 全ての Rule にマッチしなかったリクエストを拒否 なお、Rule 同士は 全て OR で繋がります 。 (else if で繋がると考えた方がわかりやすいかも?) そのため、Rule は 優先順位ごとに先勝ちで評価されます 。 まとめ 今回紹介したように普段使い慣れている(?) if 文に例えてみるとなかなか分かりやすかったので、皆様の参考になれば幸いです。 if 文同様、あまり複雑な条件にすると他の人が解けない難解な条件となり、後々とても後悔することになるので、なるべくシンプルな組み合わせを心がけたいですね。
アバター
こんにちは、制作部 フロントエンドエンジニアの武田です。 入社して5ヶ月経ちました。 ECMAScript の推し proposal は Cancellation API です。 今回は開発には切っても切れない Babel 、そのプリセットである babel-preset-env についてお話します。 このプリセットは Browserslist 1 の記述で compat table 2 を利用し、指定された環境にあったトランスパイルができるプリセットです。 使い方についての記事はすでに多くありますので今回は丁寧に触れません。 babel-preset-env を試す – アカベコマイリ 最新版で学ぶwebpack 3入門 - ICS MEDIA ピンと来ない方は、JavaScript をとりまくビルド環境のスケーリングに寄与しそう、と大きく理解していただいてよいです。 対象となりそうな人 Babel を使ったトランスパイル環境を作ったことがある人 babel-preset-env をすでに使っている人、知っている人 Babel 沼にハマったことがある人 ES2017 以降も Babel に付随するパッケージをアップデートしながらやっていきな人 書くこと そんな便利なプリセットの alpha 版である @2.0.0 を試しに使ってみて 2.0 で何が変わるか 2.0 以降、babel-preset-env を使ったトランスパイルがどう変わりそうか それらを踏まえて、今後の Babel の動向をほんのり少しだけ について書いていきます。 この記事を書いている時点の2系は alpha 版となっておりそれらを利用して書いていますので、 プロダクション環境への導入は安定版となってから十分に吟味した上で利用ください。 サンプル https://github.com/mediba-takeda/babel-env-sandbox master ブランチ = 2系、feature/preset-env@v1.x ブランチ = 1系 です。 ※ 切り替えで試してみる際は node_modules を一旦消してくださいね。 v2 でトランスパイルしたものを gh-page に上げてみました。 https://mediba-takeda.github.io/babel-env-sandbox/ ES2015, 2016, 2017 の構文を拾い環境を変えながらトランスパイルしています。 書かないこと サンプルで使用しているコードそのものについて バンドラーとして使用した webpack について babel-preset-env v1 => v2 における大きな変更点 v2になって何が変更されるかというと、大きなところでは useBuiltIns 設定の値が変更されます。 { "presets": [ ["env", { "targets": { "browsers": [ "ios >= 9" ] }, // v1 まで は true | false "useBuiltIns": true // v2 からは "usage" | "entry" | false "useBuiltIns": "usage" }] ] } useBuiltIns って何の設定ですか A way to apply babel-preset-env for polyfills (via babel-polyfill). です。要は babel-polyfill を引き込むかどうかの設定になります。 1系ではコード上に @import 'babel-polyfill' の記述が必要です。 1系で "useBuiltIns": true に設定しトランスパイルした、コンソールへのデバッグ表示が下記になります。 Using plugins: ブロックでこのブラウザターゲットに必要なものがわかります。 その後に Using polyfills: ブロックでさらに穴埋めする Polyfills 。なんですが… …便利ですけどこんなに要らないです…。使ってないものの方が多い…。 これは babel-preset-env を使用していなくても抱える悩みですね。 @import 'babel-polyfill' の全マシで要らないものまで引き込みたくない気持ちです。 v2 は余計なもの引き込まない "useBuiltIns": "usage" という v2 の新しい設定をしてトランスパイルしてみます。 2系ではこの設定を有効にした場合 @import 'babel-polyfill' の記述は必要ありません。 Using polyfills: の箇所がちょっと変わっていますね。 デバッグログだけだと少しわかりづらいですが、静的解析を行いコード上の使用に応じて穴埋めしています。 これで不要に Polyfill だけで肥大化することはなくなります。 ちなみに "useBuiltIns": "entry" の使用はエントリーファイルが1つの場合のみとして v1 時の  true と同等の動きをします。 それでもハマりそうな Babel 沼 ただケースによっては問題が出てきます。 3 Object.assign のような静的なメソッドは変換や解析がうまくいきますが、 String.prototype.includes Array.prototype.includes のようなインスタンス/プロトタイプメソッドが、コンテキスト上で arr.includes('foo') のように利用される場合、 arr の型判定をした上で変換、ということができません。 これについてはすでに issue にあがっておりまだ落としてどころがないように見えます。 4 型推論しようとすれば TypeScripts, Flow のようなアノテーションか型定義ファイルへのリファリングをしてから変換する必要があります。 現状どうするのかといえば、それ用に別の Polyfill ライブラリで埋めるか、明示的に @import 'core-js/modules/es7.array.includes' するかどちらかしかないでしょう。 v2 のその先、Babel の動向を少しだけ v2 でこの大きな変更は v1 がリリースされてすぐに issue で議論され 5 、4月段階でマージされたあとに v2.0.0-alpha.6 としてリリースされています。 6 次のロードマップとしては transform-runtime を機能的にこちらに移す話も上がっています。 7 日付としてはちょっと古いミーティングですが、下記のような記載も見受けられます。 8 Move babel-preset-env into core (or a package (like babel) that includes it by default) コアに入るような入らないような話もちらほら出ているということですね。 We released babel-preset-env v1.6 yesterday with support for node 8 and browserslist’s chromeandroid target! 2.0 is coming soon! #babeljs — Brian Ng (@existentialism) 5 July 2017 コアコミッターの Tweet を信じ、カミングスーンな安定版の 2.0 を待つことにします。 babel-preset-env v2 まとめ v2 で Polyfill がスマートになる ただ課題はまだありそうだから慎重にいったほうが良さそう v2 以降の動向を watch しておくと良さそう babel-preset-env が必ずしも銀の弾丸ではありませんが、ECMAScript の策定・採択とブラウザ実装に追従しながら、環境・コードともにスケールできると一番ステキですね。 本日は以上です。最後まで読んでいただき、ありがとうございました。 補足 Web プラットフォーム = ブラウザに限った話でしたが、babel-preset-env は Node.js のバージョン、Electron のバージョンにも有効です。 ES2015 〜 のスクリプトに関しては、UglifyJS が有効ではありません。 harmony ブランチである uglify-es では有効ですが、Babel の推奨する babili を使用してminifyしています 。 リポジトリでは webpack を使用していますが、もちろんエントリーファイルが 1 ファイルであれば babel-cli も有効です。 レガシーな端末での動作を確実にするものではありません。トランスパイルによるファイルの肥大化・端末自身の処理速度によってはパフォーマンスが著しく落ちることもあります。 サンプルの検証について サンプルで作ったものは下記端末、環境で確認しています。 ※ 多端末までは確認していませんのでご注意ください。 ※ development, recent(=last 2 version) 環境については特に検証の対象としていません。 環境指定 端末 OK android>=2 Android2.2 on IS05 ◯ android >= 4 Android4.1.2 on Xperia UL SOL22 ◯ ie >= 10 IE10 on Win7(x86) 9 ◯ ios >= 9 iOS9 on iPhone5S ◯ chrome >= 59 Chrome60 on Mac Sierra ◯ https://github.com/ai/browserslist   ↩︎ https://github.com/kangax/compat-table   ↩︎ サンプルリポジトリの Array.prototyp.includes参照   ↩︎ https://github.com/babel/babel-preset-env/issues/284   ↩︎ https://github.com/babel/babel-preset-env/issues/84   ↩︎ https://github.com/babel/babel-preset-env/pull/241   ↩︎ https://github.com/babel/babel-preset-env/issues/246   ↩︎ https://github.com/babel/notes/blob/master/2017-04/april-08.md   ↩︎ Microsoft 配布の VM イメージ  ↩︎
アバター
こんにちは!制作部 デザイナーの森本です。 最近は、スマートフォンなどの端末の解像度が上がってきているため、アイコンであっても大きな画像が必要になりますが、多用するとページの描画速度の低下にも繋がってしまいます。 そこで、画像を多用せずとも高解像に対応できる「アイコンフォント」を簡単に作る方法をご紹介いたします。 今回は最近使ってみて一番シンプルで使いやすいと感じたジェネレーターサイト「IcoMoon」を使い、 SVGからアイコンフォントを作成していきます。 IcoMoonとは? SVG形式のアイコンデータをアップロードするだけで自作のオリジナルアイコンを作成できる便利なWebサービスです。 また、商用でも無料で使用可能なアイコンが豊富に提供されています。 ※ IcoMoon(Library内)の各アイコンには複数のライセンス形態がありますので、使用する前によく確認しましょう。 アイコンフォントを使うメリット・デメリット メリット ビットマップではなくベクターとして扱えるので、解像度に依存せず拡大縮小が可能 1つのフォントファイルにまとめる事ができるので、画像と比較するとHTTPリクエストを減らせる トータルの容量を画像よりも抑えることができる 大きさや色をCSSでカスタマイズできる 導入が簡単でメンテナンスの効率もアップ! デメリット 一部古いブラウザではアイコンフォントが使用できない場合がある(参考 : Can I use web font? ) アイコンフォントが無効な環境の場合、アイコンが表示されなかったり、意図しない文字や記号が表示されたりしてしまう アイコンフォントの作成の流れ 1. IllutratorでSVG形式のデータを作成する パスでアイコンを描く アートボードは正方形にする(好きなサイズでOK) 線やフォントはアウトライン化した上で、必ずパスファインダーで結合する 余計なアンカーポイントを削除しておくことでアイコンデータが多少軽くなります SVGで書き出し保存する 「ファイル」→「別名で保存」→「ファイル形式」を「SVG(svg)」に変更して保存 2. IcoMoonを使って自作のSVGデータをアイコンフォントへ変換 IcoMoonはこちら http://icomoon.io/ 無償で使えるアイコンは、IconMoon-Freeで現在 490種類あります 2-1.右上の「IconMoon App」というボタンをクリック 2-2.たくさんのアイコンが表示されるページに遷移します このページの「IcoMoon - Free」の中のアイコンについては無料で利用できるようです。 2-3.左上の「Import Icon」ボタンをクリックし、先ほど作ったSVGデータを登録する 2-4.登録できました 2-5.アイコンデータを選択する 自分で作ったアイコン以外にも使いたいものがあればここで一緒に選択する。 2-6.右下の「Generete Font」ボタンをクリック 2-7.右下の「Download」ボタンをクリックしてデータをダウンロード! この画面でファイル名の変更などちょっとした編集を行うことができる 3. HTMLとCSSに反映する IcoMoonでダウンロードしたデータを解凍すると以下のようになっています demo-files demo.html fonts Read Me.txt selection.json style.css アイコンフォントを表示させるのに必要なファイル index.html(作成する) style.css fontフォルダ内の4つのフォントファイル icomoon.eot icomoon.svg icomoon.ttf icomoon.woff ※ 4つすべてアップしないと環境によってアイコンが表示されない場合があります index.htmlを作成し、タグを記述する index.html内でアイコンを表示させたい場所の要素にCSSクラスをつける <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>IcoMoonを使ってSVGからアイコンフォントを作る</title> <link rel="stylesheet" href="style.css"> </head> <body> <!-- アイコン --> <span class="icon-webiconfont-01"></span> <span class="icon-webiconfont-02"></span> <span class="icon-headphones"></span> </body> </html> IcoMoonのこちらの画面からソースコードをコピーすることもできます htmlのソースコードがでてきました 4. では表示してみましょう! アイコンのカスタマイズをする場合は style.css を編集します [class^="icon-"], [class*=" icon-"] { font-family: 'icomoon' !important; speak: none; font-style: normal; font-weight: normal; font-variant: normal; text-transform: none; line-height: 1; /* アイコンのカスタマイズ */ font-size: 300px; color: #44aac1; } カスタマイズ後はこちら 大きくしてもキレイですね! さいごに 大きさや色の変更が発生した際にも画像と比べてカスタマイズが簡単にできるため、とても効率的だと感じました。 簡単に実装が可能で学習コストもかからないので、単色のシンプルなアイコンであれば取り入れるととても便利だと思います。 また、ブラウザ中でベクター画像を扱うには、SVGを直接記述する方法もあります。 ブラウザ対応状況の違いやメンテナンス性の違いなど、それぞれメリットやデメリットがありますので、一概にどれが正解と言えるものではないですが、色々な手段を知っておくことで選択肢が広がり適宜対応できるのではないかと思います! 最後までご覧いただきありがとうございました!
アバター
こんにちは。広告システム開発部の竹谷です。 私のチームのサービスで、Laravelのバージョンを5.2から5.3にあげたときの話を書きます。 すでに5.4も出ていますが、段階的にまずは5.3にあげました。 PHPのバージョンは7.0.9です。 ドキュメント ドキュメントはこちらになります。 https://laravel.com/docs/5.3/upgrade#upgrade-5.3.0 composer.jsonの修正からやってみる まずは、composer.json を修正して、 $ composer install をやり直してみます。 - "laravel/framework": "5.2.*", + "laravel/framework": "5.3.*", "laravelcollective/html": "~5.0", "webpatser/laravel-uuid": "2.*", "aws/aws-sdk-php": "3.*", @@ -16,8 +16,8 @@ "fzaninotto/faker": "~1.4", "mockery/mockery": "0.9.*", "phpunit/phpunit": "~4.0", - "symfony/css-selector": "2.8.*|3.0.*", - "symfony/dom-crawler": "2.8.*|3.0.*", + "symfony/css-selector": "2.8.*|3.1.*", + "symfony/dom-crawler": "2.8.*|3.1.*", エラーがでます。 > php artisan optimize [ErrorException] Declaration of App\Providers\EventServiceProvider::boot(Illuminate\Contracts\Events\Dispatcher $events) should be compatible with Illuminate\Foundation\Support\Providers\EventServiceProvider::boot() これは、ドキュメントの Application Service Providers という項目にあるとおり、下記の3つのファイルのboot関数が変わったことが原因のようです。 boot関数の引数を消します。 app/Providers/AuthServiceProvider.php app/Providers/EventServiceProvider.php app/Providers/RouteServiceProvider.php AuthServiceProvider.php - public function boot(GateContract $gate) + public function boot() { - $this->registerPolicies($gate); + $this->registerPolicies(); EventServiceProvider.php - public function boot(DispatcherContract $events) + public function boot() { - parent::boot($events); + parent::boot(); RouteServiceProvider.php - public function boot(Router $router) + public function boot() { - parent::boot($router); + parent::boot(); もう一度、 $ composer install すると今度は通りました。 動かしてみる とりあえず動かしてみます。 エラーが出ます。 FatalErrorException in Controller.php line 13: Trait 'Illuminate\Foundation\Auth\Access\AuthorizesResources' not found ドキュメントの The AuthorizesResources Trait という項目に 「AuthorizesResourcesは、AuthorizesRequestsに統合したから削除しておきましょう。」というような記載があるので削除します。 app/Http/Controllers/Controller.php use Illuminate\Foundation\Auth\Access\AuthorizesRequests; -use Illuminate\Foundation\Auth\Access\AuthorizesResources; class Controller extends BaseController { - use AuthorizesRequests, AuthorizesResources, DispatchesJobs, ValidatesRequests; + use AuthorizesRequests, DispatchesJobs, ValidatesRequests; } 再度動かしてみると、別のエラーが出ます。 ReflectionException in Container.php line 749: Class App\Http\Controllers\Auth\LoginController does not exist ドキュメントの Authentication Scaffolding という項目に記載があります。 https://github.com/laravel/laravel/tree/5.3/app/Http/Controllers/Auth にある4つのコントローラーをコピペして新しく置きます。 AuthController は不要になります。 AuthController でやっていたカスタマイズは各コントローラーに引きつぎましょう。 routes.phpに以下も追加。 Auth::routes(); 私たちのサービスで困った点 私たちのサービスで困った点としては、次のようなことがありました。 5.2では以下のような感じでログインに使うパラメータを指定していたのですが、その指定の方法が変わっていました。 protected $username = 'login_id'; 5.3だと、username関数をオーバーライドする。 public function username() { return 'login_id'; } 他のprotected メンバ変数もオーバーライドするように変更になったようです。 ここまでで、一通り動くようになりました。 ドキュメントの変更点を確認 あとは、ドキュメントの変更点を読んで、サービスに影響がないかを確認します。 インターフェイスが変わっていたり、動作が変わっているところは要注意ですね。 まとめ そんなにひっかかるところもなくスムーズにバージョンアップができました。 なるべく早く5.4にアップグレードしたいと思います。
アバター
制作部でデザイナーをしている渡邉と申します。 昨年、2016年の4月に入社して6月の頭で配属1年になります。 学生時代は現代アートを専攻する科でカリキュラムにあらがって写真とか撮ってました。 さて今回は、私が新米Webデザイナーとして配属されてからのたくさんの学びの中から、つまずいた点や重要だと感じた点について紹介し、2年目にどう繋げていくべきかを書こうと思います。 自分のすぐ後を歩いている学生さんやWebデザイナーを目指している方の参考になれば嬉しいです。 また初心を忘れないための備忘録も兼ねて、書いていきます。 では早速。。。 目次 こういうところに注意しよう(デザイン編) こういうところに注意しよう(コーディング編) これらを身につけた上で2年目に挑戦すること 最後に こういうところに注意しよう(デザイン編) 作業中はセルフツッコミを入れ続けよう デザイン=ビジュアルを整えること、と捉えがちですが、デザインは問題解決の手段です。「誰が抱えてるどんな問題を解消するために、何ができるのか」「果たしてこの一手は適切なのか」を考えながら作業すると自ずと論理立てられたデザインができると思います。 いいデザインを見つけてどんどん真似しよう 同じような配色だったり近い文字量のバナーを参考するといいです。引き出しが増えて確実にパワーアップします。(私は学生時代に学んだ著作権の知識がブレーキをかけてしまってましたが、作ってるうちに全然違う見た目になっていくのでパクリだと不安になることはないです。) また、参考資料を探すときは英語で検索するとオシャレなアイデアが見つかりやすいです。 “一番手こずった場合"を想定してスケジューリングしよう フィードバックでは自分が想定してなかった点を指摘される訳なので、修正は自分になかったアイデアを具現化していく作業になります。思いのほか時間がかかるので、早めに提出できるスケジュールを組みましょう。 フォルダ・ファイル・レイヤーは全てにきちんと命名し整理整頓しよう 会社で作ったデータは他の人に渡したり使い回すことがあります。誰に渡しても使いやすいように職場でのユーザビリティにも気を配りましょう。「長方形3のコピーのコピー」とか「背景写真の候補2(あとで消す)」とかナシです。 ちなみに複製したレイヤーに「〜のコピー」を表示させない方法は こちら 、 レイヤーの名前を一括で管理できるプラグインについては こちら です。 引き算のデザインで迷走を防ごう 引き算のデザインとは情報量のピークから削っていって完成系はシンプルにする方法です。まず必要そうな情報をとにかく盛り込み情報量をMAXにします。羅列した情報に優先順位をつけて画面の左上から順番に配置していきます。書かなくても伝わる要素、キャンバスからはみ出た要素は容赦なく捨てちゃいましょう。「順序立てた要素を」「どうシンプルに組むか」を意識しましょう。 完璧を求めずまずは終わらせよう デザイナーたるもの人が気付かないような細部までこだわることはとても大切。文字のエフェクトに悩んでいたら暗くなっていたなんてこともありましたが、納期に間に合わなかったら元も子もありません。 自分の力で考えることが非常に大事ですが、先に進めなさそうな時はすぐに先輩に相談しましょう。間違った方向で進めるよりもよほどいいです。決してネガティブなニュアンスではなく「絶対に間に合う範囲でどこまでできるかを考える」ことも大切だとも思いました。 デザインのフローと意識すること スケジューリング(複数回の再提出も考慮して早め早めの予定を組む) ↓ プランニング(ターゲットやテーマやゴールを明確にする) ↓ エスキース(必要そうな要素をとにかく書き出す) ↓ ラフスケッチ(本当に必要な情報のみキャンバスに並べる) ↓ レイアウト(他の作品を参考にしつつ、重要度順に並べる) ↓ 調整(行間等細部の調整。画面から離れたりセルフツッコミを入れて制作物を客観視する) ※後々コーディングするデザインの場合は奇数や小数のオブジェクトがないか注意する ↓ 提出(迷走し出したら早めに相談。レビューを考慮し余裕を持って提出) ↓ 再編集(成果物を客観視しユーザー目線で改善策を模索。参作をよく観察して編集) ↓ 入稿(納品データは軽く軽く) こういうところに注意しよう(コーディング編) マークアップは"レイアウト"じゃない 最初全く馴染めなかったのですが、マークアップはデザイン案通りにパーツを並べさせることではなく、文章に適切な意味づけをして人間語の構造を機械に認識させていくことです。デザインカンプ内に並んだ各パーツをよく読み解き、「これが小見出しに当たるからh3で…」「ここは補足情報だからasideで…」などと適切に目印(マーク)を振りましょう。 コーディングを始める前に設計図を書こう 上の書いた作業をするときは構造を紙にザクザク書き込んでみましょう。要素の親子関係を図にしたり動きを絵で描いたりするとかなり頭が整理できてエディターでの作業が早くなります。もし適当なクラス名でもって見切り発車的に作り始めてしまうと、後で修正が面倒になりますしいらないケアレスミスを招いたりもします。 編集に強く、汎用性の高いマークアップを心がけよう デザインデータ同様、他の方がコーディングファイルを触ることがあります。なので"このページに足りるコーディング"ではなくて、全てのページに通用するルールに則ってコーディングをしましょう。具体的には、仕様変更に対応しやすいように各要素にクラス名を与えておくことや、社内ルールに従って命名することが大切です。 プルリク前にセルフコードレビューをしよう 余計な編集を加えていないか、リンクが機能しているか、仕様漏れはないかなど、自分で気づけるレベルのミスはレビュー前に潰しておきましょう。せっかく先輩方に確認をしていただくのに、しょうもない部分の指摘に時間を取らせてしまうのは賢明でないです。(長く同じ画面を見ていると目が慣れてしまい些細なミスに気づけないこともあります。エディターの画面だけでなく実機のプレビューでもしっかりと確認しましょう。) リリース完了まで責任を持とう どの端末でも正しく表示されているか、正しくリンク先に遷移するかなどを確認し、旅立つ子供を見えなくなるまで送る親のように、手を離れたページを見守りましょう。自分の成果物に責任を持ち、リリース完了の確認がディレクターからも取れるまでは、対応できる状態でいましょう。 ネットサーフィンはデバックモードで楽しもう たまにやってみるとかなりコーディングの勉強になります。問題と解答が一枚のページに載っている状態なのでとてもいい教材です。「よく見るレイアウトだけどこんな手法もあったのか」とか「この手のパーツにはこんなクラス名がいいんだな」とか、発見が沢山ありますw これらを身につけた上で2年目に挑戦すること 次に、上に書いた諸々を身につけたうえで、入社2年目としてやるべきことについて書きます。 自立してできる仕事を増やすこと 与えられた仕事をこなすだけでなく、自立してできる仕事を増やしたいです。そのためにまず、仕事を円滑進めるために何をするべきかを考えながら行動します。これを実践していくことで、チームで効率的に動ける(動かせる)力を身につくと思っています。また、チーム内で進行している別の仕事やチーム内の他のメンバーが持っているタスクを把握することで、必要とされる場所に自分から飛び込める人間になります。 提案すること いいものを作っていくために、よりディレクターチームと連携し企画段階から関わっていきたいです。 提案するためには自分の考えを持っている必要があり、自分の考えを持つためは知見を持っている必要があるはずです。なので提案できる人間になるために、まずはサービズの仕組みを理解したり、数値を読み解けるようになったり、日々の実務を通じてより深い部分にまで関心を向けていこうと思います。 脱"新人"を意識すること 後輩がぞくぞくと入ってきます。新卒で入社した人材として、先輩から受け継いだことを自分で磨き後輩に継承していくことが求めらるはずです。新入社員としてではなく先輩社員として求められるものは何かを考えると、技術や業務についてもっと深く知ってなければいけないと気づけます。 その他これから挑戦していきたいこと 自分の力でJSを動かせるようにする(パズルゲームの一種だと思うと勉強しやすい。動くと超楽しい) UIについて学ぶ(デザイン思考を身につけて制作するときの武器を増やしたい) 英語に慣れる(エラーメッセージやコンソールをちゃんと読む。英語の技術ブログや取説も読む努力をする) 新しいものに興味を持つ(現状は最新技術や世の中の流行りにうとい) 映像制作について学ぶ(学生時代に少しやってた動画撮影や映像制作についてもう少し学び、仕事でチャンスをつかみたい) 最低限ターミナルを使えるよになる(画像データを扱うのでFinderの方が使用機会が多いが、ターミナルでコマンド制御できたら早いしカッコイイ!) 仕事直結じゃないインプットも大切にする(本、雑誌、映画、舞台など) 最後に 自分を含めた新人デザイナーさんや、学生さんに向けて書きます。 満を持して社会に出ても初めは教えていただくことばかりで、心のどこかで学生気分を引きずってしまいがちです。しかし、私はもう自分の成果物の対価としてお給料を頂いています。会社に入ってこの活動でお給料をもらっているということは"プロ"になったということなのです。 私はまだまだ先輩方のアドバイスが必要で、自分をwebデザイナーと名乗るのはおこがましいと思ってしまいますが、そんな気持ちで遠慮していて大きなヒヨコになってはいけないです。なので今すぐにもでもプロ意識を持って仕事に取り組むべきだと思います。一緒に頑張っていきましょう!
アバター
メディアシステム開発部の野崎です。 メディアシステム開発部では、「 au Webポータル 」や「 au スマートパス 」といった、 多くのユーザ様にご利用頂いているサービスを担当しています。 このようなシステムでは新規開発や機能追加時には負荷試験を実施することは必須となります。 そこで今回は、Webシステムの負荷試験について 負荷を生成する環境にフォーカスして、 これまで行ってきたノウハウをまとめてみます。 はじめに 負荷試験と言われるものには幾つか種類があります。 性能試験、耐久試験、限界試験、ロードテストなどがありますが、 今回は簡単のために、以下の目的の試験をまとめて負荷試験と呼ぶことにします。 非機能要件で定義した性能を担保できるか? システムの性能の限界はどのくらいなのか? 長時間連続して負荷をかけて、サービスイン後状況を再現し、期待通りに動作するか? 生成する負荷としては、秒間数百から数千リクエストを想定しています。 システム前提条件 以下のような環境である前提で話を進めます。 負荷試験対象のシステムはAWSで構築されている。 負荷生成側のシステムもAWSにてEC2上に構築する。 負荷生成側のサーバはGatlingを使用する。(OSは、Amazon Linux) 負荷生成の指示はローカルのPCから行う。 レポートの生成はローカルのPC上で行う。 Gatlingは、Async, Netty, Akkaを用いたScala製でHTTPリクエストを対象とした負荷試験ツールです。 個人的に、構築が容易なところと結果レポートが見やすいところが気に入っています。 公式サイト と、 ソースコード が以降の説明で、参考になります。 準備 負荷試験の準備段階で、計画や環境をどのようにそろえるかを記載していきます。 準備1 - 試験の計画 試験を計画する際に注意したことです。 ◯ 新規開発時には、すべてのURLを対象とした試験計画にしましょう。 「このURLは大した機能でないから試験範囲から外そう」と思いがちですが、 バグが有ったり、想定外の負荷がかかったりして、結果的に全体に影響することもあります。 サービスイン後は全体の試験を行うことは難しいので、新規開発時にやっておくことが望ましいです。 ◯ 負荷試験の項目をすべて行える日程を複数回計画しましょう。 特に新規開発の場合ですが、往々にして期待どおりの性能を発揮できることはありません。 結果をレビューし、原因を特定修正し、再度負荷試験実施できる期間を設けましょう。 ◯ キャッシュが有り無しのパターンをテストケースに含めましょう 多くのユーザリクエストを受けるシステムの場合、何らかのキャッシュ機構を組み込むことが多いと思います。 その際キャッシュが有る場合、無い場合を想定しテストケースを作成しましょう。 アプリケーションにキャッシュを使わないモード実装が必要な場合もあります。 ◯ 別システムと連結している場合の考慮 オンラインでバックエンド側から別システムへ連結している場合、 別システムも含めた試験の範囲にするか確認しましょう。 範囲外であれば、スタブの作成などを検討しましょう。 準備2 - 負荷生成サーバ準備 負荷試験生成サーバの準備を行っていきます。 GatlingサーバはEC2上に構築します。構築時はインスタンスタイプは適当で大丈夫です。 ◯ インストール こちら を参考にGatlingをインストールしましょう。 JDK8のインストールとGatlingのパッケージを解凍し、適当なディレクトリに設置します。 ◯ OSのチューニング こちら を参考に、EC2のディスクリプタやカーネルパラメータを変更します。 ここを怠ると、あとで多くの台数のサーバが必要になったりします。 ◯ 1台のGatlingサーバがある程度の負荷を掛けられるかを確認 ある程度のオーダーの負荷が掛けられるかを確認しておきましょう。 もし期待する負荷を生成出来ない場合は、「OSのチューニング」を見直しましょう。 それでも負荷生成が十分でない場合は、インスタンスタイプを徐々にあげてみましょう。 場合によってはこの確認をするために、後述の申請が必要になったりします。 ◯ Gatlingのテストケースの作成 テストケースを作成し、構築したGatlingサーバに配置しましょう。 ※テストケースに関しては多くの情報がWeb上にあることもあり、今回書き方は説明しません。 ※GatlingDSLの チートシート を参照すれば調べやすいです。 ◯ Gatlingサーバを複数台用意する ここまで来たら、1台での負荷生成性能を元に、 複数台Gatlingサーバを作成しておきましょう。 後述の申請と関連するためで、2台以上あると試験当日に期待する負荷が生成出来ない場合でも、 インスタンスタイプを上げていけば必要な負荷を生成出来るでしょう。 ◯ AWSへの負荷試験の申請 ELBなどのAWS上のリソースに対して負荷をかけるときは、AWSへ事前の申請が必要です。 試験内容によってどういったことを申請する必要があるかはサポートなどで確認しましょう。 ※ 2017年4月時点での申請においては、負荷生成サーバのインスタンスIDが必要でした。(ということは、申請後にサーバ台数を増加できない・・!?) 準備3 - Gatilngサーバのスケーリングアウト ところで、GatilngにはJMeterの用にクラスタを構築する機能は提供されていないようですが、 こちら にある、スケーリングアウトの方法を取れば、 複数台のGatilngサーバから同時に負荷を生成することが出来ます。 今回はこちらの方法を取ってみます。 準備4 - ローカルPCの準備 ローカルPCはMacとして、以下の準備をします。 ◯ Gatilngのインストール 後述するレポートの作成をするために、ローカルPCにもGatilngを設置しておきます。 負荷生成サーバのインストールと同じ方法で大丈夫です。 (ローカルPC上で負荷生成はしないので、OSのチューニングは不要です。) ◯ csshx or i2cssh のインストール homebrew などで csshx や i2cssh をインストールしておきましょう。 これらは、ターミナルアプリをクラスタリングするアプリケーションで 1度のコマンド操作で、複数のサーバを同時に操作出来たりします。 実施 以下図の流れで試験を実施します。 Gatlingサーバ2台あるとし、gatling01,gatling02というホスト名とします。 $GATLING_HOME をインストールしたディレクトリとします。 1.1 ローカルPCからGatlingを実行指示 # ローカルPCのからGatlingサーバへログイン $ csshx gatling01 gatling02 (ターミナル.appの複数ウィンドウが立ち上がる以降はサーバ内操作) # 2台同時に実行指示 $ cd $GATLING_HOME $ ./bin/gatling.sh \ -s mediba.SampleCluster \ -nr \ -rd "cluster test" #オプション説明 -s : シナリオの指定 -nr : レポート出力しない -rd : 説明の記載 (実行中は以下のようになります。) 1.2 負荷生成 テストケースの実行が終わるまで待ちましょう。 csshx を利用した理由は、負荷生成の実行状況がGatlingサーバそれぞれにて確認できるからです。 Gatlingサーバのリソース不足が合った場合は、実行状況から確認できます。実行を止めて、インスタンスタイプを上げるなどしましょう。 レポート作成 負荷試験が無事終わったら、レポートを作成しましょう。 2.1 サーバごとの実行ログをローカルPCにDLする # ローカルPC $ cd $GATLING_HOME # 適当なディレクトリをresults以下に作成 $ mkdir -p ./results/samplecluster # Gatlingサーバからsimulation.logをDLする $ scp gatling01:/$GATLING_HOME/results/{実行ID}/simulation.log simulation_gatling01.log $ scp gatling02:/$GATLING_HOME/results/{実行ID}/simulation.log simulation_gatling02.log 2.2 サーバごとのログからレポートを作成する。 ./results/samplecluster/ 配下のログファイルを読み込んで、レポートを生成します。 $ sudo ./bin/gatling.sh -ro samplecluster # オプション説明: -ro : ログからレポートの作成のみ行う。 GATLING_HOME is set to /opt/gatling-charts-highcharts-bundle-2.2.3 Parsing log file(s)... Parsing log file(s) done Generating reports... ================================================================================ ---- Global Information -------------------------------------------------------- > request count 40 (OK=40 KO=0 ) > min response time 26 (OK=26 KO=- ) > max response time 113 (OK=113 KO=- ) > mean response time 39 (OK=39 KO=- ) > std deviation 14 (OK=14 KO=- ) > response time 50th percentile 37 (OK=37 KO=- ) > response time 75th percentile 40 (OK=40 KO=- ) > response time 95th percentile 47 (OK=47 KO=- ) > response time 99th percentile 100 (OK=100 KO=- ) > mean requests/sec 2 (OK=2 KO=- ) ---- Response Time Distribution ------------------------------------------------ > t < 800 ms 40 (100%) > 800 ms < t < 1200 ms 0 ( 0%) > t > 1200 ms 0 ( 0%) > failed 0 ( 0%) ================================================================================ Reports generated in 0s. Please open the following file: /opt/gatling-charts-highcharts-bundle-2.2.3/results/samplecluster/index.html こちら に有るような、 見やすいHTMLのレポートが生成されます。 このレポートをS3などにアップすれば、複数人での結果のレビューも行いやすく、 次のアクションも取りやすいと思います。 まとめ AWS環境でのGatlingを使った負荷試験について主に環境周りについてまとめてみました。 負荷試験の本来の目的は、試験対象システムを評価することです。 負荷試験生成の環境は、今回の手順などを参考に楽に構築し自由に負荷生成を行える様にしておけば、スムースに試験を実行できるかと思います。 今回の記事が皆さんの開発の参考になれば幸いです。 以上となります。
アバター
こんにちは、インフラストラクチャー部の沼沢です。 今回は、2016年の re:Invent で発表された Lambda@Edge を使って、リクエスト元のデバイス判定を実装してみます。 Lambda@Edge といえば、 CloudFront の Edge ロケーション上で Lambda が実行できる 画期的なサービスです。 現在は Limited Preview 中で、General Availability を待ち望んでいるサービスの1つです。 Lambda@Edge についてはこちら → AWS Lambda@Edge (プレビュー) 本投稿時点ではまだ Preview ですので、この記事の内容を試したい場合は こちら から事前に利用申請をしておきましょう。 re:Invent 2016 で発表された各サービスについては以下をご参考まで。 AWS re:Invent 2016 新サービスまとめ 1 | mediba Creator × Engineer Blog AWS re:Invent 2016 新サービスまとめ 2 | mediba Creator × Engineer Blog デバイス判定は既に簡単にできる機能がある デバイスタイプに基づいてオブジェクトをキャッシュするように CloudFront を設定する 上記に記載の通り、以下の4タイプの判定で事足りる場合は、このヘッダをそれぞれオリジンサーバーに転送し、オリジンサーバー側でこのヘッダを読み取ってレスポンスを分けたりする方法が良いと思います。 CloudFront-Is-Desktop-Viewer CloudFront-Is-Mobile-Viewer CloudFront-Is-SmartTV-Viewer CloudFront-Is-Tablet-Viewer しかし、この4タイプでは足りない場合(例えば iOS, Android でデザインを分けたい等)には、この方法は使えません。 その場合、User-Agent をオリジンサーバーに転送してアプリケーション側で判定する方法を取ることで解決できますが、そうすると CloudFront のキャッシュ効率が悪くなってしまうというデメリットがあります。 そこで、Lambda@Edge を使ってこの課題を解決してみたいと思います。 概要 今回は、以下の判定をできるようにします。 iPhone iPad Android 上記以外(Other) CloudFront にアクセスに来た際に、Lambda@Edge を Viewer Request で起動するように設定し、Request Header にカスタムヘッダを付けて nginx でログに出力するところまでを実装します。 Viewer Request とする理由は、CloudFront のエッジ上の動作が行われる前に、デバイス判定→ヘッダ追加を行いたいためです。 やってみる EC2(nginx) を用意 詳細な手順は割愛しますが、以下の手順で nginx がインストールされた EC2 インスタンスを用意します。 EC2 インスタンスをローンチ ローンチした EC2 インスタンスに EIP を付与(Public IP でも可) ローンチした EC2 インスタンスに ssh ログインし、nginx をインストール nginx のアクセスログをカスタマイズ /etc/nginx/nginx.conf の log_format を以下のように修正し、nginx を再起動します。 log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for" ' '$http_custom_device '; # 追加 Lambda ファンクションを用意 Lambda 関数の作成 設計図の選択: ブランク関数 トリガーの設定: (何も変更せず) 次へ 関数の設定 名前: device_judge 説明: device judge ランタイム: Edge Node.js 4.3 Lambda 関数のコード コード エントリ タイプ: コードをインラインで編集 以下のコードを入力 'use strict'; # User-Agent からデバイスを判定し、"Custom-Device" ヘッダを追加 exports.handler = (event, context, callback) => { const request = event.Records[0].cf.request; const headers = request.headers; if (headers['User-Agent']) { headers['Custom-Device'] = headers['User-Agent'][0].match(/(Android|iPhone|iPad)/)? RegExp.$1: 'Other'; } callback(null, request); }; Lambda 関数ハンドラおよびロール ハンドラ: index.handler ロール: “カスタムロールの作成” で作成される “lambda_basic_execution” を指定 CloudFront Web Distribution を用意 以下の通り設定していきます。 作成後、Status が Deployed になるまで待ち、CloudFront の Domain Name (xxxx.cloudfront.net) にアクセスした際に nginx のデフォルトページが表示されれば準備は OK です。 動作検証 ブラウザで、各 User-Agent でアクセスし、nginx のアクセスログを確認します。 “その他” 判定 “その他” を判定させるため、Google Chrome で User-Agent を変更せずアクセスしてみます。 ***.***.***.*** - - [17/Apr/2017:17:29:08 +0900] "GET / HTTP/1.1" 200 3770 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36" "***.***.***.***" Other 無事に末尾に “Other” が記録されました。 “iPhone” 判定 “iPhone” を判定させるため、Google Chrome で User-Agent を “iPhone 6 Plus” に変更し、アクセスしてみます。 ***.***.***.*** - - [17/Apr/2017:17:47:41 +0900] "GET / HTTP/1.1" 200 3770 "-" "Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1" "***.***.***.***" iPhone 無事に末尾に “iPhone” が記録されました。 念のため、"iPhone 5" にしてアクセスしてみても、同じように末尾に “iPhone” が記録されました。 “iPad” 判定 “iPad” を判定させるため、Google Chrome で User-Agent を “iPad” に変更し、アクセスしてみます。 ***.***.***.*** - - [17/Apr/2017:17:55:54 +0900] "GET / HTTP/1.1" 200 3770 "-" "Mozilla/5.0 (iPad; CPU OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1" "***.***.***.***" iPad 無事に末尾に “iPad” が記録されました。 念のため、"iPad Pro" にしてアクセスしてみても、同じように末尾に “iPad” が記録されました。 “Android” 判定 最後に、"Android" を判定させるため、Google Chrome で User-Agent を “Galaxy S5” に変更し、アクセスしてみます。 ***.***.***.*** - - [17/Apr/2017:17:59:28 +0900] "GET / HTTP/1.1" 200 3770 "-" "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Mobile Safari/537.36" "***.***.***.***" Android 無事に末尾に “Android” が記録されました。 念のため、"Nexus 6P" にしてアクセスしてみても、同じように末尾に “Android” が記録されました。 まとめ 今回は検証のため Header を全てオリジンサーバーへ渡しましたが、実際には CloudFront の設定でオリジンサーバーへ渡すヘッダを指定( “Forward Headers” の Whitelist で “Custom-Device” を指定 ) することで、指定した Header の値ごとにキャッシュを持たせることができるようになります。 また、今回は Lambda@Edge をデバイス判定に利用しましたが、他にもいろんな用途で利用できると思います。 まだ Preview なのが残念ですが、今後のシステム設計の参考になれば幸いです。
アバター
 こんにちは、広告システム開発部の菅原です。  今回は構文解析のお話です。構文解析は、コンパイラ、自然言語処理(テキストマイニング)、AST(抽象構文木)、AltJS(Alternatives to Javascript) などのベースとなる重要な技術の一つです。本記事では、実例としてコンパイラを交えて説明しつつ、パーサコンビネータを使って簡単に構文解析を行えることを紹介していきます。 構文解析が使われている技術について  文字列が関わる様々な技術の根底に、構文解析は関与しています。構文解析がどのように関わっているか、その実例としてコンパイラの仕組みを簡単に見ていきます。  コンパイラ(Compiler)とは、人間が理解しやすい高級言語で記述されたプログラムを、ハードウェア寄りの低級言語(機械語もしくは中間言語)に変換するプログラム 1 です。  一言で「変換」と表記しましたが、このプロセスは、大きく分けて次の四つの機構から成ります。 字句解析(Lexical Analysis) ソースコードを読み込んで、トークン(字句)に分解する工程 構文解析(Syntactic Analysis) 分解したトークン列をもとに構文木を構築する工程 最適化(Optimization) 構築した構文木を効率の良いものに変換する工程 コード生成(Code Generation) 構文木からオブジェクトコードを生成する工程 構文解析と構文解析器  コンパイラの場合は、二番目の機構に構文解析が含まれています。構文解析を行う機構のことを「構文解析器(Parser)」と呼びます。後述するパーサジェネレータ(パーサコンビネータ)のライブラリの最近の実装では、字句解析器 2 と構文解析器の機構も併せて持っていることが珍しくなく、トークン分割から構文木構築までカバーできるものがほとんどです。 パーサジェネレータ(パーサコンビネータ)  構文解析器を生成するプログラムを「パーサジェネレータ(Parser Generator)」と言います。パーサジェネレータそのものは構文解析器ではありません。また、パーサジェネレータの中でも、演算子と関数を組み合わせて構文解析器を生成できるプログラムを「パーサコンビネータ(Parser Combinator)」と呼びます。 構文解析器の手続き  構文解析器では、構文木に対する手続きによって、トップダウン構文解析とボトムアップ構文解析の二つに大分類されます。トップダウン構文解析は、最上位の根から最下位の葉へ向かって書き換えていく手続きであり、一方のボトムアップ構文解析では、最下位の葉から最上位の根に向かって書き換えていく手続きです。 トップダウン構文解析とボトムアップ構文解析  トップダウン構文解析器とボトムアップ構文解析器には、主たる構文解析法が下表のように分類されます。 構文解析法 分類される解析器 LL parser Recursive descent parser Packrat parser トップダウン構文解析器 LR parser(SLR parser / LALR parser / GLR parser) Earley parser Cocke-Younger-Kasami parser ボトムアップ構文解析器 パーサコンビネータを使うための準備  構文解析の簡単な概要について把握できたところで、パーサコンビネータを使う準備を整えていきます。実行環境は、macOS Sierra 10.12.4 で、パッケージ管理システムは、 Homebrew を使い、コーディングには Sublime Text 3 を使う前提です。使用する言語は、パーサコンビネータのライブラリがあれば、どの言語でも構わないのですが、文字列操作に優れた Haskell を選択します。 Haskell のインストール  まず始めに、Homebrew で Haskell(Glasgow Haskell Compiler) をインストールします。 $ brew install ghc Cabal-install のインストール  続いて、Haskell のパッケージ管理システム cabal をインストールします(Homebrew でのパッケージ名は、cabal-install になります)。 $ brew install cabal-install  インストールが完了したら、cabal のパッケージリストの更新を行います。 $ cabal update  パッケージリストが最新化されたところで、新しいバージョンの cabal-install をインストールします。 $ cabal install --global cabal-install hsdev のインストール  cabal-install を更新した後、Sublime Text 3 上でコード補完等を行ってくれるパッケージ hsdev をインストールします。 $ cabal install hsdev SublimeHaskell の導入  Sublime Text 3 上で、Shift+Controll+P のショートカットキーを押してコマンドパレットを起動させます。この状態で「install」と入力すると、ドロップダウンの予測一覧が表示されますので、その中の「Package Control:Install Pacakge」の項目にフォーカスが当たった状態で「Enter」キーを押下します。すると、パッケージ検索パレットに切り替わるので、「SublimeHaskell」を検索してインストールします。 SublimeHaskell の設定  メニューバーから、Preferences > Package Settings > SublimeHaskell の順にメニューを辿って行き、「Settings - User」の項目を押下します。下記のように、hsdev のパスを記述し、Haskell のコマンドオプションを警告の表示・非表示を自分の好みで設定します。コマンドオプションについては、 こちら が参考になります。 { "enable_hdevtools": false, "auto_completion_popup": true, "add_to_PATH": [ "~/.cabal/bin/hsdev" ], "ghc_opts": [ "-fno-warn-type-defaults", "-fno-warn-missing-signatures", "-fno-warn-incomplete-patterns", "-fwarn-unused-binds" ] } パーサコンビネータを使ってみる  Haskell には、Parsec という LL parse の非常に優れたパーサコンビネータライブラリがあります。このライブラリを使って四則演算ができる簡易計算機を作ってみようと思います。 インタラクティブモードでソースコードを実行する  簡易計算機を作る前にインタラクティブモードでソースコードを読み込んで実行する手順を示します。 Haskell でプログラミング入門恒例の hello world を書いて hello.hs というファイル名で保存します。 main = do print "hello world"  このソースコードを保存した場所に cd コマンドで移動した後、ghci コマンドでインタラクティブモードにしてから、「:load hello.hs」でソースコードを読み込み、main で実行します。 $ ghci Prelude> :load helo.hs *Main> main "hello world" 数値を読み取れるようにする  まずは、数値を読み取れるようにしてみます。1文字以上読み取りたいので many1 を、その上で数値を読み取るので digit の関数を組み合わせて使います。正規表現だと「[0-9]+」と同じようなイメージで考えてください。しかし、このままの構文解析器だと文字列出力になってしまうので、Int 型に変換します。 import Text.Parsec -- bind での書き方 number = many1 digit >>= \x -> return (read x :: Int) -- do で bind する書き方 number2 = do x <- many1 digit return (read x :: Int) main = do parseTest number "123" -- 123 parseTest number2 "456" -- 456 足し算をできるようにする  数値を読み取れるようになったので、今度は足し算をできるようにしてみましょう。足し算だけの計算式は、「a+b+…+z」の書式で表せることから、「数値」と「プラス記号と数値のセットの繰り返し」であることがわかるので、0回以上の繰り返しを表す many 関数と、数値を読み取るための自作関数 number を組み合わせます。そして、プラス記号が計算時には不要になるため、アプリカティブの「*>」(右辺のみ残す)を使用します。foldl 関数は、ここでは計算式の項を左から順に畳み込むために使用します。 import Text.Parsec import Control.Applicative ((*>)) add = do a <- number b <- many $ (char '+' *> number >>= \y -> return (+ y)) return $ foldl (\x f -> f x) a b number = do x <- many1 digit return (read x :: Int) main = do parseTest number "123" -- 123 parseTest add "1+2" -- 3 parseTest add "1+2+4" -- 7 引き算もできるようにする  足し算だけでなく引き算もできるようにしてみます。引き算そのものをできるようにするのは特に難しくありませんので、足し算と同じような内容で構文解析器を組んで行きます。ただし、Haskell の仕様上、マイナス記号が単項演算子として扱われてしまうため、subtract 関数を使うことで引き算を実現します。なお、足し算と引き算の演算優先度は同じなので、足し算と引き算の両方を演算できるようにするためには、選択「<|>」を使います。 import Text.Parsec import Control.Applicative ((*>)) expr = do a <- number b <- many $ (char '+' *> number >>= \y -> return (+ y)) <|> (char '-' *> number >>= \y -> return (subtract y)) return $ foldl (\x f -> f x) a b number = do x <- many1 digit return (read x :: Int) main = do parseTest number "123" -- 123 parseTest expr "1+2" -- 3 parseTest expr "3-1" -- 2 parseTest expr "2+3-1" -- 4 さらに掛け算と割り算もできるようにする  掛け算と割り算もできるようにしてみましょう。これらの計算は、足し算・引き算よりも演算の優先度が高いので、正しく計算するためには、同じ関数 expr の中で選択を使うことができません。そのため、掛け算と割り算用の関数 term を用意します。 import Text.Parsec import Control.Applicative ((*>)) eval a b = foldl (\x f -> f x) <$> a <*> b expr = eval term $ many $ (char '+' *> term >>= \y -> return (+ y)) <|> (char '-' *> term >>= \y -> return (subtract y)) term = eval number $ many $ (char '*' *> number >>= \y -> return (* y)) <|> (char '/' *> number >>= \y -> return (`div` y)) number = do x <- many1 digit return (read x :: Int) main = do parseTest number "123" -- 123 parseTest expr "2+4/2-1*3" -- 1 四則演算ができる簡易計算機として完成させる  四則演算ができるようになりましたが、これだけでは不完全です。計算式の空白があっても正しく計算できるように spaces 関数、アプリカティブを使いこなしてパーレン内の項を優先して計算できるようにした factor 関数を作ります。 import Text.Parsec import Control.Applicative ((<$>), (<*>), (*>), (<*)) eval a b = foldl (\x f -> f x) <$> a <*> b expr = eval term $ many $ (char '+' *> term >>= \y -> return (+ y)) <|> (char '-' *> term >>= \y -> return (subtract y)) term = eval factor $ many $ (char '*' *> factor >>= \y -> return (* y)) <|> (char '/' *> factor >>= \y -> return (`div` y)) factor = spaces *> (char '(' *> expr <* char ')' <|> number) <* spaces number = do x <- many1 digit return (read x :: Int) main = do parseTest number "123" parseTest expr "123" parseTest expr "1 + 20 + 300" -- 321 parseTest expr "100 - 20 - 3" -- 77 parseTest expr "3 * 60 + 4 / 2" -- 182 parseTest expr "3 * 30 - 50 / 2" -- 65 parseTest expr "3 * (60 + 4) / 2" -- 96 parseTest expr "3 * (30 - 50) / 2" -- -30 Haskell 以外の言語のパーサコンビネータ  Haskell 以外のプログラミング言語のパーサコンビネータライブラリの一例を紹介します。なお、ここに挙げた以外もパーサコンビネータの実装はありますので、下表のライブラリが特別良いというわけではありません。 言語名 ライブラリ名とリンク PHP loco Javascript Esprima Python Parsec.py Ruby rsec Java jparsec Go goparsec Scala scala.util.parsing.combinator.Parsers Elixir Combine C# Sprache C++ boost Spirit Visual Basic VBParserCombinator 最後に  たった数十行程度で四則演算が出来る計算機を作成できたことから、パーサコンビネータを使うと簡単に構文解析を行えることがわかるかと思います。本記事では紹介していませんが、この簡単さ具合で、テキストマイニングや抽象構文木などもより簡単にすることができます。  なお、本記事で作成した簡易計算機を発展させていくと、最終的には、プログラムのコンパイラにも、AltJS のように入力したソースコードから Javascript を出力するコンパイラにもすることができます。俺様プログラミング言語を作る夢があるなら、実現も不可能ではありません。 コンパイラはいくつかの種類があり、C 言語や C++ などの言語で、機械語へと直接一括コンパイルするコンパイラを「ネイティブコンパイラ」、C# や Java のなどの言語で、中間言語にコンパイルするコンパイラを「Ahead-Of-Time コンパイラ」と言います(中間言語から機械語にコンパイルするコンパイラは「Just-In-Time コンパイラ」と呼びます)。  ↩︎ 字句解析を行う機構を「字句解析器(Lexical Analyzer)」と呼びます。  ↩︎
アバター
フロントエンジニアの苅部です。 medibaシステム本部では一部サービスのA/BテストをGoogle Optimize(以下Optimize)で実施しております。 先日Optimizeの一般利用が可能になったようですので、これから初めてA/Bテストを実施する方に向けて、使用感を共有できたらと思います。 Optimizeの特徴 1. 無償版でも十分使える 無償版でもほとんどの機能が利用できるため、予算のないプロジェクトでもすぐに実践的なA/Bテストが開始できます。 実際にOptimizeを使ってA/Bテストを運用していますが、無償版で機能が制限されているというよりも、有償版にすることでGoogle Analyticsのオーディエンスデータを活用できる という印象を受けました。 ツールが評価できない段階で数十万円/月の予算を確保するのはなかなか難しいと思うので、いったん無償版で小さく始められる事は大きいと思います。 今のところ、ヒット数の制限もありません。(2017年5月現在)   ・Optimize vs. Optimize 360 - Optimize Help ・Optimize - Free Beta Hit Limit - The Google Advertiser Community 2. 表示遅延を最小限に抑えられる JavaScriptを使って非同期的なA/Bテストを実施する場合には、表示速度へのマイナス影響を考慮する必要があります。 サードパーティーのJavaScriptを同期的に読み込む場合 DNSLookup, 3wayHandshake, Responseの流れが完了するまではレンダーツリーの構築をブロッキングすることになります。つまり描画がネットワークのレイテンシに左右されるため、RTTの長いモバイルネットワークでは顕著に体感速度に影響がでます。 この点で、Optimizeでは以下のような配慮がされています。 - 非同期読み込み JavaScriptを非同期に読み込み、テストデータの返却 or タイムアウトでHTMLの透過を解除する処理になるため、レンダーツリー構築への影響を最小限に抑えます。 - TCPコネクションの節約 Google Analyticsのプラグインのため、google-analytics.comと同一のTCPコネクションでテスト内容も返却することになります。 - JavaScriptの最適化 JavaScriptのレスポンスは動的生成されるため、返却される内容が最適化されています。 例えばテスト対象外のユーザーには、テストデータをレスポンスに含めることはないのでオーバーヘッドが最小限に抑えられています。 A/Bテストツールの体感速度へのマイナス影響を考えると、クリティカルレンダリングパスやネットワークでのボトルネックを減らせる事はメリットが大きいと思います。 実際に他のプラットフォームの同期Scriptと比較してみたところ、SpeedIndex値の中央値で10%ほどの差が確認できました。 (パフォーマンス計測では webpagetestを利用 ) ・オプティマイズとページの読み込み速度 - Optimize ヘルプ ・クリティカル レンダリング パスのパフォーマンスを分析する | Web | Google Developers 3. Google Analyticsの延長で利用できる - 汎用性や将来の拡張性 Google Analyticsを使いこなしている人は多く、Google Analyticsの延長で使えるOptimizeは、学習コストやコミュニケーションの面でメリットがあると感じています。 インターフェイスは他のGoogle製品とも統一されているため、慣れた操作感で扱いやすいです。 そして今後はDataStudioとの接続など、他のGoogle製品との連携も期待できるのではないでしょうか。 - 実装の手軽さ すでにGoogle Analyticsを利用している場合には新たなJS読み込みをしなくても、以下のような形でプラグインの指定を1行追加して任意でJavaScriptのスニペットを追加するだけでOptimizeと連携できます。 <script> (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); ga('create', 'UA-NNNNNNNN-N', 'auto'); // 以下のrequireの1行を追加する ga('require', 'GTM-NNNNNN'); // ------------------------ ga('send', 'pageview'); </script> - イーコマース連携 Google Analytics側でイーコマース機能を使っている場合には追加設定なくコンバージョン連携できます。 テスト作成画面でのゴール設定では、TransactionとRevenueが選択できるようになります。 そしてレポート画面では個々のテスト結果をトランザクション・収益ベースで比較できます。 Google Analyticsとは異なりOptimizeではコンバージョンに統計モデルを利用していて、95%信頼区間・50%信頼区間・中央値が表示されます。 信頼区間は、どのくらいの確率でどのくらいの効果が見込まれるかが判断できるため、P値を使って帰無仮説を却下する検定方法よりも得られる情報が多いと思います。 A/Bテストの成果として重視されるのはクリック数が増加したことよりも[KPIに貢献できたかどうか]だと思っています。 そういう意味で、コンバージョンとしてトランザクション数や収益額を用いることで、より本質的なA/Bテストが可能になるのではないでしょうか。 ※ 備考 ・トランザクションをコンバージョンとして実施するテストで統計的に有意な差を出すためには、相当数のコンバージョンか、明らかに大きな差のあるテストを実施する必要があると思われます。 ・オプティマイズレポートとアナリティクスレポートの違い - Optimize ヘルプ - オーディエンスデータを利用したターゲティング(Optimize360) 有償版(Optimize360)に限定されますが、Google Analytics上でユーザーリストを作成して、そのユーザーリストを対象にターゲティングする事ができます。 例えば、 特定の商品カテゴリをよく見ている人 コンバージョンの回数が[N]の人 コンバージョンへの近さが[N]の人( セッションの品質 ) といったセグメントをGoogle Analyticsで作成することで、ユーザーごとに最適なバリエーションでテストできるようになります。 [どのバリエーションに割り振られたか]といった値も自動的にディメンションに入りますので、 バリエーションごとでセグメントを分けて深く分析できる ようになります。 つまりOptimize360とGoogle Analyics360を連携させることで、A/Bテストでのターゲティングと、レポーティングでのセグメンテーションをセットで考える事ができるようになります。 ツール導入やA/Bテスト運用のポイント 1. デザインの反映はフロントエンジニアで A/Bテストプラットフォームを利用する事のメリットに[スピード感]があると思いますが、デザイン反映にあたってはCSS/JavaScriptへの理解・動的な値/非同期処理への配慮、また実機での確実なテスト確認が求められます。 そのためデザイン実装はフロントエンジニアが行い、スピード感と共に安全性を担保する必要があります。 誰でも簡単に編集できるという事は、誰でも簡単に壊せるということでもありますので、細心の注意を払って作業した方がいいと思います。 2. A/Aテストを実施する テスト対象のページにおいては一度、[同じデザインパターンを複数]用意して2週間程度A/Aテストを実施することをオススメします。 例えばテスト対象が想定していない偏り方をすることもあると思いますし、ツールの問題で有意差が正しく判定できないかもしれません。 そのため、A/Aテストを実施して数値がどの程度の期間で平均に回帰していくか、数値の誤差がどの程度想定されるかを事前に把握できると良いと思います。 3. OptimizeはTagManagerからは配信しない TagManager経由でOptimize利用することも可能ですが、表示遅延の問題があるためGoogleは推奨していないようです。 例えばTagManager経由でanalytics.jsを配信する場合、 1) TagManagerへリクエスト 2) TagManagerからanalytics.jsのタグが返却 3) analytics.jsをリクエスト といった流れで1)〜2)がレイテンシとなり、描画に関わるOptimizeが遅延することで表示遅延につながります。 そのためTagManagerを経由せず、可能な限り早い段階(headタグ付近)でanaltyics.jsの記述を書いた方がよさそうです。 ・Serve Optimize via Google Tag Manager - Optimize Help 4. オリジナルデザインの描画に注意する A/Bテストのデータは非同期で渡されるため、レスポンスが遅延すると先にオリジナルのデザインが反映される事があります。 これを避けるため、Optimizeでは以下のようなPageHidingSnippetが用意されています。 <style scoped="scoped">.async-hide { opacity: 0 !important} </style><script>(function(a,s,y,n,c,h,i,d,e){s.className+=' '+y;h.start=1*new Date; h.end=i=function(){s.className=s.className.replace(RegExp(' ?'+y),'')}; (a[n]=a[n]||[]).hide=h;setTimeout(function(){i();h.end=null},c);h.timeout=c; })(window,document.documentElement,'async-hide','dataLayer',4000, {'GTM-XXXXXX':true});</script> このSnippetによってテスト返却されるまで or タイムアウトになるまではhtmlタグの透過度が100%となり、オリジナルの描画を防ぐ事ができます。 ※プログレッシブな描画ではなくなるめ、体感速度は若干落ちるかもしれません。 5. ツールの理解を深める Googleやリセラーと契約せず、無償版で利用する場合は自身で問題を解決するしかありません。 また社内からの問い合わせも集中してしまいますので、本格的に進める場合にはOptimizeやGoogle Analyticsの深い理解が必要になると思います。 Googleだけあってヘルプやフォーラムが充実していますので、大抵の問題は自身で解決する事はできると思います。 日本語でもヘルプが充実しています。 Help - Optimize ヘルプ 英語版ヘルプの方が情報が多いです。 Optimize Help フォーラムも用意されています。(英語) Google Optimize - The Google Advertiser Community YouTubeのGoogleAnalyticsアカウントにも動画が上がっています。(英語) Google Analytics - YouTube GoogleAnalyicsの学習にはGAIQ取得がオススメです。 Google アナリティクス個人認定資格(IQ)について OptimizeにはFeedBack機能もあるので、万が一問題が発生した場合にはこれを使ってスクリーンショット付きで報告するのも良いと思います。 6. データ分析力を身につける A/Bテストでは[勝ち/負け]といった言葉が使われる事がありますが、A/Bテストの結果はあくまで統計的な確率であり[100%の勝ち or 100%の負け]ではないと思っています。 ミスリードを防いだり説明責任を果たすためにも、基礎的な統計やデータ分析を理解して、ツールが出した数値を鵜呑みにせず、最終的に人が判断できる状態が望ましいと思います。 私自身はというと、Optimize・Google Analytics・有意差検定の説明はできますが、ベイズ統計の概要だったりOptimizeの統計モデルを把握できていないので、ざっくりでも理解していきたいなと。。 実際に導入・運用まで進めてみた感想 テストの運用までこぎ着けても、A/Bテストで成果を出すには多くの知見(技術/ノウハウ/文化)が必要だと感じています。 A/Bテストの実施を検討する場合にはツールありきではなく、KPIと目的を明確にして、そのために何が必要か考えてからツール選定すると良いと思います。 A/Bテスト自体は目的ではなく手段であって、KPIへ繋がる成果を出すことが目的ですし、グロースハックの文化を熟成していくことも大事です。 ただ細かい事を考えていても何も始まらないので、7割いけると思ったら、ツール先行でまずは走り出してしまうのがいいと思っています。 A/Bテストには[ツール導入/理解][データ分析力][成果]の3つの壁があると思っていて、1つめの壁は確実にOptimizeに優位性があると思います。 そしてGoogle Analyticsと連携できる無償のツールというところで、今A/Bテストを始めるならOptimizeをオススメしたいです。
アバター
みなさん、Golang書いてますか? お久しぶりです。メディアシステム開発部の森竹です。 前回は Docker for Macを使ってRuby on Rails開発環境を構築する を紹介させて頂きました。 今回はTravis CIを使用したGolangビルドを紹介させて頂きます。 ビルド Travis CI でGolangビルドを実行し、AWS S3へPUTします。ビルドサーバーでGolangビルドする案もありましたが、下記の観点を鑑み、Travis CIを選択しました。 Travis CI ビルドサーバー 運用コスト 月額固定( https://travis-ci.com/plans ) サーバー費用など 管理コスト 小(Golangバージョンアップは .travis.yml の変更のみ) 大(ビルドサーバー自体を管理する必要があり、Golangバージョンアップはサーバー全体に影響する) 成果物 AWS S3 ビルドサーバーのストレージなど .travis.yml は下記のように定義しています。 .travis.yml dist: trusty language: go go: - 1.7.5 env: global: - TZ=Asia/Tokyo before_install: - make glide # vendoringツールのGlideをインストールします - make awscli # aws-cliをインストールします install: - make deps # glide instalを実行し、パッケージをインストールします before_script: - make fmt # gofmtを実行します - make imports # goimportsを実行します - make lint # golintを実行します - make vet # go vetを実行します - make mysql # go testで使用するDBのcreate、migrateを実行します - make test # go testを実行します script: - make -f development.mk # Development環境のgo buildを実行します - make -f staging.mk # Staging環境のgo buildを実行します - make -f production.mk # Production環境のgo buildを実行します addons: apt: packages: - mysql-server-5.6 - mysql-client-core-5.6 - mysql-client-5.6 artifacts: s3_region: "ap-northeast-1" paths: - $(git ls-files -o --exclude-standard | tr "\n" ":") debug: true target-paths: $TRAVIS_REPO_SLUG/$TRAVIS_BRANCH bucket: "xxxxx-golang-artifacts" AWS S3へのPUTは、Travis CIの addons である travis-ci/artifacts を使用しています。上記のように定義すると、 s3://xxxxx-golang-artifacts/repository_fqdn/repository_path/branch/au_web_portal.production.linux.amd64.gz のような構成で成果物が配置されます。 AWS S3へのPUTには ARTIFACTS_KEY と ARTIFACTS_SECRET が必要です。こちらはTravis CIの Settings から Environment Variables に定義すると、 .travis.yml に定義する必要がありませんのでお勧めです。 基本的にコードフォーマット( gofmt 、 goimports )、静的解析( golint 、 go vet )、単体テスト( go test )が通らないとGolangビルド( go build )は実行されません。 Travis CIで実行するコマンドなどは Makefile に定義し、 .travis.yml から make する方針としました。 Production環境の go build を実行する Makefile は下記のように定義しました。 production.mk .PHONY: build clean NAME := au_web_portal ENV := production GOOS := linux GOARCH := amd64 HASH := $$(git rev-parse --verify HEAD) DATE := $$(date '+%Y/%m/%d %H:%M:%S %Z') GOVERSION = $$(go version) build: $(NAME).$(ENV).$(GOOS).$(GOARCH).gz $(NAME).$(ENV).$(GOOS).$(GOARCH): GOOS=$(GOOS) GOARCH=$(GOARCH) \ go build -tags=$(ENV) \ -o $(NAME) \ -ldflags "-X main.Hash=$(HASH) \ -X \"main.Date=$(DATE)\" \ -X \"main.GoVersion=$(GOVERSION)\"" \ ./au_web_portal mv $(NAME) $@ $(NAME).$(ENV).$(GOOS).$(GOARCH).gz: $(NAME).$(ENV).$(GOOS).$(GOARCH) gzip -f $< clean: rm -rf $(NAME).$(ENV).$(GOOS).$(GOARCH).gz rm -rf $(NAME).$(ENV).$(GOOS).$(GOARCH) 環境毎に異なる値は Build Constraints を使用し、各環境(Production、Staging、Development)の go build を実行します。 また ldflags オプションを指定し、Golangバイナリに情報を埋め込みます。開発段階ではGitHubへのpush後すぐ( go build が実行される前)にデプロイを実行し、変更が反映されていない状況があったりしました。そのような時に、Golangバイナリがどのような状態で動作しているのかを確認するのに便利です。 デプロイ デプロイは Capistrano を使用しました。AWS S3から各環境に対応したGolangバイナリを取得し、対象サーバーへデプロイします。 下記のようなコマンドを実行します。 $ bundle exec cap branch=master production deploy おわりに Travis CIを使用したGolangビルドを紹介させて頂きました。 今回はTravis CIが実行されると各環境(Production、Staging、Development)に対応したGolangビルドが実行されますが、GitHubブランチルールを策定し、各ブランチに対応した環境のGolangビルドを実行するようにしても良いかもしれません。 例) master ブランチの場合、Production環境のGolangビルドを実行する release/99999999 ブランチの場合、Staging環境のGolangビルドを実行する feature/xxxxxxxx ブランチの場合、Development環境のGolangビルドを実行する またGolangバイナリのAWS S3へのPUTですが、 aws-cli を使用し、下記のように実行しても良かったと思っています。 $ aws s3 cp au_web_portal.production.linux.amd64.gz s3://xxxxx-golang-artifacts/repository_fqdn/repository_path/branch/ 今後はGolangバージョンアップなど、継続的にアップデートや改善をして行きたいと思います。 参考 Travis CIを利用した継続的な成果物の配置
アバター
こんにちは、制作部 松本です。 私はこれまでCSSレイアウトで display プロパティ用いる際、 inline-block や table 、 最近では flexbox を使用してきました。 今でも float を多用している方はあまりいないのではないかと思います。 本記事では、よりフレキシブルなレイアウトが実現できる grid について紹介します。 grid は複雑なため、まだ体系的に全ての機能を説明できるほど私の理解も追いついていないので、今回はざっくりとした紹介となります。 対応ブラウザ 2017年4月現在、すでにほとんどのブラウザで grid が対応可能になっています。 残るはAndroid Browserのみ。 Can I Use CSS Grid Layout gridで何ができるのか 一言で言えば、 flexbox 、 table 同様にマルチカラムレイアウトすることができるプロパティです。 flexbox が一次元的なレイアウトなのに対して、 grid は二次元的なレイアウトが可能(らしい)です。 gridの概要 グリッドコンテナ display:grid; を指定された要素。 グリッドアイテム display:grid; を指定された要素の直下の子要素。 孫要素はグリッドアイテムとみなされない。 グリッドトラック grid の column(列) と row(行) の総称。 html の table タグをイメージしてもらうとわかりやすいです。 グリッドライン グリッドトラック間の線。 グリッドセル グリッドラインによって分けられた個々のスペース。 グリッドエリア 複数のセルをまとめたグリッド内の特定の領域。 gridのプロパティ グリッドコンテナのプロパティ grid 要素をグリッドコンテナとして定義する。 grid-template-columns グリッドトラック(列)のサイズを指定する。 grid-template-rows グリッドトラック(行)のサイズを指定する。 grid-template-areas グリッドエリアの名前を参照して、グリッドテンプレートを定義する。 grid-template grid-template-columns 、 grid-template-rows 、 grid-template-areas を指定できるショートハンド。 grid-column-gap グリッドトラック(列)の間を指定する。 grid-row-gap グリッドトラック(行)の間を指定する。 grid-gap grid-column-gap 、 grid-row-gap を指定できるショートハンド。 justify-items グリッドアイテムの行方向の整列。 align-items グリッドアイテムの列方向の整列。 justify-content グリッドトラックの行方向の整列。 align-content グリッドトラックの列方向の整列。 grid-auto-columns 自動的に生成されたグリッドトラックのサイズを指定する。 grid-auto-rows 自動的に生成されたグリッドトラックのサイズを指定する。 grid-auto-flow 明示的に配置されていないグリッドアイテムの配置を指定する。 グリッドアイテムのプロパティ grid-column-start グリッドアイテム(列)の開始位置を指定する。 grid-column-end グリッドアイテム(列)の終了位置を指定する。 grid-row-start グリッドアイテム(行)の開始位置を指定する。 grid-row-end グリッドアイテム(行)の終了位置を指定する。 grid-column grid-column-start 、 grid-column-end を指定できるショートハンド。 grid-row grid-row-start 、 grid-row-end を指定できるショートハンド。 grid-area グリッドセルに名前を付けて grid-template-areas プロパティで参照できるようにする。 grid-column 、 grid-row のショートハンドでもある。 justify-self グリッドアイテム内のコンテンツを行方向に整列する。 align-self グリッドアイテム内のコンテンツを列方向に整列する。 サンプル 上図のようなレイアウトを実現する場合、HTML/CSSの構造はとても単純です。 HTML <div class="container"> <div class="item item-a">A</div> <div class="item item-b">B</div> <div class="item item-c">C</div> <div class="item item-d">D</div> <div class="item item-e">E</div> <div class="item item-f">F</div> </div> CSS .container { display: grid; grid-template-columns: 100px 100px 100px; grid-template-rows: 100px 100px; grid-gap: 10px; color: #444; } .item { background: #bbb; color: #fff; padding: 10px; font-size: 150%; } .item:nth-child(odd) { background: #333; } このコードを図解すると↓のようになります。 grid-template-columns: 100px 100px 100px; grid-template-rows: 100px 100px; この記述は嚙み砕くと、 width100pxのグリッドアイテムを3列 height100pxのグリッドアイテムを2行 ということです。 ちなみにショートハンドで書くとこんな感じです。 grid-template:100px 100px / 100px 100px 100px; grid-template-columns を以下のように変更します。 grid-template-columns: 100px 100px 100px 100px; すると width100pxのグリッドアイテムを4列 になるので、Dのグリッドアイテムが1行目に移動します。 グリッドアイテムの数が多い場合、 repeat を使うと便利です。 grid-template: repeat(5, 100px) / repeat(5, 100px); これは↓と同じ意味です。 grid-template-columns: 100px 100px 100px 100px 100px; grid-template-rows: 100px 100px 100px 100px 100px; Aのグリッドアイテムだけ、横幅を広げたい場合、 .item-a { grid-column:span 4; } これは table タグの colspan="4" と同様の動きをします。 このとき、Fのグリッドアイテムの height が100px以下になっていますが、 それは grid-template-rows: 100px 100px; の記述で、2行目までしか height を指定していないためです。 次に、 grid でもっとも重要と思われる grid-area について説明します。 この機能を使うためには、まず テンプレートに定義したい要素 に名前をつけます。 .item-a { grid-area: area-a; /* .item-aにarea-aという名前をつける */ } .item-b { grid-area: area-b; } .item-c { grid-area: area-c; } .item-d { grid-area: area-d; } .item-e { grid-area: area-e; } .item-f { grid-area: area-f; } グリッドコンテナにテンプレートを定義します。 .container { display: grid; grid-template-columns: 100px 100px 100px; grid-template-rows: 100px 100px; grid-template-areas:"area-a area-b area-c" "area-d area-e area-f"; grid-gap: 10px; color: #444; } grid-template-areas の値はそのままグリッドアイテムの並びと連動します。 ・グリッドアイテムの順番を変える場合 grid-template-areas:"area-e area-c area-b" "area-f area-a area-d"; ・グリッドアイテムの要素を広げる場合 grid-template-areas:"area-e area-c area-c" "area-e area-a area-f" "area-b area-d area-d"; ・グリッドエリア内に空白部分が欲しい場合 grid-template-areas:"area-e area-c ......" "area-e area-a area-f" "area-b area-d area-d"; 視認性を考え他のエリア名と文字数を揃えていますが、「.」はひとつでも構いません。 ・複雑なレイアウトの場合 grid-template-areas:"area-a ...... ...... area-c" "...... area-e area-f area-c" "area-b area-b area-f area-c" "...... area-d ...... area-c"; こんな面倒くさそうなレイアウトもとても簡単です! grid特有の単位 グリッドアイテムのサイズには px 、 em 、 rem 以外に fr(flex fraction) が指定できます。 grid-template-columns: 200px 2fr 1fr; grid-template-rows: 200px 200px; fr は、グリッドアイテムの配置可能な領域を占める割合を表します。 flexbox の flex-grow と同様の動きをすると考えるとわかりやすいです。 2fr は 1fr の2倍の幅になります。 まとめ ざっくりとした紹介にはなりましたが、いかがでしたでしょうか? 恐らく grid の機能の10分の1程度しか紹介できていませんが、もっと色々な機能を使いこなせるようになれば最強の武器になると思います。 Androidでも対応可能になるその日までに、少しでも理解を進めておいて損はないのではないでしょうか!
アバター
こんにちは、お久しぶりです。mediba広告システム開発部の原です。 前回はpython+TensorFlowで画像から顔認識と分類をする簡単なモデルについて書きました。 機械学習で芸能人の顔を分類してみよう! で、今回ですが、やっぱり流行りのアレ。 流行ってますよね、pix2pix! ということで、pix2pixを使うのに必要な学習素材を動画から簡単に作れますよ、今すぐ始められますよ、という内容です。 開発環境 最初に環境の話です。 本記事の作成・検証環境は以下のとおりです。 Mac OS X 10.12.3 (10.10.5とかでも動作すること確認済み) Python 2.7.10 OpenCV2 2.4.12 概要 pix2pix pix2pixとは、簡単には画像と画像の間の関係性/2画像間の変換パターンの特徴を学習、DNNで表現してしまおう、というプログラムです。 例えば地図から航空写真に変換するための学習を行い、架空の町並みを作ってみたり、線画からカラー画像を作ったりすることができます。 GAN (Generative Adversarial Network)という仕組みを基にしているわけですが、画像から画像への変換という仕組みは数あれど、pix2pixはとにかく簡単に始められて、しかも精度がすごい、と最近話題になってます。 参考: pix2pix 作る学習モデルについて 今回はシンプルに、モノクロ画像を彩色するというモデルを作ってみようと思います。 pix2pixでの学習に用いるのは変換前後それぞれ256×256ピクセルの画像を左右に結合した、512×256の画像(便宜上、これを素材タイルと呼びます)になります。 今回は、弊社メディアである Z TOKYO のプロモーション動画から、学習に必要な素材タイルを作ってみたいと思います。 素材タイル作成 元動画について 今回、素材に用いる動画のスペックは下記のとおりです。 MP4フォーマット 1920 × 1080 30FPS 60sec 動画を読み込み1フレームずつ画像として処理 さて、上記の動画を1フレームずつ処理すれば、1920 × 1080の画像が約1800枚出来ることになります。 ※実際には60秒を少し越えていたので、1875フレームありました これでは多すぎるので、あとで適当な枚数に減らすとして、まずはこの操作を書いていきます。 また、今回は彩色モデルを作るので、併せて画像をグレースケールに変換します。 #coding=utf-8 import cv2 # 入力する動画パスを指定 cap = cv2.VideoCapture("sample.mp4") counter = 0 while(cap.isOpened()): ret, frame = cap.read() if ret == True : counter = counter + 1 gray2 = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY) # 変換するとカラー情報が落ちるので、一度ファイルに保存して開き直す cv2.imwrite('./gray.png', gray2) gray = cv2.imread('./gray.png') print(frame.shape) print(gray2.shape) print(gray.shape) if ret == False and counter != 0 : break cap.release() cv2.destroyAllWindows() これだけです。 デバッグも兼ねて各フレームのshape(高さ、幅、チャネル数)を表示しています。 これを実行すると、 (1080, 1920, 3) (1080, 1920) (1080, 1920, 3) という表示がフレーム数分表示されます。 上から元のフレーム/グレースケール変換/変換後のファイルをファイルから読み直したものの、それぞれのshapeになります。 単純なグレースケール変換ではチャネル数のデータが落ちていることがわかると思います。 あとで素材タイルに結合する際に、データ形式が違うと実行エラーが発生してしまいますので、ここで一旦ファイル保存を経由してデータ形式を揃えています(もっと効率がいい方法があるかもしれませんので、ご存じの方はこっそりおしえてください) 各画像から一部を切り出してタイルに結合 さて、上記処理で作ったframeおよびgrayは、どちらも幅1920×高さ1080の画像になっています。 素材タイルは256pix × 256pixの正方形なので次の図のように位置を指定して切り出していくことにします。 frameおよびgrayは色を除けば同じ画像ですので、それぞれから同じ位置の画像を切り抜けば、彩色有無だけが違った正方形の画像データを取り出すことが出来ます。 このやり方であれば、1フレームごとに28枚の画像を切り出すことが出来ます。 pix2pixの標準トレーニングデータは全体で600枚くらいですので、そのくらいのタイルセットが取れればとりあえずは十分だと思います。 今回は特にデータのスクリーニングを行いませんが、例えばほとんど一色のタイルなどは学習時にゴミになりえます。 そこで、この時点では少し多めにデータを用意することにして、70フレームに一度、この処理を行うことにしました。 (1800 / 70 ) * 28 = 720枚のタイルセットが出来ることになります。 pythonで書くとこうなります。 # 切り出す画像の縦横幅定義 height = 256 width = 256 # 切り出す位置の初期定義 defX = 28 # 縦位置(pixel)初期値 defY = 64 # 横位置(pixel)初期値 # 縦方向ループ for numX in range(4): # 横方向ループ for numY in range(7): cutX = defX + (numX * height) cutY = defY + (numY * width) cutImg = frame[cutX:cutX+256, cutY:cutY+256] cutGray = gray[cutX:cutX+256, cutY:cutY+256] train/test/valに画像振り分け さて、720枚のデータセットができたところで、最後に素材タイルへと結合していきます。 また、pix2pix標準のデータセット600枚は以下のような内訳になっています。 train(学習用データ)400枚程度 test(テスト用データ)100枚程度 val(モデルの検証用データ)100枚程度 これに併せてtrain:test:valを4:1:1になるようにランダムで振り分ける仕組みもついでに実装すれば、動画から素材タイルを作り出すプログラムの完成です。 で、これまで書いたものとあわせて出来上がったものがこちら。 #coding=utf-8 import cv2 from numpy.random import * # 入力する動画パスを指定 cap = cv2.VideoCapture("sample.mp4") counter = 0 dataset_counter = 0 while(cap.isOpened()): ret, frame = cap.read() if ret == True : counter = counter + 1 if counter % 70 == 0 : #70フレームに一度画像処理を行う # グレースケールに変換 gray2 = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY) # 変換するとカラー情報が落ちるので、一度ファイルに保存して開き直す cv2.imwrite('./gray.png', gray2) gray = cv2.imread('./gray.png') # 切り出す位置の初期定義 defX = 28 # 縦位置(pixel)初期値 defY = 64 # 横位置(pixel)初期値 # 切り出す画像の縦横幅定義 height = 256 width = 256 # 縦方向ループ for numX in range(4): # 横方向ループ for numY in range(7): dataset_counter = dataset_counter+1 cutX = defX + (numX * height) cutY = defY + (numY * width) cutImg = frame[cutX:cutX+256, cutY:cutY+256] cutGray = gray[cutX:cutX+256, cutY:cutY+256] # 画像の結合 imgAdd = cv2.hconcat([cutImg, cutGray]) # train:test:val = 4:1:1になるように保存ディレクトリ振り分け key = = rand() * 6 if key < 4: saveDir = 'train' elif key < 5: saveDir = 'test' else: saveDir = 'val' cv2.imwrite('./datasets/sample/%s/%d.png' % (saveDir, dataset_counter), imgAdd) if ret == False and counter != 0 : break cap.release() cv2.destroyAllWindows() これで720枚の素材を振り分けることが出来ました。 学習・結果評価 データ移動 作成したトレーニングデータをpix2pixのdatasets以下に移動します。 $ cp -a datasets/sample ~/pix2pix/datasets/movie トレーニング 移動したデータを下にpix2pixのトレーニングを実施します。 $ DATA_ROOT=./datasets/movie \ name=movieDump2 which_direction=BtoA \ gpu=0 cudnn=0 batchSize=20 \ save_epoch_freq=5 save_latest_freq=10 \ continue_train=0 niter=10 th train.lua パラメータについて以下の設定をしています。 gpu=0 cudnn=0:GPUおよびCUDNNを利用しない save_epoch_freq=5:5回学習毎にモデルを保存する save_latest_freq=10:10回回学習毎に「最新学習モデル」を保存する continue_train=0:前回の「最新学習モデル」を利用して継続学習する ※「最新学習モデル」がないと利用できない niter=10:学習ループ回数定義(デフォルトは200回) pix2pixによる彩色の実施 最後に、ここで作成したモデルで彩色を実施します。 $ DATA_ROOT=./datasets/movie \ name=movieDump2 which_direction=BtoA \ phase=val gpu=0 cudnn=0 th test.lua ここでもGPUおよびCUDNNは利用しません。 評価 実行した結果がこちらになります。 左:グレースケール変換した画像 中:pix2pixが機械的に彩色した画像 右:元動画から切り出した(正解の)画像 今回は10回ループでしたが、これでも結構学習してくれているのがわかると思います。 一方で、 こんな状況になっている画像もありました。 幾つか原因はあると思うのですが、 元動画には上下に黒帯が入っているのに除去しなかったこと 機械的に学習タイルを作成したので、元のタイルが全体的にマットで均一色彩の場合はほぼ効果が出ない(むしろじゃまになりかねない) ということなどが考えられます。 これらは切り出す位置を工夫するとか、ヒストグラムを作って均一な色合いのタイルを予め除去しておくなどの対応が取れると思います。 せっかく多めの素材タイル用意したんだからやっておけよ、という話 このように課題も見つかりましたが、簡単にpix2pixの学習素材が作れることはわかっていただけたかと思います。 それにしても、GANすごいなあ…… まとめ GANすごい GPUない環境でやるもんじゃない Z TOKYO 見に来てね!
アバター
こんにちは、制作部デザイナーの福島です。 大規模な案件になると、ヘッダーやフッターなどの共通パーツをチームのメンバーと共有して制作を進めたい、というときがありますよね。 そこで、今回Creative Cloudライブラリという、Adobe Creative Cloudの制作ツールに搭載されている機能を使ってみました。 実際に使ってみて、チームでデザイン作業を進めるのにとても役立つ機能であると感じました。 今回は、チームでデザインを進める際に役立つCreative Cloudライブラリの機能を中心に紹介したいと思います。 Creative Cloudライブラリとは アセットをクラウド上に登録して使用できるサービスで、使用するにはAdobe Creative Cloudのアカウントが必要です。 Photoshop / Illustrator / InDesign / After EffectsなどAdobe Creative Cloud制作ツールで使用する事ができ、画像やパスなどのグラフィック/ カラー情報/文字スタイル/レイヤースタイルなど、様々なアセットを登録することができます。 ライブラリに登録されたアセットは、登録したAdobe Creative Cloud制作ツール以外からでも使用可能です。 (例えばPhotoshopで登録したアセットを、Illustratorでも使用する…といったような使い方もできます) ライブラリフォルダを作成する では実際にライブラリを作成してみましょう。 今回はPhotoshopを例に紹介していきたいと思います。 ウインドウ→ライブラリからライブラリパネルを開き、右上のメニューから「新規ライブラリを」選択して、任意の名前をつけます。 作成したライブラリフォルダにアセットを登録していきます。 Photoshopで登録できるアセットは、グラフィック / 文字スタイル / レイヤースタイル / カラー情報です。 アクティブなドキュメント内のアセットを選択すると、ライブラリパネル下部のアセット追加アイコンに選択したアセットが表示さます。 アセット追加アイコンをクリックすると、ライブラリ内にアセットを追加することができます。 パスや画像などのグラフィックは、ライブラリパネルに直接ドラッグ&ドロップして追加することもできます。 ライブラリフォルダをチーム内で共有する 作成したライブラリフォルダをチームメンバーに共有してみましょう。 ライブラリパネル右上のメニューから共同利用を選択すると、共同利用者を追加できるポップアップウィンドウが開きます。 共同利用者を招待し、共同利用者が招待を認証すると、共同利用者のライブラリに共有したライブラリフォルダが表示されます。 これでアセットの共同利用ができるようになりました。 共同利用者は必要なアセットを、共通ライブラリから使用したり登録できるようになります。 ※うまく共有がされない場合は、ライブラリパネル右下にあるAdobe Creative Cloudロゴをクリックするか、Adobe Creative Cloudアプリケーションが起動しているかを確認してみてください。 Creative Cloudライブラリを便利に使いこなす リンクされたアセットを使用して、データを常に最新状態に! Creative Cloudライブラリ内のグラフィックなどは、自動でリンクアセットになります。 リンクアセットになっていると、ファイルの変更を行った場合、リンクアセットを使用している全てのプロジェクトファイルが、更新された最新ファイルに置き換わります。 様々なプロジェクトファイルで共通して使用するパーツは、リンクアセットとして登録しておくと便利です。 ここで注意する事は、更新されたリンクアセットは編集しているファイル以外は自動的に更新されません。 他のファイルで更新されたリンクアセットを見ると、レイヤーに三角の黄色いアイコンがついています。 このマークがついていると、リンクアセットが最新ではないという事なので、ウインドウ→属性パネルを開いて、同じ三角のアイコンマークをクリックし「変更されたコンテンツを更新」を選択します。 するとリンクアセットが更新されて最新のデータになります。 レイヤーカンプ機能と合わせて差分を含むデザインパーツも共有 Photoshopの「レイヤーカンプ機能」と合わせて使用すると、差分を含むデータも一つのリンクアセットとして使うことができます。 レイヤーカンプとはレイヤーの形状を記憶しておける機能なのですが、リンクアセットと組み合わせて使うととても便利な機能です。 参考: レイヤーカンプ 《例》ラベルの種類を一つのリンクアセット内に複数増やしてみる まずはリンクアセットとして登録してある「ラベル」をダブルクリックし編集します。 そして「コラム」と「無料」2種類のラベルを、それぞれレイヤーに制作しておきます。 ウインドウ→レイヤーカンプパネルを開き、新規レイヤーカンプから「コラム」のレイヤーカンプを作成します。 レイヤーの「コラム」を表示、「無料」を非表示にし、「コラム」のレイヤーカンプがカレントになっていることを確認して、レイヤーカンプパネル下部の「レイヤーカンプを更新」アイコンを押します。 すると「コラム」のレイヤーカンプにレイヤー「コラム」の形状が記憶されました。 同様に「無料」のレイヤーカンプも作成し、リンクアセットを保存します。 元のファイルに戻り、ウインドウ→属性パネルを開くと、先ほど作成したレイヤーカンプが選択できるようになっています。 これでラベルの種類を一つのリンクアセット内で簡単に切り替えられるようになります。 このように、一つのリンクアセットで何種類もの差分を含むデータを作成する事ができるので、アセットを無駄に増やす事なく管理できます。 アイコンや文字など、一部のデザインパーツをいくつかの種類に変更したい時などに活躍できる技術かと思います。 Creative Cloudライブラリを使ってみた感想 実際の仕事ではヘッダーフッターなど共通パーツを登録しておくことで、チーム間で一貫性のあるデザインデータをスムーズに制作し、チームメンバーに共有する事ができました。 また、デザインがある程度出来上がった後に修正が入る事がしばしばあったのですが、Creative Cloudライブラリに登録しておけば、パーツは常に最新の状態で更新や共有ができます。 共通パーツがデザインデータによって古くなるといった事も、Creative Cloudライブラリで簡単に解決できるようになりました。 Adobe Creative Cloudアカウントがないと共有ができなので、同じ環境でない人と連携するには向いてないと思うのですが、Adobe Creative Cloudを使用しているのであればデザインデータをチーム内で管理するには本当にオススメな機能なので、みなさんもぜひお試しください!
アバター
こんにちは、インフラストラクチャー部の沼沢です。 今回は collectd を使って php の OPcache の情報を CloudWatch に連携する具体例をご紹介したいと思います。 関連記事: nginx の各種情報を collectd を使って CloudWatch に連携する php-fpm のステータス情報を collectd を使って CloudWatch に連携する 前提 Amazon Linux AMI release 2016.09 collectd 5.4.1 php 関連 5.6.28 nginx 1.10.1 jq 1.5 なお、collectd については以下と同等の状態ができあがっているという前提で進めます。 新しい collectd の CloudWatch プラグイン | Amazon Web Services ブログ collectdのCloudWatchプラグインを試してみた | Developers.IO opcache_get_status の値を CloudWatch に連携 opcache_get_status とは、OPcache のキャッシュヒット率などのステータス情報を取得するための関数です。 詳細は PHP: opcache_get_status - Manual をご確認ください。 ローカルから curl http://localhost/opcache.php を実行できるようにするため、DocumentRoot 直下に以下の内容の opcache.php を配置します。 <?=json_encode(opcache_get_status(false)) ?> curl http://localhost/opcache.php を実行すると以下のような json が返ってくる状態になっているとします。 $ curl -s http://localhost/opcache.php | jq { "opcache_enabled": true, "cache_full": false, "restart_pending": false, "restart_in_progress": false, "memory_usage": { "used_memory": 10936200, "free_memory": 123281528, "wasted_memory": 0, "current_wasted_percentage": 0 }, "interned_strings_usage": { "buffer_size": 8388608, "used_memory": 438304, "free_memory": 7950304, "number_of_strings": 4822 }, "opcache_statistics": { "num_cached_scripts": 1, "num_cached_keys": 1, "max_cached_keys": 7963, "hits": 21, "start_time": 1485163987, "last_restart_time": 0, "oom_restarts": 0, "hash_restarts": 0, "manual_restarts": 0, "misses": 1, "blacklist_misses": 0, "blacklist_miss_ratio": 0, "opcache_hit_rate": 95.454545454545 } } URL を叩いて返ってきたレスポンスから情報を取得したい場合には、 curl プラグイン を利用しましたが、今回は json なので curl_json プラグイン の登場です。 Plugin:cURL-JSON - collectd Wiki …と言いたいところですが、curl_json プラグインはうまく動かすことができなかったので、今回は exec プラグイン を利用して、curl と jq の合わせ技で実現したいと思います。 exec プラグインは、collectd が外部スクリプトを実行して、そのスクリプトが決まったフォーマットで標準出力した内容を拾ってくれるというものです。 Plugin:Exec - collectd Wiki こちらも参考にさせていただきました。 collectd の exec プラグインで俺のデータを CloudWatch に飛ばす - ようへいの日々精進XP exec プラグイン を利用して、この json の中から、memory_usage に関する3つの項目 (used, free, wasted) を CloudWatch に連携していきます。 1. 外部スクリプトの作成 exec プラグインでは前述の通り外部スクリプトを実行して値を取得するため、そのスクリプトを用意します。 今回は、 http://localhost/opcache.php のレスポンスの json から情報を取得するため、 curl で実行結果を受け取って jq で値を抽出するスクリプトを用意します。 今回用意したスクリプトは以下です。 このスクリプトを /usr/lib/collectd/opcache_stat.sh に配置します。 #!/bin/sh HOSTNAME="${COLLECTD_HOSTNAME:-localhost}" INTERVAL="${COLLECTD_INTERVAL:-60}" while sleep "${INTERVAL}"; do opcache_stat=`curl -s http://localhost/opcache.php` used_memory=`echo ${opcache_stat} | jq -r '.memory_usage.used_memory'` free_memory=`echo ${opcache_stat} | jq -r '.memory_usage.free_memory'` wasted_memory=`echo ${opcache_stat} | jq -r '.memory_usage.wasted_memory'` echo "PUTVAL \"${HOSTNAME}/exec-opcache_stat/memory-used_memory\" interval=${INTERVAL} N:${used_memory}" echo "PUTVAL \"${HOSTNAME}/exec-opcache_stat/memory-free_memory\" interval=${INTERVAL} N:${free_memory}" echo "PUTVAL \"${HOSTNAME}/exec-opcache_stat/memory-wasted_memory\" interval=${INTERVAL} N:${wasted_memory}" done このスクリプトでは、${INTERVAL} 秒毎に、 http://localhost/opcache.php のレスポンスを受け取り、以下のフォーマットに従って標準出力への echo を繰り返します。 PUTVAL "{Host 名}/{Plugin 名}-{PluginInstance 名}/{Type}-{Metrics 名}" interval={インターバル(秒)} N:{計測値} PUTVAL (恐らく)フォーマットに従って標準出力した値を collectd が拾うための接頭辞だと思います 詳細はこちら → Plain text protocol - collectd Wiki#PUTVAL Host 名 その通り、ホスト名です。 /opt/collectd-plugins/cloudwatch/config/plugin.conf に設定している host と同じもの(=${COLLECTD_HOSTNAME} の値)を指定すると良い感じになります Plugin 名 このプラグインの名前を指定します 今回は exec プラグインを使用しているので exec を指定していますが、任意の名前で構いません(例えば “opcache” 等) PluginInstance 名 CloudWatch のメトリクスの画面で表示される PluginInstance に表示される名前 大項目を Host 名とした場合、PluginInstance は中項目のようなもの 後で出てくる画像を見ていただければどこに出てくるものかわかると思います 任意の値で構いませんので、識別しやすい名前を付けると良いと思います Type php-fpm のステータス情報を collectd を使って CloudWatch に連携する にも出てきましたが、 types.db にあるものの中から指定します types.db については以下を参照 types.db(5) – collectd – The system statistics collection daemon Metrics 名 PluginInstance 名と同じようなものです 小項目という扱いがわかりやすいかと思います インターバル(秒) 何秒間隔で値を取っているか、ということを明示するために指定します N:{計測値} “N” のところは、Unixtime 形式のタイムスタンプを指定するのですが、"N" を指定すると現在時刻という意味の指定になります 計測値には該当する値を指定します 2. collectd の設定ファイル (/etc/collectd.conf) に以下の設定をする 以下の設定ファイルを /etc/collectd.d/opcache.conf に配置する LoadPlugin exec <Plugin exec> Exec nginx "/usr/lib/collectd/opcache_stat.sh" </Plugin> Exec {スクリプト実行ユーザ} "{スクリプトの場所}" という指定の仕方をします。 対象のスクリプトを実行できるユーザなら何を指定しても良いですが、 root は指定できませんのでご注意ください。 今回は php を nginx で動かしているので、nginx ユーザでこのスクリプトを実施するように指定しています。 3. 設定後、collectd を再起動 $ sudo service collectd restart 4. blocked_metrics に以下のものが追加されていることを確認する $ cat /opt/collectd-plugins/cloudwatch/config/blocked_metrics 〜略〜 exec-opcache_stat-memory-used_memory exec-opcache_stat-memory-free_memory exec-opcache_stat-memory-wasted_memory 〜略〜 5. CloudWatch に送る対象として、上記をホワイトリストに追記する $ sudo sh -c 'echo "exec-opcache_stat-.*" >> /opt/collectd-plugins/cloudwatch/config/whitelist.conf' 6. 設定後、collectd を再起動 $ sudo service collectd restart 7. blocked_metrics から追加されたものが消えているのを確認する $ cat /opt/collectd-plugins/cloudwatch/config/blocked_metrics 8. CloudWatch 上でグラフ化されていることを確認する しばらく待つと CloudWatch に以下の通り OPcache の メトリクスが追加されます。 まとめ 今回は php の OPcache のステータス情報を、exec プラグインを利用して CloudWatch に連携してみましたが、exec プラグインを利用すればどんな値でも連携可能ということになるかと思います。 今回のように、利用しようとしたプラグインでうまくいかない場合や、対応しているプラグインが無い場合などに利用していきましょう。
アバター
こんにちは。広告システム開発部の小林です。 現在、データ解析の勉強を行っているので、今回はRFM分析のことを記事にしてみました。 なお、本記事はデータを分類しグラフの描画までを行う内容となっています。 分類後の解析などは本記事には含まれていません。 ︎RFM分析とは RFM分析は、Recency (最新購入日)、Frequency (購入頻度)、Monetary (購入金額)の3つの指標でユーザーを分類し、ユーザーの購買情報を解析する手法です。 RFM分析では、以下のような分析ができます。 Recency が現在に近いユーザーほど将来の収益に貢献する可能性が高い Recency が同じなら Frequency が多いほど常連客になっている Recency が同じなら Frequency や Monetary が高いほど購買力があるユーザー サーバー︎環境 CentOS 6.7 Python 3.5.2 Pythonのパッケージ matplotlib 2.0.0 グラフの描画に使用します numpy 1.11.3 数値の計算に使用します pandas 0.19.2 CSVを読み込み、データの集計に使用します Pythonと各種パッケージのインストールについては割愛させていただきます。 ︎分類方法 Python でのデータの分類方法をご紹介します。 集計対象のデータですが、以下のようなCSVを使用します。 データの内容は、2017/01/01 ~ 2017/01/31 の購入情報となります。 集計日を 2017/02/01 とするため前月となる 2017年1月 のデータを用意しています。 ※サンプルデータなので、グラフの描画に適したデータとなるように加工しています。 order_id,uid,amount,time 1,a,100,1483232400 2,b,200,1483318800 3,c,300,1483405200 4,d,400,1483491600 5,e,500,1483578000 6,f,600,1483664400 7,g,700,1483750800 8,h,800,1483837200 9,i,900,1483923600 10,j,1000,1484010000 . . . 210,ae,50,1485738000 211,ae,50,1485738000 212,ae,50,1485738000 データの内容 order_id : 購入ID(ユニーク) uid : ユーザーID(重複あり) amount : 1回の購入金額 time : 購入日(Unixtime) コードは以下のようになっています。 # 必要なパッケージをimport from mpl_toolkits.mplot3d import Axes3D import numpy as np import pandas as pd import matplotlib.pyplot as plt import datetime as dt # CSVを読み込む conversion_data = pd.read_csv('blog_sample.csv') print(conversion_data.head()) # order_id uid amount time #0 1 a 100 1483232400 #1 2 b 200 1483318800 #2 3 c 300 1483405200 #3 4 d 400 1483491600 #4 5 e 500 1483578000 # 集計日のtimestampを取得 (今回は2017/02/01に指定します) NOW = int( dt.datetime(2017,2,1).strftime('%s') ) # uidを元にグループ化して、集計を行う rfm = conversion_data.groupby('uid').agg({'amount': [np.sum], # 購入金額 'time': lambda x: int( (NOW - x.max()) / 86400 ), # 最新購入日 'order_id': [len] # 購入頻度 }) print(rfm.head()) # amount time order_id # sum <lambda> len #uid #a 100 30 1 #b 400 27 3 #c 800 24 4 #d 450 22 2 #e 1200 20 2 # グラフ描画用の設定 fig = plt.figure() ax = Axes3D(fig) ax.scatter3D(rfm['time'], rfm['order_id'], rfm['amount']) # ️グラフをファイルに保存 filename = "/tmp/3d_output.png" plt.savefig(filename) グループ化している以下の部分は、sum したり 項目の数をカウントするのはすんなりできたのですが、最新購入日の取得に苦労しました。 timestamp のままだと桁数が多いためかグラフ描画時にうまく描画されないので、集計日(2017/02/01)から購入日の timestamp を引いて直近何日前に購入されたかを算出しています。 rfm = conversion_data.groupby('uid').agg({'amount': [np.sum], 'time': lambda x: int( (NOW - x.max()) / 86400 ), 'order_id': [len] }) コードの実行後に[filename]で指定したパスにグラフが画像ファイルとして描画されます。 上記のコードの場合は、[tmp/3d_output.png]に出力されます。 描画されたグラフは以下のようになります。 項目が書かれていないので、ちょっと見づらく感じます。 補足を追記してみました。 所感 Python を書くのが初めてだったので、とても苦労しました。 データ解析の道のりは長いと感じました。 次回はデータの解析に焦点を当てた記事を書こうと思います。 最後まで読んで頂きありがとうございました。
アバター
こんにちは。制作部デザイナーの斉藤です。 バナーを作る際、同じ内容で色々なサイズに展開する事が多いと思いますがファイルをいくつも開いて、ちまちまいじるのってものすごく面倒な作業ですよね。 そこでPhotoshopデータセットなどの便利な機能を使ってテンプレートを作り、一発でサクっと書き出しできないか試してみました。 今回やりたかったこと バラバラなサイズのファイルを1ファイルで管理したい 商品画像を一気に差し替えたい 画像を一回でドーンと書き出したい 商品名・価格を一括で変換したい 今回初めて使うデータセットをはじめ、他にはアートボード、いつもお世話になっているスマートオブジェクト、画像アセットの生成を組み合わせてテンプレートを作成しました。 実際にやってみた 1.バラバラなサイズのファイルを1ファイルで管理したい →アートボードで解決! 1つのpsdファイル上に複数アートボードをつくります。 従来はバナーの点数分(約10点前後)ファイルをつくって、それぞれ個別に作業をしていたのですが、同じファイル上で複数のアートボードを作業できる様に作り変えました。 2.商品画像を一気に差し替えたい →スマートオブジェクトで解決! 通常、写真を縮小した後元のサイズに戻すと荒れてしまうのですが、スマートオブジェクト化してから作業するといつでも再編集できます。 また、オブジェクトとして複製できるので、同じデータを使い回したい時とても便利な機能です。 参照: スマートオブジェクト入門~直しに強いPhotoshopデータを作ろう~ 第1回:劣化知らずの「スマートオブジェクト」 3.画像を一回でドーンと書き出したい →画像アセットで解決! 画像の書き出しが自動化できます。 書き出したいアートボードやレイヤーにファイル名と拡張子(.jpg、.png、 .gif)をつけてPhotoshopメニューの ファイル > 生成 > 画像アセット にチェックを入れます。 保存すると自動的にフォルダが生成され、その後も保存毎に上書きされます。 また、拡張子以外にも出力画質や画像の大きさ(相対値または px、in、cm、mm などのサポートされている形式)なども指定できる様です。 参照: レイヤーからの画像アセットの生成 【スライス不要】Photoshop「画像アセット生成」の基本とつまずきがちな5つのコト 4.商品名・価格を一括で変換したい →データセットで解決! これ自体は以前のバージョンからある機能ですが、初めて使ってみて今まで使わなかったのは損してたなーと後悔しました。ざっくり説明すると、変更したいレイヤーに変数名をつけて、csvで差し替えできる機能になります。 同じフォーマットの文字、背景画像を差し替えて大量の画像を書き出す時にとても便利です。 (1)レイヤーに名前をつける 変更したいレイヤーに任意の変数名をつけます(これは必ず半角英数で)。 ここでは商品名のところを設定していきます。 (2)csvファイルをつくる 前項目でつけた名前をcsvファイルに設定して、さらに差し替えたいテキストも入力します。 ここでレイヤーの変数名と項目数がズレるとエラーになってしまうので、注意しましょう。 (3)データセットから読み込む Photoshopメニューの イメージ > 変数 > データセット > 読み込み から先ほど作ったcsvを読み込みます。 (4)該当箇所に反映されました! 参照: Photoshop データセットで大量の差分イメージを作る方法 やってみてどうだったか 良かった事 始めのテンプレート作りにちょっと時間がかかりましたが、毎回の作業がいくつかの工程で終わるので、かなりの時間短縮になったと思います。 これで急な修正があっても慌てずに対応できて、とても楽チンです! いまいちだった事 テキストが文字数固定でカーニング不要の場合には使えそうですが、毎回不特定な商品名・価格で調整が必須な場合など、複雑な文字組には不向きでした。 背景・文字のテーマ色に関しては、一括変換できるプラグインがありますが、有料のため使用しませんでした。もしかしたらAdobe CCのライブラリでそういう機能あるかもしれませんね。 おわりに Photoshopの自動化の存在は知っていたのですが、ちゃんとやってみたのは今回が初めてだったので知らない事がけっこうありました。 しかし参考にさせていただいたサイトにも書いてあったように、そこまで大量に点数がない場合は逆に準備に手間がかかるので、悩ましいところかも知れません。 Adobe CCについても便利な機能がまだまだいっぱい埋もれていそうなので、引き続き探っていきたいと思います。
アバター
こんにちは、インフラストラクチャー部の沼沢です。 今回は、CloudFront + S3 での IP アドレスベースのアクセス制限を実現する方法をご紹介します。 実現したかったこと 特定の外部拠点から参照されるファイルを S3 に配置したい 独自ドメインが使いたかったため、CloudFront を前段に用意 ファイルへのアクセスを特定の外部拠点の IP アドレスのみに制限したい S3 の URL への直アクセスはさせたくない これを実現しようとしてググってみると、 CloudFront の IP アドレスでのアクセス制限 S3 の IP アドレスでのアクセス制限 CloudFront のみ、S3 のみの方法は出てくるのですが、CloudFront + S3 の方法が出てこなかったので、自分で試してみました。 やったこと 先に設定した内容を箇条書きにしてみました。 AWS WAF で IP アドレスベースのアクセス制限ルールを適用 S3 直アクセスを防ぎ CloudFront 経由に限定するため、オリジンアクセスアイデンティティを利用 目新しいことはしていません。今まで既にあったことの合わせ技です。 上記の設定をすることで、やりたかったことを実現することができます。 それぞれ以下を参考にさせていただきました。 AWS WAF で CloudFront 環境にIPアドレス制限を設定する | dogmap.jp オリジンアクセスアイデンティティを使用して Amazon S3 コンテンツへのアクセスを制限する - Amazon CloudFront 設定詳細 では、設定内容の詳細を解説していきたいと思います。 S3 にファイルを用意 s3://{BucketName}/limited_access/index.html を配置。 index.html の中身は以下です。 <html> <head> <title>選ばれしHTML</title> </head> <body> ようこそ、選ばれし者よ </body> </html> AWS WAF の設定 次に AWS WAF で IP アドレスによるアクセス制限のルールを作成します。 AWS WAF のコンソールを開き、「Create web ACL」をクリックし、以下のように設定を進めて行きます。 Create conditions に戻ったら「Next」クリック 「Review and create」クリック後、内容に問題なければ「Confirm and create」をクリックして web ACL の作成は完了です。 CloudFront の設定 CloudFront のコンソールを開き、「Create Distribution」をクリックし、以下のようにして Web Distribution の作成を進めて行きます。 Restrict Bucket Access バケットへのアクセス制限をかけるか → Yes Origin Access Identity オリジンアクセスアイデンティティを新規に作成するか既存のものから選ぶか → 新規に作成(Create a New Identity) Grant Read Permissions on Bucket バケットに対する読み取り権限を自動で付与するか → バケットポリシーを更新 (Yes, Update Bucket Policy) ※ No にした場合は、自分でバケットポリシーの設定が必要 これら以外の設定項目は要件に合わせて任意に設定してください。 20〜30分程待つと CloudFront の Distribution の作成が完了します。 動作確認 以下の2パターンのアクセスを、許可 IP アドレス・それ以外の IP アドレスで検証します。 S3 の URL でアクセスする CloudFront の URL でアクセスする 許可 IP アドレスからアクセス S3 の URL でアクセスする CloudFront の URL でアクセスする それ以外の IP アドレスからアクセス S3 の URL でアクセスする CloudFront の URL でアクセスする それぞれ期待通りの動作が確認できました。 まとめ この仕組みを利用することで CloudFront + S3 の状況下でも厳密なアクセス制限が可能になります。 EC2 にファイルを置いて Security Group でアクセスを制限する方法でももちろん同じことが実現できますが、EC2 の保守運用という手間がかかりますよね。 静的なファイルであれば、CloudFront + S3 でも同じことができ、EC2 の保守運用をする必要が無くなるため、運用コストの削減が期待できます。 こうしたことの積み重ねが、AWS を使うメリットを享受するために重要なことだと思います。 こうした工夫で運用を楽にしていくことができるという気付きになれば幸いです。
アバター
こんにちは。広告システム開発部の八代です。 今回新しくWaffle.ioというツールを用いてタスク管理をしてみたので、ツールを使ってみた感想を共有したいと思います。 Waffle.ioとは GitHubのIssueやPullrequestを看板として可視化することが出来るツールです。 IssueやPullrequestの状況を看板化することにより、各進捗が1画面で把握することが可能になります。 https://waffle.io/GitHubのユーザ名/GitHubのリポジトリ名 という形でアクセスすると、簡易的にWaffleを使うことが出来ます。 例) Ruby on Railsの公式GitHubをWaffle.ioで見てみる https://waffle.io/rails/rails GitHub標準のKanban機能(Projects)もありますが、このWaffle.ioは標準のKanban機能だけだと実現出来ない痒いところまで手が届く機能が盛り込まれています。 特にラベルの可視化や、複数のIssueを1画面で俯瞰的に見ることができ、1つのIssueに対してのコメントをブラウザ遷移すること無く閲覧・書き込みすることが出来る利便性はとても高いです。 導入方法 GitHubアカウントを持っている状態で https://waffle.io/ にアクセスしてログインをし、アプリケーションをAuthorizeするだけです。 Authorizeには以下の権限を求められます。 後は + Add Project のボタンを押して、どのリポジトリと連携させるかを選ぶだけで導入は完了です。 縦の列を増やしたい場合はProject SettingsからBoard Settingsで移動することが出来ます。 特徴 幾つか特徴はありますが、ここでは何点かを取り上げます。 GitHubのIssueを追加することで看板を増やすことが出来ますが、Waffle上でもIssueを作ることもでき、アサイン、マイルストーン、ラベルも視覚的に見やすく表示されます。 Issueにコメントやテキストを編集してもWaffle側はほぼリアルタイムでレスポンスが返ってきます。 また、Issue単位で重み付けすることができるので、どのくらいのタスクなのかをひと目で把握することが出来ます。(上部には合計値が表示されます) フィルター機能も充実しており、必要なものだけ表示できるようになっています。 また、PullrequestとIssueを関連付けすることもでき、2つの看板を連結(関連付け)させることも出来ます。 使用感 今回私のチームで約2ヶ月使用してみた感想とそこで感じたメリットデメリットをあげます。 導入前 導入する前は、以下の問題点を抱えていました。 プロジェクト全体でどのくらいのタスクが残っているのかわかりにくかった 自分以外のタスクが見えにくかった タスク管理ツールが分散していた (エクセルシートや、taiga.io、redmine、backlogなど) 実際に出来上がっているコードと今行っているタスクが疎結合だった 導入後 導入後にメリットだと感じた点は以下の通り。 1画面にタスクが可視化されて表示されているので、全体が把握しやすかった アサインされている看板の位置がリアルタイムに変わるため、誰が何をやっているのか明確化された GitHubとWaffle.ioだけでタスク管理、ソース管理、WIKI(仕様管理)が行えるようになった PullrequestがどのIssueに対してのものなのかがひと目で分かり、成果物がどのIssueに基づいたものなのかわかるようになった 逆にデメリットだと感じた点 スケジュールが絡むタスクに関しては看板だけでは見づらかった GitHubからIssueを作る際には .github/ISSUE_TEMPLATE.md が機能しているが、WaffleからIssueを作る際は機能していなかった Issueが大量に増えるとその分読み込みに時間がかかってしまう まとめ 今回、導入したチームでは新規プロジェクトかつ、簡易ウォーターフォールモデルで開発していたため、スケジュールを意識したタスク管理方法が好ましかったが、 Waffleのような看板型のタスク管理方法だとスケジュールが絡むタスクに関しては少し扱いが難しかったため、上手く運用に乗せきれず終わってしまいましたが、スクラム開発をする上ではこのWaffle.ioはメリットが多いツールだと感じました。 導入は、現時点(2017年1月時点)で無料で簡単にできるものなので、機会があれば是非試してみてはいかがでしょうか。
アバター
全国の Ansible 派のみなさん、こんにちは。 ブログの投稿頻度急上昇中、インフラストラクチャー部の沼沢です。 今回は Ansible で対話型のスクリプトを自動化する際に利用する expect モジュールについてです。 先日、expect モジュールを利用していて罠にハマったので、その内容と対処法をご紹介したいと思います。 Linux には対話形式の入力を自動化する expect というコマンドがあります。 Linuxの対話がめんどくさい?そんな時こそ自動化だ!-expect編- - Qiita Ansible にも、このコマンドと同じことを実現するモジュールがあり、それが expect モジュールです。覚えやすいですね。 expect - Executes a command and responds to prompts — Ansible Documentation では、この expect モジュールを使って対話スクリプトの自動化をしてみます。 前提 本投稿は以下を対象としています。 Ansible 2.1 以上のバージョン 対向: Amazon Linux AMI release 2016.09 ec2:DescribeInstances が許可された IAM ロールが付いた EC2 インスタンス 基本的な使い方 まずは expect モジュールの基本的な使い方を、例を使ってご紹介します。 例えば、以下はユーザ名とパスワードを求められるようなスクリプトを Ansible で実行しようとした場合の例です。 - tasks: - name: ユーザ名とパスワードが必要なスクリプトを実行 expect: command: sh example.sh chdir: /tmp responses: "^Enter User Name: .*": "user1" "^Enter Password: .*": "password" 上記の task では以下のことを実行しています。 /tmp に移動してから sh example.sh を実行 "^Enter User Name: .*" に一致する文字列で入力待ち状態になったら “user1” と入力して Enter "^Enter Password: .*" に一致する文字列で入力待ち状態になったら “password” と入力して Enter 見ての通り、responses では 質問で入力待ちになる際の文字列を指定する正規表現 と、 その際に入力する文字列 を指定します。 ある日、罠にハマる この使い方で、collectd の CloudWatch プラグインのセットアップスクリプト(※)を自動化しようとした際に罠にハマりました。 ※参考: 新しい collectd の CloudWatch プラグイン | Amazon Web Services ブログ setup.py では、以下の4つの入力が求められます。 Choose AWS region for published metrics: 1. Automatic [ap-northeast-1] 2. Custom Enter choice [1]: Choose hostname for published metrics: 1. EC2 instance id [i-xxxxxxxxxxxxxxxxx] 2. Custom Enter choice [1]: Choose authentication method: 1. IAM Role [hogehoge-ec2-role] 2. IAM User Enter choice [1]: Choose how to install CloudWatch plugin in collectd: 1. Do not modify existing collectd configuration 2. Add plugin to the existing configuration Enter choice [2]: Enter choice [x]: という質問でそれぞれ入力待ちになり、未入力で Enter を押した場合は [x] の数字が選択される事になりますが、今回は以下のように入力したいと考えていました。 Choose AWS region for published metrics: 1. Automatic [ap-northeast-1] 2. Custom Enter choice [1]: 1 Choose hostname for published metrics: 1. EC2 instance id [i-xxxxxxxxxxxxxxxxx] 2. Custom Enter choice [1]: 2 Enter hostname [ip-xxx-xxx-xxx-xxx]: web01 ← EC2 の Name タグの値 Choose authentication method: 1. IAM Role [hogehoge-ec2-role] 2. IAM User Enter choice [1]: 1 Choose how to install CloudWatch plugin in collectd: 1. Do not modify existing collectd configuration 2. Add plugin to the existing configuration Enter choice [2]: 2 これを自動化しようとして Ansible を以下のように書きました。 ## ※※注※※ この Ansible は expect モジュールの部分が期待通りに動きません ## - tasks: - name: collectd インストール yum: name=collectd state=present - name: pexpect インストール (expectモジュールの利用に必要) pip: name=pexpect state=present - name: EC2 インスタンス ID 取得 shell: curl http://169.254.169.254/latest/meta-data/instance-id changed_when: False register: instance_id - name: EC2 インスタンスの Name タグの値を取得 shell: > aws ec2 describe-instances \ --region ap-northeast-1 \ --query 'Reservations[].Instances[?InstanceId==`{{ instance_id.stdout }}`].Tags[][?Key==`Name`].Value' \ --output text changed_when: False register: instance_name - name: collectd の CloudWatch プラグインのセットアップスクリプト取得 get_url: url=https://raw.githubusercontent.com/awslabs/collectd-cloudwatch/master/src/setup.py dest=/var/tmp/setup.py mode=0755 - name: collectd の CloudWatch プラグインセットアップ expect: command: ./setup.py chdir: /var/tmp responses: "Enter choice \\[\\\u001b\\[92m1\\\u001b\\[0m\\]: ": "1" "Enter choice \\[\\\u001b\\[92m1\\\u001b\\[0m\\]: ": "2" "Enter hostname \\[\\\u001b\\[92m.*\\\u001b\\[0m\\]: ": "{{ instance_name.stdout }}" "Enter choice \\[\\\u001b\\[92m1\\\u001b\\[0m\\]: ": "1" "Enter choice \\[\\\u001b\\[92m2\\\u001b\\[0m\\]: ": "2" もう既に正規表現がややこしいですね。 上記で書いてある通り、この expect モジュールの書き方では期待している入力を行ってくれません。しかも エラーにもならない ので質が悪いです。 ※そういう書き方をした自分が一番悪い 上記を実行すると、上から “1"、"1"、"1"、"2” が入力されて処理が終わります。 なぜ期待通りに動かないか よく見たらドキュメントにしっかりと書いてありました。 responses で定義した正規表現で同じ質問が連続でマッチした場合は、最初に定義した回答が全ての質問に適用されるようです。 どういうことかというと、今回の対話スクリプトの入力待ちの部分だけに注目すると、 Enter choice [1]: Enter choice [1]: Enter choice [1]: Enter choice [2]: 上記の4つのうち、 3つ目までは入力待ち時の質問の文字列が全く同じ なのです。 で、expect モジュールの responses を再度確認すると、このように書いていました。 "Enter choice \\[\\\u001b\\[92m1\\\u001b\\[0m\\]: ": "1" "Enter choice \\[\\\u001b\\[92m1\\\u001b\\[0m\\]: ": "2" "Enter hostname \\[\\\u001b\\[92m.*\\\u001b\\[0m\\]: ": "{{ instance_name.stdout }}" "Enter choice \\[\\\u001b\\[92m1\\\u001b\\[0m\\]: ": "1" "Enter choice \\[\\\u001b\\[92m2\\\u001b\\[0m\\]: ": "2" 1〜3つ目までは同じ質問が続いているため、 最初に定義してある入力待ちの質問を指定する正規表現が 1〜3つ目までの質問全てと一致している ため、その回答である “1” が1〜3つ目まで全てに入力されるということになります。 2つ目で “2” が入力されてほしいのにスルーされて悲しい気持ちです。 ではどうするか これもドキュメントに書いてありました。 この例のように書くことで、同じ質問が連続しても順番に従って回答してくれるようです。 そして今回の場合には以下のように書くことで期待通りの動きをするようになりました。(抜粋) - name: collectd の CloudWatch プラグインセットアップ expect: command: ./setup.py chdir: /var/tmp responses: "Enter choice \\[\\\u001b\\[92m1\\\u001b\\[0m\\]: ": - "1" - "2" - "1" "Enter hostname \\[\\\u001b\\[92m.*\\\u001b\\[0m\\]: ": - "{{ instance_name.stdout }}" "Enter choice \\[\\\u001b\\[92m2\\\u001b\\[0m\\]: ": - "2" 同じ質問に対して、回答内容を配列で順番に渡すことで、その順番通りに回答をしてくれます。 1回目の Enter choice [1]: では “1” 2回目の Enter choice [1]: では “2” 3回目の Enter choice [1]: では “1” 1回目の Enter hostname [1]: では取得した Name タグの値 1回目の Enter choice [2]: では “2” これで無事にやりたいことが実現できました。 注意 同じ質問に対して別の回答をする機能は、バージョン 2.1 で実装されたものなので、2.1 未満のバージョンを使っている場合は、同じ質問では同じ回答しかできないという悲しい結果になるのでご注意ください。 まとめ 今回は expect モジュールについての小ネタをご紹介しました。 ドキュメントはちゃんと読みましょう。(自戒)
アバター
こんにちは、インフラストラクチャー部の沼沢です。 前回の nginx に引き続き、collectd を使って php-fpm の情報を CloudWatch に連携する具体例をご紹介したいと思います。 参考: nginx の各種情報を collectd を使って CloudWatch に連携する 前提 Amazon Linux AMI release 2016.09 collectd 5.4.1 php 関連 5.6.28 本投稿の例は、以下と同等の状態ができあがっているという前提で進めます 新しい collectd の CloudWatch プラグイン | Amazon Web Services ブログ collectdのCloudWatchプラグインを試してみた | Developers.IO collectd の設定ファイルについては、デフォルトで記述されいてるコメントアウトの部分は基本的に無視して追記していくスタイルを取ります LoadPlugin だけはコメントアウト解除で対応 pm.status_path の値を CloudWatch に連携 pm.status_path とは、php-fpm のプロセス数などのステータス情報を取得するための設定です。 詳細は PHP: 設定 - Manual をご確認ください。 ローカルから curl http://localhost/www-status を実行して以下のような結果が返ってくる設定ができているものとします。 $ curl http://localhost/www-status pool: www process manager: static start time: 22/Dec/2016:18:32:27 +0900 start since: 406243 accepted conn: 8806 listen queue: 0 max listen queue: 0 listen queue len: 0 idle processes: 4 active processes: 1 total processes: 5 max active processes: 1 max children reached: 0 slow requests: 0 URL を叩いて返ってきたレスポンスから情報を取得したい場合には、 curl プラグイン を利用します。 Plugin:cURL - collectd Wiki curl プラグインを利用して、このレスポンスの中から、プロセスに関する4つの項目 (idle, active, total, max active) を CloudWatch に連携していきます。 余談ですが、レスポンスが json や xml の場合には curl_json プラグイン や curl_xml プラグイン を利用したほうがスマートに設定できます。 これらはまた別の機会にご紹介したいと思います。 1. プラグインをインストール curl プラグインを利用する場合、 collectd-curl をインストールします。 $ sudo yum install collectd-curl 2. collectd の設定ファイル (/etc/collectd.conf) に以下の設定をする #LoadPlugin curl のコメントアウトを外す 以下を追記する <Plugin curl> <Page "www"> URL "http://localhost/www-status" <Match> Regex "^idle processes: *([0-9]+)" DSType "GaugeLast" Type "phpfpm_processes" Instance "idle" </Match> <Match> Regex "^active processes: *([0-9]+)" DSType "GaugeLast" Type "phpfpm_processes" Instance "active" </Match> <Match> Regex "^total processes: *([0-9]+)" DSType "GaugeLast" Type "phpfpm_processes" Instance "total" </Match> <Match> Regex "^max active processes: *([0-9]+)" DSType "GaugeLast" Type "phpfpm_processes" Instance "max_active" </Match> </Page> </Plugin> 上記では、以下をそれぞれ取得するように設定しています。 1つ目の Match: 計測期間内で取れた最後の idle processes の値 2つ目の Match: 計測期間内で取れた最後の active processes の値 3つ目の Match: 計測期間内で取れた最後の total processes の値 4つ目の Match: 計測期間内で取れた最後の max active processes の値 DSType “GaugeLast” は計測期間(デフォルト10秒)内の最後の数値を取得するという意味です。 DSType については以下ご参考まで。 collectd.conf(5) – collectd – The system statistics collection daemon#plugin_tail 3. types.db に Type を追加 Type には、 types.db ファイルに定義してあるものしか指定できません。 上記の設定では Type “phpfpm_processes” を指定していますが、 phpfpm_processes はデフォルトでは用意されていないため、これを追加する作業を行います。 types.db(5) – collectd – The system statistics collection daemon types.db ファイルは、デフォルトでは /usr/share/collectd/types.db にあると思いますが、types.db ファイルの場所は設定ファイルで指定することができます。 TypesDB "/usr/share/collectd/types.db" このファイルに以下を追記します。 phpfpm_processes value:GAUGE:0:65535 4. 設定後、collectd を再起動 $ sudo service collectd restart 5. blocked_metrics に以下のものが追加されていることを確認する $ cat /opt/collectd-plugins/cloudwatch/config/blocked_metrics 〜略〜 curl-www-phpfpm_processes-idle curl-www-phpfpm_processes-active curl-www-phpfpm_processes-total curl-www-phpfpm_processes-max_active 〜略〜 6. CloudWatch に送る対象として、上記をホワイトリストに追記する $ sudo sh -c 'echo "curl-www-phpfpm_processes-.*" >> /opt/collectd-plugins/cloudwatch/config/whitelist.conf' 7. 設定後、collectd を再起動 $ sudo service collectd restart 8. blocked_metrics から追加されたものが消えているのを確認する $ cat /opt/collectd-plugins/cloudwatch/config/blocked_metrics 9. CloudWatch 上でグラフ化されていることを確認する しばらく待つと CloudWatch に以下の通り php-fpm の メトリクスが追加されます。 まとめ 今回は php-fpm のステータス情報を CloudWatch に連携してみましたが、nginx の時も今回も、こうやっていざやってみると設定はとても簡単な印象があります。 collectd の他のプラグインもどんどん使ってみたいと思っていますので、今後も利用して具体例としてどんどん公開していきたいと思います。
アバター