Skip to content

Commit

Permalink
Added prometheus exporter support for Bedrock servers
Browse files Browse the repository at this point in the history
  • Loading branch information
itzg committed Mar 19, 2020
1 parent 98536d2 commit d5b1a83
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 16 deletions.
26 changes: 19 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,18 +89,30 @@ minecraft_status,host=mc.hypixel.net,port=25565,status=success response_time=0.2

### Monitoring a server with Prometheus

When using the `export-for-prometheus` subcommand, mc-monitor will serve a Prometheus exporter that collects the Minecraft server metrics during each scrape of the path `/metrics`.
When using the `export-for-prometheus` subcommand, mc-monitor will serve a Prometheus exporter on port 8080, by default, that collects Minecraft server metrics during each scrape of `/metrics`.

Labels
- `server_host`
- `server_port`
- `server_edition` : `java` or `bedrock`
Metrics
The sub-command accepts the following arguments, which can also be viewed using `--help`:
```
-edition string
The edition of Minecraft server, java or bedrock (env EXPORT_EDITION) (default "java")
-port int
HTTP port where Prometheus metrics are exported (env EXPORT_PORT) (default 8080)
-servers host:port
one or more host:port addresses of servers to monitor, when port is omitted 19132 is used (env EXPORT_SERVERS)
```

The following metrics are exported
- `minecraft_status_healthy`
- `minecraft_status_response_time_seconds`
- `minecraft_status_players_online_count`
- `minecraft_status_players_max_count`

with the labels
- `server_host`
- `server_port`
- `server_edition` : `java` or `bedrock`

An example Docker composition is provided in [examples/mc-monitor-prom](examples/mc-monitor-prom), which was used to grab the following screenshot:

![Prometheus Chart](docs/prometheus_online_count_chart.png)
![Prometheus Chart](docs/prometheus_online_count_chart.png)

16 changes: 7 additions & 9 deletions prom_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,23 @@ package main
import (
"context"
"flag"
"fmt"
"github.com/google/subcommands"
"github.com/itzg/go-flagsfiller"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"go.uber.org/zap"
"log"
"net/http"
"os"
"strconv"
)

const promExportPath = "/metrics"

type exportPrometheusCmd struct {
Servers []string `usage:"one or more [host:port] addresses of servers to monitor"`
ExportPort int `usage:"HTTP port where Prometheus metrics are exported" default:"8080"`
Edition string `usage:"The type of Minecraft server, java or bedrock" default:"java"`
logger *zap.Logger
Servers []string `usage:"one or more [host:port] addresses of servers to monitor, when port is omitted 19132 is used"`
Port int `usage:"HTTP port where Prometheus metrics are exported" default:"8080"`
Edition string `usage:"The edition of Minecraft server, java or bedrock" default:"java"`
logger *zap.Logger
}

func (c *exportPrometheusCmd) Name() string {
Expand All @@ -46,11 +44,11 @@ func (c *exportPrometheusCmd) SetFlags(f *flag.FlagSet) {

func (c *exportPrometheusCmd) Execute(_ context.Context, _ *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
if len(c.Servers) == 0 {
_, _ = fmt.Fprintln(os.Stderr, "requires at least one server")
printUsageError("requires at least one server")
return subcommands.ExitUsageError
}
if !ValidEdition(c.Edition) {
_, _ = fmt.Fprintln(os.Stderr, "invalid edition")
printUsageError("invalid edition")
return subcommands.ExitUsageError
}

Expand All @@ -66,7 +64,7 @@ func (c *exportPrometheusCmd) Execute(_ context.Context, _ *flag.FlagSet, args .
log.Fatal(err)
}

exportAddress := ":" + strconv.Itoa(c.ExportPort)
exportAddress := ":" + strconv.Itoa(c.Port)

logger.Info("exporting metrics for prometheus",
zap.String("address", exportAddress),
Expand Down
43 changes: 43 additions & 0 deletions prom_collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
mcpinger "github.com/Raqbit/mc-pinger"
"github.com/prometheus/client_golang/prometheus"
"go.uber.org/zap"
"net"
"strconv"
"time"
)
Expand Down Expand Up @@ -61,12 +62,20 @@ func newPromCollectors(servers []string, edition ServerEdition, logger *zap.Logg
func createPromCollectors(servers []string, edition ServerEdition, logger *zap.Logger) (collectors []specificPromCollector, err error) {
for _, server := range servers {
switch edition {

case JavaEdition:
host, port, err := SplitHostPort(server, DefaultJavaPort)
if err != nil {
return nil, fmt.Errorf("failed to process server entry '%s': %w", server, err)
}
collectors = append(collectors, newPromJavaCollector(host, port, logger))

case BedrockEdition:
host, port, err := SplitHostPort(server, DefaultBedrockPort)
if err != nil {
return nil, fmt.Errorf("failed to process server entry '%s': %w", server, err)
}
collectors = append(collectors, newPromBedrockCollector(host, port, logger))
}
}
return
Expand Down Expand Up @@ -118,3 +127,37 @@ func (c *promJavaCollector) sendMetric(metrics chan<- prometheus.Metric, desc *p
metrics <- metric
}
}

type promBedrockCollector struct {
host string
port string
logger *zap.Logger
}

func newPromBedrockCollector(host string, port uint16, logger *zap.Logger) *promBedrockCollector {
return &promBedrockCollector{host: host, port: strconv.Itoa(int(port)), logger: logger}
}

func (c *promBedrockCollector) Collect(metrics chan<- prometheus.Metric) {
c.logger.Debug("pinging", zap.String("host", c.host), zap.String("port", c.port))

info, err := PingBedrockServer(net.JoinHostPort(c.host, c.port))
if err != nil {
c.sendMetric(metrics, promDescHealthy, 0)
} else {
c.sendMetric(metrics, promDescResponseTime, info.Rtt.Seconds())
c.sendMetric(metrics, promDescHealthy, 1)
c.sendMetric(metrics, promDescPlayersOnline, float64(info.Players))
c.sendMetric(metrics, promDescPlayersMax, float64(info.MaxPlayers))
}
}

func (c *promBedrockCollector) sendMetric(metrics chan<- prometheus.Metric, desc *prometheus.Desc, value float64) {
metric, err := prometheus.NewConstMetric(desc, prometheus.GaugeValue, value,
c.host, c.port, string(JavaEdition))
if err != nil {
c.logger.Error("failed to build metric", zap.Error(err), zap.String("name", desc.String()))
} else {
metrics <- metric
}
}
9 changes: 9 additions & 0 deletions constants.go → shared.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package main

import (
"fmt"
"os"
)

const (
DefaultJavaPort uint16 = 25565
DefaultBedrockPort uint16 = 19132
Expand All @@ -19,3 +24,7 @@ func ValidEdition(v string) bool {
}
return false
}

func printUsageError(msg string) {
_, _ = fmt.Fprintln(os.Stderr, msg)
}

0 comments on commit d5b1a83

Please sign in to comment.