Commit 9df65396 authored by Nathan Harris's avatar Nathan Harris
Browse files

Implement Basic Metrics (#40)

Motivation:

End users will want to capture some baseline performance metrics that can only be accurately gathered at this level of the stack.

Modifications:

Added new `RedisMetrics` struct that retains all SwiftMetrics objects used throughout the system lifecycle, and appropriate code to track desired metrics.

Result:

Users now have a way to peek into the system's performance to understand what NIORedis is being asked to do from their usage.
parent 0b284912
......@@ -63,6 +63,7 @@ extension RedisCommandHandler: ChannelInboundHandler {
}
leadPromise.fail(error)
context.fireErrorCaught(error)
RedisMetrics.commandFailureCount.increment()
}
/// Invoked by NIO when a read has been fired from earlier in the response chain. This forwards the unwrapped
......@@ -82,8 +83,13 @@ extension RedisCommandHandler: ChannelInboundHandler {
assert(popped != nil)
switch value {
case .error(let e): leadPromise.fail(e)
default: leadPromise.succeed(value)
case .error(let e):
leadPromise.fail(e)
RedisMetrics.commandFailureCount.increment()
default:
leadPromise.succeed(value)
RedisMetrics.commandSuccessCount.increment()
}
}
}
......
......@@ -13,7 +13,9 @@
//===----------------------------------------------------------------------===//
import struct Foundation.UUID
import struct Dispatch.DispatchTime
import Logging
import Metrics
import NIO
import NIOConcurrencyHelpers
......@@ -107,6 +109,8 @@ public final class RedisConnection: RedisClient {
self.logger[metadataKey: loggingKeyID] = "\(UUID())"
self.logger.debug("Connection created.")
self._state = .open
RedisMetrics.activeConnectionCount += 1
RedisMetrics.totalConnectionCount.increment()
}
/// Sends a `QUIT` command, then closes the `Channel` this instance was initialized with.
......@@ -126,7 +130,10 @@ public final class RedisConnection: RedisClient {
self.channel.close(promise: promise)
return promise.futureResult
}
.map { self.logger.debug("Connection closed.") }
.map {
self.logger.debug("Connection closed.")
RedisMetrics.activeConnectionCount -= 1
}
.recover {
self.logger.error("Encountered error during close(): \($0)")
self.state = .open
......@@ -162,7 +169,10 @@ public final class RedisConnection: RedisClient {
promise: promise
)
let startTime = DispatchTime.now().uptimeNanoseconds
promise.futureResult.whenComplete { result in
let duration = DispatchTime.now().uptimeNanoseconds - startTime
RedisMetrics.commandRoundTripTime.recordNanoseconds(duration)
guard case let .failure(error) = result else { return }
self.logger.error("\(error.localizedDescription)")
}
......
//===----------------------------------------------------------------------===//
//
// This source file is part of the NIORedis open source project
//
// Copyright (c) 2019 NIORedis project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of NIORedis project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import Metrics
/// 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 `"NIORedis.<rawValue>"`.
public enum Label: String, CustomStringConvertible {
case totalConnectionCount
case activeConnectionCount
case commandSuccessCount
case commandFailureCount
case commandRoundTripTime
public var description: String {
return "NIORedis.\(self.rawValue)"
}
}
private static let activeConnectionCountGauge = Gauge(label: .activeConnectionCount)
/// The current number of connections this library has active.
/// - Note: Changing this number will update the `Metrics.Gauge` stored for recording the new value.
public static var activeConnectionCount: Int = 0 {
didSet {
activeConnectionCountGauge.record(activeConnectionCount)
}
}
/// 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 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 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)
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment