Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add prometheus metrics support #37

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/cli/bulk.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ func processBulkEntry(entry string) (*BulkResponseItem, error) {
return item, nil
}

client, err := svrquery.NewClient(querySection, addressSection, options...)
client, err := svrquery.NewUDPClient(querySection, addressSection, options...)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will fix this

if err != nil {
item.Error = fmt.Sprintf("create client: %s", err)
return item, nil
Expand Down
2 changes: 1 addition & 1 deletion cmd/cli/bulk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ func TestCreateClient(t *testing.T) {
// Validate key setting
if tc.expKey != "" {
require.Len(t, options, 1)
c := svrquery.Client{}
c := svrquery.UDPClient{}
require.NoError(t, options[0](&c))
require.Equal(t, tc.expKey, c.Key())
}
Expand Down
89 changes: 38 additions & 51 deletions cmd/cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,17 @@ import (
"encoding/json"
"flag"
"fmt"
"log"
"net"
"os"
"time"

"github.com/multiplay/go-svrquery/lib/svrquery"
"github.com/multiplay/go-svrquery/lib/svrquery/protocol"
"github.com/multiplay/go-svrquery/lib/svrsample"
"github.com/multiplay/go-svrquery/lib/svrsample/common"
"log"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Import ordering here is wrong

"os"
)

func main() {
clientAddr := flag.String("addr", "", "Address to connect to e.g. 127.0.0.1:12345")
proto := flag.String("proto", "", "Protocol e.g. sqp, tf2e, tf2e-v7, tf2e-v8")
proto := flag.String("proto", "", "Protocol e.g. sqp, tf2e, tf2e-v7, tf2e-v8, prom")
key := flag.String("key", "", "Key to use to authenticate")
file := flag.String("file", "", "Bulk file to execute to get basic server information")
serverAddr := flag.String("server", "", "Address to start server e.g. 127.0.0.1:12121, :23232")
Expand All @@ -39,12 +37,12 @@ func main() {
switch {
case *serverAddr != "":
if *proto == "" {
bail(l, "No protocol provided in client mode")
bail(l, "No protocol provided in server mode")
}
serverMode(l, *proto, *serverAddr)
case *clientAddr != "":
if *proto == "" {
bail(l, "Protocol required in server mode")
bail(l, "Protocol required in client mode")
}
queryMode(l, *proto, *clientAddr, *key)
default:
Expand All @@ -53,24 +51,34 @@ func main() {
}

func queryMode(l *log.Logger, proto, address, key string) {
if err := query(proto, address, key); err != nil {
client, err := getClient(proto, address, key)
if err != nil {
l.Fatal(err)
}
}
defer client.Close()

func query(proto, address, key string) error {
options := make([]svrquery.Option, 0)
if key != "" {
options = append(options, svrquery.WithKey(key))
if err := query(client); err != nil {
l.Fatal(err)
}
}

c, err := svrquery.NewClient(proto, address, options...)
if err != nil {
return err
func getClient(proto, address, key string) (protocol.Client, error) {
switch proto {
case "sqp":
options := make([]svrquery.Option, 0)
if key != "" {
options = append(options, svrquery.WithKey(key))
}
return svrquery.NewUDPClient(proto, address, options...)
case "prom":
return svrquery.NewHTTPClient(proto, address)
default:
return nil, fmt.Errorf("protocol %s not supported", proto)
}
defer c.Close()
}

r, err := c.Query()
func query(client protocol.Client) error {
r, err := client.Query()
if err != nil {
return err
}
Expand All @@ -91,6 +99,12 @@ func serverMode(l *log.Logger, proto, serverAddr string) {

func server(l *log.Logger, proto, address string) error {
l.Printf("Starting sample server using protocol %s on %s", proto, address)

transport, err := svrsample.GetTransport(proto, address)
if err != nil {
return fmt.Errorf("create transport: %w", err)
}

responder, err := svrsample.GetResponder(proto, common.QueryState{
CurrentPlayers: 1,
MaxPlayers: 2,
Expand All @@ -100,43 +114,16 @@ func server(l *log.Logger, proto, address string) error {
Port: 1000,
})
if err != nil {
return err
return fmt.Errorf("create responder: %w", err)
}

addr, err := net.ResolveUDPAddr("udp4", address)
// this function will block until the transport is closed
err = transport.Start(responder)
if err != nil {
return err
}

conn, err := net.ListenUDP("udp4", addr)
if err != nil {
return err
}

for {
buf := make([]byte, 16)
_, to, err := conn.ReadFromUDP(buf)
if err != nil {
l.Println("read from udp", err)
continue
}

resp, err := responder.Respond(to.String(), buf)
if err != nil {
l.Println("error responding to query", err)
continue
}

if err = conn.SetWriteDeadline(time.Now().Add(1 * time.Second)); err != nil {
l.Println("error setting write deadline")
continue
}

if _, err = conn.WriteTo(resp, to); err != nil {
l.Println("error writing response")
}
return fmt.Errorf("transport error")
}

return nil
}

func bail(l *log.Logger, msg string) {
Expand Down
17 changes: 15 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,24 @@ module github.com/multiplay/go-svrquery

go 1.20

require github.com/stretchr/testify v1.7.0
require (
github.com/prometheus/client_golang v1.15.1
github.com/prometheus/common v0.44.0
github.com/stretchr/testify v1.7.0
)

require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect
github.com/prometheus/procfs v0.9.0 // indirect
github.com/stretchr/objx v0.3.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
golang.org/x/sys v0.8.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
39 changes: 36 additions & 3 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,16 +1,49 @@
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI=
github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk=
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 h1:v7DLqVdK4VrYkVD5diGdl4sxJurKJEMnODWRJlxV9oM=
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.3.0 h1:NGXK3lHquSN08v5vWalVI/L8XU9hdzE/G6xsrze47As=
github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
84 changes: 67 additions & 17 deletions lib/svrquery/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,16 @@ var (
DefaultNetwork = "udp"
)

// Option represents a Client option.
type Option func(*Client) error
var (
_ protocol.Client = (*UDPClient)(nil)
_ protocol.Client = (*HTTPClient)(nil)
)

// Client provides the ability to query a server.
type Client struct {
// Option represents a UDPClient option.
type Option func(*UDPClient) error

// UDPClient provides the ability to query a server.
type UDPClient struct {
protocol string
network string
addr string
Expand All @@ -34,28 +39,28 @@ type Client struct {

// WithKey sets the key used for request by for the client.
func WithKey(key string) Option {
return func(c *Client) error {
return func(c *UDPClient) error {
c.key = key
return nil
}
}

// WithTimeout sets the read and write timeout for the client.
func WithTimeout(t time.Duration) Option {
return func(c *Client) error {
return func(c *UDPClient) error {
c.timeout = t
return nil
}
}

// NewClient creates a new client that talks to addr.
func NewClient(proto, addr string, options ...Option) (*Client, error) {
// NewUDPClient creates a new client that talks to addr.
func NewUDPClient(proto, addr string, options ...Option) (*UDPClient, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is going to break users of the library as NewClient is publicly exported and the main way people will interact with the client.

Can we either have the struct do both or abstract it internally?

f, err := protocol.Get(proto)
if err != nil {
return nil, err
}

c := &Client{
c := &UDPClient{
protocol: proto,
addr: addr,
network: DefaultNetwork,
Expand All @@ -81,7 +86,7 @@ func NewClient(proto, addr string, options ...Option) (*Client, error) {
}

// Write implements io.Writer.
func (c *Client) Write(b []byte) (int, error) {
func (c *UDPClient) Write(b []byte) (int, error) {
if err := c.c.SetWriteDeadline(time.Now().Add(c.timeout)); err != nil {
return 0, err
}
Expand All @@ -90,7 +95,7 @@ func (c *Client) Write(b []byte) (int, error) {
}

// Read implements io.Reader.
func (c *Client) Read(b []byte) (int, error) {
func (c *UDPClient) Read(b []byte) (int, error) {
if err := c.c.SetReadDeadline(time.Now().Add(c.timeout)); err != nil {
return 0, err
}
Expand All @@ -107,21 +112,66 @@ func (c *Client) Read(b []byte) (int, error) {
}

// Close implements io.Closer.
func (c *Client) Close() error {
func (c *UDPClient) Close() error {
return c.c.Close()
}

// Key implements protocol.Client.
func (c *Client) Key() string {
// Key implements protocol.UDPClient.
func (c *UDPClient) Key() string {
return c.key
}

// Address implements protocol.Client.
func (c *Client) Address() string {
// Address implements protocol.UDPClient.
func (c *UDPClient) Address() string {
return c.addr
}

// Protocol returns the protocol of the client.
func (c *Client) Protocol() string {
func (c *UDPClient) Protocol() string {
return c.protocol
}

type HTTPClient struct {
protocol.Queryer
address string
}

func NewHTTPClient(proto, address string) (*HTTPClient, error) {
queryerCreator, err := protocol.Get(proto)
if err != nil {
return nil, err
}
client := &HTTPClient{address: address}
client.Queryer = queryerCreator(client)

return client, nil
}

func (c *HTTPClient) Read(p []byte) (n int, err error) {
//TODO implement me
panic("implement me")
}

func (c *HTTPClient) Write(p []byte) (n int, err error) {
//TODO implement me
panic("implement me")
}

func (c *HTTPClient) Query() (protocol.Responser, error) {
return c.Queryer.Query()
}

func (c *HTTPClient) Key() string {
//TODO implement me
panic("implement me")
}

func (c *HTTPClient) Address() string {
return c.address
}

// Close implements io.Closer.
func (c *HTTPClient) Close() error {
// no-op
return nil
}
Loading
Loading