LoginSignup
11

More than 5 years have passed since last update.

Golangで簡単に一時ファイルを吐くことを考える

Posted at

はじめに

インフラエンジニアな私の場合、Go言語で書くのは「daemonのように常時起動しているプログラム」より、「1回走って終わり」な簡単なプログラムであることが多い。このとき、前回の結果を参照できるかどうかで、できることがだいぶ違う。

前回の実行結果が取得できれば、アラートを何度も送ってしまうようなことはないし、いわゆるカウンタータイプのデータからCPU使用率が計算できたり、通信速度が計算できたりする。

だからといって、永続化のためにDBを使用するのはちょっとやり過ぎだと思う。これは前回のログ出力の記事も同じ思想だ。Pythonのshelveのように、(個人的に)簡単で使いやすい方法を考えたい。

書いてみた

出力方式は色々あるが、プレーンテキストにしてパーサを書くのは面倒である。
汎用性のあるJSON形式で出力することにした。

サンプルコード全文

サンプルの動作のために、余計な部分が多い。

sample.go
package main

import (
    "encoding/json"
    "os"
    "io/ioutil"
    // 以下はサンプル用のimport
    "fmt"
    "math/rand"
    "time"
)

type Data struct {
    Time   int64
    Value1 int64
    Value2 string
}

const DataFile = "/tmp/lastdata.json"

func writeDataMap(path string, dataMap *map[string]Data) error {
    if len(*dataMap) == 0 {
        // do nothing
        return nil
    }
    bytes, _ := json.Marshal(*dataMap)
    return ioutil.WriteFile(path, bytes, os.FileMode(0600))
}

func readDataMap(path string) (dataMap map[string]Data, err error) {
    file, err := ioutil.ReadFile(path)
    if err != nil {
        // do nothing
        return nil, err
    }
    err = json.Unmarshal(file, &dataMap)
    return
}

func main() {
    // 前回の値の取り込みと今回の値の準備
    lastDataMap, _ := readDataMap(DataFile)
    var newDataMap = map[string]Data{}


    // 現在のデータを集め、TestKeyという名前で登録する
    var newData Data
    newData.Time = time.Now().Unix()
    rand.Seed(newData.Time)
    value := rand.Int63n(1000)
    newData.Value1 = value
    newData.Value2 = fmt.Sprintf("Rawdata is %d", value)
    newDataMap["TestKey"] = newData

    // 差分計算
    if lastData, ok := lastDataMap["TestKey"]; !ok {
        // 前回の値がない場合は、計算をしない
        fmt.Println("This is firsttime.")
    } else {
        // 前回の値との差分を求める
        interval := newData.Time - lastData.Time
        differ := newData.Value1 - lastData.Value1

        fmt.Printf("last message = %s\n", lastData.Value2)
        fmt.Printf("current message = %s\n", newData.Value2)
        fmt.Printf("differ = %d & interval = %d\n", differ, interval)
        fmt.Printf("XXX per sec = %.2f\n", float64(differ)/float64(interval))
    }

    // 現在のデータを保存
    writeDataMap(DataFile, &newDataMap)
}

実行例

1回目の実行。

$ /vagrant/src/test/test_stat
This is firsttime.

$ ls -l /tmp/lastdata.json
-rw------- 1 user user 70  7月 23 19:17 2015 /tmp/lastdata.json

$ cat /tmp/lastdata.json
{"TestKey":{"Time":1437646657,"Value1":356,"Value2":"Rawdata is 356"}}

よし、JSONファイルが作られている。

もう1回実行する。

$ /vagrant/src/test/test_stat
last message = Rawdata is 356
current message = Rawdata is 990
differ = 634 & interval = 15
XXX per sec = 42.27

前回の値を参照して差分計算をした。

コードの説明

サンプル実装のせいで見た目は結構長いが、核となる部分は簡単だ。
重要なのは以下。

  • Data構造体
  • var newDataMap = map[string]Data{} という宣言
  • * writeDataMap関数
  • readDataMap関数

Data構造体

必要な情報を格納する構造体を予め宣言しておく。
これは、保存したデータを取り込むときに、型変換などを気にせずに扱えるようにするためだ。

ここに書かれているように、任意のJSONヘッダにすることもできる。将来的な互換性のためにはそうすべきだろう。

var newDataMap = map[string]Data{} という宣言

このサンプルコードでは、先ほどのData構造体を、map(dataMap変数)に突っ込んでいる。これはこのサンプルコードでの取り扱いなので、必ずしも従う必要はない。

ただ、こうすることで 本来保存したいデータメタデータ を保存・読込できたりと、便利になる。というか、1ファイルに1データしか保存しないのも勿体無い感じがするし。

mapをJSON化する際は、 Keyは必ずstringでなければならない ことに注意。

writeDataMap関数

このサンプルでは、この2行が全て。

bytes, _ := json.Marshal(*dataMap)
return ioutil.WriteFile(path, bytes, os.FileMode(0600))

Data構造体のmapを、json.Marshal関数でバイト列に変換して出力する。
前のファイルは上書きされる。クローズ処理もいらない。

かなり雑なので、本来はエラー処理などもちゃんと書くべきと思う・・・

readDataMap関数

readもwriteとほぼ一緒で、重要なのはほんの数行。

(関数定義の) dataMap map[string]Data

file, err := ioutil.ReadFile(path)
err = json.Unmarshal(file, &dataMap)

writeした時と同じ構造のデータ型にjson.Unmarshalすればいい。

終わりに

色々気にはなるが、Pythonよりも厳しいGo言語でこのくらいのコード量で済むなら許容範囲かと。ただ、JSONというのが最大の難点でもある。

simplejson使うべきかなぁ。
参考: http://astaxie.gitbooks.io/build-web-application-with-golang/content/ja/07.2.html

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11