Linter config tuning and first version of Prometheus metrics parser.
This commit is contained in:
parent
edc3351eb8
commit
5b1094c258
@ -21,3 +21,5 @@ linters-settings:
|
|||||||
line-length: 120
|
line-length: 120
|
||||||
gocyclo:
|
gocyclo:
|
||||||
min-complexity: 40
|
min-complexity: 40
|
||||||
|
gocognit:
|
||||||
|
min-complexity: 40
|
||||||
|
@ -3,6 +3,8 @@ package application
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"log"
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"go.dev.pztrn.name/metricator/internal/storage"
|
"go.dev.pztrn.name/metricator/internal/storage"
|
||||||
"go.dev.pztrn.name/metricator/internal/storage/memory"
|
"go.dev.pztrn.name/metricator/internal/storage/memory"
|
||||||
@ -18,6 +20,11 @@ type Application struct {
|
|||||||
|
|
||||||
storage storage.Metrics
|
storage storage.Metrics
|
||||||
storageDone chan struct{}
|
storageDone chan struct{}
|
||||||
|
|
||||||
|
fetchIsRunning bool
|
||||||
|
fetchIsRunningMutex sync.RWMutex
|
||||||
|
|
||||||
|
httpClient *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewApplication creates new application.
|
// NewApplication creates new application.
|
||||||
@ -50,6 +57,8 @@ func (a *Application) initialize() {
|
|||||||
func (a *Application) Start() {
|
func (a *Application) Start() {
|
||||||
a.storage.Start()
|
a.storage.Start()
|
||||||
|
|
||||||
|
go a.startFetcher()
|
||||||
|
|
||||||
// The Context Listening Goroutine.
|
// The Context Listening Goroutine.
|
||||||
go func() {
|
go func() {
|
||||||
<-a.ctx.Done()
|
<-a.ctx.Done()
|
||||||
|
@ -6,10 +6,10 @@ import "time"
|
|||||||
type Config struct {
|
type Config struct {
|
||||||
// Endpoint is a remote application endpoint which should give us metrics
|
// Endpoint is a remote application endpoint which should give us metrics
|
||||||
// in Prometheus format.
|
// in Prometheus format.
|
||||||
Endpoint string
|
Endpoint string `yaml:"endpoint"`
|
||||||
// Headers is a list of headers that should be added to metrics request.
|
// Headers is a list of headers that should be added to metrics request.
|
||||||
Headers map[string]string
|
Headers map[string]string `yaml:"headers"`
|
||||||
// TimeBetweenRequests is a minimal amount of time which should pass
|
// TimeBetweenRequests is a minimal amount of time which should pass
|
||||||
// between requests.
|
// between requests.
|
||||||
TimeBetweenRequests time.Duration
|
TimeBetweenRequests time.Duration `yaml:"time_between_requests"`
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,86 @@
|
|||||||
package application
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Fetches data from remote endpoint, parses it and updates in storage.
|
||||||
|
func (a *Application) fetch() {
|
||||||
|
// Do not do anything if fetching is running.
|
||||||
|
// ToDo: maybe another approach?
|
||||||
|
a.fetchIsRunningMutex.RLock()
|
||||||
|
isFetching := a.fetchIsRunning
|
||||||
|
a.fetchIsRunningMutex.RUnlock()
|
||||||
|
|
||||||
|
if isFetching {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Fetching data for", a.name)
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(a.ctx, "GET", a.config.Endpoint, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Failed to create request for", a.name, "metrics:", err.Error())
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for header, value := range a.config.Headers {
|
||||||
|
req.Header.Add(header, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := a.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Failed to execute request for", a.name, "metrics:", err.Error())
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Failed to read response body for", a.name, "metrics:", err.Error())
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data := a.parse(string(body))
|
||||||
|
|
||||||
|
a.storage.Put(data)
|
||||||
|
|
||||||
|
a.fetchIsRunningMutex.Lock()
|
||||||
|
a.fetchIsRunning = true
|
||||||
|
a.fetchIsRunningMutex.Unlock()
|
||||||
|
|
||||||
|
a.fetchIsRunningMutex.Lock()
|
||||||
|
a.fetchIsRunning = false
|
||||||
|
a.fetchIsRunningMutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
// Configures and starts Prometheus data fetching goroutine.
|
// Configures and starts Prometheus data fetching goroutine.
|
||||||
func (a *Application) startFetcher() {}
|
func (a *Application) startFetcher() {
|
||||||
|
fetchTicker := time.NewTicker(a.config.TimeBetweenRequests)
|
||||||
|
|
||||||
|
// nolint:exaustivestruct
|
||||||
|
a.httpClient = &http.Client{
|
||||||
|
Timeout: time.Second * 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
defer log.Println("Fetcher for", a.name, "completed")
|
||||||
|
|
||||||
|
// First fetch should be executed ASAP.
|
||||||
|
a.fetch()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-a.ctx.Done():
|
||||||
|
return
|
||||||
|
case <-fetchTicker.C:
|
||||||
|
a.fetch()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
105
internal/application/parser.go
Normal file
105
internal/application/parser.go
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Parses passed body and returns a map suitable for pushing into storage.
|
||||||
|
func (a *Application) parse(body string) map[string]string {
|
||||||
|
data := make(map[string]string)
|
||||||
|
|
||||||
|
// ToDo: switch to bytes buffer and maybe do not read body in caller?
|
||||||
|
splittedBody := strings.Split(body, "\n")
|
||||||
|
|
||||||
|
for _, line := range splittedBody {
|
||||||
|
// Prometheus line contains metric name and metric parameters defined
|
||||||
|
// in "{}".
|
||||||
|
var (
|
||||||
|
name, value string
|
||||||
|
params []string
|
||||||
|
)
|
||||||
|
|
||||||
|
// Skip empty lines.
|
||||||
|
if line == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that line isn't commented. We should skip comments for now.
|
||||||
|
if strings.HasPrefix(line, "#") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Analyzing line:", line)
|
||||||
|
|
||||||
|
// Check if we have parametrized metric. If no - push it to data map.
|
||||||
|
if !strings.Contains(line, "{") {
|
||||||
|
name = strings.Split(line, " ")[0]
|
||||||
|
value = strings.Split(line, " ")[1]
|
||||||
|
} else {
|
||||||
|
value = strings.Split(line, " ")[1]
|
||||||
|
name = strings.Split(line, "{")[0]
|
||||||
|
|
||||||
|
// Parse params into "name:value" string.
|
||||||
|
valuesString := strings.Split(strings.Split(line, "{")[1], "}")[0]
|
||||||
|
|
||||||
|
var (
|
||||||
|
paramName, paramValue string
|
||||||
|
paramNameFinished, paramValueStarted, paramValueFinished bool
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, r := range valuesString {
|
||||||
|
if paramValueFinished && string(r) == "," {
|
||||||
|
params = append(params, paramName+":"+paramValue)
|
||||||
|
paramName, paramValue = "", ""
|
||||||
|
paramNameFinished, paramValueStarted, paramValueFinished = false, false, false
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !paramNameFinished {
|
||||||
|
if string(r) != "=" {
|
||||||
|
paramName += string(r)
|
||||||
|
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
paramNameFinished = true
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if string(r) == "\"" && !paramValueStarted {
|
||||||
|
paramValueStarted = true
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if paramValueStarted && string(r) != "\"" {
|
||||||
|
paramValue += string(r)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if paramValueStarted && string(r) == "\"" {
|
||||||
|
paramValueFinished = true
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if paramName != "" && paramValue != "" {
|
||||||
|
params = append(params, paramName+":"+paramValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, param := range params {
|
||||||
|
name += "/" + param
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data[name] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Data parsed: %+v\n", data)
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user