Initial commit.
This commit is contained in:
8
internal/common/const.go
Normal file
8
internal/common/const.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package common
|
||||
|
||||
var (
|
||||
Branch string
|
||||
Build string
|
||||
CommitHash string
|
||||
Version string
|
||||
)
|
11
internal/common/context_keys.go
Normal file
11
internal/common/context_keys.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package common
|
||||
|
||||
// ContextKey is a type of context.Context keys.
|
||||
type ContextKey string
|
||||
|
||||
const (
|
||||
// ContextKeyApplication specifies that returned value is a name of application.
|
||||
ContextKeyApplication ContextKey = "applicationName"
|
||||
// ContextKeyMetric specifies that returned value is a name of metric of application.
|
||||
ContextKeyMetric ContextKey = "metricName"
|
||||
)
|
6
internal/common/http_handler.go
Normal file
6
internal/common/http_handler.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package common
|
||||
|
||||
import "context"
|
||||
|
||||
// HTTPHandlerFunc describes signature of HTTP requests handling function.
|
||||
type HTTPHandlerFunc func(context.Context) string
|
93
internal/configuration/config.go
Normal file
93
internal/configuration/config.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrConfigurationFileDoesNotExist = errors.New("configuration file does not exist")
|
||||
ErrConfigurationFilePathUndefined = errors.New("configuration file path wasn't provided")
|
||||
)
|
||||
|
||||
// Config is an application's configuration.
|
||||
type Config struct {
|
||||
configPath string
|
||||
// Applications describes configuration for remote application's endpoints.
|
||||
// Key is an application's name.
|
||||
Applications map[string]struct {
|
||||
// Endpoint is a remote application endpoint which should give us metrics
|
||||
// in Prometheus format.
|
||||
Endpoint 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.
|
||||
func NewConfig() *Config {
|
||||
c := &Config{}
|
||||
c.initialize()
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// Initializes configuration.
|
||||
func (c *Config) initialize() {
|
||||
flag.StringVar(&c.configPath, "config", "", "Configuration file path.")
|
||||
}
|
||||
|
||||
// Parse parses configuration.
|
||||
func (c *Config) Parse() error {
|
||||
if c.configPath == "" {
|
||||
return ErrConfigurationFilePathUndefined
|
||||
}
|
||||
|
||||
if strings.HasPrefix(c.configPath, "~") {
|
||||
userDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", "file path normalization: getting user's home directory", err)
|
||||
}
|
||||
|
||||
c.configPath = strings.Replace(c.configPath, "~", userDir, 1)
|
||||
}
|
||||
|
||||
cfgPath, err := filepath.Abs(c.configPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", "file path normalization: getting absolute path", err)
|
||||
}
|
||||
|
||||
c.configPath = cfgPath
|
||||
|
||||
if c.configPath == "" {
|
||||
return fmt.Errorf("%s: %w", "file path normalization", ErrConfigurationFilePathUndefined)
|
||||
}
|
||||
|
||||
fileData, err := ioutil.ReadFile(c.configPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", "configuration file read", err)
|
||||
}
|
||||
|
||||
err = yaml.Unmarshal(fileData, c)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", "configuration file parsing", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
25
internal/datastore/application_store.go
Normal file
25
internal/datastore/application_store.go
Normal file
@@ -0,0 +1,25 @@
|
||||
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() {}
|
56
internal/datastore/datastore.go
Normal file
56
internal/datastore/datastore.go
Normal file
@@ -0,0 +1,56 @@
|
||||
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()
|
||||
}
|
||||
}
|
20
internal/httpserver/handler.go
Normal file
20
internal/httpserver/handler.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package httpserver
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"go.dev.pztrn.name/metricator/internal/common"
|
||||
)
|
||||
|
||||
// HTTP requests handler.
|
||||
type handler struct {
|
||||
handler common.HTTPHandlerFunc
|
||||
}
|
||||
|
||||
// Registers request's handler.
|
||||
func (h *handler) register(hndl common.HTTPHandlerFunc) {
|
||||
h.handler = hndl
|
||||
}
|
||||
|
||||
// ServeHTTP handles every HTTP request.
|
||||
func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {}
|
79
internal/httpserver/httpserver.go
Normal file
79
internal/httpserver/httpserver.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package httpserver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.dev.pztrn.name/metricator/internal/configuration"
|
||||
)
|
||||
|
||||
// HTTPServer is a controlling structure for HTTP server.
|
||||
type HTTPServer struct {
|
||||
config *configuration.Config
|
||||
ctx context.Context
|
||||
doneChan chan struct{}
|
||||
handler *handler
|
||||
server *http.Server
|
||||
}
|
||||
|
||||
func NewHTTPServer(ctx context.Context, cfg *configuration.Config) (*HTTPServer, chan struct{}) {
|
||||
h := &HTTPServer{
|
||||
config: cfg,
|
||||
ctx: ctx,
|
||||
doneChan: make(chan struct{}),
|
||||
}
|
||||
h.initialize()
|
||||
|
||||
return h, h.doneChan
|
||||
}
|
||||
|
||||
// Returns request's context based on main context of application.
|
||||
// Basically it returns main context and does nothing more.
|
||||
func (h *HTTPServer) getRequestContext(_ net.Listener) context.Context {
|
||||
return h.ctx
|
||||
}
|
||||
|
||||
// Initializes handler and HTTP server structure.
|
||||
func (h *HTTPServer) initialize() {
|
||||
h.handler = &handler{}
|
||||
h.server = &http.Server{
|
||||
// ToDo: make it all configurable.
|
||||
Addr: ":34421",
|
||||
BaseContext: h.getRequestContext,
|
||||
Handler: h.handler,
|
||||
ReadTimeout: time.Second * 10,
|
||||
WriteTimeout: time.Second * 10,
|
||||
MaxHeaderBytes: 1 << 20,
|
||||
}
|
||||
}
|
||||
|
||||
// Start starts HTTP server in another goroutine and one more goroutine which
|
||||
// is listening to main context's Cancel() call to stop HTTP server.
|
||||
func (h *HTTPServer) Start() {
|
||||
go func() {
|
||||
err := h.server.ListenAndServe()
|
||||
if err != nil {
|
||||
if !strings.Contains(err.Error(), "Server closed") {
|
||||
log.Println("HTTP server failed to listen:", err.Error())
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
<-h.ctx.Done()
|
||||
log.Println("Shutting down HTTP server")
|
||||
|
||||
err := h.server.Shutdown(h.ctx)
|
||||
if err != nil && !strings.Contains(err.Error(), "context canceled") {
|
||||
log.Println("Failed to stop HTTP server:", err.Error())
|
||||
}
|
||||
|
||||
log.Println("HTTP server stopped")
|
||||
|
||||
h.doneChan <- struct{}{}
|
||||
}()
|
||||
}
|
Reference in New Issue
Block a user