mirror of
https://github.com/pgsty/minio.git
synced 2026-11-10 01:31:52 +03:00
94d91703b5
An ILM expiry that is refused, because the rule no longer applies when
it is checked again under the object lock or Object Lock protects the
version, or that fails, for example for lack of quorum, left almost no
trace. The bulk noncurrent purge skipped such entries without audit,
trace, log or count; the scanner traced a refusal exactly like a
successful expiry; and nothing counted either. Operators could not see
why a version they expected gone was still there.
- Count every such expiry by reason in expiry_incomplete_total
{reason="not_applicable"|"locked"|"history_unavailable"|"error"}, in
metrics v2 and v3; the counting unit is one expiry attempt, and no
bucket or object is a label.
- Trace it with result=refused or result=failed and the reason. A
failure is also audited and logged once per object; a refusal changes
nothing and is not audited, since a split key is refused on every scan.
- Never announce it: no ObjectRemoved notification for a version that is
still there.
- Name a completed expiry by what its delete did. An expiry that
addressed no version and whose result is a delete marker created that
marker, also with the null version ID of the version it replaced; any
other removed the version it addressed, also a delete marker. The
scanner and the transitioned expiry took the identity from the queued
object instead: the expiry of a latest transitioned version in a
versioned bucket announced the removal of the version it kept, and the
removal of an expired delete marker was announced as a marker created.
Tests: classification of each error; a bulk purge with a refused, a
failed and a removed entry counts and traces the first two, audits only
the failure and announces only the removal; the expiry events name the
created marker for a latest version (versioned, suspended null,
transitioned) and the removed version for a noncurrent version and an
expired delete marker; both metrics versions export the counter by
reason. The #278 side-effect tests now tell removal traces from
refusal and failure traces.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Feng Ruohang <rh@vonng.com>
495 lines
14 KiB
Go
495 lines
14 KiB
Go
// Copyright (c) 2015-2024 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 (
|
|
"slices"
|
|
"strings"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"github.com/prometheus/client_golang/prometheus/collectors"
|
|
)
|
|
|
|
// Collector paths.
|
|
//
|
|
// These are paths under the top-level /minio/metrics/v3 metrics endpoint. Each
|
|
// of these paths returns a set of V3 metrics.
|
|
//
|
|
// Per-bucket metrics endpoints always start with /bucket and the bucket name is
|
|
// appended to the path. e.g. if the collector path is /bucket/api, the endpoint
|
|
// for the bucket "mybucket" would be /minio/metrics/v3/bucket/api/mybucket
|
|
const (
|
|
apiRequestsCollectorPath collectorPath = "/api/requests"
|
|
|
|
bucketAPICollectorPath collectorPath = "/bucket/api"
|
|
bucketReplicationCollectorPath collectorPath = "/bucket/replication"
|
|
|
|
systemNetworkInternodeCollectorPath collectorPath = "/system/network/internode"
|
|
systemDriveCollectorPath collectorPath = "/system/drive"
|
|
systemMemoryCollectorPath collectorPath = "/system/memory"
|
|
systemCPUCollectorPath collectorPath = "/system/cpu"
|
|
systemProcessCollectorPath collectorPath = "/system/process"
|
|
|
|
debugGoCollectorPath collectorPath = "/debug/go"
|
|
|
|
clusterHealthCollectorPath collectorPath = "/cluster/health"
|
|
clusterUsageObjectsCollectorPath collectorPath = "/cluster/usage/objects"
|
|
clusterUsageBucketsCollectorPath collectorPath = "/cluster/usage/buckets"
|
|
clusterErasureSetCollectorPath collectorPath = "/cluster/erasure-set"
|
|
clusterIAMCollectorPath collectorPath = "/cluster/iam"
|
|
clusterConfigCollectorPath collectorPath = "/cluster/config"
|
|
|
|
ilmCollectorPath collectorPath = "/ilm"
|
|
auditCollectorPath collectorPath = "/audit"
|
|
loggerWebhookCollectorPath collectorPath = "/logger/webhook"
|
|
replicationCollectorPath collectorPath = "/replication"
|
|
notificationCollectorPath collectorPath = "/notification"
|
|
scannerCollectorPath collectorPath = "/scanner"
|
|
)
|
|
|
|
const (
|
|
clusterBasePath = "/cluster"
|
|
)
|
|
|
|
type metricsV3Collection struct {
|
|
mgMap map[collectorPath]*MetricsGroup
|
|
bucketMGMap map[collectorPath]*MetricsGroup
|
|
|
|
// Gatherers for non-bucket MetricsGroup's
|
|
mgGatherers map[collectorPath]prometheus.Gatherer
|
|
|
|
collectorPaths []collectorPath
|
|
}
|
|
|
|
func newMetricGroups(r *prometheus.Registry) *metricsV3Collection {
|
|
// Create all metric groups.
|
|
apiRequestsMG := NewMetricsGroup(apiRequestsCollectorPath,
|
|
[]MetricDescriptor{
|
|
apiRejectedAuthTotalMD,
|
|
apiRejectedHeaderTotalMD,
|
|
apiRejectedTimestampTotalMD,
|
|
apiRejectedInvalidTotalMD,
|
|
|
|
apiRequestsWaitingTotalMD,
|
|
apiRequestsIncomingTotalMD,
|
|
apiRequestsInFlightTotalMD,
|
|
apiRequestsTotalMD,
|
|
apiRequestsErrorsTotalMD,
|
|
apiRequests5xxErrorsTotalMD,
|
|
apiRequests4xxErrorsTotalMD,
|
|
apiRequestsCanceledTotalMD,
|
|
|
|
apiRequestsTTFBSecondsDistributionMD,
|
|
|
|
apiTrafficSentBytesMD,
|
|
apiTrafficRecvBytesMD,
|
|
},
|
|
JoinLoaders(loadAPIRequestsHTTPMetrics, loadAPIRequestsTTFBMetrics,
|
|
loadAPIRequestsNetworkMetrics),
|
|
)
|
|
|
|
bucketAPIMG := NewBucketMetricsGroup(bucketAPICollectorPath,
|
|
[]MetricDescriptor{
|
|
bucketAPITrafficRecvBytesMD,
|
|
bucketAPITrafficSentBytesMD,
|
|
|
|
bucketAPIRequestsInFlightMD,
|
|
bucketAPIRequestsTotalMD,
|
|
bucketAPIRequestsCanceledMD,
|
|
bucketAPIRequests4xxErrorsMD,
|
|
bucketAPIRequests5xxErrorsMD,
|
|
|
|
bucketAPIRequestsTTFBSecondsDistributionMD,
|
|
},
|
|
JoinBucketLoaders(loadBucketAPIHTTPMetrics, loadBucketAPITTFBMetrics),
|
|
)
|
|
|
|
bucketReplicationMG := NewBucketMetricsGroup(bucketReplicationCollectorPath,
|
|
[]MetricDescriptor{
|
|
bucketReplLastHrFailedBytesMD,
|
|
bucketReplLastHrFailedCountMD,
|
|
bucketReplLastMinFailedBytesMD,
|
|
bucketReplLastMinFailedCountMD,
|
|
bucketReplLatencyMsMD,
|
|
bucketReplProxiedDeleteTaggingRequestsTotalMD,
|
|
bucketReplProxiedGetRequestsFailuresMD,
|
|
bucketReplProxiedGetRequestsTotalMD,
|
|
bucketReplProxiedGetTaggingRequestsFailuresMD,
|
|
bucketReplProxiedGetTaggingRequestsTotalMD,
|
|
bucketReplProxiedHeadRequestsFailuresMD,
|
|
bucketReplProxiedHeadRequestsTotalMD,
|
|
bucketReplProxiedPutTaggingRequestsFailuresMD,
|
|
bucketReplProxiedPutTaggingRequestsTotalMD,
|
|
bucketReplSentBytesMD,
|
|
bucketReplSentCountMD,
|
|
bucketReplTotalFailedBytesMD,
|
|
bucketReplTotalFailedCountMD,
|
|
bucketReplProxiedDeleteTaggingRequestsFailuresMD,
|
|
},
|
|
loadBucketReplicationMetrics,
|
|
)
|
|
|
|
systemNetworkInternodeMG := NewMetricsGroup(systemNetworkInternodeCollectorPath,
|
|
[]MetricDescriptor{
|
|
internodeErrorsTotalMD,
|
|
internodeDialedErrorsTotalMD,
|
|
internodeDialAvgTimeNanosMD,
|
|
internodeSentBytesTotalMD,
|
|
internodeRecvBytesTotalMD,
|
|
},
|
|
loadNetworkInternodeMetrics,
|
|
)
|
|
|
|
systemMemoryMG := NewMetricsGroup(systemMemoryCollectorPath,
|
|
[]MetricDescriptor{
|
|
memTotalMD,
|
|
memUsedMD,
|
|
memFreeMD,
|
|
memAvailableMD,
|
|
memBuffersMD,
|
|
memCacheMD,
|
|
memSharedMD,
|
|
memUsedPercMD,
|
|
},
|
|
loadMemoryMetrics,
|
|
)
|
|
|
|
systemCPUMG := NewMetricsGroup(systemCPUCollectorPath,
|
|
[]MetricDescriptor{
|
|
sysCPUAvgIdleMD,
|
|
sysCPUAvgIOWaitMD,
|
|
sysCPULoadMD,
|
|
sysCPULoadPercMD,
|
|
sysCPUNiceMD,
|
|
sysCPUStealMD,
|
|
sysCPUSystemMD,
|
|
sysCPUUserMD,
|
|
},
|
|
loadCPUMetrics,
|
|
)
|
|
|
|
systemProcessMG := NewMetricsGroup(systemProcessCollectorPath,
|
|
[]MetricDescriptor{
|
|
processLocksReadTotalMD,
|
|
processLocksWriteTotalMD,
|
|
processCPUTotalSecondsMD,
|
|
processGoRoutineTotalMD,
|
|
processIORCharBytesMD,
|
|
processIOReadBytesMD,
|
|
processIOWCharBytesMD,
|
|
processIOWriteBytesMD,
|
|
processStarttimeSecondsMD,
|
|
processUptimeSecondsMD,
|
|
processFileDescriptorLimitTotalMD,
|
|
processFileDescriptorOpenTotalMD,
|
|
processSyscallReadTotalMD,
|
|
processSyscallWriteTotalMD,
|
|
processResidentMemoryBytesMD,
|
|
processVirtualMemoryBytesMD,
|
|
processVirtualMemoryMaxBytesMD,
|
|
},
|
|
loadProcessMetrics,
|
|
)
|
|
|
|
systemDriveMG := NewMetricsGroup(systemDriveCollectorPath,
|
|
[]MetricDescriptor{
|
|
driveUsedBytesMD,
|
|
driveFreeBytesMD,
|
|
driveTotalBytesMD,
|
|
driveUsedInodesMD,
|
|
driveFreeInodesMD,
|
|
driveTotalInodesMD,
|
|
driveTimeoutErrorsMD,
|
|
driveIOErrorsMD,
|
|
driveAvailabilityErrorsMD,
|
|
driveWaitingIOMD,
|
|
driveAPILatencyMD,
|
|
driveHealthMD,
|
|
|
|
driveOfflineCountMD,
|
|
driveOnlineCountMD,
|
|
driveCountMD,
|
|
|
|
// iostat related
|
|
driveReadsPerSecMD,
|
|
driveReadsKBPerSecMD,
|
|
driveReadsAwaitMD,
|
|
driveWritesPerSecMD,
|
|
driveWritesKBPerSecMD,
|
|
driveWritesAwaitMD,
|
|
drivePercUtilMD,
|
|
},
|
|
loadDriveMetrics,
|
|
)
|
|
|
|
clusterHealthMG := NewMetricsGroup(clusterHealthCollectorPath,
|
|
[]MetricDescriptor{
|
|
healthDrivesOfflineCountMD,
|
|
healthDrivesOnlineCountMD,
|
|
healthDrivesCountMD,
|
|
|
|
healthNodesOfflineCountMD,
|
|
healthNodesOnlineCountMD,
|
|
|
|
healthCapacityRawTotalBytesMD,
|
|
healthCapacityRawFreeBytesMD,
|
|
healthCapacityUsableTotalBytesMD,
|
|
healthCapacityUsableFreeBytesMD,
|
|
},
|
|
JoinLoaders(loadClusterHealthDriveMetrics,
|
|
loadClusterHealthNodeMetrics,
|
|
loadClusterHealthCapacityMetrics),
|
|
)
|
|
|
|
clusterUsageObjectsMG := NewMetricsGroup(clusterUsageObjectsCollectorPath,
|
|
[]MetricDescriptor{
|
|
usageSinceLastUpdateSecondsMD,
|
|
usageTotalBytesMD,
|
|
usageObjectsCountMD,
|
|
usageVersionsCountMD,
|
|
usageDeleteMarkersCountMD,
|
|
usageBucketsCountMD,
|
|
usageObjectsDistributionMD,
|
|
usageVersionsDistributionMD,
|
|
},
|
|
loadClusterUsageObjectMetrics,
|
|
)
|
|
|
|
clusterUsageBucketsMG := NewMetricsGroup(clusterUsageBucketsCollectorPath,
|
|
[]MetricDescriptor{
|
|
usageSinceLastUpdateSecondsMD,
|
|
usageBucketTotalBytesMD,
|
|
usageBucketObjectsTotalMD,
|
|
usageBucketVersionsCountMD,
|
|
usageBucketDeleteMarkersCountMD,
|
|
usageBucketQuotaTotalBytesMD,
|
|
usageBucketObjectSizeDistributionMD,
|
|
usageBucketObjectVersionCountDistributionMD,
|
|
},
|
|
loadClusterUsageBucketMetrics,
|
|
)
|
|
|
|
clusterErasureSetMG := NewMetricsGroup(clusterErasureSetCollectorPath,
|
|
[]MetricDescriptor{
|
|
erasureSetOverallWriteQuorumMD,
|
|
erasureSetOverallHealthMD,
|
|
erasureSetReadQuorumMD,
|
|
erasureSetWriteQuorumMD,
|
|
erasureSetOnlineDrivesCountMD,
|
|
erasureSetHealingDrivesCountMD,
|
|
erasureSetHealthMD,
|
|
erasureSetReadToleranceMD,
|
|
erasureSetWriteToleranceMD,
|
|
erasureSetReadHealthMD,
|
|
erasureSetWriteHealthMD,
|
|
},
|
|
loadClusterErasureSetMetrics,
|
|
)
|
|
|
|
clusterNotificationMG := NewMetricsGroup(notificationCollectorPath,
|
|
[]MetricDescriptor{
|
|
notificationCurrentSendInProgressMD,
|
|
notificationEventsErrorsTotalMD,
|
|
notificationEventsSentTotalMD,
|
|
notificationEventsSkippedTotalMD,
|
|
},
|
|
loadClusterNotificationMetrics,
|
|
)
|
|
|
|
clusterIAMMG := NewMetricsGroup(clusterIAMCollectorPath,
|
|
[]MetricDescriptor{
|
|
lastSyncDurationMillisMD,
|
|
pluginAuthnServiceFailedRequestsMinuteMD,
|
|
pluginAuthnServiceLastFailSecondsMD,
|
|
pluginAuthnServiceLastSuccSecondsMD,
|
|
pluginAuthnServiceSuccAvgRttMsMinuteMD,
|
|
pluginAuthnServiceSuccMaxRttMsMinuteMD,
|
|
pluginAuthnServiceTotalRequestsMinuteMD,
|
|
sinceLastSyncMillisMD,
|
|
syncFailuresMD,
|
|
syncSuccessesMD,
|
|
revocationRecordsMD,
|
|
revocationHealFailuresMD,
|
|
revocationHealDurationMillisMD,
|
|
revocationHealLastSuccessMD,
|
|
},
|
|
loadClusterIAMMetrics,
|
|
)
|
|
|
|
clusterReplicationMG := NewMetricsGroup(replicationCollectorPath,
|
|
[]MetricDescriptor{
|
|
replicationAverageActiveWorkersMD,
|
|
replicationAverageQueuedBytesMD,
|
|
replicationAverageQueuedCountMD,
|
|
replicationAverageDataTransferRateMD,
|
|
replicationCurrentActiveWorkersMD,
|
|
replicationCurrentDataTransferRateMD,
|
|
replicationLastMinuteQueuedBytesMD,
|
|
replicationLastMinuteQueuedCountMD,
|
|
replicationMaxActiveWorkersMD,
|
|
replicationMaxQueuedBytesMD,
|
|
replicationMaxQueuedCountMD,
|
|
replicationMaxDataTransferRateMD,
|
|
replicationRecentBacklogCountMD,
|
|
replicationMRFDroppedOperationsMD,
|
|
replicationMRFDroppedBytesMD,
|
|
},
|
|
loadClusterReplicationMetrics,
|
|
)
|
|
|
|
clusterConfigMG := NewMetricsGroup(clusterConfigCollectorPath,
|
|
[]MetricDescriptor{
|
|
configRRSParityMD,
|
|
configStandardParityMD,
|
|
},
|
|
loadClusterConfigMetrics,
|
|
)
|
|
|
|
scannerMG := NewMetricsGroup(scannerCollectorPath,
|
|
[]MetricDescriptor{
|
|
scannerBucketScansFinishedMD,
|
|
scannerBucketScansStartedMD,
|
|
scannerDirectoriesScannedMD,
|
|
scannerObjectsScannedMD,
|
|
scannerVersionsScannedMD,
|
|
scannerLastActivitySecondsMD,
|
|
},
|
|
loadClusterScannerMetrics,
|
|
)
|
|
|
|
loggerWebhookMG := NewMetricsGroup(loggerWebhookCollectorPath,
|
|
[]MetricDescriptor{
|
|
webhookFailedMessagesMD,
|
|
webhookQueueLengthMD,
|
|
webhookTotalMessagesMD,
|
|
},
|
|
loadLoggerWebhookMetrics,
|
|
)
|
|
|
|
auditMG := NewMetricsGroup(auditCollectorPath,
|
|
[]MetricDescriptor{
|
|
auditFailedMessagesMD,
|
|
auditTargetQueueLengthMD,
|
|
auditTotalMessagesMD,
|
|
},
|
|
loadAuditMetrics,
|
|
)
|
|
|
|
ilmMG := NewMetricsGroup(ilmCollectorPath,
|
|
[]MetricDescriptor{
|
|
ilmExpiryPendingTasksMD,
|
|
ilmTransitionActiveTasksMD,
|
|
ilmTransitionPendingTasksMD,
|
|
ilmTransitionMissedImmediateTasksMD,
|
|
ilmVersionsScannedMD,
|
|
ilmExpiryIncompleteTotalMD,
|
|
},
|
|
loadILMMetrics,
|
|
)
|
|
|
|
allMetricGroups := []*MetricsGroup{
|
|
apiRequestsMG,
|
|
bucketAPIMG,
|
|
bucketReplicationMG,
|
|
|
|
systemNetworkInternodeMG,
|
|
systemDriveMG,
|
|
systemMemoryMG,
|
|
systemCPUMG,
|
|
systemProcessMG,
|
|
|
|
clusterHealthMG,
|
|
clusterUsageObjectsMG,
|
|
clusterUsageBucketsMG,
|
|
clusterErasureSetMG,
|
|
clusterNotificationMG,
|
|
clusterIAMMG,
|
|
clusterReplicationMG,
|
|
clusterConfigMG,
|
|
|
|
ilmMG,
|
|
scannerMG,
|
|
auditMG,
|
|
loggerWebhookMG,
|
|
}
|
|
|
|
// Bucket metrics are special, they always include the bucket label. These
|
|
// metrics required a list of buckets to be passed to the loader, and the list
|
|
// of buckets is not known until the request is made. So we keep a separate
|
|
// map for bucket metrics and handle them specially.
|
|
|
|
// Add the serverName and poolIndex labels to all non-cluster metrics.
|
|
//
|
|
// Also create metric group maps and set the cache.
|
|
metricsCache := newMetricsCache()
|
|
mgMap := make(map[collectorPath]*MetricsGroup)
|
|
bucketMGMap := make(map[collectorPath]*MetricsGroup)
|
|
for _, mg := range allMetricGroups {
|
|
if !strings.HasPrefix(string(mg.CollectorPath), clusterBasePath) {
|
|
mg.AddExtraLabels(
|
|
serverName, globalLocalNodeName,
|
|
// poolIndex, strconv.Itoa(globalLocalPoolIdx),
|
|
)
|
|
}
|
|
mg.SetCache(metricsCache)
|
|
if mg.IsBucketMetricsGroup() {
|
|
bucketMGMap[mg.CollectorPath] = mg
|
|
} else {
|
|
mgMap[mg.CollectorPath] = mg
|
|
}
|
|
}
|
|
|
|
// Prepare to register the collectors. Other than `MetricGroup` collectors,
|
|
// we also have standard collectors like `GoCollector`.
|
|
|
|
// Create all Non-`MetricGroup` collectors here.
|
|
collectors := map[collectorPath]prometheus.Collector{
|
|
debugGoCollectorPath: collectors.NewGoCollector(),
|
|
}
|
|
|
|
// Add all `MetricGroup` collectors to the map.
|
|
for _, mg := range allMetricGroups {
|
|
collectors[mg.CollectorPath] = mg
|
|
}
|
|
|
|
// Helper function to register a collector and return a gatherer for it.
|
|
mustRegister := func(c ...prometheus.Collector) prometheus.Gatherer {
|
|
subRegistry := prometheus.NewRegistry()
|
|
for _, col := range c {
|
|
subRegistry.MustRegister(col)
|
|
}
|
|
r.MustRegister(subRegistry)
|
|
return subRegistry
|
|
}
|
|
|
|
// Register all collectors and create gatherers for them.
|
|
gatherers := make(map[collectorPath]prometheus.Gatherer, len(collectors))
|
|
collectorPaths := make([]collectorPath, 0, len(collectors))
|
|
for path, collector := range collectors {
|
|
gatherers[path] = mustRegister(collector)
|
|
collectorPaths = append(collectorPaths, path)
|
|
}
|
|
slices.Sort(collectorPaths)
|
|
return &metricsV3Collection{
|
|
mgMap: mgMap,
|
|
bucketMGMap: bucketMGMap,
|
|
mgGatherers: gatherers,
|
|
collectorPaths: collectorPaths,
|
|
}
|
|
}
|