diff --git a/ydb/core/tablet/node_whiteboard.cpp b/ydb/core/tablet/node_whiteboard.cpp index c76877ae4af5..0a0b8db4a15a 100644 --- a/ydb/core/tablet/node_whiteboard.cpp +++ b/ydb/core/tablet/node_whiteboard.cpp @@ -721,6 +721,9 @@ class TNodeWhiteboardService : public TActorBootstrapped auto& endpoint = *SystemStateInfo.AddEndpoints(); endpoint.SetName(ev->Get()->Name); endpoint.SetAddress(ev->Get()->Address); + std::sort(SystemStateInfo.MutableEndpoints()->begin(), SystemStateInfo.MutableEndpoints()->end(), [](const auto& a, const auto& b) { + return a.GetName() < b.GetName(); + }); SystemStateInfo.SetChangeTime(ctx.Now().MilliSeconds()); } diff --git a/ydb/core/viewer/tests/canondata/result.json b/ydb/core/viewer/tests/canondata/result.json index af422fd482df..f0859e110725 100644 --- a/ydb/core/viewer/tests/canondata/result.json +++ b/ydb/core/viewer/tests/canondata/result.json @@ -2972,6 +2972,242 @@ "TotalNodes": "1" } }, + "test.test_viewer_nodes_issue_14992": { + "response_group": { + "FieldsAvailable": "0000000110111110111111100000111", + "FieldsRequired": "0000000001000000010000000000101", + "FoundNodes": "3", + "MaximumDisksPerNode": "1", + "Nodes": [ + { + "CpuUsage": "not-zero-number", + "DiskSpaceUsage": "not-zero-number", + "NodeId": 1, + "SystemState": { + "ChangeTime": "not-zero-number-text", + "CoresTotal": "not-zero-number", + "CoresUsed": "not-zero-number", + "Endpoints": [ + { + "Address": "text", + "Name": "grpc" + }, + { + "Address": "text", + "Name": "http-mon" + }, + { + "Address": "text", + "Name": "ic" + } + ], + "Host": "text", + "LoadAverage": "not-empty-array", + "Location": { + "DataCenter": "1", + "Rack": "1", + "Unit": "1" + }, + "MaxDiskUsage": "not-zero-number", + "MemoryLimit": "not-zero-number-text", + "NodeId": 1, + "NumberOfCpus": "not-zero-number", + "PoolStats": [ + { + "Limit": "not-zero-number", + "Name": "System", + "Threads": 2, + "Usage": "number" + }, + { + "Limit": "not-zero-number", + "Name": "User", + "Threads": 3, + "Usage": "number" + }, + { + "Limit": "not-zero-number", + "Name": "Batch", + "Threads": 2, + "Usage": "number" + }, + { + "Limit": "not-zero-number", + "Name": "IO", + "Threads": 1, + "Usage": "number" + }, + { + "Limit": "not-zero-number", + "Name": "IC", + "Threads": 1, + "Usage": "number" + } + ], + "Roles": "not-empty-array", + "StartTime": "not-zero-number-text", + "SystemState": "Green", + "TotalSessions": 0 + }, + "UptimeSeconds": "number" + }, + { + "CpuUsage": "not-zero-number", + "Database": "/Root/shared_db", + "NodeId": 50001, + "SystemState": { + "ChangeTime": "not-zero-number-text", + "CoresTotal": "not-zero-number", + "CoresUsed": "not-zero-number", + "Endpoints": [ + { + "Address": "text", + "Name": "grpc" + }, + { + "Address": "text", + "Name": "http-mon" + }, + { + "Address": "text", + "Name": "ic" + } + ], + "Host": "text", + "LoadAverage": "not-empty-array", + "Location": {}, + "MemoryLimit": "not-zero-number-text", + "NodeId": 50001, + "NumberOfCpus": "not-zero-number", + "PoolStats": [ + { + "Limit": "not-zero-number", + "Name": "System", + "Threads": 2, + "Usage": "number" + }, + { + "Limit": "not-zero-number", + "Name": "User", + "Threads": 3, + "Usage": "number" + }, + { + "Limit": "not-zero-number", + "Name": "Batch", + "Threads": 2, + "Usage": "number" + }, + { + "Limit": "not-zero-number", + "Name": "IO", + "Threads": 1, + "Usage": "number" + }, + { + "Limit": "not-zero-number", + "Name": "IC", + "Threads": 1, + "Usage": "number" + } + ], + "Roles": "not-empty-array", + "StartTime": "not-zero-number-text", + "SystemState": "Green", + "Tenants": [ + "/Root/shared_db" + ], + "TotalSessions": 0 + }, + "UptimeSeconds": "number" + }, + { + "CpuUsage": "not-zero-number", + "Database": "/Root/dedicated_db", + "NodeId": 50000, + "SystemState": { + "ChangeTime": "not-zero-number-text", + "CoresTotal": "not-zero-number", + "CoresUsed": "not-zero-number", + "Endpoints": [ + { + "Address": "text", + "Name": "grpc" + }, + { + "Address": "text", + "Name": "http-mon" + }, + { + "Address": "text", + "Name": "ic" + } + ], + "Host": "text", + "LoadAverage": "not-empty-array", + "Location": {}, + "MemoryLimit": "not-zero-number-text", + "NodeId": 50000, + "NumberOfCpus": "not-zero-number", + "PoolStats": [ + { + "Limit": "not-zero-number", + "Name": "System", + "Threads": 2, + "Usage": "number" + }, + { + "Limit": "not-zero-number", + "Name": "User", + "Threads": 3, + "Usage": "number" + }, + { + "Limit": "not-zero-number", + "Name": "Batch", + "Threads": 2, + "Usage": "number" + }, + { + "Limit": "not-zero-number", + "Name": "IO", + "Threads": 1, + "Usage": "number" + }, + { + "Limit": "not-zero-number", + "Name": "IC", + "Threads": 1, + "Usage": "number" + } + ], + "Roles": "not-empty-array", + "StartTime": "not-zero-number-text", + "SystemState": "Green", + "Tenants": [ + "/Root/dedicated_db" + ], + "TotalSessions": 0 + }, + "UptimeSeconds": "number" + } + ], + "TotalNodes": "3" + }, + "response_group_by": { + "FieldsAvailable": "0000000110111110111111100000111", + "FieldsRequired": "0000000001000000010000000000101", + "FoundNodes": "3", + "MaximumDisksPerNode": "1", + "NodeGroups": [ + { + "GroupName": "up <10m", + "NodeCount": "3" + } + ], + "TotalNodes": "3" + } + }, "test.test_viewer_pdiskinfo": { "PDiskStateInfo": [ { diff --git a/ydb/core/viewer/tests/test.py b/ydb/core/viewer/tests/test.py index 8107183f21eb..cb30c55b7330 100644 --- a/ydb/core/viewer/tests/test.py +++ b/ydb/core/viewer/tests/test.py @@ -431,9 +431,6 @@ def test_viewer_nodes(): result = get_viewer_db_normalized("/viewer/nodes", { 'fields_required': 'all' }) - for name in databases: - for node in result[name]['Nodes']: - node['SystemState']['Endpoints'].sort(key=lambda x: x['Name']) return result @@ -445,8 +442,6 @@ def test_storage_groups(): def test_viewer_sysinfo(): result = get_viewer_normalized("/viewer/sysinfo") - for node in result['SystemStateInfo']: - node['Endpoints'].sort(key=lambda x: x['Name']) return result @@ -560,3 +555,18 @@ def test_pqrb_tablet(): 'PathId', 'SchemeShard' ]) + + +def test_viewer_nodes_issue_14992(): + response_group_by = get_viewer_normalized("/viewer/nodes", { + 'group': 'Uptime' + }) + response_group = get_viewer_normalized("/viewer/nodes", { + 'filter_group_by': 'Uptime', + 'filter_group' : response_group_by['NodeGroups'][0]['GroupName'], + }) + result = { + 'response_group_by': response_group_by, + 'response_group': response_group, + } + return result diff --git a/ydb/core/viewer/viewer_nodes.h b/ydb/core/viewer/viewer_nodes.h index dcdcf547592d..afb35a9ec61e 100644 --- a/ydb/core/viewer/viewer_nodes.h +++ b/ydb/core/viewer/viewer_nodes.h @@ -206,7 +206,7 @@ class TJsonNodes : public TViewerPipeClient { bool HasDisks = false; bool GotDatabaseFromDatabaseBoardInfo = false; bool GotDatabaseFromResourceBoardInfo = false; - int UptimeSeconds = 0; + std::optional UptimeSeconds = 0; ui32 Connections = 0; ui64 SendThroughput = 0; ui64 ReceiveThroughput = 0; @@ -460,11 +460,19 @@ class TJsonNodes : public TViewerPipeClient { return TInstant::MilliSeconds(SystemState.GetDisconnectTime()); } - int GetUptimeSeconds(TInstant now) const { + std::optional GetUptimeSeconds(TInstant now) const { if (Disconnected) { - return static_cast(GetDisconnectTime().Seconds()) - static_cast(now.Seconds()); // negative for disconnected nodes + if (SystemState.HasDisconnectTime()) { + return static_cast(GetDisconnectTime().Seconds()) - static_cast(now.Seconds()); // negative for disconnected nodes + } else { + return std::nullopt; + } } else { - return static_cast(now.Seconds()) - static_cast(GetStartTime().Seconds()); + if (SystemState.HasStartTime()) { + return static_cast(now.Seconds()) - static_cast(GetStartTime().Seconds()); + } else { + return std::nullopt; + } } } @@ -538,39 +546,42 @@ class TJsonNodes : public TViewerPipeClient { } TString GetUptimeForGroup() const { - if (!Disconnected && UptimeSeconds >= 0) { - if (UptimeSeconds < 60 * 10) { - return "up <10m"; - } - if (UptimeSeconds < 60 * 60) { - return "up <1h"; - } - if (UptimeSeconds < 60 * 60 * 24) { - return "up <24h"; - } - if (UptimeSeconds < 60 * 60 * 24 * 7) { - return "up 24h+"; - } - return "up 1 week+"; - } else { - if (SystemState.HasDisconnectTime()) { - if (UptimeSeconds > -60 * 10) { + if (UptimeSeconds) { + if (*UptimeSeconds >= 0) { + if (*UptimeSeconds < 60 * 10) { + return "up <10m"; + } + if (*UptimeSeconds < 60 * 60) { + return "up <1h"; + } + if (*UptimeSeconds < 60 * 60 * 24) { + return "up <24h"; + } + if (*UptimeSeconds < 60 * 60 * 24 * 7) { + return "up 24h+"; + } + return "up 1 week+"; + } else { + if (*UptimeSeconds > -60 * 10) { return "down <10m"; } - if (UptimeSeconds > -60 * 60) { + if (*UptimeSeconds > -60 * 60) { return "down <1h"; } - if (UptimeSeconds > -60 * 60 * 24) { + if (*UptimeSeconds > -60 * 60 * 24) { return "down <24h"; } - if (UptimeSeconds > -60 * 60 * 24 * 7) { + if (*UptimeSeconds > -60 * 60 * 24 * 7) { return "down 24h+"; } return "down 1 week+"; - } else { - return "disconnected"; } } + if (Disconnected) { + return "disconnected"; + } else { + return "unknown"; + } } TString GetVersionForGroup() const { @@ -682,7 +693,7 @@ class TJsonNodes : public TViewerPipeClient { case ENodeFields::Missing: return MissingDisks; case ENodeFields::Uptime: - return UptimeSeconds; + return UptimeSeconds.value_or(0); case ENodeFields::SystemState: return static_cast(GetOverall()); case ENodeFields::ConnectStatus: @@ -937,6 +948,9 @@ class TJsonNodes : public TViewerPipeClient { FilterGroup = params.Get("filter_group"); FilterGroupBy = ParseENodeFields(params.Get("filter_group_by")); FieldsRequired.set(+FilterGroupBy); + if (FilterGroupBy == ENodeFields::Uptime) { + FieldsRequired.set(+ENodeFields::DisconnectTime); + } } OffloadMerge = FromStringWithDefault(params.Get("offload_merge"), OffloadMerge); @@ -1034,6 +1048,9 @@ class TJsonNodes : public TViewerPipeClient { NeedGroup = true; GroupBy = ParseENodeFields(group); FieldsRequired.set(+GroupBy); + if (GroupBy == ENodeFields::Uptime) { + FieldsRequired.set(+ENodeFields::DisconnectTime); + } NeedSort = false; NeedLimit = false; } @@ -1094,7 +1111,7 @@ class TJsonNodes : public TViewerPipeClient { TIntrusivePtr domains = AppData()->DomainsInfo; auto* domain = domains->GetDomain(); DomainPath = "/" + domain->Name; - if (ProblemNodesOnly || GroupBy == ENodeFields::Uptime) { + if (ProblemNodesOnly || FieldsRequired.test(+ENodeFields::Uptime) || FieldsRequired.test(+ENodeFields::DisconnectTime)) { FieldsRequired.set(+ENodeFields::SystemState); TTabletId rootHiveId = domains->GetHive(); HivesToAsk.push_back(rootHiveId); @@ -1294,7 +1311,7 @@ class TJsonNodes : public TViewerPipeClient { if (UptimeSeconds > 0 && FieldsAvailable.test(+ENodeFields::SystemState)) { TNodeView nodeView; for (TNode* node : NodeView) { - if (node->UptimeSeconds < UptimeSeconds) { + if (node->UptimeSeconds.value_or(0) < UptimeSeconds) { nodeView.push_back(node); } } @@ -1446,7 +1463,7 @@ class TJsonNodes : public TViewerPipeClient { NeedSort = false; break; case ENodeFields::Uptime: - SortCollection(NodeView, [](const TNode* node) { return node->UptimeSeconds; }, ReverseSort); + SortCollection(NodeView, [](const TNode* node) { return node->UptimeSeconds.value_or(0); }, ReverseSort); NeedSort = false; break; case ENodeFields::Memory: @@ -3126,7 +3143,7 @@ class TJsonNodes : public TViewerPipeClient { jsonNode.SetDatabase(node->Database); } if (node->UptimeSeconds) { - jsonNode.SetUptimeSeconds(node->UptimeSeconds); + jsonNode.SetUptimeSeconds(*(node->UptimeSeconds)); } if (node->Disconnected) { jsonNode.SetDisconnected(node->Disconnected);