Goならわかるシステムプログラミング:第3章

  • 標準入力
package main

import (
    "os"
    "io"
    "fmt"
)

func main() {
    for {
        buffer := make([]byte, 5)
        size, err := os.Stdin.Read(buffer)
        if err == io.EOF {
            fmt.Println("EOF")
            break
        }
        fmt.Printf("size=%d input='%s'\n", size, string(buffer))
    }
}
  • ファイル入力
package main

import (
    "os"
    "io"
)

func main() {
    file, err := os.Open("main.go")
    if err != nil {
        panic(err)
    }
    defer file.Close()
    io.Copy(os.Stdout, file)
}
  • ネットワーク通信の読み込み
package main

import (
    "net"
    "net/http"
    "bufio"
    "fmt"
    "io"
    "os"
)

func main() {
    conn, err := net.Dial("tcp", "ascii.jp:80")
    if err != nil {
        panic(err)
    }
    conn.Write([]byte("GET / HTTP/1.0\r\nHost: ascii.jp\r\n\r\n"))
    res, err := http.ReadResponse(bufio.NewReader(conn), nil)
    fmt.Println(res.Header)
    defer res.Body.Close()
    io.Copy(os.Stdout, res.Body)

}
package main

import (
    "bytes"
    "encoding/binary"
    "fmt"
)

func main() {
    data := []byte{0x0, 0x0, 0x27, 0x10}
    var i int32
    binary.Read(bytes.NewReader(data), binary.BigEndian, &i)
    fmt.Printf("data: %d\n", i)
}
  • PNGファイルを分析してみる
package main

import (
    "io"
    "encoding/binary"
    "fmt"
    "os"
)

func dumpChunk(chunk io.Reader) {
    var length int32
    binary.Read(chunk, binary.BigEndian, &length)
    buffer := make([]byte, 4)
    chunk.Read(buffer)
    fmt.Printf("chunk, '%v' (%d bytes)\n", string(buffer), length)
}

func readChunks(file *os.File) []io.Reader {
    var chunks []io.Reader
    file.Seek(8, 0)
    var offset int64 = 8

    for {
        var length int32
        err := binary.Read(file, binary.BigEndian, &length)
        if err == io.EOF {
            break
        }
        chunks = append(chunks, io.NewSectionReader(file, offset, int64(length)+12))
        offset, _ = file.Seek(int64(length+8), 1)
    }
    return chunks
}

func main() {
    file, err := os.Open("Lenna.png")
    if err != nil {
        panic(err)
    }
    defer file.Close()
    chunks := readChunks(file)

    for _, chunk := range chunks {
        dumpChunk(chunk)
    }
}

実行結果

chunk, 'IHDR' (13 bytes)
chunk, 'sRGB' (1 bytes)
chunk, 'IDAT' (473761 bytes)
chunk, 'IEND' (0 bytes)

Process finished with exit code 0
  • PNG画像に秘密のテキストを入れてみる
package main

import (
    "io"
    "bytes"
    "encoding/binary"
    "hash/crc32"
    "os"
    "fmt"
)

func textChunk(text string) io.Reader {
    byteData := []byte(text)
    var buffer bytes.Buffer
    binary.Write(&buffer, binary.BigEndian, int32(len(byteData)))
    buffer.WriteString("tExt")
    buffer.Write(byteData)
    crc := crc32.NewIEEE()
    io.WriteString(crc, "tExt")
    binary.Write(&buffer, binary.BigEndian, crc.Sum32())
    return &buffer
}
func readChunks(file *os.File) []io.Reader {
    var chunks []io.Reader
    file.Seek(8, 0)
    var offset int64 = 8

    for {
        var length int32
        err := binary.Read(file, binary.BigEndian, &length)
        if err != nil {
            break
        }
        chunks = append(chunks, io.NewSectionReader(file, offset, int64(length)+12))
        offset , _ = file.Seek(int64(length+8), 1)
    }
    return chunks
}

func dumpChunk(chunk io.Reader) {
    var length int32
    binary.Read(chunk, binary.BigEndian, &length)
    buffer := make([]byte, 4)
    chunk.Read(buffer)
    fmt.Printf("chunk '%v' (%d bytes\n", string(buffer), length)
    if bytes.Equal(buffer, []byte("tExt")) {
        rawText := make([]byte, length)
        chunk.Read(rawText)
        fmt.Println(string(rawText))
    }
}

func writeFile() {
    file, err := os.Open("Lenna.png")
    if err != nil {
        panic(err)
    }
    defer file.Close()
    newFile, err := os.Create("Lenna2.png")
    if err != nil {
        panic(err)
    }
    chunks := readChunks(file)
    io.WriteString(newFile, "\x89PNG\r\n\x1a\n")
    io.Copy(newFile, chunks[0])
    io.Copy(newFile, textChunk("ASCII PROGRAMMING++"))
    for _, chunk := range chunks[1:] {
        io.Copy(newFile, chunk)
    }
}

func readFile() {
    file, err := os.Open("Lenna2.png")
    if err != nil {
        panic(err)
    }
    defer file.Close()
    chunks := readChunks(file)
    for _, chunk := range chunks {
        dumpChunk(chunk)
    }
}

func main() {
    writeFile()
    readFile()
}

  • テキスト解析 改行/単語で区切る
package main

import (
    "bufio"
    "strings"
    "fmt"
    "io"
)

var source = `1行目
2行目
3行目`

func main() {
    reader :=bufio.NewReader(strings.NewReader(source))
    for {
        line, err := reader.ReadString('\n')
        fmt.Printf("%#v\n", line)
        if err == io.EOF {
            break
        }
    }
}
  • bufio.Scannerを使う
package main

import (
    "bufio"
    "strings"
    "fmt"
)

var source = `1行目
2行目
3行目`

func main() {
    scanner := bufio.NewScanner(strings.NewReader(source))
    for scanner.Scan() {
        fmt.Printf("%#v\n", scanner.Text())
    }
}
  • データ型を指定して解析
package main

import (
    "strings"
    "fmt"
)

var source = "123 1.234 1.0e4 test"

func main() {
    reader := strings.NewReader(source)
    var i int
    var f, g float64
    var s string
    fmt.Fscan(reader, &i, &f, &g, &s)
    fmt.Printf("i=%#v f=%#v g=%#v s=%#v\n", i, f, g, s)
}

実行結果

i=123 f=1.234 g=10000 s="test"

Fscanはスペース(または改行)で区切られたstringを読み込んで2つ目以降の引数の変数に代入していく。 

  • csvフォーマットの文字列の解析
package main

import (
    "strings"
    "encoding/csv"
    "io"
    "fmt"
)

var csvSource =
    `13101, "100 ", "1000003", "トウキョウト", "チヨダク", "ヒトツバシ(1チョウメ)", "東京都", "千代田区", "一ツ橋(1丁目)", 1,0,1,0,0,0 
13101, "101  ", "1000003", "トウキョウト", "チヨダク", "ヒトツバシ(2チョウメ)", "東京都", "千代田区", "一ツ橋(2丁目)", 1,0,1,0,0,0
13101, "100  ", "1000012", "トウキョウト", "チヨダク", "ヒビヤコウエン", "東京都", "千代田区", "日比谷公園", 0,0,0,0,0,0
13101, "102  ", "1000093", "トウキョウト", "チヨダク", "ヒトツバシ(1チョウメ)", "東京都", "千代田区", "平河町", 0,0,1,0,0,0
13101, "102  ", "1000071", "トウキョウト", "チヨダク", "ヒトツバシ(1チョウメ)", "東京都", "千代田区", "富士見", 0,0,1,0,0,0
`

func main() {
    reader := strings.NewReader(csvSource)
    csvReader := csv.NewReader(reader)
    csvReader.LazyQuotes = true
    for {
        line, err := csvReader.Read()
        if err == io.EOF {
            break
        }
        fmt.Println(line[2], line[6:9])
    }
}

実行結果

 "1000003" [ "東京都"  "千代田区"  "一ツ橋(1丁目)"]
 "1000003" [ "東京都"  "千代田区"  "一ツ橋(2丁目)"]
 "1000012" [ "東京都"  "千代田区"  "日比谷公園"]
 "1000093" [ "東京都"  "千代田区"  "平河町"]
 "1000071" [ "東京都"  "千代田区"  "富士見"]

BOM付UTF-8にはまった。書籍そのままでは動かない。

GoでBOM付きのUTF8なCSVを扱うには | tail -f pinzo.log

encoding/csv: bare " in non-quoted-field · Issue #21672 · golang/go · GitHub

  • io.Reader/io.Writerでストリームを自由に操る
package main

import (
    "bytes"
    "io"
    "os"
)

func main() {
    header := bytes.NewBufferString(("----- HEADER -----\n"))
    content := bytes.NewBufferString("Example of io.MultiReader\n")
    footer := bytes.NewBufferString("----- FOOTER -----\n")

    reader := io.MultiReader(header, content, footer)
    io.Copy(os.Stdout, reader)
}

package main

import (
    "bytes"
    "fmt"
    "io"
    "io/ioutil"
)

func main() {
    var buffer bytes.Buffer
    reader := bytes.NewBufferString("Example of io.TeeReader\n")
    teeReader := io.TeeReader(reader, &buffer)
    _, _ = ioutil.ReadAll(teeReader)
    fmt.Println(buffer.String())
}

Prometheusのドキュメントを読む

  • prometheusを起動
./prometheus --config.file=prometheus.yml

http://localhost:9090でステータスページが見られる http://localhost:9090/metricsで自身のmetricsのendpointが見られる(自分自身のmetricsも収集している)

  • 設定の再読込をするには?
    • PrometheusプロセスにSIGHUPを送る
    • /-/reloadにPOSTリクエストを送る

https://prometheus.io/docs/introduction/faq/#can-i-reload-prometheus-s-configuration

  • バッチジョブの監視はどうする?

    • Pushgatewayを使う
  • データ形式 <metric name>{<label name>=<label value>, ...}

    • 例:api_http_requests_total{method="POST", handler="/messages"}
  • prometheus.ymlの構成

    • global
      • prometheus自体の一般的な設定
    • rule_files
      • 追加で作った計算式を別ファイルに定義し、それを読み込む
    • scrape_configs
      • データを取得しにいくEndpoint(サーバー)などを記載
  • インストール方法

  • prometheus.rules.yml

    • recording_rulesとalerting_rulesがある
    • recording_rulesは集計したい値の追加
    • alerting_rulesはalertmanagerに送る条件の追加
    • prometheusはアラートの発火をシンプルに行うだけ。アラートの集約・通知条件・依存関係の定義などの細かい設定はalertmanagerで行う、というように役割分担する。
  • templateはGo言語のtext/templateパッケージをベースにしている

Goならわかるシステムプログラミング:第2章

第2章。

  • ファイルディスクリプタ・・・OSがカーネルのレイヤーで用意している抽象化の仕組み limits.confでいじるやつ。 0が標準入力 1が標準出力 2が標準エラー出力

  • 構造体が持つべきメソッドを表現するのがインタフェース

package main

import "fmt"

type Talker interface {
    Talk()
}

type Greeter struct {
    name string
}

func (g Greeter) Talk() {
    fmt.Printf("Hello, my name is %s\n", g.name)
}

func main() {
    var talker Talker

    talker = &Greeter{"wozozo"}
    talker.Talk()
}
  • ファイル出力の例
package main

import (
    "os"
)

func main() {
    file, err := os.Create("test.txt")
    if err != nil {
        panic(err)
    }
    file.Write([]byte("os.File example\n"))
    file.Close()
}
  • 画面出力
package main

import "os"

func main() {
    os.Stdout.Write([]byte("os.Stdout example\n"))
}
  • バッファ
package main

import (
    "bytes"
    "fmt"
)

func main() {
    var buffer bytes.Buffer
    buffer.Write([]byte("bytes.Buffer example\n"))
        //buffer.WriteString("bytes.Buffer example\n") これでもOK
    fmt.Println(buffer.String())
}
  • インターネットアクセス
package main

import (
    "net"
    "io"
    "os"
)

func main() {
    conn, err := net.Dial("tcp", "ascii.jp:80")
    if err != nil {
        panic(err)
    }
    io.WriteString(conn, "GET / HTTP1.0\r\nHost: ascii.jp\r\n\r\n")
    io.Copy(os.Stdout, conn)
}
  • httpパッケージを使う方法
package main

import (
    "net"
    "io"
    "os"
    "net/http"
)

func main() {
    conn, err := net.Dial("tcp", "ascii.jp:80")
    if err != nil {
        panic(err)
    }
    req, err := http.NewRequest("GET", "http://ascii.jp", nil)
    if err != nil {
        panic(err)
    }
    req.Write(conn)
    io.Copy(os.Stdout, conn)
}
  • ウェブサーバーからブラウザに対して書き込む
package main

import (
    "net/http"
    "io"
)

func handler(w http.ResponseWriter, r *http.Request) {
    io.WriteString(w, "http.ResponseWriter sample")
}

func main() {
    http.HandleFunc("/", handler)
    http.ListenAndServe(":8080", nil)
}
  • デコレータ・・・io.Writerを受け取り、書き込まれtデータを加工して別のio.Writerに書き出す 下記の例は1つのWriteでファイルと標準出力両方に書き込んでいる
package main

import (
    "os"
    "io"
)

func main() {
    file, err := os.Create("multiwriter.txt")
    if err != nil {
        panic(err)
    }
    writer := io.MultiWriter(file, os.Stdout)
    io.WriteString(writer, "io.MultiWriter example\n")
}

*gzip圧縮してos.Fileに中継する

package main

import (
    "os"
    "compress/gzip"
    "io"
)

func main() {
    file, err := os.Create("test.txt.gz")
    if err != nil {
        panic(err)
    }
    writer := gzip.NewWriter(file)
    writer.Header.Name = "test.txt"
    io.WriteString(writer, "gzip.Writer example")
    writer.Close()
}
  • バッファをためておいてFlush()で書き込む
package main

import (
    "bufio"
    "os"
)

func main() {
    buffer := bufio.NewWriter(os.Stdout)
    buffer.WriteString("bufio.Writer ")
    buffer.Flush()
    buffer.WriteString("example\n")
    buffer.Flush()
}
  • Fprintf()
package main

import (
    "fmt"
    "os"
    "time"
)

func main() {
    fmt.Fprintf(os.Stdout, "Write with os.Stdout at %v", time.Now())
}
Write with os.Stdout at 2018-07-05 23:07:33.224526589 +0900 JST m=+0.000422627
  • JSONを整形して標準出力に出す
package main

import (
    "encoding/json"
    "os"
)

func main() {
    encoder := json.NewEncoder(os.Stdout)
    encoder.SetIndent("", "   ")
    encoder.Encode(map[string]string {
        "example": "encoding/json",
        "hello": "world",
    })
}

問題

Q2.1: ファイルに対するフォーマット出力

package main

import (
    "os"
    "fmt"
)

func main() {
    file, err := os.Create("test.txt")
    if err != nil {
        panic(err)
    }
    d := 1
    s := "s"
    f := 0.2
    fmt.Fprintf(file, "%d, %s, %f", d, s, f)
    file.Close()
}

Q2.2: CSV出力

package main

import (
    "os"
    "encoding/csv"
)

func main() {
    file, err := os.Create("test.csv")
    if err != nil {
        panic(err)
    }
    writer := csv.NewWriter(file)
    writer.Write([]string{"apple", "orange", "lemon"})
    writer.Write([]string{"red", "orange", "yellow"})
    writer.Flush()
    file.Close()
}

Q2.3 gzipされたJSON出力をしながら、標準出力にログを出力

package main

import (
    "net/http"
    "encoding/json"
    "io"
    "compress/gzip"
    "os"
)

func handler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Encoding", "gzip")
    w.Header().Set("Content-Type", "application/json")
    source := map[string]string {
        "Hello": "World",
    }

    gzipWriter := gzip.NewWriter(w)
    encoder := json.NewEncoder(io.MultiWriter(gzipWriter, os.Stdout))
    encoder.SetIndent("", "   ")
    encoder.Encode(source)
    gzipWriter.Close()
}

func main() {
    http.HandleFunc("/", handler)
    http.ListenAndServe(":8080", nil)
}

Goならわかるシステムプログラミング:第1章

Goならわかるシステムプログラミング

Goならわかるシステムプログラミング

ライブラリで借りた。 ちょうどGoと、インフラレイヤーの知識をあらためてつけたいという希望がいっぺんに満たせそうなので取り組んでいく。

第1章

GoとVisual Studio Codeの設定、delveを使ったデバッガーを使用してみるところまで。 Visual StudioのKeybindに慣れなかったが、プラグインIntelliJのKeybindというのがあってそれをいれたらいっきに楽になった。

package main

import (
    "fmt"
)

func main() {
    fmt.Println("Hello, World!") // ここにブレークポイント
}

魚は痛みを感じるか?

魚は痛みを感じるか?

魚は痛みを感じるか?

ライブラリにあったので手にとった。

図解・ベイズ統計「超」入門 あいまいなデータから未来を予測する技術

読んだ。 基礎がわかってないのでこういう本を他にも読んでいきたい