読者です 読者をやめる 読者になる 読者になる

golangでparseのinstall数を集計するスクリプト書いた

書いた.

Parse.com使ってて(オンプレ移行は成功しそう),Parseに突っ込んでるログからインストール数の集計をできるようなコマンドを書いてみた.リポジトリに上げたいところなんだけどまだ,キーがハードコードされてるので治ったら.

要件

  • コマンド一つ集計可能
  • AndroidiOSでそれぞれカウントする
  • 月ごとの集計ができる

package main

package main

import (
    "fmt"
    "time"

    "bitbucket.org/xxx/xxxx/parseclient" // 見せられないよ
)

var (
    defaultApplicationID = "applicationID"
    defaultRestAPIKey    = "restAPIKey"
)

func printMonthlyCount(date string, count int) {
    fmt.Println(date, ": ", count)
}

func main() {
    client := parseclient.NewClient(defaultApplicationID, defaultRestAPIKey)

    androidCount, iosCount, dmAndroid, dmIos, err := client.FetchCount()
    if err != nil {
        panic(err)
    }

    fmt.Println(time.Now(), "のインストール数")
    fmt.Println("Android:", androidCount)
    fmt.Println("iOS:", iosCount)
    fmt.Println("月ごとの集計結果")
    fmt.Println("Android")
    dmAndroid.Each(printMonthlyCount)
    fmt.Println("iOS")
    dmIos.Each(printMonthlyCount)
    fmt.Println("合計:", androidCount+iosCount)
}

取ってきて出力.dmAndroid.EachdmIos.Eachについては後述

package dmap

空のときにデフォルト値が設定できるようなmapを定義 さっきのEachはここ

http://ashitani.jp/golangtips/tips_map.html#map_Default

package dmap

// Dmap Dictionaly map
type Dmap struct {
    m map[string]int
}

// Get 値をGet
func (d Dmap) Get(key string) int {
    v, ok := d.m[key]
    if ok {
        return v
    }
    return 0
}

// Set 値をset
func (d Dmap) Set(key string, value int) {
    d.m[key] = value
}

// Each ループ回せる
func (d Dmap) Each(f func(string, int)) {
    for key, value := range d.m {
        f(key, value)
    }
}

// New func
func New() *Dmap {
    return &Dmap{map[string]int{}}
}

parseからの取得

package parseclient

import (
    "net/url"
    "strconv"
    "time"

    "bitbucket.org/xxx/xxxx/dmap"

    "github.com/facebookgo/parse"
)

type device struct {
    CreatedAt time.Time `json:"createdAt"`
    Os        string    `json:"os"`
}

type result struct {
    Results []device `json:"results"`
}

// Client インストール数問い合わせのクライアント
type Client struct {
    parseClient parse.Client
}

// NewClient Client生成
func NewClient(applicationID string, restAPIKey string) *Client {
    return &Client{
        parseClient: parse.Client{
            Credentials: parse.RestAPIKey{
                ApplicationID: applicationID,
                RestAPIKey:    restAPIKey,
            },
        },
    }
}

// FetchCount AndroidとiOSそれぞれカウントする
func (c Client) FetchCount() (int, int, *dmap.Dmap, *dmap.Dmap, error) {
    jst, err := time.LoadLocation("Asia/Tokyo")
    if err != nil {
        return 0, 0, nil, nil, err
    }

    count := 0
    androidCount := 0
    iosCount := 0
    dmAndroid := dmap.New()
    dmIos := dmap.New()

    for {
        var res result
        v := make(url.Values)
        v.Set("skip", strconv.Itoa(count))
        v.Set("limit", "100")
        _, err := c.parseClient.Get(&url.URL{Path: "/1/classes/install_log", RawQuery: v.Encode()}, &res)
        if err != nil {
            return 0, 0, nil, nil, err
        }

        for _, element := range res.Results {
            jstTime := element.CreatedAt.In(jst)
            key := jstTime.Format("2006-01")

            if element.Os == "Android" {
                androidCount++
                dmAndroid.Set(key, dmAndroid.Get(key)+1)
            }
            if element.Os == "iPhone OS" {
                iosCount++
                dmIos.Set(key, dmIos.Get(key)+1)
            }
        }

        size := len(res.Results)
        count += size

        if size != 100 {
            break
        }

        time.Sleep(1000) // マナー
    }

    return androidCount, iosCount, dmAndroid, dmIos, nil
}

実装は至ってシンプルで,APIをskipを使ってページ制御しながら,全件みつつOS種類に応じてカウントする. 月ごとの集計がだるかったので,dmapのkeyとして月を利用することでなんとかしました.ただ問題があって月ごとの並び順がバラバラになりがちなのでなんとかしたい.

感想

jsonのパースが楽だと思いました. これgolangとして良い書き方なのかわからないので詳しい人教えてください.