diff --git a/.docker-compose_cfg/config.yaml b/.docker-compose_cfg/config.yaml
deleted file mode 100644
index b3b7d73d5..000000000
--- a/.docker-compose_cfg/config.yaml
+++ /dev/null
@@ -1,79 +0,0 @@
-# Copyright © 2023 OpenIM. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-#---------------Infrastructure configuration---------------------#
-etcd:
- etcdSchema: openim #默认即可
- etcdAddr: [ 127.0.0.1:2379 ] #单机部署时,默认即可
- userName:
- password:
- secret: openIM123
-
-mysql:
- dbMysqlDatabaseName: admin_chat # 数据库名字 默认即可
-
-# 默认管理员账号
-admin:
- defaultAccount:
- account: [ "admin1", "admin2" ]
- defaultPassword: [ "password1", "password2" ]
- openIMUserID: [ "openIM123456", "openIMAdmin" ]
- faceURL: [ "", "" ]
- nickname: [ "admin1", "admin2" ]
- level: [ 1, 100 ]
-
-
-adminapi:
- openImAdminApiPort: [ 10009 ] #管理后台api服务端口,默认即可,需要开放此端口或做nginx转发
- listenIP: 0.0.0.0
-
-chatapi:
- openImChatApiPort: [ 10008 ] #登录注册,默认即可,需要开放此端口或做nginx转发
- listenIP: 0.0.0.0
-
-rpcport: # rpc服务端口 默认即可
- openImAdminPort: [ 30200 ]
- openImChatPort: [ 30300 ]
-
-
-rpcregistername: #rpc注册服务名,默认即可
- openImChatName: Chat
- openImAdminCMSName: Admin
-
-chat:
- codeTTL: 300 #短信验证码有效时间(秒)
- superVerificationCode: 666666 # 超级验证码
- alismsverify: #阿里云短信配置,在阿里云申请成功后修改以下四项
- accessKeyId:
- accessKeySecret:
- signName:
- verificationCodeTemplateCode:
-
-
-oss:
- tempDir: enterprise-temp # 临时密钥上传的目录
- dataDir: enterprise-data # 最终存放目录
- aliyun:
- endpoint: https://oss-cn-chengdu.aliyuncs.com
- accessKeyID: ""
- accessKeySecret: ""
- bucket: ""
- tencent:
- BucketURL: ""
- serviceURL: https://cos.COS_REGION.myqcloud.com
- secretID: ""
- secretKey: ""
- sessionToken: ""
- bucket: ""
- use: "minio"
\ No newline at end of file
diff --git a/.docker-compose_cfg/datasource-compose.yaml b/.docker-compose_cfg/datasource-compose.yaml
deleted file mode 100644
index 2be73952d..000000000
--- a/.docker-compose_cfg/datasource-compose.yaml
+++ /dev/null
@@ -1,27 +0,0 @@
-# Copyright © 2023 OpenIM. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-#more datasource-compose.yaml
-apiVersion: 1
-
-datasources:
-- name: Prometheus
- type: prometheus
- access: proxy
- orgId: 1
- url: http://127.0.0.1:9091
- basicAuth: false
- isDefault: true
- version: 1
- editable: true
\ No newline at end of file
diff --git a/.docker-compose_cfg/grafana.db b/.docker-compose_cfg/grafana.db
deleted file mode 100644
index 362adfe67..000000000
Binary files a/.docker-compose_cfg/grafana.db and /dev/null differ
diff --git a/.docker-compose_cfg/grafana.ini b/.docker-compose_cfg/grafana.ini
deleted file mode 100644
index 8c1d9dd11..000000000
--- a/.docker-compose_cfg/grafana.ini
+++ /dev/null
@@ -1,1285 +0,0 @@
-##################### Grafana Configuration Defaults #####################
-#
-# Do not modify this file in grafana installs
-#
-
-# possible values : production, development
-app_mode = production
-
-# instance name, defaults to HOSTNAME environment variable value or hostname if HOSTNAME var is empty
-instance_name = ${HOSTNAME}
-
-# force migration will run migrations that might cause dataloss
-force_migration = false
-
-#################################### Paths ###############################
-[paths]
-# Path to where grafana can store temp files, sessions, and the sqlite3 db (if that is used)
-data = data
-
-# Temporary files in `data` directory older than given duration will be removed
-temp_data_lifetime = 24h
-
-# Directory where grafana can store logs
-logs = data/log
-
-# Directory where grafana will automatically scan and look for plugins
-plugins = data/plugins
-
-# folder that contains provisioning config files that grafana will apply on startup and while running.
-provisioning = conf/provisioning
-
-#################################### Server ##############################
-[server]
-# Protocol (http, https, h2, socket)
-protocol = http
-
-# The ip address to bind to, empty will bind to all interfaces
-http_addr =
-
-# The http port to use
-http_port = 10007
-
-# The public facing domain name used to access grafana from a browser
-domain = localhost
-
-# Redirect to correct domain if host header does not match domain
-# Prevents DNS rebinding attacks
-enforce_domain = false
-
-# The full public facing url
-root_url = %(protocol)s://%(domain)s:%(http_port)s/
-
-# Serve Grafana from subpath specified in `root_url` setting. By default it is set to `false` for compatibility reasons.
-serve_from_sub_path = false
-
-# Log web requests
-router_logging = false
-
-# the path relative working path
-static_root_path = public
-
-# enable gzip
-enable_gzip = false
-
-# https certs & key file
-cert_file =
-cert_key =
-
-# Unix socket path
-socket = /tmp/grafana.sock
-
-# CDN Url
-cdn_url =
-
-# Sets the maximum time in minutes before timing out read of an incoming request and closing idle connections.
-# `0` means there is no timeout for reading the request.
-read_timeout = 0
-
-#################################### Database ############################
-[database]
-# You can configure the database connection by specifying type, host, name, user and password
-# as separate properties or as on string using the url property.
-
-# Either "mysql", "postgres" or "sqlite3", it's your choice
-type = sqlite3
-host = 127.0.0.1:3306
-name = grafana
-user = root
-# If the password contains # or ; you have to wrap it with triple quotes. Ex """#password;"""
-password =
-# Use either URL or the previous fields to configure the database
-# Example: mysql://user:secret@host:port/database
-url =
-
-# Max idle conn setting default is 2
-max_idle_conn = 2
-
-# Max conn setting default is 0 (mean not set)
-max_open_conn =
-
-# Connection Max Lifetime default is 14400 (means 14400 seconds or 4 hours)
-conn_max_lifetime = 14400
-
-# Set to true to log the sql calls and execution times.
-log_queries =
-
-# For "postgres", use either "disable", "require" or "verify-full"
-# For "mysql", use either "true", "false", or "skip-verify".
-ssl_mode = disable
-
-# Database drivers may support different transaction isolation levels.
-# Currently, only "mysql" driver supports isolation levels.
-# If the value is empty - driver's default isolation level is applied.
-# For "mysql" use "READ-UNCOMMITTED", "READ-COMMITTED", "REPEATABLE-READ" or "SERIALIZABLE".
-isolation_level =
-
-ca_cert_path =
-client_key_path =
-client_cert_path =
-server_cert_name =
-
-# For "sqlite3" only, path relative to data_path setting
-path = grafana.db
-
-# For "sqlite3" only. cache mode setting used for connecting to the database
-cache_mode = private
-
-# For "mysql" only if migrationLocking feature toggle is set. How many seconds to wait before failing to lock the database for the migrations, default is 0.
-locking_attempt_timeout_sec = 0
-
-#################################### Cache server #############################
-[remote_cache]
-# Either "redis", "memcached" or "database" default is "database"
-type = database
-
-# cache connectionstring options
-# database: will use Grafana primary database.
-# redis: config like redis server e.g. `addr=127.0.0.1:6379,pool_size=100,db=0,ssl=false`. Only addr is required. ssl may be 'true', 'false', or 'insecure'.
-# memcache: 127.0.0.1:11211
-connstr =
-
-#################################### Data proxy ###########################
-[dataproxy]
-
-# This enables data proxy logging, default is false
-logging = false
-
-# How long the data proxy waits to read the headers of the response before timing out, default is 30 seconds.
-# This setting also applies to core backend HTTP data sources where query requests use an HTTP client with timeout set.
-timeout = 30
-
-# How long the data proxy waits to establish a TCP connection before timing out, default is 10 seconds.
-dialTimeout = 10
-
-# How many seconds the data proxy waits before sending a keepalive request.
-keep_alive_seconds = 30
-
-# How many seconds the data proxy waits for a successful TLS Handshake before timing out.
-tls_handshake_timeout_seconds = 10
-
-# How many seconds the data proxy will wait for a server's first response headers after
-# fully writing the request headers if the request has an "Expect: 100-continue"
-# header. A value of 0 will result in the body being sent immediately, without
-# waiting for the server to approve.
-expect_continue_timeout_seconds = 1
-
-# Optionally limits the total number of connections per host, including connections in the dialing,
-# active, and idle states. On limit violation, dials will block.
-# A value of zero (0) means no limit.
-max_conns_per_host = 0
-
-# The maximum number of idle connections that Grafana will keep alive.
-max_idle_connections = 100
-
-# How many seconds the data proxy keeps an idle connection open before timing out.
-idle_conn_timeout_seconds = 90
-
-# If enabled and user is not anonymous, data proxy will add X-Grafana-User header with username into the request.
-send_user_header = false
-
-# Limit the amount of bytes that will be read/accepted from responses of outgoing HTTP requests.
-response_limit = 0
-
-# Limits the number of rows that Grafana will process from SQL data sources.
-row_limit = 1000000
-
-#################################### Analytics ###########################
-[analytics]
-# Server reporting, sends usage counters to stats.grafana.org every 24 hours.
-# No ip addresses are being tracked, only simple counters to track
-# running instances, dashboard and error counts. It is very helpful to us.
-# Change this option to false to disable reporting.
-reporting_enabled = true
-
-# The name of the distributor of the Grafana instance. Ex hosted-grafana, grafana-labs
-reporting_distributor = grafana-labs
-
-# Set to false to disable all checks to https://grafana.com
-# for new versions of grafana. The check is used
-# in some UI views to notify that a grafana update exists.
-# This option does not cause any auto updates, nor send any information
-# only a GET request to https://raw.githubusercontent.com/grafana/grafana/main/latest.json to get the latest version.
-check_for_updates = true
-
-# Set to false to disable all checks to https://grafana.com
-# for new versions of plugins. The check is used
-# in some UI views to notify that a plugin update exists.
-# This option does not cause any auto updates, nor send any information
-# only a GET request to https://grafana.com to get the latest versions.
-check_for_plugin_updates = true
-
-# Google Analytics universal tracking code, only enabled if you specify an id here
-google_analytics_ua_id =
-
-# Google Tag Manager ID, only enabled if you specify an id here
-google_tag_manager_id =
-
-# Rudderstack write key, enabled only if rudderstack_data_plane_url is also set
-rudderstack_write_key =
-
-# Rudderstack data plane url, enabled only if rudderstack_write_key is also set
-rudderstack_data_plane_url =
-
-# Rudderstack SDK url, optional, only valid if rudderstack_write_key and rudderstack_data_plane_url is also set
-rudderstack_sdk_url =
-
-# Rudderstack Config url, optional, used by Rudderstack SDK to fetch source config
-rudderstack_config_url =
-
-# Application Insights connection string. Specify an URL string to enable this feature.
-application_insights_connection_string =
-
-# Optional. Specifies an Application Insights endpoint URL where the endpoint string is wrapped in backticks ``.
-application_insights_endpoint_url =
-
-# Controls if the UI contains any links to user feedback forms
-feedback_links_enabled = true
-
-#################################### Security ############################
-[security]
-# disable creation of admin user on first start of grafana
-disable_initial_admin_creation = false
-
-# default admin user, created on startup
-admin_user = admin
-
-# default admin password, can be changed before first start of grafana, or in profile settings
-admin_password = admin
-
-# default admin email, created on startup
-admin_email = admin@localhost
-
-# used for signing
-secret_key = SW2YcwTIb9zpOOhoPsMm
-
-# current key provider used for envelope encryption, default to static value specified by secret_key
-encryption_provider = secretKey.v1
-
-# list of configured key providers, space separated (Enterprise only): e.g., awskms.v1 azurekv.v1
-available_encryption_providers =
-
-# disable gravatar profile images
-disable_gravatar = false
-
-# data source proxy whitelist (ip_or_domain:port separated by spaces)
-data_source_proxy_whitelist =
-
-# disable protection against brute force login attempts
-disable_brute_force_login_protection = false
-
-# set to true if you host Grafana behind HTTPS. default is false.
-cookie_secure = false
-
-# set cookie SameSite attribute. defaults to `lax`. can be set to "lax", "strict", "none" and "disabled"
-cookie_samesite = lax
-
-# set to true if you want to allow browsers to render Grafana in a ,
## 🟢 扫描微信进群交流
-
+
## Ⓜ️ 关于 OpenIM
diff --git a/README.md b/README.md
index 722de0240..a2c5fc732 100644
--- a/README.md
+++ b/README.md
@@ -231,7 +231,7 @@ Before you start, please make sure your changes are in demand. The best for that
- [OpenIM Makefile Utilities](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/util-makefile.md)
- [OpenIM Script Utilities](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/util-scripts.md)
- [OpenIM Versioning](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/version.md)
-
+- [Manage backend and monitor deployment](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/prometheus-grafana.md)
## :busts_in_silhouette: Community
diff --git a/config/alertmanager.yml b/config/alertmanager.yml
new file mode 100644
index 000000000..71cdd2b8f
--- /dev/null
+++ b/config/alertmanager.yml
@@ -0,0 +1,32 @@
+###################### AlertManager Configuration ######################
+# AlertManager configuration using environment variables
+#
+# Resolve timeout
+# SMTP configuration for sending alerts
+# Templates for email notifications
+# Routing configurations for alerts
+# Receiver configurations
+global:
+ resolve_timeout: 5m
+ smtp_from: alert@openim.io
+ smtp_smarthost: smtp.163.com:465
+ smtp_auth_username: alert@openim.io
+ smtp_auth_password: YOURAUTHPASSWORD
+ smtp_require_tls: false
+ smtp_hello: xxx监控告警
+
+templates:
+ - /etc/alertmanager/email.tmpl
+
+route:
+ group_wait: 5s
+ group_interval: 5s
+ repeat_interval: 5m
+ receiver: email
+receivers:
+ - name: email
+ email_configs:
+ - to: {EMAIL_TO:-'alert@example.com'}
+ html: '{{ template "email.to.html" . }}'
+ headers: { Subject: "[OPENIM-SERVER]Alarm" }
+ send_resolved: true
diff --git a/config/config.yaml b/config/config.yaml
index 2964522fb..d66f15704 100644
--- a/config/config.yaml
+++ b/config/config.yaml
@@ -198,7 +198,7 @@ rpcRegisterName:
# Whether to output in json format
# Whether to include stack trace in logs
log:
- storageLocation: ./logs/
+ storageLocation: ../logs/
rotationTime: 24
remainRotationCount: 2
remainLogLevel: 6
@@ -382,7 +382,7 @@ callback:
# The number of Prometheus ports per service needs to correspond to rpcPort
# The number of ports needs to be consistent with msg_transfer_service_num in script/path_info.sh
prometheus:
- enable: true
+ enable: false
prometheusUrl: "https://openim.prometheus"
apiPrometheusPort: [20100]
userPrometheusPort: [ 20110 ]
diff --git a/config/email.tmpl b/config/email.tmpl
new file mode 100644
index 000000000..0385601d0
--- /dev/null
+++ b/config/email.tmpl
@@ -0,0 +1,16 @@
+{{ define "email.to.html" }}
+{{ range .Alerts }}
+
+
+
OpenIM Alert
+
Alert Program: Prometheus Alert
+
Severity Level: {{ .Labels.severity }}
+
Alert Type: {{ .Labels.alertname }}
+
Affected Host: {{ .Labels.instance }}
+
Affected Service: {{ .Labels.job }}
+
Alert Subject: {{ .Annotations.summary }}
+
Trigger Time: {{ .StartsAt.Format "2006-01-02 15:04:05" }}
+
+
+{{ end }}
+{{ end }}
diff --git a/config/instance-down-rules.yml b/config/instance-down-rules.yml
new file mode 100644
index 000000000..72b1f5aa3
--- /dev/null
+++ b/config/instance-down-rules.yml
@@ -0,0 +1,11 @@
+groups:
+ - name: instance_down
+ rules:
+ - alert: InstanceDown
+ expr: up == 0
+ for: 1m
+ labels:
+ severity: critical
+ annotations:
+ summary: "Instance {{ $labels.instance }} down"
+ description: "{{ $labels.instance }} of job {{ $labels.job }} has been down for more than 1 minutes."
\ No newline at end of file
diff --git a/config/prometheus-dashboard.yaml b/config/prometheus-dashboard.yaml
new file mode 100644
index 000000000..0e7823797
--- /dev/null
+++ b/config/prometheus-dashboard.yaml
@@ -0,0 +1,1460 @@
+{
+ "annotations": {
+ "list": [
+ {
+ "builtIn": 1,
+ "datasource": {
+ "type": "grafana",
+ "uid": "-- Grafana --"
+ },
+ "enable": true,
+ "hide": true,
+ "iconColor": "rgba(0, 211, 255, 1)",
+ "name": "Annotations & Alerts",
+ "target": {
+ "limit": 100,
+ "matchAny": false,
+ "tags": [],
+ "type": "dashboard"
+ },
+ "type": "dashboard"
+ }
+ ]
+ },
+ "editable": true,
+ "fiscalYearStartMonth": 0,
+ "graphTooltip": 1,
+ "id": 3,
+ "iteration": 1699530082761,
+ "links": [],
+ "liveNow": false,
+ "panels": [
+ {
+ "collapsed": false,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 0
+ },
+ "id": 16,
+ "panels": [],
+ "title": "openim自定义指标",
+ "type": "row"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 10,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "lineInterpolation": "linear",
+ "lineWidth": 2,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "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": 1
+ },
+ "id": 12,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "table",
+ "placement": "right",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "sum(online_user_num{job=~\"^($job)$\"})",
+ "instant": false,
+ "legendFormat": "online_user_num",
+ "range": true,
+ "refId": "A"
+ }
+ ],
+ "title": "在线人数",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 10,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "lineInterpolation": "linear",
+ "lineWidth": 2,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "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": 1
+ },
+ "id": 15,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "table",
+ "placement": "right",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "sum(user_login_total{job=~\"^($job)$\"})",
+ "instant": false,
+ "legendFormat": "user_login_total",
+ "range": true,
+ "refId": "A"
+ }
+ ],
+ "title": "登入/注册人数",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 10,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "lineInterpolation": "linear",
+ "lineWidth": 2,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "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": 9
+ },
+ "id": 13,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "table",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "multi",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "sum(rate(single_chat_msg_process_failed_total{job=~\"^($job)$\"}[$interval])) by (job)",
+ "instant": false,
+ "legendFormat": "{{job}}-single_chat_msg_process_failed",
+ "range": true,
+ "refId": "A"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "sum(rate(group_chat_msg_process_failed_total{job=~\"^($job)$\"}[$interval])) by (job)",
+ "hide": false,
+ "instant": false,
+ "legendFormat": "{{job}}-group_chat_msg_process_failed",
+ "range": true,
+ "refId": "B"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "sum(rate(msg_offline_push_failed_total{job=~\"^($job)$\"}[$interval])) by (job)",
+ "hide": false,
+ "instant": false,
+ "legendFormat": "{{job}}-msg_offline_push_failed",
+ "range": true,
+ "refId": "C"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "sum(rate(msg_insert_redis_failed_total{job=~\"^($job)$\"}[$interval])) by (job)",
+ "hide": false,
+ "instant": false,
+ "legendFormat": "{{job}}-msg_insert_redis_failed",
+ "range": true,
+ "refId": "D"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "sum(rate(msg_insert_mongo_failed_total{job=~\"^($job)$\"}[$interval])) by (job)",
+ "hide": false,
+ "instant": false,
+ "legendFormat": "{{job}}-msg_insert_mongo_failed",
+ "range": true,
+ "refId": "E"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "sum(rate(seq_set_failed_total{job=~\"^($job)$\"}[$interval])) by (job)",
+ "hide": false,
+ "instant": false,
+ "legendFormat": "{{job}}-seq_set_failed",
+ "range": true,
+ "refId": "F"
+ }
+ ],
+ "title": "failues/s",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 10,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "lineInterpolation": "linear",
+ "lineWidth": 2,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "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": 9
+ },
+ "id": 14,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "table",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "multi",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "sum(rate(single_chat_msg_process_success_total{job=~\"^($job)$\"}[$interval])) by (job)",
+ "instant": false,
+ "legendFormat": "{{job}}-single_chat_msg_process_success",
+ "range": true,
+ "refId": "A"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "sum(rate(group_chat_msg_process_success_total{job=~\"^($job)$\"}[$interval])) by (job)",
+ "hide": false,
+ "instant": false,
+ "legendFormat": "{{job}}-group_chat_msg_process_success",
+ "range": true,
+ "refId": "B"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "sum(rate(msg_insert_redis_success_total{job=~\"^($job)$\"}[$interval])) by (job)",
+ "hide": false,
+ "instant": false,
+ "legendFormat": "{{job}}-msg_insert_redis_success",
+ "range": true,
+ "refId": "C"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "sum(rate(msg_insert_mongo_success_total{job=~\"^($job)$\"}[$interval])) by (job)",
+ "hide": false,
+ "instant": false,
+ "legendFormat": "{{job}}-msg_insert_mongo_success",
+ "range": true,
+ "refId": "D"
+ }
+ ],
+ "title": "messages/s",
+ "type": "timeseries"
+ },
+ {
+ "collapsed": false,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 17
+ },
+ "id": 11,
+ "panels": [],
+ "title": "Go Stats",
+ "type": "row"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 10,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "lineInterpolation": "linear",
+ "lineStyle": {
+ "fill": "solid"
+ },
+ "lineWidth": 2,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "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": 18
+ },
+ "id": 10,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "table",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "multi",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "avg(go_goroutines{job=~\"^($job)$\"}) by (job)",
+ "instant": false,
+ "legendFormat": "{{job}}",
+ "range": true,
+ "refId": "A"
+ }
+ ],
+ "title": "Goroutines",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 10,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "lineInterpolation": "linear",
+ "lineStyle": {
+ "fill": "solid"
+ },
+ "lineWidth": 2,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "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": 18
+ },
+ "id": 7,
+ "options": {
+ "legend": {
+ "calcs": [
+ "mean",
+ "lastNotNull",
+ "max"
+ ],
+ "displayMode": "table",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "exemplar": false,
+ "expr": "go_gc_duration_seconds{job=~\"^($job)$\",quantile=\"1\"}",
+ "instant": false,
+ "legendFormat": "{{pod}}: {{quantile}}",
+ "range": true,
+ "refId": "A"
+ }
+ ],
+ "title": "GC duration quantiles",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 10,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "lineInterpolation": "linear",
+ "lineWidth": 2,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "bytes"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 26
+ },
+ "id": 9,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "table",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "multi",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "avg(go_memstats_alloc_bytes{job=~\"^($job)$\"}) by (job)",
+ "instant": false,
+ "legendFormat": "{{job}}",
+ "range": true,
+ "refId": "A"
+ }
+ ],
+ "title": "process memory",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 10,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "lineInterpolation": "linear",
+ "lineWidth": 2,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "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": 26
+ },
+ "id": 8,
+ "options": {
+ "legend": {
+ "calcs": [
+ "mean",
+ "lastNotNull",
+ "max"
+ ],
+ "displayMode": "table",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "multi",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "sum(process_open_fds{job=~\"^($job)$\"})by (job)",
+ "instant": false,
+ "legendFormat": "{{job}}",
+ "range": true,
+ "refId": "A"
+ }
+ ],
+ "title": "open fds",
+ "type": "timeseries"
+ },
+ {
+ "collapsed": true,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 34
+ },
+ "id": 6,
+ "panels": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 10,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green"
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ }
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 3
+ },
+ "id": 2,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "table",
+ "placement": "right",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "multi",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "10.1.5",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "disableTextWrap": false,
+ "editorMode": "code",
+ "exemplar": false,
+ "expr": "sum(up{job=~\"^($job)$\"})by(job)",
+ "fullMetaSearch": false,
+ "includeNullMetadata": true,
+ "instant": false,
+ "legendFormat": "{{job}}-up",
+ "range": true,
+ "refId": "A",
+ "useBackend": false
+ }
+ ],
+ "title": "Up",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "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": "never",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green"
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "none"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 12,
+ "y": 3
+ },
+ "id": 3,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "table",
+ "placement": "right",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "10.1.5",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "exemplar": true,
+ "expr": "sum(rate(app_requests_total{job=~\"^($job)$\", code!~\"200\"}[$interval])) by (job)",
+ "instant": false,
+ "legendFormat": "{{job}}",
+ "range": true,
+ "refId": "A"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "sum(rate(grpc_server_handled_total{job=~\"^($job)$\",grpc_code!~\"OK\"}[$interval])) by (job)",
+ "hide": false,
+ "instant": false,
+ "legendFormat": "__auto",
+ "range": true,
+ "refId": "B"
+ }
+ ],
+ "title": "failures/s",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 10,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green"
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "req/s"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 12,
+ "x": 0,
+ "y": 11
+ },
+ "id": 4,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "table",
+ "placement": "right",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "multi",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "10.1.5",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "sum(rate(app_requests_total{job=~\"^($job)$\"}[$interval])) by (job)",
+ "instant": false,
+ "legendFormat": "{{job}}-http",
+ "range": true,
+ "refId": "A"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "sum(rate(grpc_server_started_total{job=~\"^($job)$\"}[$interval])) by (job)",
+ "hide": false,
+ "instant": false,
+ "legendFormat": "{{job}}-grpc",
+ "range": true,
+ "refId": "B"
+ }
+ ],
+ "title": "qps",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 10,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "lineInterpolation": "linear",
+ "lineStyle": {
+ "fill": "solid"
+ },
+ "lineWidth": 2,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green"
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "s"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 12,
+ "x": 12,
+ "y": 11
+ },
+ "id": 5,
+ "maxDataPoints": 100,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "table",
+ "placement": "right",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "multi",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "10.1.5",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "exemplar": false,
+ "expr": "histogram_quantile(0.95, \r\n sum(rate(app_request_duration_seconds_bucket{job=~\"^($job)$\"}[$interval])) \r\n by (le, job)\r\n)",
+ "instant": false,
+ "legendFormat": "{{job}}",
+ "range": true,
+ "refId": "A"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "editorMode": "code",
+ "expr": "histogram_quantile(0.95, \r\n sum(rate(grpc_server_handling_seconds_bucket{job=~\"^($job)$\"}[$interval])) \r\n by (le, job)\r\n)",
+ "hide": false,
+ "instant": false,
+ "legendFormat": "{{job}}",
+ "range": true,
+ "refId": "B"
+ }
+ ],
+ "title": "Latency",
+ "type": "timeseries"
+ }
+ ],
+ "title": "应用服务器流量指标",
+ "type": "row"
+ }
+ ],
+ "refresh": "",
+ "schemaVersion": 34,
+ "style": "dark",
+ "tags": [],
+ "templating": {
+ "list": [
+ {
+ "current": {
+ "selected": false,
+ "text": "Prometheus",
+ "value": "Prometheus"
+ },
+ "hide": 0,
+ "includeAll": false,
+ "label": "Data Source",
+ "multi": false,
+ "name": "datasource",
+ "options": [],
+ "query": "prometheus",
+ "queryValue": "",
+ "refresh": 1,
+ "regex": "",
+ "skipUrlSync": false,
+ "type": "datasource"
+ },
+ {
+ "current": {
+ "selected": true,
+ "text": [
+ "All"
+ ],
+ "value": [
+ "$__all"
+ ]
+ },
+ "datasource": {
+ "type": "prometheus",
+ "uid": "${datasource}"
+ },
+ "definition": "label_values(go_memstats_alloc_bytes,job) ",
+ "hide": 0,
+ "includeAll": true,
+ "label": "job",
+ "multi": true,
+ "name": "job",
+ "options": [],
+ "query": {
+ "query": "label_values(go_memstats_alloc_bytes,job) ",
+ "refId": "StandardVariableQuery"
+ },
+ "refresh": 2,
+ "regex": "",
+ "skipUrlSync": false,
+ "sort": 0,
+ "type": "query"
+ },
+ {
+ "auto": false,
+ "auto_count": 30,
+ "auto_min": "10s",
+ "current": {
+ "selected": false,
+ "text": "1m",
+ "value": "1m"
+ },
+ "hide": 0,
+ "name": "interval",
+ "options": [
+ {
+ "selected": true,
+ "text": "1m",
+ "value": "1m"
+ },
+ {
+ "selected": false,
+ "text": "10m",
+ "value": "10m"
+ },
+ {
+ "selected": false,
+ "text": "30m",
+ "value": "30m"
+ },
+ {
+ "selected": false,
+ "text": "1h",
+ "value": "1h"
+ },
+ {
+ "selected": false,
+ "text": "6h",
+ "value": "6h"
+ },
+ {
+ "selected": false,
+ "text": "12h",
+ "value": "12h"
+ },
+ {
+ "selected": false,
+ "text": "1d",
+ "value": "1d"
+ }
+ ],
+ "query": "1m,10m,30m,1h,6h,12h,1d",
+ "queryValue": "",
+ "refresh": 2,
+ "skipUrlSync": false,
+ "type": "interval"
+ }
+ ]
+ },
+ "time": {
+ "from": "now-24h",
+ "to": "now"
+ },
+ "timepicker": {},
+ "timezone": "",
+ "title": "docker-openim-custom1",
+ "uid": "f5f5de9a-6ec5-499a-841e-6e901c33b1f7",
+ "version": 16,
+ "weekStart": ""
+}
\ No newline at end of file
diff --git a/config/prometheus.yml b/config/prometheus.yml
new file mode 100644
index 000000000..7950c5d33
--- /dev/null
+++ b/config/prometheus.yml
@@ -0,0 +1,85 @@
+# my global config
+global:
+ scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
+ evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
+ # scrape_timeout is set to the global default (10s).
+
+# Alertmanager configuration
+alerting:
+ alertmanagers:
+ - static_configs:
+ - targets: ['172.28.0.1:19093']
+
+# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
+rule_files:
+ - "instance-down-rules.yml"
+# - "first_rules.yml"
+# - "second_rules.yml"
+
+# A scrape configuration containing exactly one endpoint to scrape:
+# Here it's Prometheus itself.
+scrape_configs:
+ # The job name is added as a label "job='job_name'"" to any timeseries scraped from this config.
+ # Monitored information captured by prometheus
+ - job_name: 'node-exporter'
+ static_configs:
+ - targets: [ '172.28.0.1:19100' ]
+ labels:
+ namespace: 'default'
+
+ # prometheus fetches application services
+ - job_name: 'openimserver-openim-api'
+ static_configs:
+ - targets: [ '172.28.0.1:20100' ]
+ labels:
+ namespace: 'default'
+ - job_name: 'openimserver-openim-msggateway'
+ static_configs:
+ - targets: [ '172.28.0.1:20140' ]
+ labels:
+ namespace: 'default'
+ - job_name: 'openimserver-openim-msgtransfer'
+ static_configs:
+ - targets: [ 172.28.0.1:21400, 172.28.0.1:21401, 172.28.0.1:21402, 172.28.0.1:21403 ]
+ labels:
+ namespace: 'default'
+ - job_name: 'openimserver-openim-push'
+ static_configs:
+ - targets: [ '172.28.0.1:20170' ]
+ labels:
+ namespace: 'default'
+ - job_name: 'openimserver-openim-rpc-auth'
+ static_configs:
+ - targets: [ '172.28.0.1:20160' ]
+ labels:
+ namespace: 'default'
+ - job_name: 'openimserver-openim-rpc-conversation'
+ static_configs:
+ - targets: [ '172.28.0.1:20230' ]
+ labels:
+ namespace: 'default'
+ - job_name: 'openimserver-openim-rpc-friend'
+ static_configs:
+ - targets: [ '172.28.0.1:20120' ]
+ labels:
+ namespace: 'default'
+ - job_name: 'openimserver-openim-rpc-group'
+ static_configs:
+ - targets: [ '172.28.0.1:20150' ]
+ labels:
+ namespace: 'default'
+ - job_name: 'openimserver-openim-rpc-msg'
+ static_configs:
+ - targets: [ '172.28.0.1:20130' ]
+ labels:
+ namespace: 'default'
+ - job_name: 'openimserver-openim-rpc-third'
+ static_configs:
+ - targets: [ '172.28.0.1:21301' ]
+ labels:
+ namespace: 'default'
+ - job_name: 'openimserver-openim-rpc-user'
+ static_configs:
+ - targets: [ '172.28.0.1:20110' ]
+ labels:
+ namespace: 'default'
diff --git a/deployments/templates/alertmanager.yml b/deployments/templates/alertmanager.yml
new file mode 100644
index 000000000..95e96571d
--- /dev/null
+++ b/deployments/templates/alertmanager.yml
@@ -0,0 +1,32 @@
+###################### AlertManager Configuration ######################
+# AlertManager configuration using environment variables
+#
+# Resolve timeout
+# SMTP configuration for sending alerts
+# Templates for email notifications
+# Routing configurations for alerts
+# Receiver configurations
+global:
+ resolve_timeout: ${ALERTMANAGER_RESOLVE_TIMEOUT}
+ smtp_from: ${ALERTMANAGER_SMTP_FROM}
+ smtp_smarthost: ${ALERTMANAGER_SMTP_SMARTHOST}
+ smtp_auth_username: ${ALERTMANAGER_SMTP_AUTH_USERNAME}
+ smtp_auth_password: ${ALERTMANAGER_SMTP_AUTH_PASSWORD}
+ smtp_require_tls: ${ALERTMANAGER_SMTP_REQUIRE_TLS}
+ smtp_hello: ${ALERTMANAGER_SMTP_HELLO}
+
+templates:
+ - /etc/alertmanager/email.tmpl
+
+route:
+ group_wait: 5s
+ group_interval: 5s
+ repeat_interval: 5m
+ receiver: email
+receivers:
+ - name: email
+ email_configs:
+ - to: ${ALERTMANAGER_EMAIL_TO}
+ html: '{{ template "email.to.html" . }}'
+ headers: { Subject: "[OPENIM-SERVER]Alarm" }
+ send_resolved: true
\ No newline at end of file
diff --git a/deployments/templates/env_template.yaml b/deployments/templates/env_template.yaml
index afa67404e..954b2cf65 100644
--- a/deployments/templates/env_template.yaml
+++ b/deployments/templates/env_template.yaml
@@ -94,12 +94,22 @@ OPENIM_CHAT_NETWORK_ADDRESS=${OPENIM_CHAT_NETWORK_ADDRESS}
# Address or hostname for the Prometheus network.
# Default: PROMETHEUS_NETWORK_ADDRESS=172.28.0.11
PROMETHEUS_NETWORK_ADDRESS=${PROMETHEUS_NETWORK_ADDRESS}
-
+
# Address or hostname for the Grafana network.
# Default: GRAFANA_NETWORK_ADDRESS=172.28.0.12
GRAFANA_NETWORK_ADDRESS=${GRAFANA_NETWORK_ADDRESS}
-
-
+
+# Address or hostname for the node_exporter network.
+# Default: NODE_EXPORTER_NETWORK_ADDRESS=172.28.0.13
+NODE_EXPORTER_NETWORK_ADDRESS=${NODE_EXPORTER_NETWORK_ADDRESS}
+
+# Address or hostname for the OpenIM admin network.
+# Default: OPENIM_ADMIN_NETWORK_ADDRESS=172.28.0.14
+OPENIM_ADMIN_FRONT_NETWORK_ADDRESS=${OPENIM_ADMIN_FRONT_NETWORK_ADDRESS}
+
+# Address or hostname for the alertmanager network.
+# Default: ALERT_MANAGER_NETWORK_ADDRESS=172.28.0.15
+ALERT_MANAGER_NETWORK_ADDRESS=${ALERT_MANAGER_NETWORK_ADDRESS}
# ===============================================
# = Component Extension Configuration =
# ===============================================
@@ -215,7 +225,7 @@ PROMETHEUS_PORT=${PROMETHEUS_PORT}
GRAFANA_ADDRESS=${GRAFANA_NETWORK_ADDRESS}
# Port on which Grafana service is running.
-# Default: GRAFANA_PORT=3000
+# Default: GRAFANA_PORT=13000
GRAFANA_PORT=${GRAFANA_PORT}
# ======================================
@@ -283,3 +293,23 @@ SERVER_BRANCH=${SERVER_BRANCH}
# Port for the OpenIM admin API.
# Default: OPENIM_ADMIN_API_PORT=10009
OPENIM_ADMIN_API_PORT=${OPENIM_ADMIN_API_PORT}
+
+# Port for the node exporter.
+# Default: NODE_EXPORTER_PORT=19100
+NODE_EXPORTER_PORT=${NODE_EXPORTER_PORT}
+
+# Port for the prometheus.
+# Default: PROMETHEUS_PORT=19090
+PROMETHEUS_PORT=${PROMETHEUS_PORT}
+
+# Port for the grafana.
+# Default: GRAFANA_PORT=13000
+GRAFANA_PORT=${GRAFANA_PORT}
+
+# Port for the admin front.
+# Default: OPENIM_ADMIN_FRONT_PORT=11002
+OPENIM_ADMIN_FRONT_PORT=${OPENIM_ADMIN_FRONT_PORT}
+
+# Port for the alertmanager.
+# Default: ALERT_MANAGER_PORT=19093
+ALERT_MANAGER_PORT=${ALERT_MANAGER_PORT}
\ No newline at end of file
diff --git a/deployments/templates/prometheus.yml b/deployments/templates/prometheus.yml
new file mode 100644
index 000000000..fb07a8129
--- /dev/null
+++ b/deployments/templates/prometheus.yml
@@ -0,0 +1,85 @@
+# my global config
+global:
+ scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
+ evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
+ # scrape_timeout is set to the global default (10s).
+
+# Alertmanager configuration
+alerting:
+ alertmanagers:
+ - static_configs:
+ - targets: ['${ALERT_MANAGER_ADDRESS}:${ALERT_MANAGER_PORT}']
+
+# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
+rule_files:
+ - "instance-down-rules.yml"
+# - "first_rules.yml"
+# - "second_rules.yml"
+
+# A scrape configuration containing exactly one endpoint to scrape:
+# Here it's Prometheus itself.
+scrape_configs:
+ # The job name is added as a label "job='job_name'"" to any timeseries scraped from this config.
+ # Monitored information captured by prometheus
+ - job_name: 'node-exporter'
+ static_configs:
+ - targets: [ '${NODE_EXPORTER_ADDRESS}:${NODE_EXPORTER_PORT}' ]
+ labels:
+ namespace: 'default'
+
+ # prometheus fetches application services
+ - job_name: 'openimserver-openim-api'
+ static_configs:
+ - targets: [ '${OPENIM_SERVER_ADDRESS}:${API_PROM_PORT}' ]
+ labels:
+ namespace: 'default'
+ - job_name: 'openimserver-openim-msggateway'
+ static_configs:
+ - targets: [ '${OPENIM_SERVER_ADDRESS}:${MSG_GATEWAY_PROM_PORT}' ]
+ labels:
+ namespace: 'default'
+ - job_name: 'openimserver-openim-msgtransfer'
+ static_configs:
+ - targets: [ ${MSG_TRANSFER_PROM_ADDRESS_PORT} ]
+ labels:
+ namespace: 'default'
+ - job_name: 'openimserver-openim-push'
+ static_configs:
+ - targets: [ '${OPENIM_SERVER_ADDRESS}:${PUSH_PROM_PORT}' ]
+ labels:
+ namespace: 'default'
+ - job_name: 'openimserver-openim-rpc-auth'
+ static_configs:
+ - targets: [ '${OPENIM_SERVER_ADDRESS}:${AUTH_PROM_PORT}' ]
+ labels:
+ namespace: 'default'
+ - job_name: 'openimserver-openim-rpc-conversation'
+ static_configs:
+ - targets: [ '${OPENIM_SERVER_ADDRESS}:${CONVERSATION_PROM_PORT}' ]
+ labels:
+ namespace: 'default'
+ - job_name: 'openimserver-openim-rpc-friend'
+ static_configs:
+ - targets: [ '${OPENIM_SERVER_ADDRESS}:${FRIEND_PROM_PORT}' ]
+ labels:
+ namespace: 'default'
+ - job_name: 'openimserver-openim-rpc-group'
+ static_configs:
+ - targets: [ '${OPENIM_SERVER_ADDRESS}:${GROUP_PROM_PORT}' ]
+ labels:
+ namespace: 'default'
+ - job_name: 'openimserver-openim-rpc-msg'
+ static_configs:
+ - targets: [ '${OPENIM_SERVER_ADDRESS}:${MESSAGE_PROM_PORT}' ]
+ labels:
+ namespace: 'default'
+ - job_name: 'openimserver-openim-rpc-third'
+ static_configs:
+ - targets: [ '${OPENIM_SERVER_ADDRESS}:${THIRD_PROM_PORT}' ]
+ labels:
+ namespace: 'default'
+ - job_name: 'openimserver-openim-rpc-user'
+ static_configs:
+ - targets: [ '${OPENIM_SERVER_ADDRESS}:${USER_PROM_PORT}' ]
+ labels:
+ namespace: 'default'
diff --git a/docker-compose.yml b/docker-compose.yml
index a93824865..1b9b391b2 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -67,17 +67,17 @@ services:
ipv4_address: ${REDIS_NETWORK_ADDRESS}
zookeeper:
- image: bitnami/zookeeper:3.8
- container_name: zookeeper
- ports:
- - "${ZOOKEEPER_PORT}:2181"
- volumes:
- - "/etc/localtime:/etc/localtime"
- environment:
- - ALLOW_ANONYMOUS_LOGIN=yes
- - TZ="Asia/Shanghai"
- restart: always
- networks:
+ image: bitnami/zookeeper:3.8
+ container_name: zookeeper
+ ports:
+ - "${ZOOKEEPER_PORT}:2181"
+ volumes:
+ - "/etc/localtime:/etc/localtime"
+ environment:
+ - ALLOW_ANONYMOUS_LOGIN=yes
+ - TZ="Asia/Shanghai"
+ restart: always
+ networks:
server:
ipv4_address: ${ZOOKEEPER_NETWORK_ADDRESS}
@@ -100,6 +100,7 @@ services:
- KAFKA_CFG_NODE_ID=0
- KAFKA_CFG_PROCESS_ROLES=controller,broker
- KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=0@:9093
+ - KAFKA_HEAP_OPTS:"-Xmx256m -Xms256m"
- KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093,EXTERNAL://:9094
- KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://kafka:9092,EXTERNAL://${DOCKER_BRIDGE_GATEWAY}:${KAFKA_PORT}
- KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,EXTERNAL:PLAINTEXT,PLAINTEXT:PLAINTEXT
@@ -142,3 +143,68 @@ services:
server:
ipv4_address: ${OPENIM_WEB_NETWORK_ADDRESS}
+ openim-admin:
+ image: ${IMAGE_REGISTRY}/openim-admin-front:v3.4.0
+ # image: ghcr.io/openimsdk/openim-admin-front:v3.4.0
+ # image: registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-admin-front:v3.4.0
+ # image: openim/openim-admin-front:v3.4.0
+ container_name: openim-admin
+ restart: always
+ ports:
+ - "${OPENIM_ADMIN_FRONT_PORT}:80"
+ networks:
+ server:
+ ipv4_address: ${OPENIM_ADMIN_FRONT_NETWORK_ADDRESS}
+
+ prometheus:
+ image: prom/prometheus
+ container_name: prometheus
+ hostname: prometheus
+ restart: always
+ volumes:
+ - ./config/prometheus.yml:/etc/prometheus/prometheus.yml
+ - ./config/instance-down-rules.yml:/etc/prometheus/instance-down-rules.yml
+ ports:
+ - "${PROMETHEUS_PORT}:9090"
+ networks:
+ server:
+ ipv4_address: ${PROMETHEUS_NETWORK_ADDRESS}
+
+ alertmanager:
+ image: prom/alertmanager
+ container_name: alertmanager
+ hostname: alertmanager
+ restart: always
+ volumes:
+ - ./config/alertmanager.yml:/etc/alertmanager/alertmanager.yml
+ - ./config/email.tmpl:/etc/alertmanager/email.tmpl
+ ports:
+ - "${ALERT_MANAGER_PORT}:9093"
+ networks:
+ server:
+ ipv4_address: ${ALERT_MANAGER_NETWORK_ADDRESS}
+
+ grafana:
+ image: grafana/grafana
+ container_name: grafana
+ hostname: grafana
+ user: root
+ restart: always
+ ports:
+ - "${GRAFANA_PORT}:3000"
+ volumes:
+ - ${DATA_DIR}/components/grafana:/var/lib/grafana
+ networks:
+ server:
+ ipv4_address: ${GRAFANA_NETWORK_ADDRESS}
+
+ node-exporter:
+ image: quay.io/prometheus/node-exporter
+ container_name: node-exporter
+ hostname: node-exporter
+ restart: always
+ ports:
+ - "${NODE_EXPORTER_PORT}:9100"
+ networks:
+ server:
+ ipv4_address: ${NODE_EXPORTER_NETWORK_ADDRESS}
diff --git a/docs/contrib/environment.md b/docs/contrib/environment.md
index 20939f213..221efdf03 100644
--- a/docs/contrib/environment.md
+++ b/docs/contrib/environment.md
@@ -150,7 +150,7 @@ For convenience, configuration through modifying environment variables is recomm
+ **Description**: API address.
+ **Note**: If the server has an external IP, it will be automatically obtained. For internal networks, set this variable to the IP serving internally.
- ```
+ ```bash
export API_URL="http://ip:10002"
```
@@ -412,7 +412,7 @@ Configuration for Grafana, including its port and address.
| Parameter | Example Value | Description |
| --------------- | -------------------------- | --------------------- |
-| GRAFANA_PORT | "3000" | Port used by Grafana. |
+| GRAFANA_PORT | "13000" | Port used by Grafana. |
| GRAFANA_ADDRESS | "${DOCKER_BRIDGE_GATEWAY}" | Address for Grafana. |
### 2.16. RPC Port Configuration Variables
diff --git a/docs/contrib/prometheus-grafana.md b/docs/contrib/prometheus-grafana.md
new file mode 100644
index 000000000..a59847f71
--- /dev/null
+++ b/docs/contrib/prometheus-grafana.md
@@ -0,0 +1,323 @@
+# Deployment and Design of OpenIM's Management Backend and Monitoring
+
+
+* 1. [Source Code & Docker](#SourceCodeDocker)
+ * 1.1. [Deployment](#Deployment)
+ * 1.2. [Configuration](#Configuration)
+ * 1.3. [Monitoring Running in Docker Guide](#MonitoringRunninginDockerGuide)
+ * 1.3.1. [Introduction](#Introduction)
+ * 1.3.2. [Prerequisites](#Prerequisites)
+ * 1.3.3. [Step 1: Clone the Repository](#Step1:ClonetheRepository)
+ * 1.3.4. [Step 2: Start Docker Compose](#Step2:StartDockerCompose)
+ * 1.3.5. [Step 3: Use the OpenIM Web Interface](#Step3:UsetheOpenIMWebInterface)
+ * 1.3.6. [Running Effect](#RunningEffect)
+ * 1.3.7. [Step 4: Access the Admin Panel](#Step4:AccesstheAdminPanel)
+ * 1.3.8. [Step 5: Access the Monitoring Interface](#Step5:AccesstheMonitoringInterface)
+ * 1.3.9. [Next Steps](#NextSteps)
+ * 1.3.10. [Troubleshooting](#Troubleshooting)
+* 2. [Kubernetes](#Kubernetes)
+ * 2.1. [Middleware Monitoring](#MiddlewareMonitoring)
+ * 2.2. [Custom OpenIM Metrics](#CustomOpenIMMetrics)
+ * 2.3. [Node Exporter](#NodeExporter)
+* 3. [Setting Up and Configuring AlertManager Using Environment Variables and `make init`](#SettingUpandConfiguringAlertManagerUsingEnvironmentVariablesandmakeinit)
+ * 3.1. [Introduction](#Introduction-1)
+ * 3.2. [Prerequisites](#Prerequisites-1)
+ * 3.3. [Configuration Steps](#ConfigurationSteps)
+ * 3.3.1. [Exporting Environment Variables](#ExportingEnvironmentVariables)
+ * 3.3.2. [Initializing AlertManager](#InitializingAlertManager)
+ * 3.3.3. [Key Configuration Fields](#KeyConfigurationFields)
+ * 3.3.4. [Configuring SMTP Authentication Password](#ConfiguringSMTPAuthenticationPassword)
+ * 3.3.5. [Useful Links for Common Email Servers](#UsefulLinksforCommonEmailServers)
+ * 3.4. [Conclusion](#Conclusion)
+
+
+
+
+OpenIM offers various flexible deployment options to suit different environments and requirements. Here is a simplified and optimized description of these deployment options:
+
+1. Source Code Deployment:
+ + **Regular Source Code Deployment**: Deployment using the `nohup` method. This is a basic deployment method suitable for development and testing environments. For details, refer to the [Regular Source Code Deployment Guide](https://docs.openim.io/).
+ + **Production-Level Deployment**: Deployment using the `system` method, more suitable for production environments. This method provides higher stability and reliability. For details, refer to the [Production-Level Deployment Guide](https://docs.openim.io/guides/gettingStarted/install-openim-linux-system).
+2. Cluster Deployment:
+ + **Kubernetes Deployment**: Provides two deployment methods, including deployment through Helm and sealos. This is suitable for environments that require high availability and scalability. Specific methods can be found in the [Kubernetes Deployment Guide](https://docs.openim.io/guides/gettingStarted/k8s-deployment).
+3. Docker Deployment:
+ + **Regular Docker Deployment**: Suitable for quick deployments and small projects. For detailed information, refer to the [Docker Deployment Guide](https://docs.openim.io/guides/gettingStarted/dockerCompose).
+ + **Docker Compose Deployment**: Provides more convenient service management and configuration, suitable for complex multi-container applications.
+
+Next, we will introduce the specific steps, monitoring, and management backend configuration for each of these deployment methods, as well as usage tips to help you choose the most suitable deployment option according to your needs.
+
+## 1. Source Code & Docker
+
+### 1.1. Deployment
+
+OpenIM deploys openim-server and openim-chat from source code, while other components are deployed via Docker.
+
+For Docker deployment, you can deploy all components with a single command using the [openimsdk/openim-docker](https://github.com/openimsdk/openim-docker) repository. The deployment configuration can be found in the [environment.sh](https://github.com/openimsdk/open-im-server/blob/main/scripts/install/environment.sh) document, which provides information on how to learn and familiarize yourself with various environment variables.
+
+For Prometheus, it is not enabled by default. To enable it, set the environment variable before executing `make init`:
+
+```bash
+export PROMETHEUS_ENABLE=true # Default is false
+```
+
+Then, execute:
+
+```bash
+make init
+docker compose up -d
+```
+
+### 1.2. Configuration
+
+To configure Prometheus data sources in Grafana, follow these steps:
+
+1. **Log in to Grafana**: First, open your web browser and access the Grafana URL. If you haven't changed the port, the address is typically [http://localhost:13000](http://localhost:13000/).
+
+2. **Log in with default credentials**: Grafana's default username and password are both `admin`. You will be prompted to change the password on your first login.
+
+3. **Access Data Sources Settings**:
+
+ + In the left menu of Grafana, look for and click the "gear" icon representing "Configuration."
+ + In the configuration menu, select "Data Sources."
+
+4. **Add a New Data Source**:
+
+ + On the Data Sources page, click the "Add data source" button.
+ + In the list, find and select "Prometheus."
+
+ 
+
+ Click `Add New connection` to add more data sources, such as Loki (responsible for log storage and query processing).
+
+5. **Configure the Prometheus Data Source**:
+
+ + On the configuration page, fill in the details of the Prometheus server. This typically includes the URL of the Prometheus service (e.g., if Prometheus is running on the same machine as OpenIM, the URL might be `http://172.28.0.1:19090`, with the address matching the `DOCKER_BRIDGE_GATEWAY` variable address). OpenIM and the components are linked via a gateway. The default port used by OpenIM is `19090`.
+ + Adjust other settings as needed, such as authentication and TLS settings.
+
+ 
+
+6. **Save and Test**:
+
+ + After completing the configuration, click the "Save & Test" button to ensure that Grafana can successfully connect to Prometheus.
+
+**Importing Dashboards in Grafana**
+
+Importing Grafana Dashboards is a straightforward process and is applicable to OpenIM Server application services and Node Exporter. Here are detailed steps and necessary considerations:
+
+**Key Metrics Overview and Deployment Steps**
+
+To monitor OpenIM in Grafana, you need to focus on three categories of key metrics, each with its specific deployment and configuration steps:
+
+1. **OpenIM Metrics (`prometheus-dashboard.yaml`)**:
+ + **Configuration File Path**: Located at `config/prometheus-dashboard.yaml`.
+ + **Enabling Monitoring**: Set the environment variable `export PROMETHEUS_ENABLE=true` to enable Prometheus monitoring.
+ + **More Information**: Refer to the [OpenIM Configuration Guide](https://docs.openim.io/configurations/prometheus-integration).
+2. **Node Exporter**:
+ + **Container Deployment**: Deploy the `quay.io/prometheus/node-exporter` container for node monitoring.
+ + **Get Dashboard**: Access the [Node Exporter Full Feature Dashboard](https://grafana.com/grafana/dashboards/1860-node-exporter-full/) and import it using YAML file download or ID import.
+ + **Deployment Guide**: Refer to the [Node Exporter Deployment Documentation](https://prometheus.io/docs/guides/node-exporter/).
+3. **Middleware Metrics**: Each middleware requires specific steps and configurations to enable monitoring. Here is a list of common middleware and links to their respective setup guides:
+ + MySQL:
+ + **Configuration**: Ensure MySQL has performance monitoring enabled.
+ + **Link**: Refer to the [MySQL Monitoring Configuration Guide](https://grafana.com/docs/grafana/latest/datasources/mysql/).
+ + Redis:
+ + **Configuration**: Configure Redis to allow monitoring data export.
+ + **Link**: Refer to the [Redis Monitoring Guide](https://grafana.com/docs/grafana/latest/datasources/redis/).
+ + MongoDB:
+ + **Configuration**: Set up monitoring metrics for MongoDB.
+ + **Link**: Refer to the [MongoDB Monitoring Guide](https://grafana.com/grafana/plugins/grafana-mongodb-datasource/).
+ + Kafka:
+ + **Configuration**: Integrate Kafka with Prometheus monitoring.
+ + **Link**: Refer to the [Kafka Monitoring Guide](https://grafana.com/grafana/plugins/grafana-kafka-datasource/).
+ + Zookeeper:
+ + **Configuration**: Ensure Zookeeper can be monitored by Prometheus.
+ + **Link**: Refer to the [Zookeeper Monitoring Configuration](https://grafana.com/docs/grafana/latest/datasources/zookeeper/).
+
+
+
+**Importing Steps**:
+
+1. Access the Dashboard Import Interface:
+
+ + Click the `+` icon on the left menu or in the top right corner of Grafana, then select "Create."
+ + Choose "Import" to access the dashboard import interface.
+
+2. **Perform Dashboard Import**:
+ + **Upload via File**: Directly upload your YAML file.
+ + **Paste Content**: Open the YAML file, copy its content, and paste it into the import interface.
+ + **Import via Grafana.com Dashboard**: Visit [Grafana Dashboards](https://grafana.com/grafana/dashboards/), search for the desired dashboard, and import it using its ID.
+3. **Configure the Dashboard**:
+ + Select the appropriate data source, such as the previously configured Prometheus.
+ + Adjust other settings, such as the dashboard name or folder.
+4. **Save and View the Dashboard**:
+ + After configuring, click "Import" to complete the process.
+ + Immediately view the new dashboard after successful import.
+
+**Graph Examples:**
+
+
+
+
+
+### 1.3. Monitoring Running in Docker Guide
+
+#### 1.3.1. Introduction
+
+This guide provides the steps to run OpenIM using Docker. OpenIM is an open-source instant messaging solution that can be quickly deployed using Docker. For more information, please refer to the [OpenIM Docker GitHub](https://github.com/openimsdk/openim-docker).
+
+#### 1.3.2. Prerequisites
+
++ Ensure that Docker and Docker Compose are installed.
++ Basic understanding of Docker and containerization technology.
+
+#### 1.3.3. Step 1: Clone the Repository
+
+First, clone the OpenIM Docker repository:
+
+```bash
+git clone https://github.com/openimsdk/openim-docker.git
+```
+
+Navigate to the repository directory and check the `README` file for more information and configuration options.
+
+#### 1.3.4. Step 2: Start Docker Compose
+
+In the repository directory, run the following command to start the service:
+
+```bash
+docker-compose up -d
+```
+
+This will download the required Docker images and start the OpenIM service.
+
+#### 1.3.5. Step 3: Use the OpenIM Web Interface
+
++ Open a browser in private mode and access [OpenIM Web](http://localhost:11001/).
++ Register two users and try adding friends.
++ Test sending messages and pictures.
+
+#### 1.3.6. Running Effect
+
+
+
+#### 1.3.7. Step 4: Access the Admin Panel
+
++ Access the [OpenIM Admin Panel](http://localhost:11002/).
++ Log in using the default username and password (`admin1:admin1`).
+
+Running Effect Image:
+
+
+
+#### 1.3.8. Step 5: Access the Monitoring Interface
+
++ Log in to the [Monitoring Interface](http://localhost:3000/login) using the credentials (`admin:admin`).
+
+#### 1.3.9. Next Steps
+
++ Configure and manage the services following the steps provided in the OpenIM source code.
++ Refer to the `README` file for advanced configuration and management.
+
+#### 1.3.10. Troubleshooting
+
++ If you encounter any issues, please check the documentation on [OpenIM Docker GitHub](https://github.com/openimsdk/openim-docker) or search for related issues in the Issues section.
++ If the problem persists, you can create an issue on the [openim-docker](https://github.com/openimsdk/openim-docker/issues/new/choose) repository or the [openim-server](https://github.com/openimsdk/open-im-server/issues/new/choose) repository.
+
+
+
+## 2. Kubernetes
+
+Refer to [openimsdk/helm-charts](https://github.com/openimsdk/helm-charts).
+
+When deploying and monitoring OpenIM in a Kubernetes environment, you will focus on three main metrics: middleware, custom OpenIM metrics, and Node Exporter. Here are detailed steps and guidelines:
+
+### 2.1. Middleware Monitoring
+
+Middleware monitoring is crucial to ensure the overall system's stability. Typically, this includes monitoring the following components:
+
++ **MySQL**: Monitor database performance, query latency, and more.
++ **Redis**: Track operation latency, memory usage, and more.
++ **MongoDB**: Observe database operations, resource usage, and more.
++ **Kafka**: Monitor message throughput, latency, and more.
++ **Zookeeper**: Keep an eye on cluster status, performance metrics, and more.
+
+For Kubernetes environments, you can use the corresponding Prometheus Exporters to collect monitoring data for these middleware components.
+
+### 2.2. Custom OpenIM Metrics
+
+Custom OpenIM metrics provide essential information about the OpenIM application itself, such as user activity, message traffic, system performance, and more. To monitor these metrics in Kubernetes:
+
++ Ensure OpenIM application configurations expose Prometheus metrics.
++ When deploying using Helm charts (refer to [OpenIM Helm Charts](https://github.com/openimsdk/helm-charts)), pay attention to configuring relevant monitoring settings.
+
+### 2.3. Node Exporter
+
+Node Exporter is used to collect hardware and operating system-level metrics for Kubernetes nodes, such as CPU, memory, disk usage, and more. To integrate Node Exporter in Kubernetes:
+
++ Deploy Node Exporter using the appropriate Helm chart. You can find information and guides on [Prometheus Community](https://prometheus.io/docs/guides/node-exporter/).
++ Ensure Node Exporter's data is collected by Prometheus instances within your cluster.
+
+
+
+## 3. Setting Up and Configuring AlertManager Using Environment Variables and `make init`
+
+### 3.1. Introduction
+
+AlertManager, a component of the Prometheus monitoring system, handles alerts sent by client applications such as the Prometheus server. It takes care of deduplicating, grouping, and routing them to the correct receiver. This document outlines how to set up and configure AlertManager using environment variables and the `make init` command. We will focus on configuring key fields like the sender's email, SMTP settings, and SMTP authentication password.
+
+### 3.2. Prerequisites
+
++ Basic knowledge of terminal and command-line operations.
++ AlertManager installed on your system.
++ Access to an SMTP server for sending emails.
+
+### 3.3. Configuration Steps
+
+#### 3.3.1. Exporting Environment Variables
+
+Before initializing AlertManager, you need to set environment variables. These variables are used to configure the AlertManager settings without altering the code. Use the `export` command in your terminal. Here are some key variables you might set:
+
++ `export ALERTMANAGER_RESOLVE_TIMEOUT='5m'`
++ `export ALERTMANAGER_SMTP_FROM='alert@example.com'`
++ `export ALERTMANAGER_SMTP_SMARTHOST='smtp.example.com:465'`
++ `export ALERTMANAGER_SMTP_AUTH_USERNAME='alert@example.com'`
++ `export ALERTMANAGER_SMTP_AUTH_PASSWORD='your_password'`
++ `export ALERTMANAGER_SMTP_REQUIRE_TLS='false'`
+
+#### 3.3.2. Initializing AlertManager
+
+After setting the necessary environment variables, you can initialize AlertManager by running the `make init` command. This command typically runs a script that prepares AlertManager with the provided configuration.
+
+#### 3.3.3. Key Configuration Fields
+
+##### a. Sender's Email (`ALERTMANAGER_SMTP_FROM`)
+
+This variable sets the email address that will appear as the sender in the notifications sent by AlertManager.
+
+##### b. SMTP Configuration
+
++ **SMTP Server (`ALERTMANAGER_SMTP_SMARTHOST`):** Specifies the address and port of the SMTP server used for sending emails.
++ **SMTP Authentication Username (`ALERTMANAGER_SMTP_AUTH_USERNAME`):** The username for authenticating with the SMTP server.
++ **SMTP Authentication Password (`ALERTMANAGER_SMTP_AUTH_PASSWORD`):** The password for SMTP server authentication. It's crucial to keep this value secure.
+
+#### 3.3.4. Configuring SMTP Authentication Password
+
+The SMTP authentication password can be set using the `ALERTMANAGER_SMTP_AUTH_PASSWORD` environment variable. It's recommended to use a secure method to set this variable to avoid exposing sensitive information. For instance, you might read the password from a secure file or a secret management tool.
+
+#### 3.3.5. Useful Links for Common Email Servers
+
+For specific configurations related to common email servers, you may refer to their respective documentation:
+
++ Gmail SMTP Settings:
+ + [Gmail SMTP Configuration](https://support.google.com/mail/answer/7126229?hl=en)
++ Microsoft Outlook SMTP Settings:
+ + [Outlook Email Settings](https://support.microsoft.com/en-us/office/pop-imap-and-smtp-settings-8361e398-8af4-4e97-b147-6c6c4ac95353)
++ Yahoo Mail SMTP Settings:
+ + [Yahoo SMTP Configuration](https://help.yahoo.com/kb/SLN4724.html)
+
+### 3.4. Conclusion
+
+Setting up and configuring AlertManager with environment variables provides a flexible and secure way to manage alert settings. By following the above steps, you can easily configure AlertManager for your monitoring needs. Always ensure to secure sensitive information, especially when dealing with SMTP authentication credentials.
\ No newline at end of file
diff --git a/docs/images/Wechat.jpg b/docs/images/Wechat.jpg
index d6fbe5b0d..85b812a8d 100644
Binary files a/docs/images/Wechat.jpg and b/docs/images/Wechat.jpg differ
diff --git a/internal/msggateway/hub_server.go b/internal/msggateway/hub_server.go
index 670757850..807c4af3b 100644
--- a/internal/msggateway/hub_server.go
+++ b/internal/msggateway/hub_server.go
@@ -17,22 +17,19 @@ package msggateway
import (
"context"
- "github.com/OpenIMSDK/tools/mcontext"
-
- "github.com/openimsdk/open-im-server/v3/pkg/authverify"
-
- "github.com/OpenIMSDK/tools/errs"
"google.golang.org/grpc"
- "github.com/openimsdk/open-im-server/v3/pkg/common/db/cache"
-
"github.com/OpenIMSDK/protocol/constant"
"github.com/OpenIMSDK/protocol/msggateway"
"github.com/OpenIMSDK/tools/discoveryregistry"
+ "github.com/OpenIMSDK/tools/errs"
"github.com/OpenIMSDK/tools/log"
+ "github.com/OpenIMSDK/tools/mcontext"
"github.com/OpenIMSDK/tools/utils"
+ "github.com/openimsdk/open-im-server/v3/pkg/authverify"
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
+ "github.com/openimsdk/open-im-server/v3/pkg/common/db/cache"
"github.com/openimsdk/open-im-server/v3/pkg/common/startrpc"
)
@@ -41,6 +38,7 @@ func (s *Server) InitServer(disCov discoveryregistry.SvcDiscoveryRegistry, serve
if err != nil {
return err
}
+
msgModel := cache.NewMsgCacheModel(rdb)
s.LongConnServer.SetDiscoveryRegistry(disCov)
s.LongConnServer.SetCacheHandler(msgModel)
@@ -97,22 +95,25 @@ func (s *Server) GetUsersOnlineStatus(
if !ok {
continue
}
- temp := new(msggateway.GetUsersOnlineStatusResp_SuccessResult)
- temp.UserID = userID
+
+ uresp := new(msggateway.GetUsersOnlineStatusResp_SuccessResult)
+ uresp.UserID = userID
for _, client := range clients {
- if client != nil {
- ps := new(msggateway.GetUsersOnlineStatusResp_SuccessDetail)
- ps.Platform = constant.PlatformIDToName(client.PlatformID)
- ps.Status = constant.OnlineStatus
- ps.ConnID = client.ctx.GetConnID()
- ps.Token = client.token
- ps.IsBackground = client.IsBackground
- temp.Status = constant.OnlineStatus
- temp.DetailPlatformStatus = append(temp.DetailPlatformStatus, ps)
+ if client == nil {
+ continue
}
+
+ ps := new(msggateway.GetUsersOnlineStatusResp_SuccessDetail)
+ ps.Platform = constant.PlatformIDToName(client.PlatformID)
+ ps.Status = constant.OnlineStatus
+ ps.ConnID = client.ctx.GetConnID()
+ ps.Token = client.token
+ ps.IsBackground = client.IsBackground
+ uresp.Status = constant.OnlineStatus
+ uresp.DetailPlatformStatus = append(uresp.DetailPlatformStatus, ps)
}
- if temp.Status == constant.OnlineStatus {
- resp.SuccessResult = append(resp.SuccessResult, temp)
+ if uresp.Status == constant.OnlineStatus {
+ resp.SuccessResult = append(resp.SuccessResult, uresp)
}
}
return &resp, nil
@@ -129,50 +130,55 @@ func (s *Server) SuperGroupOnlineBatchPushOneMsg(
ctx context.Context,
req *msggateway.OnlineBatchPushOneMsgReq,
) (*msggateway.OnlineBatchPushOneMsgResp, error) {
- var singleUserResult []*msggateway.SingleMsgToUserResults
+
+ var singleUserResults []*msggateway.SingleMsgToUserResults
+
for _, v := range req.PushToUserIDs {
var resp []*msggateway.SingleMsgToUserPlatform
- tempT := &msggateway.SingleMsgToUserResults{
+ results := &msggateway.SingleMsgToUserResults{
UserID: v,
}
clients, ok := s.LongConnServer.GetUserAllCons(v)
if !ok {
log.ZDebug(ctx, "push user not online", "userID", v)
- tempT.Resp = resp
- singleUserResult = append(singleUserResult, tempT)
+ results.Resp = resp
+ singleUserResults = append(singleUserResults, results)
continue
}
+
log.ZDebug(ctx, "push user online", "clients", clients, "userID", v)
for _, client := range clients {
- if client != nil {
- temp := &msggateway.SingleMsgToUserPlatform{
- RecvID: v,
- RecvPlatFormID: int32(client.PlatformID),
- }
- if !client.IsBackground ||
- (client.IsBackground == true && client.PlatformID != constant.IOSPlatformID) {
- err := client.PushMessage(ctx, req.MsgData)
- if err != nil {
- temp.ResultCode = -2
- resp = append(resp, temp)
- } else {
- if utils.IsContainInt(client.PlatformID, s.pushTerminal) {
- tempT.OnlinePush = true
- resp = append(resp, temp)
- }
- }
+ if client == nil {
+ continue
+ }
+
+ userPlatform := &msggateway.SingleMsgToUserPlatform{
+ RecvID: v,
+ RecvPlatFormID: int32(client.PlatformID),
+ }
+ if !client.IsBackground ||
+ (client.IsBackground && client.PlatformID != constant.IOSPlatformID) {
+ err := client.PushMessage(ctx, req.MsgData)
+ if err != nil {
+ userPlatform.ResultCode = -2
+ resp = append(resp, userPlatform)
} else {
- temp.ResultCode = -3
- resp = append(resp, temp)
+ if utils.IsContainInt(client.PlatformID, s.pushTerminal) {
+ results.OnlinePush = true
+ resp = append(resp, userPlatform)
+ }
}
+ } else {
+ userPlatform.ResultCode = -3
+ resp = append(resp, userPlatform)
}
}
- tempT.Resp = resp
- singleUserResult = append(singleUserResult, tempT)
+ results.Resp = resp
+ singleUserResults = append(singleUserResults, results)
}
return &msggateway.OnlineBatchPushOneMsgResp{
- SinglePushResult: singleUserResult,
+ SinglePushResult: singleUserResults,
}, nil
}
@@ -181,17 +187,21 @@ func (s *Server) KickUserOffline(
req *msggateway.KickUserOfflineReq,
) (*msggateway.KickUserOfflineResp, error) {
for _, v := range req.KickUserIDList {
- if clients, _, ok := s.LongConnServer.GetUserPlatformCons(v, int(req.PlatformID)); ok {
- for _, client := range clients {
- log.ZDebug(ctx, "kick user offline", "userID", v, "platformID", req.PlatformID, "client", client)
- if err := client.longConnServer.KickUserConn(client); err != nil {
- log.ZWarn(ctx, "kick user offline failed", err, "userID", v, "platformID", req.PlatformID)
- }
- }
- } else {
+ clients, _, ok := s.LongConnServer.GetUserPlatformCons(v, int(req.PlatformID))
+ if !ok {
log.ZInfo(ctx, "conn not exist", "userID", v, "platformID", req.PlatformID)
+ continue
+ }
+
+ for _, client := range clients {
+ log.ZDebug(ctx, "kick user offline", "userID", v, "platformID", req.PlatformID, "client", client)
+ if err := client.longConnServer.KickUserConn(client); err != nil {
+ log.ZWarn(ctx, "kick user offline failed", err, "userID", v, "platformID", req.PlatformID)
+ }
}
+ continue
}
+
return &msggateway.KickUserOfflineResp{}, nil
}
diff --git a/internal/msggateway/n_ws_server.go b/internal/msggateway/n_ws_server.go
index fea35397f..a54519682 100644
--- a/internal/msggateway/n_ws_server.go
+++ b/internal/msggateway/n_ws_server.go
@@ -374,7 +374,7 @@ func (ws *WsServer) unregisterClient(client *Client) {
}
ws.onlineUserConnNum.Add(-1)
ws.SetUserOnlineStatus(client.ctx, client, constant.Offline)
- log.ZInfo(client.ctx, "user offline", "close reason", client.closedErr, "online user Num", ws.onlineUserNum, "online user conn Num",
+ log.ZInfo(client.ctx, "user offline", "close reason", client.closedErr, "online user Num", ws.onlineUserNum.Load(), "online user conn Num",
ws.onlineUserConnNum.Load(),
)
}
diff --git a/internal/push/push_to_client.go b/internal/push/push_to_client.go
index b3e2cfad5..2ee8c457f 100644
--- a/internal/push/push_to_client.go
+++ b/internal/push/push_to_client.go
@@ -18,8 +18,9 @@ import (
"context"
"encoding/json"
"errors"
+ "sync"
- "github.com/openimsdk/open-im-server/v3/pkg/msgprocessor"
+ "golang.org/x/sync/errgroup"
"github.com/OpenIMSDK/protocol/constant"
"github.com/OpenIMSDK/protocol/conversation"
@@ -40,6 +41,7 @@ import (
"github.com/openimsdk/open-im-server/v3/pkg/common/db/controller"
"github.com/openimsdk/open-im-server/v3/pkg/common/db/localcache"
"github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics"
+ "github.com/openimsdk/open-im-server/v3/pkg/msgprocessor"
"github.com/openimsdk/open-im-server/v3/pkg/rpcclient"
)
@@ -285,18 +287,44 @@ func (p *Pusher) GetConnsAndOnlinePush(ctx context.Context, msg *sdkws.MsgData,
if err != nil {
return nil, err
}
+
+ var (
+ mu sync.Mutex
+ wg = errgroup.Group{}
+ input = &msggateway.OnlineBatchPushOneMsgReq{MsgData: msg, PushToUserIDs: pushToUserIDs}
+ maxWorkers = config.Config.Push.MaxConcurrentWorkers
+ )
+
+ if maxWorkers < 3 {
+ maxWorkers = 3
+ }
+
+ wg.SetLimit(maxWorkers)
+
// Online push message
- for _, v := range conns {
- msgClient := msggateway.NewMsgGatewayClient(v)
- reply, err := msgClient.SuperGroupOnlineBatchPushOneMsg(ctx, &msggateway.OnlineBatchPushOneMsgReq{MsgData: msg, PushToUserIDs: pushToUserIDs})
- if err != nil {
- continue
- }
- log.ZDebug(ctx, "push result", "reply", reply)
- if reply != nil && reply.SinglePushResult != nil {
- wsResults = append(wsResults, reply.SinglePushResult...)
- }
+ for _, conn := range conns {
+ conn := conn // loop var safe
+ wg.Go(func() error {
+ msgClient := msggateway.NewMsgGatewayClient(conn)
+ reply, err := msgClient.SuperGroupOnlineBatchPushOneMsg(ctx, input)
+ if err != nil {
+ return nil
+ }
+
+ log.ZDebug(ctx, "push result", "reply", reply)
+ if reply != nil && reply.SinglePushResult != nil {
+ mu.Lock()
+ wsResults = append(wsResults, reply.SinglePushResult...)
+ mu.Unlock()
+ }
+
+ return nil
+ })
}
+
+ _ = wg.Wait()
+
+ // always return nil
return wsResults, nil
}
diff --git a/pkg/common/config/config.go b/pkg/common/config/config.go
index 94688b0fb..fdb1cee00 100644
--- a/pkg/common/config/config.go
+++ b/pkg/common/config/config.go
@@ -199,8 +199,9 @@ type configStruct struct {
} `yaml:"longConnSvr"`
Push struct {
- Enable string `yaml:"enable"`
- GeTui struct {
+ MaxConcurrentWorkers int `yaml:"maxConcurrentWorkers"`
+ Enable string `yaml:"enable"`
+ GeTui struct {
PushUrl string `yaml:"pushUrl"`
AppKey string `yaml:"appKey"`
Intent string `yaml:"intent"`
diff --git a/pkg/common/db/cache/msg.go b/pkg/common/db/cache/msg.go
index dd4418a09..5cd3cb22c 100644
--- a/pkg/common/db/cache/msg.go
+++ b/pkg/common/db/cache/msg.go
@@ -20,11 +20,8 @@ import (
"strconv"
"time"
- "github.com/dtm-labs/rockscache"
"golang.org/x/sync/errgroup"
- unrelationtb "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/unrelation"
-
"github.com/openimsdk/open-im-server/v3/pkg/msgprocessor"
"github.com/OpenIMSDK/tools/errs"
@@ -136,10 +133,7 @@ func NewMsgCacheModel(client redis.UniversalClient) MsgModel {
type msgCache struct {
metaCache
- rdb redis.UniversalClient
- expireTime time.Duration
- rcClient *rockscache.Client
- msgDocDatabase unrelationtb.MsgDocModelInterface
+ rdb redis.UniversalClient
}
func (c *msgCache) getMaxSeqKey(conversationID string) string {
@@ -176,29 +170,6 @@ func (c *msgCache) getSeqs(ctx context.Context, items []string, getkey func(s st
}
return m, nil
-
- //pipe := c.rdb.Pipeline()
- //for _, v := range items {
- // if err := pipe.Get(ctx, getkey(v)).Err(); err != nil && err != redis.Nil {
- // return nil, errs.Wrap(err)
- // }
- //}
- //result, err := pipe.Exec(ctx)
- //if err != nil && err != redis.Nil {
- // return nil, errs.Wrap(err)
- //}
- //m = make(map[string]int64, len(items))
- //for i, v := range result {
- // seq := v.(*redis.StringCmd)
- // if seq.Err() != nil && seq.Err() != redis.Nil {
- // return nil, errs.Wrap(v.Err())
- // }
- // val := utils.StringToInt64(seq.Val())
- // if val != 0 {
- // m[items[i]] = val
- // }
- //}
- //return m, nil
}
func (c *msgCache) SetMaxSeq(ctx context.Context, conversationID string, maxSeq int64) error {
@@ -224,15 +195,6 @@ func (c *msgCache) setSeqs(ctx context.Context, seqs map[string]int64, getkey fu
}
}
return nil
- //pipe := c.rdb.Pipeline()
- //for k, seq := range seqs {
- // err := pipe.Set(ctx, getkey(k), seq, 0).Err()
- // if err != nil {
- // return errs.Wrap(err)
- // }
- //}
- //_, err := pipe.Exec(ctx)
- //return err
}
func (c *msgCache) SetMinSeqs(ctx context.Context, seqs map[string]int64) error {
@@ -637,20 +599,49 @@ func (c *msgCache) DelUserDeleteMsgsList(ctx context.Context, conversationID str
}
func (c *msgCache) DeleteMessages(ctx context.Context, conversationID string, seqs []int64) error {
+ if config.Config.Redis.EnablePipeline {
+ return c.PipeDeleteMessages(ctx, conversationID, seqs)
+ }
+
+ return c.ParallelDeleteMessages(ctx, conversationID, seqs)
+}
+
+func (c *msgCache) ParallelDeleteMessages(ctx context.Context, conversationID string, seqs []int64) error {
+ wg := errgroup.Group{}
+ wg.SetLimit(concurrentLimit)
+
for _, seq := range seqs {
- if err := c.rdb.Del(ctx, c.getMessageCacheKey(conversationID, seq)).Err(); err != nil {
+ seq := seq
+ wg.Go(func() error {
+ err := c.rdb.Del(ctx, c.getMessageCacheKey(conversationID, seq)).Err()
+ if err != nil {
+ return errs.Wrap(err)
+ }
+ return nil
+ })
+ }
+
+ return wg.Wait()
+}
+
+func (c *msgCache) PipeDeleteMessages(ctx context.Context, conversationID string, seqs []int64) error {
+ pipe := c.rdb.Pipeline()
+ for _, seq := range seqs {
+ _ = pipe.Del(ctx, c.getMessageCacheKey(conversationID, seq))
+ }
+
+ results, err := pipe.Exec(ctx)
+ if err != nil {
+ return errs.Wrap(err, "pipe.del")
+ }
+
+ for _, res := range results {
+ if res.Err() != nil {
return errs.Wrap(err)
}
}
+
return nil
- //pipe := c.rdb.Pipeline()
- //for _, seq := range seqs {
- // if err := pipe.Del(ctx, c.getMessageCacheKey(conversationID, seq)).Err(); err != nil {
- // return errs.Wrap(err)
- // }
- //}
- //_, err := pipe.Exec(ctx)
- //return errs.Wrap(err)
}
func (c *msgCache) CleanUpOneConversationAllMsg(ctx context.Context, conversationID string) error {
@@ -667,14 +658,6 @@ func (c *msgCache) CleanUpOneConversationAllMsg(ctx context.Context, conversatio
}
}
return nil
- //pipe := c.rdb.Pipeline()
- //for _, v := range vals {
- // if err := pipe.Del(ctx, v).Err(); err != nil {
- // return errs.Wrap(err)
- // }
- //}
- //_, err = pipe.Exec(ctx)
- //return errs.Wrap(err)
}
func (c *msgCache) DelMsgFromCache(ctx context.Context, userID string, seqs []int64) error {
diff --git a/pkg/common/db/cache/msg_test.go b/pkg/common/db/cache/msg_test.go
index c5a4fb870..3fddf5965 100644
--- a/pkg/common/db/cache/msg_test.go
+++ b/pkg/common/db/cache/msg_test.go
@@ -249,3 +249,139 @@ func testPipeGetMessagesBySeqWithLostHalfSeqs(t *testing.T, cid string, seqs []i
assert.Equal(t, msg.Seq, seqs[idx])
}
}
+
+func TestPipeDeleteMessages(t *testing.T) {
+ var (
+ cid = fmt.Sprintf("cid-%v", rand.Int63())
+ seqFirst = rand.Int63()
+ msgs = []*sdkws.MsgData{}
+ )
+
+ var seqs []int64
+ for i := 0; i < 100; i++ {
+ msgs = append(msgs, &sdkws.MsgData{
+ Seq: seqFirst + int64(i),
+ })
+ seqs = append(seqs, msgs[i].Seq)
+ }
+
+ testPipeSetMessageToCache(t, cid, msgs)
+ testPipeDeleteMessagesOK(t, cid, seqs, msgs)
+
+ // set again
+ testPipeSetMessageToCache(t, cid, msgs)
+ testPipeDeleteMessagesMix(t, cid, seqs[:90], msgs)
+}
+
+func testPipeDeleteMessagesOK(t *testing.T, cid string, seqs []int64, inputMsgs []*sdkws.MsgData) {
+ rdb := redis.NewClient(&redis.Options{})
+ defer rdb.Close()
+
+ cacher := msgCache{rdb: rdb}
+
+ err := cacher.PipeDeleteMessages(context.Background(), cid, seqs)
+ assert.Nil(t, err)
+
+ // validate
+ for _, msg := range inputMsgs {
+ key := cacher.getMessageCacheKey(cid, msg.Seq)
+ val := rdb.Exists(context.Background(), key).Val()
+ assert.EqualValues(t, 0, val)
+ }
+}
+
+func testPipeDeleteMessagesMix(t *testing.T, cid string, seqs []int64, inputMsgs []*sdkws.MsgData) {
+ rdb := redis.NewClient(&redis.Options{})
+ defer rdb.Close()
+
+ cacher := msgCache{rdb: rdb}
+
+ err := cacher.PipeDeleteMessages(context.Background(), cid, seqs)
+ assert.Nil(t, err)
+
+ // validate
+ for idx, msg := range inputMsgs {
+ key := cacher.getMessageCacheKey(cid, msg.Seq)
+ val, err := rdb.Exists(context.Background(), key).Result()
+ assert.Nil(t, err)
+ if idx < 90 {
+ assert.EqualValues(t, 0, val) // not exists
+ continue
+ }
+
+ assert.EqualValues(t, 1, val) // exists
+ }
+}
+
+func TestParallelDeleteMessages(t *testing.T) {
+ var (
+ cid = fmt.Sprintf("cid-%v", rand.Int63())
+ seqFirst = rand.Int63()
+ msgs = []*sdkws.MsgData{}
+ )
+
+ var seqs []int64
+ for i := 0; i < 100; i++ {
+ msgs = append(msgs, &sdkws.MsgData{
+ Seq: seqFirst + int64(i),
+ })
+ seqs = append(seqs, msgs[i].Seq)
+ }
+
+ randSeqs := []int64{}
+ for i := seqFirst + 100; i < seqFirst+200; i++ {
+ randSeqs = append(randSeqs, i)
+ }
+
+ testParallelSetMessageToCache(t, cid, msgs)
+ testParallelDeleteMessagesOK(t, cid, seqs, msgs)
+
+ // set again
+ testParallelSetMessageToCache(t, cid, msgs)
+ testParallelDeleteMessagesMix(t, cid, seqs[:90], msgs, 90)
+ testParallelDeleteMessagesOK(t, cid, seqs[90:], msgs[:90])
+
+ // set again
+ testParallelSetMessageToCache(t, cid, msgs)
+ testParallelDeleteMessagesMix(t, cid, randSeqs, msgs, 0)
+}
+
+func testParallelDeleteMessagesOK(t *testing.T, cid string, seqs []int64, inputMsgs []*sdkws.MsgData) {
+ rdb := redis.NewClient(&redis.Options{})
+ defer rdb.Close()
+
+ cacher := msgCache{rdb: rdb}
+
+ err := cacher.PipeDeleteMessages(context.Background(), cid, seqs)
+ assert.Nil(t, err)
+
+ // validate
+ for _, msg := range inputMsgs {
+ key := cacher.getMessageCacheKey(cid, msg.Seq)
+ val := rdb.Exists(context.Background(), key).Val()
+ assert.EqualValues(t, 0, val)
+ }
+}
+
+func testParallelDeleteMessagesMix(t *testing.T, cid string, seqs []int64, inputMsgs []*sdkws.MsgData, lessValNonExists int) {
+ rdb := redis.NewClient(&redis.Options{})
+ defer rdb.Close()
+
+ cacher := msgCache{rdb: rdb}
+
+ err := cacher.PipeDeleteMessages(context.Background(), cid, seqs)
+ assert.Nil(t, err)
+
+ // validate
+ for idx, msg := range inputMsgs {
+ key := cacher.getMessageCacheKey(cid, msg.Seq)
+ val, err := rdb.Exists(context.Background(), key).Result()
+ assert.Nil(t, err)
+ if idx < lessValNonExists {
+ assert.EqualValues(t, 0, val) // not exists
+ continue
+ }
+
+ assert.EqualValues(t, 1, val) // exists
+ }
+}
diff --git a/pkg/common/db/controller/msg.go b/pkg/common/db/controller/msg.go
index b9cf9427f..fb0a9c702 100644
--- a/pkg/common/db/controller/msg.go
+++ b/pkg/common/db/controller/msg.go
@@ -29,6 +29,8 @@ import (
"github.com/OpenIMSDK/tools/errs"
"github.com/OpenIMSDK/tools/log"
+ "go.mongodb.org/mongo-driver/mongo"
+
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
"github.com/openimsdk/open-im-server/v3/pkg/common/convert"
"github.com/openimsdk/open-im-server/v3/pkg/common/db/cache"
@@ -36,8 +38,6 @@ import (
"github.com/openimsdk/open-im-server/v3/pkg/common/db/unrelation"
"github.com/openimsdk/open-im-server/v3/pkg/common/kafka"
- "go.mongodb.org/mongo-driver/mongo"
-
pbmsg "github.com/OpenIMSDK/protocol/msg"
"github.com/OpenIMSDK/protocol/sdkws"
"github.com/OpenIMSDK/tools/utils"
diff --git a/scripts/genconfig.sh b/scripts/genconfig.sh
index 9c5f39a11..2371edc9d 100755
--- a/scripts/genconfig.sh
+++ b/scripts/genconfig.sh
@@ -13,8 +13,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-# 本脚本功能:根据 scripts/environment.sh 配置,生成 OPENIM 组件 YAML 配置文件。
-# 示例:./scripts/genconfig.sh scripts/install/environment.sh scripts/template/config.yaml
+# Function of this script: Generate the OPENIM component YAML configuration file according to the scripts/environment.sh configuration.
+# eg:./scripts/genconfig.sh scripts/install/environment.sh scripts/template/config.yaml
# Read: https://github.com/openimsdk/open-im-server/blob/main/docs/contrib/init-config.md
env_file="$1"
@@ -29,8 +29,17 @@ if [ $# -ne 2 ];then
exit 1
fi
-# Check if the required commands exist
openim::util::require-dig
+result=$?
+if [ $result -ne 0 ]; then
+ openim::log::info "Please install 'dig' to use this feature."
+ openim::log::info "Installation instructions:"
+ openim::log::info " For Ubuntu/Debian: sudo apt-get install dnsutils"
+ openim::log::info " For CentOS/RedHat: sudo yum install bind-utils"
+ openim::log::info " For macOS: 'dig' should be preinstalled. If missing, try: brew install bind"
+ openim::log::info " For Windows: Install BIND9 tools from https://www.isc.org/download/"
+ openim::log::error_exit "Error: 'dig' command is required but not installed."
+fi
source "${env_file}"
diff --git a/scripts/init-config.sh b/scripts/init-config.sh
index 9da84fb21..a4672c62d 100755
--- a/scripts/init-config.sh
+++ b/scripts/init-config.sh
@@ -31,6 +31,8 @@ readonly ENV_FILE=${ENV_FILE:-"${OPENIM_ROOT}/scripts/install/environment.sh"}
declare -A TEMPLATES=(
["${OPENIM_ROOT}/deployments/templates/env_template.yaml"]="${OPENIM_ROOT}/.env"
["${OPENIM_ROOT}/deployments/templates/openim.yaml"]="${OPENIM_ROOT}/config/config.yaml"
+ ["${OPENIM_ROOT}/deployments/templates/prometheus.yml"]="${OPENIM_ROOT}/config/prometheus.yml"
+ ["${OPENIM_ROOT}/deployments/templates/alertmanager.yml"]="${OPENIM_ROOT}/config/alertmanager.yml"
)
for template in "${!TEMPLATES[@]}"; do
@@ -45,6 +47,7 @@ for template in "${!TEMPLATES[@]}"; do
openim::log::error "Error processing template file ${template}"
exit 1
}
+ sleep 0.5
done
done
diff --git a/scripts/install/environment.sh b/scripts/install/environment.sh
index 26c9141e0..777f88e18 100755
--- a/scripts/install/environment.sh
+++ b/scripts/install/environment.sh
@@ -116,7 +116,12 @@ LAST_OCTET=$((LAST_OCTET + 1))
PROMETHEUS_NETWORK_ADDRESS=$(generate_ip)
LAST_OCTET=$((LAST_OCTET + 1))
GRAFANA_NETWORK_ADDRESS=$(generate_ip)
-
+LAST_OCTET=$((LAST_OCTET + 1))
+NODE_EXPORTER_NETWORK_ADDRESS=$(generate_ip)
+LAST_OCTET=$((LAST_OCTET + 1))
+OPENIM_ADMIN_FRONT_NETWORK_ADDRESS=$(generate_ip)
+LAST_OCTET=$((LAST_OCTET + 1))
+ALERT_MANAGER_NETWORK_ADDRESS=$(generate_ip)
###################### openim 配置 ######################
# read: https://github.com/openimsdk/open-im-server/blob/main/deployment/README.md
def "OPENIM_DATA_DIR" "/data/openim"
@@ -242,6 +247,9 @@ def "OPENIM_WEB_PORT" "11001" # openim-web的端口
def "OPENIM_WEB_ADDRESS" "${DOCKER_BRIDGE_GATEWAY}" # openim-web的地址
def "OPENIM_WEB_DIST_PATH" "/app/dist" # openim-web的dist路径
+###################### openim-admin-front 配置信息 ######################
+def "OPENIM_ADMIN_FRONT_PORT" "11002" # openim-admin-front的端口
+
###################### RPC 配置信息 ######################
def "RPC_REGISTER_IP" # RPC的注册IP
def "RPC_LISTEN_IP" "0.0.0.0" # RPC的监听IP
@@ -250,8 +258,38 @@ def "RPC_LISTEN_IP" "0.0.0.0" # RPC的监听IP
def "PROMETHEUS_PORT" "19090" # Prometheus的端口
def "PROMETHEUS_ADDRESS" "${DOCKER_BRIDGE_GATEWAY}" # Prometheus的地址
+###################### node-exporter 配置 ######################
+def "NODE_EXPORTER_PORT" "19100" # node-exporter的端口
+def "NODE_EXPORTER_ADDRESS" "${DOCKER_BRIDGE_GATEWAY}" # node-exporter的地址
+
+###################### alertmanagerS 配置 ######################
+def "ALERT_MANAGER_PORT" "19093" # node-exporter的端口
+def "ALERT_MANAGER_ADDRESS" "${DOCKER_BRIDGE_GATEWAY}" # node-exporter的地址
+
+###################### AlertManager Configuration Script ######################
+# 解析超时
+readonly ALERTMANAGER_RESOLVE_TIMEOUT=${ALERTMANAGER_RESOLVE_TIMEOUT:-'5m'}
+# 发件人邮箱
+readonly ALERTMANAGER_SMTP_FROM=${ALERTMANAGER_SMTP_FROM:-'alert@openim.io'}
+# SMTP服务器地址和端口
+readonly ALERTMANAGER_SMTP_SMARTHOST=${ALERTMANAGER_SMTP_SMARTHOST:-'smtp.163.com:465'}
+# SMTP认证用户名
+readonly ALERTMANAGER_SMTP_AUTH_USERNAME=${SMTP_USERNAME:-"alert@openim.io"}
+# SMTP认证密码
+readonly ALERTMANAGER_SMTP_AUTH_PASSWORD=${SMTP_PASSWORD:-"YOURAUTHPASSWORD"}
+# SMTP是否需要TLS
+readonly ALERTMANAGER_SMTP_REQUIRE_TLS=${ALERTMANAGER_SMTP_REQUIRE_TLS:-"false"}
+# SMTP HELO/EHLO标识符
+readonly ALERTMANAGER_SMTP_HELLO=${ALERTMANAGER_SMTP_HELLO:-"xxx监控告警"}
+# 邮箱接收人
+readonly ALERTMANAGER_EMAIL_TO=${ALERTMANAGER_EMAIL_TO:-"{EMAIL_TO:-'alert@example.com'}"}
+# 邮箱主题
+readonly ALERTMANAGER_EMAIL_SUBJECT=${ALERTMANAGER_EMAIL_SUBJECT:-"{EMAIL_SUBJECT:-'[Alert] Notification'}"}
+# 是否发送已解决的告警
+readonly ALERTMANAGER_SEND_RESOLVED=${ALERTMANAGER_SEND_RESOLVED:-"{SEND_RESOLVED:-'true'}"}
+
###################### Grafana 配置信息 ######################
-def "GRAFANA_PORT" "3000" # Grafana的端口
+def "GRAFANA_PORT" "13000" # Grafana的端口
def "GRAFANA_ADDRESS" "${DOCKER_BRIDGE_GATEWAY}" # Grafana的地址
###################### RPC Port Configuration Variables ######################
@@ -340,8 +378,9 @@ def "IOS_BADGE_COUNT" "true" # IOS徽章计数
def "IOS_PRODUCTION" "false" # IOS生产
###################### Prometheus 配置信息 ######################
-def "PROMETHEUS_ENABLE" "false" # 是否启用 Prometheus
-def "PROMETHEUS_URL" "/prometheus"
+# 是否启用 Prometheus
+readonly PROMETHEUS_ENABLE=${PROMETHEUS_ENABLE:-'false'}
+def "PROMETHEUS_URL" "${GRAFANA_ADDRESS}:${GRAFANA_PORT}"
# Api 服务的 Prometheus 端口
readonly API_PROM_PORT=${API_PROM_PORT:-'20100'}
# User 服务的 Prometheus 端口
@@ -367,6 +406,7 @@ readonly THIRD_PROM_PORT=${THIRD_PROM_PORT:-'21301'}
# Message Transfer 服务的 Prometheus 端口列表
readonly MSG_TRANSFER_PROM_PORT=${MSG_TRANSFER_PROM_PORT:-'21400, 21401, 21402, 21403'}
+readonly MSG_TRANSFER_PROM_ADDRESS_PORT=${MSG_TRANSFER_PROM_ADDRESS_PORT:-"${DOCKER_BRIDGE_GATEWAY}:21400, ${DOCKER_BRIDGE_GATEWAY}:21401, ${DOCKER_BRIDGE_GATEWAY}:21402, ${DOCKER_BRIDGE_GATEWAY}:21403"}
###################### OpenIM openim-api ######################
def "OPENIM_API_HOST" "127.0.0.1"
diff --git a/scripts/install/install-protobuf.sh b/scripts/install/install-protobuf.sh
index c75dc7e25..33ceaeb0d 100755
--- a/scripts/install/install-protobuf.sh
+++ b/scripts/install/install-protobuf.sh
@@ -22,6 +22,8 @@
# It can be downloaded from the following link:
# https://github.com/OpenIMSDK/Open-IM-Protoc/releases/tag/v1.0.0
#
+# About the tool:
+# https://github.com/openimsdk/open-im-server/blob/main/docs/contrib/protoc-tools.md
# Download link (Windows): https://github.com/OpenIMSDK/Open-IM-Protoc/releases/download/v1.0.0/windows.zip
# Download link (Linux): https://github.com/OpenIMSDK/Open-IM-Protoc/releases/download/v1.0.0/linux.zip
#
diff --git a/scripts/install/install.sh b/scripts/install/install.sh
index afa07df6d..b88fe9083 100755
--- a/scripts/install/install.sh
+++ b/scripts/install/install.sh
@@ -76,8 +76,8 @@ function openim::install::install_openim() {
openim::log::info "check openim dependency"
openim::common::sudo "cp -r ${OPENIM_ROOT}/config/* ${OPENIM_CONFIG_DIR}/"
- echo ${LINUX_PASSWORD} | sudo -S bash -c \
- "${OPENIM_ROOT}/scripts/genconfig.sh ${ENV_FILE} deployments/templates/openim.yaml > ${OPENIM_CONFIG_DIR}/config.yaml"
+ ${OPENIM_ROOT}/scripts/genconfig.sh ${ENV_FILE} ${OPENIM_ROOT}/deployments/templates/openim.yaml > ${OPENIM_CONFIG_DIR}/config.yaml
+ ${OPENIM_ROOT}/scripts/genconfig.sh ${ENV_FILE} ${OPENIM_ROOT}/deployments/templates/prometheus.yml > ${OPENIM_CONFIG_DIR}/prometheus.yml
openim::util::check_ports ${OPENIM_DEPENDENCY_PORT_LISTARIES[@]}
diff --git a/scripts/install/openim-msggateway.sh b/scripts/install/openim-msggateway.sh
index af3d87b23..2b2a84b12 100755
--- a/scripts/install/openim-msggateway.sh
+++ b/scripts/install/openim-msggateway.sh
@@ -123,6 +123,7 @@ function openim::msggateway::status() {
# Check the running status of the ${SERVER_NAME}. If active (running) is displayed, the ${SERVER_NAME} is started successfully.
systemctl status ${SERVER_NAME}|grep -q 'active' || {
openim::log::error "${SERVER_NAME} failed to start, maybe not installed properly"
+
return 1
}
diff --git a/scripts/lib/util.sh b/scripts/lib/util.sh
index de94d8ceb..ad3baa6bf 100755
--- a/scripts/lib/util.sh
+++ b/scripts/lib/util.sh
@@ -1121,8 +1121,7 @@ function openim::util::check-file-in-alphabetical-order {
# Checks whether jq is installed.
function openim::util::require-jq {
if ! command -v jq &>/dev/null; then
- echo "jq not found. Please install." 1>&2
- return 1
+ openim::log::errexit "jq not found. Please install." 1>&2
fi
}
@@ -1130,15 +1129,10 @@ function openim::util::require-jq {
# Checks whether dig is installed and provides installation instructions if it is not.
function openim::util::require-dig {
if ! command -v dig &>/dev/null; then
- echo "dig command not found."
- echo "Please install 'dig' to use this feature."
- echo "Installation instructions:"
- echo " For Ubuntu/Debian: sudo apt-get install dnsutils"
- echo " For CentOS/RedHat: sudo yum install bind-utils"
- echo " For macOS: 'dig' should be preinstalled. If missing, try: brew install bind"
- echo " For Windows: Install BIND9 tools from https://www.isc.org/download/"
+ openim::log::error "dig command not found."
return 1
fi
+ return 0
}
# outputs md5 hash of $1, works on macOS and Linux
diff --git a/test/e2e/api/user/curd.go b/test/e2e/api/user/curd.go
index 9e8286384..c0380b235 100644
--- a/test/e2e/api/user/curd.go
+++ b/test/e2e/api/user/curd.go
@@ -52,5 +52,6 @@ func GetUsersOnlineStatus(token string, userIDs []string) error {
requestBody := GetUsersOnlineStatusRequest{
UserIDs: userIDs,
}
+
return sendPostRequestWithToken(url, token, requestBody)
}