2018年10月17日水曜日

Xcode10 + mojave で CreateML を触ってみる

概要

CreateML が正式に試せるようになったので Playground でとりあえず試してみました
学習されるデータは画像情報になります

環境

  • macOS 10.14
  • Xcode 10.0

Playground なプロジェクトを作成する

CreateML を最も簡単に試せるのは Playground です
Xcode を立ち上げて「Get started with a playground」を選択します
createml1.png

macOS を選択する

Playground は macOS アプリとして動作させるので macOS を選択しましょう
以下では Single View を選択していますが何でも OK です
createml2.png

コーディング

以下のコードを貼り付けましょう

import CreateMLUI

let builder = MLImageClassifierBuilder()
builder.showInLiveView()

左側の矢印から Playground を実行します
そして右上の「Show the Assistant editor」から Live view を表示します
createml3.png

Live view が表示されない場合は Xcode を立ち上げて Assistant editor を開いて少し待ってみましょう
ダイアログが表示され許可すると Live view が立ち上がると思います
それでもダメな場合は再起動して許可のダイアログが表示されるまで繰り返してください

学習および評価データ

Playground の CreateML は画像情報を学習し評価してくれます
今回は犬と猫の画像データをここから取得しました
犬と猫の画像データがそれぞれ 1 万件ほどあります
学習データのラベル付けやテストデータのラベル付けはディレクトリに分けるだけで OK です

今回は犬の学習データ (train/dog) とテストデータ (test/dog)
そして猫の学習データ (train/cat) とテストデータを (test/cat) をそれぞれ 100 ファイルづつコピーして使いました

1 万ファイルすべてを使いたい場合は 9000:1000 くらいで学習:テストで分けると良いと思います

$ tree -d
.
├── test
│   ├── cat (1.jpg から 100.jpgをコピー)
│   └── dog (1.jpg から 100.jpgをコピー)
└── train
    ├── cat (101.jpg から 200.jpgをコピー)
    └── dog (101.jpg から 200.jpgをコピー)

学習データと評価データの選択

Live view にプルダウンがあるのでクリックすると「Training data」と「Validation data」を選択する部分があります
ここに先程作成したデータのディレクトリをそれぞれ選択します
createml4.png

あとは「Train」を押せば学習が始まります
学習している画像が次々と表示されると思います

モデルを保存する

学習が完了するとモデルを保存することができます
与えたテストデータでは 97% の精度が出ていることが確認できます
コンソールには更に詳しい学習状況が表示されています

createml5.png

ImageClassifier.mlmodel という名前のモデルが保存されていると思います
作成したモデルは CoreML を使うことで iOS アプリに組み込むことができます
https://developer.apple.com/documentation/vision/classifying_images_with_vision_and_core_ml

最後に

とりあえず CreateML で画像認識してみました
モデルだけであればかなり簡単に作成できました
あとはモデルを使う方法を学べばすぐにアプリにできると思います

他にもテキストデータを学習したり分類などもできるようです

参考サイト

2018年10月16日火曜日

ブックマークの一覧を取得する方法

概要

Firefox の extension に bookmark の API があります
これを使えばブックマークのタイトルや URL を取得することができます
今回はブックマークツールバーにあるブックマークの一覧を取得してみました

環境

  • macOS 10.13.6
  • Firefox 62.0

manifest.json

  • vim manifest.json
{
  "manifest_version": 2,
  "name": "Test bookmarks",
  "version": "1.0",
  "description": "Fetch bookmarks info",
  "background": {
    "scripts": ["main.js"]
  },
  "permissions": [
    "bookmarks"
  ]
}

permissions.bookmarks が必要です

main.js

  • vim main.js
browser.bookmarks.getTree(function(results) {
  results[0].children.forEach(function(toolbar) {
    if (toolbar.id == "toolbar_____") {
      toolbar.children.forEach(function(bookmark) {
        console.log(bookmark.id);
        console.log(bookmark.url);
      })
    }
  });
});

browser.bookmarks.getTree でブックマーク全体の情報を取得します
次に results[0].children で直下にあるブックマークのディレクトリ情報を取得します
このディレクトリの中にある "toolbar_____" という ID を持つディレクトリがブックマークツールバーになります

この配下の toolbar.children でブックマークの一覧の配列を取得しあとは forEach で回すだけです

取得できる情報は BookmarkTreeNode のオブジェクトになります
今回は id と url の情報を表示しています

動作確認

about:debugging からデバッグ画面を表示して確認すると以下のように表示されると思います
bookmark1.png

最後に

Firefox の extension でブックマークの操作をしてみました
今回は取得しかしませんでしたがブックマークの登録や削除などもできます
提供されている API は以下の参考サイトのリンクに記載されています

参考サイト

2018年10月15日月曜日

Firefox の WebExtension で簡単なローカルストレージを使う方法

概要

Firefox の Webextension で設定内容などを保存したい場合にはブラウザのローカルストレージが使えます
特にクラウドでデータを連携する必要がない場合などは簡単かつ軽量に使えるので便利です
今回は簡単な使い方を紹介します

環境

  • macOS 10.13.6
  • Firefox 62.0

manifest.json

  • vim manifest.json
{
  "manifest_version": 2,
  "name": "Local storage demo",
  "version": "1.0",
  "description": "Store and save your data in local storage",
  "icons": {
    "48": "icons/border-48.png"
  },
  "background": {
    "scripts": ["main.js"]
  },
  "permissions": [
    "storage"
  ]
}

permissions.storage が必要になります

main.js

  • vim main.js
browser.storage.local.get("config", function(value) {
  if (value.config === undefined) {
    browser.storage.local.set({
      config: {
        count: 1
      }
    });
  } else {
    var c = value.config.count;
    c++;
    console.log(c);
    browser.storage.local.set({
      config: {
        count: c
      }
    });
  }
});

あまり良いサンプルじゃないかもしれません、、
ローカルストレージからのデータの取得は browser.storage.local.get で行います
ローカルストレージへのデータの保存は browser.storage.local.set で行います

データは JSON 形式で保存します
そして取得するときも JSON になるのでキーをドットでつないで参照します
初回はデータがなく undefined になるので、その場合は初期化する処理を書いています

今回のサンプルの場合 extension を読み込むたびにカウントアップしていくサンプルになります
about:debugging の画面でデバッグ画面を開いて何度も最読み込みするとカウントアップするのがわかると思います
普通はオプション画面などで使うと思います
ちなみにオプション画面は manifest.json に options_ui を定義することがで実現できます

"options_ui": {
  "page": "options.html"
}

この HTML 内で更に js ファイルを参照してそこで browser.storage.local.set を呼ぶ感じです
こうすることでページやタブを跨いでもデータを横断して参照することができるようになります

最後に

Firefox の Webextension でローカルストレージを使ってみました
データは extension が削除されたりユーザがブラウザのキャッシュ情報などを意図的に削除するとなくなります

用途してはテンポラリー的な感じかなと思うので重要なデータなどはクラウドストレージなどと連携すると良いと思います
storage.sync などを使えば Firefox Account 同士でデータを共有することができます
ただその場合は認証なども必要になるので少し大変な実装になるかとは思います

2018年10月14日日曜日

Patreon の OAuth 認証を使ってみた

概要

Patreon には OAuth の仕組みがありこれを使えば Patreon のサーバ情報に API を使ってアクセスすることができます
今回は OAuth を実現するためのログイン画面の使い方から OAuth 後の API の呼び出しまで基本的な流れを試してみました

環境

  • macOS 10.14
  • Ruby 2.5.1p57
  • patreon-ruby 0.5.0

クライアントアプリ作成

まずは OAuth 用のクライアントアプリを作成しましょう
このページから作成できます
コールバック用の URL は localhost で動作させるアプリを指定します

patreon_oauth3.png

クライアントを作成すると「Client ID」と「Client Secret」が取得できるのでメモしておきましょう

ライブラリインストール

  • bundle init
  • vim Gemfile
gem "sinatra"
gem "patreon"
  • bundle install --path vendor

アプリ作成

今回のアプリの流れとしては

  • Patreon でログインページへ遷移
  • ログインできた場合はコールバック用のページでトークンを取得
  • 取得したトークンを使って Patreon の情報を取得

になります

ログイン画面の作成

まずは Patreon のログイン画面に遷移させるページを作成します

  • vim app.rb
require 'sinatra/base'

class TestOAuth < Sinatra::Base
  get '/login' do
    erb :login
  end
end

/login にアクセスした場合にログイン画面に遷移するリンクを表示します

  • mkdir views
  • vim views/login.erb
<html>
<head>
</head>
<body>
  <a href="https://www.patreon.com/oauth2/authorize?response_type=code&client_id=0xmmvlIcKC0PAhdZHdQ31myNO1qPD4MDQBqHOLoZQ19n5DCmfupfyZdlhwv8ikMe&redirect_uri=http://localhost:9292/callback">login</a>
</body>
</html>

Patreon のログイン画面へのリンクにはフォーマットが決められており https://www.patreon.com/oauth2/authorize に対して response_typeclient_idredirect_url をパラメータに付与してリクエストします
ここで client_id は先程クライアント作成時にメモしておいた「Client ID」を記載してください
また redirect_url もクライアントアプリを作成するときに指定した URL を指定してください
間違っている場合ログイン画面が表示されません

コールバック用のページの作成

ログインに成功した場合に呼び出されるコールバック用のページを作成します
app.rb にコールバック用のリクエストを受け付けるルーティング /callback を追加します

  • vim app.rb
require 'sinatra/base'
require 'patreon'

class TestOAuth < Sinatra::Base
  get '/login' do
    erb :login
  end

  get '/callback' do
    client_id = '0xmmvlIcKC0PAhdZHdQ31myNO1qPD4MDQBqHOLoZQ19n5DCmfupfyZdlhwv8ikMe'
    client_secret = 'Xex5ENhpLeZwv7UdZWBF2HS6bLqwE6cUYQJVkQWevcBMBc2bINhKKeh-l069Uypq'
    redirect_url = 'http://localhost:9292/callback'

    oauth_client = Patreon::OAuth.new(client_id, client_secret)
    tokens = oauth_client.get_tokens(params['code'], redirect_url)

    api_client = Patreon::API.new(tokens['access_token'])
    user = api_client.fetch_user()
    @user_data = user.data
    erb :callback
  end
end

client_id, client_secret は作成したクライアントのものを指定してください
その 2 つからトークンを取得するための oauth_client を作成します
コールバックされたページには code というパラメータが付与されて呼び出されます
その code と redirect_url そして oauth_client を使って get_tokens メソッドを呼び出すことでトークン情報を取得することができます

トークンにはいくつか種類がありますが API をコールするために必要なのは access_token になります
ハッシュとして受け取れるので access_token にアクセスしましょう

あとはトークンを元に Patreon::API で API をコールするためのクライアントを作成し fetch_user などのメソッドをコールすれば OK です

今回は取得したデータをテンプレートに渡してそちらでアイコンと名前を表示します

  • vim views/callback.erb
<html>
<head>
</head>
<body>
  <h1><%= @user_data.full_name %></h1>
  <img src="<%= @user_data.thumb_url %>">
</body>
</html>

動作確認

  • bundle exec rackup config.ru

でアプリを起動しましょう
あとは localhost:9292/login にアクセスするとログインへのリンクが表示されるのでそれを踏み Patreon のログイン画面でログインすればユーザ情報が表示されるはずです

patreon_oauth_demo.gif

最後に

Patreon の OAuth 機能を使ってログインから情報を取得するまでの基本的な流れを紹介しました
OAuth 自体に特に難しかった点はなかったのですが、Ruby のクライアントライブラリの使い方などは知っておく必要がありそうです
公式に Sinatra の OAuth のサンプルもあったのでそれも参考にすると良いかもしれません

今回使用したクライアントアプリはすでに削除しているので client_idclient_secret は使えませんのでご注意ください

参考サイト

2018年10月13日土曜日

ReactJS 入門

概要

ブラウザだけで ReactJS に入門してみました
簡単な英語のチュートリアルがあったのでそれの動かし方を説明します
とりあえず動かしてみたい人向けのチュートリアルになります

環境

  • macOS 10.14
  • ReactJS 16

サンプルコード

雛形の index.html の作成

  • vim index.html
<html>
<head>
  <script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
  <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.26.0/babel.min.js"></script>
</head>
<body>
  <div id="root"></div>
  <script type="text/babel">
  /* 
  ここにコードを記載します
  */
  </script>
</body>
</html>

head の部分に ReactJS を使うためのスクリプトファイルを追加します
babel-standalone も使うのでそれも追加します
どちらも CDN で配信してくれているのでそれを利用します
body 内に ReactJS のコードを記載することで DOM をレンダリングしていきます

Hello world を出力する DOM を追加する

先程のコードの「ここにコードを記載します」に追加します

  • vim index.html
<html>
<head>
  <script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
  <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.26.0/babel.min.js"></script>
</head>
<body>
  <div id="root"></div>
  <script type="text/babel">
  class Hello extends React.Component {
    render() {
      return <h1>Hello world!</h1>;
    }
  }
  ReactDOM.render(
    <Hello />, 
    document.getElementById("root")
  );
  </script>
</body>
</html>

React.Component を継承したクラスで render メソッドを実装します
そして ReactDOM.render で作成したクラスと描画する DOM の情報を指定します

動作確認

あとは index.html をブラウザで開けば「Hello world!」と表示されます
こんな感じで Component を作成して render する感じになります

props と state

ReactJS では props と state というデータが使えます

props

props は ReactDOM.render でクラスを指定した際に属性名と値を指定すると、その値をクラス側で参照することができます
具体的には以下のように変更します

class Hello extends React.Component {
  render() {
    return <h1>Hello {this.props.name}!</h1>;
  }
}
ReactDOM.render(
  <Hello name="hoge" />, 
  document.getElementById("root")
);

JSON を渡すこともできます

const d = {"first":"hoge","family":"fuga"}
class Hello extends React.Component {
  render() {
    return <h1>Hello {this.props.name.family}!</h1>;
  }
}
ReactDOM.render(
  <Hello name={d} />, 
  document.getElementById("root")
);

state

state は値が変更されると再度 render を走らせることができるデータです
ボタンをクリックすると state を変更し表示内容を変更してみます
以下のように ReactJS の部分を書き換えます

class Hello extends React.Component {
  render() {
    return(
      <div>
        <h1>Hello {this.state.name}!</h1>
        <button onClick={this.updateName}>Click me!</button>
      </div>
    )
  }
  constructor() {
    super();
    this.state = {
      name: ""
    };
    this.updateName = this.updateName.bind(this);
  }
  updateName() {
    this.setState({
      name: "hawk"
    });
  }
}
ReactDOM.render(
  <Hello />, 
  document.getElementById("root")
);

ReactDOM.render は元のやつに書き換えます
まず render() メソッドで初期状態の DOM を return するようにします
ボタンを新たに設置しクリック時に onClick={this.updateName} をコールさせます
Hello 側は state に応じた値を表示させるため {this.state.name} を設定します

updateName メソッド内で setState をコールします
state を変更する場合は直接プロパティを参照することはせず setState メソッドを呼ぶようにしましょう
これでクリック時の挙動は定義できました

あと新たに追加したのは constructor() になります
これは Hello クラスが呼び出された場合に一番始めにコールされる初期化用のメソッドです
ここで this.state に対して初期化します
今回の場合、空文字で初期化しているので画面を呼び出した際には名前は何も表示されません

そして最後に this.updateName = this.updateName.bind(this); しています
これは updateName メソッドに this を bind しているのですがこれをすることでどうなるかというと updateName 内で this が参照できるようになります

これで動作確認するとボタンが表示されボタンをクリックすると名前が表示されるのがわかると思います

最後に

ReactJS を使って簡単な DOM のレンダリングと基本となるデータの使い方を学びました
かなり簡単なチュートリアルだったので更にやってみたい方は公式のチュートリアルがオススメです
ゲームを開発するチュートリアルっぽいです
他には今回参考にさせて頂いたチュートリアルの続きでチャットアプリを開発するチュートリアルもあるのでこれも良いかもしれません

フロントエンドのフレームワークはやたらありそれぞれが独自の思想を持っているケースが多いのですべてを覚えてることは不可能ですが、ReactJS や Angular, jQuery あたりのメジャーどころは覚えておいて損はないかと思います

参考サイト

2018年10月12日金曜日

macOS で react-native 入門

概要

macOS で react-native を使って iOS アプリを開発してみました
なお今回は expo を使わずに react-native-cli を直接使います

環境

  • macOS 10.14
  • Xcode 10.0 (10A255)
  • node 10.1.0
  • watchman 4.9.0
  • react-native-cli 2.0.1
  • react-native 0.57.2 -> 0.57.1

事前準備

まずは環境を構築します

  • brew install node
  • brew install watchman

node は 8.3 以上が必要です

react-native-cli のインストール

react-native-cli プロジェクトを作成したり

  • npm install -g react-native-cli

Xcode コマンドラインツールのインストール

Xcode を開き Preferences -> Locations からインストールできます

新規アプリケーションの作成

とりあえずサンプル用のアプリケーションを作成します

  • react-native init AwesomeProject

いろいろとダウンロードが始まります
プロジェクトの作成が完了し各プラットフォームでのビルド方法が表示されれば OK です

To run your app on iOS:
   cd /Users/hawksnowlog/work/AwesomeProject
   react-native run-ios
   - or -
   Open ios/AwesomeProject.xcodeproj in Xcode
   Hit the Run button
To run your app on Android:
   cd /Users/hawksnowlog/work/AwesomeProject
   Have an Android emulator running (quickest way to get started), or a device connected
   react-native run-android
(node:67987) ExperimentalWarning: The fs.promises API is experimental

ios/AwesomeProject.xcodeproj を Xcode で開き設定変更

このままビルドしても Print: Entry, ":CFBundleIdentifier", Does Not Exist のエラーでビルドできません
一度プロジェクトの設定を変更します

  • File -> Project Settings
  • Advanced

で Build Location を以下のように変更します
react-native2.png

Products と Intermediates の先頭に build/ を入力しました
またプルダウンは「Relative to Workspace」を選択します

とりあえず動かしてみる

  • cd AwesomeProject
  • react-native run-ios

でシミュレータが起動しテストできます
8081 で LISTEN するビルドサーバが立ち上がるのですでにポートを使っている場合はプロセスを停止しましょう
ビルドしてパッケージを転送するのでアプリが起動する前に結構時間がかかります
またやたら warning が出ている感じがしますが気にせず進めます
環境によるかもしれませんがシミュレータは iPhone6 が立ち上がりました
react-native3.png

ターミナルでは以下のようになっていれば成功です
これが 100% にならない場合はどこかでビルドが失敗しています
react-native4.png

error: bundling failed: Error: Unable to resolve module ./../react-transform-hmr/lib/index.js

どうやら react-native 0.57.2 で出るようです (参考)
0.57.1 にダウングレードしてビルドし直しましょう

  • vim package.json
"react-native": "0.57.1"
  • npm add @babel/runtime
  • npm install
  • rm -rf $TMPDIR/react-*; rm -rf $TMPDIR/haste-*; rm -rf $TMPDIR/metro-*; watchman watch-del-all

をしてから再度 react-native run-ios してみてください

ちょろっとコードを改修する

App.js を編集しましょう
とりあえずウェルカムメッセージの部分を変更しました

<Text style={styles.welcome}>Welcome to React Native!!!</Text>

保存して Command + r をシミュレータ上で実行するとアプリの変更が反映されていると思います

最後に

react-native を使って iOS アプリをビルドしてみました
Android 用のビルド環境を作れば Android でもビルドできると思います

今回は最新バージョン (0.57.2) でバグのような挙動があったので苦労しましたが本来であればエラーは出ずにビルドできると思います
一度ビルドしてアプリを起動すれば変更などはすぐに確認できるのは良いかなと思います

ただ最近は Airbnb が react-native での開発をやめたりとあまり良いニュースがないイメージです
もともと Swift なり Kotlin で native 開発できるのであれば多少手間でも将来性を考えると native を使ったほうが良いかなと思います

参考サイト

2018年10月11日木曜日

Firefox の Webextension で Notifications を出す方法

概要

Firefox の Webextension では新たに Notification が使えるようになりました
今回は簡単な使い方を紹介します

環境

  • macOS 10.13.6
  • Firefox 62.0

manifest.json

  • vim manifest.json
{
  "manifest_version": 2,
  "name": "Notifications sample",
  "version": "1.0",
  "description": "Display a notification when starting",
  "icons": {
    "48": "icons/border-48.png"
  },
  "background": {
    "scripts": ["main.js"]
  },
  "permissions": [
    "notifications"
  ]
}

ポイントは permissionsnotifications を追加するところです

main.js

  • vim main.js
browser.notifications.create({
  "type": "basic",
  "iconUrl": browser.extension.getURL("icons/icon-48.png"),
  "title": "Hello",
  "message": "This notification is test"
});

type は basic を使っています
他にも image や list, progress などがあります
詳細はこちらをご覧ください
iconUrl は通知時にアイコンを指定することができます
指定のアイコンがない場合はデフォルトで Firefox のアイコンが表示されます
title, message は通知に表示する文字列情報になります

今回の場合はアドオンを読み込みした時点で通知します
本来であればクリックのイベントやコールバック処理などで呼び出しましょう

動作確認

about:debugging から manifest.json を読み込みましょう
すると以下のように通知が来ると思います
notification1.png

最後に

Firefox の Webextension で Notifications 機能を使ってみました
background で動作させる場合には window.alert などが使えないので代用として Notifications は良いかもしれません
また HTML が不要なのも嬉しい点かなと思います

通知時のイベントもあり onButtonClicked, onClicked, onClosed, onShown があります (参考)

通知はウザがられがちですが使い方によっては便利なのでうまく活用してみてください

2018年10月10日水曜日

Ruby で ReactiveX に入門してみた

概要

そもそも ReactiveX とは何かという話ですが簡単に言えば Ruby においては Enumerable の拡張という感じです
簡単に言い過ぎているので詳細は公式などを見てほしいのですが、例えば普通の Ruby の配列 ArrayRx::Observable な配列として扱うことで使いやすくすることができます
今回は RxRuby サンプルを動かしつつ ReactiveX を理解してみました

環境

  • macOS 10.14
  • Ruby 2.5.1p57
  • rx 0.0.3

ライブラリインストール

RxRuby という gem が公開されているのでこれを使います

  • bundle init
  • vim Gemfile
gem "rx"
  • bundle install --path vendor

とりあえず動かしてみる

サンプルにあるものをとりあえず動かしてみましょう
Rx::Observable な配列を作成して zip を使って各要素を結合します

  • vim rx1.rb
require 'rx'

a1 = Rx::Observable.from_array [1, 2, 3]
a2 = Rx::Observable.from_array [4, 5, 6]

sub = a1.zip(a2).subscribe(
  lambda { |x|
    p x
  },
  lambda { |e|
    p e
  },
  lambda {
    p "end"
  }
)

これで bundle exec ruby rx1.rb として実行すると

[1, 4]
[2, 5]
[3, 6]
"end"

という感じで表示されると思います

普通に zip してみる

もし Rx::Observable な配列を使わない場合は以下のようなるかと思います

  • vim nrx1.rb
a1 = [1, 2, 3]
a2 = [4, 5, 6]

r = a1.zip(a2)
r.each { |rt| p rt }

これでも意図した結果を得ることができます

何が違うか

この 2 つのサンプルで大きく違うのは各要素を結合した際の経過を監視 (observe) することがでている部分です
Rx::Observable を使った場合 zip メソッドを subscribe し各要素が結合された際にコールされる lambda を定義することができます
またこの 3 つの lambda は Ruby だけではなく ReactiveX の Observable として仕様が決まっており onNext, onError, onCompleted の 3 つ流れを受け取ることができます
それぞれ結合が「成功した場合」「失敗した場合」「終了した場合」にコールされます

このように結合の処理ごとにその状況を監視することが Rx::Observable を使う大きなメリットかなと思います

ちなみにここでとりあげた zip オペレーションは ReactiveX のページでこういう風に実装しろというのがしっかりと定義されています
各言語の ReactiveX 用のライブラリは基本的にこれらを参考に書くオペレーションが実装されています

また Rx::Observable の処理は非同期処理になります
なので subscribe の処理の完了を待たずに次の処理に進むので注意が必要です

その他のサンプルを動かしてみる

ReactiveX には zip 以外にも様々なオペレータが用意されています
すべて紹介するのは厳しいのでよく使われそうなものを紹介します

timer

  • vim rx_timer.rb
require 'rx'

t = Rx::Observable.timer(3, 1)
s = t.time_interval().pluck('interval').take(10)

sub = s.subscribe(
  lambda { |x|
    p x
  },
  lambda { |e|
    p e
  },
  lambda {
    p "end"
  }
)

while Thread.list.size > 1
  (Thread.list - [Thread.current]).each &:join
end

3 秒待ってから 1 秒ごとに 10 回カウントアップするタイマー処理になります
Rx::Observable.timer(3, 1) の部分がスタートしてから 3 秒待つ処理とインターバルの秒数を 1 秒ごとに設定している部分です
ここを変更すればスタートまでのウェイトとインターバルの秒数を変更できます

take(10) で 10 回カウントアップします
カウントアップはインターバルで指定した秒数待ってからカウントされます

同じようなオペレーションで interval というものもあります

flatMap

flatMap は配列内の各要素順番に処理し最終的に 1 つの配列としてまとめる処理です

  • vim rx_flat_map.rb
require 'rx'

times = [
    { value: 0, time: 0.1 },
    { value: 1, time: 0.6 },
    { value: 2, time: 0.4 },
    { value: 3, time: 0.7 },
    { value: 4, time: 0.2 }
]

s = Rx::Observable.from(times).flat_map { |item|
  Rx::Observable.of(item[:value]).delay(item[:time])
}

sub = s.subscribe(
  lambda {|x|
    p x
  },
  lambda {|e|
    p e
  },
  lambda {|r
    p "end"
  })

while Thread.list.size > 1
  (Thread.list - [Thread.current]).each &:join
end

上記のサンプルはいわゆる遅延インサート的なことを実現しています
Rx::Observable.of(item[:value]) は値をそのまま onNext に流す処理なのですが .delay(item[:time]) と組み合わせことで指定した時間遅延させてから処理を実行することができます

なので出力される順番が 0 から 4 の順番ではなく time で指定した値が小さい方から出力されることになります

0
4
2
1
3
"end"

こんな感じで複数の Observable と組み合わせて配列の中の値を処理したい場合には flatMap を使うのが便利です

reduce

reduce は各要素の前後を順番に取り出し処理するためのオペレータです
例えば普通の Ruby で配列の各要素を足し合わせる場合には sum を使います

a = [1, 2, 3, 4, 5]
p a.sum

# => 15

これを RxRuby で書き換えると以下のように書けます

  • vim rx_reduce.rb
require 'rx'

ra = Rx::Observable.from_array [1, 2, 3, 4, 5]
s = ra.reduce(0) { |x, y|
  x + y
}

s.subscribe(
  lambda {|x|
    p x
  },
  lambda {|e|
    p e
  },
  lambda {
    p "end"
  }
)

reduce(0) とすることで初期値を 0 に設定します
そして取り出す 2 つの要素を x, y で受け取り加算します
加算した値が次の x に代入されます
なので今回の加算の流れは以下のようになります

  1. x=1, y=2 => 3
  2. x=3, y=3 => 6
  3. x=6, y=4 => 10
  4. x=10, y=5 => 15

最後に

Ruby の RxRuby を使って ReactiveX に入門してみました
実際に比較して動かしてみることで使い方やメリットが理解できるかなと思います
リファレンスが Web 上になさそうなのでメソッドなどの詳細はコードを直接見るしかなさそうです

今回紹介したオペレータはほんの一部です
ReactiveX はこのオペレータたちを如何に使いこなすかがポイントなので使いこなすにはオペレータの使い方と挙動を覚える必要があるかなと思います
また Subject と呼ばれる Observer と Observable の 2 つの性能を持った機能も存在します

基本的に ReactiveX は「使うべきケース」「使えるケース」が決まっているかなと思っています
何でもかんでも ReactiveX で書くのは良くないと思います
既存のコードで配列処理を扱う部分がありその処理の内容を逐次監視したい場合などに使ったり、タイマー処理を時系列で眺めたい場合などには適しているかなと思います
また基本は非同期なので要素それぞれに対して処理したい場合には適していますが、結合結果をメイン側で受け取って何かしたい場合には適していないと思います

その辺りのユースケースも先駆者の経験からいろいろと Web を調べると出てくるので参考にしてみるといいかもしれません
ただ RxRuby のユースケースは少ない感じがするので、その場合は他の言語の適用ケースを見ると良いとかと思います

参考サイト

2018年10月9日火曜日

ツールバーに独自のアイコンを表示する方法

概要

ツールバーは URL バーの隣にある領域です
ここに独自にアイコンをセットすることでクリック時の処理などを extension で書くことができます
今回は設置方法とクリックイベントのハンドリングをしてみたいと覆います

環境

  • macOS 10.13.6
  • Firefox 62.0

manifest.json

  • vim manifest.json
{
  "manifest_version": 2,
  "name": "Toolbar test",
  "version": "1.0",
  "description": "Display icon on toolbar and click test.",
  "background": {
    "scripts": ["main.js"]
  },
  "browser_action": {
    "default_icon": {
      "20": "icons/icon-20.png"
    },
    "default_title": "test"
  }
}

browser_action でツールバーにアイコンを配置できます
icons/icon-20.png を適当に配置しましょう
画像がない場合は透明なアイコンが表示されます、透明なのでアイコンがあるかどうかはカーソルを合わせないとわからないので画像を準備しましょう
default_title はカーソルを合わせた際のツールチップの情報です

main.js

  • vim main.js
function onClicked() {
  console.log("clicked");
}

browser.browserAction.onClicked.addListener(onClicked);

browser.browserAction.onClicked にリスナーを追加することでクリックイベントを監視することができます

動作確認 

about:debugging にアクセスして manifest.json を読み込みましょう
アイコンが表示されたクリックすると console.log が表示されると思います
browser_action1.png

最後に

Firefox の Webextension でツールバーを操作する方法を紹介しました
コンテキストメニューの場合は右クリックが必要でしたがツールバーの場合はクリックのみなのでやることが一つ減ります

ただツールバーの場合はアイコン用の画像が必要になります
extension でやりたいことに合わせて適切な UI を選択しましょう

参考サイト

2018年10月8日月曜日

flasgger で作成したアプリに Prometheus のメトリック情報を取得できるようにする

概要

flask-prometheus を使うと簡単にできます
本記事ではやり方を紹介します

環境

  • macOS 10.14
  • Python 3.7.0
  • flask-prometheus 0.0.1

ライブラリインストール

  • vim Pipfile
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]
flasgger = "==0.9.0"
marshmallow = "*"
apispec = "==0.38.0"
flask-prometheus = "*"

[dev-packages]

[requires]
python_version = "3.6"

Pythone3.7 + flasgger の場合 flasgger を 0.9.0 にしないと AttributeError: 'NoneType' object has no attribute 'Str' になってしまいます

  • pipenv install

で OK です

flasgger アプリ作成

  • vim app.py
# coding: utf-8
from flask import Flask, jsonify, request
from flasgger import Schema, Swagger, SwaggerView, fields


class NameSchema(Schema):
    name = fields.Str()


class ConvertView(SwaggerView):
    summary = 'Show your name'
    description = 'Query string converts to json string'
    parameters = [
        {
            'name': 'name',
            'in': 'query',
            'description': 'Your name',
            'type': 'string'
        }
    ]
    responses = {
        200: {
            'description': 'Success jsonify',
            'schema': NameSchema
        }
    }

    def get(self):
        name = request.args['name']
        d = self.external_api(name)
        return jsonify(NameSchema().dump(d).data)

    def external_api(self, name):
        return {'name': name}


app = Flask(__name__)
app.add_url_rule(
    '/convert/',
    view_func=ConvertView.as_view('convert'),
    methods=['GET']
)
Swagger(app)


if __name__ == '__main__':
    app.run(debug=True)

これに flask-prometheus を追加します

flask-prometheus 追加後

  • vim app.py
# coding: utf-8
from flask import Flask, jsonify, request
from flasgger import Schema, Swagger, SwaggerView, fields
from flask_prometheus import monitor


class NameSchema(Schema):
    name = fields.Str()


class ConvertView(SwaggerView):
    summary = 'Show your name'
    description = 'Query string converts to json string'
    parameters = [
        {
            'name': 'name',
            'in': 'query',
            'description': 'Your name',
            'type': 'string'
        }
    ]
    responses = {
        200: {
            'description': 'Success jsonify',
            'schema': NameSchema
        }
    }

    def get(self):
        name = request.args['name']
        d = self.external_api(name)
        return jsonify(NameSchema().dump(d).data)

    def external_api(self, name):
        return {'name': name}


app = Flask(__name__)
app.add_url_rule(
    '/convert/',
    view_func=ConvertView.as_view('convert'),
    methods=['GET']
)
Swagger(app)


if __name__ == '__main__':
    monitor(app, port=8000, addr='0.0.0.0')
    app.run(debug=False)

追加しているのは以下の 3 行です

3a4
> from flask_prometheus import monitor
47c48,49
<     app.run(debug=True)
---
>     monitor(app, port=8000, addr='0.0.0.0')
>     app.run(debug=False)

ポイントはデバッグモードでは動かない点です
デバッグモードが True だと OSError: [Errno 48] Address already in use になってしまいます

動作確認

  • pipenv run python3 app.py

でアプリが動作します
localhost:5000 ではこれまで通り flask アプリにアクセスできます
localhost:8000/metrics にアクセスすると Prometheus 用のメトリックが取得できるようになります

最後に

flask-prometheus を使って flasgger で作成したアプリにメトリック情報を追加してみました
簡単なのでとりあえずアプリの状況を確認したい場合には良いかなと思います
ただ flask-prometheus のメンテナンスがほぼ行われていないのが心配かなと
p-r や issue は上がっているようですがほぼ対応されていません
flask-prometheus のコードはかなり簡単なので自分でメンテしてもいいかもしれませんが、できれば本体に取り込んでほしい気はします

2018年10月7日日曜日

Python の prometheus_client を使ってみた

概要

Python の prometheus_client を使ってみました
Python で開発した Web アプリに組み込むことで Prometheus 用の metrics が取得できるようになります
既存の flask アプリを想定した組み込み方法も紹介します

環境

  • macOS 10.14
  • Python 3.7.0
  • prometheus-client 0.3.1

ライブラリインストール

  • pipenv install prometheus_client

Getting Started

  • vim app.py
from prometheus_client import start_http_server, Summary
import random
import time

REQUEST_TIME = Summary('request_processing_seconds', 'Time spent processing request')

@REQUEST_TIME.time()
def process_request(t):
    time.sleep(t)

if __name__ == '__main__':
    start_http_server(8000)
    while True:
        process_request(random.random())

動作確認

  • pipenv run python3 app.py
  • curl localhost:8000

でメトリックが取得できます

# HELP python_info Python platform information
# TYPE python_info gauge
python_info{implementation="CPython",major="3",minor="7",patchlevel="0",version="3.7.0"} 1.0
# HELP request_processing_seconds Time spent processing request
# TYPE request_processing_seconds summary
request_processing_seconds_count 38.0
request_processing_seconds_sum 21.631802963

デフォルトだと python_info という名前のメトリックが実装されているようです
Summary タイプを使うと自動的にメトリック名の後ろに _count_sum を追加してメトリックを生成してくれます

既存の flask アプリにメトリックを追加する

Counter タイプのメトリックを追加してみます
flask で作ったアプリにアクセスがあった場合にアクセス数をカウントします

  • vim app1.py
from flask import Flask
from werkzeug.wsgi import DispatcherMiddleware
from werkzeug.serving import run_simple
from prometheus_client import make_wsgi_app, Counter

c = Counter('my_counter', 'Description of access counter')

app = Flask(__name__)

@app.route('/')
def index():
    return 'index'

@app.before_request
def before_request():
    c.inc()

app_dispatch = DispatcherMiddleware(app, {
    '/metrics': make_wsgi_app()
})

if __name__ == '__main__':
    run_simple('localhost', 8000, app_dispatch, use_reloader=True, use_debugger=True, use_evalex=True)

まず Counter メトリックの定義は Counter('my_counter', 'Description of access counter') でします
これだけで追加することができます
今回の場合 flask 側のアプリにアクセスがあった場合にこのカウントをインクリメント (c.inc()) します
「flask 側」という表現を使っているのは既存の flask アプリを想定しており、それに対し prometheus_client 側のアプリを追加する感じになります
アプリを追加するには flask の DispatcherMiddleware を使います
これに /metrics には prometheus_client のアプリである make_wsgi_app を設定します
prometheus_client 側のアプリは WSGI で作成されているようです
こうすることで 2 つの異なるアプリを一つのアプリとして扱うことができます

結合したアプリは run_simple を使って起動します

動作確認

  • pipenv run python3 app.py
  • curl localhost:8000/metrics

でメトリックを取得できます
カウンタを増やしたい場合は localhost:8000 にアクセスしてからメトリックを取得するとメトリックが増えていることが確認できると思います

# HELP python_info Python platform information
# TYPE python_info gauge
python_info{implementation="CPython",major="3",minor="7",patchlevel="0",version="3.7.0"} 1.0                                   
# HELP my_counter Description of access counter
# TYPE my_counter counter
my_counter 2.0

最後に

Python の prometheus_client を使ってメトリックを取得できるようにしてみました
既存の flask アプリにも追加することができました

WSGI 側のアプリを直接変更することでメトリック側のページのカウンタを作ることもできるはずですがやり方は不明です

Ruby の prometheus-client ライブラリはデフォルトでレスポンスタイムなども返してくれますが Python の場合はそうではないようです

参考サイトに記載したのですが flask であれば専用のライブラリとして公開している方がいるので最低限のメトリックでいいのであればそっちを使ったほうが無難かなと思います
内部的には今回紹介した prometheus_client を使っているので、ソースを参考にして同じように自分のアプリに組み込むこともできます

参考サイト

2018年10月6日土曜日

prometheus-client を使って独自の exporter を作成する方法

概要

前回 prometheus-client を使ってデフォルトのメトリックを自分のアプリに組み込む方法を紹介しました
今回はカスタム Exporter を作成して独自のメトリック情報も取得できるようにしてみます
なお、組み込むアプリは Rails アプリではなく Sinatra をベースにしたアプリになります

環境

  • macOS 10.14
  • ruby 2.5.1p57
  • prometheus-client 0.8.0

カスタム Controller の作成

まず Controller を作成します
Controller では独自で追加するメトリックのメタデータの登録を行います

  • vim helper/custom_controller.rb
require 'prometheus/client'

module Prometheus
  module Controller
    prometheus = Prometheus::Client.registry
    gauge = Prometheus::Client::Gauge.new(:online_flag, 'Check the flag whether it is online')
    prometheus.register(gauge)
  end
end

とりあえずこんな感じで作ります
Gauge は 1 or 0 のような二値を返却するときに使うタイプになります
新しく Gauge のオブジェクトを作成し引数にメトリック名と説明を与えます
あとは prometheus.register することで Controller を登録します

なおこの状態で config.ru から参照すると /metrics にメトリック名だけ表示することができます

require './helper/custom_controller'

カスタム Exporter の作成

次に Exporter を作成します
Exporter は実際にメトリックに出力するデータを操作する役割になります

  • vim helper/custom_exporter.rb
require 'prometheus/middleware/exporter'

module Prometheus
  module Middleware
    class CustomExporter < Prometheus::Middleware::Exporter

      def respond_with(format)
        guage = @registry.metrics.first
        guage.set({
          name: :my_site
        }, online?)
        super
      end

      def online?
        0
      end

    end
  end
end

まず Prometheus::Middleware::Exporter を継承します
そして respond_with を実装します
ここで先程登録したメトリック項目を @registry で参照できます
@registry には登録したメトリック情報がキュー配列で格納されています
先程登録したメトリック (:online_flag) は先頭に格納されるので .first で取得することができます
ちなみに今回の場合であれば配列は 4 つあり残り 3 つはデフォルトで Exporter に実装されているメトリックが入っています

あとはメトリックにラベルとコールバック用のメソッドを set します
ラベルはハッシュで好きな内容が登録できます
コールバック用のメソッドは実際にメトリックとして出力する内容を取得するメソッドになります
今回は単純に 0 を返却していますが実際はここに例えばサイトがオンラインであるかどうかチェックするロジックを記載します

Tips

もしデフォルトのメトリック情報を削除したい場合は Prometheus::Middleware::Exporter を継承しないで独自メトリック情報の文字列を返却する Exporter を作成する必要があります

@registry.metrics の配列から Prometheus::Client を削除しようとしても readonly なフィールドなので削除することはできません

config.ru から呼び出す

あとは Controller と Exporter を config.ru から参照するだけです

  • vim config.ru
require './helper/custom_controller'
require './helper/custom_exporter'
require 'prometheus/middleware/collector'
# require 'prometheus/middleware/exporter'

use Rack::Deflater
use Prometheus::Middleware::Collector
# use Prometheus::Middleware::Exporter
use Prometheus::Middleware::CustomExporter

Prometheus の部分だけ抜粋しています
Prometheus::Middleware::Exporter は作成したカスタム Exporter が継承しているので不要になります

またあとで気づいたのですが、デフォルトのメトリック情報を削除したいのであれば prometheus/middleware/collectorPrometheus::Middleware::Collector を削除すれば出なくなります

最後に

Ruby の prometheus-client を使って独自のメトリックを作成する方法を紹介しました
Controller でメトリックの項目を登録し、Exporter で登録したメトリックに対して実際に値を取得するロジックを実装する感じになります

今回 Exporter は Prometheus::Middleware::Exporter を継承して実装しました
おそらくこの方法が一番簡単な方法だとは思います
が、実はこの方法にこだわる必要は全くありません
というのも Prometheus は結局 exporter と HTTP で話しができてその出力結果がフォーマットに沿った text/plain であれば良いのです
なので、自分で erb を作成してそれを出力してもいいですし単純にフォーマットに沿ったテキストファイルを読み込んでそれを Sinatra で出力する感じでも全然問題ありません

今回の方法を使うと、その辺りのフォーマット関係を気にせず実装できるという感じになります
あとは Prometheus から exporter にアクセスがあったときに値の集計を行うコールバックが呼ばれるので無駄なメトリック取得のためのアクセスやコールを防ぐことができるというメリットもあります

参考サイト

2018年10月5日金曜日

Prometheus ruby client を自分のアプリに組み込んでみよう

概要

prometheus-client という gem を使うと自分の Web アプリケーションに簡単に Prometheus で監視するためのメトリックを追加することができます
具体的には /metrics にアクセスするとアプリの監視のためのメトリックが取得できるようになります

環境

  • macOS 10.14
  • ruby 2.5.1p57
  • prometheus-client 0.8.0

ライブラリインストール

  • bundle init
  • vim Gemfile
gem 'sinatra'
gem 'prometheus-client'
  • bundle install --path vendor

Web フレームワークは Sinatra を使います
prometheus-client の Rack 用のモジュールを使うためです

メトリック取得用のロジック追加

prometheus-client にはすでにいくつかのメトリックが用意されています
Rack の設定ファイル (config.ru) にモジュールを読み込む設定を追加するだけで簡単にメトリックを取得できます

  • vim config.ru
require 'prometheus/middleware/collector'
require 'prometheus/middleware/exporter'

use Rack::Deflater
use Prometheus::Middleware::Collector
use Prometheus::Middleware::Exporter

上記を既存の config.ru に追加してあげましょう
先頭にでも追加すれば OK です

動作確認

  • bundle exec rackup config.ru

でアプリを起動したら localhost:9292/metrics にアクセスしてみましょう
ブラウザで確認すると以下のようなメトリックが取得できると思います
prometheus_client1.png

基本的にはアクセスに関するメトリックでアクセス数やレスポンスタイムなどを返却してくれます

最後に

Ruby の prometheus-client を使って自分のアプリでメトリックが取得できるようにしてみました
最低限のアプリの QA がしたいのであればこの程度の情報でもいいかもしれません

今回はデフォルトで用意されたメトリックを追加しました
実際のケースでは独自のメトリックも取得したくなると思います
この状態から独自のメトリックを追加する方法は別の記事で紹介したいと思います

参考サイト

2018年10月4日木曜日

Google さんから突如 Google AdMob ad serving has been disabled to your application という連絡が来て広告が表示されなくなったので対処してみた

概要

Googleさん (正確には Admob) からタイトルの連絡がありアプリ内で広告が出せなくなりました
それは困るので対処してみました

環境

  • Admob (2018/10/03 時点)
  • Android Studio 3.2.0

時系列

  • 9月27日 警告の連絡が来る
  • 9月27日 アピール送信
  • 9月28日 アピール送信二回目
  • 10月2日 アピール送信三回目
  • 10月3日 9時、解除連絡が来る
  • 10月3日 10時、広告表示を確認

以下それぞれ詳細を紹介します

内容

まず来たメールは以下の内容でした
admob_disable1.png

メインの内容は

This email is to alert you that one of your applications is not currently in compliance with our AdMob program policies and as a result, ad serving has been disabled to your application.

要するにあなたのアプリから広告を表示しなくしたよということが書いてあります
で、その下あたりに該当のアプリの情報があります

そして一番下にそうなった理由が書いてあります

GOOGLE PLAY REMOVAL: AdMob publishers are not permitted to abuse or promote the abuse of any Google product, such as Google Play, YouTube, or Blogger. This includes circumventing, or providing the means to circumvent, the policies or terms of these or other Google products, such as by allowing users to download YouTube videos.

If your app is removed by Google Play policy enforcement, please contact Google Play about an app removal here. If Google Play reinstates your app, please submit an appeal to our team.

GOOGLE PLAY REMOVAL という規約に引っかかりました
もし対処したりアピールしたい場合はここから連絡してね
ということが書いてあります

この GOOGLE PLAY REMOVAL のところはいろいろと種類があるらしく自分は GOOGLE PLAY REMOVAL でした
要するに Google Play からアプリが非公開になってしまったということらしいです
思い当たる状況を考えてみました

なぜこうなったのか

何の理由もなく Google さんがこんなことをするわけがないと思ったので思い当たることを考えみました

実は少し前にアプリが「プライバシーポリシーの記載がない」ということでリジェクトを食らっていました
しかしそれはすぐに対応して apk も公開し直しその後は問題なく配信されていました
おそらくその時のラグで Google Play から Admob に連絡が行くのが遅れて、まだアプリが非公開で対処されていないと判断され今回の連絡が来たのだと判断しました

つまりすでにアプリはプライバシーポリシーの対処をして公開状態になっているという旨を連絡すれば何とかなりそうだということに気が付きました

で、早速アピールしてみる

メールにもアピール用の URL が記載があります
https://support.google.com/admob/contact/appeal_policy_violation

admob_disable2.png

画面は一部抜粋ですがアプリの ID や今回の Issue の ID、Google Developer のメールアドレスを入力します
そして最後に「What changes have you made to your app or account in order to comply with our program policies? *」という欄がありここにアピールを書きます
文章の内容は残していなかったので記憶からですが以下のような内容を送信しました

  • プライバシーポリシーの警告が来てアプリがリジェクトされたのは間違いない
  • が、それは対処して apk も修正して公開し直した
  • 現在は問題なく公開されている

適当に英文を作成して送信しましたが日本語のページに切り替えることもできるので日本語でもアピール可能です
で、この内容でアピールすると以下のような自動返信が来ました
admob_disable3.png

あとは待つことしかできないかなーということで返信を待つことにしました
が、連絡が来るまでの間は当然広告は表示されません

連絡が来ないので何度か連絡してみた

時系列的に記載したのですが最終的に 3 回もアピールしてしまいました
今思うと必要なかったなと反省しています

土日も挟んだので当然と言えば当然かなと思います
営業日的には木、金、月、火、水 なので 5 営業日かかった計算になります
こちらの非で今回の状況になったのを考えると妥当な数字かなと思います

ただ不確かなのはこれが 3 回連絡したからそうなったのかどうかです
例えば 1 回だった場合には実はもっと遅くなっていたのでは、逆に早くなっていた可能性もあるかも、という点に関しては何とも言えません

さすがに 1 週間経っても何も音沙汰がない場合はお尻をつついてもいいかもしれません (もしちゃんとアプリの対応しているのであれば)

解除連絡到着

以下のメールが来ました
メールが届いてもすぐには広告が表示されることはなく何時間かかかりました
admob_disable4.png

一応自分の場合は 1 時間後に再度確認してみたら問題なく表示されていました

最後に

Admob の広告が突如表示されなくなったので対応してみました
基本はアプリの対応をちゃんとして Admob にアピール連絡するしかないと思います
最近は Google Play もちゃんとアプリの審査をするようになったかなと思います
良いことだと思うので続けて行ってほしいなとは思いますが開発者側にも負担が増えるので頑張らないなとは思います

他の理由で広告が非表示になるケースもあるらしいですが基本は同じ流れかなと思います
本当に何も思い当たる節がないのに連絡が来たのであれば、それもとりあえずアピールしてみると良いかと思います
詳細な理由を教えてくれるかは不明ですが

2018年10月3日水曜日

Prometheus と cAdvisor でコンテナの死活監視をする方法

概要

cAdvisor のメトリックを使って Prometheus でコンテナの死活監視をする方法です
どうやら cAdvisor のバグっぽいのでいずれはこの方法を使わないでも監視できるようになるかもしれないです

環境

  • Ubuntu 16.04 LTS
  • Prometheus 2.4.2
  • Prometheus AlertManager v0.15.2
  • cAdvisor v0.27.4
  • docker 18.03.1-ce

Prometheus の監視ルール

特定のコンテナを死活監視するルールは以下の通りです

groups:
  - name: 'Containers monitoring'
    rules:
    - alert: 'container_1 not running'
      expr: absent(container_tasks_state{name="container_1",state="running"}) == 1
      for: 5s
      annotations:
        summary: "{{ $value }}"

ポイントは absent を使う点です
本来 container_tasks_statestate に応じて適切な値を返却するはずなのですがどの state も常に 0 を返却するバグがあるようです
そしてコンテナが停止した場合には何もデータが入ってきません

absent はデータがない場合に 1 を返却してくれる関数になります
つまりデータがない = コンテナが停止しているときにアラートを上げる監視ルールを追加して上げれば OK です

Issue を見ると bug のラベルが振られているのでいずれ修正される問題かなと思います

参考サイト

2018年10月2日火曜日

Prometheus のアラート機能を試してみた

概要

Prometheus には蓄積したデータからアラートを発泡することができます
アラートの種類はさまざまあり Slack や Gmail などに通知することができます
アラートを通知するには AlertManager というプロセスを立ち上げる必要があります
今回は AlertManager の構築方法と構築した Prometheus サーバとの連携方法を紹介します
また簡単な通知のテストを Slack を使ってやってみました

環境

  • Ubuntu 16.04 LTS
  • Prometheus 2.4.2
  • Prometheus AlertManager v0.15.2
  • docker 18.03.1-ce

事前準備

Prometheus サーバの構築と Slack の Incomming Webhook のインテグレーションの設定は済ませておいてください

Prometheus サーバに AlertManager を登録する

まずは Prometheus サーバに通知を依頼する AlertManager を登録します
登録するには YAML ファイルを編集して Prometheus サーバを再起動します

  • vim prometheus.yml
alerting:
  alertmanagers:
  - static_configs:
    - targets:
      - '192.168.100.20:9093'

alertmanagers ディレクティブを使います
192.168.100.20 は AlertManager です
あとでこのサーバ上で AlertManager を構築します
ポートは AlertManager デフォルトの 9093 を使用します

Prometheus サーバに監視ルールを登録する

Prometheus に蓄積データを使って通知を飛ばすには監視ルールを作成する必要があります
ここで言う監視ルールとは単純に「CPU 使用率 80%」などのルールになります
ルールを登録するには例のごとく YAML ファイルを編集します
ちなみにルールを UI から追加することはできないので新規で監視ルールを追加する場合はルール用の設定ファイルを毎度編集する必要があります

  • vim prometheus.yml
rule_files:
  - 'alerts.rule'

alerts.rule ファイルを別途作成しここに監視のルールを記載します
ルールも YAML で定義します
Prometheus のバージョン 2 からルールも YAML で定義できるようになりました
バージョン 1 を使っている場合は監視ルールの YAML 定義が使えないので注意してください

groups:
  - name: 'Containers monitoring'
    rules:
    - alert: 'container_1 cpu usage alert'
      expr: container_cpu_usage_seconds_total{name=~"container_1",cpu="cpu01"} > 80
      for: 5s
      annotations:
        summary: 'container_cpu_usage_seconds_total:container_1 > 80'

ポイントは exprforannotatins.summary になります

expr は監視する式を記載します
container_cpu_usage_seconds_total は cAdvisor の exporter を使って取得できるデータになります
cAdvisor と Prometheus を連携する方法はこちらを参照してください
あとは引数に監視するコンテナ名と CPU が複数あるので 1 つ分の CPU を指定します
この値が > 80 になったときにアラートが発泡されます
> 80 は単純に CPU 使用率が 80% を越えたらという条件になります

for> 80 の条件がどれだけの期間続いたらアラートを発泡するかの指定になります
つまり 5s を指定した場合は 1 秒や 2 秒だけ 80% を越えてもアラートは来ません

annotations.summary はアラート時のメッセージになります
今回は使っていませんが $value などの変数が使えます
CPU の使用率などが変数に展開されるのでそれを通知内容に含めることができます

AlertManager 用の設定ファイルを作成する

AlertManager 用の設定ファイルには主に通知先の設定を記載します
今回は Slack に通知するので Slack の Incomming Webhook の URL やチャネルなどを定義します

  • vim alertmanager.yml
route:
  receiver: 'containers_notification'
receivers:
- name: 'containers_notification'
  slack_configs:
    - api_url: 'https://hooks.slack.com/services/xxxxxxxx/xxxxxxxxx/xxxxxxxxxxxxxxxxxxxxxxxx'
      channel: '#general'
      text: "{{ .CommonAnnotations.summary }}"
      send_resolved: true

api_urlchannel に関しは Slack の情報を入力してください
ポイントは {{ .CommonAnnotations.summary }} でルールで定義した annotatins.summary の内容を参照することができます

AlertManager を起動する

今回は Docker で起動します

  • docker run -d -p 9093:9093 --name alertmanager -v $(pwd):/alertmanager prom/alertmanager --config.file=/alertmanager/alertmanager.yml

で 9093 で LISTEN すれば OK です
ブラウザでアクセスすると簡単な管理画面を確認することができます

alertmanager: error: unknown short flag '-c', try --help というエラーが出る場合は引数を見直してください
--config.file でハイフンが 2 つ指定が必要です
-config.file でハイフンが 1 つだと上記のエラーが発生します

Prometheus サーバを起動する

すでにコンテナが起動している場合は docker restart だけで OK です
まだコンテナが起動していない場合は以下で起動します

  • docker run -d -p 9090:9090 --name prometheus -v $(pwd):/prometheus-data prom/prometheus --config.file=/prometheus-data/prometheus.yml

9090 にアクセスすると管理画面が確認できます

動作確認

まずはちゃんと監視ルールが Prometheus サーバに認識されているか確認しましょう
Alert を開いて監視ルールが存在するか確認します
prometheus_alertmanager1.png

現状は CPU 使用率が 80% 以下なのでアラートが飛びません
適当にコンテナの CPU 使用率を上げると AlertManager に対して通知依頼おリクエストが飛びます

Slack には以下のようにアラートが来ると思います
prometheus_alertmanager2.png

最後に

Prometheus のアラート機能を試してみました
ポイントは監視ルールの定義と AlertManager の通知設定かなと思います
まずは監視ルールだけ追加して問題なくルールが動作しているようであれば AlertManager を追加する流れが良いかなと思います

通知先と AlertManager の登録は一度行えば OK なので後は監視ルールの変更をチューニングしましょう

参考サイト

2018年10月1日月曜日

Prometheus と cAdvisor を連携してコンテナごとのメトリックを取得する方法

概要

前回 dockerd の Daemon metrics を使って docker コンテナのメトリック情報を監視してみました
ただ Daemon metrics の場合各コンテナごとのメトリック情報は取得できませんでした
今回は代わりに cAdvisor を使い各コンテナごとのメトリック情報を取得してみました

環境

  • Ubuntu 16.04 LTS
  • Prometheus 2.4.2
  • docker 18.03.1-ce
  • cAdvisor v0.27.4

cAdvisor のインストール

監視対象の docker ホストで以下のコマンドを実行します
普通に cAdvisor をインストールしています
ホスト側は 8080 ポートを使っているのですでに 8080 ポートを使っている場合は別のポートにしてください

docker run \
  --volume=/:/rootfs:ro \
  --volume=/var/run:/var/run:ro \
  --volume=/sys:/sys:ro \
  --volume=/var/lib/docker/:/var/lib/docker:ro \
  --volume=/dev/disk/:/dev/disk:ro \
  --publish=8080:8080 \
  --detach=true \
  --name=cadvisor \
  google/cadvisor:latest

この時点で cAdvisor はインストールされているので localhost:8080 にアクセスすると cAdvisor の UI でメトリック情報を確認することができます

Prometheus に監視対象を追加する

  • vim prometheus.yml

scrape_configs に以下の job_name を追加しましょう

scrape_configs:
  - job_name: 'worker_cadvisor'
    static_configs:
    - targets: ['192.168.100.12:8080']

追記したら Prometheus のコンテナを再起動します

  • docker restart prometheus

prometheus の部分は Prometheus のコンテナ名またはコンテナ ID になります

動作確認

Status -> Targets に監視対象の exporter が追加されていると思います
スタータスが UP になっていることを確認しましょう
メトリック名は container_ から始める項目になります

最後に

Prometheus と cAdvisor を連携してみました
これで各コンテナごとのメトリックが Prometheus で監視できるようになります
目的のコンテナのデータを取得するには promQL を駆使する必要があります

参考サイト

2018年9月30日日曜日

Prometheus で docker コンテナを監視する方法

概要

Docker の daemon metrics の機能を使うと簡単に Prometheus と連携することができます
Docker Damon metrics を有効にする方法と Prometheus との連携方法を紹介します

環境

  • Ubuntu 16.04
  • Prometheus 2.4.2
  • docker 18.03.1-ce

Docker Damon metrics の有効化

事前に停止が必要なコンテナは停止しておいてください

  • touch /etc/docker/daemon.json
  • vim /etc/docker/daemon.json
{
  "metrics-addr" : "0.0.0.0:9323",
  "experimental" : true
}
  • systemctl restart docker

これで 9323 ポートでメトリックが取得できるようになります
念のため netstat -an | grep 9232 などでポートが開いているか確認してください

Prometheus で監視対象を追加する

  • vim prometheus.yml

scrape_configs に以下の job_name を追加しましょう

scrape_configs:
  - job_name: 'worker2_docker'
    static_configs:
    - targets: ['192.168.100.12:9323']

追記したら Prometheus のコンテナを再起動します

  • docker restart prometheus

prometheus の部分は Prometheus のコンテナ名またはコンテナ ID になります

動作確認

Status -> Targets を確認すると監視対象が追加されていると思います
メトリックの名前は daemon_swam_ から始まる項目になります

最後に

dockerd の Daemon metrics の機能を使って使って Prometheus と連携してみました
連携自体はかなり簡単に行えます

ただ、Daemon metrics はかなり簡易的な情報しか取得できません
非公式ではありますが cAdvisor と連携する方法があります
cAdvisor の方が確かに各コンテナの情報を取得できるので細かい情報まで確認することができます

今回の方法は docker 公式のページで紹介されている方法なので今後取得できるメトリックの量などが増えるといいなーと思っています

参考サイト

2018年9月29日土曜日

Prometheus を試してみた

概要

Prometheus は次世代の監視システムとして話題のツールになっています
基本構成は監視対象のサーバに exporter と呼ばれるエージェントをインストールすることで監視できるようになります
exporter には様々な種類があり監視したいプロセスやミドルウェアによってインストールする exporter を変更します
今回は基本的なサーバ監視として node exporter をインストールしてサーバ監視をしてみました

環境

  • サーバ
    • Ubuntu 16.04 LTS
    • Prometheus 2.4.2
  • 監視対象
    • Ubuntu 16.04 LTS

Prometheus サーバ構築

設定ファイル作成

  • vim prometheus.yml
global:
  scrape_interval:     15s
  evaluation_interval: 15s

alerting:
  alertmanagers:
  - static_configs:
    - targets:
      # - alertmanager:9093

rule_files:

scrape_configs:
  - job_name: 'worker'
    static_configs:
    - targets: ['192.168.100.10:9100']
  - job_name: 'worker2'
    static_configs:
    - targets: ['192.168.100.11:9100']

ポイントは scrape_configs でここで監視対象のサーバを指定します
9100 ポートは監視対象のサーバで node exporter をインストールした際に LISTEN するデフォルトのポートです

Prometheus はここで定義された監視対象に対して scrape_interval で指定された間隔ごとにメトリックの取得を行います

サーバ起動

  • docker run -d -p 9090:9090 -v $(pwd):/prometheus-data prom/prometheus --config.file=/prometheus-data/prometheus.yml

作成した設定ファイルをコンテナにマウントして起動します

localhost:9090 にアクセスすると、とりあえず Prometheus の UI にアクセスできます

node exporter のインストール

監視対象のサーバで以下のコマンドを実行するだけです

  • docker run -d --net="host" --pid="host" quay.io/prometheus/node-exporter

動作確認

Prometheus の UI からStatus -> Targets を確認しましょう
prometheus1.png

監視対象の一覧が表示されます
スタータスが up になっていれば監視が始まります

うまくステータスが OK にならない場合は ufw などの設定を確認してください
Prometheus サーバから各サーバの 9100 ポートにアクセスできる必要があります

あとはトップ画面から表示したいメトリックを選択して「Execute」すれば OK です
node exporter で取得できるメトリック名はサフィックスに node_ が付与されています
以下は「node_memory_MemFree_bytes」の表示結果です
prometheus2.png

タグを Graph に切り替えれば時系列のグラフを確認することができます

最後に

Prometheus に入門してみました
よく Docker などのコンテナ監視をする際に Prometheus が出てくることがあります
Docker には Prometheus のメトリック情報をデフォルトで取得できる方法があるため連携しやすいという感じです
その方法も次回紹介したいと思います

参考サイト