From 68d481d956a42548f6bd4689981b6ef9b0bab1ec Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Thu, 15 Nov 2018 15:09:27 +0100 Subject: [PATCH] feat(aws-elasticloadbalancingv2): add metrics (#1173) Add `metricsXxx()` methods to construct Metric objects for the metrics exposed by Application and Network Load Balancers. Fixes #853. --- .gitignore | 1 + CONTRIBUTING.md | 102 ++++- .../lib/alb/application-load-balancer.ts | 356 ++++++++++++++++++ .../lib/alb/application-target-group.ts | 141 +++++++ .../lib/nlb/network-load-balancer.ts | 131 +++++++ .../lib/shared/base-target-group.ts | 21 ++ .../aws-elasticloadbalancingv2/package.json | 2 + .../test/alb/test.listener.ts | 38 ++ .../test/alb/test.load-balancer.ts | 43 ++- scripts/clean-stale-files.sh | 21 ++ 10 files changed, 841 insertions(+), 15 deletions(-) create mode 100755 scripts/clean-stale-files.sh diff --git a/.gitignore b/.gitignore index 5a03c2b774932..b969fc3b38cb9 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ coverage .nyc_output .LAST_BUILD *.swp +tsconfig.json diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f4d94591854be..c613b8348a346 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,19 +19,19 @@ your contributions. Please read it carefully and let us know if it's not up-to-d into a single commit, so you can use that to frame your scope. 1. Create a commit with the proposed change changes: * Commit title and message (and PR title and description) must adhere to [conventionalcommits]. - * The title must begin with `feat(module): title`, `fix(module): title`, + * The title must begin with `feat(module): title`, `fix(module): title`, `reactor(module): title` or `chore(module): title`. * Title should be lowercase. * No period at the end of the title. - * Commit message should describe _motivation_. Think about your code reviewers and what - information they need in order to understand what you did. If it's a big commit (hopefully not), + * Commit message should describe _motivation_. Think about your code reviewers and what + information they need in order to understand what you did. If it's a big commit (hopefully not), try to provide some good entry points so it will be easier to follow. * Commit message should indicate which issues are fixed: `fixes #` or `closes #`. * Shout out to collaborators. * If not obvious (i.e. from unit tests), describe how you verified that your change works. * If this commit includes a breaking change, the commit message must end with a - a single pragraph + a single pragraph `BREAKING CHANGE: description of what broke and how to achieve this beahvior now`. 2. Push to a fork or to a branch (naming convention: `/`) 3. Submit a Pull Requests on GitHub and assign the PR for a review to the "awslabs/aws-cdk" team. @@ -50,7 +50,7 @@ your contributions. Please read it carefully and let us know if it's not up-to-d ## Tools The CDK is a big project, and, at the moment, all of the CDK modules are mastered in a single monolithic -repository (uses [lerna](https://github.com/lerna/lerna)). There are pros and cons to this approach, +repository (uses [lerna](https://github.com/lerna/lerna)). There are pros and cons to this approach, and it's especially valuable to maintain integrity in the early stage of thr project where things constantly change across the stack. In the future we believe many of these modules will be extracted to their own repositories. Another complexity is that the CDK is packaged using [jsii](https://github.com/awslabs/jsii) to multiple @@ -58,7 +58,13 @@ programming languages. This means that when a full build is complete, there will module for each supported language. However, in many cases, you can probably get away with just building a portion of the -project, based on areas that you want to work on. +project, based on areas that you want to work on. + +We recommend that you use [Visual Studio Code](https://code.visualstudio.com/) to work on the CDK. +Be sure to install the [tslint extension](https://marketplace.visualstudio.com/items?itemName=eg2.tslint) +for it as well, since we have strict linting rules that will prevent your code from compiling, +but with VSCode and this extension can be automatically fixed for you by hitting `Ctrl-.` when +your cursor is on a red underline. ### Main build scripts @@ -66,16 +72,16 @@ The build process is divided into stages, so you can invoke them as needed: - __`install.sh`__: installs all external dependencies and symlinks internal dependencies (using `lerna link`). - __`build.sh`__: runs `npm build` and `npm test` in all modules (in topological order). -- __`pack.sh`__: packages all modules to all supported languages and produces a `dist/` directory - with all the outputs (running this script requires that you installed the [toolchains](#Toolchains) +- __`pack.sh`__: packages all modules to all supported languages and produces a `dist/` directory + with all the outputs (running this script requires that you installed the [toolchains](#Toolchains) for all target languages on your system). ### Partial build tools There are also two useful scripts in the `scripts` directory that can help you build part of the repo: -- __`scripts/buildup`__: builds the current module and all of it's dependencies (in topological order). -- __`scripts/builddown`__: builds the current module and all of it's consumers (in topological order). +- __`scripts/buildup`__: builds the current module and all of its dependencies (in topological order). +- __`scripts/builddown`__: builds the current module and all of its consumers (in topological order). ### Useful aliases @@ -95,7 +101,7 @@ alias lw='lr watch' ### pkglint -The `pkglint` tool "lints" package.json files across the repo according +The `pkglint` tool "lints" package.json files across the repo according to [rules.ts](tools/pkglint/lib/rules.ts). To evaluate (and attempt to fix) all package linting issues in the repo, run the following command from the root @@ -131,7 +137,7 @@ $ ./install.sh $ ./build.sh ``` -If you also wish to package to all languages, make sure you have all the [toolchains](#Toolchains] +If you also wish to package to all languages, make sure you have all the [toolchains](#Toolchains] and now run: ``` @@ -140,7 +146,7 @@ $ ./pack.sh ### Partial build -In many cases, you don't really need to build the entire project. Say you want to work +In many cases, you don't really need to build the entire project. Say you want to work on the `@aws-cdk/aws-ec2` module: ```console @@ -175,7 +181,7 @@ $ nodeunit test/test.*.js ### Build Documentation Only -The CDK documentation source is hosted under [`./docs/src`](./docs/src). +The CDK documentation source is hosted under [`./docs/src`](./docs/src). Module reference documentation is gathered after build from the `dist/sphinx` directory (generated by jsii from source via the `./pack.sh` script). To build the docs even if reference docs are not present: @@ -239,6 +245,74 @@ We use `npm update` to 5. Now, run `./build.sh` again to verify all tests pass. 6. Submit a Pull Request. +### Troubleshooting common issues + +Most build issues can be solved by doing a full clean rebuild: + +```shell +$ git clean -fqdx . +$ ./build.sh +``` + +However, this will be time consuming. In this section we'll describe some common issues you may encounter and some more +targeted commands you can run to resolve your issue. + +* The compiler is throwing errors on files that I renamed/it's running old tests that I meant to remove/code coverage is + low and I didn't change anything. + +If you switch to a branch in which `.ts` files got renamed or deleted, the generated `.js` and `.d.ts` files from the +previous compilation run are still around and may in some cases still be picked up by the compiler or test runners. + +Run the following to clear out stale build artifacts: + +```shell +$ scripts/clean-stale-files.sh +``` + +* I added a dependency but it's not being picked up by the build + +You need to tell Lerna to update all dependencies: + +```shell +$ node_modules/.bin/lerna bootstrap +``` + +* I added a dependency but it's not being picked up by a `watch` background compilation run. + +No it's not. After re-bootstrapping you need to restart the watch command. + +* I added a dependency but it's not being picked up by Visual Studio Code (I still get red underlines). + +The TypeScript compiler that's running has cached your dependency tree. After re-bootstrapping, +restart the TypeScript compiler. + +Hit F1, type `> TypeScript: Restart TS Server`. + +* I'm doing refactorings between packages and compile times are killing me/I need to switch between + differently-verionsed branches a lot and rebuilds because of version errors are taking too long. + +Our build steps for each package do a couple of things, such as generating code and generating +JSII assemblies. If you've done a full build at least once to generate all source files, you can +do a quicker TypeScript-only rebuild of the entire source tree by doing the following: + +```shell +# Only works after at least one full build to generate source files +$ scripts/build-typescript.sh + +# Also works to start a project-wide watch compile +$ scripts/build-typescript.sh -w +``` + +This does not do code generation and it does not do JSII checks and JSII assembly generation. Instead of doing a +package-by-package ordered build, it compiles all `.ts` files in the repository all at once. This takes about the same +time as it does to compile the biggest package all by itself, and on my machine is the difference between a 15 +CPU-minute build and a 20 CPU-second build. If you use this methods of recompiling and you want to run the test, you +have to disable the built-in rebuild functionality of `lerna run test`: + +```shell +$ CDK_TEST_BUILD=false lr test +``` + ## Toolchains If you wish to use `pack.sh` to package the project to all supported languages, you will need the diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts index 357551789ff7d..8fe575d0bcc9e 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts @@ -1,3 +1,4 @@ +import cloudwatch = require('@aws-cdk/aws-cloudwatch'); import ec2 = require('@aws-cdk/aws-ec2'); import iam = require('@aws-cdk/aws-iam'); import s3 = require('@aws-cdk/aws-s3'); @@ -115,6 +116,361 @@ export class ApplicationLoadBalancer extends BaseLoadBalancer implements IApplic securityGroupId: this.securityGroup.export().securityGroupId, }; } + + /** + * Return the given named metric for this Application Load Balancer + * + * @default Average over 5 minutes + */ + public metric(metricName: string, props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + return new cloudwatch.Metric({ + namespace: 'AWS/ApplicationELB', + metricName, + dimensions: { LoadBalancer: this.fullName }, + ...props + }); + } + + /** + * The total number of concurrent TCP connections active from clients to the + * load balancer and from the load balancer to targets. + * + * @default Sum over 5 minutes + */ + public metricActiveConnectionCount(props?: cloudwatch.MetricCustomization) { + return this.metric('ActiveConnectionCount', { + statistic: 'sum', + ...props + }); + } + + /** + * The number of TLS connections initiated by the client that did not + * establish a session with the load balancer. Possible causes include a + * mismatch of ciphers or protocols. + * + * @default Sum over 5 minutes + */ + public metricClientTlsNegotiationErrorCount(props?: cloudwatch.MetricCustomization) { + return this.metric('ClientTLSNegotiationErrorCount', { + statistic: 'sum', + ...props + }); + } + + /** + * The number of load balancer capacity units (LCU) used by your load balancer. + * + * @default Sum over 5 minutes + */ + public metricConsumedLCUs(props?: cloudwatch.MetricCustomization) { + return this.metric('ConsumedLCUs', { + statistic: 'sum', + ...props + }); + } + + /** + * The number of fixed-response actions that were successful. + * + * @default Sum over 5 minutes + */ + public metricHttpFixedResponseCount(props?: cloudwatch.MetricCustomization) { + return this.metric('HTTP_Fixed_Response_Count', { + statistic: 'Sum', + ...props + }); + } + + /** + * The number of redirect actions that were successful. + * + * @default Sum over 5 minutes + */ + public metricHttpRedirectCount(props?: cloudwatch.MetricCustomization) { + return this.metric('HTTP_Redirect_Count', { + statistic: 'Sum', + ...props + }); + } + + /** + * The number of redirect actions that couldn't be completed because the URL + * in the response location header is larger than 8K. + * + * @default Sum over 5 minutes + */ + public metricHttpRedirectUrlLimitExceededCount(props?: cloudwatch.MetricCustomization) { + return this.metric('HTTP_Redirect_Url_Limit_Exceeded_Count', { + statistic: 'Sum', + ...props + }); + } + + /** + * The number of HTTP 3xx/4xx/5xx codes that originate from the load balancer. + * + * This does not include any response codes generated by the targets. + * + * @default Sum over 5 minutes + */ + public metricHttpCodeElb(code: HttpCodeElb, props?: cloudwatch.MetricCustomization) { + return this.metric(code, { + statistic: 'Sum', + ...props + }); + } + + /** + * The number of HTTP 2xx/3xx/4xx/5xx response codes generated by all targets + * in the load balancer. + * + * This does not include any response codes generated by the load balancer. + * + * @default Sum over 5 minutes + */ + public metricHttpCodeTarget(code: HttpCodeTarget, props?: cloudwatch.MetricCustomization) { + return this.metric(code, { + statistic: 'Sum', + ...props + }); + } + + /** + * The total number of bytes processed by the load balancer over IPv6. + * + * @default Sum over 5 minutes + */ + public metricIPv6ProcessedBytes(props?: cloudwatch.MetricCustomization) { + return this.metric('IPv6ProcessedBytes', { + statistic: 'Sum', + ...props + }); + } + + /** + * The number of IPv6 requests received by the load balancer. + * + * @default Sum over 5 minutes + */ + public metricIPv6RequestCount(props?: cloudwatch.MetricCustomization) { + return this.metric('IPv6RequestCount', { + statistic: 'Sum', + ...props + }); + } + + /** + * The total number of new TCP connections established from clients to the + * load balancer and from the load balancer to targets. + * + * @default Sum over 5 minutes + */ + public metricNewConnectionCount(props?: cloudwatch.MetricCustomization) { + return this.metric('NewConnectionCount', { + statistic: 'Sum', + ...props + }); + } + + /** + * The total number of bytes processed by the load balancer over IPv4 and IPv6. + * + * @default Sum over 5 minutes + */ + public metricProcessedBytes(props?: cloudwatch.MetricCustomization) { + return this.metric('ProcessedBytes', { + statistic: 'Sum', + ...props + }); + } + + /** + * The number of connections that were rejected because the load balancer had + * reached its maximum number of connections. + * + * @default Sum over 5 minutes + */ + public metricRejectedConnectionCount(props?: cloudwatch.MetricCustomization) { + return this.metric('RejectedConnectionCount', { + statistic: 'Sum', + ...props + }); + } + + /** + * The number of requests processed over IPv4 and IPv6. + * + * This count includes only the requests with a response generated by a target of the load balancer. + * + * @default Sum over 5 minutes + */ + public metricRequestCount(props?: cloudwatch.MetricCustomization) { + return this.metric('RequestCount', { + statistic: 'Sum', + ...props + }); + } + + /** + * The number of rules processed by the load balancer given a request rate averaged over an hour. + * + * @default Sum over 5 minutes + */ + public metricRuleEvaluations(props?: cloudwatch.MetricCustomization) { + return this.metric('RuleEvaluations', { + statistic: 'Sum', + ...props + }); + } + + /** + * The number of connections that were not successfully established between the load balancer and target. + * + * @default Sum over 5 minutes + */ + public metricTargetConnectionErrorCount(props?: cloudwatch.MetricCustomization) { + return this.metric('TargetConnectionErrorCount', { + statistic: 'Sum', + ...props + }); + } + + /** + * The time elapsed, in seconds, after the request leaves the load balancer until a response from the target is received. + * + * @default Average over 5 minutes + */ + public metricTargetResponseTime(props?: cloudwatch.MetricCustomization) { + return this.metric('TargetResponseTime', { + statistic: 'Average', + ...props + }); + } + + /** + * The number of TLS connections initiated by the load balancer that did not establish a session with the target. + * + * Possible causes include a mismatch of ciphers or protocols. + * + * @default Sum over 5 minutes + */ + public metricTargetTLSNegotiationErrorCount(props?: cloudwatch.MetricCustomization) { + return this.metric('TargetTLSNegotiationErrorCount', { + statistic: 'Sum', + ...props + }); + } + + /** + * The number of user authentications that could not be completed + * + * Because an authenticate action was misconfigured, the load balancer + * couldn't establish a connection with the IdP, or the load balancer + * couldn't complete the authentication flow due to an internal error. + * + * @default Sum over 5 minutes + */ + public metricElbAuthError(props?: cloudwatch.MetricCustomization) { + return this.metric('ELBAuthError', { + statistic: 'Sum', + ...props + }); + } + + /** + * The number of user authentications that could not be completed because the + * IdP denied access to the user or an authorization code was used more than + * once. + * + * @default Sum over 5 minutes + */ + public metricElbAuthFailure(props?: cloudwatch.MetricCustomization) { + return this.metric('ELBAuthFailure', { + statistic: 'Sum', + ...props + }); + } + + /** + * The time elapsed, in milliseconds, to query the IdP for the ID token and user info. + * + * If one or more of these operations fail, this is the time to failure. + * + * @default Average over 5 minutes + */ + public metricElbAuthLatency(props?: cloudwatch.MetricCustomization) { + return this.metric('ELBAuthLatency', { + statistic: 'Average', + ...props + }); + } + + /** + * The number of authenticate actions that were successful. + * + * This metric is incremented at the end of the authentication workflow, + * after the load balancer has retrieved the user claims from the IdP. + * + * @default Sum over 5 minutes + */ + public metricElbAuthSuccess(props?: cloudwatch.MetricCustomization) { + return this.metric('ELBAuthSuccess', { + statistic: 'Sum', + ...props + }); + } +} + +/** + * Count of HTTP status originating from the load balancer + * + * This count does not include any response codes generated by the targets. + */ +export enum HttpCodeElb { + /** + * The number of HTTP 3XX redirection codes that originate from the load balancer. + */ + Elb3xxCount = 'HTTPCode_ELB_3XX_Count', + + /** + * The number of HTTP 4XX client error codes that originate from the load balancer. + * + * Client errors are generated when requests are malformed or incomplete. + * These requests have not been received by the target. This count does not + * include any response codes generated by the targets. + */ + Elb4xxCount = 'HTTPCode_ELB_4XX_Count', + + /** + * The number of HTTP 5XX server error codes that originate from the load balancer. + */ + Elb5xxCount = 'HTTPCode_ELB_5XX_Count', +} + +/** + * Count of HTTP status originating from the targets + */ +export enum HttpCodeTarget { + /** + * The number of 2xx response codes from targets + */ + Target2xxCount = 'HTTPCode_Target_2XX_Count', + + /** + * The number of 3xx response codes from targets + */ + Target3xxCount = 'HTTPCode_Target_3XX_Count', + + /** + * The number of 4xx response codes from targets + */ + Target4xxCount = 'HTTPCode_Target_4XX_Count', + + /** + * The number of 5xx response codes from targets + */ + Target5xxCount = 'HTTPCode_Target_5XX_Count' } /** diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts index c0e419f83653b..7969c81ea1a72 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts @@ -1,3 +1,4 @@ +import cloudwatch = require('@aws-cdk/aws-cloudwatch'); import ec2 = require('@aws-cdk/aws-ec2'); import cdk = require('@aws-cdk/cdk'); import { BaseTargetGroup, BaseTargetGroupProps, ITargetGroup, LoadBalancerTargetProps, TargetGroupRefProps } from '../shared/base-target-group'; @@ -5,6 +6,7 @@ import { ApplicationProtocol } from '../shared/enums'; import { BaseImportedTargetGroup } from '../shared/imported'; import { determineProtocolAndPort, LazyDependable } from '../shared/util'; import { IApplicationListener } from './application-listener'; +import { HttpCodeTarget } from './application-load-balancer'; /** * Properties for defining an Application Target Group @@ -146,6 +148,145 @@ export class ApplicationTargetGroup extends BaseTargetGroup { this.listeners.push(listener); this.loadBalancerAssociationDependencies.push(dependable || listener); } + + /** + * Return the given named metric for this Application Load Balancer Target Group + * + * Returns the metric for this target group from the point of view of the first + * load balancer load balancing to it. If you have multiple load balancers load + * sending traffic to the same target group, you will have to override the dimensions + * on this metric. + * + * @default Average over 5 minutes + */ + public metric(metricName: string, props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + return new cloudwatch.Metric({ + namespace: 'AWS/ApplicationELB', + metricName, + dimensions: { + TargetGroup: this.targetGroupFullName, + LoadBalancer: this.firstLoadBalancerFullName, + }, + ...props + }); + } + + /** + * The number of IPv6 requests received by the target group + * + * @default Sum over 5 minutes + */ + public metricIPv6RequestCount(props?: cloudwatch.MetricCustomization) { + return this.metric('IPv6RequestCount', { + statistic: 'Sum', + ...props + }); + } + + /** + * The number of requests processed over IPv4 and IPv6. + * + * This count includes only the requests with a response generated by a target of the load balancer. + * + * @default Sum over 5 minutes + */ + public metricRequestCount(props?: cloudwatch.MetricCustomization) { + return this.metric('RequestCount', { + statistic: 'Sum', + ...props + }); + } + + /** + * The number of healthy hosts in the target group + * + * @default Average over 5 minutes + */ + public metricHealthyHostCount(props?: cloudwatch.MetricCustomization) { + return this.metric('HealthyHostCount', { + statistic: 'Average', + ...props + }); + } + + /** + * The number of unhealthy hosts in the target group + * + * @default Average over 5 minutes + */ + public metricUnhealthyHostCount(props?: cloudwatch.MetricCustomization) { + return this.metric('UnhealthyHostCount', { + statistic: 'Average', + ...props + }); + } + + /** + * The number of HTTP 2xx/3xx/4xx/5xx response codes generated by all targets in this target group. + * + * This does not include any response codes generated by the load balancer. + * + * @default Sum over 5 minutes + */ + public metricHttpCodeTarget(code: HttpCodeTarget, props?: cloudwatch.MetricCustomization) { + return this.metric(code, { + statistic: 'Sum', + ...props + }); + } + + /** + * The average number of requests received by each target in a target group. + * + * The only valid statistic is Sum. Note that this represents the average not the sum. + * + * @default Sum over 5 minutes + */ + public metricRequestCountPerTarget(props?: cloudwatch.MetricCustomization) { + return this.metric('RequestCountPerTarget', { + statistic: 'Sum', + ...props + }); + } + + /** + * The number of connections that were not successfully established between the load balancer and target. + * + * @default Sum over 5 minutes + */ + public metricTargetConnectionErrorCount(props?: cloudwatch.MetricCustomization) { + return this.metric('TargetConnectionErrorCount', { + statistic: 'Sum', + ...props + }); + } + + /** + * The time elapsed, in seconds, after the request leaves the load balancer until a response from the target is received. + * + * @default Average over 5 minutes + */ + public metricTargetResponseTime(props?: cloudwatch.MetricCustomization) { + return this.metric('TargetResponseTime', { + statistic: 'Average', + ...props + }); + } + + /** + * The number of TLS connections initiated by the load balancer that did not establish a session with the target. + * + * Possible causes include a mismatch of ciphers or protocols. + * + * @default Sum over 5 minutes + */ + public metricTargetTLSNegotiationErrorCount(props?: cloudwatch.MetricCustomization) { + return this.metric('TargetTLSNegotiationErrorCount', { + statistic: 'Sum', + ...props + }); + } + } /** diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-load-balancer.ts index f42792bcb7264..17ff56bd4af91 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-load-balancer.ts @@ -1,3 +1,4 @@ +import cloudwatch = require('@aws-cdk/aws-cloudwatch'); import ec2 = require('@aws-cdk/aws-ec2'); import cdk = require('@aws-cdk/cdk'); import { BaseLoadBalancer, BaseLoadBalancerProps } from '../shared/base-load-balancer'; @@ -51,6 +52,136 @@ export class NetworkLoadBalancer extends BaseLoadBalancer implements INetworkLoa loadBalancerArn: new cdk.Output(this, 'LoadBalancerArn', { value: this.loadBalancerArn }).makeImportValue().toString() }; } + + /** + * Return the given named metric for this Network Load Balancer + * + * @default Average over 5 minutes + */ + public metric(metricName: string, props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + return new cloudwatch.Metric({ + namespace: 'AWS/NetworkELB', + metricName, + dimensions: { LoadBalancer: this.fullName }, + ...props + }); + } + + /** + * The total number of concurrent TCP flows (or connections) from clients to targets. + * + * This metric includes connections in the SYN_SENT and ESTABLISHED states. + * TCP connections are not terminated at the load balancer, so a client + * opening a TCP connection to a target counts as a single flow. + * + * @default Average over 5 minutes + */ + public metricActiveFlowCount(props?: cloudwatch.MetricCustomization) { + return this.metric('ActiveFlowCount', { + statistic: 'Average', + ...props + }); + } + + /** + * The number of load balancer capacity units (LCU) used by your load balancer. + * + * @default Sum over 5 minutes + */ + public metricConsumedLCUs(props?: cloudwatch.MetricCustomization) { + return this.metric('ConsumedLCUs', { + statistic: 'Sum', + ...props + }); + } + + /** + * The number of targets that are considered healthy. + * + * @default Average over 5 minutes + */ + public metricHealthyHostCount(props?: cloudwatch.MetricCustomization) { + return this.metric('HealthyHostCount', { + statistic: 'Average', + ...props + }); + } + + /** + * The number of targets that are considered unhealthy. + * + * @default Average over 5 minutes + */ + public metricUnHealthyHostCount(props?: cloudwatch.MetricCustomization) { + return this.metric('UnHealthyHostCount', { + statistic: 'Average', + ...props + }); + } + + /** + * The total number of new TCP flows (or connections) established from clients to targets in the time period. + * + * @default Sum over 5 minutes + */ + public metricNewFlowCount(props?: cloudwatch.MetricCustomization) { + return this.metric('NewFlowCount', { + statistic: 'Sum', + ...props + }); + } + + /** + * The total number of bytes processed by the load balancer, including TCP/IP headers. + * + * @default Sum over 5 minutes + */ + public metricProcessedBytes(props?: cloudwatch.MetricCustomization) { + return this.metric('ProcessedBytes', { + statistic: 'Sum', + ...props + }); + } + + /** + * The total number of reset (RST) packets sent from a client to a target. + * + * These resets are generated by the client and forwarded by the load balancer. + * + * @default Sum over 5 minutes + */ + public metricTcpClientResetCount(props?: cloudwatch.MetricCustomization) { + return this.metric('TCP_Client_Reset_Count', { + statistic: 'Sum', + ...props + }); + } + + /** + * The total number of reset (RST) packets generated by the load balancer. + * + * @default Sum over 5 minutes + */ + public metricTcpElbResetCount(props?: cloudwatch.MetricCustomization) { + return this.metric('TCP_ELB_Reset_Count', { + statistic: 'Sum', + ...props + }); + } + + /** + * The total number of reset (RST) packets sent from a target to a client. + * + * These resets are generated by the target and forwarded by the load balancer. + * + * @default Sum over 5 minutes + */ + public metricTcpTargetResetCount(props?: cloudwatch.MetricCustomization) { + return this.metric('TCP_Target_Reset_Count', { + statistic: 'Sum', + ...props + }); + } } /** diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts index 81d88262c283b..ab65597ad267b 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts @@ -147,6 +147,21 @@ export abstract class BaseTargetGroup extends cdk.Construct implements ITargetGr */ public readonly targetGroupName: string; + /** + * ARNs of load balancers load balancing to this TargetGroup + */ + public readonly targetGroupLoadBalancerArns: string[]; + + /** + * Full name of first load balancer + * + * This identifier is emitted as a dimensions of the metrics of this target + * group. + * + * @example app/my-load-balancer/123456789 + */ + public readonly firstLoadBalancerFullName: string; + /** * Health check for the members of this target group */ @@ -214,10 +229,16 @@ export abstract class BaseTargetGroup extends cdk.Construct implements ITargetGr ...additionalProps }); + this.targetGroupLoadBalancerArns = this.resource.targetGroupLoadBalancerArns.toList(); this.targetGroupArn = this.resource.ref; this.targetGroupFullName = this.resource.targetGroupFullName; this.targetGroupName = this.resource.targetGroupName; this.defaultPort = `${additionalProps.port}`; + + const firstLoadBalancerArn = new cdk.FnSelect(0, this.targetGroupLoadBalancerArns); + // arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-internal-load-balancer/50dc6c495c0c9188 + const arnParts = new cdk.FnSplit('/', firstLoadBalancerArn); + this.firstLoadBalancerFullName = `${new cdk.FnSelect(1, arnParts)}/${new cdk.FnSelect(2, arnParts)}/${new cdk.FnSelect(3, arnParts)}`; } /** diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json b/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json index 38b0f4e8019bd..053c6bbc7d4f4 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json @@ -59,6 +59,7 @@ "pkglint": "^0.17.0" }, "dependencies": { + "@aws-cdk/aws-cloudwatch": "^0.17.0", "@aws-cdk/aws-codedeploy-api": "^0.17.0", "@aws-cdk/aws-ec2": "^0.17.0", "@aws-cdk/aws-iam": "^0.17.0", @@ -68,6 +69,7 @@ }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { + "@aws-cdk/aws-cloudwatch": "^0.17.0", "@aws-cdk/aws-codedeploy-api": "^0.17.0", "@aws-cdk/aws-ec2": "^0.17.0", "@aws-cdk/aws-s3": "^0.17.0", diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.listener.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.listener.ts index 2f42dd58206b0..b7881e4c31110 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.listener.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.listener.ts @@ -385,6 +385,44 @@ export = { test.done(); }, + 'Exercise metrics'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'VPC'); + const group = new elbv2.ApplicationTargetGroup(stack, 'TargetGroup', { vpc, port: 80 }); + + // WHEN + const metrics = []; + metrics.push(group.metricHttpCodeTarget(elbv2.HttpCodeTarget.Target3xxCount)); + metrics.push(group.metricIPv6RequestCount()); + metrics.push(group.metricUnhealthyHostCount()); + metrics.push(group.metricUnhealthyHostCount()); + metrics.push(group.metricRequestCount()); + metrics.push(group.metricTargetConnectionErrorCount()); + metrics.push(group.metricTargetResponseTime()); + metrics.push(group.metricTargetTLSNegotiationErrorCount()); + + for (const metric of metrics) { + test.equal('AWS/ApplicationELB', metric.namespace); + const firstArn = { "Fn::Select": [0, { "Fn::GetAtt": ["TargetGroup3D7CD9B8", "LoadBalancerArns"] }] }; + test.deepEqual(cdk.resolve(metric.dimensions), { + TargetGroup: { 'Fn::GetAtt': [ 'TargetGroup3D7CD9B8', 'TargetGroupFullName' ] }, + LoadBalancer: { 'Fn::Join': + [ '', + [ { 'Fn::Select': [ 1, { 'Fn::Split': [ '/', firstArn ] } ] }, + '/', + { 'Fn::Select': [ 2, { 'Fn::Split': [ '/', firstArn ] } ] }, + '/', + { 'Fn::Select': [ 3, { 'Fn::Split': [ '/', firstArn ] } ] } + ] + ] + } + }); + } + + test.done(); + }, + 'Can add dependency on ListenerRule via TargetGroup'(test: Test) { // GIVEN const stack = new cdk.Stack(); diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.load-balancer.ts index d1a4de47b3f4d..cbad96ac9e4af 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.load-balancer.ts @@ -123,5 +123,46 @@ export = { })); test.done(); - } + }, + + 'Exercise metrics'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'Stack'); + const lb = new elbv2.ApplicationLoadBalancer(stack, 'LB', { vpc }); + + // WHEN + const metrics = []; + metrics.push(lb.metricActiveConnectionCount()); + metrics.push(lb.metricClientTlsNegotiationErrorCount()); + metrics.push(lb.metricConsumedLCUs()); + metrics.push(lb.metricElbAuthError()); + metrics.push(lb.metricElbAuthFailure()); + metrics.push(lb.metricElbAuthLatency()); + metrics.push(lb.metricElbAuthSuccess()); + metrics.push(lb.metricHttpCodeElb(elbv2.HttpCodeElb.Elb3xxCount)); + metrics.push(lb.metricHttpCodeTarget(elbv2.HttpCodeTarget.Target3xxCount)); + metrics.push(lb.metricHttpFixedResponseCount()); + metrics.push(lb.metricHttpRedirectCount()); + metrics.push(lb.metricHttpRedirectUrlLimitExceededCount()); + metrics.push(lb.metricIPv6ProcessedBytes()); + metrics.push(lb.metricIPv6RequestCount()); + metrics.push(lb.metricNewConnectionCount()); + metrics.push(lb.metricProcessedBytes()); + metrics.push(lb.metricRejectedConnectionCount()); + metrics.push(lb.metricRequestCount()); + metrics.push(lb.metricRuleEvaluations()); + metrics.push(lb.metricTargetConnectionErrorCount()); + metrics.push(lb.metricTargetResponseTime()); + metrics.push(lb.metricTargetTLSNegotiationErrorCount()); + + for (const metric of metrics) { + test.equal('AWS/ApplicationELB', metric.namespace); + test.deepEqual(cdk.resolve(metric.dimensions), { + LoadBalancer: { 'Fn::GetAtt': ['LB8A12904C', 'LoadBalancerFullName'] } + }); + } + + test.done(); + }, }; diff --git a/scripts/clean-stale-files.sh b/scripts/clean-stale-files.sh new file mode 100755 index 0000000000000..75242401eb0d6 --- /dev/null +++ b/scripts/clean-stale-files.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# Script to clean stale .js and .d.ts files (that don't have a corresponding .ts +# file). + +for filename in $(find . \ + -not \( -name node_modules -prune \) \ + -not \( -name coverage -prune \) \ + -type f \ + -name \*.js -o -name \*.d.ts | git check-ignore --stdin); do + + if [[ $filename == *.d.ts ]]; then + source=${filename%.d.ts}.ts + else + source=${filename%.*}.ts + fi + + if [[ ! -f $source ]]; then + rm $filename + echo Removed $filename + fi +done