watermint.org - Takayuki Okazaki's note

Cross compiling Go app with go-sqlite3 on Docker

Part of my Go app in my project, toolbox, using go-sqlite3. I spent couple of hours to setup cross compiling environment for this.

go-sqlite3 using CGO for binding sqlite3 to go library. That requires cross compilers for each target platform. First, I tried to setup mingw64, etc for each platform. But it was easy to implement if you are familiar with xgo.

xgo is a Docker images that pre configured for each Go version and target platforms. If you are using glide for packaging system. Prepare Dockerfile like below.

FROM karalabe/xgo-1.7.x

RUN apt-get update -y
RUN apt-get upgrade -y
RUN apt-get install -y zip git curl

ENV GOBIN=/usr/local/go/bin
ENV PATH=$PATH:/usr/local/go/bin
RUN curl https://glide.sh/get | sh

Then, just build with xgo command like below.

xgo --ldflags="$LD_FLAGS" -targets "windows/*,linux/*,darwin/*" github.com/watermint/toolbox/tools/$t

Mathematicaの練習: Asanaタスクの集計

大学で使ってからもう十数年ぶりになりますがMathematicaをさわっています。数学とあとは機械学習関連を勉強するためMathematicaのHome版を購入してみました。まずはデータを分析したりするところから手始めにやってみようと思っていますが、 十数年ぶりということもありますし、大学時代もさほど深く使い込んでいなかったのでWolfram Languageの知識はほぼゼロの状態です。

慣れたプログラミング言語で書けばすぐできることですが、手始めにAsanaで管理しているタスクを集計したり分析する流れをやってみました。AsanaのタスクはJSON形式でエクスポートができますのでこれを読み込んで集計してみます。

週ごとのタスク数

JSONを読み込むにはImportを使いますがこのとき、”JSON”ではなく、”RawJSON”をつかうとすべてがAssociation(連想配列のようなもの)として読み込むことができます。ここまでくるだけで結構つまづきました。Asanaから得られるJSONは”data”というキーが最初にあるのでいろいろなプログラミング言語の連想配列と同じように["data"]のように書いて値を取得します。値が取り出せたので続いて集計です。

AsanaのJSON書式はAPI referenceあたりを参照しながら処理を進めましょう。たとえばタスクが作成された日時は、”created_at”というキーに対する値として設定されています。時刻の書式はISO 8601形式です。

ISO8601形式をMathematicaで読み込むには DateObject["2017-03-31T09:00:00Z"]のようにDateObjectへ渡してやればよいようです。

手元のAsanaデータは1月中旬に整理整頓してそれ以前はあまり正確ではありませんでしたから、1月中旬以降のものだけを集計対象としています。

asanaTasks = 
  Select[asanaJSON, 
   DateObject[#["created_at"]] > DateObject[{2017, 1, 15}] &];

集計対象としたいタスクだけを取り出すにはSelect関数を使えば良いようです。関数型言語でプログラミングした経験があれば比較的すんなり理解しやすいかと思いますが、&のような簡略書式はMathematica独特なのでこれはマニュアルや例をみながら見様見まねで覚えるしかなさそうですね。

集計をするときもCountsByのような関数で簡単に集計できます。関数がたくさんあるので、プロトタイピングするにはもってこいです。

週ごとに集計するために日付を丸めたかったのですがこれがなかなかわからず苦労しました。

taskCountPerWeek = CountsBy[
   asanaTasks,
   CurrentDate[
     DateObject[#["created_at"]],
     "Week"
     ] &
   ];

最近出た11.1というリリースで追加されたCurrentDateを使うと簡単にできるようです。CurrentDate[Now, "Week"]のように日付の粒度を指定すればその粒度で値がかえってきます。語感としてややこしいのは、CurrentDateとありますがDateObjectなどを渡してやれば与えた日時を基準に変換してくれます。

最後にDateListPlotでグラフを書けば週ごとにどれぐらいタスクが作成されているかわかりました。

Dropbox Business API: Properties API Proof of Concept

I wrote sample script of Properties API of Dropbox Business in Python.

Note: Properties APIs are alpha release (as of Feb 2017). Specification of APIs may change without notice.

Properties API

Dropbox API and Dropbox Business API provide store properties of files or folders. These properties are not displayed on desktop, mobile and web. Properties APIs are designed for application which integrate with Dropbox Business. Here are possible use cases.

  • Store security policies for each files/folders.
  • Store approval status of documents.
  • Store document ID which issued from other CMS.

Properties template

Before storing properties of files/folders. You need to create Properties template in Dropbox Business team. To perform properties template operation, application must have “Team member file access” token. See more detail about auth type at Access Type.

Properties template can have multiple fields. Field can have single string value.

Here are APIs for Properties template. No method for delete template. Applications should maintain properties template id. Because you can create same name properties template. Template name is just for human friendly purpose.

Here is example of list existing templates:

templates = client.team_properties_template_list()

for t in templates.template_ids:
    template = client.team_properties_template_get(t)
    print "Template Id: %s" % t
    print "Template Name: %s" % template.name
    print "Description: %s" % template.description
    for f in template.fields:
        print "Field[%s] Description[%s]" % (f.name, f.description)

Store/retrieve properties of files/folders

To retrieve properties of file/folder, use /files/alpha/get_metadata. For retrieve properties, specify template id in include_property_templates attribute.

To store properties of file/folder, use /files/properties/add or /files/properties/overwrite.

Code sample

Here is complete code sample of use case of properties APIs. This sample expect to store security policy and levels for each files. This sample code using latest Dropbox Python SDK.

import dropbox
from dropbox.files import PropertyGroupUpdate
from dropbox.properties import PropertyFieldTemplate, PropertyType, PropertyField, PropertyGroup

# Dropbox Business Team file access token
DROPBOX_TEAM_FILE = ''


def list_more_files(client, cursor):
    """
    :type client: dropbox.Dropbox
    :type cursor: str
    :rtype: list[dropbox.files.Metadata]
    """
    chunk = client.files_list_folder_continue(cursor)
    if chunk.has_more:
        return chunk.entries + list_more_files(client, chunk.cursor)
    else:
        return chunk.entries


def list_files(client, path):
    """
    :type client: dropbox.Dropbox
    :type path: str
    :rtype: list[dropbox.files.Metadata]
    """

    # Set recursive=False because files under shared folders are not listed.
    # call traverse_files for extract files recursively
    chunk = client.files_list_folder(path, recursive=False)
    if chunk.has_more:
        return traverse_files(client, chunk.entries + list_more_files(client, chunk.cursor))
    else:
        return traverse_files(client, chunk.entries)


def traverse_files(client, entries):
    """
    :type client: dropbox.Dropbox
    :type entries: list[dropbox.files.Metadata]
    :rtype: list[dropbox.files.Metadata]
    """
    all = []
    for f in entries:
        all.append(f)
        if isinstance(f, dropbox.files.FolderMetadata):
            all += list_files(client, f.path_lower)

    return all


def list_more_members(client, cursor):
    """
    :type client: dropbox.DropboxTeam
    :type cursor: str
    :rtype: list[dropbox.team.TeamMemberInfo]
    """
    chunk = client.team_members_list_continue(cursor)
    if chunk.has_more:
        return chunk.members + list_more_members(client, chunk.cursor)
    else:
        return chunk.members


def list_members(client):
    """
    :type client: dropbox.DropboxTeam
    :rtype: list[dropbox.team.TeamMemberInfo]
    """
    chunk = client.team_members_list()
    if chunk.has_more:
        return chunk.members + list_more_members(client, chunk.cursor)
    else:
        return chunk.members


def show_properties_templates(client):
    """
    :type client: dropbox.DropboxTeam
    """
    templates = client.team_properties_template_list()

    for t in templates.template_ids:
        template = client.team_properties_template_get(t)
        print "Template Id: %s" % t
        print "Template Name: %s" % template.name
        print "Description: %s" % template.description
        for f in template.fields:
            print "Field[%s] Description[%s]" % (f.name, f.description)


def find_properties_template_id_by_name(client, name):
    """
    :type client: dropbox.DropboxTeam
    :type name: str
    :rtype: str | None
    """
    templates = client.team_properties_template_list()
    for t in templates.template_ids:
        template = client.team_properties_template_get(t)
        if template.name == name:
            return t

    return None


def audit_file(client, template_id, file):
    """
    :type client: dropbox.Dropbox
    :type template_id: str
    :type file: dropbox.files.FileMetadata
    """
    print "Auditing file: %s" % file.path_display

    meta = client.files_alpha_get_metadata(file.path_lower, include_property_templates=[template_id])
    if isinstance(meta, dropbox.files.FileMetadata) and meta.property_groups is not None:
        for p in meta.property_groups:
            if p.template_id == template_id:
                for field in p.fields:
                    print "File[%s] Security Policy: %s = %s" % (file.path_display, field.name, field.value)

    # Mark as Confidential for every '.pdf'
    if file.path_lower.endswith('.pdf'):
        print "Updating security policy: %s : template_id=%s" % (file.path_display, template_id)
        level_field = PropertyField('Level', 'Confidential')
        prop_group = PropertyGroup(template_id, [level_field])
        client.files_properties_overwrite(file.path_lower, [prop_group])


def audit_member(client, template_id, member):
    """
    :type client: dropbox.Dropbox
    :type template_id: str
    :type member: dropbox.team.TeamMemberInfo
    """
    print "Auditing files of member: %s" % member.profile.email

    files = list_files(client, "")
    for f in files:
        if isinstance(f, dropbox.files.FileMetadata):
            audit_file(client, template_id, f)


if __name__ == '__main__':
    client_team = dropbox.DropboxTeam(DROPBOX_TEAM_FILE)

    # List existing properties template
    show_properties_templates(client_team)

    # Lookup template named 'Security Policy'
    tmpl_name = 'Security Policy'
    tmpl_desc = 'These properties describe how confidential this file is.'
    tmpl_field_level_name = 'Level'
    tmpl_field_level_desc = 'Level can be Confidential, Public or Internal.'

    security_policy_template_id = find_properties_template_id_by_name(client_team, tmpl_name)

    # Add template if not exist
    if security_policy_template_id is None:
        fields = [
            PropertyFieldTemplate(tmpl_field_level_name, tmpl_field_level_desc, PropertyType.string)
        ]
        security_policy_template_id = client_team.team_properties_template_add(tmpl_name, tmpl_desc, fields)
        print security_policy_template_id

    # Audit member files
    members = list_members(client_team)
    for m in members:
        # Create client as user
        c = client_team.as_user(m.profile.team_member_id)
        audit_member(c, security_policy_template_id, m)

Aerobaticから再びGithub Pages + CloudFlareへ

つい2週間ほど前にこのブログのホスティングサービスをAerobatic引っ越しをしたところですが、またGithub Pages + CloudFlareに切り戻すことにしました。サービス体系が大幅に見直されAerobaticの無償プランにカスタムドメインのサービスが含まれなくなってしまったためです。また、ツール群もBitbucketのアドインという形式から、別途CLIベースのものになりました。

無償プランがなくなったから、あるいはCLIになったからというだけで移行するには忍びないですが、有償プランが一本化され$15/月だけになってしまったのは残念です。「$2〜3/月でカスタムドメイン1つ」程度であれば充分利用する気持ちになったのですが、このような小規模ブログには新料金プランはオーバースペックでした。

このようなこともありまた元のGithub Pages + CloudFlareへ切り戻しをしました。幸いにしてJekyllで制作したブログのホスティング先切り替えは非常に簡単です。10分程度で終わってしまいました。

Go: Bandwidth limit for multiple Reader and Writer

Bandwidth limit is really important for stabilizing systems. Core business logic stops if low priority job consume entire bandwidth of the system. When writing bandwidth limit for Reader or Writer in Go, I found go-flowrate. This library enables easy way to limit bandwidth like the code below.

func main() {
    f, _ := os.Open("data.dat")

    // Create wrapper with 100 bytes per second
    f2 := flowrate.NewReader(f, 100)

    // Biz logic
    // ...
}

APIs are pretty simple and reliable. But go-flowrate can limit bandwidth only for single Reader or Writer. In my case, I was working on file uploader for Dropbox which has concurrent upload feature. This code require limiting bandwidth for multiple I/O. So I decided create new small library for bandwidth limit.

bwlimit

bwlimit is the name of my library. You can see or clone from github bwlimit. Here is code example of bwlimit.

func main() {
    // Limit 100 bytes per second
    bwlimit := NewBwlimit(100, false)

    // Prepare multiple readers
    f1, _ := os.Open("data1.dat")
    f2, _ := os.Open("data2.dat")

    // Create wrapper
    fr1 := bwlimit.Reader(f1)
    fr2 := bwlimit.Reader(f2)

    // Biz logic
    // ...

    // Wait for all Reader to be closed or reach EOF
    bwlimit.Wait()
}

1) Prepare bandwidth limiting object (bwlimit in above code) first. Second argument is the flag for block(true) or unblock(false) for Read or Write operation. 2) Then, create wrapper for each Reader or Writer.

Concept

The concept of bwlimit comes from Toyota’s Production System (TPS). In TPS, Takt time is the core concept for leveling production. Define time unit of ship defined quantity of products.

For example; 1) Takt time is 100ms, 2) bandwidth limit is 1,000 bytes per second. 100 bytes is maximum transferrable data size per takt time. If bwlimit object has two Readers, that mean 50 bytes per Reader per takt time.

Flow control

bwlimit does not carry bandwidth window to next takt time. If the Reader have 50 bytes window per takt time, and the Reader read nothing. Then, window size of next takt time will be 50 bytes. This is because if bwlimit carry unused window to next takt time. bwlimit may allow burst IO for certain takt time. That sometime cause buffer overflow of routers. To prevent incident caused by burst IO.