apple / swift-cluster-membership

Distributed Membership Protocol implementations in Swift
https://apple.github.io/swift-cluster-membership/
Apache License 2.0
201 stars 20 forks source link

SWIM + Metrics: Expose SWIM metrics, like how long pings take to come back etc #8

Closed ktoso closed 4 years ago

ktoso commented 4 years ago

build in some metrics, probably right away into the instance.

ktoso commented 4 years ago
extension SWIM {
    public struct Metrics {

        /// Number of members (total)
        let members: Gauge
        /// Number of members (alive)
        let membersAlive: Gauge
        /// Number of members (suspect)
        let membersSuspect: Gauge
        /// Number of members (unreachable)
        let membersUnreachable: Gauge
        /// Number of members (dead)
        let membersDead: Gauge

        /// Records time it takes for ping round-trips
        let pingResponseTime: Timer

        /// Records time it takes for (every) pingRequest round-trip
        let pingRequestResponseTimeAll: Timer
        let pingRequestResponseTimeFirst: Timer

        /// Records the incarnation of the SWIM instance.
        ///
        /// Incarnation numbers are bumped whenever the node needs to refute some gossip about itself,
        /// as such the incarnation number *growth* is an interesting indicator of cluster observation churn.
        let incarnation: Gauge

        let successfulProbeCount: Gauge

        let failedProbeCount: Gauge

        // TODO: message sizes (count and bytes)

        init(settings: SWIM.Settings) {
            self.members = Gauge(
                label: settings.metrics.makeLabel("members"),
                dimensions: [("status", "all")]
            )
            self.membersAlive = Gauge(
                label: settings.metrics.makeLabel("members"),
                dimensions: [("status", "alive")]
            )
            self.membersSuspect = Gauge(
                label: settings.metrics.makeLabel("members"),
                dimensions: [("status", "dead")]
            )
            self.membersUnreachable = Gauge(
                label: settings.metrics.makeLabel("members"),
                dimensions: [("status", "unreachable")]
            )
            self.membersDead = Gauge(
                label: settings.metrics.makeLabel("members"),
                dimensions: [("status", "dead")]
            )

            self.pingResponseTime = Timer(label: settings.metrics.makeLabel("responseRoundTrip", "ping"))
            self.pingRequestResponseTimeAll = Timer(
                label: settings.metrics.makeLabel("responseRoundTrip", "pingRequest"),
                dimensions: [("type", "all")]
            )
            self.pingRequestResponseTimeFirst = Timer(
                label: settings.metrics.makeLabel("responseRoundTrip", "pingRequest"),
                dimensions: [("type", "firstSuccessful")]
            )
            self.incarnation = Gauge(label: settings.metrics.makeLabel("incarnation"))

            self.successfulProbeCount = Gauge(
                label: settings.metrics.makeLabel("incarnation"),
                dimensions: [("type", "successful")]
            )
            self.failedProbeCount = Gauge(
                label: settings.metrics.makeLabel("incarnation"),
                dimensions: [("type", "failed")]
            )
        }
    }
}

// ==== ----------------------------------------------------------------------------------------------------------------
// MARK: SWIM Metrics Settings

/// Configure label names and other details about metrics reported by the `SWIM.Instance`.
public struct SWIMMetricsSettings {
    public init() {}

    /// Configure the segments separator for use when creating labels;
    /// Some systems like graphite like "." as the separator, yet others may not treat this as legal character.
    ///
    /// Typical alternative values are "/" or "_", though consult your metrics backend before changing this setting.
    public var segmentSeparator: String = "."

    /// Prefix all metrics with this segment.
    ///
    /// If set, this is used as the first part of a label name, followed by `labelPrefix`.
    public var systemName: String?

    /// Label string prefixed before all emitted metrics names in their labels.
    ///
    /// - SeeAlso: `systemName`, if set, is prefixed before `labelPrefix` when creating label names.
    public var labelPrefix: String? = "swim"

    func makeLabel(_ segments: String...) -> String {
        let systemNamePart: String = self.systemName.map { "\($0)\(self.segmentSeparator)" } ?? ""
        let systemMetricsPrefixPart: String = self.labelPrefix.map { "\($0)\(self.segmentSeparator)" } ?? ""
        let joinedSegments = segments.joined(separator: self.segmentSeparator)

        return "\(systemNamePart)\(systemMetricsPrefixPart)\(joinedSegments)"
    }
}

and insert those at the right spots