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 69% 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 0000000..538f261 Binary files /dev/null and b/example/grafana-dashboard.png differ 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() }