From cb4d2349c5a0cd9f1b0f6084ff2325fd4bc34218 Mon Sep 17 00:00:00 2001 From: Tyler Stiene Date: Mon, 9 Aug 2021 23:07:27 -0400 Subject: [PATCH 01/13] wip allow a sleepct ticker to pause and be signaled --- README.md | 2 ++ internal/bridge/discord.go | 10 +++---- internal/bridge/mumble.go | 2 +- pkg/sleepct/sleepct.go | 55 +++++++++++++++++++++++++++----------- test/timing_test.go | 37 ++++++++++++++++++++++--- 5 files changed, 80 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 12ca1a1..b9ddf95 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,8 @@ The bot requires the following permissions: * Voice Channel Speak * Voice Channel Use Voice Activity +Permission integer 36768768. + ### Finding Discord CID and GID Discord GID is a unique ID linked to one Discord Server, also called Guild. CID is similarly a unique ID for a Discord Channel. To find these you need to set Discord into developer Mode. diff --git a/internal/bridge/discord.go b/internal/bridge/discord.go index 0ccc666..52c5e30 100644 --- a/internal/bridge/discord.go +++ b/internal/bridge/discord.go @@ -99,7 +99,7 @@ func (dd *DiscordDuplex) discordSendPCM(ctx context.Context, wg *sync.WaitGroup, default: } - sleepTick.SleepNextTarget() + sleepTick.SleepNextTarget(true) if (len(pcm) > 1 && streaming) || (len(pcm) > dd.Bridge.BridgeConfig.DiscordStartStreamingCount && !streaming) { if !streaming { @@ -135,7 +135,7 @@ func (dd *DiscordDuplex) discordSendPCM(ctx context.Context, wg *sync.WaitGroup, // We want to do this after alerting the user of possible short speaking cycles for i := 0; i < 5; i++ { internalSend(opusSilence) - sleepTick.SleepNextTarget() + sleepTick.SleepNextTarget(false) } dd.Bridge.DiscordVoice.Speaking(false) @@ -234,7 +234,7 @@ func (dd *DiscordDuplex) discordReceivePCM(ctx context.Context, wg *sync.WaitGro dd.fromDiscordMap[p.SSRC] = s dd.discordMutex.Unlock() - p.PCM, err = s.decoder.Decode(p.Opus, deltaT*2, false) + p.PCM, err = s.decoder.Decode(p.Opus, deltaT, false) if err != nil { OnError("Error decoding opus data", err) continue @@ -282,7 +282,7 @@ func (dd *DiscordDuplex) fromDiscordMixer(ctx context.Context, wg *sync.WaitGrou default: } - sleepTick.SleepNextTarget() + sleepTick.SleepNextTarget(true) dd.discordMutex.Lock() @@ -354,7 +354,7 @@ func (dd *DiscordDuplex) fromDiscordMixer(ctx context.Context, wg *sync.WaitGrou for i := 0; i < 5; i++ { mumbleTimeoutSend(mumbleSilence) - sleepTick.SleepNextTarget() + sleepTick.SleepNextTarget(false) } toMumbleStreaming = false diff --git a/internal/bridge/mumble.go b/internal/bridge/mumble.go index be3edd8..8c037ec 100644 --- a/internal/bridge/mumble.go +++ b/internal/bridge/mumble.go @@ -61,7 +61,7 @@ func (m MumbleDuplex) fromMumbleMixer(ctx context.Context, wg *sync.WaitGroup, t default: } - sleepTick.SleepNextTarget() + sleepTick.SleepNextTarget(true) mutex.Lock() diff --git a/pkg/sleepct/sleepct.go b/pkg/sleepct/sleepct.go index 8a82d0a..08ae4e8 100644 --- a/pkg/sleepct/sleepct.go +++ b/pkg/sleepct/sleepct.go @@ -2,19 +2,21 @@ package sleepct import ( "fmt" - "sync" "time" ) -// SleepCT - Sleep constant time step crates a sleep based ticker -// designed maintain a sleep/tick interval +// SleepCT - Sleep constant time step crates a sleep based ticker. +// designed maintain a consistent sleep/tick interval. +// The sleeper can be paused waiting to be singaled from another go routine. +// This allows for the pausing of loops that do not have work to complete type SleepCT struct { - sync.Mutex - d time.Duration // duration - t time.Time // last time target + d time.Duration // desired duration between targets + t time.Time // last time target + resume chan bool } func (s *SleepCT) Start(d time.Duration) { + s.resume = make(chan bool, 1) if s.t.IsZero() { s.d = d s.t = time.Now() @@ -23,28 +25,49 @@ func (s *SleepCT) Start(d time.Duration) { } } -func (s *SleepCT) SleepNextTarget() { - s.Lock() - - now := time.Now() - +// Sleep to the next target duration. +// If pause it set to true will sleep the duration and wait to be notified. +// The notification channel will be cleared when the thread wakes. +// SleepNextTarget should not be call more than once concurrently. +func (s *SleepCT) SleepNextTarget(pause bool) { var last time.Time if s.t.IsZero() { fmt.Println("SleepCT reset") - last = now.Add(-s.d) + last = time.Now().Add(-s.d) } else { last = s.t } - // Next Target + // Sleep to Next Target s.t = last.Add(s.d) - d := s.t.Sub(now) + d := time.Until(s.t) time.Sleep(d) - // delta := now.Sub(s.t) + // delta := time.Since(s.t) // fmt.Println("delta", delta, d, time.Since(s.t)) - s.Unlock() + if pause { + // wait until resume + if len(s.resume) == 0 { + <-s.resume + // if we did pause set the last sleep target to now + last = time.Now() + } + } + + // Drain the resume channel + select { + case <-s.resume: + default: + } +} + +// Notify attempts to resume a paused sleeper. +// It is safe to call notify from other processes and as often as desired. +func (s *SleepCT) Notify() { + select { + case s.resume <- true: + } } diff --git a/test/timing_test.go b/test/timing_test.go index d3cfdcb..7985e85 100644 --- a/test/timing_test.go +++ b/test/timing_test.go @@ -13,12 +13,12 @@ import ( "github.com/stieneee/tickerct" ) -const testCount int64 = 10000 +const testCount int64 = 1000 const maxSleepInterval time.Duration = 15 * time.Millisecond const tickerInterval time.Duration = 10 * time.Millisecond const testDuration time.Duration = time.Duration(testCount * 10 * int64(time.Millisecond)) -func testTickerBaseCase(wg *sync.WaitGroup) { +func testTickerBaseCase(wg *sync.WaitGroup, test *testing.T) { wg.Add(1) go func(interval time.Duration) { now := time.Now() @@ -39,7 +39,7 @@ func testTickerBaseCase(wg *sync.WaitGroup) { func TestTickerBaseCase(t *testing.T) { wg := sync.WaitGroup{} - testTickerBaseCase(&wg) + testTickerBaseCase(&wg, t) wg.Wait() } @@ -115,7 +115,7 @@ func testSleepCT(wg *sync.WaitGroup) { if i+1 < testCount { time.Sleep(time.Duration(float64(maxSleepInterval) * rand.Float64())) } - s.SleepNextTarget() + s.SleepNextTarget(false) } fmt.Println("SleepCT (loaded) after", testDuration, "drifts", time.Since(start)-testDuration) wg.Done() @@ -130,6 +130,35 @@ func TestSleepCT(t *testing.T) { wg.Wait() } +func testSleepCTPause(wg *sync.WaitGroup) { + wg.Add(1) + go func(interval time.Duration) { + now := time.Now() + start := now + // start the ticker + s := sleepct.SleepCT{} + s.Start(interval) + var i int64 + for i = 0; i < testCount; i++ { + if i+1 < testCount { + time.Sleep(time.Duration(float64(maxSleepInterval) * rand.Float64())) + } + s.Notify() + s.SleepNextTarget(true) + } + fmt.Println("SleepCT Pause (loaded) after", testDuration, "drifts", time.Since(start)-testDuration) + wg.Done() + }(tickerInterval) +} + +func TestSleepCTPause(t *testing.T) { + wg := sync.WaitGroup{} + + testSleepCTPause(&wg) + + wg.Wait() +} + func TestIdleJitter(t *testing.T) { wg := sync.WaitGroup{} From 6658c8534c91d40c56dde3c6665dcd3dd14609c8 Mon Sep 17 00:00:00 2001 From: Tyler Stiene Date: Mon, 23 Aug 2021 00:00:39 -0400 Subject: [PATCH 02/13] add prometheus metrics add grafana and promethus docker-container add example grafana dashboard sleepct now returns delta time from target --- .gitignore | 3 +- README.md | 49 +- cmd/mumble-discord-bridge/main.go | 9 + ...-compose.yml => docker-compose.bridge.yml} | 3 + example/docker-compose.monitoring.yaml | 69 + example/grafana-dashboard.png | Bin 0 -> 159614 bytes .../provisioning/dashboards/dashboards.yml | 24 + .../dashboards/mumble-discord-bridge.json | 1154 +++++++++++++++++ .../provisioning/datasources/datasource.yml | 11 + example/monitoring/prometheus/prometheus.yml | 12 + go.mod | 2 +- go.sum | 166 ++- internal/bridge/bridge.go | 12 + internal/bridge/discord-handlers.go | 6 +- internal/bridge/discord.go | 18 +- internal/bridge/mumble-handlers.go | 44 +- internal/bridge/mumble.go | 11 +- internal/bridge/prom.go | 141 ++ pkg/sleepct/sleepct.go | 4 +- 19 files changed, 1670 insertions(+), 68 deletions(-) rename example/{docker-compose.yml => docker-compose.bridge.yml} (64%) create mode 100644 example/docker-compose.monitoring.yaml create mode 100644 example/grafana-dashboard.png create mode 100644 example/monitoring/grafana/provisioning/dashboards/dashboards.yml create mode 100644 example/monitoring/grafana/provisioning/dashboards/mumble-discord-bridge.json create mode 100644 example/monitoring/grafana/provisioning/datasources/datasource.yml create mode 100644 example/monitoring/prometheus/prometheus.yml create mode 100644 internal/bridge/prom.go diff --git a/.gitignore b/.gitignore index 03432ec..edc4385 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ dist *.out *.test cert.pem -*.gob \ No newline at end of file +*.gob +docker-compose.yml \ No newline at end of file diff --git a/README.md b/README.md index 12ca1a1..984dbcc 100644 --- a/README.md +++ b/README.md @@ -10,48 +10,6 @@ Several configuration variables must be set for the binary to function correctly All variables can be set using flags or in the environment. The binary will also attempt to load .env file located in the working directory. -```bash -Usage of ./mumble-discord-bridge: - -cpuprofile file - write cpu profile to file - -debug-level int - DEBUG_LEVEL, Discord debug level, optional, (default 1) (default 1) - -discord-cid string - DISCORD_CID, discord cid, required - -discord-command string - DISCORD_COMMAND, Discord command string, env alt DISCORD_COMMAND, optional, (defaults mumble-discord) (default "mumble-discord") - -discord-disable-text - DISCORD_DISABLE_TEXT, disable sending direct messages to discord, (default false) - -discord-gid string - DISCORD_GID, discord gid, required - -discord-token string - DISCORD_TOKEN, discord bot token, required - -mode string - MODE, [constant, manual, auto] determine which mode the bridge starts in, (default constant) (default "constant") - -mumble-address string - MUMBLE_ADDRESS, mumble server address, example example.com, required - -mumble-certificate string - MUMBLE_CERTIFICATE, client certificate to use when connecting to the Mumble server - -mumble-channel string - MUMBLE_CHANNEL, mumble channel to start in, using '/' to separate nested channels, optional - -mumble-disable-text - MUMBLE_DISABLE_TEXT, disable sending text to mumble, (default false) - -mumble-insecure - MUMBLE_INSECURE, mumble insecure, optional - -mumble-password string - MUMBLE_PASSWORD, mumble password, optional - -mumble-port int - MUMBLE_PORT, mumble port, (default 64738) (default 64738) - -mumble-username string - MUMBLE_USERNAME, mumble username, (default: discord) (default "Discord") - -nice - NICE, whether the bridge should automatically try to 'nice' itself, (default false) - -to-discord-buffer int - TO_DISCORD_BUFFER, Jitter buffer from Mumble to Discord to absorb timing issues related to network, OS and hardware quality. (Increments of 10ms) (default 50) - -to-mumble-buffer int - TO_MUMBLE_BUFFER, Jitter buffer from Discord to Mumble to absorb timing issues related to network, OS and hardware quality. (Increments of 10ms) (default 50) -``` - The bridge can be run with the follow modes: ```bash @@ -187,6 +145,13 @@ A default jitter of 50ms should be adequate for most scenarios. A warning will be logged if short burst or audio are seen. A single warning can be ignored multiple warnings in short time spans would suggest the need for a larger jitter buffer. +## Monitoring the Bridge + +The bridge can be started with a Prometheus metrics endpoint enabled. +The example folder contains the a docker-compose file that will spawn the bridge, Prometheus and Grafana configured to serve a single a pre-configured dashboard. + +![Mumble Discord Bridge Grafana Dashboard](example/grafana-dashboard.png "Grafana Dashboard") + ## Known Issues Currently there is an issue opening the discord voice channel. diff --git a/cmd/mumble-discord-bridge/main.go b/cmd/mumble-discord-bridge/main.go index 4e66f16..1758e66 100644 --- a/cmd/mumble-discord-bridge/main.go +++ b/cmd/mumble-discord-bridge/main.go @@ -53,6 +53,8 @@ func main() { mode := flag.String("mode", lookupEnvOrString("MODE", "constant"), "MODE, [constant, manual, auto] determine which mode the bridge starts in, (default constant)") nice := flag.Bool("nice", lookupEnvOrBool("NICE", false), "NICE, whether the bridge should automatically try to 'nice' itself, (default false)") debug := flag.Int("debug-level", lookupEnvOrInt("DEBUG", 1), "DEBUG_LEVEL, Discord debug level, optional, (default 1)") + promEnable := flag.Bool("prometheus-enable", lookupEnvOrBool("PROMETHEUS_ENABLE", false), "PROMETHEUS_ENABLE, Enable prometheus metrics") + promPort := flag.Int("prometheus-port", lookupEnvOrInt("PROMETHEUS_PORT", 9559), "PROMETHEUS_PORT, Prometheus metrics port, optional, (default 9559)") cpuprofile := flag.String("cpuprofile", "", "write cpu profile to `file`") @@ -85,6 +87,10 @@ func main() { } } + if *promEnable { + go bridge.StartPromServer(*promPort) + } + // Optional CPU Profiling if *cpuprofile != "" { f, err := os.Create(*cpuprofile) @@ -136,6 +142,8 @@ func main() { MumbleUsers: make(map[string]bool), } + bridge.PromApplicationStartTime.SetToCurrentTime() + // MUMBLE SETUP Bridge.BridgeConfig.MumbleConfig = gumble.NewConfig() Bridge.BridgeConfig.MumbleConfig.Username = *mumbleUsername @@ -149,6 +157,7 @@ func main() { Bridge.BridgeConfig.MumbleConfig.Attach(gumbleutil.Listener{ Connect: Bridge.MumbleListener.MumbleConnect, UserChange: Bridge.MumbleListener.MumbleUserChange, + // ChannelChange: Bridge.MumbleListener.MumbleChannelChange, }) // DISCORD SETUP diff --git a/example/docker-compose.yml b/example/docker-compose.bridge.yml similarity index 64% rename from example/docker-compose.yml rename to example/docker-compose.bridge.yml index f5e548f..bb0de3e 100644 --- a/example/docker-compose.yml +++ b/example/docker-compose.bridge.yml @@ -1,3 +1,6 @@ +# This a basic docker-compose file to run an instance an instance of Mumble-Discord-Bridge +# docker-compose -f ./docker-compose.yml up -d + version: "3" services: diff --git a/example/docker-compose.monitoring.yaml b/example/docker-compose.monitoring.yaml new file mode 100644 index 0000000..644732b --- /dev/null +++ b/example/docker-compose.monitoring.yaml @@ -0,0 +1,69 @@ +# This docker compose file contians an exmaple of staring Mumble-Discord-Bridge with Prometheus and Grafana +# The monitoring folder is need to provide the nesscary default configs for Promethus and Grafana +# Prometheus port 9090 +# Grafana port 3030 + +version: '3.8' + +volumes: + prometheus_data: {} + grafana_data: {} + +services: + + services: + mumble-discord-bridge: + image: stieneee/mumble-discord-bridge + restart: unless-stopped + networks: + - mdb + environment: + - MUMBLE_ADDRESS=example.com" + - MUMBLE_USERNAME=discord-bridge + - MUMBLE_PASSWORD=password + - DISCORD_TOKEN=token + - DISCORD_GID=gid + - DISCORD_CID=cid + + prometheus: + image: prom/prometheus:latest + container_name: prometheus + volumes: + - ./prometheus:/etc/prometheus + - prometheus_data:/prometheus + command: + - '--config.file=/etc/prometheus/prometheus.yml' + - '--storage.tsdb.path=/prometheus' + - '--web.console.libraries=/etc/prometheus/console_libraries' + - '--web.console.templates=/etc/prometheus/consoles' + - '--storage.tsdb.retention.time=200h' + - '--web.enable-lifecycle' + restart: unless-stopped + expose: + - 9090 + ports: + - 9090:9090 + depends_on: + - mumble-discord-bridge + + grafana: + image: grafana/grafana:latest + container_name: grafana + volumes: + # - grafana_data:/var/lib/grafana + - ./grafana/provisioning/dashboards:/etc/grafana/provisioning/dashboards + - ./grafana/provisioning/datasources:/etc/grafana/provisioning/datasources + environment: + # - GF_SECURITY_ADMIN_USER=${ADMIN_USER:-admin} + # - GF_SECURITY_ADMIN_PASSWORD=${ADMIN_PASSWORD:-admin} + - GF_USERS_ALLOW_SIGN_UP=false + - GF_AUTH_ANONYMOUS_ENABLED=true + - GF_AUTH_ANONYMOUS_ORG_NAME=Main Org. + - GF_AUTH_ANONYMOUS_ORG_ROLE=Editor + restart: unless-stopped + expose: + - 3000 + ports: + - 3030:3000 + depends_on: + - prometheus diff --git a/example/grafana-dashboard.png b/example/grafana-dashboard.png new file mode 100644 index 0000000000000000000000000000000000000000..538f2612b4cdc4ac2d1b98c0e891ff082295f376 GIT binary patch literal 159614 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≶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ƪeS&=y3TI6bHdrw=2pW>Ctz)# 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

~~|fY(k3w{U0cZ^1}J`8#O$J@;(Aqs_wn%DJ%{ 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 QDAmEL+V}GBK6>&00FMu^ng9R* literal 0 HcmV?d00001 diff --git a/example/monitoring/grafana/provisioning/dashboards/dashboards.yml b/example/monitoring/grafana/provisioning/dashboards/dashboards.yml new file mode 100644 index 0000000..10ff45f --- /dev/null +++ b/example/monitoring/grafana/provisioning/dashboards/dashboards.yml @@ -0,0 +1,24 @@ +apiVersion: 1 + +providers: + # an unique provider name. Required + - name: 'MDB' + # Org id. Default to 1 + orgId: 1 + # name of the dashboard folder. + folder: '' + # folder UID. will be automatically generated if not specified + folderUid: '' + # provider type. Default to 'file' + type: file + # disable dashboard deletion + disableDeletion: false + # how often Grafana will scan for changed dashboards + updateIntervalSeconds: 30 + # allow updating provisioned dashboards from the UI + allowUiUpdates: true + options: + # path to dashboard files on disk. Required when using the 'file' type + path: /etc/grafana/provisioning/dashboards + # use folder names from filesystem to create folders in Grafana + foldersFromFilesStructure: false \ No newline at end of file diff --git a/example/monitoring/grafana/provisioning/dashboards/mumble-discord-bridge.json b/example/monitoring/grafana/provisioning/dashboards/mumble-discord-bridge.json new file mode 100644 index 0000000..d8ebddf --- /dev/null +++ b/example/monitoring/grafana/provisioning/dashboards/mumble-discord-bridge.json @@ -0,0 +1,1154 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": 1, + "links": [], + "panels": [ + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 2, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "exemplar": true, + "expr": "mdb_mumble_users_gauge", + "interval": "", + "legendFormat": "Mumble", + "refId": "A" + }, + { + "exemplar": true, + "expr": "mdb_discord_users_gauge", + "hide": false, + "interval": "", + "legendFormat": "Discord", + "refId": "B" + } + ], + "title": "Connected Users", + "type": "stat" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 4, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "right" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "exemplar": true, + "expr": "mdb_mumble_ping", + "interval": "", + "legendFormat": "Mumble Ping", + "refId": "A" + }, + { + "exemplar": true, + "expr": "mdb_discord_latency", + "hide": false, + "interval": "", + "legendFormat": "Discord Heatbeat Latency", + "refId": "B" + } + ], + "title": "Latency", + "type": "timeseries" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "pps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 5 + }, + "id": 6, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "exemplar": true, + "expr": "rate(mdb_mumble_received_count[$__rate_interval])", + "interval": "", + "legendFormat": "Mumble Recieved", + "refId": "A" + }, + { + "exemplar": true, + "expr": "mdb_discord_buffer_gauge", + "hide": false, + "interval": "", + "legendFormat": "To Discord Buffer", + "refId": "C" + }, + { + "exemplar": true, + "expr": "rate(mdb_discord_sent_count[$__rate_interval])", + "hide": false, + "interval": "", + "legendFormat": "Discord Sent", + "refId": "B" + }, + { + "exemplar": true, + "expr": "rate(mdb_to_discord_dropped[$__rate_interval])", + "hide": false, + "interval": "", + "legendFormat": "To Discord Dropped", + "refId": "D" + } + ], + "title": "Mumble to Discord", + "type": "timeseries" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "pps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 5 + }, + "id": 8, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "exemplar": true, + "expr": "rate(mdb_discord_received_count[$__rate_interval])", + "interval": "", + "legendFormat": "Discord Recieved", + "refId": "A" + }, + { + "exemplar": true, + "expr": "rate(mdb_mumble_sent_count[$__rate_interval])", + "hide": false, + "interval": "", + "legendFormat": "Mumble Sent", + "refId": "B" + }, + { + "exemplar": true, + "expr": "rate(mdb_to_mumble_dropped[$__rate_interval])", + "hide": false, + "interval": "", + "legendFormat": "To Mumble Dropped", + "refId": "C" + } + ], + "title": "Discord to Mumble", + "type": "timeseries" + }, + { + "cards": { + "cardPadding": null, + "cardRound": null + }, + "color": { + "cardColor": "#5794F2", + "colorScale": "sqrt", + "colorScheme": "interpolateSpectral", + "exponent": 0.1, + "mode": "opacity" + }, + "dataFormat": "tsbuckets", + "datasource": null, + "gridPos": { + "h": 6, + "w": 8, + "x": 0, + "y": 13 + }, + "heatmap": {}, + "hideZeroBuckets": true, + "highlightCards": true, + "id": 10, + "legend": { + "show": true + }, + "maxDataPoints": 25, + "reverseYBuckets": false, + "targets": [ + { + "exemplar": true, + "expr": "sum(increase(mdb_timer_mumble_mixer_bucket[$__interval])) by (le)", + "format": "heatmap", + "interval": "", + "legendFormat": "{{le}}", + "refId": "A" + } + ], + "title": "Mumble Mixer Timer Performance", + "tooltip": { + "show": true, + "showHistogram": false + }, + "type": "heatmap", + "xAxis": { + "show": true + }, + "xBucketNumber": null, + "xBucketSize": null, + "yAxis": { + "decimals": null, + "format": "µs", + "logBase": 1, + "max": null, + "min": null, + "show": true, + "splitFactor": null + }, + "yBucketBound": "auto", + "yBucketNumber": null, + "yBucketSize": null + }, + { + "cards": { + "cardPadding": null, + "cardRound": null + }, + "color": { + "cardColor": "#73BF69", + "colorScale": "sqrt", + "colorScheme": "interpolateSpectral", + "exponent": 0.1, + "mode": "opacity" + }, + "dataFormat": "tsbuckets", + "datasource": null, + "gridPos": { + "h": 6, + "w": 8, + "x": 8, + "y": 13 + }, + "heatmap": {}, + "hideZeroBuckets": true, + "highlightCards": true, + "id": 11, + "legend": { + "show": true + }, + "maxDataPoints": 25, + "reverseYBuckets": false, + "targets": [ + { + "exemplar": true, + "expr": "sum(increase(mdb_timer_discord_send_bucket[$__interval])) by (le)", + "format": "heatmap", + "interval": "", + "legendFormat": "{{le}}", + "refId": "A" + } + ], + "title": "Discord Send Timer Performance", + "tooltip": { + "show": true, + "showHistogram": false + }, + "type": "heatmap", + "xAxis": { + "show": true + }, + "xBucketNumber": null, + "xBucketSize": null, + "yAxis": { + "decimals": null, + "format": "µs", + "logBase": 1, + "max": null, + "min": null, + "show": true, + "splitFactor": null + }, + "yBucketBound": "auto", + "yBucketNumber": null, + "yBucketSize": null + }, + { + "cards": { + "cardPadding": null, + "cardRound": null + }, + "color": { + "cardColor": "#FF9830", + "colorScale": "sqrt", + "colorScheme": "interpolateSpectral", + "exponent": 0.1, + "mode": "opacity" + }, + "dataFormat": "tsbuckets", + "datasource": null, + "gridPos": { + "h": 6, + "w": 8, + "x": 16, + "y": 13 + }, + "heatmap": {}, + "hideZeroBuckets": true, + "highlightCards": true, + "id": 12, + "legend": { + "show": true + }, + "maxDataPoints": 25, + "reverseYBuckets": false, + "targets": [ + { + "exemplar": true, + "expr": "sum(increase(mdb_timer_discord_mixer_bucket[$__interval])) by (le)", + "format": "heatmap", + "interval": "", + "legendFormat": "{{le}}", + "refId": "A" + } + ], + "title": "Discord Mixer Timer Performance", + "tooltip": { + "show": true, + "showHistogram": false + }, + "type": "heatmap", + "xAxis": { + "show": true + }, + "xBucketNumber": null, + "xBucketSize": null, + "yAxis": { + "decimals": null, + "format": "µs", + "logBase": 1, + "max": null, + "min": null, + "show": true, + "splitFactor": null + }, + "yBucketBound": "auto", + "yBucketNumber": null, + "yBucketSize": null + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 19 + }, + "id": 14, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "exemplar": true, + "expr": "mdb_mumble_streaming_gauge", + "interval": "", + "legendFormat": "Mumble", + "refId": "A" + }, + { + "exemplar": true, + "expr": "mdb_discord_streaming_gauge", + "hide": false, + "interval": "", + "legendFormat": "Discord", + "refId": "B" + } + ], + "title": "Active Streams", + "type": "timeseries" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "dateTimeAsIso" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 12, + "y": 19 + }, + "id": 20, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "exemplar": true, + "expr": "mdb_bridge_start_time*1000", + "format": "time_series", + "instant": false, + "interval": "", + "legendFormat": "Application Start Time", + "refId": "A" + }, + { + "exemplar": true, + "expr": "mdb_bridge_starts_time*1000", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "Current Bridge Start", + "refId": "B" + } + ], + "title": "Start Times", + "type": "stat" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 18, + "y": 19 + }, + "id": 22, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "8.0.6", + "targets": [ + { + "exemplar": true, + "expr": "mdb_bridge_starts_count", + "instant": false, + "interval": "", + "legendFormat": "Bridge Connections", + "refId": "A" + }, + { + "exemplar": true, + "expr": "mdb_discord_array_size_gauge", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "Discord Array Size", + "refId": "B" + }, + { + "exemplar": true, + "expr": "mdb_to_mumble_array_size_gauge", + "hide": false, + "interval": "", + "legendFormat": "Mumble Array Size", + "refId": "C" + } + ], + "title": "Additional Information", + "type": "stat" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 26 + }, + "id": 18, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "exemplar": true, + "expr": "go_goroutines", + "interval": "", + "legendFormat": "Go Routines", + "refId": "A" + }, + { + "exemplar": true, + "expr": "go_threads", + "hide": false, + "interval": "", + "legendFormat": "OS Threads", + "refId": "B" + } + ], + "title": "Go OS", + "type": "timeseries" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 26 + }, + "id": 16, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "exemplar": true, + "expr": "go_gc_duration_seconds", + "interval": "", + "legendFormat": "{{quantile}}", + "refId": "A" + } + ], + "title": "Go GC Performance", + "type": "timeseries" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 34 + }, + "id": 26, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "exemplar": true, + "expr": "go_memstats_alloc_bytes", + "interval": "", + "legendFormat": "Heap Alloc In Use", + "refId": "A" + }, + { + "exemplar": true, + "expr": "go_memstats_sys_bytes", + "hide": false, + "interval": "", + "legendFormat": "System Bytes", + "refId": "B" + } + ], + "title": "Application Memory", + "type": "timeseries" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 34 + }, + "id": 24, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "exemplar": true, + "expr": "increase(go_gc_duration_seconds_count[$__rate_interval])", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "GC Events", + "type": "timeseries" + } + ], + "refresh": "5s", + "schemaVersion": 30, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-5m", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Mumble-Discord-Bridge", + "uid": "QYiiu_77z", + "version": 5 +} \ No newline at end of file diff --git a/example/monitoring/grafana/provisioning/datasources/datasource.yml b/example/monitoring/grafana/provisioning/datasources/datasource.yml new file mode 100644 index 0000000..a08624a --- /dev/null +++ b/example/monitoring/grafana/provisioning/datasources/datasource.yml @@ -0,0 +1,11 @@ +apiVersion: 1 + +datasources: + - name: Prometheus + type: prometheus + access: proxy + orgId: 1 + url: http://prometheus:9090 + basicAuth: false + isDefault: true + editable: true diff --git a/example/monitoring/prometheus/prometheus.yml b/example/monitoring/prometheus/prometheus.yml new file mode 100644 index 0000000..a2637f4 --- /dev/null +++ b/example/monitoring/prometheus/prometheus.yml @@ -0,0 +1,12 @@ +global: + scrape_interval: 15s + evaluation_interval: 15s + +# A scrape configuration containing exactly one endpoint to scrape. +scrape_configs: + - job_name: 'mdb' + scrape_interval: 1s + static_configs: + - targets: [ + 'mumble-discord-bridge:9559', + ] diff --git a/go.mod b/go.mod index 001a44e..8a339cc 100644 --- a/go.mod +++ b/go.mod @@ -5,9 +5,9 @@ go 1.15 require ( github.com/bwmarrin/discordgo v0.23.3-0.20210512035133-7d7206b01bb5 github.com/joho/godotenv v1.3.0 + github.com/prometheus/client_golang v1.11.0 github.com/stieneee/gopus v0.0.0-20210424193312-6d10f6090335 github.com/stieneee/gumble v0.0.0-20210424210604-732f48b5e0de github.com/stieneee/tickerct v0.0.0-20210420020607-d1b092aa40e9 golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect - golang.org/x/sys v0.0.0-20210108172913-0df2131ae363 // indirect ) diff --git a/go.sum b/go.sum index 9eba3fd..251e830 100644 --- a/go.sum +++ b/go.sum @@ -1,38 +1,196 @@ +cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +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/bwmarrin/discordgo v0.23.3-0.20210512035133-7d7206b01bb5 h1:VtiZMSjY2N6XpM1luSchBVX76QURpS0HA7BffVuHOCo= github.com/bwmarrin/discordgo v0.23.3-0.20210512035133-7d7206b01bb5/go.mod h1:OMKxbTmkKofBjBi4/yidO3ItxbJ6PUfEUkjchM4En8c= +github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +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/dchote/go-openal v0.0.0-20171116030048-f4a9a141d372 h1:tz3KnXWtRZR0RWOfcMNOw+HHezWLQa7vfSOWTtKjchI= github.com/dchote/go-openal v0.0.0-20171116030048-f4a9a141d372/go.mod h1:74z+CYu2/mx4N+mcIS/rsvfAxBPBV9uv8zRAnwyFkdI= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0 h1:DGJh0Sm43HbOeYDNnVZFl8BvcYVvjD5bqYJvp0REbwQ= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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 v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/stieneee/gopus v0.0.0-20210424193312-6d10f6090335 h1:yzwz6AqGKysli5du4CrQ48BMGUCSkrl7V7Kbo9VaG8w= github.com/stieneee/gopus v0.0.0-20210424193312-6d10f6090335/go.mod h1:tAKYr3fSBJGold7c9DMPlhupn9oy8hTgl3cZ0hoyRQs= github.com/stieneee/gumble v0.0.0-20210424210604-732f48b5e0de h1:4dWOeXRnba4jHVa3KuWf7i/GOIAlBMR3euVTUXOey2I= github.com/stieneee/gumble v0.0.0-20210424210604-732f48b5e0de/go.mod h1:hVIsmrlrudlx2HJbsDkIZI4crkv6NHSau0ldEWbQI/Y= github.com/stieneee/tickerct v0.0.0-20210420020607-d1b092aa40e9 h1:0l2H6Oj6JHMmkqm9xaBMQA5MOGhPT+Nn/thlTUcD9Iw= github.com/stieneee/tickerct v0.0.0-20210420020607-d1b092aa40e9/go.mod h1:54+oZlabriEpT52rPAjAeEWUFgYqv325LrS3hNxHGFE= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210108172913-0df2131ae363 h1:wHn06sgWHMO1VsQ8F+KzDJx/JzqfsNLnc+oEi07qD7s= -golang.org/x/sys v0.0.0-20210108172913-0df2131ae363/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40 h1:JWgyZ1qgdTaF3N3oxC+MdTV7qvEEgHo3otj+HB5CM7Q= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/internal/bridge/bridge.go b/internal/bridge/bridge.go index 7ab4f37..f793a48 100644 --- a/internal/bridge/bridge.go +++ b/internal/bridge/bridge.go @@ -119,6 +119,9 @@ func (b *BridgeState) StartBridge() { var err error + promBridgeStarts.Inc() + promBridgeStartTime.SetToCurrentTime() + // DISCORD Connect Voice log.Println("Attempting to join Discord voice channel") if b.DiscordChannelID == "" { @@ -252,6 +255,9 @@ func (b *BridgeState) DiscordStatusUpdate() { log.Printf("error pinging mumble server %v\n", err) b.DiscordSession.UpdateListeningStatus("an error pinging mumble") } else { + + promMumblePing.Set(float64(resp.Ping.Milliseconds())) + b.MumbleUsersMutex.Lock() b.BridgeMutex.Lock() b.MumbleUserCount = resp.ConnectedUsers @@ -271,6 +277,12 @@ func (b *BridgeState) DiscordStatusUpdate() { b.MumbleUsersMutex.Unlock() b.DiscordSession.UpdateListeningStatus(status) } + + discordHeartBeat := b.DiscordSession.LastHeartbeatAck.Sub(b.DiscordSession.LastHeartbeatSent).Milliseconds() + if discordHeartBeat > 0 { + promDiscordHeartBeat.Set(float64(discordHeartBeat)) + } + } } diff --git a/internal/bridge/discord-handlers.go b/internal/bridge/discord-handlers.go index c4a4fd4..919540e 100644 --- a/internal/bridge/discord-handlers.go +++ b/internal/bridge/discord-handlers.go @@ -227,9 +227,13 @@ func (l *DiscordListener) VoiceUpdate(s *discordgo.Session, event *discordgo.Voi l.Bridge.MumbleClient.Self.Channel.Send(fmt.Sprintf("%v has left Discord channel\n", l.Bridge.DiscordUsers[id].username), false) }) } - l.Bridge.BridgeMutex.Unlock() delete(l.Bridge.DiscordUsers, id) + l.Bridge.BridgeMutex.Unlock() } } + + l.Bridge.BridgeMutex.Lock() + promDiscordUsers.Set(float64(len(l.Bridge.DiscordUsers))) + l.Bridge.BridgeMutex.Unlock() } } diff --git a/internal/bridge/discord.go b/internal/bridge/discord.go index 0ccc666..bcc9615 100644 --- a/internal/bridge/discord.go +++ b/internal/bridge/discord.go @@ -87,6 +87,7 @@ func (dd *DiscordDuplex) discordSendPCM(ctx context.Context, wg *sync.WaitGroup, readyTimeout.Stop() } else { dd.Bridge.DiscordVoice.OpusSend <- opus + promDiscordSentPackets.Inc() } dd.Bridge.DiscordVoice.RWMutex.RUnlock() } @@ -99,7 +100,7 @@ func (dd *DiscordDuplex) discordSendPCM(ctx context.Context, wg *sync.WaitGroup, default: } - sleepTick.SleepNextTarget() + promTimerDiscordSend.Observe(float64(sleepTick.SleepNextTarget())) if (len(pcm) > 1 && streaming) || (len(pcm) > dd.Bridge.BridgeConfig.DiscordStartStreamingCount && !streaming) { if !streaming { @@ -135,7 +136,7 @@ func (dd *DiscordDuplex) discordSendPCM(ctx context.Context, wg *sync.WaitGroup, // We want to do this after alerting the user of possible short speaking cycles for i := 0; i < 5; i++ { internalSend(opusSilence) - sleepTick.SleepNextTarget() + promTimerDiscordSend.Observe(float64(sleepTick.SleepNextTarget())) } dd.Bridge.DiscordVoice.Speaking(false) @@ -242,6 +243,8 @@ func (dd *DiscordDuplex) discordReceivePCM(ctx context.Context, wg *sync.WaitGro // fmt.Println(p.SSRC, p.Type, deltaT, p.Sequence, p.Sequence-s.lastSequence, oldReceiving, s.streaming, len(p.Opus), len(p.PCM)) + promDiscordReceivedPackets.Inc() + // Push data into pcm channel in 10ms chunks of mono pcm data dd.discordMutex.Lock() for l := 0; l < len(p.PCM); l = l + 480 { @@ -282,12 +285,13 @@ func (dd *DiscordDuplex) fromDiscordMixer(ctx context.Context, wg *sync.WaitGrou default: } - sleepTick.SleepNextTarget() + promTimerDiscordMixer.Observe(float64(sleepTick.SleepNextTarget())) dd.discordMutex.Lock() sendAudio = false internalMixerArr := make([][]int16, 0) + streamingCount := 0 // Work through each channel for i := range dd.fromDiscordMap { @@ -306,6 +310,7 @@ func (dd *DiscordDuplex) fromDiscordMixer(ctx context.Context, wg *sync.WaitGrou dd.fromDiscordMap[i] = x } + streamingCount++ x1 := (<-dd.fromDiscordMap[i].pcm) internalMixerArr = append(internalMixerArr, x1) } else { @@ -318,6 +323,9 @@ func (dd *DiscordDuplex) fromDiscordMixer(ctx context.Context, wg *sync.WaitGrou } } + promDiscordArraySize.Set(float64(len(dd.fromDiscordMap))) + promDiscordStreaming.Set(float64(streamingCount)) + dd.discordMutex.Unlock() mumbleTimeoutSend := func(outBuf []int16) { @@ -329,8 +337,10 @@ func (dd *DiscordDuplex) fromDiscordMixer(ctx context.Context, wg *sync.WaitGrou select { case toMumble <- outBuf: + promSentMumblePackets.Inc() case <-timeout: log.Println("To Mumble timeout. Dropping packet") + promToMumbleDropped.Inc() } } @@ -354,7 +364,7 @@ func (dd *DiscordDuplex) fromDiscordMixer(ctx context.Context, wg *sync.WaitGrou for i := 0; i < 5; i++ { mumbleTimeoutSend(mumbleSilence) - sleepTick.SleepNextTarget() + promTimerDiscordMixer.Observe(float64(sleepTick.SleepNextTarget())) } toMumbleStreaming = false diff --git a/internal/bridge/mumble-handlers.go b/internal/bridge/mumble-handlers.go index 05c9a93..e788a3d 100644 --- a/internal/bridge/mumble-handlers.go +++ b/internal/bridge/mumble-handlers.go @@ -1,8 +1,10 @@ package bridge import ( + "fmt" "log" "strings" + "time" "github.com/stieneee/gumble/gumble" ) @@ -12,28 +14,44 @@ type MumbleListener struct { Bridge *BridgeState } +func (l *MumbleListener) updateUsers() { + l.Bridge.MumbleUsersMutex.Lock() + l.Bridge.MumbleUsers = make(map[string]bool) + for _, user := range l.Bridge.MumbleClient.Self.Channel.Users { + //note, this might be too slow for really really big channels? + //event listeners block while processing + //also probably bad to rebuild the set every user change. + if user.Name != l.Bridge.MumbleClient.Self.Name { + l.Bridge.MumbleUsers[user.Name] = true + } + } + promMumbleUsers.Set(float64(len(l.Bridge.MumbleUsers))) + l.Bridge.MumbleUsersMutex.Unlock() + +} + func (l *MumbleListener) MumbleConnect(e *gumble.ConnectEvent) { //join specified channel startingChannel := e.Client.Channels.Find(l.Bridge.BridgeConfig.MumbleChannel...) if startingChannel != nil { e.Client.Self.Move(startingChannel) } + + // l.updateUsers() // patch below + + // This is an ugly patch Mumble Client state is slow to update + time.AfterFunc(5*time.Second, func() { + defer func() { + if r := recover(); r != nil { + fmt.Printf("Failed to mumble user list %v \n", r) + } + }() + l.updateUsers() + }) } func (l *MumbleListener) MumbleUserChange(e *gumble.UserChangeEvent) { - l.Bridge.MumbleUsersMutex.Lock() - if e.Type.Has(gumble.UserChangeConnected) || e.Type.Has(gumble.UserChangeChannel) || e.Type.Has(gumble.UserChangeDisconnected) { - l.Bridge.MumbleUsers = make(map[string]bool) - for _, user := range l.Bridge.MumbleClient.Self.Channel.Users { - //note, this might be too slow for really really big channels? - //event listeners block while processing - //also probably bad to rebuild the set every user change. - if user.Name != l.Bridge.MumbleClient.Self.Name { - l.Bridge.MumbleUsers[user.Name] = true - } - } - } - l.Bridge.MumbleUsersMutex.Unlock() + l.updateUsers() if e.Type.Has(gumble.UserChangeConnected) { diff --git a/internal/bridge/mumble.go b/internal/bridge/mumble.go index be3edd8..59a7310 100644 --- a/internal/bridge/mumble.go +++ b/internal/bridge/mumble.go @@ -29,6 +29,8 @@ func (m MumbleDuplex) OnAudioStream(e *gumble.AudioStreamEvent) { mumbleStreamingArr = append(mumbleStreamingArr, false) mutex.Unlock() + promMumbleArraySize.Set(float64(len(fromMumbleArr))) + go func() { name := e.User.Name log.Println("New mumble audio stream", name) @@ -39,6 +41,7 @@ func (m MumbleDuplex) OnAudioStream(e *gumble.AudioStreamEvent) { for i := 0; i < len(p.AudioBuffer)/480; i++ { localMumbleArray <- p.AudioBuffer[480*i : 480*(i+1)] } + promReceivedMumblePackets.Inc() } log.Println("Mumble audio stream ended", name) }() @@ -61,12 +64,13 @@ func (m MumbleDuplex) fromMumbleMixer(ctx context.Context, wg *sync.WaitGroup, t default: } - sleepTick.SleepNextTarget() + promTimerMumbleMixer.Observe(float64(sleepTick.SleepNextTarget())) mutex.Lock() sendAudio = false internalMixerArr := make([]gumble.AudioBuffer, 0) + streamingCount := 0 // Work through each channel for i := 0; i < len(fromMumbleArr); i++ { @@ -74,6 +78,7 @@ func (m MumbleDuplex) fromMumbleMixer(ctx context.Context, wg *sync.WaitGroup, t sendAudio = true if !mumbleStreamingArr[i] { mumbleStreamingArr[i] = true + streamingCount++ // log.Println("Mumble starting", i) } @@ -89,6 +94,8 @@ func (m MumbleDuplex) fromMumbleMixer(ctx context.Context, wg *sync.WaitGroup, t mutex.Unlock() + promMumbleStreaming.Set(float64(streamingCount)) + if sendAudio { outBuf := make([]int16, 480) @@ -111,10 +118,12 @@ func (m MumbleDuplex) fromMumbleMixer(ctx context.Context, wg *sync.WaitGroup, t } } + promToDiscordBufferSize.Set(float64(len(toDiscord))) select { case toDiscord <- outBuf: default: log.Println("Error: toDiscord buffer full. Dropping packet") + promToDiscordDropped.Inc() } } } diff --git a/internal/bridge/prom.go b/internal/bridge/prom.go new file mode 100644 index 0000000..545b7a0 --- /dev/null +++ b/internal/bridge/prom.go @@ -0,0 +1,141 @@ +package bridge + +import ( + "log" + "net/http" + "strconv" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +var ( + // Bridge General + + PromApplicationStartTime = promauto.NewGauge(prometheus.GaugeOpts{ + Name: "mdb_bridge_start_time", + Help: "The time the application started", + }) + + promBridgeStarts = promauto.NewCounter(prometheus.CounterOpts{ + Name: "mdb_bridge_starts_count", + Help: "The number of times the bridge start routine has been called", + }) + + promBridgeStartTime = promauto.NewGauge(prometheus.GaugeOpts{ + Name: "mdb_bridge_starts_time", + Help: "The time the current bridge instance started", + }) + + // MUMBLE + promMumblePing = promauto.NewGauge(prometheus.GaugeOpts{ + Name: "mdb_mumble_ping", + Help: "Mumble ping", + }) + + promMumbleUsers = promauto.NewGauge(prometheus.GaugeOpts{ + Name: "mdb_mumble_users_gauge", + Help: "The number of connected Mumble users", + }) + + promReceivedMumblePackets = promauto.NewCounter(prometheus.CounterOpts{ + Name: "mdb_mumble_received_count", + Help: "The count of Mumble audio packets received", + }) + + promSentMumblePackets = promauto.NewCounter(prometheus.CounterOpts{ + Name: "mdb_mumble_sent_count", + Help: "The count of audio packets sent to mumble", + }) + + // promToMumbleBufferSize = promauto.NewGauge(prometheus.GaugeOpts{ + // Name: "mdb_to_mumble_buffer_gauge", + // Help: "", + // }) + + promToMumbleDropped = promauto.NewCounter(prometheus.CounterOpts{ + Name: "mdb_to_mumble_dropped", + Help: "The number of packets timeouts to mumble", + }) + + promMumbleArraySize = promauto.NewGauge(prometheus.GaugeOpts{ + Name: "mdb_to_mumble_array_size_gauge", + Help: "The array size of mumble streams", + }) + + promMumbleStreaming = promauto.NewGauge(prometheus.GaugeOpts{ //SUMMARY? + Name: "mdb_mumble_streaming_gauge", + Help: "The number of active audio streams streaming audio from mumble", + }) + + // DISCORD + + // TODO Discrod Ping + + promDiscordHeartBeat = promauto.NewGauge(prometheus.GaugeOpts{ + Name: "mdb_discord_latency", + Help: "Discord heartbeat latency", + }) + + promDiscordUsers = promauto.NewGauge(prometheus.GaugeOpts{ + Name: "mdb_discord_users_gauge", + Help: "The number of Connected Discord users", + }) + + promDiscordReceivedPackets = promauto.NewCounter(prometheus.CounterOpts{ + Name: "mdb_discord_received_count", + Help: "The number of received packed from Discord", + }) + + promDiscordSentPackets = promauto.NewCounter(prometheus.CounterOpts{ + Name: "mdb_discord_sent_count", + Help: "The number of packets sent to Discord", + }) + + promToDiscordBufferSize = promauto.NewGauge(prometheus.GaugeOpts{ + Name: "mdb_discord_buffer_gauge", + Help: "The buffer size for packets to Discord", + }) + + promToDiscordDropped = promauto.NewCounter(prometheus.CounterOpts{ + Name: "mdb_to_discord_dropped", + Help: "The count of packets dropped to discord", + }) + + promDiscordArraySize = promauto.NewGauge(prometheus.GaugeOpts{ + Name: "mdb_discord_array_size_gauge", + Help: "The discord receiving array size", + }) + + promDiscordStreaming = promauto.NewGauge(prometheus.GaugeOpts{ + Name: "mdb_discord_streaming_gauge", + Help: "The number of active audio streams streaming from discord", + }) + + // Sleep Timer Performance + + promTimerDiscordSend = promauto.NewHistogram(prometheus.HistogramOpts{ + Name: "mdb_timer_discord_send", + Help: "Timer performance for Discord send", + Buckets: []float64{1000, 2000, 5000, 10000, 20000}, + }) + + promTimerDiscordMixer = promauto.NewHistogram(prometheus.HistogramOpts{ + Name: "mdb_timer_discord_mixer", + Help: "Timer performance for the Discord mixer", + Buckets: []float64{1000, 2000, 5000, 10000, 20000}, + }) + + promTimerMumbleMixer = promauto.NewHistogram(prometheus.HistogramOpts{ + Name: "mdb_timer_mumble_mixer", + Help: "Timer performance for the Mumble mixer", + Buckets: []float64{1000, 2000, 5000, 10000, 20000}, + }) +) + +func StartPromServer(port int) { + log.Println("Starting Metrics Server") + http.Handle("/metrics", promhttp.Handler()) + http.ListenAndServe(":"+strconv.Itoa(port), nil) +} diff --git a/pkg/sleepct/sleepct.go b/pkg/sleepct/sleepct.go index 8a82d0a..fad9459 100644 --- a/pkg/sleepct/sleepct.go +++ b/pkg/sleepct/sleepct.go @@ -23,7 +23,7 @@ func (s *SleepCT) Start(d time.Duration) { } } -func (s *SleepCT) SleepNextTarget() { +func (s *SleepCT) SleepNextTarget() int64 { s.Lock() now := time.Now() @@ -47,4 +47,6 @@ func (s *SleepCT) SleepNextTarget() { // fmt.Println("delta", delta, d, time.Since(s.t)) s.Unlock() + + return now.Sub(s.t).Microseconds() } From f7f492670c4b454212c046987f6b155695fec1f9 Mon Sep 17 00:00:00 2001 From: Tyler Stiene Date: Mon, 23 Aug 2021 00:35:01 -0400 Subject: [PATCH 03/13] correct buffer discord buffer axis --- .../dashboards/mumble-discord-bridge.json | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/example/monitoring/grafana/provisioning/dashboards/mumble-discord-bridge.json b/example/monitoring/grafana/provisioning/dashboards/mumble-discord-bridge.json index d8ebddf..6c5e4a8 100644 --- a/example/monitoring/grafana/provisioning/dashboards/mumble-discord-bridge.json +++ b/example/monitoring/grafana/provisioning/dashboards/mumble-discord-bridge.json @@ -221,7 +221,24 @@ }, "unit": "pps" }, - "overrides": [] + "overrides": [ + { + "matcher": { + "id": "byFrameRefID", + "options": "C" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "right" + }, + { + "id": "unit", + "value": "none" + } + ] + } + ] }, "gridPos": { "h": 8, @@ -1150,5 +1167,5 @@ "timezone": "", "title": "Mumble-Discord-Bridge", "uid": "QYiiu_77z", - "version": 5 + "version": 2 } \ No newline at end of file From 649fe9f33eea49e5a03c88d4f246d5c002f9edc8 Mon Sep 17 00:00:00 2001 From: Tyler Stiene Date: Mon, 13 Sep 2021 00:50:23 -0400 Subject: [PATCH 04/13] fix sleepct pausing --- .gitignore | 3 ++- internal/bridge/discord.go | 21 +++++++++++------- internal/bridge/mumble.go | 11 +++++++--- pkg/sleepct/sleepct.go | 45 +++++++++++++++++++++++++++----------- test/timing_test.go | 7 +++--- 5 files changed, 59 insertions(+), 28 deletions(-) diff --git a/.gitignore b/.gitignore index edc4385..b48a929 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ dist *.test cert.pem *.gob -docker-compose.yml \ No newline at end of file +docker-compose.yml +mdb-local \ No newline at end of file diff --git a/internal/bridge/discord.go b/internal/bridge/discord.go index bef104e..c50590a 100644 --- a/internal/bridge/discord.go +++ b/internal/bridge/discord.go @@ -30,6 +30,9 @@ type DiscordDuplex struct { fromDiscordMap map[uint32]fromDiscord } +var discrodSendSleepTick sleepct.SleepCT = sleepct.SleepCT{} +var discrodReceiveSleepTick sleepct.SleepCT = sleepct.SleepCT{} + // OnError gets called by dgvoice when an error is encountered. // By default logs to STDERR var OnError = func(str string, err error) { @@ -61,8 +64,7 @@ func (dd *DiscordDuplex) discordSendPCM(ctx context.Context, wg *sync.WaitGroup, // Generate Opus Silence Frame opusSilence := []byte{0xf8, 0xff, 0xfe} - sleepTick := sleepct.SleepCT{} - sleepTick.Start(20 * time.Millisecond) + discrodSendSleepTick.Start(20 * time.Millisecond) lastReady := true var readyTimeout *time.Timer @@ -100,7 +102,8 @@ func (dd *DiscordDuplex) discordSendPCM(ctx context.Context, wg *sync.WaitGroup, default: } - promTimerDiscordSend.Observe(float64(sleepTick.SleepNextTarget(true))) + // if we are not streaming try to pause + promTimerDiscordSend.Observe(float64(discrodSendSleepTick.SleepNextTarget(ctx, !streaming))) if (len(pcm) > 1 && streaming) || (len(pcm) > dd.Bridge.BridgeConfig.DiscordStartStreamingCount && !streaming) { if !streaming { @@ -136,7 +139,7 @@ func (dd *DiscordDuplex) discordSendPCM(ctx context.Context, wg *sync.WaitGroup, // We want to do this after alerting the user of possible short speaking cycles for i := 0; i < 5; i++ { internalSend(opusSilence) - promTimerDiscordSend.Observe(float64(sleepTick.SleepNextTarget(true))) + promTimerDiscordSend.Observe(float64(discrodSendSleepTick.SleepNextTarget(ctx, true))) } dd.Bridge.DiscordVoice.Speaking(false) @@ -260,6 +263,8 @@ func (dd *DiscordDuplex) discordReceivePCM(ctx context.Context, wg *sync.WaitGro } } dd.discordMutex.Unlock() + + discrodReceiveSleepTick.Notify() } } @@ -270,8 +275,7 @@ func (dd *DiscordDuplex) fromDiscordMixer(ctx context.Context, wg *sync.WaitGrou } var speakingStart time.Time - sleepTick := sleepct.SleepCT{} - sleepTick.Start(10 * time.Millisecond) + discrodReceiveSleepTick.Start(10 * time.Millisecond) sendAudio := false toMumbleStreaming := false @@ -285,7 +289,8 @@ func (dd *DiscordDuplex) fromDiscordMixer(ctx context.Context, wg *sync.WaitGrou default: } - promTimerDiscordMixer.Observe(float64(sleepTick.SleepNextTarget(true))) + // if didn't send audio try to pause + promTimerDiscordMixer.Observe(float64(discrodReceiveSleepTick.SleepNextTarget(ctx, !sendAudio))) dd.discordMutex.Lock() @@ -364,7 +369,7 @@ func (dd *DiscordDuplex) fromDiscordMixer(ctx context.Context, wg *sync.WaitGrou for i := 0; i < 5; i++ { mumbleTimeoutSend(mumbleSilence) - promTimerDiscordMixer.Observe(float64(sleepTick.SleepNextTarget(false))) + promTimerDiscordMixer.Observe(float64(discrodReceiveSleepTick.SleepNextTarget(ctx, false))) } toMumbleStreaming = false diff --git a/internal/bridge/mumble.go b/internal/bridge/mumble.go index bfc72dd..609525f 100644 --- a/internal/bridge/mumble.go +++ b/internal/bridge/mumble.go @@ -18,6 +18,8 @@ var mumbleStreamingArr []bool // MumbleDuplex - listenera and outgoing type MumbleDuplex struct{} +var mumbleSleepTick sleepct.SleepCT = sleepct.SleepCT{} + // OnAudioStream - Spawn routines to handle incoming packets func (m MumbleDuplex) OnAudioStream(e *gumble.AudioStreamEvent) { @@ -42,14 +44,14 @@ func (m MumbleDuplex) OnAudioStream(e *gumble.AudioStreamEvent) { localMumbleArray <- p.AudioBuffer[480*i : 480*(i+1)] } promReceivedMumblePackets.Inc() + mumbleSleepTick.Notify() } log.Println("Mumble audio stream ended", name) }() } func (m MumbleDuplex) fromMumbleMixer(ctx context.Context, wg *sync.WaitGroup, toDiscord chan []int16) { - sleepTick := sleepct.SleepCT{} - sleepTick.Start(10 * time.Millisecond) + mumbleSleepTick.Start(10 * time.Millisecond) sendAudio := false bufferWarning := false @@ -64,7 +66,8 @@ func (m MumbleDuplex) fromMumbleMixer(ctx context.Context, wg *sync.WaitGroup, t default: } - promTimerMumbleMixer.Observe(float64(sleepTick.SleepNextTarget(true))) + // if we sent audio on the last pass attempt to pause + promTimerMumbleMixer.Observe(float64(mumbleSleepTick.SleepNextTarget(ctx, !sendAudio))) mutex.Lock() @@ -125,6 +128,8 @@ func (m MumbleDuplex) fromMumbleMixer(ctx context.Context, wg *sync.WaitGroup, t log.Println("Error: toDiscord buffer full. Dropping packet") promToDiscordDropped.Inc() } + + discrodSendSleepTick.Notify() } } } diff --git a/pkg/sleepct/sleepct.go b/pkg/sleepct/sleepct.go index 10edb6b..1ba67ce 100644 --- a/pkg/sleepct/sleepct.go +++ b/pkg/sleepct/sleepct.go @@ -1,22 +1,25 @@ package sleepct import ( + "context" "fmt" "time" ) // SleepCT - Sleep constant time step crates a sleep based ticker. -// designed maintain a consistent sleep/tick interval. +// designed to maintain a consistent sleep/tick interval. // The sleeper can be paused waiting to be signaled from another go routine. // This allows for the pausing of loops that do not have work to complete type SleepCT struct { d time.Duration // desired duration between targets t time.Time // last time target resume chan bool + wake time.Time // last wake time + drift int64 // last wake drift microseconds } func (s *SleepCT) Start(d time.Duration) { - s.resume = make(chan bool, 1) + s.resume = make(chan bool, 2) if s.t.IsZero() { s.d = d s.t = time.Now() @@ -29,31 +32,43 @@ func (s *SleepCT) Start(d time.Duration) { // If pause it set to true will sleep the duration and wait to be notified. // The notification channel will be cleared when the thread wakes. // SleepNextTarget should not be call more than once concurrently. -func (s *SleepCT) SleepNextTarget(pause bool) int64 { - var last time.Time +func (s *SleepCT) SleepNextTarget(ctx context.Context, pause bool) int64 { now := time.Now() + // if target is zero safety net if s.t.IsZero() { fmt.Println("SleepCT reset") - last = now.Add(-s.d) - } else { - last = s.t + s.t = now.Add(-s.d) } // Sleep to Next Target - s.t = last.Add(s.d) + s.t = s.t.Add(s.d) + // Compute the desired sleep time to reach the target d := time.Until(s.t) + // Sleep time.Sleep(d) + // record the wake time + s.wake = time.Now() + s.drift = s.wake.Sub(s.t).Microseconds() + + // fmt.Println(s.t.UnixMilli(), d.Milliseconds(), wake.UnixMilli(), drift, pause, len(s.resume)) + + // external pause control if pause { - // wait until resume + // don't pause if the notification channel has something if len(s.resume) == 0 { - <-s.resume + // fmt.Println("pause") + select { + case <-s.resume: + case <-ctx.Done(): + // fmt.Println("sleepct ctx exit") + } // if we did pause set the last sleep target to now - last = time.Now() + s.t = time.Now() } } @@ -63,11 +78,15 @@ func (s *SleepCT) SleepNextTarget(pause bool) int64 { default: } - return now.Sub(s.t).Microseconds() + // return the drift for monitoring purposes + return s.drift } // Notify attempts to resume a paused sleeper. // It is safe to call notify from other processes and as often as desired. func (s *SleepCT) Notify() { - s.resume <- true + select { + case s.resume <- true: + default: + } } diff --git a/test/timing_test.go b/test/timing_test.go index 7985e85..a6f2ddd 100644 --- a/test/timing_test.go +++ b/test/timing_test.go @@ -1,6 +1,7 @@ package main import ( + "context" "fmt" "math" "math/rand" @@ -13,7 +14,7 @@ import ( "github.com/stieneee/tickerct" ) -const testCount int64 = 1000 +const testCount int64 = 10000 const maxSleepInterval time.Duration = 15 * time.Millisecond const tickerInterval time.Duration = 10 * time.Millisecond const testDuration time.Duration = time.Duration(testCount * 10 * int64(time.Millisecond)) @@ -115,7 +116,7 @@ func testSleepCT(wg *sync.WaitGroup) { if i+1 < testCount { time.Sleep(time.Duration(float64(maxSleepInterval) * rand.Float64())) } - s.SleepNextTarget(false) + s.SleepNextTarget(context.TODO(), false) } fmt.Println("SleepCT (loaded) after", testDuration, "drifts", time.Since(start)-testDuration) wg.Done() @@ -144,7 +145,7 @@ func testSleepCTPause(wg *sync.WaitGroup) { time.Sleep(time.Duration(float64(maxSleepInterval) * rand.Float64())) } s.Notify() - s.SleepNextTarget(true) + s.SleepNextTarget(context.TODO(), true) } fmt.Println("SleepCT Pause (loaded) after", testDuration, "drifts", time.Since(start)-testDuration) wg.Done() From 0f57c5d33a86591337662846371853e00e4e92e2 Mon Sep 17 00:00:00 2001 From: Tyler Stiene Date: Thu, 4 Nov 2021 01:21:41 -0400 Subject: [PATCH 05/13] improve discord buffer packet drop message kill the bridge if unable to send to discord for 5 seconds --- internal/bridge/bridge.go | 2 +- internal/bridge/mumble.go | 23 +++++++++++++++++++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/internal/bridge/bridge.go b/internal/bridge/bridge.go index f793a48..b36ef6f 100644 --- a/internal/bridge/bridge.go +++ b/internal/bridge/bridge.go @@ -183,7 +183,7 @@ func (b *BridgeState) StartBridge() { // Start Passing Between // From Mumble - go b.MumbleStream.fromMumbleMixer(ctx, &wg, toDiscord) + go b.MumbleStream.fromMumbleMixer(ctx, &wg, cancel, toDiscord) // From Discord b.DiscordStream = &DiscordDuplex{ diff --git a/internal/bridge/mumble.go b/internal/bridge/mumble.go index 609525f..ec4c3a4 100644 --- a/internal/bridge/mumble.go +++ b/internal/bridge/mumble.go @@ -3,6 +3,7 @@ package bridge import ( "context" "log" + "strconv" "sync" "time" @@ -50,12 +51,15 @@ func (m MumbleDuplex) OnAudioStream(e *gumble.AudioStreamEvent) { }() } -func (m MumbleDuplex) fromMumbleMixer(ctx context.Context, wg *sync.WaitGroup, toDiscord chan []int16) { +func (m MumbleDuplex) fromMumbleMixer(ctx context.Context, wg *sync.WaitGroup, cancel context.CancelFunc, toDiscord chan []int16) { mumbleSleepTick.Start(10 * time.Millisecond) sendAudio := false bufferWarning := false + droppingPackets := false + droppingPacketCount := 0 + wg.Add(1) for { @@ -124,9 +128,24 @@ func (m MumbleDuplex) fromMumbleMixer(ctx context.Context, wg *sync.WaitGroup, t promToDiscordBufferSize.Set(float64(len(toDiscord))) select { case toDiscord <- outBuf: + { + if droppingPackets { + log.Println("Discord buffer ok, total packets dropped " + strconv.Itoa(droppingPacketCount)) + droppingPackets = false + } + } default: - log.Println("Error: toDiscord buffer full. Dropping packet") + if !droppingPackets { + log.Println("Error: toDiscord buffer full. Dropping packets") + droppingPackets = true + droppingPacketCount = 0 + } + droppingPacketCount++ promToDiscordDropped.Inc() + if droppingPacketCount > 250 { + log.Println("Discord Timeout") + cancel() + } } discrodSendSleepTick.Notify() From 8ca66fb500cbebd7687a7d7287f6c6965b936b40 Mon Sep 17 00:00:00 2001 From: Tyler Stiene Date: Wed, 17 Nov 2021 23:58:54 -0500 Subject: [PATCH 06/13] refactor wait group added debug message for bridge cancel --- internal/bridge/bridge.go | 26 +++++++++++++++++++++----- internal/bridge/discord.go | 17 ++++++----------- internal/bridge/mumble.go | 6 ++---- 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/internal/bridge/bridge.go b/internal/bridge/bridge.go index b36ef6f..80765ae 100644 --- a/internal/bridge/bridge.go +++ b/internal/bridge/bridge.go @@ -183,7 +183,11 @@ func (b *BridgeState) StartBridge() { // Start Passing Between // From Mumble - go b.MumbleStream.fromMumbleMixer(ctx, &wg, cancel, toDiscord) + wg.Add(1) + go func() { + defer wg.Done() + b.MumbleStream.fromMumbleMixer(ctx, cancel, toDiscord) + }() // From Discord b.DiscordStream = &DiscordDuplex{ @@ -191,15 +195,28 @@ func (b *BridgeState) StartBridge() { fromDiscordMap: make(map[uint32]fromDiscord), } - go b.DiscordStream.discordReceivePCM(ctx, &wg, cancel) - go b.DiscordStream.fromDiscordMixer(ctx, &wg, toMumble) + wg.Add(1) + go func() { + defer wg.Done() + b.DiscordStream.discordReceivePCM(ctx, cancel) + }() + wg.Add(1) + go func() { + defer wg.Done() + b.DiscordStream.fromDiscordMixer(ctx, toMumble) + }() // To Discord - go b.DiscordStream.discordSendPCM(ctx, &wg, cancel, toDiscord) + wg.Add(1) + go func() { + defer wg.Done() + b.DiscordStream.discordSendPCM(ctx, cancel, toDiscord) + }() // Monitor Mumble wg.Add(1) go func() { + defer wg.Done() ticker := time.NewTicker(500 * time.Millisecond) for { select { @@ -213,7 +230,6 @@ func (b *BridgeState) StartBridge() { cancel() } case <-ctx.Done(): - wg.Done() return } } diff --git a/internal/bridge/discord.go b/internal/bridge/discord.go index c50590a..de2722a 100644 --- a/internal/bridge/discord.go +++ b/internal/bridge/discord.go @@ -47,7 +47,7 @@ var OnError = func(str string, err error) { // SendPCM will receive on the provied channel encode // received PCM data into Opus then send that to Discordgo -func (dd *DiscordDuplex) discordSendPCM(ctx context.Context, wg *sync.WaitGroup, cancel context.CancelFunc, pcm <-chan []int16) { +func (dd *DiscordDuplex) discordSendPCM(ctx context.Context, cancel context.CancelFunc, pcm <-chan []int16) { const channels int = 1 const frameRate int = 48000 // audio sampling rate const frameSize int = 960 // uint16 size of each audio frame @@ -70,8 +70,6 @@ func (dd *DiscordDuplex) discordSendPCM(ctx context.Context, wg *sync.WaitGroup, var readyTimeout *time.Timer var speakingStart time.Time - wg.Add(1) - internalSend := func(opus []byte) { dd.Bridge.DiscordVoice.RWMutex.RLock() if !dd.Bridge.DiscordVoice.Ready || dd.Bridge.DiscordVoice.OpusSend == nil { @@ -97,7 +95,7 @@ func (dd *DiscordDuplex) discordSendPCM(ctx context.Context, wg *sync.WaitGroup, for { select { case <-ctx.Done(): - wg.Done() + log.Println("Stopping Discord send PCM") return default: } @@ -151,7 +149,7 @@ func (dd *DiscordDuplex) discordSendPCM(ctx context.Context, wg *sync.WaitGroup, // ReceivePCM will receive on the the Discordgo OpusRecv channel and decode // the opus audio into PCM then send it on the provided channel. -func (dd *DiscordDuplex) discordReceivePCM(ctx context.Context, wg *sync.WaitGroup, cancel context.CancelFunc) { +func (dd *DiscordDuplex) discordReceivePCM(ctx context.Context, cancel context.CancelFunc) { var err error lastReady := true @@ -162,8 +160,6 @@ func (dd *DiscordDuplex) discordReceivePCM(ctx context.Context, wg *sync.WaitGro zeros[i] = 0 } - wg.Add(1) - for { dd.Bridge.DiscordVoice.RWMutex.RLock() if !dd.Bridge.DiscordVoice.Ready || dd.Bridge.DiscordVoice.OpusRecv == nil { @@ -188,7 +184,7 @@ func (dd *DiscordDuplex) discordReceivePCM(ctx context.Context, wg *sync.WaitGro select { case <-ctx.Done(): - wg.Done() + log.Println("Stopping Discord receive PCM") return case p, ok = <-dd.Bridge.DiscordVoice.OpusRecv: } @@ -268,7 +264,7 @@ func (dd *DiscordDuplex) discordReceivePCM(ctx context.Context, wg *sync.WaitGro } } -func (dd *DiscordDuplex) fromDiscordMixer(ctx context.Context, wg *sync.WaitGroup, toMumble chan<- gumble.AudioBuffer) { +func (dd *DiscordDuplex) fromDiscordMixer(ctx context.Context, toMumble chan<- gumble.AudioBuffer) { mumbleSilence := gumble.AudioBuffer{} for i := 3; i < 480; i++ { mumbleSilence = append(mumbleSilence, 0x00) @@ -279,12 +275,11 @@ func (dd *DiscordDuplex) fromDiscordMixer(ctx context.Context, wg *sync.WaitGrou sendAudio := false toMumbleStreaming := false - wg.Add(1) for { select { case <-ctx.Done(): - wg.Done() + log.Println("Stopping from Discord mixer") return default: } diff --git a/internal/bridge/mumble.go b/internal/bridge/mumble.go index ec4c3a4..36ffea6 100644 --- a/internal/bridge/mumble.go +++ b/internal/bridge/mumble.go @@ -51,7 +51,7 @@ func (m MumbleDuplex) OnAudioStream(e *gumble.AudioStreamEvent) { }() } -func (m MumbleDuplex) fromMumbleMixer(ctx context.Context, wg *sync.WaitGroup, cancel context.CancelFunc, toDiscord chan []int16) { +func (m MumbleDuplex) fromMumbleMixer(ctx context.Context, cancel context.CancelFunc, toDiscord chan []int16) { mumbleSleepTick.Start(10 * time.Millisecond) sendAudio := false @@ -60,12 +60,10 @@ func (m MumbleDuplex) fromMumbleMixer(ctx context.Context, wg *sync.WaitGroup, c droppingPackets := false droppingPacketCount := 0 - wg.Add(1) - for { select { case <-ctx.Done(): - wg.Done() + log.Println("Stopping From Mumble Mixer") return default: } From c94362581aad3419afda9b8bfbecc097070ac110 Mon Sep 17 00:00:00 2001 From: Tyler Stiene Date: Fri, 19 Nov 2021 00:57:20 -0500 Subject: [PATCH 07/13] refactor global variables into duplex structs to fix issue on bridge restart disable timer pausing in discord send untill better notification method can be developed --- internal/bridge/bridge.go | 11 +++---- internal/bridge/discord.go | 53 ++++++++++++++++++++++-------- internal/bridge/mumble.go | 66 ++++++++++++++++++++------------------ 3 files changed, 79 insertions(+), 51 deletions(-) diff --git a/internal/bridge/bridge.go b/internal/bridge/bridge.go index 80765ae..20917b1 100644 --- a/internal/bridge/bridge.go +++ b/internal/bridge/bridge.go @@ -141,7 +141,7 @@ func (b *BridgeState) StartBridge() { // MUMBLE Connect - b.MumbleStream = &MumbleDuplex{} + b.MumbleStream = NewMumbleDuplex() det := b.BridgeConfig.MumbleConfig.AudioListeners.Attach(b.MumbleStream) defer det.Detach() @@ -180,6 +180,9 @@ func (b *BridgeState) StartBridge() { defer close(toDiscord) defer close(toMumble) + // From Discord + b.DiscordStream = NewDiscordDuplex(b) + // Start Passing Between // From Mumble @@ -189,12 +192,6 @@ func (b *BridgeState) StartBridge() { b.MumbleStream.fromMumbleMixer(ctx, cancel, toDiscord) }() - // From Discord - b.DiscordStream = &DiscordDuplex{ - Bridge: b, - fromDiscordMap: make(map[uint32]fromDiscord), - } - wg.Add(1) go func() { defer wg.Done() diff --git a/internal/bridge/discord.go b/internal/bridge/discord.go index de2722a..09cd1b5 100644 --- a/internal/bridge/discord.go +++ b/internal/bridge/discord.go @@ -26,12 +26,20 @@ type fromDiscord struct { type DiscordDuplex struct { Bridge *BridgeState - discordMutex sync.Mutex - fromDiscordMap map[uint32]fromDiscord + discordMutex sync.Mutex + fromDiscordMap map[uint32]fromDiscord + discordSendSleepTick sleepct.SleepCT + discordReceiveSleepTick sleepct.SleepCT } -var discrodSendSleepTick sleepct.SleepCT = sleepct.SleepCT{} -var discrodReceiveSleepTick sleepct.SleepCT = sleepct.SleepCT{} +func NewDiscordDuplex(b *BridgeState) *DiscordDuplex { + return &DiscordDuplex{ + Bridge: b, + fromDiscordMap: make(map[uint32]fromDiscord), + discordSendSleepTick: sleepct.SleepCT{}, + discordReceiveSleepTick: sleepct.SleepCT{}, + } +} // OnError gets called by dgvoice when an error is encountered. // By default logs to STDERR @@ -46,7 +54,7 @@ var OnError = func(str string, err error) { } // SendPCM will receive on the provied channel encode -// received PCM data into Opus then send that to Discordgo +// received PCM data with Opus then send that to Discordgo func (dd *DiscordDuplex) discordSendPCM(ctx context.Context, cancel context.CancelFunc, pcm <-chan []int16) { const channels int = 1 const frameRate int = 48000 // audio sampling rate @@ -64,12 +72,28 @@ func (dd *DiscordDuplex) discordSendPCM(ctx context.Context, cancel context.Canc // Generate Opus Silence Frame opusSilence := []byte{0xf8, 0xff, 0xfe} - discrodSendSleepTick.Start(20 * time.Millisecond) + dd.discordSendSleepTick.Start(20 * time.Millisecond) lastReady := true var readyTimeout *time.Timer var speakingStart time.Time + // Spy on the PCM channel to notify + // TODO determine a method to notify a paused sleepct + // pcm := make(chan []int16, 10) + // go func() { + // for { + // t, ok := <-pcmIn + // if !ok { + // close(pcm) + // return + // } else { + // dd.discordSendSleepTick.Notify() + // pcm <- t + // } + // } + // }() + internalSend := func(opus []byte) { dd.Bridge.DiscordVoice.RWMutex.RLock() if !dd.Bridge.DiscordVoice.Ready || dd.Bridge.DiscordVoice.OpusSend == nil { @@ -101,7 +125,8 @@ func (dd *DiscordDuplex) discordSendPCM(ctx context.Context, cancel context.Canc } // if we are not streaming try to pause - promTimerDiscordSend.Observe(float64(discrodSendSleepTick.SleepNextTarget(ctx, !streaming))) + // promTimerDiscordSend.Observe(float64(dd.discordSendSleepTick.SleepNextTarget(ctx, !streaming))) + promTimerDiscordSend.Observe(float64(dd.discordSendSleepTick.SleepNextTarget(ctx, false))) if (len(pcm) > 1 && streaming) || (len(pcm) > dd.Bridge.BridgeConfig.DiscordStartStreamingCount && !streaming) { if !streaming { @@ -128,7 +153,7 @@ func (dd *DiscordDuplex) discordSendPCM(ctx context.Context, cancel context.Canc // It is possible that short speaking cycle is the result of a short input to mumble (Not a problem). ie a quick tap of push to talk button. // Or when timing delays are introduced via network, hardware or kernel delays (Problem). // The problem delays result in choppy or stuttering sounds, especially when the silence frames are introduced into the opus frames below. - // Multiple short cycle delays can result in a Discrod rate limiter being trigger due to of multiple JSON speaking/not-speaking state changes + // Multiple short cycle delays can result in a discord rate limiter being trigger due to of multiple JSON speaking/not-speaking state changes if time.Since(speakingStart).Milliseconds() < 50 { log.Println("Warning: Short Mumble to Discord speaking cycle. Consider increaseing the size of the to Discord jitter buffer.") } @@ -137,7 +162,9 @@ func (dd *DiscordDuplex) discordSendPCM(ctx context.Context, cancel context.Canc // We want to do this after alerting the user of possible short speaking cycles for i := 0; i < 5; i++ { internalSend(opusSilence) - promTimerDiscordSend.Observe(float64(discrodSendSleepTick.SleepNextTarget(ctx, true))) + // promTimerDiscordSend.Observe(float64(dd.discordSendSleepTick.SleepNextTarget(ctx, true))) + promTimerDiscordSend.Observe(float64(dd.discordSendSleepTick.SleepNextTarget(ctx, false))) + } dd.Bridge.DiscordVoice.Speaking(false) @@ -260,7 +287,7 @@ func (dd *DiscordDuplex) discordReceivePCM(ctx context.Context, cancel context.C } dd.discordMutex.Unlock() - discrodReceiveSleepTick.Notify() + dd.discordReceiveSleepTick.Notify() } } @@ -271,7 +298,7 @@ func (dd *DiscordDuplex) fromDiscordMixer(ctx context.Context, toMumble chan<- g } var speakingStart time.Time - discrodReceiveSleepTick.Start(10 * time.Millisecond) + dd.discordReceiveSleepTick.Start(10 * time.Millisecond) sendAudio := false toMumbleStreaming := false @@ -285,7 +312,7 @@ func (dd *DiscordDuplex) fromDiscordMixer(ctx context.Context, toMumble chan<- g } // if didn't send audio try to pause - promTimerDiscordMixer.Observe(float64(discrodReceiveSleepTick.SleepNextTarget(ctx, !sendAudio))) + promTimerDiscordMixer.Observe(float64(dd.discordReceiveSleepTick.SleepNextTarget(ctx, !sendAudio))) dd.discordMutex.Lock() @@ -364,7 +391,7 @@ func (dd *DiscordDuplex) fromDiscordMixer(ctx context.Context, toMumble chan<- g for i := 0; i < 5; i++ { mumbleTimeoutSend(mumbleSilence) - promTimerDiscordMixer.Observe(float64(discrodReceiveSleepTick.SleepNextTarget(ctx, false))) + promTimerDiscordMixer.Observe(float64(dd.discordReceiveSleepTick.SleepNextTarget(ctx, false))) } toMumbleStreaming = false diff --git a/internal/bridge/mumble.go b/internal/bridge/mumble.go index 36ffea6..1132562 100644 --- a/internal/bridge/mumble.go +++ b/internal/bridge/mumble.go @@ -12,27 +12,34 @@ import ( "github.com/stieneee/mumble-discord-bridge/pkg/sleepct" ) -var mutex sync.Mutex -var fromMumbleArr []chan gumble.AudioBuffer -var mumbleStreamingArr []bool +// MumbleDuplex - listener and outgoing +type MumbleDuplex struct { + mutex sync.Mutex + fromMumbleArr []chan gumble.AudioBuffer + mumbleStreamingArr []bool + mumbleSleepTick sleepct.SleepCT +} -// MumbleDuplex - listenera and outgoing -type MumbleDuplex struct{} - -var mumbleSleepTick sleepct.SleepCT = sleepct.SleepCT{} +func NewMumbleDuplex() *MumbleDuplex { + return &MumbleDuplex{ + fromMumbleArr: make([]chan gumble.AudioBuffer, 0), + mumbleStreamingArr: make([]bool, 0), + mumbleSleepTick: sleepct.SleepCT{}, + } +} // OnAudioStream - Spawn routines to handle incoming packets -func (m MumbleDuplex) OnAudioStream(e *gumble.AudioStreamEvent) { +func (m *MumbleDuplex) OnAudioStream(e *gumble.AudioStreamEvent) { // hold a reference ot the channel in the closure - localMumbleArray := make(chan gumble.AudioBuffer, 100) + streamChan := make(chan gumble.AudioBuffer, 100) - mutex.Lock() - fromMumbleArr = append(fromMumbleArr, localMumbleArray) - mumbleStreamingArr = append(mumbleStreamingArr, false) - mutex.Unlock() + m.mutex.Lock() + m.fromMumbleArr = append(m.fromMumbleArr, streamChan) + m.mumbleStreamingArr = append(m.mumbleStreamingArr, false) + m.mutex.Unlock() - promMumbleArraySize.Set(float64(len(fromMumbleArr))) + promMumbleArraySize.Set(float64(len(m.fromMumbleArr))) go func() { name := e.User.Name @@ -42,17 +49,17 @@ func (m MumbleDuplex) OnAudioStream(e *gumble.AudioStreamEvent) { // 480 per 10ms for i := 0; i < len(p.AudioBuffer)/480; i++ { - localMumbleArray <- p.AudioBuffer[480*i : 480*(i+1)] + streamChan <- p.AudioBuffer[480*i : 480*(i+1)] } promReceivedMumblePackets.Inc() - mumbleSleepTick.Notify() + m.mumbleSleepTick.Notify() } log.Println("Mumble audio stream ended", name) }() } -func (m MumbleDuplex) fromMumbleMixer(ctx context.Context, cancel context.CancelFunc, toDiscord chan []int16) { - mumbleSleepTick.Start(10 * time.Millisecond) +func (m *MumbleDuplex) fromMumbleMixer(ctx context.Context, cancel context.CancelFunc, toDiscord chan []int16) { + m.mumbleSleepTick.Start(10 * time.Millisecond) sendAudio := false bufferWarning := false @@ -68,36 +75,35 @@ func (m MumbleDuplex) fromMumbleMixer(ctx context.Context, cancel context.Cancel default: } - // if we sent audio on the last pass attempt to pause - promTimerMumbleMixer.Observe(float64(mumbleSleepTick.SleepNextTarget(ctx, !sendAudio))) + promTimerMumbleMixer.Observe(float64(m.mumbleSleepTick.SleepNextTarget(ctx, false))) - mutex.Lock() + m.mutex.Lock() sendAudio = false internalMixerArr := make([]gumble.AudioBuffer, 0) streamingCount := 0 // Work through each channel - for i := 0; i < len(fromMumbleArr); i++ { - if len(fromMumbleArr[i]) > 0 { + for i := 0; i < len(m.fromMumbleArr); i++ { + if len(m.fromMumbleArr[i]) > 0 { sendAudio = true - if !mumbleStreamingArr[i] { - mumbleStreamingArr[i] = true + if !m.mumbleStreamingArr[i] { + m.mumbleStreamingArr[i] = true streamingCount++ // log.Println("Mumble starting", i) } - x1 := (<-fromMumbleArr[i]) + x1 := (<-m.fromMumbleArr[i]) internalMixerArr = append(internalMixerArr, x1) } else { - if mumbleStreamingArr[i] { - mumbleStreamingArr[i] = false + if m.mumbleStreamingArr[i] { + m.mumbleStreamingArr[i] = false // log.Println("Mumble stopping", i) } } } - mutex.Unlock() + m.mutex.Unlock() promMumbleStreaming.Set(float64(streamingCount)) @@ -145,8 +151,6 @@ func (m MumbleDuplex) fromMumbleMixer(ctx context.Context, cancel context.Cancel cancel() } } - - discrodSendSleepTick.Notify() } } } From fd4884cdb65d53c5c83aa6d1be82c26c5c910633 Mon Sep 17 00:00:00 2001 From: Tyler Stiene Date: Sat, 20 Nov 2021 16:58:39 -0500 Subject: [PATCH 08/13] from to discord buffer warning --- internal/bridge/mumble.go | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/internal/bridge/mumble.go b/internal/bridge/mumble.go index 1132562..c97ac91 100644 --- a/internal/bridge/mumble.go +++ b/internal/bridge/mumble.go @@ -62,7 +62,6 @@ func (m *MumbleDuplex) fromMumbleMixer(ctx context.Context, cancel context.Cance m.mumbleSleepTick.Start(10 * time.Millisecond) sendAudio := false - bufferWarning := false droppingPackets := false droppingPacketCount := 0 @@ -117,18 +116,6 @@ func (m *MumbleDuplex) fromMumbleMixer(ctx context.Context, cancel context.Cance } } - if len(toDiscord) > 20 { - if !bufferWarning { - log.Println("Warning: toDiscord buffer size") - bufferWarning = true - } - } else { - if bufferWarning { - log.Println("Resolved: toDiscord buffer size") - bufferWarning = false - } - } - promToDiscordBufferSize.Set(float64(len(toDiscord))) select { case toDiscord <- outBuf: From 404af876db0d3ecd3b5b385e0412f4faad64e53e Mon Sep 17 00:00:00 2001 From: Tyler Stiene Date: Sat, 20 Nov 2021 17:12:04 -0500 Subject: [PATCH 09/13] dsiable sleepct pausing untill additional testing --- internal/bridge/discord.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/bridge/discord.go b/internal/bridge/discord.go index 09cd1b5..7cf3f01 100644 --- a/internal/bridge/discord.go +++ b/internal/bridge/discord.go @@ -312,7 +312,9 @@ func (dd *DiscordDuplex) fromDiscordMixer(ctx context.Context, toMumble chan<- g } // if didn't send audio try to pause - promTimerDiscordMixer.Observe(float64(dd.discordReceiveSleepTick.SleepNextTarget(ctx, !sendAudio))) + // promTimerDiscordMixer.Observe(float64(dd.discordReceiveSleepTick.SleepNextTarget(ctx, !sendAudio))) + // TODO Additional pause testing + promTimerDiscordMixer.Observe(float64(dd.discordReceiveSleepTick.SleepNextTarget(ctx, false))) dd.discordMutex.Lock() From 7dea05bdb4c1ded3c6d99d799e9fe378ff4e203e Mon Sep 17 00:00:00 2001 From: Tyler Stiene Date: Sat, 20 Nov 2021 20:22:14 -0500 Subject: [PATCH 10/13] remove recover statement from constant bridge loop --- cmd/mumble-discord-bridge/main.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/cmd/mumble-discord-bridge/main.go b/cmd/mumble-discord-bridge/main.go index 1758e66..6d79b20 100644 --- a/cmd/mumble-discord-bridge/main.go +++ b/cmd/mumble-discord-bridge/main.go @@ -207,11 +207,6 @@ func main() { Bridge.Mode = bridge.BridgeModeConstant Bridge.DiscordChannelID = Bridge.BridgeConfig.CID go func() { - defer func() { - if r := recover(); r != nil { - fmt.Println("Bridge paniced", r) - } - }() for { Bridge.StartBridge() log.Println("Bridge died") From 3e96b27564b4a16b8f93418101ac0f2c0454ece8 Mon Sep 17 00:00:00 2001 From: Tyler Stiene Date: Tue, 7 Dec 2021 22:59:04 -0500 Subject: [PATCH 11/13] allow discord send loop to exit with ctx done --- internal/bridge/bridge.go | 2 +- internal/bridge/discord.go | 6 +++++- internal/bridge/prom.go | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/internal/bridge/bridge.go b/internal/bridge/bridge.go index 20917b1..c401949 100644 --- a/internal/bridge/bridge.go +++ b/internal/bridge/bridge.go @@ -210,7 +210,7 @@ func (b *BridgeState) StartBridge() { b.DiscordStream.discordSendPCM(ctx, cancel, toDiscord) }() - // Monitor Mumble + // Monitor wg.Add(1) go func() { defer wg.Done() diff --git a/internal/bridge/discord.go b/internal/bridge/discord.go index 7cf3f01..2f41f6a 100644 --- a/internal/bridge/discord.go +++ b/internal/bridge/discord.go @@ -110,7 +110,11 @@ func (dd *DiscordDuplex) discordSendPCM(ctx context.Context, cancel context.Canc lastReady = true readyTimeout.Stop() } else { - dd.Bridge.DiscordVoice.OpusSend <- opus + select { + case dd.Bridge.DiscordVoice.OpusSend <- opus: + case <-ctx.Done(): + } + promDiscordSentPackets.Inc() } dd.Bridge.DiscordVoice.RWMutex.RUnlock() diff --git a/internal/bridge/prom.go b/internal/bridge/prom.go index 545b7a0..af56eb8 100644 --- a/internal/bridge/prom.go +++ b/internal/bridge/prom.go @@ -85,7 +85,7 @@ var ( promDiscordReceivedPackets = promauto.NewCounter(prometheus.CounterOpts{ Name: "mdb_discord_received_count", - Help: "The number of received packed from Discord", + Help: "The number of received packets from Discord", }) promDiscordSentPackets = promauto.NewCounter(prometheus.CounterOpts{ From 3d24e1dbc444b7daaa455ae0e3665f403288be19 Mon Sep 17 00:00:00 2001 From: Tyler Stiene Date: Sun, 12 Dec 2021 23:54:32 -0500 Subject: [PATCH 12/13] build and deploy improvements --- .github/workflows/build-release.yml | 16 +++++++++++-- Dockerfile | 9 ++++--- Makefile | 1 + go.sum | 37 ----------------------------- 4 files changed, 21 insertions(+), 42 deletions(-) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index f84eda8..25cf828 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -20,7 +20,13 @@ jobs: name: Set up Go uses: actions/setup-go@v2 with: - go-version: 1.15 + go-version: 1.17 + - + name: go-license install + run: go get github.com/google/go-licenses + - + name: go-license save + run: go-licenses save ./cmd/mumble-discord-bridge --force --save_path="./dist/LICENSES" - name: Run GoReleaser uses: goreleaser/goreleaser-action@v2 @@ -29,4 +35,10 @@ jobs: version: latest args: release --rm-dist env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - + name: Upload assets + uses: actions/upload-artifact@v2 + with: + name: mdb + path: dist/* \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 2b04d76..8cd6d36 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,12 +2,14 @@ # Stage 1 -FROM golang:1.16 as builder +FROM golang:1.17 as builder WORKDIR /go/src/app COPY . . -RUN curl -sfL https://install.goreleaser.com/github.com/goreleaser/goreleaser.sh | sh RUN apt update && apt install -y libopus-dev -RUN ./bin/goreleaser build --skip-validate +RUN go install github.com/goreleaser/goreleaser@latest +RUN go install github.com/google/go-licenses@latest +RUN goreleaser build --skip-validate +RUN go-licenses save ./cmd/mumble-discord-bridge --force --save_path="./dist/LICENSES" # Stage 2 @@ -15,6 +17,7 @@ FROM alpine:latest as final WORKDIR /opt/ RUN apk add opus RUN mkdir /lib64 && ln -s /lib/libc.musl-x86_64.so.1 /lib64/ld-linux-x86-64.so.2 +COPY --from=builder /go/src/app/dist/LICENSES . COPY --from=builder /go/src/app/dist/mumble-discord-bridge_linux_amd64/mumble-discord-bridge . # FROM ubuntu:latest as final diff --git a/Makefile b/Makefile index 21de543..54347e0 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,7 @@ GOFILES=$(shell find ./ -type f -name '*.go') mumble-discord-bridge: $(GOFILES) .goreleaser.yml goreleaser build --skip-validate --rm-dist + go-licenses save ./cmd/mumble-discord-bridge --force --save_path="./dist/LICENSES" dev: $(GOFILES) .goreleaser.yml goreleaser build --skip-validate --rm-dist && sudo ./dist/mumble-discord-bridge_linux_amd64/mumble-discord-bridge diff --git a/go.sum b/go.sum index 251e830..0e96510 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,8 @@ -cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= @@ -16,22 +13,15 @@ github.com/bwmarrin/discordgo v0.23.3-0.20210512035133-7d7206b01bb5/go.mod h1:OM github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 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/dchote/go-openal v0.0.0-20171116030048-f4a9a141d372 h1:tz3KnXWtRZR0RWOfcMNOw+HHezWLQa7vfSOWTtKjchI= github.com/dchote/go-openal v0.0.0-20171116030048-f4a9a141d372/go.mod h1:74z+CYu2/mx4N+mcIS/rsvfAxBPBV9uv8zRAnwyFkdI= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/log v0.1.0 h1:DGJh0Sm43HbOeYDNnVZFl8BvcYVvjD5bqYJvp0REbwQ= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -52,48 +42,34 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= -github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -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 v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= @@ -115,7 +91,6 @@ github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3x github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/stieneee/gopus v0.0.0-20210424193312-6d10f6090335 h1:yzwz6AqGKysli5du4CrQ48BMGUCSkrl7V7Kbo9VaG8w= github.com/stieneee/gopus v0.0.0-20210424193312-6d10f6090335/go.mod h1:tAKYr3fSBJGold7c9DMPlhupn9oy8hTgl3cZ0hoyRQs= @@ -124,11 +99,9 @@ github.com/stieneee/gumble v0.0.0-20210424210604-732f48b5e0de/go.mod h1:hVIsmrlr github.com/stieneee/tickerct v0.0.0-20210420020607-d1b092aa40e9 h1:0l2H6Oj6JHMmkqm9xaBMQA5MOGhPT+Nn/thlTUcD9Iw= github.com/stieneee/tickerct v0.0.0-20210420020607-d1b092aa40e9/go.mod h1:54+oZlabriEpT52rPAjAeEWUFgYqv325LrS3hNxHGFE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -141,14 +114,11 @@ golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -163,16 +133,12 @@ golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40 h1:JWgyZ1qgdTaF3N3oxC+MdTV7qvEEgHo3otj+HB5CM7Q= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -183,14 +149,11 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From 08b4eb377112648fce95b672348e87cdd5ef4da1 Mon Sep 17 00:00:00 2001 From: Tyler Stiene Date: Mon, 13 Dec 2021 00:10:48 -0500 Subject: [PATCH 13/13] flag to disable discord bot status --- cmd/mumble-discord-bridge/main.go | 2 ++ internal/bridge/bridge.go | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/cmd/mumble-discord-bridge/main.go b/cmd/mumble-discord-bridge/main.go index 6d79b20..de63df0 100644 --- a/cmd/mumble-discord-bridge/main.go +++ b/cmd/mumble-discord-bridge/main.go @@ -50,6 +50,7 @@ func main() { discordSendBuffer := flag.Int("to-discord-buffer", lookupEnvOrInt("TO_DISCORD_BUFFER", 50), "TO_DISCORD_BUFFER, Jitter buffer from Mumble to Discord to absorb timing issues related to network, OS and hardware quality. (Increments of 10ms)") discordCommand := flag.String("discord-command", lookupEnvOrString("DISCORD_COMMAND", "mumble-discord"), "DISCORD_COMMAND, Discord command string, env alt DISCORD_COMMAND, optional, (defaults mumble-discord)") discordDisableText := flag.Bool("discord-disable-text", lookupEnvOrBool("DISCORD_DISABLE_TEXT", false), "DISCORD_DISABLE_TEXT, disable sending direct messages to discord, (default false)") + discordDisableBotStatus := flag.Bool("discord-disable-bot-status", lookupEnvOrBool("DISCORD_DISABLE_BOT_STATUS", false), "DISCORD_DISABLE_BOT_STATUS, disable updating bot status, (default false)") mode := flag.String("mode", lookupEnvOrString("MODE", "constant"), "MODE, [constant, manual, auto] determine which mode the bridge starts in, (default constant)") nice := flag.Bool("nice", lookupEnvOrBool("NICE", false), "NICE, whether the bridge should automatically try to 'nice' itself, (default false)") debug := flag.Int("debug-level", lookupEnvOrInt("DEBUG", 1), "DEBUG_LEVEL, Discord debug level, optional, (default 1)") @@ -135,6 +136,7 @@ func main() { CID: *discordCID, DiscordStartStreamingCount: discordStartStreamingCount, DiscordDisableText: *discordDisableText, + DiscordDisableBotStatus: *discordDisableBotStatus, Version: version, }, Connected: false, diff --git a/internal/bridge/bridge.go b/internal/bridge/bridge.go index c401949..90b0ce1 100644 --- a/internal/bridge/bridge.go +++ b/internal/bridge/bridge.go @@ -42,6 +42,7 @@ type BridgeConfig struct { CID string DiscordStartStreamingCount int DiscordDisableText bool + DiscordDisableBotStatus bool Version string } @@ -288,7 +289,9 @@ func (b *BridgeState) DiscordStatusUpdate() { } b.BridgeMutex.Unlock() b.MumbleUsersMutex.Unlock() - b.DiscordSession.UpdateListeningStatus(status) + if !b.BridgeConfig.DiscordDisableBotStatus { + b.DiscordSession.UpdateListeningStatus(status) + } } discordHeartBeat := b.DiscordSession.LastHeartbeatAck.Sub(b.DiscordSession.LastHeartbeatSent).Milliseconds()