Rethink how metricator will work, golangci-lint and gitlab ci configs.
This commit is contained in:
parent
e679fecf6e
commit
f915470c0b
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
|||||||
._bin
|
._bin
|
||||||
.vscode
|
.vscode
|
||||||
|
metricator.yaml
|
||||||
|
10
.gitlab-ci.yml
Normal file
10
.gitlab-ci.yml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
stages:
|
||||||
|
- test
|
||||||
|
|
||||||
|
lint:
|
||||||
|
stage: test
|
||||||
|
tags:
|
||||||
|
- docker
|
||||||
|
image: golangci/golangci-lint:v1.32.2
|
||||||
|
script:
|
||||||
|
- golangci-lint run
|
23
.golangci.yml
Normal file
23
.golangci.yml
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
run:
|
||||||
|
deadline: 5m
|
||||||
|
linters:
|
||||||
|
enable-all: true
|
||||||
|
disable:
|
||||||
|
# Because globals might exist, but according to our codestyle they
|
||||||
|
# should be lowercased and considered as unexported.
|
||||||
|
- gochecknoglobals
|
||||||
|
# While it might be useful it'll create more problems that will solve.
|
||||||
|
- gocritic
|
||||||
|
# Complains about main() lengths, which isn't an issue.
|
||||||
|
- funlen
|
||||||
|
# Magic numbers might be everywhere. Disabled for now.
|
||||||
|
- gomnd
|
||||||
|
# ToDos everywhere
|
||||||
|
- godox
|
||||||
|
# Why? WHY? WHY _test???
|
||||||
|
- testpackage
|
||||||
|
linters-settings:
|
||||||
|
lll:
|
||||||
|
line-length: 120
|
||||||
|
gocyclo:
|
||||||
|
min-complexity: 40
|
4
Makefile
4
Makefile
@ -1,6 +1,8 @@
|
|||||||
# This is a Metricator Makefile.
|
# This is a Metricator Makefile.
|
||||||
# It contains calls to scripts placed in scripts directory.
|
# It contains calls to scripts placed in scripts directory.
|
||||||
|
|
||||||
|
CONFIG ?= ./metricator.example.yaml
|
||||||
|
|
||||||
help: Makefile
|
help: Makefile
|
||||||
@echo -e "Metricator Makefile available subcommands:\n"
|
@echo -e "Metricator Makefile available subcommands:\n"
|
||||||
@cat $< | grep "## " | sort | sed -n 's/^## //p'
|
@cat $< | grep "## " | sort | sed -n 's/^## //p'
|
||||||
@ -19,7 +21,7 @@ metricatord-build: check-build-dir
|
|||||||
|
|
||||||
## metricatord-run: starts metricator daemon.
|
## metricatord-run: starts metricator daemon.
|
||||||
metricatord-run: metricatord-build
|
metricatord-run: metricatord-build
|
||||||
./._bin/metricatord -config ./metricator.example.yaml
|
./._bin/metricatord -config ${CONFIG}
|
||||||
|
|
||||||
show-git-data:
|
show-git-data:
|
||||||
@echo "Parameters for current source code state:"
|
@echo "Parameters for current source code state:"
|
||||||
|
@ -14,12 +14,11 @@ Metricator will issue only one request and cache data in memory between them. Al
|
|||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
*TBW*
|
TBW
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
*TBW*
|
TBW
|
||||||
|
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
|
@ -8,9 +8,9 @@ import (
|
|||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"go.dev.pztrn.name/metricator/internal/application"
|
||||||
"go.dev.pztrn.name/metricator/internal/common"
|
"go.dev.pztrn.name/metricator/internal/common"
|
||||||
"go.dev.pztrn.name/metricator/internal/configuration"
|
"go.dev.pztrn.name/metricator/internal/configuration"
|
||||||
"go.dev.pztrn.name/metricator/internal/datastore"
|
|
||||||
"go.dev.pztrn.name/metricator/internal/httpserver"
|
"go.dev.pztrn.name/metricator/internal/httpserver"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -26,16 +26,27 @@ func main() {
|
|||||||
config := configuration.NewConfig()
|
config := configuration.NewConfig()
|
||||||
|
|
||||||
httpSrv, httpStopped := httpserver.NewHTTPServer(mainCtx, config)
|
httpSrv, httpStopped := httpserver.NewHTTPServer(mainCtx, config)
|
||||||
dataStore, dataStoreStopped := datastore.NewDataStore(mainCtx, config)
|
|
||||||
|
|
||||||
|
// Parse configuration.
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
err := config.Parse()
|
err := config.Parse()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln("Failed to parse configuration:", err.Error())
|
log.Fatalln("Failed to parse configuration:", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Configuration parsed: %+v\n", config)
|
log.Printf("Configuration parsed: %+v\n", config)
|
||||||
|
|
||||||
dataStore.Start()
|
// Create applications.
|
||||||
|
apps := make([]*application.Application, 0, len(config.Applications))
|
||||||
|
|
||||||
|
for appName, appConfig := range config.Applications {
|
||||||
|
app := application.NewApplication(mainCtx, appName, appConfig)
|
||||||
|
app.Start()
|
||||||
|
|
||||||
|
apps = append(apps, app)
|
||||||
|
}
|
||||||
|
|
||||||
httpSrv.Start()
|
httpSrv.Start()
|
||||||
|
|
||||||
log.Println("Metricator is started and ready to serve requests")
|
log.Println("Metricator is started and ready to serve requests")
|
||||||
@ -46,15 +57,18 @@ func main() {
|
|||||||
|
|
||||||
signal.Notify(signalHandler, os.Interrupt, syscall.SIGTERM)
|
signal.Notify(signalHandler, os.Interrupt, syscall.SIGTERM)
|
||||||
|
|
||||||
go func() {
|
go func(apps []*application.Application) {
|
||||||
<-signalHandler
|
<-signalHandler
|
||||||
cancelFunc()
|
cancelFunc()
|
||||||
|
|
||||||
<-dataStoreStopped
|
for _, app := range apps {
|
||||||
|
<-app.GetDoneChan()
|
||||||
|
}
|
||||||
|
|
||||||
<-httpStopped
|
<-httpStopped
|
||||||
|
|
||||||
shutdownDone <- true
|
shutdownDone <- true
|
||||||
}()
|
}(apps)
|
||||||
|
|
||||||
<-shutdownDone
|
<-shutdownDone
|
||||||
log.Println("Metricator stopped")
|
log.Println("Metricator stopped")
|
||||||
|
2
go.mod
2
go.mod
@ -2,4 +2,4 @@ module go.dev.pztrn.name/metricator
|
|||||||
|
|
||||||
go 1.13
|
go 1.13
|
||||||
|
|
||||||
require gopkg.in/yaml.v2 v2.3.0
|
require gopkg.in/yaml.v2 v2.4.0
|
||||||
|
2
go.sum
2
go.sum
@ -1,3 +1,5 @@
|
|||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
63
internal/application/application.go
Normal file
63
internal/application/application.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"go.dev.pztrn.name/metricator/internal/storage"
|
||||||
|
"go.dev.pztrn.name/metricator/internal/storage/memory"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Application is a thing that responsible for all application-related
|
||||||
|
// actions like data fetching, storing, etc. on higher level.
|
||||||
|
type Application struct {
|
||||||
|
config *Config
|
||||||
|
ctx context.Context
|
||||||
|
doneChan chan struct{}
|
||||||
|
name string
|
||||||
|
|
||||||
|
storage storage.Metrics
|
||||||
|
storageDone chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewApplication creates new application.
|
||||||
|
func NewApplication(ctx context.Context, name string, config *Config) *Application {
|
||||||
|
a := &Application{
|
||||||
|
config: config,
|
||||||
|
ctx: ctx,
|
||||||
|
doneChan: make(chan struct{}),
|
||||||
|
name: name,
|
||||||
|
}
|
||||||
|
a.initialize()
|
||||||
|
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDoneChan returns a channel which should be used to block execution until
|
||||||
|
// application's routines are completed.
|
||||||
|
func (a *Application) GetDoneChan() chan struct{} {
|
||||||
|
return a.doneChan
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initializes internal things like storage, HTTP client, etc.
|
||||||
|
func (a *Application) initialize() {
|
||||||
|
a.storage, a.storageDone = memory.NewStorage(a.ctx, a.name+" storage")
|
||||||
|
|
||||||
|
log.Printf("Application '%s' initialized with configuration: %+v\n", a.name, a.config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start starts asynchronous things like data fetching, storage cleanup, etc.
|
||||||
|
func (a *Application) Start() {
|
||||||
|
a.storage.Start()
|
||||||
|
|
||||||
|
// The Context Listening Goroutine.
|
||||||
|
go func() {
|
||||||
|
<-a.ctx.Done()
|
||||||
|
// We should wait until storage routines are also stopped.
|
||||||
|
<-a.storage.GetDoneChan()
|
||||||
|
|
||||||
|
log.Println("Application", a.name, "stopped")
|
||||||
|
|
||||||
|
a.doneChan <- struct{}{}
|
||||||
|
}()
|
||||||
|
}
|
15
internal/application/config.go
Normal file
15
internal/application/config.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package application
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// Config is a generic application configuration.
|
||||||
|
type Config struct {
|
||||||
|
// Endpoint is a remote application endpoint which should give us metrics
|
||||||
|
// in Prometheus format.
|
||||||
|
Endpoint string
|
||||||
|
// Headers is a list of headers that should be added to metrics request.
|
||||||
|
Headers map[string]string
|
||||||
|
// TimeBetweenRequests is a minimal amount of time which should pass
|
||||||
|
// between requests.
|
||||||
|
TimeBetweenRequests time.Duration
|
||||||
|
}
|
4
internal/application/fetcher.go
Normal file
4
internal/application/fetcher.go
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
package application
|
||||||
|
|
||||||
|
// Configures and starts Prometheus data fetching goroutine.
|
||||||
|
func (a *Application) startFetcher() {}
|
@ -8,8 +8,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
|
"go.dev.pztrn.name/metricator/internal/application"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -23,23 +23,7 @@ type Config struct {
|
|||||||
configPath string
|
configPath string
|
||||||
// Applications describes configuration for remote application's endpoints.
|
// Applications describes configuration for remote application's endpoints.
|
||||||
// Key is an application's name.
|
// Key is an application's name.
|
||||||
Applications map[string]struct {
|
Applications map[string]*application.Config `yaml:"applications"`
|
||||||
// Endpoint is a remote application endpoint which should give us metrics
|
|
||||||
// in Prometheus format.
|
|
||||||
Endpoint string
|
|
||||||
// Headers is a list of headers that should be added to metrics request.
|
|
||||||
Headers map[string]string
|
|
||||||
// TimeBetweenRequests is a minimal amount of time which should pass
|
|
||||||
// between requests.
|
|
||||||
TimeBetweenRequests time.Duration
|
|
||||||
}
|
|
||||||
// Datastore describes data storage configuration.
|
|
||||||
Datastore struct {
|
|
||||||
// ValidTimeout is a timeout for which every data entry will be considered
|
|
||||||
// as valid. After that timeout if value wasn't updated it will be considered
|
|
||||||
// as invalid and purged from memory.
|
|
||||||
ValidTimeout time.Duration `yaml:"valid_timeout"`
|
|
||||||
} `yaml:"datastore"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConfig returns new configuration.
|
// NewConfig returns new configuration.
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
package datastore
|
|
||||||
|
|
||||||
import "sync"
|
|
||||||
|
|
||||||
// This is application-specific data storage.
|
|
||||||
type applicationStorage struct {
|
|
||||||
metrics map[string]string
|
|
||||||
metricsMutex sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates new application-specific storage.
|
|
||||||
func newApplicationStorage() *applicationStorage {
|
|
||||||
as := &applicationStorage{}
|
|
||||||
as.initialize()
|
|
||||||
|
|
||||||
return as
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initializes internal things.
|
|
||||||
func (as *applicationStorage) initialize() {
|
|
||||||
as.metrics = make(map[string]string)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Starts application-specific things, like goroutine for HTTP requests.
|
|
||||||
func (as *applicationStorage) start() {}
|
|
@ -1,56 +0,0 @@
|
|||||||
package datastore
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"log"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"go.dev.pztrn.name/metricator/internal/configuration"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DataStore is a data storage structure. It keeps all gathered metrics and gives
|
|
||||||
// them away on request.
|
|
||||||
type DataStore struct {
|
|
||||||
config *configuration.Config
|
|
||||||
ctx context.Context
|
|
||||||
doneChan chan struct{}
|
|
||||||
|
|
||||||
datas map[string]*applicationStorage
|
|
||||||
datasMutex sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDataStore creates new data storage.
|
|
||||||
func NewDataStore(ctx context.Context, cfg *configuration.Config) (*DataStore, chan struct{}) {
|
|
||||||
ds := &DataStore{
|
|
||||||
config: cfg,
|
|
||||||
ctx: ctx,
|
|
||||||
doneChan: make(chan struct{}),
|
|
||||||
}
|
|
||||||
ds.initialize()
|
|
||||||
|
|
||||||
return ds, ds.doneChan
|
|
||||||
}
|
|
||||||
|
|
||||||
// Internal things initialization.
|
|
||||||
func (ds *DataStore) initialize() {
|
|
||||||
ds.datas = make(map[string]*applicationStorage)
|
|
||||||
|
|
||||||
// Create applications defined in configuration.
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
<-ds.ctx.Done()
|
|
||||||
log.Println("Data storage stopped")
|
|
||||||
|
|
||||||
ds.doneChan <- struct{}{}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start starts data storage asynchronous things.
|
|
||||||
func (ds *DataStore) Start() {
|
|
||||||
log.Println("Starting data storage...")
|
|
||||||
|
|
||||||
ds.datasMutex.RLock()
|
|
||||||
for _, storage := range ds.datas {
|
|
||||||
storage.start()
|
|
||||||
}
|
|
||||||
}
|
|
@ -40,6 +40,8 @@ func (h *HTTPServer) getRequestContext(_ net.Listener) context.Context {
|
|||||||
// Initializes handler and HTTP server structure.
|
// Initializes handler and HTTP server structure.
|
||||||
func (h *HTTPServer) initialize() {
|
func (h *HTTPServer) initialize() {
|
||||||
h.handler = &handler{}
|
h.handler = &handler{}
|
||||||
|
// We do not need to specify all possible parameters for HTTP server, so:
|
||||||
|
// nolint:exaustivestruct
|
||||||
h.server = &http.Server{
|
h.server = &http.Server{
|
||||||
// ToDo: make it all configurable.
|
// ToDo: make it all configurable.
|
||||||
Addr: ":34421",
|
Addr: ":34421",
|
||||||
|
15
internal/storage/generic.go
Normal file
15
internal/storage/generic.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package storage
|
||||||
|
|
||||||
|
// GenericStorage describes interface every other storage should embed
|
||||||
|
// and conform to as it contains essential things like context handling.
|
||||||
|
type GenericStorage interface {
|
||||||
|
// Get returns data from storage by key.
|
||||||
|
Get(string) string
|
||||||
|
// GetDoneChan returns a channel which should be used to block execution
|
||||||
|
// until storage's routines are completed.
|
||||||
|
GetDoneChan() chan struct{}
|
||||||
|
// Put puts passed data into storage.
|
||||||
|
Put(map[string]string)
|
||||||
|
// Start starts asynchronous things if needed.
|
||||||
|
Start()
|
||||||
|
}
|
75
internal/storage/memory/memory.go
Normal file
75
internal/storage/memory/memory.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
package memory
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Storage is an in-memory storage.
|
||||||
|
type Storage struct {
|
||||||
|
ctx context.Context
|
||||||
|
doneChan chan struct{}
|
||||||
|
name string
|
||||||
|
|
||||||
|
data map[string]string
|
||||||
|
dataMutex sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStorage creates new in-memory storage to use.
|
||||||
|
func NewStorage(ctx context.Context, name string) (*Storage, chan struct{}) {
|
||||||
|
s := &Storage{
|
||||||
|
ctx: ctx,
|
||||||
|
doneChan: make(chan struct{}),
|
||||||
|
name: name,
|
||||||
|
}
|
||||||
|
s.initialize()
|
||||||
|
|
||||||
|
return s, s.doneChan
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns data from storage by key.
|
||||||
|
func (s *Storage) Get(key string) string {
|
||||||
|
s.dataMutex.RLock()
|
||||||
|
defer s.dataMutex.RUnlock()
|
||||||
|
|
||||||
|
data, found := s.data[key]
|
||||||
|
if !found {
|
||||||
|
return "Not found"
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDoneChan returns a channel which should be used to block execution
|
||||||
|
// until storage's routines are completed.
|
||||||
|
func (s *Storage) GetDoneChan() chan struct{} {
|
||||||
|
return s.doneChan
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initializes internal things.
|
||||||
|
func (s *Storage) initialize() {
|
||||||
|
s.data = make(map[string]string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put puts passed data into storage.
|
||||||
|
func (s *Storage) Put(data map[string]string) {
|
||||||
|
s.dataMutex.Lock()
|
||||||
|
defer s.dataMutex.Unlock()
|
||||||
|
|
||||||
|
for k, v := range data {
|
||||||
|
s.data[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start starts asynchronous things if needed.
|
||||||
|
func (s *Storage) Start() {
|
||||||
|
// The Context Listening Goroutine.
|
||||||
|
go func() {
|
||||||
|
<-s.ctx.Done()
|
||||||
|
|
||||||
|
log.Println("In-memory storage", s.name, "done")
|
||||||
|
|
||||||
|
s.doneChan <- struct{}{}
|
||||||
|
}()
|
||||||
|
}
|
6
internal/storage/metrics.go
Normal file
6
internal/storage/metrics.go
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
package storage
|
||||||
|
|
||||||
|
// Metrics describes generic metrics storage interface.
|
||||||
|
type Metrics interface {
|
||||||
|
GenericStorage
|
||||||
|
}
|
@ -1,2 +1,12 @@
|
|||||||
datastore:
|
# Applications configuration.
|
||||||
valid_timeout: 30s
|
applications:
|
||||||
|
# Application name.
|
||||||
|
theveryexampleapp:
|
||||||
|
# Endpoint to fetch. HTTP GET will be used.
|
||||||
|
endpoint: http://127.0.0.1:8789/metrics
|
||||||
|
# Headers to append to request.
|
||||||
|
headers:
|
||||||
|
X-API-KEY: th4apik3yh4rdt0gu3ss
|
||||||
|
# Timeout between requests. Not neccessarily be exact and requests might
|
||||||
|
# be sent in 60 or more seconds (in this example).
|
||||||
|
time_between_requests: 60s
|
||||||
|
Loading…
Reference in New Issue
Block a user