From 4d5d6edcbe433853bf41d16a2d1a61c2d97223ce Mon Sep 17 00:00:00 2001
From: Dmitrii Stepanov <d.stepanov@yadro.com>
Date: Wed, 5 Mar 2025 15:44:15 +0300
Subject: [PATCH] [#1653] treeSvc: Add operations by IO tag metric

Signed-off-by: Dmitrii Stepanov <d.stepanov@yadro.com>
---
 internal/metrics/treeservice.go | 15 +++++++++++++++
 pkg/services/tree/metrics.go    |  2 ++
 pkg/services/tree/service.go    |  9 +++++++++
 3 files changed, 26 insertions(+)

diff --git a/internal/metrics/treeservice.go b/internal/metrics/treeservice.go
index 6702aa83c..e192c4398 100644
--- a/internal/metrics/treeservice.go
+++ b/internal/metrics/treeservice.go
@@ -12,12 +12,14 @@ type TreeMetricsRegister interface {
 	AddReplicateTaskDuration(time.Duration, bool)
 	AddReplicateWaitDuration(time.Duration, bool)
 	AddSyncDuration(time.Duration, bool)
+	AddOperation(string, string)
 }
 
 type treeServiceMetrics struct {
 	replicateTaskDuration *prometheus.HistogramVec
 	replicateWaitDuration *prometheus.HistogramVec
 	syncOpDuration        *prometheus.HistogramVec
+	ioTagOpsCounter       *prometheus.CounterVec
 }
 
 var _ TreeMetricsRegister = (*treeServiceMetrics)(nil)
@@ -42,6 +44,12 @@ func newTreeServiceMetrics() *treeServiceMetrics {
 			Name:      "sync_duration_seconds",
 			Help:      "Duration of synchronization operations",
 		}, []string{successLabel}),
+		ioTagOpsCounter: metrics.NewCounterVec(prometheus.CounterOpts{
+			Namespace: namespace,
+			Subsystem: treeServiceSubsystem,
+			Name:      "requests_total",
+			Help:      "Count of requests for each IO tag",
+		}, []string{methodLabel, ioTagLabel}),
 	}
 }
 
@@ -62,3 +70,10 @@ func (m *treeServiceMetrics) AddSyncDuration(d time.Duration, success bool) {
 		successLabel: strconv.FormatBool(success),
 	}).Observe(d.Seconds())
 }
+
+func (m *treeServiceMetrics) AddOperation(op string, ioTag string) {
+	m.ioTagOpsCounter.With(prometheus.Labels{
+		ioTagLabel:  ioTag,
+		methodLabel: op,
+	}).Inc()
+}
diff --git a/pkg/services/tree/metrics.go b/pkg/services/tree/metrics.go
index 0f0e4ee57..07503f8c3 100644
--- a/pkg/services/tree/metrics.go
+++ b/pkg/services/tree/metrics.go
@@ -6,6 +6,7 @@ type MetricsRegister interface {
 	AddReplicateTaskDuration(time.Duration, bool)
 	AddReplicateWaitDuration(time.Duration, bool)
 	AddSyncDuration(time.Duration, bool)
+	AddOperation(string, string)
 }
 
 type defaultMetricsRegister struct{}
@@ -13,3 +14,4 @@ type defaultMetricsRegister struct{}
 func (defaultMetricsRegister) AddReplicateTaskDuration(time.Duration, bool) {}
 func (defaultMetricsRegister) AddReplicateWaitDuration(time.Duration, bool) {}
 func (defaultMetricsRegister) AddSyncDuration(time.Duration, bool)          {}
+func (defaultMetricsRegister) AddOperation(string, string)                  {}
diff --git a/pkg/services/tree/service.go b/pkg/services/tree/service.go
index 2e9722e79..f9b7395e7 100644
--- a/pkg/services/tree/service.go
+++ b/pkg/services/tree/service.go
@@ -105,6 +105,7 @@ func (s *Service) Shutdown() {
 }
 
 func (s *Service) Add(ctx context.Context, req *AddRequest) (*AddResponse, error) {
+	defer s.metrics.AddOperation("Add", qos.IOTagFromContext(ctx))
 	if !s.initialSyncDone.Load() {
 		return nil, ErrAlreadySyncing
 	}
@@ -148,6 +149,7 @@ func (s *Service) Add(ctx context.Context, req *AddRequest) (*AddResponse, error
 }
 
 func (s *Service) AddByPath(ctx context.Context, req *AddByPathRequest) (*AddByPathResponse, error) {
+	defer s.metrics.AddOperation("AddByPath", qos.IOTagFromContext(ctx))
 	if !s.initialSyncDone.Load() {
 		return nil, ErrAlreadySyncing
 	}
@@ -203,6 +205,7 @@ func (s *Service) AddByPath(ctx context.Context, req *AddByPathRequest) (*AddByP
 }
 
 func (s *Service) Remove(ctx context.Context, req *RemoveRequest) (*RemoveResponse, error) {
+	defer s.metrics.AddOperation("Remove", qos.IOTagFromContext(ctx))
 	if !s.initialSyncDone.Load() {
 		return nil, ErrAlreadySyncing
 	}
@@ -247,6 +250,7 @@ func (s *Service) Remove(ctx context.Context, req *RemoveRequest) (*RemoveRespon
 // Move applies client operation to the specified tree and pushes in queue
 // for replication on other nodes.
 func (s *Service) Move(ctx context.Context, req *MoveRequest) (*MoveResponse, error) {
+	defer s.metrics.AddOperation("Move", qos.IOTagFromContext(ctx))
 	if !s.initialSyncDone.Load() {
 		return nil, ErrAlreadySyncing
 	}
@@ -290,6 +294,7 @@ func (s *Service) Move(ctx context.Context, req *MoveRequest) (*MoveResponse, er
 }
 
 func (s *Service) GetNodeByPath(ctx context.Context, req *GetNodeByPathRequest) (*GetNodeByPathResponse, error) {
+	defer s.metrics.AddOperation("GetNodeByPath", qos.IOTagFromContext(ctx))
 	if !s.initialSyncDone.Load() {
 		return nil, ErrAlreadySyncing
 	}
@@ -363,6 +368,7 @@ func (s *Service) GetNodeByPath(ctx context.Context, req *GetNodeByPathRequest)
 }
 
 func (s *Service) GetSubTree(req *GetSubTreeRequest, srv TreeService_GetSubTreeServer) error {
+	defer s.metrics.AddOperation("GetSubTree", qos.IOTagFromContext(srv.Context()))
 	if !s.initialSyncDone.Load() {
 		return ErrAlreadySyncing
 	}
@@ -590,6 +596,7 @@ func sortByFilename(nodes []pilorama.NodeInfo, d GetSubTreeRequest_Body_Order_Di
 
 // Apply locally applies operation from the remote node to the tree.
 func (s *Service) Apply(ctx context.Context, req *ApplyRequest) (*ApplyResponse, error) {
+	defer s.metrics.AddOperation("Apply", qos.IOTagFromContext(ctx))
 	err := verifyMessage(req)
 	if err != nil {
 		return nil, err
@@ -633,6 +640,7 @@ func (s *Service) Apply(ctx context.Context, req *ApplyRequest) (*ApplyResponse,
 }
 
 func (s *Service) GetOpLog(req *GetOpLogRequest, srv TreeService_GetOpLogServer) error {
+	defer s.metrics.AddOperation("GetOpLog", qos.IOTagFromContext(srv.Context()))
 	if !s.initialSyncDone.Load() {
 		return ErrAlreadySyncing
 	}
@@ -697,6 +705,7 @@ func (s *Service) GetOpLog(req *GetOpLogRequest, srv TreeService_GetOpLogServer)
 }
 
 func (s *Service) TreeList(ctx context.Context, req *TreeListRequest) (*TreeListResponse, error) {
+	defer s.metrics.AddOperation("TreeList", qos.IOTagFromContext(ctx))
 	if !s.initialSyncDone.Load() {
 		return nil, ErrAlreadySyncing
 	}