Skip to content

Commit

Permalink
added how to export metrics to prometheus docs
Browse files Browse the repository at this point in the history
  • Loading branch information
kuzxnia committed Feb 17, 2024
1 parent 3863a88 commit 59bd07a
Show file tree
Hide file tree
Showing 11 changed files with 446 additions and 183 deletions.
36 changes: 33 additions & 3 deletions cli/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import (
"context"

"github.com/kuzxnia/loadbot/lbot"
"github.com/samber/lo"
)

// tutaj nie powinno wchodzić proto
func StartAgent(context context.Context, stdin bool, cmdPort string, configFile string) (err error) {
func StartAgent(context context.Context, config *lbot.AgentRequest, stdin bool, configFile string) (err error) {
var requestConfig *lbot.ConfigRequest

if stdin {
Expand All @@ -24,9 +25,38 @@ func StartAgent(context context.Context, stdin bool, cmdPort string, configFile
}
}

if cmdPort != "" {
requestConfig.AgentPort = cmdPort
if lo.IsNil(requestConfig) {
requestConfig = &lbot.ConfigRequest{}
}
if lo.IsNil(requestConfig.Agent) {
requestConfig.Agent = &lbot.AgentRequest{}
}
if lo.IsEmpty(requestConfig.Agent.MetricsExportIntervalSeconds) {
requestConfig.Agent.MetricsExportIntervalSeconds = 10
}
if lo.IsEmpty(requestConfig.Agent.Port) {
requestConfig.Agent.Port = "1234"
}
if lo.IsEmpty(requestConfig.Agent.MetricsExportPort) {
requestConfig.Agent.MetricsExportPort = "9090"
}

if lo.IsNotEmpty(config.Name) {
requestConfig.Agent.Name = config.Name
}
if lo.IsNotEmpty(config.Port) {
requestConfig.Agent.Port = config.Port
}
if lo.IsNotEmpty(config.MetricsExportUrl) {
requestConfig.Agent.MetricsExportUrl = config.MetricsExportUrl
}
if lo.IsNotEmpty(config.MetricsExportIntervalSeconds) {
requestConfig.Agent.MetricsExportIntervalSeconds = config.MetricsExportIntervalSeconds
}
if lo.IsNotEmpty(config.MetricsExportPort) {
requestConfig.Agent.MetricsExportPort = config.MetricsExportPort
}

agent := lbot.NewAgent(context)
if requestConfig != nil {
agent.ApplyConfig(requestConfig)
Expand Down
32 changes: 26 additions & 6 deletions cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,11 @@ const (
CommandStartAgent = "start-agent"

// agent args
Port = "port"
AgentName = "name"
AgentPort = "port"
MetricsExportUrl = "metrics_export_url"
MetricsExportIntervalSeconds = "metrics_export_interval_seconds"
MetricsExportPort = "metrics_export_port"
)

var AgentGroup = cobra.Group{
Expand All @@ -209,24 +213,40 @@ var AgentGroup = cobra.Group{
func provideAgentCommands() []*cobra.Command {
agentCommand := cobra.Command{
Use: CommandStartAgent,
Aliases: []string{"a"},
Short: "Start loadbot-agent",
RunE: func(cmd *cobra.Command, args []string) (err error) {
flags := cmd.Flags()

name, _ := flags.GetString(AgentName)
port, _ := flags.GetString(AgentPort)
metricsExportUrl, _ := flags.GetString(MetricsExportUrl)
metricsExportIntervalSeconds, _ := flags.GetUint64(MetricsExportIntervalSeconds)
metricsExportPort, _ := flags.GetString(MetricsExportPort)

agentConfig := &lbot.AgentRequest{
Name: name,
Port: port,
MetricsExportUrl: metricsExportUrl,
MetricsExportIntervalSeconds: metricsExportIntervalSeconds,
MetricsExportPort: metricsExportPort,
}

configFile, _ := flags.GetString(ConfigFile)
stdin, _ := flags.GetBool(StdIn)
port, _ := flags.GetString(Port)

return StartAgent(cmd.Context(), stdin, port, configFile)
return StartAgent(cmd.Context(), agentConfig, stdin, configFile)
},
GroupID: AgentGroup.ID,
}

flags := agentCommand.Flags()
flags.StringP(AgentName, "n", "", "Agent name")
flags.StringP(ConfigFile, "f", "", "Config file for loadbot-agent")
flags.StringP(Port, "p", "1234", "Agent port")
flags.Bool(StdIn, false, "get workload configuration from stdin")
flags.Bool(StdIn, false, "Provide configuration from stdin.")
flags.StringP(AgentPort, "p", "", "Agent port")
flags.String(MetricsExportUrl, "", "Prometheus export url used for pushing metrics")
flags.Uint64(MetricsExportIntervalSeconds, 0, "Prometheus export push interval")
flags.String(MetricsExportPort, "", "Expose metrics on port instead pushing to prometheus")

return []*cobra.Command{&agentCommand}
}
Expand Down
52 changes: 52 additions & 0 deletions docs/setup/agent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Agent configuration

The agent configuration allows you to customize various parameters for the behavior of the agent. Below is an example file illustrating the structure and available fields for configuring the agent:
> Note:
> You can configure the agent both from the command line interface and from a configuration file.
### CLI Usage
To start the loadbot agent, use the following command:

Usage:
loadbot start-agent [flags]

Flags:
-f, --config-file string Config file for loadbot-agent
-h, --help help for start-agent
--metrics_export_interval_seconds uint Prometheus export push interval
--metrics_export_port string Expose metrics on port instead pushing to prometheus
--metrics_export_url string Prometheus export url used for pushing metrics
-n, --name string Agent name
-p, --port string Agent port
--stdin Provide configuration from stdin.

> Note:
> Configurations specified from the command line interface will overwrite those from the configuration file.
### Configuration file

In the configuration file, you're able to configure both workloads and the agent, whereas via the command-line interface, you can solely configure the agent.

```json
{
"agent": {
"name": "mongo 6.0.8 workload",
"port": "1234",
"metrics_export_url": "http://victoria-metrics:8428/api/v1/import/prometheus",
"metrics_export_interval_seconds": 10,
"metrics_export_port": "9090",
}
}
```

> Note:
> The agent configuration is only applied when the agent is started. It cannot be changed at runtime using commands such as `loadbot config`.
### Agent Fields
- **name** (string, optional): Specifies the name of the agent. This field is used as a label in the metrics exporter. Useful when exporting metrics from multiple agents.
- **port** (string, optional): Specifies the port on which the agent listens for incoming connections.
- **metrics_export_url** (string, optional): If set, metrics will be pushed to the specified endpoint in the Prometheus standard format.
- **metrics_export_interval_seconds** (integer, optional): Specifies the interval (in seconds) at which metrics are pushed to the export endpoint.
- **metrics_export_port** (string, optional): If set, metrics will be accessible on the provided port.


61 changes: 61 additions & 0 deletions docs/setup/metrics.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
The metrics exporting feature enables you to monitor and analyze various metrics related to your workload. Below, we detail the key aspects of this feature:

### Library Dependency
Our project utilizes the lightweight library [github.com/VictoriaMetrics/metrics](https://github.com/VictoriaMetrics/metrics) as an alternative to [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang). This library facilitates efficient handling and exporting of metrics for monitoring purposes.

### Exporting Strategies
You have the flexibility to choose between two strategies for exporting metrics: pull or push.

#### Push Strategy:

With this strategy, the tool pushes metrics to the provided URL at the specified interval.

Configure the [agent](/loadbot/setup/agent/) using the following flags:

- **metrics_export_url**: Specifies the URL where metrics will be pushed.
- **metrics_export_interval_seconds**: Defines the interval (in seconds) for pushing metrics.

#### Pull Strategy:
With this strategy, external app pull metrics from us and put them into Prometheus.

Configure the [agent](/loadbot/setup/agent/) using the following flags:
- **metrics_export_url**: Configure the agent to expose an HTTP server on the provided port.


### Metrics Selection

The metrics available for monitoring encompass both system-level metrics and custom metrics related to your workloads. Here are some popular metrics from a Go application's perspective:

System Metrics:

- `go_goroutines`
- `go_threads`
- `go_memstats_alloc_bytes`
- `go_memstats_heap_inuse_bytes`
- `go_memstats_alloc_bytes_total`
- `process_resident_memory_bytes`

Custom Workload Metrics:

- `requests_total`
- `requests_error`
- `requests_duration_seconds (histogram)`

#### Labels for Querying
When querying metrics, you can utilize labels to specify job-related information:

```
{job="job_name_here", job_uuid="auto_generate_uuid", agent="agent_name_here"}
```

Example query:
```
requests_total{job="workload 1", agent="186.12.9.19"}
```

The `job_uuid` label distinguishes between different job runs/attempts, allowing you to track and analyze performance across multiple executions of the same job. Additionally, all metrics are labeled with the `name of the agent`, enabling you to differentiate metrics coming from different agents.

### Additional Resources
Metrics have been extracted from VictoriaMetrics sources. For more in-depth information about VictoriaMetrics, you can refer to the following article: [VictoriaMetrics: Creating the Best Remote Storage for Prometheus](https://faun.pub/victoriametrics-creating-the-best-remote-storage-for-prometheus-5d92d66787ac).

You can also refer to an excellent article discussing Prometheus metrics in Go, which can provide further insights (Prometheus Go Metrics)[https://povilasv.me/prometheus-go-metrics/].
2 changes: 1 addition & 1 deletion docs/setup/other.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Other features

- JSON standardization - comments and trailing commas support ex.
- config file JSON standardization - comments and trailing commas support ex.
```json
{
"jobs": [
Expand Down
43 changes: 22 additions & 21 deletions lbot/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (

"github.com/VictoriaMetrics/metrics"
"github.com/kuzxnia/loadbot/lbot/proto"
"github.com/samber/lo"
log "github.com/sirupsen/logrus"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
Expand Down Expand Up @@ -47,51 +48,51 @@ func (a *Agent) Listen() error {
signal.Notify(
stopSignal, os.Interrupt, syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM,
)

log.Info("waiting for new config version")

address := "0.0.0.0:" + a.lbot.config.AgentPort
tcpListener, err := net.Listen("tcp", address)
if err != nil {
log.Fatal("listen error:", err)
return err
}
defer tcpListener.Close()

log.Info("Started lbot-agent on " + address)
address := "0.0.0.0:" + a.lbot.config.Agent.Port

defer func() {
log.Info("Stopped lbot-agent started on " + address)
a.grpcServer.GracefulStop()
}()

go func() {
log.Info("Started lbot-agent on " + address)
tcpListener, err := net.Listen("tcp", address)
if err != nil {
log.Fatal("listen error:", err)
panic(err)
}
if err := a.grpcServer.Serve(tcpListener); err != nil {
log.Fatalf("failed to serve: %s", err)
}
}()

if a.lbot.config.MetricsExportPort != "" {
if a.lbot.config.Agent.MetricsExportPort != "" {
http.HandleFunc("/metrics", func(w http.ResponseWriter, req *http.Request) {
metrics.WritePrometheus(w, true)
})
go func() {
log.Infof("Started metrics exporter on :%s/metrics", a.lbot.config.MetricsExportPort)
http.ListenAndServe(":"+a.lbot.config.MetricsExportPort, nil)
log.Infof("Started metrics exporter on :%s/metrics", a.lbot.config.Agent.MetricsExportPort)
http.ListenAndServe(":"+a.lbot.config.Agent.MetricsExportPort, nil)
}()
} else if a.lbot.config.MetricsExportUrl != "" {
log.Info("Started exporting metrics :", a.lbot.config.MetricsExportPort)
metricsLabels := fmt.Sprintf(`instance="%s"`, a.lbot.config.AgentName)
} else if a.lbot.config.Agent.MetricsExportUrl != "" {
log.Info("Started exporting metrics :", a.lbot.config.Agent.MetricsExportPort)

metricsLabels := lo.If(
a.lbot.config.Agent.Name != "",
fmt.Sprintf(`instance="%s"`, a.lbot.config.Agent.Name),
).Else("")

metrics.InitPush(
a.lbot.config.MetricsExportUrl,
a.lbot.config.Agent.MetricsExportUrl,
10*time.Second, // todo: add interval param
metricsLabels,
true,
)
}

<-stopSignal
fmt.Println("Received stop signal. Exiting.")
// a.grpcServer.GracefulStop()
fmt.Println("\nReceived stop signal. Exiting.")

// is this needed?
_, cancel := context.WithCancel(a.ctx)
Expand Down
59 changes: 35 additions & 24 deletions lbot/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,17 @@ import (

func NewConfig(request *ConfigRequest) *config.Config {
cfg := &config.Config{
ConnectionString: request.ConnectionString,
AgentName: request.AgentName,
AgentPort: request.AgentPort,
MetricsExportUrl: request.MetricsExportUrl,
MetricsExportPort: request.MetricsExportPort,
Jobs: make([]*config.Job, len(request.Jobs)),
Schemas: make([]*config.Schema, len(request.Schemas)),
Debug: request.Debug,
ConnectionString: request.ConnectionString,
Agent: &config.Agent{
Name: request.Agent.Name,
Port: request.Agent.Port,
MetricsExportUrl: request.Agent.MetricsExportUrl,
MetricsExportIntervalSeconds: request.Agent.MetricsExportIntervalSeconds,
MetricsExportPort: request.Agent.MetricsExportPort,
},
Jobs: make([]*config.Job, len(request.Jobs)),
Schemas: make([]*config.Schema, len(request.Schemas)),
Debug: request.Debug,
}
for i, job := range request.Jobs {
cfg.Jobs[i] = &config.Job{
Expand Down Expand Up @@ -58,14 +61,17 @@ func NewConfig(request *ConfigRequest) *config.Config {

func NewConfigFromProtoConfigRequest(request *proto.ConfigRequest) *config.Config {
cfg := &config.Config{
ConnectionString: request.ConnectionString,
AgentName: request.AgentName,
AgentPort: request.AgentPort,
MetricsExportUrl: request.MetricsExportUrl,
MetricsExportPort: request.MetricsExportPort,
Jobs: make([]*config.Job, len(request.Jobs)),
Schemas: make([]*config.Schema, len(request.Schemas)),
Debug: request.Debug,
ConnectionString: request.ConnectionString,
Agent: &config.Agent{
Name: request.Agent.Name,
Port: request.Agent.Port,
MetricsExportUrl: request.Agent.MetricsExportUrl,
MetricsExportIntervalSeconds: request.Agent.MetricsExportIntervalSeconds,
MetricsExportPort: request.Agent.MetricsExportPort,
},
Jobs: make([]*config.Job, len(request.Jobs)),
Schemas: make([]*config.Schema, len(request.Schemas)),
Debug: request.Debug,
}
for i, job := range request.Jobs {
duration, _ := time.ParseDuration(job.Duration)
Expand Down Expand Up @@ -101,14 +107,19 @@ func NewConfigFromProtoConfigRequest(request *proto.ConfigRequest) *config.Confi

// todo: should be pointers
type ConfigRequest struct {
ConnectionString string `json:"connection_string"`
AgentName string `json:"agent_name"`
AgentPort string `json:"agent_port"`
MetricsExportUrl string `json:"metrics_export_url"`
MetricsExportPort string `json:"metrics_export_port"`
Jobs []*JobRequest `json:"jobs"`
Schemas []*SchemaRequest `json:"schemas"`
Debug bool `json:"debug"`
ConnectionString string `json:"connection_string"`
Agent *AgentRequest `json:"agent"`
Jobs []*JobRequest `json:"jobs"`
Schemas []*SchemaRequest `json:"schemas"`
Debug bool `json:"debug"`
}

type AgentRequest struct {
Name string `json:"name"`
Port string `json:"port"`
MetricsExportUrl string `json:"metrics_export_url"`
MetricsExportIntervalSeconds uint64 `json:"metrics_export_interval_seconds"`
MetricsExportPort string `json:"metrics_export_port"`
}

type JobRequest struct {
Expand Down
Loading

0 comments on commit 59bd07a

Please sign in to comment.