Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement RED metrics recording #245

Merged
merged 2 commits into from May 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions Package.swift
Expand Up @@ -21,6 +21,7 @@ let package = Package(
.library(name: "AWSSDKSwiftCore", targets: ["AWSSDKSwiftCore"])
],
dependencies: [
.package(url: "https://github.com/apple/swift-metrics.git", "1.0.0" ..< "3.0.0"),
.package(url: "https://github.com/apple/swift-nio.git", .upToNextMajor(from:"2.13.1")),
.package(url: "https://github.com/apple/swift-nio-ssl.git", .upToNextMajor(from:"2.4.1")),
.package(url: "https://github.com/apple/swift-nio-transport-services.git", .upToNextMajor(from:"1.0.0")),
Expand All @@ -32,6 +33,7 @@ let package = Package(
dependencies: [
"AsyncHTTPClient",
"AWSSignerV4",
"Metrics",
"NIO",
"NIOHTTP1",
"NIOSSL",
Expand Down
36 changes: 31 additions & 5 deletions Sources/AWSSDKSwiftCore/AWSClient.swift
Expand Up @@ -13,14 +13,15 @@
//===----------------------------------------------------------------------===//

import AsyncHTTPClient
import Dispatch
import Metrics
import NIO
import NIOHTTP1
import NIOTransportServices
import class Foundation.ProcessInfo
import class Foundation.JSONSerialization
import class Foundation.JSONDecoder
import struct Foundation.Data
import struct Foundation.Date
import struct Foundation.URL
import struct Foundation.URLComponents
import struct Foundation.URLQueryItem
Expand Down Expand Up @@ -249,7 +250,7 @@ extension AWSClient {
/// Empty Future that completes when response is received
public func send<Input: AWSEncodableShape>(operation operationName: String, path: String, httpMethod: String, input: Input, on eventLoop: EventLoop? = nil) -> EventLoopFuture<Void> {
let eventLoop = eventLoop ?? eventLoopGroup.next()
return credentialProvider.getCredential(on: eventLoop).flatMapThrowing { credential in
let future: EventLoopFuture<Void> = credentialProvider.getCredential(on: eventLoop).flatMapThrowing { credential in
let signer = AWSSigner(credentials: credential, name: self.serviceConfig.signingName, region: self.serviceConfig.region.rawValue)
let awsRequest = try self.createAWSRequest(
operation: operationName,
Expand All @@ -262,6 +263,7 @@ extension AWSClient {
}.map { _ in
return
}
return recordMetrics(future, service: serviceConfig.service, operation: operationName)
}

/// send an empty request and return a future with an empty response
Expand All @@ -273,7 +275,7 @@ extension AWSClient {
/// Empty Future that completes when response is received
public func send(operation operationName: String, path: String, httpMethod: String, on eventLoop: EventLoop? = nil) -> EventLoopFuture<Void> {
let eventLoop = eventLoop ?? eventLoopGroup.next()
return credentialProvider.getCredential(on: eventLoop).flatMapThrowing { credential in
let future: EventLoopFuture<Void> = credentialProvider.getCredential(on: eventLoop).flatMapThrowing { credential in
let signer = AWSSigner(credentials: credential, name: self.serviceConfig.signingName, region: self.serviceConfig.region.rawValue)
let awsRequest = try self.createAWSRequest(
operation: operationName,
Expand All @@ -285,6 +287,7 @@ extension AWSClient {
}.map { _ in
return
}
return recordMetrics(future, service: serviceConfig.service, operation: operationName)
}

/// send an empty request and return a future with the output object generated from the response
Expand All @@ -296,7 +299,7 @@ extension AWSClient {
/// Future containing output object that completes when response is received
public func send<Output: AWSDecodableShape>(operation operationName: String, path: String, httpMethod: String, on eventLoop: EventLoop? = nil) -> EventLoopFuture<Output> {
let eventLoop = eventLoop ?? eventLoopGroup.next()
return credentialProvider.getCredential(on: eventLoop).flatMapThrowing { credential in
let future: EventLoopFuture<Output> = credentialProvider.getCredential(on: eventLoop).flatMapThrowing { credential in
let signer = AWSSigner(credentials: credential, name: self.serviceConfig.signingName, region: self.serviceConfig.region.rawValue)
let awsRequest = try self.createAWSRequest(
operation: operationName,
Expand All @@ -308,6 +311,7 @@ extension AWSClient {
}.flatMapThrowing { response in
return try self.validate(operation: operationName, response: response)
}
return recordMetrics(future, service: serviceConfig.service, operation: operationName)
}

/// send a request with an input object and return a future with the output object generated from the response
Expand All @@ -320,7 +324,7 @@ extension AWSClient {
/// Future containing output object that completes when response is received
public func send<Output: AWSDecodableShape, Input: AWSEncodableShape>(operation operationName: String, path: String, httpMethod: String, input: Input, on eventLoop: EventLoop? = nil) -> EventLoopFuture<Output> {
let eventLoop = eventLoop ?? eventLoopGroup.next()
return credentialProvider.getCredential(on: eventLoop).flatMapThrowing { credential in
let future: EventLoopFuture<Output> = credentialProvider.getCredential(on: eventLoop).flatMapThrowing { credential in
let signer = AWSSigner(credentials: credential, name: self.serviceConfig.signingName, region: self.serviceConfig.region.rawValue)
let awsRequest = try self.createAWSRequest(
operation: operationName,
Expand All @@ -333,6 +337,7 @@ extension AWSClient {
}.flatMapThrowing { response in
return try self.validate(operation: operationName, response: response)
}
return recordMetrics(future, service: serviceConfig.service, operation: operationName)
}

/// generate a signed URL
Expand Down Expand Up @@ -782,6 +787,27 @@ extension AWSClient.ClientError: CustomStringConvertible {
}
}

extension AWSClient {
func recordMetrics<Output>(_ future: EventLoopFuture<Output>, service: String, operation: String) -> EventLoopFuture<Output> {
let dimensions: [(String, String)] = [("service", service), ("operation", operation)]
let startTime = DispatchTime.now().uptimeNanoseconds

Counter(label: "aws_requests_total", dimensions: dimensions).increment()

return future.map { response in
Metrics.Timer(
label: "aws_request_duration",
dimensions: dimensions,
preferredDisplayUnit: .seconds
).recordNanoseconds(DispatchTime.now().uptimeNanoseconds - startTime)
return response
}.flatMapErrorThrowing { error in
Counter(label: "aws_request_errors", dimensions: dimensions).increment()
throw error
}
}
}

protocol QueryEncodableArray {
var queryEncoded: [String] { get }
}
Expand Down
6 changes: 5 additions & 1 deletion Sources/AWSSDKSwiftCore/ServiceConfig.swift
Expand Up @@ -16,10 +16,13 @@ import class Foundation.ProcessInfo

/// Configuration class defining an AWS service
public struct ServiceConfig {

/// Region where service is running
public let region: Region
/// The destination service of the request. Added as a header value, along with the operation name
public let amzTarget: String?
/// Name of service
public let service: String
/// Name used to sign requests
public let signingName: String
/// Protocol used by service json/xml/query
Expand All @@ -32,7 +35,7 @@ public struct ServiceConfig {
public let possibleErrorTypes: [AWSErrorType.Type]
/// Middleware code specific to the service used to edit requests before they sent and responses before they are decoded
public let middlewares: [AWSServiceMiddleware]

/// Create a ServiceConfig object
///
/// - Parameters:
Expand Down Expand Up @@ -75,6 +78,7 @@ public struct ServiceConfig {
self.region = .useast1
}

self.service = service
self.apiVersion = apiVersion
self.signingName = signingName ?? service
self.amzTarget = amzTarget
Expand Down