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

はじめに

インフラエンジニアな私の場合、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


『 Go 』Article List
Category List

Eye Catch Image
Read More

Androidに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

AWSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Bitcoinに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

CentOSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

dockerに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

GitHubに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Goに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Javaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

JavaScriptに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Laravelに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Pythonに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Rubyに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Scalaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Swiftに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Unityに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Vue.jsに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Wordpressに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

機械学習に関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。