Repository: ydb-platform/nbs
Pull request #3215: issue-3: add info about lagging devices on volume mon page
Author: login: vladstepanyuk
Labels: name: large-tests
description: Launch large tests for PR
name: blockstore
description: Add this label to run only cloud/blockstore build and tests on PR
Created: 2025-03-19T10:37:19Z | Merged: 2025-03-20T09:17:09Z

#3


Diff:
diff --git a/cloud/blockstore/libs/storage/volume/volume_actor.h b/cloud/blockstore/libs/storage/volume/volume_actor.h
index 162ef592b8b..dcd7eac2cc2 100644
--- a/cloud/blockstore/libs/storage/volume/volume_actor.h
+++ b/cloud/blockstore/libs/storage/volume/volume_actor.h
@@ -429,6 +429,7 @@ class TVolumeActor final
     void RenderStatus(IOutputStream& out) const;
     void RenderMigrationStatus(IOutputStream& out) const;
     void RenderResyncStatus(IOutputStream& out) const;
+    void RenderLaggingStatus(IOutputStream& out) const;
     void RenderMountSeqNumber(IOutputStream& out) const;
     void RenderHistory(
         const TVolumeMountHistorySlice& history,
diff --git a/cloud/blockstore/libs/storage/volume/volume_actor_monitoring.cpp b/cloud/blockstore/libs/storage/volume/volume_actor_monitoring.cpp
index 8c15212165e..abb0b249314 100644
--- a/cloud/blockstore/libs/storage/volume/volume_actor_monitoring.cpp
+++ b/cloud/blockstore/libs/storage/volume/volume_actor_monitoring.cpp
@@ -1058,6 +1058,16 @@ void TVolumeActor::RenderHtmlInfo(IOutputStream& out, TInstant now) const
             }
         }
 
+        if (LaggingDevicesAreAllowed()) {
+            DIV_CLASS("row")
+            {
+                DIV_CLASS("col-md-6")
+                {
+                    RenderLaggingStatus(out);
+                }
+            }
+        }
+
         DIV_CLASS("row") {
             DIV_CLASS("col-md-6") {
                 RenderCommonButtons(out);
@@ -1626,6 +1636,38 @@ void TVolumeActor::RenderResyncStatus(IOutputStream& out) const
     }
 }
 
+void TVolumeActor::RenderLaggingStatus(IOutputStream& out) const
+{
+    HTML(out)
+    {
+        TAG(TH3)
+        {
+            TString statusText = "ok";
+            TString cssClass = "label-success";
+
+            if (State->HasLaggingAgents()) {
+                statusText = "has lagging devices";
+                cssClass = "label-info";
+            }
+
+            if (!State->GetNonreplicatedPartitionConfig()
+                     ->GetOutdatedDeviceIds()
+                     .empty())
+            {
+                statusText = "has outdated devices";
+                cssClass = "label-danger";
+            }
+
+            out << "LaggingStatus:";
+
+            SPAN_CLASS_STYLE("label " + cssClass, "margin-left:10px")
+            {
+                out << statusText;
+            }
+        }
+    }
+}
+
 void TVolumeActor::RenderCommonButtons(IOutputStream& out) const
 {
     if (!State) {
@@ -1831,6 +1873,10 @@ void TVolumeActor::HandleHttpInfo_RenderNonreplPartitionInfo(
                         return nullptr;
                     };
 
+                bool renderLaggingState = IsReliableDiskRegistryMediaKind(
+                    State->GetConfig().GetStorageMediaKind());
+                auto laggingDevices = State->GetLaggingDevices();
+
                 auto outputDevices = [&] (const TDevices& devices) {
                     TABLED() {
                         TABLE_CLASS("table table-bordered") {
@@ -1846,6 +1892,12 @@ void TVolumeActor::HandleHttpInfo_RenderNonreplPartitionInfo(
                                     TABLEH() { out << "BlockSize"; }
                                     TABLEH() { out << "Blocks"; }
                                     TABLEH() { out << "BlockRange"; }
+                                    if (renderLaggingState) {
+                                        TABLEH()
+                                        {
+                                            out << "LaggingState";
+                                        }
+                                    }
                                 }
                             }
 
@@ -1883,6 +1935,33 @@ void TVolumeActor::HandleHttpInfo_RenderNonreplPartitionInfo(
                                             d.GetBlocksCount());
                                     out << DescribeRange(currentRange);
                                 }
+                                if (renderLaggingState) {
+                                    TABLED()
+                                    {
+                                        const auto* stateMsg = "ok";
+                                        const auto* color = "green";
+
+                                        if (laggingDevices.contains(
+                                                d.GetDeviceUUID()))
+                                        {
+                                            stateMsg = "lagging";
+                                            color = "blue";
+                                        }
+
+                                        if (State
+                                                ->GetNonreplicatedPartitionConfig()
+                                                ->GetOutdatedDeviceIds()
+                                                .contains(d.GetDeviceUUID()))
+                                        {
+                                            stateMsg = "outdated";
+                                            color = "red";
+                                        }
+
+                                        out << "<font color=" << color << ">";
+                                        out << stateMsg;
+                                        out << "</font>";
+                                    }
+                                }
                             };
 
                             for (int i = 0; i < devices.size(); ++i) {
diff --git a/cloud/blockstore/libs/storage/volume/volume_state.cpp b/cloud/blockstore/libs/storage/volume/volume_state.cpp
index 0f1a98dd0fd..b4a138a3bd5 100644
--- a/cloud/blockstore/libs/storage/volume/volume_state.cpp
+++ b/cloud/blockstore/libs/storage/volume/volume_state.cpp
@@ -223,6 +223,11 @@ std::optional<NProto::TLaggingAgent> TVolumeState::RemoveLaggingAgent(
     return std::nullopt;
 }
 
+bool TVolumeState::HasLaggingAgents() const
+{
+    return Meta.GetLaggingAgentsInfo().AgentsSize() != 0;
+}
+
 bool TVolumeState::HasLaggingInReplica(ui32 replicaIndex) const
 {
     for (const auto& agent: Meta.GetLaggingAgentsInfo().GetAgents()) {
diff --git a/cloud/blockstore/libs/storage/volume/volume_state.h b/cloud/blockstore/libs/storage/volume/volume_state.h
index 98bce6e3773..85629266041 100644
--- a/cloud/blockstore/libs/storage/volume/volume_state.h
+++ b/cloud/blockstore/libs/storage/volume/volume_state.h
@@ -8,6 +8,7 @@
 #include <cloud/blockstore/libs/diagnostics/config.h>
 #include <cloud/blockstore/libs/kikimr/components.h>
 #include <cloud/blockstore/libs/kikimr/public.h>
+#include <cloud/blockstore/libs/storage/core/config.h>
 #include <cloud/blockstore/libs/storage/core/disk_counters.h>
 #include <cloud/blockstore/libs/storage/core/metrics.h>
 #include <cloud/blockstore/libs/storage/core/public.h>
@@ -315,6 +316,7 @@ class TVolumeState
     void AddLaggingAgent(NProto::TLaggingAgent agent);
     std::optional<NProto::TLaggingAgent> RemoveLaggingAgent(
         const TString& agentId);
+    [[nodiscard]] bool HasLaggingAgents() const;
     [[nodiscard]] bool HasLaggingInReplica(ui32 replicaIndex) const;
     [[nodiscard]] THashSet<TString> GetLaggingDevices() const;
 


Reviews:
user: login: komarevtsev-d
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-19T10:47:04Z
user: login: komarevtsev-d
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-19T10:55:31Z
user: login: vladstepanyuk
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-19T11:08:34Z
user: login: komarevtsev-d
state: DISMISSED
author_association: COLLABORATOR
submitted_at: 2025-03-19T11:39:46Z
user: login: komarevtsev-d
state: DISMISSED
author_association: COLLABORATOR
submitted_at: 2025-03-19T12:10:31Z
user: login: SvartMetal
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-19T12:59:43Z
user: login: SvartMetal
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-19T13:02:29Z
user: login: SvartMetal
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-19T13:03:31Z
user: login: komarevtsev-d
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-19T13:04:51Z
user: login: SvartMetal
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-19T13:06:25Z
user: login: SvartMetal
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-20T09:15:06Z
user: login: komarevtsev-d
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-20T09:16:53Z

Review comments:
diff_hunk: @@ -1058,9 +1058,13 @@ void TVolumeActor::RenderHtmlInfo(IOutputStream& out, TInstant now) const
             }
         }
 
-        DIV_CLASS("row") {
-            DIV_CLASS("col-md-6") {
-                RenderCommonButtons(out);
path: cloud/blockstore/libs/storage/volume/volume_actor_monitoring.cpp
user: login: komarevtsev-d
body: А это почему убрал?
created_at: 2025-03-19T10:43:03Z
author_association: COLLABORATOR
diff_hunk: @@ -315,9 +316,18 @@ class TVolumeState
     void AddLaggingAgent(NProto::TLaggingAgent agent);
     std::optional<NProto::TLaggingAgent> RemoveLaggingAgent(
         const TString& agentId);
+    [[nodiscard]] bool HasLagging() const;
     [[nodiscard]] bool HasLaggingInReplica(ui32 replicaIndex) const;
     [[nodiscard]] THashSet<TString> GetLaggingDevices() const;
-
+    [[nodiscard]] bool LaggingDevicesAllowed() const
+    {
+        auto mediaKind = GetConfig().GetStorageMediaKind();
+        return IsDiskRegistryMediaKind() &&
+               (mediaKind == NProto::STORAGE_MEDIA_SSD_MIRROR2 &&
+                    StorageConfig->GetLaggingDevicesForMirror2DisksEnabled() ||
+                mediaKind == NProto::STORAGE_MEDIA_SSD_MIRROR3 &&
+                    StorageConfig->GetLaggingDevicesForMirror3DisksEnabled());
path: cloud/blockstore/libs/storage/volume/volume_state.h
user: login: komarevtsev-d
body: Уже же есть TVolumeActor::LaggingDevicesAreAllowed ?
created_at: 2025-03-19T10:46:18Z
author_association: COLLABORATOR
diff_hunk: @@ -1883,6 +1932,33 @@ void TVolumeActor::HandleHttpInfo_RenderNonreplPartitionInfo(
                                             d.GetBlocksCount());
                                     out << DescribeRange(currentRange);
                                 }
+                                if (State->LaggingDevicesAllowed()) {
path: cloud/blockstore/libs/storage/volume/volume_actor_monitoring.cpp
user: login: komarevtsev-d
body: Не скопмилируется?
created_at: 2025-03-19T10:55:31Z
author_association: COLLABORATOR
diff_hunk: @@ -1883,6 +1932,33 @@ void TVolumeActor::HandleHttpInfo_RenderNonreplPartitionInfo(
                                             d.GetBlocksCount());
                                     out << DescribeRange(currentRange);
                                 }
+                                if (State->LaggingDevicesAllowed()) {
path: cloud/blockstore/libs/storage/volume/volume_actor_monitoring.cpp
user: login: vladstepanyuk
body: aga

created_at: 2025-03-19T11:08:34Z
author_association: COLLABORATOR
diff_hunk: @@ -1058,6 +1058,17 @@ void TVolumeActor::RenderHtmlInfo(IOutputStream& out, TInstant now) const
             }
         }
 
+        if (LaggingDevicesAreAllowed()) {
+            DIV_CLASS("row")
+            {
+                DIV_CLASS("col-md-6")
+                {
+                    RenderLaggingStatus(out);
+                }
+            }
+        }
+
path: cloud/blockstore/libs/storage/volume/volume_actor_monitoring.cpp
user: login: SvartMetal
body: лишний отступ
created_at: 2025-03-19T12:59:43Z
author_association: COLLABORATOR
diff_hunk: @@ -1846,6 +1889,12 @@ void TVolumeActor::HandleHttpInfo_RenderNonreplPartitionInfo(
                                     TABLEH() { out << "BlockSize"; }
                                     TABLEH() { out << "Blocks"; }
                                     TABLEH() { out << "BlockRange"; }
+                                    if (LaggingDevicesAreAllowed()) {
path: cloud/blockstore/libs/storage/volume/volume_actor_monitoring.cpp
user: login: SvartMetal
body: а этот if обязателен?

разве не будет LaggingDevices пустой при отключенной фиче?

кажется нет ничего страшного нарисовать пустой LaggingState, зато код проще
created_at: 2025-03-19T13:02:29Z
author_association: COLLABORATOR
diff_hunk: @@ -223,6 +223,11 @@ std::optional<NProto::TLaggingAgent> TVolumeState::RemoveLaggingAgent(
     return std::nullopt;
 }
 
+bool TVolumeState::HasLagging() const
path: cloud/blockstore/libs/storage/volume/volume_state.cpp
user: login: SvartMetal
body: HasLaggingAgents
created_at: 2025-03-19T13:03:31Z
author_association: COLLABORATOR
diff_hunk: @@ -1846,6 +1889,12 @@ void TVolumeActor::HandleHttpInfo_RenderNonreplPartitionInfo(
                                     TABLEH() { out << "BlockSize"; }
                                     TABLEH() { out << "Blocks"; }
                                     TABLEH() { out << "BlockRange"; }
+                                    if (LaggingDevicesAreAllowed()) {
path: cloud/blockstore/libs/storage/volume/volume_actor_monitoring.cpp
user: login: komarevtsev-d
body: Я бы хотел оставить либо этот if, либо условие что мы имеем дело с mirror диском. 
По аналогии что для ssd дисков, например, мы не рисуем статус миграции, а для NRD статус resync'a
created_at: 2025-03-19T13:04:51Z
author_association: COLLABORATOR
diff_hunk: @@ -1846,6 +1889,12 @@ void TVolumeActor::HandleHttpInfo_RenderNonreplPartitionInfo(
                                     TABLEH() { out << "BlockSize"; }
                                     TABLEH() { out << "Blocks"; }
                                     TABLEH() { out << "BlockRange"; }
+                                    if (LaggingDevicesAreAllowed()) {
path: cloud/blockstore/libs/storage/volume/volume_actor_monitoring.cpp
user: login: SvartMetal
body: if про mirror диск выглядит логично
created_at: 2025-03-19T13:06:25Z
author_association: COLLABORATOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-03-19T10:37:28Z
body: Hi! Thank you for contributing!
The tests on this PR will run after a maintainer adds an `ok-to-test` label to this PR manually. Thank you for your patience!
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-03-19T14:01:36Z
body: <!-- status pr=3215, run=10076, build_preset=linux-x86_64-relwithdebinfo, dry_run=False -->
> [!NOTE]
> This is an automated comment that will be appended during run.

:green_circle: **linux-x86_64-relwithdebinfo**: all tests PASSED for commit 5a5a47230f2a1777e360b0279784a76200db52f3.
| TESTS | PASSED | ERRORS | FAILED | SKIPPED | MUTED<sup>[?](https://github.com/ydb-platform/nbs/tree/main/.github/config "All mute rules are defined here")</sup> |
| ---: | ---: | ---: | ---: | ---: | ---: |
| [3889](https://github-actions-s3.storage.eu-north1.nebius.cloud/ydb-platform/nbs/PR-check/13946112363/1/nebius-x86-64/summary/ya-test.html) | [3889](https://github-actions-s3.storage.eu-north1.nebius.cloud/ydb-platform/nbs/PR-check/13946112363/1/nebius-x86-64/summary/ya-test.html#PASS) | 0 | 0 | 0 | 0 |
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-03-20T06:07:27Z
body: <!-- status pr=3215, run=10095, build_preset=linux-x86_64-relwithdebinfo, dry_run=False -->
> [!NOTE]
> This is an automated comment that will be appended during run.

:green_circle: **linux-x86_64-relwithdebinfo**: all tests PASSED for commit 763f0c9eb30e6e1d753f1f39306da46eede1db80.
| TESTS | PASSED | ERRORS | FAILED | SKIPPED | MUTED<sup>[?](https://github.com/ydb-platform/nbs/tree/main/.github/config "All mute rules are defined here")</sup> |
| ---: | ---: | ---: | ---: | ---: | ---: |
| [3889](https://github-actions-s3.storage.eu-north1.nebius.cloud/ydb-platform/nbs/PR-check/13962107289/1/nebius-x86-64/summary/ya-test.html) | [3889](https://github-actions-s3.storage.eu-north1.nebius.cloud/ydb-platform/nbs/PR-check/13962107289/1/nebius-x86-64/summary/ya-test.html#PASS) | 0 | 0 | 0 | 0 |
author_association: CONTRIBUTOR