Writing Custom StackDriver Metrics -- The Go Daemon
Tags: go, devops, metrics, beginners
Published: 2019-02-17 19:12:23

---
title: Writing Custom StackDriver Metrics -- The Go Daemon
published: true
description: In part two, we write custom stackdriver metrics and publish using a tiny Go Daemon.
tags: go,devops,metrics,beginners
---


[ See Part 1 for the Custom Metric Setup](https://tonym.us/writing-custom-metrics-to-stackdriver-in-golang.html#writing-custom-metrics-to-stackdriver-in-golang)
## 2. Collect and Harvest Custom Metric with Golang

For this example, we're trivally tracking the line count. It can be expanded
to parse success vs error lines and send those as two metrics, e.g.
`backup-count` vs `error-backup-count`

The code below creates a 5-sec timer which counts the file's lines and sends
that count to stackdriver. stackdriver reporting will allow you to run
statistics e.g. avg() & max() and allow you to see the backup-count growth
over time

```
package main

import (
        "bytes"
        "context"
        "fmt"
        "io"
        "log"
        "os"
        "time"

        monitoring "cloud.google.com/go/monitoring/apiv3"
        timestamp "github.com/golang/protobuf/ptypes/timestamp"
        metricpb "google.golang.org/genproto/googleapis/api/metric"
        monitoredres "google.golang.org/genproto/googleapis/api/monitoredres"
        monitoringpb "google.golang.org/genproto/googleapis/monitoring/v3"
)

const metricType = "custom.googleapis.com/photos/backup-count"

const (
    projectID = "YOUR-GCP-PROJECT-ID"
    instanceID = "INSTANCE-ID-FOR-TESTING"
    INTERVAL = 3
    zone = "us-east1-c"
)

func writeTimeSeriesValue(projectID, metricType string, value int) error {
        ctx := context.Background()
        c, err := monitoring.NewMetricClient(ctx)
        if err != nil {
                return err
        }
        now := &timestamp.Timestamp{
                Seconds: time.Now().Unix(),
        }
        req := &monitoringpb.CreateTimeSeriesRequest{
                Name: "projects/" + projectID,
                TimeSeries: []*monitoringpb.TimeSeries{{
                        Metric: &metricpb.Metric{
                                Type: metricType,
                        },
                        Resource: &monitoredres.MonitoredResource{
                                Type: "gce_instance",
                                Labels: map[string]string{
                                        "project_id":  projectID,
                                        "instance_id": instanceID,
                                        "zone":        zone,
                                },
                        },
                        Points: []*monitoringpb.Point{{
                                Interval: &monitoringpb.TimeInterval{
                                        StartTime: now,
                                        EndTime:   now,
                                },
                                Value: &monitoringpb.TypedValue{
                                        Value: &monitoringpb.TypedValue_Int64Value{
                                                Int64Value: int64(value),
                                        },
                                },
                        }},
                }},
        }
        log.Printf("writeTimeseriesRequest: %+v\n", req)

        err = c.CreateTimeSeries(ctx, req)
        if err != nil {
                return fmt.Errorf("could not write time series value, %v ", err)
        }
        return nil
}


// [END monitoring_read_timeseries_simple]

func LineCounter(r io.Reader) (int, error) {
        buf := make([]byte, 32*1024)
        count := 0
        lineSep := []byte{'\n'}

        for {
                c, err := r.Read(buf)
                count += bytes.Count(buf[:c], lineSep)

                switch {
                case err == io.EOF:
                        return count, nil

                case err != nil:
                        return count, err
                }
        }
}

// given interval, return the # of lines in the file
func WatchFile(interval time.Duration, filename string) <-chan int {
        handle, err := os.Open(filename)
        ticker := time.NewTicker(interval)
        sizeChan := make(chan int)
        go func() {
                for _ = range ticker.C {
                        lineCount, _ := LineCounter(handle)
                        sizeChan <- lineCount
                        if err != nil {
                                fmt.Println("error")
                        }
                        handle.Seek(0, 0)
                }
        }()
        return sizeChan
}

func main() {
        if len(os.Args) < 2 {
                fmt.Printf("%s FILENAME\n", os.Args[0])
                os.Exit(-1)
        }
        sizeChan := WatchFile(INTERVAL*time.Second, os.Args[1])
        for {
                select {
                case <-sizeChan:
                        curSize := <-sizeChan
                        fmt.Printf("Lines in %s : %d\n", os.Args[1], curSize)
                        writeTimeSeriesValue(projectID, metricType, curSize)
                }
        }
}
```