mirror of
				https://github.com/minio/minio.git
				synced 2025-11-04 02:01:05 +01:00 
			
		
		
		
	Provides individual and aggregate stats for each RPC connection.
Example:
```
  "rpc": {
   "collectedAt": "2024-05-31T14:33:29.1373103+02:00",
   "connected": 30,
   "disconnected": 0,
   "outgoingStreams": 69,
   "incomingStreams": 0,
   "outgoingBytes": 174822796,
   "incomingBytes": 175821566,
   "outgoingMessages": 768595,
   "incomingMessages": 768589,
   "outQueue": 0,
   "lastPongTime": "2024-05-31T12:33:28Z",
   "byDestination": {
    "http://127.0.0.1:9001": {
     "collectedAt": "2024-05-31T14:33:29.1373103+02:00",
     "connected": 5,
     "disconnected": 0,
     "outgoingStreams": 2,
     "incomingStreams": 0,
     "outgoingBytes": 38432543,
     "incomingBytes": 66604052,
     "outgoingMessages": 229496,
     "incomingMessages": 229575,
     "outQueue": 0,
     "lastPongTime": "2024-05-31T12:33:27Z"
    },
    "http://127.0.0.1:9002": {
     "collectedAt": "2024-05-31T14:33:29.1373103+02:00",
     "connected": 5,
     "disconnected": 0,
     "outgoingStreams": 6,
     "incomingStreams": 0,
     "outgoingBytes": 38215680,
     "incomingBytes": 66121283,
     "outgoingMessages": 228525,
     "incomingMessages": 228510,
     "outQueue": 0,
     "lastPongTime": "2024-05-31T12:33:27Z"
    },
...
```
		
	
			
		
			
				
	
	
		
			232 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			232 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright (c) 2015-2022 MinIO, Inc.
 | 
						|
//
 | 
						|
// This file is part of MinIO Object Storage stack
 | 
						|
//
 | 
						|
// This program is free software: you can redistribute it and/or modify
 | 
						|
// it under the terms of the GNU Affero General Public License as published by
 | 
						|
// the Free Software Foundation, either version 3 of the License, or
 | 
						|
// (at your option) any later version.
 | 
						|
//
 | 
						|
// This program is distributed in the hope that it will be useful
 | 
						|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
						|
// GNU Affero General Public License for more details.
 | 
						|
//
 | 
						|
// You should have received a copy of the GNU Affero General Public License
 | 
						|
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
						|
 | 
						|
package cmd
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"fmt"
 | 
						|
	"net/http"
 | 
						|
	"strings"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/minio/madmin-go/v3"
 | 
						|
	"github.com/minio/minio/internal/disk"
 | 
						|
	"github.com/minio/minio/internal/net"
 | 
						|
	c "github.com/shirou/gopsutil/v3/cpu"
 | 
						|
	"github.com/shirou/gopsutil/v3/load"
 | 
						|
)
 | 
						|
 | 
						|
type collectMetricsOpts struct {
 | 
						|
	hosts map[string]struct{}
 | 
						|
	disks map[string]struct{}
 | 
						|
	jobID string
 | 
						|
	depID string
 | 
						|
}
 | 
						|
 | 
						|
func collectLocalMetrics(types madmin.MetricType, opts collectMetricsOpts) (m madmin.RealtimeMetrics) {
 | 
						|
	if types == madmin.MetricsNone {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	byHostName := globalMinioAddr
 | 
						|
	if len(opts.hosts) > 0 {
 | 
						|
		server := getLocalServerProperty(globalEndpoints, &http.Request{
 | 
						|
			Host: globalLocalNodeName,
 | 
						|
		}, false)
 | 
						|
		if _, ok := opts.hosts[server.Endpoint]; ok {
 | 
						|
			byHostName = server.Endpoint
 | 
						|
		} else {
 | 
						|
			return
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if strings.HasPrefix(byHostName, ":") && !strings.HasPrefix(globalLocalNodeName, ":") {
 | 
						|
		byHostName = globalLocalNodeName
 | 
						|
	}
 | 
						|
 | 
						|
	if types.Contains(madmin.MetricsDisk) {
 | 
						|
		m.ByDisk = make(map[string]madmin.DiskMetric)
 | 
						|
		aggr := madmin.DiskMetric{
 | 
						|
			CollectedAt: time.Now(),
 | 
						|
		}
 | 
						|
		for name, disk := range collectLocalDisksMetrics(opts.disks) {
 | 
						|
			m.ByDisk[name] = disk
 | 
						|
			aggr.Merge(&disk)
 | 
						|
		}
 | 
						|
		m.Aggregated.Disk = &aggr
 | 
						|
	}
 | 
						|
 | 
						|
	if types.Contains(madmin.MetricsScanner) {
 | 
						|
		metrics := globalScannerMetrics.report()
 | 
						|
		m.Aggregated.Scanner = &metrics
 | 
						|
	}
 | 
						|
	if types.Contains(madmin.MetricsOS) {
 | 
						|
		metrics := globalOSMetrics.report()
 | 
						|
		m.Aggregated.OS = &metrics
 | 
						|
	}
 | 
						|
	if types.Contains(madmin.MetricsBatchJobs) {
 | 
						|
		m.Aggregated.BatchJobs = globalBatchJobsMetrics.report(opts.jobID)
 | 
						|
	}
 | 
						|
	if types.Contains(madmin.MetricsSiteResync) {
 | 
						|
		m.Aggregated.SiteResync = globalSiteResyncMetrics.report(opts.depID)
 | 
						|
	}
 | 
						|
	if types.Contains(madmin.MetricNet) {
 | 
						|
		m.Aggregated.Net = &madmin.NetMetrics{
 | 
						|
			CollectedAt:   UTCNow(),
 | 
						|
			InterfaceName: globalInternodeInterface,
 | 
						|
		}
 | 
						|
		netStats, err := net.GetInterfaceNetStats(globalInternodeInterface)
 | 
						|
		if err != nil {
 | 
						|
			m.Errors = append(m.Errors, fmt.Sprintf("%s: %v  (nicstats)", byHostName, err.Error()))
 | 
						|
		} else {
 | 
						|
			m.Aggregated.Net.NetStats = netStats
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if types.Contains(madmin.MetricsMem) {
 | 
						|
		m.Aggregated.Mem = &madmin.MemMetrics{
 | 
						|
			CollectedAt: UTCNow(),
 | 
						|
		}
 | 
						|
		m.Aggregated.Mem.Info = madmin.GetMemInfo(GlobalContext, byHostName)
 | 
						|
	}
 | 
						|
	if types.Contains(madmin.MetricsCPU) {
 | 
						|
		m.Aggregated.CPU = &madmin.CPUMetrics{
 | 
						|
			CollectedAt: UTCNow(),
 | 
						|
		}
 | 
						|
		cm, err := c.Times(false)
 | 
						|
		if err != nil {
 | 
						|
			m.Errors = append(m.Errors, fmt.Sprintf("%s: %v (cpuTimes)", byHostName, err.Error()))
 | 
						|
		} else {
 | 
						|
			// not collecting per-cpu stats, so there will be only one element
 | 
						|
			if len(cm) == 1 {
 | 
						|
				m.Aggregated.CPU.TimesStat = &cm[0]
 | 
						|
			} else {
 | 
						|
				m.Errors = append(m.Errors, fmt.Sprintf("%s: Expected one CPU stat, got %d", byHostName, len(cm)))
 | 
						|
			}
 | 
						|
		}
 | 
						|
		cpuCount, err := c.Counts(true)
 | 
						|
		if err != nil {
 | 
						|
			m.Errors = append(m.Errors, fmt.Sprintf("%s: %v (cpuCount)", byHostName, err.Error()))
 | 
						|
		} else {
 | 
						|
			m.Aggregated.CPU.CPUCount = cpuCount
 | 
						|
		}
 | 
						|
 | 
						|
		loadStat, err := load.Avg()
 | 
						|
		if err != nil {
 | 
						|
			m.Errors = append(m.Errors, fmt.Sprintf("%s: %v (loadStat)", byHostName, err.Error()))
 | 
						|
		} else {
 | 
						|
			m.Aggregated.CPU.LoadStat = loadStat
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if types.Contains(madmin.MetricsRPC) {
 | 
						|
		gr := globalGrid.Load()
 | 
						|
		if gr == nil {
 | 
						|
			m.Errors = append(m.Errors, fmt.Sprintf("%s: Grid not initialized", byHostName))
 | 
						|
		} else {
 | 
						|
			stats := gr.ConnStats()
 | 
						|
			m.Aggregated.RPC = &stats
 | 
						|
		}
 | 
						|
	}
 | 
						|
	// Add types...
 | 
						|
 | 
						|
	// ByHost is a shallow reference, so careful about sharing.
 | 
						|
	m.ByHost = map[string]madmin.Metrics{byHostName: m.Aggregated}
 | 
						|
	m.Hosts = append(m.Hosts, byHostName)
 | 
						|
 | 
						|
	return m
 | 
						|
}
 | 
						|
 | 
						|
func collectLocalDisksMetrics(disks map[string]struct{}) map[string]madmin.DiskMetric {
 | 
						|
	objLayer := newObjectLayerFn()
 | 
						|
	if objLayer == nil {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	metrics := make(map[string]madmin.DiskMetric)
 | 
						|
	storageInfo := objLayer.LocalStorageInfo(GlobalContext, true)
 | 
						|
	for _, d := range storageInfo.Disks {
 | 
						|
		if len(disks) != 0 {
 | 
						|
			_, ok := disks[d.Endpoint]
 | 
						|
			if !ok {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if d.State != madmin.DriveStateOk && d.State != madmin.DriveStateUnformatted {
 | 
						|
			metrics[d.Endpoint] = madmin.DiskMetric{NDisks: 1, Offline: 1}
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		var dm madmin.DiskMetric
 | 
						|
		dm.NDisks = 1
 | 
						|
		if d.Healing {
 | 
						|
			dm.Healing++
 | 
						|
		}
 | 
						|
		if d.Metrics != nil {
 | 
						|
			dm.LifeTimeOps = make(map[string]uint64, len(d.Metrics.APICalls))
 | 
						|
			for k, v := range d.Metrics.APICalls {
 | 
						|
				if v != 0 {
 | 
						|
					dm.LifeTimeOps[k] = v
 | 
						|
				}
 | 
						|
			}
 | 
						|
			dm.LastMinute.Operations = make(map[string]madmin.TimedAction, len(d.Metrics.APICalls))
 | 
						|
			for k, v := range d.Metrics.LastMinute {
 | 
						|
				if v.Count != 0 {
 | 
						|
					dm.LastMinute.Operations[k] = v
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		st, err := disk.GetDriveStats(d.Major, d.Minor)
 | 
						|
		if err == nil {
 | 
						|
			dm.IOStats = madmin.DiskIOStats{
 | 
						|
				ReadIOs:        st.ReadIOs,
 | 
						|
				ReadMerges:     st.ReadMerges,
 | 
						|
				ReadSectors:    st.ReadSectors,
 | 
						|
				ReadTicks:      st.ReadTicks,
 | 
						|
				WriteIOs:       st.WriteIOs,
 | 
						|
				WriteMerges:    st.WriteMerges,
 | 
						|
				WriteSectors:   st.WriteSectors,
 | 
						|
				WriteTicks:     st.WriteTicks,
 | 
						|
				CurrentIOs:     st.CurrentIOs,
 | 
						|
				TotalTicks:     st.TotalTicks,
 | 
						|
				ReqTicks:       st.ReqTicks,
 | 
						|
				DiscardIOs:     st.DiscardIOs,
 | 
						|
				DiscardMerges:  st.DiscardMerges,
 | 
						|
				DiscardSectors: st.DiscardSectors,
 | 
						|
				DiscardTicks:   st.DiscardTicks,
 | 
						|
				FlushIOs:       st.FlushIOs,
 | 
						|
				FlushTicks:     st.FlushTicks,
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		metrics[d.Endpoint] = dm
 | 
						|
	}
 | 
						|
	return metrics
 | 
						|
}
 | 
						|
 | 
						|
func collectRemoteMetrics(ctx context.Context, types madmin.MetricType, opts collectMetricsOpts) (m madmin.RealtimeMetrics) {
 | 
						|
	if !globalIsDistErasure {
 | 
						|
		return
 | 
						|
	}
 | 
						|
	all := globalNotificationSys.GetMetrics(ctx, types, opts)
 | 
						|
	for _, remote := range all {
 | 
						|
		m.Merge(&remote)
 | 
						|
	}
 | 
						|
	return m
 | 
						|
}
 |