新着記事

Re:dash ダッシュボードツールを使って、運営しているあらゆるサービスの数値を1ページで見る

Re:dash とは

リダッシュ。公式サイトはこちらになります。

http://redash.io/


公式サイトには動作可能デモもあるので、試しに触っていただくとわかりやすいと思うのですが、簡潔に言うと

  • Webアプリ上で SQL などクエリー文を登録
  • 結果を表やグラフで、そのまま表示
  • その表やグラフのけっかをまとめて1つのページに組み合わせることができる

というツールです。「ダッシュボード」を作るWebアプリですね。


TORICO では、2016年に Redash を使い始めましたが、使い勝手が良いので継続して使っていきたいです。

オープンソースです。

https://github.com/getredash/redash/

クエリーは定期的に実行してくれます。設定も、「10分ごと」「12時間に1回」「1週間に1回」のようなざっくりした指定ができるのがとても便利です。時刻指定で毎日実行させることもできます。

クエリーはページを表示する毎に実行されるわけではないので、少し重いクエリーでも気兼ねなく実行できます。今までは自前でダッシュボード作って、クエリーをいちいちキャッシュするようなコードを書いてましたが、Redash ではプルダウンから選ぶだけなので簡単で良いです。

グラフ(ビジュアライゼーション) いくつか


棒グラフ、線グラフの他にも、使い勝手の良いビジュアライゼーションがいくつか入っています。

ピボットテーブル


二次元でのクエリ結果… 例えば、日付 ✕ アイテム ✕ 販売数  ですとか、四半期間 ✕ サービス ✕ 売上 のようなクエリ結果を、整形せずともきれいな二次元表として表示できます。

カウンター



ただ数字を表示します。ダッシュボードの上部に、達成率などを表示すると使い勝手が良さそうですね。

コホート表

お客様継続率の表示に使うやつですね。

https://demo.redash.io/queries/67

※デモを見ていただくとわかるのですが、コホートの分析結果 までを自前で作る必要があります。
Redash がやってくれるのは、分析結果から表を生成する所だけです。

ユーザーID と デイリーアクセスのログ だけでは、この表は作れずに、一度分析した結果を DB に格納するとか、それなりの解析ができるクエリーを自分で作らないといけないです。

その他、株価チャートなどに使う箱ひげ図、地図上プロット、パイチャート、XY相関表のようなものを表示できます。

インストール方法

参考: http://docs.redash.io/en/latest/setup.html

AWS、Google Compute Engine でイメージが公開されていますので、これを使うのが一番手っ取り早いでしょう。

上記参考サイトの特定リージョン ( 日本なら ap-northeast-1 ) の、AMI のリンクをクリックして、ボタンをクリックしていけばすぐに出来ます。

Ubuntu 用のプロビジョニングスクリプトが用意されているようなので、Ubuntu であれば自分でインストールも出来なくはなさそうです。

SSL 設定


イメージから立ち上げた場合、nginx が HTTP サーバとなります。
SSL証明書をサーバにコピーし、/etc/nginx/sites-available/redash を修正してその SSL 証明書を使うようにすれば、redash は問題なく HTTPS で動きます。

ドキュメントはこちらです http://docs.redash.io/en/latest/misc/ssl.html

離れたネットワークの DB に接続する

インフラが AWS で完結している場合は、セキュリティグループなんかで権限管理すれば RDS など DB への接続は問題無いと思いますが、AWS の外にあるサーバなどにも繋ぎたい所です。

MySQL over SSL で接続する、などの手も考えられますが、セキュリティ周りの調整がシビアになりそうです。

平文通信を SSHトンネルの中を通すのが簡単でしょう。

常時 SSH トンネルをキープするツールとして、autossh というものがあり便利です。

インストール

$ sudo apt-get install autossh

このように起動します。

$ autossh -M 0 -f -N -L 127.0.0.1:13306:127.0.0.1:3306 user@example.com


-M 0 … 接続確認ポートを使わない
-f … デーモンモード
-N … SSH接続設定

参考: autossh - MQTT and …

この設定の場合、

1. まず、SSH で user ユーザーで example.com ホストへ接続する
2. example.com ホストから見た、127.0.0.1:3306 ポートを、
3. authossh 起動ホストの、127.0.0.1:13306 にバインドする

となります。

この autossh が起動している状態で、redash サーバ上で

$ mysql --host=127.0.0.1 --port=13306 --user=hoge --password


とすれば、接続先の example.com の中で起動している MySQL サーバに、SSH 越しに接続できるというわけです。

※ redash イメージは MySQL クライアントコマンドは入ってないので、実際には上のコマンドは事前に
$ sudo apt-get install mysql-client-core なんかでインストールしておく必要があります。

SSH 秘密鍵の作成

SSH で redashサーバからリモートサーバに接続する際は、SSH キーペアの作成が必要です。
定番ですが一応書いておきます。

redashサーバ上で

$ ssh-keygen -C redash-server@example.com

.ssh/id_rsa , .ssh/id_rsa.pub
が出来ます。

-C 以下は公開鍵に書き込まれるコメント


id_rsa を上書きしたくない場合は -f でパスを指定

$ ssh-keygen -f ~/secret -C redash-server@example.com


id_rsa.pub の内容を、接続先サーバの ~/.ssh/authorized_keys に追記 (ファイルが無ければ作成)

これで、redash から接続先サーバに SSH で接続できるようになります。

アップデート方法


Redash は頻繁にアップデートされていますので、追従してサーバをアップデートするとバグが直ってたり新しい機能が使えたりするので幸せです。

ドキュメント http://docs.redash.io/en/latest/upgrade.html

アップデートの前に、EC2インスタンスのイメージをバックアップのために作っておくと良いでしょう。

アップデートは、mac など手元のPC から fabric を使って行います。fabric は、Python のデプロイツールです。capistrano みたいなやつです。TORICOでも多くのケースで使っています。

$ pip install fabric requests


で fabric をインストールし、

https://gist.github.com/arikfr/440d1403b4aeb76ebaf8

ここから fabfile.py をダウンロードします。

fabfile.py があるディレクトリ (もしくはそれ以下のディレクトリ) で、

fab -H{your re:dash host} -u{the ssh user for this host} -i{path to key file for passwordless login} deploy_latest_release


このように、deploy_latest_release を実行すると簡単にアップデートできます。

この fabfile には deploy_latest_release 以外にもいくつかのタスクが登録されています。

タスク一覧を表示するには

$ fab -l

Redash を使ってみての感想


非常に良いです。TORICO では、複数のサービスを様々な環境下でリリースしていますが、それらの数値をまとめて1つのページに簡単に出来るのは便利です。IAM が作られているので環境構築が簡単だというのも良いですね。

会社、もしくは部署で1つ作っておいて、autossh で各サーバにつなぎ、数値は全部そこで見る。みたいな運用が理想的だと思います。

Python + Selenium で、簡単にブラウザの自動操作をする

mac 上の Python から、Selenium を使って簡単に Firefox を自動操作できます。

Firefox がインストールされている必要があります。Python は、2 でも 3 でも大丈夫です。

単純な Google 検索

コマンド1発で「Hello, world!」で google 検索をするところまでを書きます。

1. selenium をインストール

$ sudo pip install selenium

2. pythonスクリプトを作成

hello_selenium.py

#!/usr/bin/env python

from selenium import webdriver

if __name__ == '__main__':
    driver = webdriver.Firefox()
    driver.get('http://google.com')
    driver.find_element_by_css_selector(
        'input[name="q"]').send_keys("Hello, world!")
    driver.find_element_by_css_selector('input[type="submit"]').click()

3. 実行

$ chmod +x hello_selenium.py
$ ./hello_selenium.py

Firefoxが起動し、Hello, world! で Google 検索されたと思います。簡単ですね。

このパターンは、フォームに入力し submit するだけですが、応用すると認証ページに自動ログインしたりなどはすぐに書けると思います。

いつも使っている Firefox ではなく、クッキーや履歴などがまっさらな状態の Firefox を簡単につくれるので、テストには重宝します。

何度も実行すると、Dock が Firefox だらけになります。以下のコマンドで一気に kill できます。

$ killall firefox-bin

スマートフォンの User-Agent でアクセスする

#!/usr/bin/env python

from selenium import webdriver

if __name__ == '__main__':
    user_agent = "Mozilla/5.0 (Linux; Android 4.1.1; Nexus 7 Build/JRO03D) " \
                 "AppleWebKit/535.19 (KHTML, like Gecko) " \
                 "Chrome/18.0.1025.166 Safari/535.19"

    profile = webdriver.FirefoxProfile()
    profile.set_preference("general.useragent.override", user_agent)
    driver = webdriver.Firefox(firefox_profile=profile)
    driver.get('http://www.mangazenkan.com')

webdriver.Firefox の引数に、FirefoxProfile を与えると設定を変更できます。 これは、Android のUA を設定しているので、UA で表示を分けているタイプのサイトでは、SP 表示のテストができます。

レスポンシブデザインが主流だと思うので、あまり UA で分岐するようなサイトは最近見ませんけどね。

プロキシ設定をする (socks)

#!/usr/bin/env python

from selenium import webdriver

if __name__ == '__main__':
    profile = webdriver.FirefoxProfile()
    profile.set_preference('network.proxy.type', 1)
    profile.set_preference('network.proxy.socks', '127.0.0.1')
    profile.set_preference('network.proxy.socks_port', 10080)
    driver = webdriver.Firefox(firefox_profile=profile)
    driver.get('http://example.com')

これで、プロキシの socks 設定を上書きできますので、

$ ssh -N -D 10080 ubuntu@example.com

予め、このようなコマンドで socks トンネルを作っておき、( デーモンモードで起動するには -f を付与します。& してもいいかも)

このコマンドを実行すれば、socks 先の踏み台サーバを経由して Firefox を使えるため、アクセス元を考慮した確認に大変便利です。例えば、外国からのアクセスを試してみたい場合など。

EPUBファイルから画像を抽出する

電子書籍フォーマットとして広く使われている EPUB ファイルから、連番で画像を抽出する方法です。

ツール作りました! pip でインストールできます。https://github.com/ytyng/epub-extract-jpeg

EPUB ファイルの概要

EPUB ファイルとは、平たく言えば ZIP圧縮された XHTML です。 コミックで一般的に使われる形式では、1ページが1つの XHTML ファイルになっており、その中に 1 つの img タグが あり、画像ファイルにリンクされています。

そのため、手順としては

  • EPUB ファイルを解凍
  • 構成情報の XML ファイルを解析し、ページ画像の URL (パス) を取得
  • ページ画像を連番で改名コピー(移動)

となります。

1. EPUBファイルを解凍

unzip で一発です。

$ mkdir /tmp/epub-extract
$ unzip sample.epub -d /tmp/epub-extract

2. 構成情報の XML ファイルを解析し、ページ画像の URL (パス) を取得

まず、展開後のディレクトリにある META-INF/container.xml を開きます。 ここに、rootfile というタグがあるので、その full-path 属性を見ます。 full-path の XML ファイルが、各ページの目次のようなものになります。

full-path が示す XML ファイルで、manifest タグの中に item タグが複数あります。 これらは、EPUB 中の XHTML から使われているファイルです。

3. ページ画像を連番で改名コピー(移動)

この、item タグの中はおそらくページ順になっているので、このファイルをスクリプトで連番で改名コピーしながら収集すれば、ページ画像を抽出できます。

本来であれば、直接画像のパスを読むのではなく、ページの XHTML ファイルを開き、そこからリンクされている画像を収集していくのが正しいのですが、ページの XHTML の順と画像の item タグの順が一致しないケースは稀だと思いますので(EPUB 作成者が意図的に XHTML ファイルと画像ファイルの順番を一致させなかった場合などは、ページ数が正しく取得できません)、item タグの順で処理して基本的には問題無いでしょう。

これで、EPUB ファイルから画像ファイルを抽出する方法は終わりです。 最後に、Python スクリプトにした例を掲載しておきます。

from __future__ import print_function, unicode_literals

import os
import time
import sys
import subprocess
import shutil
from xml.etree import ElementTree

TEMP_DIR = '/tmp/epub-extract-{}'.format(int(time.time()))


def procedure(file_path):
    if not os.path.exists(file_path):
        print("{} is not exist.".format(file_path), file=sys.stderr)
        return

    output_dir, ext = os.path.splitext(file_path)

    if ext != '.epub':
        print("{} is not epub.".format(file_path), file=sys.stderr)
        return

    if os.path.exists(output_dir):
        print("{} is already exists.".format(output_dir), file=sys.stderr)
        return

    os.mkdir(TEMP_DIR)

    subprocess.Popen(
        ('unzip', file_path, "-d", TEMP_DIR),
        stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()

    os.mkdir(output_dir)

    container_xml_path = os.path.join(TEMP_DIR, 'META-INF', 'container.xml')
    etree = ElementTree.parse(container_xml_path)
    rootfile_node = etree.find(
        ".//{urn:oasis:names:tc:opendocument:xmlns:container}rootfile")
    content_opf_path = rootfile_node.attrib['full-path']

    content_xml_path = os.path.join(TEMP_DIR, content_opf_path)
    etree = ElementTree.parse(content_xml_path)
    manifest = etree.find('.//{http://www.idpf.org/2007/opf}manifest')
    items = manifest.findall('.//{http://www.idpf.org/2007/opf}item')

    image_paths = []
    for item in items:
        if item.attrib['media-type'] == 'image/jpeg':
            image_paths.append(item.attrib['href'])

    root_dir = os.path.dirname(content_xml_path)

    for i, image_path in enumerate(image_paths, start=1):
        destination_image_name = '{:03d}.jpg'.format(i)
        source_image_path = os.path.join(root_dir, image_path)
        destination_image_path = os.path.join(
            output_dir, destination_image_name)
        shutil.move(source_image_path, destination_image_path)
        print('{} -> {}'.format(image_path, destination_image_name))

    shutil.rmtree(TEMP_DIR)


def main():
    for arg in sys.argv[1:]:
        procedure(arg)


if __name__ == '__main__':
    main()
追記: Github に上げて、pip でインストールできるようにしました。
Search