Skip to content

Commit

Permalink
Implement collector and basic CLI
Browse files Browse the repository at this point in the history
  • Loading branch information
daenney committed Apr 27, 2021
1 parent 6d521ca commit 11c5222
Show file tree
Hide file tree
Showing 9 changed files with 801 additions and 0 deletions.
26 changes: 26 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: release
on:
push:
tags:
- 'v*'

jobs:
test:
name: Build and package
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: '~1.16.3'
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
with:
version: latest
args: release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
34 changes: 34 additions & 0 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
project_name: vattenfall
builds:
- id: vattenfall
binary: vattenfall
mod_timestamp: '{{ .CommitTimestamp }}'
flags:
- -trimpath
ldflags:
- -s
- -w
- -X main.version={{.Version}} -X main.commit={{.FullCommit}} -X main.date={{.CommitDate}} -X main.repository={{.GitURL}}
goos:
- windows
- darwin
- linux
goarch:
- amd64
- arm64
- arm
goarm:
- 7
archives:
- id: vattenfall
builds:
- vattenfall
wrap_in_directory: true
files:
- LICENSE
- README.md
replacements:
darwin: macOS
format_overrides:
- goos: windows
format: zip
50 changes: 50 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Vattenfall

This is a very basic Prometheus exporter for the Vattenfall electricity spot
prices in Sweden. It'll export one metric, `energy_price_per_kwh` for each
region:

```
# HELP energy_price_per_kwh Energy price per kWh for a region
# TYPE energy_price_per_kwh gauge
energy_price_per_kwh{country="SE",currency="SEK",region="SN1"} 0.4707
energy_price_per_kwh{country="SE",currency="SEK",region="SN2"} 0.4707
energy_price_per_kwh{country="SE",currency="SEK",region="SN3"} 0.4707
energy_price_per_kwh{country="SE",currency="SEK",region="SN4"} 0.4707
```

Data is cached for 30min in memory to not hammer Vattenfall each time you
scrape the collector (and their API is slow), but still catch any price
adjustments that might occur.

## Usage

```
-output.file string
write metrics to specified file (must have .prom extension)
-output.http string
host:port to listen on for HTTP scrapes
-region value
region to query for, SN1-4, can be passed multiple times
```

To run it as a Prometheus exporter that you can query over HTTP:

```sh
$ vattenfall -output.http=":9000" -region SN1 -region SN2 -region SN3 -region SN4
```

Please note that there's 2 endpoints `/metrics` which instruments the
collector itself, and `/prices` with the pricing info.

If you want to use it with the textfile collector, for example in an hourly cron:

```sh
$ vattenfall -output.file="/etc/prometheus/textfile/electricity.prom" -region SN1 -region SN2 -region SN3 -region SN4
```

Or to just get the values on the console:

```sh
$ vattenfall -region SN1 -region SN2 -region SN3 -region SN4
```
109 changes: 109 additions & 0 deletions data.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package main

import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"sync"
"time"
)

const (
source = "https://www.vattenfall.se/api/price/spot/pricearea/%s/%s/%s"
)

type Data struct {
Timestamp time.Time
Region string
Value float64
Currency string
}

var (
info = map[string][]Data{}
lastFetch = map[string]time.Time{}
lock = sync.RWMutex{}
)

func (d *Data) UnmarshalJSON(data []byte) error {
type internal struct {
Timestamp string `json:"TimeStamp"`
Value float64 `json:"Value"`
PriceArea string `json:"PriceArea"`
}

var v internal
if err := json.Unmarshal(data, &v); err != nil {
return err
}

parsed, err := time.Parse("2006-01-02T15:04:05", v.Timestamp)
if err != nil {
return err
}

d.Timestamp = parsed
d.Region = v.PriceArea
d.Value = (v.Value * 100) / 10000
d.Currency = "SEK"

return nil
}

func fetch(date time.Time, region string) ([]Data, error) {
lock.RLock()
lf := lastFetch[region]
lock.RUnlock()

if lf.Add(30 * time.Minute).Before(date) {
b, err := fetchFromURL(date, region)
if err != nil {
return nil, err
}

data := []Data{}
err = json.Unmarshal(b, &data)
if err != nil {
return nil, err
}

lock.Lock()
lastFetch[region] = date
info[region] = data
lock.Unlock()

return data, nil
}

return info[region], nil
}

func fetchFromURL(date time.Time, region string) ([]byte, error) {
res := fmt.Sprintf(
source,
date.Format("2006-01-02"),
date.Format("2006-01-02"),
region,
)
resp, err := http.Get(res)
if err != nil {
return nil, fmt.Errorf("failed to fetch %s: %w", res, err)
}
defer func() {
io.Copy(ioutil.Discard, resp.Body)
resp.Body.Close()
}()

if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("expected 200 OK when fetching: %s, got: %d", res, resp.StatusCode)
}

data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response body: %w", err)
}

return data, nil
}
8 changes: 8 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module github.com/daenney/vattenfall

go 1.16

require (
github.com/prometheus/client_golang v1.10.0
github.com/prometheus/common v0.18.0
)
Loading

0 comments on commit 11c5222

Please sign in to comment.