The very basic metricator-client and package.
Package can be used in external things if needed.
This commit is contained in:
parent
f1418a7a31
commit
614526b16d
9
Makefile
9
Makefile
@ -14,11 +14,20 @@ help: Makefile
|
||||
check-build-dir:
|
||||
@if [ ! -d "._bin" ]; then mkdir ._bin; fi
|
||||
|
||||
## metricator-client-build: builds metricator client and places into ${PWD}/._bin.
|
||||
metricator-client-build: check-build-dir
|
||||
@rm ./._bin/metricator-client || true
|
||||
@scripts/build.sh metricator-client
|
||||
|
||||
## metricatord-build: builds metricator daemon and places into ${PWD}/._bin.
|
||||
metricatord-build: check-build-dir
|
||||
@rm ./._bin/metricatord || true
|
||||
@scripts/build.sh metricatord
|
||||
|
||||
## metricator-client-run: starts metricator client. Use ARGS to supply args.
|
||||
metricator-client-run: metricator-client-build
|
||||
@./._bin/metricator-client -config ${CONFIG} $(ARGS)
|
||||
|
||||
## metricatord-run: starts metricator daemon.
|
||||
metricatord-run: metricatord-build
|
||||
./._bin/metricatord -config ${CONFIG}
|
||||
|
105
cmd/metricator-client/main.go
Normal file
105
cmd/metricator-client/main.go
Normal file
@ -0,0 +1,105 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"go.dev.pztrn.name/metricator/internal/common"
|
||||
"go.dev.pztrn.name/metricator/internal/configuration"
|
||||
"go.dev.pztrn.name/metricator/internal/logger"
|
||||
"go.dev.pztrn.name/metricator/pkg/client"
|
||||
)
|
||||
|
||||
// nolint:gochecknoglobals
|
||||
var (
|
||||
application = flag.String("application", "", "Application to query.")
|
||||
appsList = flag.Bool("apps-list", false, "Show application's list registered at Metricator.")
|
||||
metricatorHost = flag.String("metricator-host", "", "IP address or domain on which Metricator is available")
|
||||
metricatorTimeout = flag.Int("metricator-timeout", 5, "Timeout for requests sent to Metricator.")
|
||||
metricsList = flag.Bool("metrics-list", false, "Show metrics list. Requires 'application' parameter.")
|
||||
metric = flag.String("metric", "", "Metric data to retrieve. Requires 'application' parameter.")
|
||||
)
|
||||
|
||||
func main() {
|
||||
config := configuration.NewConfig()
|
||||
|
||||
// Parse configuration.
|
||||
flag.Parse()
|
||||
|
||||
err := config.Parse()
|
||||
if err != nil {
|
||||
log.Fatalln("Failed to parse configuration:", err.Error())
|
||||
}
|
||||
|
||||
logger := logger.NewLogger(config.Logger)
|
||||
|
||||
logger.Debugf("Starting Metricator client, version %s from branch %s (build #%s, commit hash %s)\n",
|
||||
common.Version,
|
||||
common.Branch,
|
||||
common.Build,
|
||||
common.CommitHash,
|
||||
)
|
||||
|
||||
// Check configuration.
|
||||
// We cannot work at all if host isn't defined.
|
||||
if *metricatorHost == "" {
|
||||
logger.Infoln("Host isn't defined.")
|
||||
|
||||
flag.PrintDefaults()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// If nothing is requested - show error message.
|
||||
if !*appsList && !*metricsList && *metric == "" {
|
||||
logger.Infoln("No action specified.")
|
||||
|
||||
flag.PrintDefaults()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// When asking to metrics list we need application to be defined.
|
||||
if *metricsList && *application == "" {
|
||||
logger.Infoln("Getting metrics list requires 'application' parameter to be filled.")
|
||||
|
||||
flag.PrintDefaults()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// When asking for specific metric we need application to be defined.
|
||||
if *metric != "" && *application == "" {
|
||||
logger.Infoln("Getting metric data requires 'application' parameter to be filled.")
|
||||
|
||||
flag.PrintDefaults()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
clientConfig := &client.Config{
|
||||
Host: *metricatorHost,
|
||||
Timeout: *metricatorTimeout,
|
||||
}
|
||||
|
||||
c := client.NewClient(clientConfig, logger)
|
||||
|
||||
var data interface{}
|
||||
|
||||
switch {
|
||||
case *appsList:
|
||||
data = c.GetAppsList()
|
||||
case *metricsList:
|
||||
data = c.GetMetricsList(*application)
|
||||
case *metric != "":
|
||||
data = c.GetMetric(*application, *metric)
|
||||
}
|
||||
|
||||
dataAsBytes, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
logger.Infoln("Failed to marshal data from Metricator:", err.Error())
|
||||
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
fmt.Println(string(dataAsBytes))
|
||||
}
|
@ -3,12 +3,12 @@ package application
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"go.dev.pztrn.name/metricator/internal/models"
|
||||
"go.dev.pztrn.name/metricator/pkg/schema"
|
||||
)
|
||||
|
||||
// Parses passed body and returns a map suitable for pushing into storage.
|
||||
func (a *Application) parse(body string) map[string]models.Metric {
|
||||
data := make(map[string]models.Metric)
|
||||
func (a *Application) parse(body string) map[string]schema.Metric {
|
||||
data := make(map[string]schema.Metric)
|
||||
|
||||
// ToDo: switch to bytes buffer and maybe do not read body in caller?
|
||||
splittedBody := strings.Split(body, "\n")
|
||||
@ -35,7 +35,7 @@ func (a *Application) parse(body string) map[string]models.Metric {
|
||||
if !found {
|
||||
a.logger.Debugln("Metric wasn't yet created, creating new structure")
|
||||
|
||||
metric = models.NewMetric(name, "", "", nil)
|
||||
metric = schema.NewMetric(name, "", "", nil)
|
||||
}
|
||||
|
||||
a.logger.Debugf("Got metric to use: %+v\n", metric)
|
||||
|
@ -1,19 +1,19 @@
|
||||
package storage
|
||||
|
||||
import "go.dev.pztrn.name/metricator/internal/models"
|
||||
import "go.dev.pztrn.name/metricator/pkg/schema"
|
||||
|
||||
// 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) (models.Metric, error)
|
||||
Get(string) (schema.Metric, error)
|
||||
// GetAsSlice returns all data from storage as slice.
|
||||
GetAsSlice() []models.Metric
|
||||
GetAsSlice() []schema.Metric
|
||||
// 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]models.Metric)
|
||||
Put(map[string]schema.Metric)
|
||||
// Start starts asynchronous things if needed.
|
||||
Start()
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"go.dev.pztrn.name/metricator/internal/logger"
|
||||
"go.dev.pztrn.name/metricator/internal/models"
|
||||
"go.dev.pztrn.name/metricator/pkg/schema"
|
||||
)
|
||||
|
||||
// ErrMetricNotFound appears if requested metric wasn't found in storage.
|
||||
@ -17,7 +17,7 @@ type Storage struct {
|
||||
ctx context.Context
|
||||
doneChan chan struct{}
|
||||
logger *logger.Logger
|
||||
data map[string]models.Metric
|
||||
data map[string]schema.Metric
|
||||
name string
|
||||
dataMutex sync.RWMutex
|
||||
}
|
||||
@ -36,7 +36,7 @@ func NewStorage(ctx context.Context, name string, logger *logger.Logger) (*Stora
|
||||
}
|
||||
|
||||
// Get returns data from storage by key.
|
||||
func (s *Storage) Get(key string) (models.Metric, error) {
|
||||
func (s *Storage) Get(key string) (schema.Metric, error) {
|
||||
s.logger.Debugln("Retrieving data for", key, "key from storage...")
|
||||
|
||||
s.dataMutex.RLock()
|
||||
@ -46,7 +46,7 @@ func (s *Storage) Get(key string) (models.Metric, error) {
|
||||
if !found {
|
||||
s.logger.Infoln("Key", key, "not found in storage!")
|
||||
|
||||
return models.NewMetric("", "", "", nil), ErrMetricNotFound
|
||||
return schema.NewMetric("", "", "", nil), ErrMetricNotFound
|
||||
}
|
||||
|
||||
s.logger.Debugf("Key %s found: %+v\n", key, data)
|
||||
@ -55,10 +55,10 @@ func (s *Storage) Get(key string) (models.Metric, error) {
|
||||
}
|
||||
|
||||
// GetAsSlice returns all data from storage as slice.
|
||||
func (s *Storage) GetAsSlice() []models.Metric {
|
||||
func (s *Storage) GetAsSlice() []schema.Metric {
|
||||
s.logger.Debugln("Returning all stored metrics as slice...")
|
||||
|
||||
metrics := make([]models.Metric, 0, len(s.data))
|
||||
metrics := make([]schema.Metric, 0, len(s.data))
|
||||
|
||||
for _, metric := range s.data {
|
||||
metrics = append(metrics, metric)
|
||||
@ -75,11 +75,11 @@ func (s *Storage) GetDoneChan() chan struct{} {
|
||||
|
||||
// Initializes internal things.
|
||||
func (s *Storage) initialize() {
|
||||
s.data = make(map[string]models.Metric)
|
||||
s.data = make(map[string]schema.Metric)
|
||||
}
|
||||
|
||||
// Put puts passed data into storage.
|
||||
func (s *Storage) Put(data map[string]models.Metric) {
|
||||
func (s *Storage) Put(data map[string]schema.Metric) {
|
||||
s.dataMutex.Lock()
|
||||
defer s.dataMutex.Unlock()
|
||||
|
||||
|
146
pkg/client/client.go
Normal file
146
pkg/client/client.go
Normal file
@ -0,0 +1,146 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"go.dev.pztrn.name/metricator/internal/logger"
|
||||
"go.dev.pztrn.name/metricator/pkg/schema"
|
||||
)
|
||||
|
||||
// Client is a Metricator client that is ready to be used in other applications
|
||||
// or libraries.
|
||||
type Client struct {
|
||||
config *Config
|
||||
logger *logger.Logger
|
||||
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
// NewClient creates new Metricator client.
|
||||
func NewClient(config *Config, logger *logger.Logger) *Client {
|
||||
c := &Client{
|
||||
config: config,
|
||||
logger: logger,
|
||||
}
|
||||
c.initialize()
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// Executes request and parses it's contents.
|
||||
func (c *Client) executeAndParse(req *http.Request, dest interface{}) error {
|
||||
c.logger.Debugf("Executing HTTP request to %s%s", c.config.Host, req.URL.RequestURI())
|
||||
|
||||
ctx, cancelFunc := context.WithTimeout(context.Background(), time.Second*time.Duration(c.config.Timeout))
|
||||
defer cancelFunc()
|
||||
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
response, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
c.logger.Infoln("Failed to execute request to Metricator:", err.Error())
|
||||
|
||||
return fmt.Errorf("metricator client: %w", err)
|
||||
}
|
||||
|
||||
defer response.Body.Close()
|
||||
|
||||
respData, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
c.logger.Infoln("Failed to read response body:", err.Error())
|
||||
|
||||
return fmt.Errorf("metricator client: %w", err)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(respData, dest)
|
||||
if err != nil {
|
||||
c.logger.Infoln("Failed to parse response:", err.Error())
|
||||
|
||||
return fmt.Errorf("metricator client: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAppsList returns a slice with applications that was registered at Metricator.
|
||||
func (c *Client) GetAppsList() schema.AppsList {
|
||||
address := fmt.Sprintf("%s/api/v1/apps_list", c.config.Host)
|
||||
|
||||
// Request's context sets in c.executeAndParse, so:
|
||||
// nolint:noctx
|
||||
req, err := http.NewRequest("GET", address, nil)
|
||||
if err != nil {
|
||||
c.logger.Infoln("Failed to create HTTP request:", err.Error())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
appsList := make(schema.AppsList, 0)
|
||||
|
||||
err = c.executeAndParse(req, &appsList)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return appsList
|
||||
}
|
||||
|
||||
// GetMetric returns value for metric.
|
||||
func (c *Client) GetMetric(appName, metricName string) interface{} {
|
||||
address := fmt.Sprintf("%s/api/v1/metrics/%s/%s", c.config.Host, appName, metricName)
|
||||
|
||||
// Request's context sets in c.executeAndParse, so:
|
||||
// nolint:noctx
|
||||
req, err := http.NewRequest("GET", address, nil)
|
||||
if err != nil {
|
||||
c.logger.Infoln("Failed to create HTTP request:", err.Error())
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
var data interface{}
|
||||
|
||||
err = c.executeAndParse(req, &data)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
// GetMetricsList returns a slice with metrics names for passed application.
|
||||
func (c *Client) GetMetricsList(appName string) schema.Metrics {
|
||||
address := fmt.Sprintf("%s/api/v1/metrics/%s", c.config.Host, appName)
|
||||
|
||||
// Request's context sets in c.executeAndParse, so:
|
||||
// nolint:noctx
|
||||
req, err := http.NewRequest("GET", address, nil)
|
||||
if err != nil {
|
||||
c.logger.Infoln("Failed to create HTTP request:", err.Error())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
data := make(schema.Metrics, 0)
|
||||
|
||||
err = c.executeAndParse(req, &data)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
// Initializes internal states and storages.
|
||||
func (c *Client) initialize() {
|
||||
// We do not need to set everything for client actually, so:
|
||||
// nolint:exhaustivestruct
|
||||
c.httpClient = &http.Client{
|
||||
Timeout: time.Second * time.Duration(c.config.Timeout),
|
||||
}
|
||||
}
|
9
pkg/client/config.go
Normal file
9
pkg/client/config.go
Normal file
@ -0,0 +1,9 @@
|
||||
package client
|
||||
|
||||
// Config is a Metricator client configuration.
|
||||
type Config struct {
|
||||
// Host is a host where Metricator is available for requests.
|
||||
Host string
|
||||
// Timeout specifies HTTP client timeout.
|
||||
Timeout int
|
||||
}
|
9
pkg/schema/apps_list.go
Normal file
9
pkg/schema/apps_list.go
Normal file
@ -0,0 +1,9 @@
|
||||
package schema
|
||||
|
||||
// AppsList represents applications list structure from Metricator's API.
|
||||
type AppsList []string
|
||||
|
||||
// IsEmpty returns true if returned applications list is empty.
|
||||
func (a AppsList) IsEmpty() bool {
|
||||
return len(a) == 0
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
package models
|
||||
package schema
|
||||
|
||||
// Metric is a generic metric structure.
|
||||
// Metric is a generic metric structure. Used in HTTP responses and data storage.
|
||||
type Metric struct {
|
||||
// BaseName is a metric's base name, used for constructing name.
|
||||
BaseName string
|
9
pkg/schema/metrics.go
Normal file
9
pkg/schema/metrics.go
Normal file
@ -0,0 +1,9 @@
|
||||
package schema
|
||||
|
||||
// Metrics is a metrics collection response.
|
||||
type Metrics []*Metric
|
||||
|
||||
// IsEmpty returns true if returned applications list is empty.
|
||||
func (m Metrics) IsEmpty() bool {
|
||||
return len(m) == 0
|
||||
}
|
Loading…
Reference in New Issue
Block a user