-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3 from hikhvar/add-channel-exporter
Add channel info collector
- Loading branch information
Showing
8 changed files
with
286 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
package collector | ||
|
||
import ( | ||
"github.com/hikhvar/ts3exporter/pkg/serverquery" | ||
"github.com/prometheus/client_golang/prometheus" | ||
) | ||
|
||
const channelSubsystem = "channel" | ||
|
||
var channelLabels = []string{virtualServerLabel, channelLabel} | ||
|
||
type Channel struct { | ||
channels ChannelInformer | ||
|
||
ClientsOnline *prometheus.Desc | ||
MaxClients *prometheus.Desc | ||
Codec *prometheus.Desc | ||
CodecQuality *prometheus.Desc | ||
LatencyFactor *prometheus.Desc | ||
Unencrypted *prometheus.Desc | ||
Permanent *prometheus.Desc | ||
SemiPermanent *prometheus.Desc | ||
Default *prometheus.Desc | ||
Password *prometheus.Desc | ||
} | ||
|
||
// A ChannelInformer knows how to collect the data from all the channels on the monitoring target | ||
type ChannelInformer interface { | ||
Refresh() error | ||
All() []serverquery.Channel | ||
} | ||
|
||
func NewChannel(ci ChannelInformer) *Channel { | ||
return &Channel{ | ||
channels: ci, | ||
ClientsOnline: prometheus.NewDesc(fqdn(channelSubsystem, "clients_online"), "number of clients currently online", channelLabels, nil), | ||
MaxClients: prometheus.NewDesc(fqdn(channelSubsystem, "max_clients"), "maximal number of clients allowed in this channel", channelLabels, nil), | ||
Codec: prometheus.NewDesc(fqdn(channelSubsystem, "codec"), "numeric number of configured codec for this channel", channelLabels, nil), | ||
CodecQuality: prometheus.NewDesc(fqdn(channelSubsystem, "codec_quality"), "numeric number of codec quality level chosen for this channel", channelLabels, nil), | ||
LatencyFactor: prometheus.NewDesc(fqdn(channelSubsystem, "codec_latency_factor"), "numeric number of codec latency factor chosen for this channel", channelLabels, nil), | ||
Unencrypted: prometheus.NewDesc(fqdn(channelSubsystem, "unencrypted"), "is the channel unencrypted", channelLabels, nil), | ||
Permanent: prometheus.NewDesc(fqdn(channelSubsystem, "permanent"), "is the channel permanent", channelLabels, nil), | ||
SemiPermanent: prometheus.NewDesc(fqdn(channelSubsystem, "semi_permanent"), "is the channel semi permanent", channelLabels, nil), | ||
Default: prometheus.NewDesc(fqdn(channelSubsystem, "default"), "is the channel the default channel", channelLabels, nil), | ||
Password: prometheus.NewDesc(fqdn(channelSubsystem, "password"), "is the channel saved by an password", channelLabels, nil), | ||
} | ||
} | ||
|
||
func (c *Channel) Describe(desc chan<- *prometheus.Desc) { | ||
prometheus.DescribeByCollect(c, desc) | ||
} | ||
|
||
func (c *Channel) Collect(met chan<- prometheus.Metric) { | ||
for _, ch := range c.channels.All() { | ||
met <- prometheus.MustNewConstMetric(c.ClientsOnline, prometheus.GaugeValue, float64(ch.ClientsOnline), ch.HostingServer.Name, ch.Name) | ||
met <- prometheus.MustNewConstMetric(c.MaxClients, prometheus.GaugeValue, float64(ch.MaxClients), ch.HostingServer.Name, ch.Name) | ||
met <- prometheus.MustNewConstMetric(c.Codec, prometheus.GaugeValue, float64(ch.Codec), ch.HostingServer.Name, ch.Name) | ||
met <- prometheus.MustNewConstMetric(c.CodecQuality, prometheus.GaugeValue, float64(ch.CodecQuality), ch.HostingServer.Name, ch.Name) | ||
met <- prometheus.MustNewConstMetric(c.LatencyFactor, prometheus.GaugeValue, float64(ch.LatencyFactor), ch.HostingServer.Name, ch.Name) | ||
met <- prometheus.MustNewConstMetric(c.Unencrypted, prometheus.GaugeValue, float64(ch.Unencrypted), ch.HostingServer.Name, ch.Name) | ||
met <- prometheus.MustNewConstMetric(c.Permanent, prometheus.GaugeValue, float64(ch.Permanent), ch.HostingServer.Name, ch.Name) | ||
met <- prometheus.MustNewConstMetric(c.SemiPermanent, prometheus.GaugeValue, float64(ch.SemiPermanent), ch.HostingServer.Name, ch.Name) | ||
met <- prometheus.MustNewConstMetric(c.Default, prometheus.GaugeValue, float64(ch.Default), ch.HostingServer.Name, ch.Name) | ||
met <- prometheus.MustNewConstMetric(c.Password, prometheus.GaugeValue, float64(ch.Password), ch.HostingServer.Name, ch.Name) | ||
} | ||
} | ||
|
||
func (c *Channel) Refresh() error { | ||
return c.channels.Refresh() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
package serverquery | ||
|
||
import "fmt" | ||
|
||
type ChannelID int | ||
|
||
type Channel struct { | ||
HostingServer VirtualServer | ||
stale bool | ||
ID ChannelID `sq:"cid"` | ||
PID int `sq:"pid"` | ||
Order int `sq:"channel_order"` | ||
Name string `sq:"channel_name"` | ||
ClientsOnline int `sq:"total_clients"` | ||
MaxClients int `sq:"channel_maxclients"` | ||
Codec int `sq:"channel_codec"` | ||
CodecQuality int `sq:"channel_codec_quality"` | ||
LatencyFactor int `sq:"channel_codec_latency_factor"` | ||
Unencrypted int `sq:"channel_codec_is_unencrypted"` | ||
Permanent int `sq:"channel_flag_permanent"` | ||
SemiPermanent int `sq:"channel_flag_semi_permanent"` | ||
Default int `sq:"channel_flag_default"` | ||
Password int `sq:"channel_flag_password"` | ||
} | ||
|
||
// VServerInventory can return all known VServer | ||
type VServerInventory interface { | ||
All() []VirtualServer | ||
} | ||
|
||
type ChannelView struct { | ||
e Executor | ||
channels map[ChannelID]Channel | ||
vServer VServerInventory | ||
} | ||
|
||
func NewChannelView(e Executor, inventory VServerInventory) *ChannelView { | ||
return &ChannelView{ | ||
e: e, | ||
channels: make(map[ChannelID]Channel), | ||
vServer: inventory, | ||
} | ||
} | ||
|
||
// Refresh refreshes the internal representation of the ChannelView. It changes into all virtual server | ||
// known by the vServer inventory | ||
func (c *ChannelView) Refresh() error { | ||
c.markAllStale() | ||
defer c.deleteAllStale() | ||
for _, vServer := range c.vServer.All() { | ||
err := c.updateAllOnVServer(vServer) | ||
if err != nil { | ||
return fmt.Errorf("failed to update metrics on vServer %s: %w", vServer.Name, err) | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// All returns all known Channels | ||
func (c *ChannelView) All() []Channel { | ||
ret := make([]Channel, 0, len(c.channels)) | ||
for _, ch := range c.channels { | ||
ret = append(ret, ch) | ||
} | ||
return ret | ||
} | ||
|
||
// markAllStale marks all channels stale. They are set during scrape. If the aren't set, they will be deleted | ||
// by deleteAllStale | ||
func (c *ChannelView) markAllStale() { | ||
for id, channel := range c.channels { | ||
channel.stale = true | ||
c.channels[id] = channel | ||
} | ||
} | ||
|
||
// deleteAllStale deletes all stale channels | ||
func (c *ChannelView) deleteAllStale() { | ||
for id, channel := range c.channels { | ||
if channel.stale { | ||
delete(c.channels, id) | ||
} | ||
} | ||
} | ||
|
||
// updateAllOnVServer update all channels on the given virtual server | ||
func (c *ChannelView) updateAllOnVServer(vServer VirtualServer) error { | ||
_, err := c.e.Exec(fmt.Sprintf("use %d", vServer.ID)) | ||
if err != nil { | ||
return fmt.Errorf("failed to use virtual server %d: %w", vServer.ID, err) | ||
} | ||
res, err := c.e.Exec("channellist") | ||
if err != nil { | ||
return fmt.Errorf("failed to list channels: %w", err) | ||
} | ||
for _, r := range res { | ||
for _, i := range r.Items { | ||
var ch Channel | ||
if err := i.ReadInto(&ch); err != nil { | ||
return fmt.Errorf("failed to parse channel from response: %w", err) | ||
} | ||
if err := c.getDetails(&ch); err != nil { | ||
return fmt.Errorf("failed to parse details for channel %d: %w", ch.ID, err) | ||
} | ||
c.channels[ch.ID] = ch | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// getDetails populates the given channels with details | ||
func (c *ChannelView) getDetails(ch *Channel) error { | ||
res, err := c.e.Exec(fmt.Sprintf("channelinfo cid=%d", ch.ID)) | ||
if err != nil { | ||
return fmt.Errorf("failed to run channelinfo command: %w", err) | ||
} | ||
for _, r := range res { | ||
if len(r.Items) != 1 { | ||
return fmt.Errorf("expected exactly one channelinfo response got %d", len(r.Items)) | ||
} | ||
if err = r.Items[0].ReadInto(ch); err != nil { | ||
return fmt.Errorf("failed to parse channel response: %w", err) | ||
} | ||
return nil | ||
} | ||
return fmt.Errorf("reached unreachable code") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package serverquery | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
const channelInput = `pid=10 channel_name=Default\sChannel channel_topic=Default\sChannel\shas\sno\stopic channel_description=This\sis\sthe\sdefault\schannel channel_password channel_codec=4 channel_codec_quality=6 channel_maxclients=-1 channel_maxfamilyclients=-1 channel_order=11 channel_flag_permanent=1 channel_flag_semi_permanent=0 channel_flag_default=1 channel_flag_password=0 channel_codec_latency_factor=1 channel_codec_is_unencrypted=1 channel_security_salt channel_delete_delay=0 channel_unique_identifier=d1eb7624-664a-4809-9b7e-84596d937a6d channel_flag_maxclients_unlimited=1 channel_flag_maxfamilyclients_unlimited=1 channel_flag_maxfamilyclients_inherited=0 channel_filepath=files\/virtualserver_1\/channel_1 channel_needed_talk_power=0 channel_forced_silence=0 channel_name_phonetic channel_icon_id=0 channel_banner_gfx_url channel_banner_mode=0 seconds_empty=-1` | ||
|
||
func TestChannelStructTags(t *testing.T) { | ||
res, err := Parse(channelInput) | ||
require.Nil(t, err) | ||
require.Len(t, res.Items, 1) | ||
ch := Channel{ | ||
ClientsOnline: 5, | ||
ID: 1, | ||
} | ||
err = res.Items[0].ReadInto(&ch) | ||
require.Nil(t, err) | ||
expected := Channel{ | ||
HostingServer: VirtualServer{}, | ||
stale: false, | ||
ID: 1, | ||
PID: 10, | ||
Order: 11, | ||
Name: "Default Channel", | ||
ClientsOnline: 5, | ||
MaxClients: -1, | ||
Codec: 4, | ||
CodecQuality: 6, | ||
LatencyFactor: 1, | ||
Unencrypted: 1, | ||
Permanent: 1, | ||
SemiPermanent: 0, | ||
Default: 1, | ||
Password: 0, | ||
} | ||
assert.Equal(t, expected, ch) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters