Skip to content

Commit

Permalink
Implement Basic Metrics (#40)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Mordil committed May 27, 2019
1 parent 1a9f086 commit 4406252
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 3 deletions.
10 changes: 8 additions & 2 deletions Sources/NIORedis/ChannelHandlers/RedisCommandHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ extension RedisCommandHandler: ChannelInboundHandler {
return assertionFailure("Received unexpected error while idle: \(error.localizedDescription)")
}
leadPromise.fail(error)
RedisMetrics.commandFailureCount.increment()
}

/// Invoked by NIO when a read has been fired from earlier in the response chain. This forwards the unwrapped
Expand All @@ -81,8 +82,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()
}
}
}
Expand Down
12 changes: 11 additions & 1 deletion Sources/NIORedis/RedisClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
//===----------------------------------------------------------------------===//

import struct Foundation.UUID
import struct Dispatch.DispatchTime
import Logging
import Metrics
import NIO
import NIOConcurrencyHelpers

Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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)")
}
Expand Down
83 changes: 83 additions & 0 deletions Sources/NIORedis/RedisMetrics.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
//===----------------------------------------------------------------------===//
//
// 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)
}
}

0 comments on commit 4406252

Please sign in to comment.