mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-07 19:22:14 +09:00
feat(gateway,admin): Expand stats for metrics (#3064)
This commit is contained in:
@@ -12059,6 +12059,9 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"status": {"type": "string", "minLength": 1, "maxLength": 256},
|
"status": {"type": "string", "minLength": 1, "maxLength": 256},
|
||||||
"sessions": {"$ref": "#/components/schemas/Int32Type"},
|
"sessions": {"$ref": "#/components/schemas/Int32Type"},
|
||||||
|
"session_resumes_total": {"type": "integer", "minimum": 0, "maximum": 9007199254740991},
|
||||||
|
"websocket_dispatches_total": {"type": "integer", "minimum": 0, "maximum": 9007199254740991},
|
||||||
|
"websocket_dispatch_drops_total": {"type": "integer", "minimum": 0, "maximum": 9007199254740991},
|
||||||
"guilds": {"$ref": "#/components/schemas/Int32Type"},
|
"guilds": {"$ref": "#/components/schemas/Int32Type"},
|
||||||
"presences": {"$ref": "#/components/schemas/Int32Type"},
|
"presences": {"$ref": "#/components/schemas/Int32Type"},
|
||||||
"calls": {"$ref": "#/components/schemas/Int32Type"},
|
"calls": {"$ref": "#/components/schemas/Int32Type"},
|
||||||
@@ -12085,6 +12088,24 @@
|
|||||||
"node_id": {"type": "string", "minLength": 1, "maxLength": 256},
|
"node_id": {"type": "string", "minLength": 1, "maxLength": 256},
|
||||||
"status": {"type": "string", "minLength": 1, "maxLength": 256},
|
"status": {"type": "string", "minLength": 1, "maxLength": 256},
|
||||||
"sessions": {"$ref": "#/components/schemas/Int32Type"},
|
"sessions": {"$ref": "#/components/schemas/Int32Type"},
|
||||||
|
"session_resumes_total": {
|
||||||
|
"nullable": true,
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 9007199254740991
|
||||||
|
},
|
||||||
|
"websocket_dispatches_total": {
|
||||||
|
"nullable": true,
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 9007199254740991
|
||||||
|
},
|
||||||
|
"websocket_dispatch_drops_total": {
|
||||||
|
"nullable": true,
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 9007199254740991
|
||||||
|
},
|
||||||
"guilds": {"$ref": "#/components/schemas/Int32Type"},
|
"guilds": {"$ref": "#/components/schemas/Int32Type"},
|
||||||
"presences": {"$ref": "#/components/schemas/Int32Type"},
|
"presences": {"$ref": "#/components/schemas/Int32Type"},
|
||||||
"calls": {"$ref": "#/components/schemas/Int32Type"},
|
"calls": {"$ref": "#/components/schemas/Int32Type"},
|
||||||
@@ -12138,6 +12159,9 @@
|
|||||||
"node_id",
|
"node_id",
|
||||||
"status",
|
"status",
|
||||||
"sessions",
|
"sessions",
|
||||||
|
"session_resumes_total",
|
||||||
|
"websocket_dispatches_total",
|
||||||
|
"websocket_dispatch_drops_total",
|
||||||
"guilds",
|
"guilds",
|
||||||
"presences",
|
"presences",
|
||||||
"calls",
|
"calls",
|
||||||
@@ -12153,6 +12177,9 @@
|
|||||||
"required": [
|
"required": [
|
||||||
"status",
|
"status",
|
||||||
"sessions",
|
"sessions",
|
||||||
|
"session_resumes_total",
|
||||||
|
"websocket_dispatches_total",
|
||||||
|
"websocket_dispatch_drops_total",
|
||||||
"guilds",
|
"guilds",
|
||||||
"presences",
|
"presences",
|
||||||
"calls",
|
"calls",
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ pub(crate) fn stat_card(label: &str, value: &str) -> Markup {
|
|||||||
|
|
||||||
pub(crate) fn node_stats_section(data: &serde_json::Value, expanded: bool, base: &str) -> Markup {
|
pub(crate) fn node_stats_section(data: &serde_json::Value, expanded: bool, base: &str) -> Markup {
|
||||||
let sessions = data.get("sessions").and_then(|v| v.as_u64()).unwrap_or(0);
|
let sessions = data.get("sessions").and_then(|v| v.as_u64()).unwrap_or(0);
|
||||||
|
let reconnects: u64 = data
|
||||||
|
.get("session_resumes_total")
|
||||||
|
.and_then(|v| v.as_u64())
|
||||||
|
.unwrap_or(0);
|
||||||
let guilds = data.get("guilds").and_then(|v| v.as_u64()).unwrap_or(0);
|
let guilds = data.get("guilds").and_then(|v| v.as_u64()).unwrap_or(0);
|
||||||
let presences = data.get("presences").and_then(|v| v.as_u64()).unwrap_or(0);
|
let presences = data.get("presences").and_then(|v| v.as_u64()).unwrap_or(0);
|
||||||
let calls = data.get("calls").and_then(|v| v.as_u64()).unwrap_or(0);
|
let calls = data.get("calls").and_then(|v| v.as_u64()).unwrap_or(0);
|
||||||
@@ -64,6 +68,7 @@ pub(crate) fn node_stats_section(data: &serde_json::Value, expanded: bool, base:
|
|||||||
div class="grid grid-cols-2 gap-3 sm:gap-4 md:grid-cols-3 lg:grid-cols-6" {
|
div class="grid grid-cols-2 gap-3 sm:gap-4 md:grid-cols-3 lg:grid-cols-6" {
|
||||||
(stat_card("Nodes", &node_count.to_string()))
|
(stat_card("Nodes", &node_count.to_string()))
|
||||||
(stat_card("Sessions", &sessions.to_string()))
|
(stat_card("Sessions", &sessions.to_string()))
|
||||||
|
(stat_card("Reconnects", &reconnects.to_string()))
|
||||||
(stat_card("Guilds", &guilds.to_string()))
|
(stat_card("Guilds", &guilds.to_string()))
|
||||||
(stat_card("Presences", &presences.to_string()))
|
(stat_card("Presences", &presences.to_string()))
|
||||||
(stat_card("Calls", &calls.to_string()))
|
(stat_card("Calls", &calls.to_string()))
|
||||||
@@ -83,6 +88,7 @@ pub(crate) fn node_stats_table(nodes: &[serde_json::Value]) -> Markup {
|
|||||||
tr {
|
tr {
|
||||||
th class="px-6 py-3 text-left text-neutral-600 text-xs uppercase" { "Node" }
|
th class="px-6 py-3 text-left text-neutral-600 text-xs uppercase" { "Node" }
|
||||||
th class="px-6 py-3 text-right text-neutral-600 text-xs uppercase" { "Sessions" }
|
th class="px-6 py-3 text-right text-neutral-600 text-xs uppercase" { "Sessions" }
|
||||||
|
th class="px-6 py-3 text-right text-neutral-600 text-xs uppercase" { "Session Resumes" }
|
||||||
th class="px-6 py-3 text-right text-neutral-600 text-xs uppercase" { "Guilds" }
|
th class="px-6 py-3 text-right text-neutral-600 text-xs uppercase" { "Guilds" }
|
||||||
th class="px-6 py-3 text-right text-neutral-600 text-xs uppercase" { "Presences" }
|
th class="px-6 py-3 text-right text-neutral-600 text-xs uppercase" { "Presences" }
|
||||||
th class="px-6 py-3 text-right text-neutral-600 text-xs uppercase" { "Calls" }
|
th class="px-6 py-3 text-right text-neutral-600 text-xs uppercase" { "Calls" }
|
||||||
@@ -95,6 +101,7 @@ pub(crate) fn node_stats_table(nodes: &[serde_json::Value]) -> Markup {
|
|||||||
@let label = format_node_id(node_id, i);
|
@let label = format_node_id(node_id, i);
|
||||||
@let status = node.get("status").and_then(|v| v.as_str()).unwrap_or("-");
|
@let status = node.get("status").and_then(|v| v.as_str()).unwrap_or("-");
|
||||||
@let ns = node.get("sessions").and_then(|v| v.as_u64()).unwrap_or(0);
|
@let ns = node.get("sessions").and_then(|v| v.as_u64()).unwrap_or(0);
|
||||||
|
@let nsr = node.get("session_resumes_total").and_then(|v| v.as_u64()).unwrap_or(0);
|
||||||
@let ng = node.get("guilds").and_then(|v| v.as_u64()).unwrap_or(0);
|
@let ng = node.get("guilds").and_then(|v| v.as_u64()).unwrap_or(0);
|
||||||
@let np = node.get("presences").and_then(|v| v.as_u64()).unwrap_or(0);
|
@let np = node.get("presences").and_then(|v| v.as_u64()).unwrap_or(0);
|
||||||
@let nc = node.get("calls").and_then(|v| v.as_u64()).unwrap_or(0);
|
@let nc = node.get("calls").and_then(|v| v.as_u64()).unwrap_or(0);
|
||||||
@@ -105,6 +112,7 @@ pub(crate) fn node_stats_table(nodes: &[serde_json::Value]) -> Markup {
|
|||||||
div class="text-neutral-500 text-xs" { (status) }
|
div class="text-neutral-500 text-xs" { (status) }
|
||||||
}
|
}
|
||||||
td class="whitespace-nowrap px-6 py-4 text-right text-sm" { (ns) }
|
td class="whitespace-nowrap px-6 py-4 text-right text-sm" { (ns) }
|
||||||
|
td class="whitespace-nowrap px-6 py-4 text-right text-sm" { (nsr) }
|
||||||
td class="whitespace-nowrap px-6 py-4 text-right text-sm" { (ng) }
|
td class="whitespace-nowrap px-6 py-4 text-right text-sm" { (ng) }
|
||||||
td class="whitespace-nowrap px-6 py-4 text-right text-sm" { (np) }
|
td class="whitespace-nowrap px-6 py-4 text-right text-sm" { (np) }
|
||||||
td class="whitespace-nowrap px-6 py-4 text-right text-sm" { (nc) }
|
td class="whitespace-nowrap px-6 py-4 text-right text-sm" { (nc) }
|
||||||
|
|||||||
@@ -1744,6 +1744,8 @@ export class GatewayService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getNodeStats(): Promise<GatewayNodeStats> {
|
async getNodeStats(): Promise<GatewayNodeStats> {
|
||||||
|
const a = this.call<GatewayNodeStats>('process.node_stats', {});
|
||||||
|
console.log(a.then((stats) => console.log('Node stats:', stats)));
|
||||||
return this.call<GatewayNodeStats>('process.node_stats', {});
|
return this.call<GatewayNodeStats>('process.node_stats', {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,6 +62,9 @@ export interface GatewayNodeStatsEntry {
|
|||||||
node_id: string;
|
node_id: string;
|
||||||
status: string;
|
status: string;
|
||||||
sessions: number;
|
sessions: number;
|
||||||
|
session_resumes_total: number | null;
|
||||||
|
websocket_dispatches_total: number | null;
|
||||||
|
websocket_dispatch_drops_total: number | null;
|
||||||
guilds: number;
|
guilds: number;
|
||||||
presences: number;
|
presences: number;
|
||||||
calls: number;
|
calls: number;
|
||||||
@@ -74,6 +77,9 @@ export interface GatewayNodeStatsEntry {
|
|||||||
export interface GatewayNodeStats {
|
export interface GatewayNodeStats {
|
||||||
status: string;
|
status: string;
|
||||||
sessions: number;
|
sessions: number;
|
||||||
|
session_resumes_total: number;
|
||||||
|
websocket_dispatches_total: number;
|
||||||
|
websocket_dispatch_drops_total: number;
|
||||||
guilds: number;
|
guilds: number;
|
||||||
presences: number;
|
presences: number;
|
||||||
calls: number;
|
calls: number;
|
||||||
|
|||||||
@@ -968,6 +968,9 @@ export class NoopGatewayService extends IGatewayService {
|
|||||||
return {
|
return {
|
||||||
status: 'ok',
|
status: 'ok',
|
||||||
sessions: 0,
|
sessions: 0,
|
||||||
|
session_resumes_total: 0,
|
||||||
|
websocket_dispatches_total: 0,
|
||||||
|
websocket_dispatch_drops_total: 0,
|
||||||
guilds: 0,
|
guilds: 0,
|
||||||
presences: 0,
|
presences: 0,
|
||||||
calls: 0,
|
calls: 0,
|
||||||
|
|||||||
@@ -269,6 +269,7 @@ const ACCEPTED_TABLE_FINDINGS = new Map<string, Readonly<Partial<Record<TableRul
|
|||||||
['admin-api/applications.mdx', {'table-identifier': 1}],
|
['admin-api/applications.mdx', {'table-identifier': 1}],
|
||||||
['admin-api/bulk-jobs.mdx', {'table-fit': 1}],
|
['admin-api/bulk-jobs.mdx', {'table-fit': 1}],
|
||||||
['admin-api/discovery.mdx', {'table-identifier': 1}],
|
['admin-api/discovery.mdx', {'table-identifier': 1}],
|
||||||
|
['admin-api/gateway.mdx', {'table-identifier': 2}],
|
||||||
['admin-api/guilds.mdx', {'table-identifier': 3}],
|
['admin-api/guilds.mdx', {'table-identifier': 3}],
|
||||||
['admin-api/index.mdx', {'table-identifier': 3}],
|
['admin-api/index.mdx', {'table-identifier': 3}],
|
||||||
['admin-api/instance.mdx', {'table-identifier': 8}],
|
['admin-api/instance.mdx', {'table-identifier': 8}],
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ One snapshot of the Gateway cluster, taken across the nodes the request polled.
|
|||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| status<sup>1</sup> | string | The aggregate health of the cluster |
|
| status<sup>1</sup> | string | The aggregate health of the cluster |
|
||||||
| sessions | integer | The connected client Gateway sessions, summed across nodes |
|
| sessions | integer | The connected client Gateway sessions, summed across nodes |
|
||||||
|
| session_resumes_total | integer | The total number of session resumes, summed across nodes |
|
||||||
|
| websocket_dispatches_total | integer | The total number of WebSocket dispatches, summed across nodes |
|
||||||
|
| websocket_dispatch_drops_total | integer | The total number of WebSocket dispatches dropped, summed across nodes |
|
||||||
| guilds | integer | The live guild processes, summed across nodes |
|
| guilds | integer | The live guild processes, summed across nodes |
|
||||||
| presences | integer | The tracked presences, summed across nodes |
|
| presences | integer | The tracked presences, summed across nodes |
|
||||||
| calls | integer | The live calls, summed across nodes |
|
| calls | integer | The live calls, summed across nodes |
|
||||||
@@ -48,6 +51,9 @@ One snapshot of the Gateway cluster, taken across the nodes the request polled.
|
|||||||
{
|
{
|
||||||
"status": "healthy",
|
"status": "healthy",
|
||||||
"sessions": 4820,
|
"sessions": 4820,
|
||||||
|
"session_resumes_total": 1234,
|
||||||
|
"websocket_dispatches_total": 1234567,
|
||||||
|
"websocket_dispatch_drops_total": 123,
|
||||||
"guilds": 1913,
|
"guilds": 1913,
|
||||||
"presences": 4611,
|
"presences": 4611,
|
||||||
"calls": 12,
|
"calls": 12,
|
||||||
@@ -71,6 +77,9 @@ One entry for each node the request polled, including nodes that did not answer
|
|||||||
| node_id<sup>1</sup> | string | The name the node reports for itself |
|
| node_id<sup>1</sup> | string | The name the node reports for itself |
|
||||||
| status<sup>2</sup> | string | The health this node reported |
|
| status<sup>2</sup> | string | The health this node reported |
|
||||||
| sessions | integer | The connected client Gateway sessions on this node |
|
| sessions | integer | The connected client Gateway sessions on this node |
|
||||||
|
| session_resumes_total | integer | The total number of session resumes on this node |
|
||||||
|
| websocket_dispatches_total | integer | The total number of WebSocket dispatches on this node |
|
||||||
|
| websocket_dispatch_drops_total | integer | The total number of WebSocket dispatches dropped on this node |
|
||||||
| guilds | integer | The live guild processes on this node |
|
| guilds | integer | The live guild processes on this node |
|
||||||
| presences | integer | The tracked presences on this node |
|
| presences | integer | The tracked presences on this node |
|
||||||
| calls | integer | The live calls on this node |
|
| calls | integer | The live calls on this node |
|
||||||
|
|||||||
@@ -8,6 +8,12 @@
|
|||||||
record_discovery_resolve_failure/0,
|
record_discovery_resolve_failure/0,
|
||||||
record_membership_transition/1,
|
record_membership_transition/1,
|
||||||
record_owner_resolution/1,
|
record_owner_resolution/1,
|
||||||
|
record_resume/0,
|
||||||
|
resumes_total/0,
|
||||||
|
record_dispatch/0,
|
||||||
|
dispatches_total/0,
|
||||||
|
record_dispatch_drop/0,
|
||||||
|
dispatch_drops_total/0,
|
||||||
snapshot/0,
|
snapshot/0,
|
||||||
reset_for_tests/0
|
reset_for_tests/0
|
||||||
]).
|
]).
|
||||||
@@ -18,7 +24,10 @@
|
|||||||
-define(MEMBERSHIP_DOWN_IDX, 3).
|
-define(MEMBERSHIP_DOWN_IDX, 3).
|
||||||
-define(OWNER_SELF_IDX, 4).
|
-define(OWNER_SELF_IDX, 4).
|
||||||
-define(OWNER_PEER_IDX, 5).
|
-define(OWNER_PEER_IDX, 5).
|
||||||
-define(COUNTER_COUNT, 5).
|
-define(RESUMES_IDX, 6).
|
||||||
|
-define(DISPATCHES_IDX, 7).
|
||||||
|
-define(DISPATCH_DROPS_IDX, 8).
|
||||||
|
-define(COUNTER_COUNT, 8).
|
||||||
|
|
||||||
-spec init() -> ok.
|
-spec init() -> ok.
|
||||||
init() ->
|
init() ->
|
||||||
@@ -41,11 +50,38 @@ record_owner_resolution(self) ->
|
|||||||
record_owner_resolution(peer) ->
|
record_owner_resolution(peer) ->
|
||||||
add(?OWNER_PEER_IDX, 1).
|
add(?OWNER_PEER_IDX, 1).
|
||||||
|
|
||||||
|
-spec record_resume() -> ok.
|
||||||
|
record_resume() ->
|
||||||
|
add(?RESUMES_IDX, 1).
|
||||||
|
|
||||||
|
-spec resumes_total() -> non_neg_integer().
|
||||||
|
resumes_total() ->
|
||||||
|
counters:get(counters_ref(), ?RESUMES_IDX).
|
||||||
|
|
||||||
|
-spec record_dispatch() -> ok.
|
||||||
|
record_dispatch() ->
|
||||||
|
add(?DISPATCHES_IDX, 1).
|
||||||
|
|
||||||
|
-spec dispatches_total() -> non_neg_integer().
|
||||||
|
dispatches_total() ->
|
||||||
|
counters:get(counters_ref(), ?DISPATCHES_IDX).
|
||||||
|
|
||||||
|
-spec record_dispatch_drop() -> ok.
|
||||||
|
record_dispatch_drop() ->
|
||||||
|
add(?DISPATCH_DROPS_IDX, 1).
|
||||||
|
|
||||||
|
-spec dispatch_drops_total() -> non_neg_integer().
|
||||||
|
dispatch_drops_total() ->
|
||||||
|
counters:get(counters_ref(), ?DISPATCH_DROPS_IDX).
|
||||||
|
|
||||||
-spec snapshot() -> map().
|
-spec snapshot() -> map().
|
||||||
snapshot() ->
|
snapshot() ->
|
||||||
Counters = counters_ref(),
|
Counters = counters_ref(),
|
||||||
#{
|
#{
|
||||||
<<"gateway_cluster_member_count">> => gateway_cluster_membership:alive_count(),
|
<<"gateway_cluster_member_count">> => gateway_cluster_membership:alive_count(),
|
||||||
|
<<"session_resumes_total">> => counters:get(Counters, ?RESUMES_IDX),
|
||||||
|
<<"websocket_dispatches_total">> => counters:get(Counters, ?DISPATCHES_IDX),
|
||||||
|
<<"websocket_dispatch_drops_total">> => counters:get(Counters, ?DISPATCH_DROPS_IDX),
|
||||||
<<"gateway_cluster_discovery_resolve_failures_total">> =>
|
<<"gateway_cluster_discovery_resolve_failures_total">> =>
|
||||||
counters:get(Counters, ?DISCOVERY_RESOLVE_FAILURES_IDX),
|
counters:get(Counters, ?DISCOVERY_RESOLVE_FAILURES_IDX),
|
||||||
<<"gateway_cluster_membership_transitions_total">> => #{
|
<<"gateway_cluster_membership_transitions_total">> => #{
|
||||||
@@ -83,7 +119,7 @@ valid_counters_or_new(Counters) ->
|
|||||||
validate_counters(Counters) ->
|
validate_counters(Counters) ->
|
||||||
try
|
try
|
||||||
Ref = eqwalizer:dynamic_cast(Counters),
|
Ref = eqwalizer:dynamic_cast(Counters),
|
||||||
_ = counters:get(Ref, ?DISCOVERY_RESOLVE_FAILURES_IDX),
|
_ = counters:get(Ref, ?COUNTER_COUNT),
|
||||||
{ok, Ref}
|
{ok, Ref}
|
||||||
catch
|
catch
|
||||||
_:_ -> error
|
_:_ -> error
|
||||||
@@ -108,6 +144,7 @@ snapshot_defaults_to_zero_test() ->
|
|||||||
persistent_term:erase({gateway_cluster_membership, members}),
|
persistent_term:erase({gateway_cluster_membership, members}),
|
||||||
Snapshot = snapshot(),
|
Snapshot = snapshot(),
|
||||||
?assertEqual(1, maps:get(<<"gateway_cluster_member_count">>, Snapshot)),
|
?assertEqual(1, maps:get(<<"gateway_cluster_member_count">>, Snapshot)),
|
||||||
|
?assertEqual(0, maps:get(<<"session_resumes_total">>, Snapshot)),
|
||||||
?assertEqual(0, maps:get(<<"gateway_cluster_discovery_resolve_failures_total">>, Snapshot)),
|
?assertEqual(0, maps:get(<<"gateway_cluster_discovery_resolve_failures_total">>, Snapshot)),
|
||||||
Membership = maps:get(<<"gateway_cluster_membership_transitions_total">>, Snapshot),
|
Membership = maps:get(<<"gateway_cluster_membership_transitions_total">>, Snapshot),
|
||||||
?assertEqual(0, maps:get(<<"up">>, Membership)),
|
?assertEqual(0, maps:get(<<"up">>, Membership)),
|
||||||
@@ -117,16 +154,41 @@ records_counters_test() ->
|
|||||||
reset_for_tests(),
|
reset_for_tests(),
|
||||||
record_discovery_resolve_failure(),
|
record_discovery_resolve_failure(),
|
||||||
record_membership_transition(up),
|
record_membership_transition(up),
|
||||||
|
record_resume(),
|
||||||
record_membership_transition(down),
|
record_membership_transition(down),
|
||||||
record_owner_resolution(self),
|
record_owner_resolution(self),
|
||||||
record_owner_resolution(peer),
|
record_owner_resolution(peer),
|
||||||
Snapshot = snapshot(),
|
Snapshot = snapshot(),
|
||||||
?assertEqual(1, maps:get(<<"gateway_cluster_discovery_resolve_failures_total">>, Snapshot)),
|
?assertEqual(1, maps:get(<<"gateway_cluster_discovery_resolve_failures_total">>, Snapshot)),
|
||||||
Membership = maps:get(<<"gateway_cluster_membership_transitions_total">>, Snapshot),
|
Membership = maps:get(<<"gateway_cluster_membership_transitions_total">>, Snapshot),
|
||||||
|
?assertEqual(1, maps:get(<<"session_resumes_total">>, Snapshot)),
|
||||||
Owners = maps:get(<<"gateway_node_router_owner_resolutions_total">>, Snapshot),
|
Owners = maps:get(<<"gateway_node_router_owner_resolutions_total">>, Snapshot),
|
||||||
?assertEqual(1, maps:get(<<"up">>, Membership)),
|
?assertEqual(1, maps:get(<<"up">>, Membership)),
|
||||||
?assertEqual(1, maps:get(<<"down">>, Membership)),
|
?assertEqual(1, maps:get(<<"down">>, Membership)),
|
||||||
?assertEqual(1, maps:get(<<"self">>, Owners)),
|
?assertEqual(1, maps:get(<<"self">>, Owners)),
|
||||||
?assertEqual(1, maps:get(<<"peer">>, Owners)).
|
?assertEqual(1, maps:get(<<"peer">>, Owners)).
|
||||||
|
|
||||||
|
dispatch_counter_test() ->
|
||||||
|
reset_for_tests(),
|
||||||
|
?assertEqual(0, dispatches_total()),
|
||||||
|
record_dispatch(),
|
||||||
|
record_dispatch(),
|
||||||
|
?assertEqual(2, dispatches_total()),
|
||||||
|
?assertEqual(2, maps:get(<<"websocket_dispatches_total">>, snapshot())).
|
||||||
|
|
||||||
|
dispatch_drop_counter_test() ->
|
||||||
|
reset_for_tests(),
|
||||||
|
?assertEqual(0, dispatch_drops_total()),
|
||||||
|
record_dispatch_drop(),
|
||||||
|
record_dispatch_drop(),
|
||||||
|
?assertEqual(2, dispatch_drops_total()),
|
||||||
|
?assertEqual(0, dispatches_total()),
|
||||||
|
?assertEqual(2, maps:get(<<"websocket_dispatch_drops_total">>, snapshot())).
|
||||||
|
|
||||||
|
undersized_counters_test() ->
|
||||||
|
persistent_term:put(?COUNTERS_KEY, counters:new(5, [write_concurrency])),
|
||||||
|
record_dispatch(),
|
||||||
|
?assertEqual(1, dispatches_total()),
|
||||||
|
reset_for_tests().
|
||||||
|
|
||||||
-endif.
|
-endif.
|
||||||
|
|||||||
@@ -138,8 +138,10 @@ dispatch_pre_encoded(Event, EncodedData, Seq, #{compress_ctx := CompressCtx} = S
|
|||||||
case gateway_compress:compress(JsonFrame, Ctx) of
|
case gateway_compress:compress(JsonFrame, Ctx) of
|
||||||
{ok, Compressed, NewCompressCtx} ->
|
{ok, Compressed, NewCompressCtx} ->
|
||||||
Frame = gateway_handler_encode:make_frame(Compressed, text, NewCompressCtx),
|
Frame = gateway_handler_encode:make_frame(Compressed, text, NewCompressCtx),
|
||||||
|
gateway_cluster_metrics:record_dispatch(),
|
||||||
{[Frame], State#{compress_ctx => NewCompressCtx}};
|
{[Frame], State#{compress_ctx => NewCompressCtx}};
|
||||||
{error, _Reason} ->
|
{error, _Reason} ->
|
||||||
|
gateway_cluster_metrics:record_dispatch_drop(),
|
||||||
{ok, State}
|
{ok, State}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
@@ -156,9 +158,11 @@ dispatch_standard(Event, Data, Seq, State) ->
|
|||||||
},
|
},
|
||||||
case gateway_handler_encode:encode_and_compress(Message, State) of
|
case gateway_handler_encode:encode_and_compress(Message, State) of
|
||||||
{ok, Frame, NewState} ->
|
{ok, Frame, NewState} ->
|
||||||
|
gateway_cluster_metrics:record_dispatch(),
|
||||||
trigger_gc_after_large_dispatch(Data),
|
trigger_gc_after_large_dispatch(Data),
|
||||||
{[Frame], NewState};
|
{[Frame], NewState};
|
||||||
{error, _Reason} ->
|
{error, _Reason} ->
|
||||||
|
gateway_cluster_metrics:record_dispatch_drop(),
|
||||||
{ok, State}
|
{ok, State}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|||||||
@@ -158,6 +158,7 @@ finalize_resume(Pid, Seq, MissedEvents, GwTimings0, State) ->
|
|||||||
),
|
),
|
||||||
ResumedData = put_resumed_gateway_timings(session_is_staff(Pid), GwTimings),
|
ResumedData = put_resumed_gateway_timings(session_is_staff(Pid), GwTimings),
|
||||||
SocketPid ! {dispatch, resumed, ResumedData, Seq},
|
SocketPid ! {dispatch, resumed, ResumedData, Seq},
|
||||||
|
gateway_cluster_metrics:record_resume(),
|
||||||
erlang:garbage_collect(self(), [{type, major}]),
|
erlang:garbage_collect(self(), [{type, major}]),
|
||||||
{ok, State#{
|
{ok, State#{
|
||||||
session_pid => Pid,
|
session_pid => Pid,
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ get_local_node_id() ->
|
|||||||
-spec get_local_node_stats() -> map().
|
-spec get_local_node_stats() -> map().
|
||||||
get_local_node_stats() ->
|
get_local_node_stats() ->
|
||||||
SessionCount = get_manager_count(session_manager),
|
SessionCount = get_manager_count(session_manager),
|
||||||
|
ResumeCount = gateway_cluster_metrics:resumes_total(),
|
||||||
GuildCount = get_manager_count(guild_manager),
|
GuildCount = get_manager_count(guild_manager),
|
||||||
PresenceCount = get_manager_count(presence_manager),
|
PresenceCount = get_manager_count(presence_manager),
|
||||||
CallCount = get_manager_count(call_manager),
|
CallCount = get_manager_count(call_manager),
|
||||||
@@ -41,6 +42,9 @@ get_local_node_stats() ->
|
|||||||
<<"node_id">> => node_id(node()),
|
<<"node_id">> => node_id(node()),
|
||||||
<<"status">> => <<"healthy">>,
|
<<"status">> => <<"healthy">>,
|
||||||
<<"sessions">> => SessionCount,
|
<<"sessions">> => SessionCount,
|
||||||
|
<<"session_resumes_total">> => ResumeCount,
|
||||||
|
<<"websocket_dispatches_total">> => gateway_cluster_metrics:dispatches_total(),
|
||||||
|
<<"websocket_dispatch_drops_total">> => gateway_cluster_metrics:dispatch_drops_total(),
|
||||||
<<"guilds">> => GuildCount,
|
<<"guilds">> => GuildCount,
|
||||||
<<"presences">> => PresenceCount,
|
<<"presences">> => PresenceCount,
|
||||||
<<"calls">> => CallCount,
|
<<"calls">> => CallCount,
|
||||||
@@ -180,6 +184,13 @@ aggregate_node_stats(NodeStats) ->
|
|||||||
#{
|
#{
|
||||||
<<"status">> => aggregate_status(SortedNodes),
|
<<"status">> => aggregate_status(SortedNodes),
|
||||||
<<"sessions">> => sum_stat(SortedNodes, <<"sessions">>),
|
<<"sessions">> => sum_stat(SortedNodes, <<"sessions">>),
|
||||||
|
<<"session_resumes_total">> => sum_stat(SortedNodes, <<"session_resumes_total">>),
|
||||||
|
<<"websocket_dispatches_total">> => sum_stat(
|
||||||
|
SortedNodes, <<"websocket_dispatches_total">>
|
||||||
|
),
|
||||||
|
<<"websocket_dispatch_drops_total">> => sum_stat(
|
||||||
|
SortedNodes, <<"websocket_dispatch_drops_total">>
|
||||||
|
),
|
||||||
<<"guilds">> => sum_stat(SortedNodes, <<"guilds">>),
|
<<"guilds">> => sum_stat(SortedNodes, <<"guilds">>),
|
||||||
<<"presences">> => sum_stat(SortedNodes, <<"presences">>),
|
<<"presences">> => sum_stat(SortedNodes, <<"presences">>),
|
||||||
<<"calls">> => sum_stat(SortedNodes, <<"calls">>),
|
<<"calls">> => sum_stat(SortedNodes, <<"calls">>),
|
||||||
@@ -266,6 +277,9 @@ unavailable_node_stats(TargetNode) ->
|
|||||||
<<"node_id">> => node_id(TargetNode),
|
<<"node_id">> => node_id(TargetNode),
|
||||||
<<"status">> => <<"unavailable">>,
|
<<"status">> => <<"unavailable">>,
|
||||||
<<"sessions">> => null,
|
<<"sessions">> => null,
|
||||||
|
<<"session_resumes_total">> => null,
|
||||||
|
<<"websocket_dispatches_total">> => null,
|
||||||
|
<<"websocket_dispatch_drops_total">> => null,
|
||||||
<<"guilds">> => null,
|
<<"guilds">> => null,
|
||||||
<<"presences">> => null,
|
<<"presences">> => null,
|
||||||
<<"calls">> => null,
|
<<"calls">> => null,
|
||||||
@@ -315,6 +329,9 @@ aggregate_node_stats_sums_cluster_totals_test() ->
|
|||||||
<<"node_id">> => <<"[email protected]">>,
|
<<"node_id">> => <<"[email protected]">>,
|
||||||
<<"status">> => <<"healthy">>,
|
<<"status">> => <<"healthy">>,
|
||||||
<<"sessions">> => 10,
|
<<"sessions">> => 10,
|
||||||
|
<<"session_resumes_total">> => 1,
|
||||||
|
<<"websocket_dispatches_total">> => 7,
|
||||||
|
<<"websocket_dispatch_drops_total">> => 7,
|
||||||
<<"guilds">> => 20,
|
<<"guilds">> => 20,
|
||||||
<<"presences">> => 30,
|
<<"presences">> => 30,
|
||||||
<<"calls">> => 40,
|
<<"calls">> => 40,
|
||||||
@@ -327,6 +344,9 @@ aggregate_node_stats_sums_cluster_totals_test() ->
|
|||||||
<<"node_id">> => <<"[email protected]">>,
|
<<"node_id">> => <<"[email protected]">>,
|
||||||
<<"status">> => <<"healthy">>,
|
<<"status">> => <<"healthy">>,
|
||||||
<<"sessions">> => 3,
|
<<"sessions">> => 3,
|
||||||
|
<<"session_resumes_total">> => 1,
|
||||||
|
<<"websocket_dispatches_total">> => 7,
|
||||||
|
<<"websocket_dispatch_drops_total">> => 7,
|
||||||
<<"guilds">> => 4,
|
<<"guilds">> => 4,
|
||||||
<<"presences">> => 5,
|
<<"presences">> => 5,
|
||||||
<<"calls">> => 6,
|
<<"calls">> => 6,
|
||||||
@@ -336,7 +356,10 @@ aggregate_node_stats_sums_cluster_totals_test() ->
|
|||||||
<<"uptime_seconds">> => 90
|
<<"uptime_seconds">> => 90
|
||||||
},
|
},
|
||||||
A = aggregate_node_stats([N1, N2]),
|
A = aggregate_node_stats([N1, N2]),
|
||||||
|
?assertEqual(14, maps:get(<<"websocket_dispatches_total">>, A)),
|
||||||
|
?assertEqual(14, maps:get(<<"websocket_dispatch_drops_total">>, A)),
|
||||||
?assertEqual(13, maps:get(<<"sessions">>, A)),
|
?assertEqual(13, maps:get(<<"sessions">>, A)),
|
||||||
|
?assertEqual(2, maps:get(<<"session_resumes_total">>, A)),
|
||||||
?assertEqual(24, maps:get(<<"guilds">>, A)),
|
?assertEqual(24, maps:get(<<"guilds">>, A)),
|
||||||
?assertEqual(35, maps:get(<<"presences">>, A)),
|
?assertEqual(35, maps:get(<<"presences">>, A)),
|
||||||
?assertEqual(46, maps:get(<<"calls">>, A)),
|
?assertEqual(46, maps:get(<<"calls">>, A)),
|
||||||
|
|||||||
@@ -51,6 +51,9 @@ render_metrics() ->
|
|||||||
-spec render_gateway_gauges() -> iolist().
|
-spec render_gateway_gauges() -> iolist().
|
||||||
render_gateway_gauges() ->
|
render_gateway_gauges() ->
|
||||||
Sessions = safe_apply_int(fun session_manager:session_count/0),
|
Sessions = safe_apply_int(fun session_manager:session_count/0),
|
||||||
|
DispatchDrops = safe_apply_int(fun gateway_cluster_metrics:dispatch_drops_total/0),
|
||||||
|
Dispatches = safe_apply_int(fun gateway_cluster_metrics:dispatches_total/0),
|
||||||
|
Resumes = safe_apply_int(fun gateway_cluster_metrics:resumes_total/0),
|
||||||
Guilds = safe_apply_int(fun guild_manager:local_guild_count/0),
|
Guilds = safe_apply_int(fun guild_manager:local_guild_count/0),
|
||||||
VoiceCounts = safe_apply_map(fun voice_state_counts_cache:get_local_counts/0),
|
VoiceCounts = safe_apply_map(fun voice_state_counts_cache:get_local_counts/0),
|
||||||
CallIds = safe_apply_list(fun call_manager:local_call_ids/0),
|
CallIds = safe_apply_list(fun call_manager:local_call_ids/0),
|
||||||
@@ -64,12 +67,30 @@ render_gateway_gauges() ->
|
|||||||
<<"Connected WebSocket sessions">>,
|
<<"Connected WebSocket sessions">>,
|
||||||
integer_to_binary(Sessions)
|
integer_to_binary(Sessions)
|
||||||
),
|
),
|
||||||
|
format_metric(
|
||||||
|
<<"fluxer_gateway_session_resumes_total">>,
|
||||||
|
<<"counter">>,
|
||||||
|
<<"Successful WebSocket session resumes">>,
|
||||||
|
integer_to_binary(Resumes)
|
||||||
|
),
|
||||||
format_metric(
|
format_metric(
|
||||||
<<"fluxer_gateway_guilds_total">>,
|
<<"fluxer_gateway_guilds_total">>,
|
||||||
<<"gauge">>,
|
<<"gauge">>,
|
||||||
<<"Locally loaded guilds">>,
|
<<"Locally loaded guilds">>,
|
||||||
integer_to_binary(Guilds)
|
integer_to_binary(Guilds)
|
||||||
),
|
),
|
||||||
|
format_metric(
|
||||||
|
<<"fluxer_gateway_websocket_dispatches_total">>,
|
||||||
|
<<"counter">>,
|
||||||
|
<<"WebSocket dispatch frames handed to the transport, including replays">>,
|
||||||
|
integer_to_binary(Dispatches)
|
||||||
|
),
|
||||||
|
format_metric(
|
||||||
|
<<"fluxer_gateway_websocket_dispatch_drops_total">>,
|
||||||
|
<<"counter">>,
|
||||||
|
<<"WebSocket dispatches dropped due to encoding or compression errors">>,
|
||||||
|
integer_to_binary(DispatchDrops)
|
||||||
|
),
|
||||||
render_voice_metrics(VoiceCounts),
|
render_voice_metrics(VoiceCounts),
|
||||||
format_metric(
|
format_metric(
|
||||||
<<"fluxer_gateway_calls_total">>,
|
<<"fluxer_gateway_calls_total">>,
|
||||||
|
|||||||
@@ -1133,6 +1133,9 @@ export type ReloadAllGuildsResponse = z.infer<typeof ReloadAllGuildsResponse>;
|
|||||||
export const NodeStatsResponse = z.object({
|
export const NodeStatsResponse = z.object({
|
||||||
status: createStringType(1, 256),
|
status: createStringType(1, 256),
|
||||||
sessions: Int32Type,
|
sessions: Int32Type,
|
||||||
|
session_resumes_total: z.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
|
||||||
|
websocket_dispatches_total: z.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
|
||||||
|
websocket_dispatch_drops_total: z.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
|
||||||
guilds: Int32Type,
|
guilds: Int32Type,
|
||||||
presences: Int32Type,
|
presences: Int32Type,
|
||||||
calls: Int32Type,
|
calls: Int32Type,
|
||||||
@@ -1151,6 +1154,9 @@ export const NodeStatsResponse = z.object({
|
|||||||
node_id: createStringType(1, 256),
|
node_id: createStringType(1, 256),
|
||||||
status: createStringType(1, 256),
|
status: createStringType(1, 256),
|
||||||
sessions: Int32Type,
|
sessions: Int32Type,
|
||||||
|
session_resumes_total: z.number().int().min(0).max(Number.MAX_SAFE_INTEGER).nullable(),
|
||||||
|
websocket_dispatches_total: z.number().int().min(0).max(Number.MAX_SAFE_INTEGER).nullable(),
|
||||||
|
websocket_dispatch_drops_total: z.number().int().min(0).max(Number.MAX_SAFE_INTEGER).nullable(),
|
||||||
guilds: Int32Type,
|
guilds: Int32Type,
|
||||||
presences: Int32Type,
|
presences: Int32Type,
|
||||||
calls: Int32Type,
|
calls: Int32Type,
|
||||||
|
|||||||
Reference in New Issue
Block a user