Mordil/RediStack

View on GitHub
Sources/RediStack/RedisMetrics.swift

Summary

Maintainability
A
1 hr
Test Coverage
//===----------------------------------------------------------------------===//
//
// This source file is part of the RediStack open source project
//
// Copyright (c) 2019-2020 RediStack project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of RediStack project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import Atomics
import Metrics
import NIOConcurrencyHelpers

/// The system funnel for all `Metrics` interactions from the Redis library.
///
/// It is highly recommended to not interact with this directly, and to let the library
/// use it how it sees fit.
///
/// There is a nested enum type of `RedisMetrics.Label` that is available to query, match, etc. the
/// labels used for all of the `Metrics` types created by the Redis library.
public struct RedisMetrics {
    /// An enumeration of all the labels used by the Redis library for various `Metrics` data points.
    ///
    /// Each is backed by a raw string, and this type is `CustomStringConvertible` to receive a
    /// namespaced description in the form of `"RediStack.<rawValue>"`.
    public enum Label: String, CustomStringConvertible {
        case totalConnectionCount
        case activeConnectionCount
        case activeChannelSubscriptions
        case activePatternSubscriptions
        case subscriptionMessagesReceivedCount
        case commandSuccessCount
        case commandFailureCount
        case commandRoundTripTime

        public var description: String {
            return "RediStack.\(self.rawValue)"
        }
    }

    /// The wrapped `Metrics.Gauge` maintaining the current number of connections this library has active.
    public static var activeConnectionCount = IncrementalGauge(.activeConnectionCount)
    /// The wrapped `Metrics.Gauge` maintaining the current number of subscriptions to channels.
    public static var activeChannelSubscriptions = IncrementalGauge(.activeChannelSubscriptions)
    /// The wrapped `Metrics.Gauge` maintaining the current number of subscriptions to channel patterns.
    public static var activePatternSubscriptions = IncrementalGauge(.activePatternSubscriptions)
    /// The `Metrics.Counter` that retains the number of connections made since application startup.
    public static let totalConnectionCount = Counter(label: .totalConnectionCount)
    /// The `Metrics.Counter` that retains the number of subscription messages that have been received.
    public static let subscriptionMessagesReceivedCount = Counter(label: .subscriptionMessagesReceivedCount)
    /// The `Metrics.Counter` that retains the number of commands that successfully returned from Redis
    /// since application startup.
    public static let commandSuccessCount = Counter(label: .commandSuccessCount)
    /// The `Metrics.Counter` that retains the number of commands that failed from errors returned
    /// by Redis since application startup.
    public static let commandFailureCount = Counter(label: .commandFailureCount)
    /// The `Metrics.Timer` that receives command response times in nanoseconds from when a command
    /// is first sent through the `NIO.Channel`, to when the response is first resolved.
    public static let commandRoundTripTime = Timer(label: .commandRoundTripTime)

    private init() { }
}

extension RedisMetrics {
    /// A specialized wrapper class for working with `Metrics.Gauge` objects for the purpose of an incrementing or decrementing count of active objects.
    public class IncrementalGauge {
        private let gauge: Gauge
        private let count = ManagedAtomic<Int>(0)
        
        /// The number of the objects that are currently reported as active.
        public var currentCount: Int { return count.load(ordering: .sequentiallyConsistent) }
        
        internal init(_ label: Label) {
            self.gauge = .init(label: label)
        }
        
        /// Increments the current count by the amount specified.
        /// - Parameter amount: The number to increase the current count by. Default is `1`.
        public func increment(by amount: Int = 1) {
            self.count.wrappingIncrement(by: amount, ordering: .sequentiallyConsistent)
            self.gauge.record(self.count.load(ordering: .sequentiallyConsistent))
        }
        
        /// Decrements the current count by the amount specified.
        /// - Parameter amount: The number to decrease the current count by. Default is `1`.
        public func decrement(by amount: Int = 1) {
            self.count.wrappingDecrement(by: amount, ordering: .sequentiallyConsistent)
            self.gauge.record(self.count.load(ordering: .sequentiallyConsistent))
        }
        
        /// Resets the current count to `0`.
        public func reset() {
            _ = self.count.exchange(0, ordering: .sequentiallyConsistent)
            self.gauge.record(self.count.load(ordering: .sequentiallyConsistent))
        }
    }
}

// MARK: SwiftMetrics Convenience

extension Metrics.Counter {
    @inline(__always)
    convenience init(label: RedisMetrics.Label) {
        self.init(label: label.description)
    }
}

extension Metrics.Gauge {
    @inline(__always)
    convenience init(label: RedisMetrics.Label) {
        self.init(label: label.description)
    }
}

extension Metrics.Timer {
    @inline(__always)
    convenience init(label: RedisMetrics.Label) {
        self.init(label: label.description)
    }
}