Skip to content
Snippets Groups Projects
Verified Commit 4091d8f0 authored by Janne Mareike Koschinski's avatar Janne Mareike Koschinski
Browse files

Implement Postgres and Quassel monitoring

parent cf87e73d
No related branches found
No related tags found
No related merge requests found
......@@ -56,7 +56,7 @@ func (api ConfigApi) Request(target interface{}, method string, url string, body
}
if res.StatusCode != 200 {
return MetaResponse{}, errors.New(fmt.Sprintf("API Responded with non-200 status code: %d", res.StatusCode))
return MetaResponse{}, errors.New(fmt.Sprintf("API Responded with status code %d: %s %s", res.StatusCode, res.Request.Method, res.Request.URL))
}
if target != nil {
......
......@@ -28,11 +28,11 @@ func (api ConfigApi) MetricPoints(metricId int) ApiMetricPointRepo {
}
}
func (repo ApiMetricPointRepo) List(id int) (points []ApiMetricPoint, err error) {
func (repo ApiMetricPointRepo) List() (points []ApiMetricPoint, err error) {
_, err = repo.Request(
&points,
"GET",
fmt.Sprintf("/metrics/%d/points/%d", repo.MetricId, id),
fmt.Sprintf("/metrics/%d/points", repo.MetricId),
nil,
)
return
......
......@@ -38,4 +38,17 @@ type ConfigComponent struct {
Body string `yaml:"body,omitempty"`
} `yaml:"expected,omitempty"`
} `yaml:"http,omitempty"`
Quassel struct {
Hostname string `yaml:"hostname,omitempty"`
Port int `yaml:"port,omitempty"`
Insecure bool `yaml:"insecure,omitempty"`
} `yaml:"quassel,omitempty"`
Postgres struct {
Hostname string `yaml:"hostname,omitempty"`
Port int `yaml:"port,omitempty"`
Username string `yaml:"username,omitempty"`
Password string `yaml:"password,omitempty"`
Database string `yaml:"database,omitempty"`
Insecure bool `yaml:"insecure,omitempty"`
} `yaml:"postgres,omitempty"`
}
......@@ -3,6 +3,7 @@ module git.kuschku.de/justjanne/cachet-monitor
go 1.12
require (
github.com/lib/pq v1.0.0
github.com/sirupsen/logrus v1.4.1
gopkg.in/yaml.v2 v2.2.2
)
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docopt/docopt-go v0.0.0-20160216232012-784ddc588536/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 h1:bWDMxwH3px2JBh6AyO7hdCn/PkvCZXii8TGj7sbtEbQ=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/miekg/dns v1.1.8 h1:1QYRAKU3lN5cRfLCkPU08hwvLJFhvjP6MqNMmQz6ZVI=
github.com/miekg/dns v1.1.8/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a h1:Igim7XhdOpBnWPuYJ70XcNpq8q3BCACtVgNfoJxOV7g=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e h1:nFYrTHrdrAOpShe27kaFHjsqYSEQ0KWqdWLu3xuZJts=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
......@@ -50,8 +50,11 @@ func main() {
setDefaultString(&config.Components[index].ThresholdDuration, "5m")
setDefaultString(&config.Components[index].Http.Method, "GET")
setDefaultInt(&config.Components[index].Http.Expected.StatusCode, 200)
setDefaultInt(&config.Components[index].Quassel.Port, 4242)
setDefaultInt(&config.Components[index].Postgres.Port, 5432)
}
storage := NewStorage()
......@@ -61,8 +64,13 @@ func main() {
for index, component := range config.Components {
logrus.Infof("Starting Monitor #%d: %s", index, component.Name)
var monitor Monitor
if component.Type == "http" {
switch component.Type {
case "http":
monitor = NewHttpMonitor(component)
case "quassel":
monitor = NewQuasselMonitor(component)
case "postgres":
monitor = NewPostgresMonitor(component)
}
wrapper := NewMonitorWrapper(&config.Api, &storage, monitor)
monitors = append(monitors, &wrapper)
......
package main
import (
"fmt"
"github.com/sirupsen/logrus"
"strconv"
"sync"
"time"
)
......@@ -13,10 +13,6 @@ type Datapoint struct {
Latency int
}
func (d Datapoint) String() string {
return fmt.Sprintf("Datapoint{time=%s, up=%t, latency=%d}", d.Time.Format(time.RFC3339), d.Up, d.Latency)
}
type MonitorWrapper struct {
api *ConfigApi
storage *Storage
......@@ -79,6 +75,7 @@ func (w *MonitorWrapper) tick() {
return
}
if w.config.ComponentId != 0 {
status := DetermineStatus(w.config, w.storage.Store(w.config.ComponentId, datapoint, w.thresholdDuration))
_, err = w.api.Components().Save(w.config.ComponentId, ApiComponentBody{
Status: status,
......@@ -87,6 +84,17 @@ func (w *MonitorWrapper) tick() {
logrus.Error(err.Error())
}
}
if w.config.MetricId != 0 {
_, err = w.api.MetricPoints(w.config.MetricId).Create(ApiMetricPointBody{
Metric: w.config.MetricId,
Value: datapoint.Latency,
Timestamp: strconv.FormatInt(datapoint.Time.Unix(), 10),
})
if err != nil {
logrus.Error(err.Error())
}
}
}
func (w *MonitorWrapper) ClockStop() {
select {
......
......@@ -47,7 +47,12 @@ func (m *HttpMonitor) Measure() (Datapoint, error) {
after := time.Now()
latency := int(after.Sub(before).Nanoseconds() / 1000000)
if err != nil {
return Datapoint{}, err
logrus.Infof("Monitor %s: %s", err.Error())
return Datapoint{
Time: after,
Up: false,
Latency: latency,
}, nil
}
defer resp.Body.Close()
......
package main
import (
"database/sql"
"fmt"
_ "github.com/lib/pq"
"github.com/sirupsen/logrus"
"time"
)
type PostgresMonitor struct {
config ConfigComponent
}
func NewPostgresMonitor(config ConfigComponent) *PostgresMonitor {
return &PostgresMonitor{
config: config,
}
}
func (m *PostgresMonitor) Config() *ConfigComponent {
return &m.config
}
func (m *PostgresMonitor) Measure() (Datapoint, error) {
sslMode := "verify-full"
if m.config.Postgres.Insecure {
sslMode = "allow"
}
connStr := fmt.Sprintf(
"postgres://%s:%s@%s:%d/%s?sslmode=%s",
m.config.Postgres.Username,
m.config.Postgres.Password,
m.config.Postgres.Hostname,
m.config.Postgres.Port,
m.config.Postgres.Database,
sslMode,
)
before := time.Now()
db, err := sql.Open("postgres", connStr)
after := time.Now()
latency := int(after.Sub(before).Nanoseconds() / 1000000)
defer db.Close()
if err != nil {
logrus.Infof("Monitor %s: %s", err)
return Datapoint{
Time: after,
Up: false,
Latency: latency,
}, nil
}
return Datapoint{
Time: after,
Up: true,
Latency: latency,
}, nil
}
package main
import (
"git.kuschku.de/justjanne/cachet-monitor/quassel"
"github.com/sirupsen/logrus"
"time"
)
type QuasselMonitor struct {
config ConfigComponent
}
func NewQuasselMonitor(config ConfigComponent) *QuasselMonitor {
return &QuasselMonitor{
config: config,
}
}
func (m *QuasselMonitor) Config() *ConfigComponent {
return &m.config
}
func (m *QuasselMonitor) Measure() (Datapoint, error) {
before := time.Now()
conn := quassel.Connect(quassel.ConnectOptions{
ConnectionOptions: quassel.ConnectionOptions{
Address: m.config.Quassel.Hostname,
Port: m.config.Quassel.Port,
Timeout: time.Duration(m.config.LatencyThresholds.Down) * time.Millisecond,
},
Verify: !m.config.Quassel.Insecure,
})
defer conn.Close()
after := time.Now()
latency := int(after.Sub(before).Nanoseconds() / 1000000)
if conn.Error() != nil {
logrus.Infof("Monitor %s: %s", conn.Error())
return Datapoint{
Time: after,
Up: false,
Latency: latency,
}, nil
}
return Datapoint{
Time: after,
Up: true,
Latency: latency,
}, nil
}
package quassel
import (
"bufio"
"crypto/tls"
"encoding/binary"
"fmt"
"net"
"time"
)
type Connection struct {
hostname string
socket net.Conn
tlsSocket *tls.Conn
readWriter *bufio.ReadWriter
buffer []byte
protocolInfo ProtocolInfo
err error
}
type ConnectionOptions struct {
Address string
Port int
Timeout time.Duration
}
func MakeConnection(options ConnectionOptions) Connection {
socket, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", options.Address, options.Port), options.Timeout)
if err != nil {
panic(err.Error())
}
return Connection{
hostname: options.Address,
socket: socket,
readWriter: bufio.NewReadWriter(
bufio.NewReader(socket),
bufio.NewWriter(socket),
),
buffer: make([]byte, 4),
}
}
func (c *Connection) Write(data uint32) {
if c.err == nil {
binary.BigEndian.PutUint32(c.buffer, data)
_, c.err = c.readWriter.Write(c.buffer)
}
}
func (c *Connection) Read(len int) []byte {
buffer := make([]byte, len)
if c.err == nil {
_, c.err = c.readWriter.Read(buffer)
}
return buffer
}
func (c *Connection) Flush() {
if c.err == nil {
c.err = c.readWriter.Flush()
}
}
func (c *Connection) WithTLS(verify bool) {
if c.err == nil {
config := &tls.Config{
ServerName: c.hostname,
InsecureSkipVerify: !verify,
}
c.tlsSocket = tls.Client(c.socket, config)
c.readWriter = bufio.NewReadWriter(
bufio.NewReader(c.tlsSocket),
bufio.NewWriter(c.tlsSocket),
)
c.err = c.tlsSocket.Handshake()
}
}
func (c *Connection) TlsState() *tls.ConnectionState {
if c.tlsSocket != nil {
state := c.tlsSocket.ConnectionState()
return &state
} else {
return nil
}
}
func (c *Connection) ProtocolInfo() ProtocolInfo {
return c.protocolInfo
}
func (c *Connection) Error() error {
return c.err
}
func (c *Connection) Close() {
_ = c.readWriter.Flush()
_ = c.tlsSocket.Close()
_ = c.socket.Close()
}
package quassel
import (
"encoding/binary"
)
const ProtocolMagic uint32 = 0x42b33f00
const ProtocolFeatureTls uint8 = 0x01
const ProtocolFeatureCompression uint8 = 0x02
const ProtocolDatastream uint32 = 0x02
type ProtocolInfo struct {
FlagTLS bool
FlagCompression bool
Data uint16
Version uint8
}
func parseProtocolInfo(data []byte) ProtocolInfo {
rawFeatures := data[0]
rawData := data[1:3]
rawVersion := data[3]
return ProtocolInfo{
rawFeatures&ProtocolFeatureTls != 0,
rawFeatures&ProtocolFeatureCompression != 0,
binary.BigEndian.Uint16(rawData),
rawVersion,
}
}
type ConnectOptions struct {
ConnectionOptions
Verify bool
}
func Connect(options ConnectOptions) Connection {
conn := MakeConnection(options.ConnectionOptions)
conn.Write(ProtocolMagic | uint32(ProtocolFeatureTls))
supportedProtocols := []uint32{
ProtocolDatastream,
}
for _, protocol := range supportedProtocols {
conn.Write(protocol)
}
conn.Write(1 << 31)
conn.Flush()
conn.protocolInfo = parseProtocolInfo(conn.Read(4))
if conn.protocolInfo.FlagTLS {
conn.WithTLS(options.Verify)
}
return conn
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment