master e67b8bb19073 cached
38 files
106.6 KB
26.8k tokens
80 symbols
1 requests
Download .txt
Repository: channl/dynamodb-lambda-autoscale
Branch: master
Commit: e67b8bb19073
Files: 38
Total size: 106.6 KB

Directory structure:
gitextract_feix3qd4/

├── .babelrc
├── .eslintrc
├── .flowconfig
├── .gitignore
├── LICENSE
├── README.md
├── flow/
│   ├── aws-sdk.js
│   ├── invariant.js
│   ├── measured.js
│   └── warning.js
├── gulpfile.js
├── make-webpack-config.js
├── package.json
├── scripts/
│   └── start.js
├── src/
│   ├── App.js
│   ├── CapacityCalculator.js
│   ├── Global.js
│   ├── Index.js
│   ├── Provisioner.js
│   ├── aws/
│   │   ├── CloudWatch.js
│   │   └── DynamoDB.js
│   ├── capacity/
│   │   └── CapacityCalculatorBase.js
│   ├── configuration/
│   │   ├── ClimbingProvisioner.json
│   │   ├── DefaultProvisioner.json
│   │   ├── FixedProvisioner.json
│   │   └── Region.json
│   ├── flow/
│   │   └── FlowTypes.js
│   ├── provisioning/
│   │   ├── ProvisionerBase.js
│   │   ├── ProvisionerConfigurableBase.js
│   │   └── ProvisionerLogging.js
│   └── utils/
│       ├── CostEstimation.js
│       ├── Delay.js
│       ├── RateLimitedDecrement.js
│       ├── Stats.js
│       └── Throughput.js
├── webpack-dev.config.js
├── webpack-prod.config.js
└── webpack.config.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .babelrc
================================================
{
  "presets": ["react-native"]
}


================================================
FILE: .eslintrc
================================================
{
  "parser": "babel-eslint",

  "plugins": [
    "babel"
  ],

  "env": {
    "es6": true,
    "node": true
  },

  "ecmaFeatures": {
    "arrowFunctions": true,
    "binaryLiterals": true,
    "blockBindings": true,
    "classes": true,
    "defaultParams": true,
    "destructuring": true,
    "experimentalObjectRestSpread": true,
    "forOf": true,
    "generators": true,
    "globalReturn": true,
    "jsx": true,
    "modules": true,
    "objectLiteralComputedProperties": true,
    "objectLiteralDuplicateProperties": true,
    "objectLiteralShorthandMethods": true,
    "objectLiteralShorthandProperties": true,
    "octalLiterals": true,
    "regexUFlag": true,
    "regexYFlag": true,
    "restParams": true,
    "spread": true,
    "superInFunctions": true,
    "templateStrings": true,
    "unicodeCodePointEscapes": true
  },

  "rules": {
    "babel/arrow-parens": [2, "as-needed"],

    "array-bracket-spacing": [2, "always"],
    "arrow-spacing": 2,
    "block-scoped-var": 0,
    "brace-style": [2, "1tbs", {"allowSingleLine": true}],
    "callback-return": 2,
    "camelcase": [2, {"properties": "always"}],
    "comma-dangle": 0,
    "comma-spacing": 0,
    "comma-style": [2, "last"],
    "complexity": 0,
    "computed-property-spacing": [2, "never"],
    "consistent-return": 0,
    "consistent-this": 0,
    "curly": [2, "all"],
    "default-case": 0,
    "dot-location": [2, "property"],
    "dot-notation": 0,
    "eol-last": 2,
    "eqeqeq": [2, "allow-null"],
    "func-names": 0,
    "func-style": 0,
    "generator-star-spacing": [0, {"before": true, "after": false}],
    "guard-for-in": 2,
    "handle-callback-err": [2, "error"],
    "id-length": 0,
    "id-match": [2, "^(?:_?[a-zA-Z0-9]*)|[_A-Z0-9]+$"],
    "indent": [2, 2, {"SwitchCase": 1}],
    "init-declarations": 0,
    "key-spacing": [2, {"beforeColon": false, "afterColon": true}],
    "linebreak-style": 2,
    "lines-around-comment": 0,
    "max-depth": 0,
    "max-len": [2, 100, 4],
    "max-nested-callbacks": 0,
    "max-params": 0,
    "max-statements": 0,
    "new-cap": 0,
    "new-parens": 2,
    "newline-after-var": 0,
    "no-alert": 2,
    "no-array-constructor": 2,
    "no-bitwise": 0,
    "no-caller": 2,
    "no-catch-shadow": 0,
    "no-class-assign": 2,
    "no-cond-assign": 2,
    "no-console": 1,
    "no-const-assign": 2,
    "no-constant-condition": 2,
    "no-continue": 0,
    "no-control-regex": 0,
    "no-debugger": 1,
    "no-delete-var": 2,
    "no-div-regex": 2,
    "no-dupe-args": 2,
    "no-dupe-keys": 2,
    "no-duplicate-case": 2,
    "no-else-return": 2,
    "no-empty": 2,
    "no-empty-character-class": 2,
//    "no-empty-label": 2,
    "no-eq-null": 0,
    "no-eval": 2,
    "no-ex-assign": 2,
    "no-extend-native": 2,
    "no-extra-bind": 2,
    "no-extra-boolean-cast": 2,
    "no-extra-parens": 0,
    "no-extra-semi": 2,
    "no-fallthrough": 2,
    "no-floating-decimal": 2,
    "no-func-assign": 2,
    "no-implicit-coercion": 2,
    "no-implied-eval": 2,
    "no-inline-comments": 0,
    "no-inner-declarations": [2, "functions"],
    "no-invalid-regexp": 2,
    "no-invalid-this": 0,
    "no-irregular-whitespace": 2,
    "no-iterator": 2,
    "no-label-var": 2,
    "no-labels": 0,
    "no-lone-blocks": 2,
    "no-lonely-if": 2,
    "no-loop-func": 0,
    "no-mixed-requires": [2, true],
    "no-mixed-spaces-and-tabs": 2,
    "no-multi-spaces": 2,
    "no-multi-str": 2,
    "no-multiple-empty-lines": 0,
    "no-native-reassign": 0,
    "no-negated-in-lhs": 2,
    "no-nested-ternary": 0,
    "no-new": 2,
    "no-new-func": 0,
    "no-new-object": 2,
    "no-new-require": 2,
    "no-new-wrappers": 2,
    "no-obj-calls": 2,
    "no-octal": 2,
    "no-octal-escape": 2,
    "no-param-reassign": 2,
    "no-path-concat": 2,
    "no-plusplus": 0,
    "no-process-env": 0,
    "no-process-exit": 0,
    "no-proto": 2,
    "no-redeclare": 2,
    "no-regex-spaces": 2,
    "no-restricted-modules": 0,
    "no-return-assign": 2,
    "no-script-url": 2,
    "no-self-compare": 0,
    "no-sequences": 2,
    "no-shadow": 2,
    "no-shadow-restricted-names": 2,
    "no-spaced-func": 2,
    "no-sparse-arrays": 2,
    "no-sync": 2,
    "no-ternary": 0,
    "no-this-before-super": 2,
    "no-throw-literal": 2,
    "no-trailing-spaces": 2,
    "no-undef": 2,
    "no-undef-init": 2,
    "no-undefined": 0,
    "no-underscore-dangle": 0,
    "no-unexpected-multiline": 2,
    "no-unneeded-ternary": 2,
    "no-unreachable": 2,
    "no-unused-expressions": 2,
    "no-unused-vars": [2, {"vars": "all", "args": "after-used"}],
    "no-use-before-define": 0,
    "no-useless-call": 2,
    "no-var": 0,
    "no-void": 2,
    "no-warning-comments": 0,
    "no-with": 2,
    "object-curly-spacing": [0, "always"],
    "object-shorthand": [2, "always"],
    "one-var": [2, "never"],
    "operator-assignment": [2, "always"],
    "operator-linebreak": [2, "after"],
    "padded-blocks": 0,
    "prefer-const": 0,
    "prefer-reflect": 0,
    "prefer-spread": 0,
    "quote-props": [2, "as-needed"],
    "quotes": [2, "single"],
    "radix": 2,
    "require-yield": 2,
    "semi": [2, "always"],
    "semi-spacing": [2, {"before": false, "after": true}],
    "sort-vars": 0,
//    "space-after-keywords": [2, "always"],
    "space-before-blocks": [2, "always"],
    "space-before-function-paren": [2, {"anonymous": "always", "named": "never"}],
    "space-in-parens": 0,
    "space-infix-ops": [2, {"int32Hint": false}],
//    "space-return-throw-case": 2,
    "space-unary-ops": [2, {"words": true, "nonwords": false}],
    "spaced-comment": [2, "always"],
    "strict": 0,
    "use-isnan": 2,
    "valid-jsdoc": 0,
    "valid-typeof": 2,
    "vars-on-top": 0,
    "wrap-iife": 2,
    "wrap-regex": 0,
    "yoda": [2, "never", {"exceptRange": true}]
  }
}


================================================
FILE: .flowconfig
================================================
[ignore]
.*/lib/.*
.*/dist/.*
.*/coverage/.*
.*/resources/.*

[include]

[libs]
./flow

[options]
esproposal.class_static_fields=enable
esproposal.class_instance_fields=enable
munge_underscores=true
suppress_comment= \\(.\\|\n\\)*\\$FlowIgnore


================================================
FILE: .gitignore
================================================
# Logs
logs
*.log
npm-debug.log*

# Runtime data
pids
*.pid
*.seed

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# node-waf configuration
.lock-wscript

# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release

# Dependency directory
node_modules

# Optional npm cache directory
.npm

# Optional REPL history
.node_repl_history

dist/
dist.zip
build/
config.env.production


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) 2016 for dynamodb-lambda-autoscale

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# dynamodb-lambda-autoscale
**Autoscale AWS DynamoDB using an AWS Lambda function**

+ 5 minute setup process
+ Serverless design
+ Flexible code over configuration style
+ Autoscale table and global secondary indexes
+ Autoscale multiple tables
+ Autoscale by fixed settings
+ Autoscale by provisioned capacity utilisation
+ Autoscale by throttled event metrics
+ Optimised for large spikes in usage and hotkey issues by incorporating throttled event metrics
+ Optimised performance using concurrent queries
+ RateLimitedDecrement as imposed by AWS
+ Statistics via 'measured'
+ AWS credential configuration via 'dotenv'
+ Optimised lambda package via 'webpack'
+ ES7 code
+ 100% [Flow](https://flowtype.org/) static type checking coverage

## Disclaimer

Any reliance you place on dynamodb-lambda-autoscale is strictly at your own
risk.

In no event will we be liable for any loss or damage including without
limitation, indirect or consequential loss or damage, or any loss or damage
whatsoever arising from loss of data or profits arising out of, or in
connection with, the use of this code.

## Getting started

Note: dynamodb-lambda-autoscale uses [Flow](https://flowtype.org/) extensively for static type
checking, we highly recommend you use [Nuclide](https://nuclide.io/) when making modification to code /
configuration.  Please see the respective websites for advantages / reasons.

1. Build and package the code
  1. Fork the repo
  2. Clone your fork
  3. Create a new file in the root folder called 'config.env.production'
  4. Put your AWS credentials into the file in the following format, only if you want to run a local test (not needed for lambda)

    ```javascript
    AWS_ACCESS_KEY_ID="###################"
    AWS_SECRET_ACCESS_KEY="###############"
    ```

  5. Update [Region.json](./src/configuration/Region.json) to match the region of your DynamoDB instance
  6. Run 'npm install'
  7. Run 'npm run build'
  8. Verify this has created a 'dist.zip' file
  9. Optionally, run a local test by running 'npm run start'

## Running on AWS Lambda

1. Follow the steps in 'Running locally'
2. Create an AWS Policy and Role
  1. Create a policy called 'DynamoDBLambdaAutoscale'
  2. Use the following content to give access to dynamoDB, cloudwatch and lambda logging

      ```javascript
      {
        "Version": "2012-10-17",
        "Statement": [
          {
            "Action": [
              "dynamodb:ListTables",
              "dynamodb:DescribeTable",
              "dynamodb:UpdateTable",
              "cloudwatch:GetMetricStatistics",
              "logs:CreateLogGroup",
              "logs:CreateLogStream",
              "logs:PutLogEvents"
            ],
            "Effect": "Allow",
            "Resource": "*"
          }
        ]
      }
      ```

  3. Create a role called 'DynamoDBLambdaAutoscale'
  4. Attach the newly created policy to the role
3. Create a AWS Lambda function
  1. Skip the pre defined functions step
  2. Set the name to 'DynamoDBLambdaAutoscale'
  3. Set the runtime to 'Node.js 4.3'
  4. Select upload a zip file and select 'dist.zip' which you created earlier
  5. Set the handler to 'index.handler'
  6. Set the Role to 'DynamoDBLambdaAutoscale'
  7. Set the Memory to the lowest value initially but test different values at a later date to see how it affects performance
  8. Set the Timeout to approximately 5 seconds (higher or lower depending on the amount of tables you have and the selected memory setting)
  9. Once the function is created, attach a 'scheduled event' event source and make it run every minute.  Event Sources > Add Event Source > Event Type = Cloudwatch Events - Schedule. Set the name to 'DynamoDBLambdaAutoscale' and the schedule expression to 'rate(1 minute)'

## Configuration

The default setup in the [Provisioner.js](./src/Provisioner.js) allows for a quick no touch setup.
A breakdown of the configuration behaviour is as follows:
- AWS region is set to 'us-east-1' via [Region.json](./src/configuration/Region.json) configuration
- Autoscales all tables and indexes
- Autoscaling 'Strategy' settings are defined in [DefaultProvisioner.json](./src/configuration/DefaultProvisioner.json) and are as follows
  - Separate 'Read' and 'Write' capacity adjustment strategies
  - Separate asymmetric 'Increment' and 'Decrement' capacity adjustment strategies
  - Read/Write provisioned capacity increased
    - when capacity utilisation > 75% or throttled events in the last minute > 25
    - by 3 + (0.7 * throttled events) units or by 30% + (0.7 * throttled events) of provisioned value or to 130% of the current consumed capacity, which ever is the greater
    - with hard min/max limits of 1 and 100 respectively
  - Read/Write provisioned capacity decreased
    - when capacity utilisation < 30% AND
    - when at least 60 minutes have passed since the last increment AND
    - when at least 60 minutes have passed since the last decrement AND
    - when the adjustment will be at least 5 units AND
    - when we are allowed to utilise 1 of our 4 AWS enforced decrements
    - to the consumed throughput value
    - with hard min/max limits of 1 and 100 respectively

## Strategy Settings

The strategy settings described above uses a simple schema which applies to both Read/Write and to
both the Increment/Decrement.  Using the options below many different strategies can be constructed:
- ReadCapacity.Min : (Optional) Define a minimum allowed capacity, otherwise 1
- ReadCapacity.Max : (Optional) Define a maximum allowed capacity, otherwise unlimited
- ReadCapacity.Increment : (Optional) Defined an increment strategy
- ReadCapacity.Increment.When : (Required) Define when capacity should be incremented
- ReadCapacity.Increment.When.ThrottledEventsPerMinuteIsAbove : (Optional) Define a threshold at which throttled events trigger an increment
- ReadCapacity.Increment.When.UtilisationIsAbovePercent : (Optional) Define a percentage utilisation upper threshold at which capacity is subject to recalculation
- ReadCapacity.Increment.When.UtilisationIsBelowPercent : (Optional) Define a percentage utilisation lower threshold at which capacity is subject to recalculation, possible but non sensical for increments however.
- ReadCapacity.Increment.When.AfterLastIncrementMinutes : (Optional) Define a grace period based off the previous increment in which capacity adjustments should not occur
- ReadCapacity.Increment.When.AfterLastDecrementMinutes : (Optional) Define a grace period based off the previous decrement in which capacity adjustments should not occur
- ReadCapacity.Increment.When.UnitAdjustmentGreaterThan : (Optional) Define a minimum unit adjustment so that only capacity adjustments of a certain size are allowed
- ReadCapacity.Increment.By : (Optional) Define a 'relative' value to change the capacity by
- ReadCapacity.Increment.By.ConsumedPercent : (Optional) Define a 'relative' percentage adjustment based on the current ConsumedCapacity
- ReadCapacity.Increment.By.ProvisionedPercent : (Optional) Define a 'relative' percentage adjustment based on the current ProvisionedCapacity
- ReadCapacity.Increment.By.Units : (Optional) Define a 'relative' unit adjustment
- ReadCapacity.Increment.By.ThrottledEventsWithMultiplier : (Optional) Define a 'multiple' of the throttled events in the last minute which are added to all other 'By' unit adjustments
- ReadCapacity.Increment.To : (Optional) Define an 'absolute' value to change the capacity to
- ReadCapacity.Increment.To.ConsumedPercent : (Optional) Define an 'absolute' percentage adjustment based on the current ConsumedCapacity
- ReadCapacity.Increment.To.ProvisionedPercent : (Optional) Define an 'absolute' percentage adjustment based on the current ProvisionedCapacity
- ReadCapacity.Increment.To.Units : (Optional) Define an 'absolute' unit adjustment

A sample of the strategy setting json is...
```javascript
{
  "ReadCapacity": {
    "Min": 1,
    "Max": 100,
    "Increment": {
      "When": {
        "UtilisationIsAbovePercent": 75,
        "ThrottledEventsPerMinuteIsAbove": 25
      },
      "By": {
        "Units": 3,
        "ProvisionedPercent": 30,
        "ThrottledEventsWithMultiplier": 0.7
      },
      "To": {
        "ConsumedPercent": 130
      }
    },
    "Decrement": {
      "When": {
        "UtilisationIsBelowPercent": 30,
        "AfterLastIncrementMinutes": 60,
        "AfterLastDecrementMinutes": 60,
        "UnitAdjustmentGreaterThan": 5
      },
      "To": {
        "ConsumedPercent": 100
      }
    }
  },
  "WriteCapacity": {
    "Min": 1,
    "Max": 100,
    "Increment": {
      "When": {
        "UtilisationIsAbovePercent": 75,
        "ThrottledEventsPerMinuteIsAbove": 25
      },
      "By": {
        "Units": 3,
        "ProvisionedPercent": 30,
        "ThrottledEventsWithMultiplier": 0.7
      },
      "To": {
        "ConsumedPercent": 130
      }
    },
    "Decrement": {
      "When": {
        "UtilisationIsBelowPercent": 30,
        "AfterLastIncrementMinutes": 60,
        "AfterLastDecrementMinutes": 60,
        "UnitAdjustmentGreaterThan": 5
      },
      "To": {
        "ConsumedPercent": 100
      }
    }
  }
}
```

## Advanced Configuration

This project takes a 'React' style code first approach over declarative configuration traditionally
used by other autoscaling community projects.  Rather than being limited to a structured
configuration file or even the 'strategy' settings above you have the option to extend the [ProvisionerBase.js](./src/provisioning/ProvisionerBase.js)
abstract base class for yourself and programmatically implement any desired logic.

The following three functions are all that is required to complete the provisioning functionality.  
As per the 'React' style, only actual updates to the ProvisionedCapacity will be sent to AWS.

```javascript
getDynamoDBRegion(): string {
  // Return the AWS region as a string
}

async getTableNamesAsync(): Promise<string[]> {
  // Return the table names to apply autoscaling to as a string array promise
}

async getTableUpdateAsync(
  tableDescription: TableDescription,
  tableConsumedCapacityDescription: TableConsumedCapacityDescription):
  Promise<?UpdateTableRequest> {
  // Given an AWS DynamoDB TableDescription and AWS CloudWatch ConsumedCapacity metrics
  // return an AWS DynamoDB UpdateTable request
}
```
[DescribeTable.ResponseSyntax](http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_DescribeTable.html#API_DescribeTable_ResponseSyntax)
[UpdateTable.RequestSyntax](http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateTable.html#API_UpdateTable_RequestSyntax)

Flexibility is great, but implementing all the logic required for a robust autoscaling
strategy isn't something everyone wants to do.  Hence, the default 'Provisioner' builds upon the base
class in a layered approach.  The layers are as follows:
- [Provisioner.js](./src/Provisioner.js) concrete implementation which provides very robust autoscaling logic which can be manipulated with a 'strategy' settings json object
- [ProvisionerConfigurableBase.js](./src/provisioning/ProvisionerConfigurableBase.js) abstract base class which breaks out the 'getTableUpdateAsync' function into more manageable abstract methods
- [ProvisionerBase.js](./src/provisioning/ProvisionerBase.js) the root abstract base class which defines the minimum contract

## Throttled Events
Throttled events are now taken into account as part of the provisioning calculation.  A multiple of the events can be added to the existing calculation so that both large spikes in usage and hot key issues are both dealt with.

## Rate Limited Decrement

AWS only allows 4 table decrements in a calendar day.  To account for this we have included
an algorithm which segments the remaining time to midnight by the amount of decrements we have left.
This logic allows us to utilise each 4 decrements as efficiently as possible.  The increments on the
other hand are unlimited, so the algorithm follows a unique 'sawtooth' profile, dropping the
provisioned capacity all the way down to the consumed throughput rather than gradually.  Please see
[RateLimitedDecrement.js](./src/utils/RateLimitedDecrement.js) for full implementation.

## Capacity Calculation

As well as implementing the correct Provisioning logic it is also important to calculate the
ConsumedCapacity for the current point in time.  We have provided a default algorithm in
[CapacityCalculator.js](./src/CapacityCalculator.js) which should be good enough for most purposes
but it could be swapped out with perhaps an improved version.  The newer version could potentially
take a series of data points and plot a linear regression line through them for example.

## Dependencies

This project has the following main dependencies (n.b. all third party dependencies are compiled
into a single javascript file before being zipped and uploaded to lambda):
+ aws-sdk - Access to AWS services
+ dotenv - Environment variable configuration useful for lambda
+ measured - Statistics gathering

## Licensing

The source code is licensed under the MIT license found in the
[LICENSE](LICENSE) file in the root directory of this source tree.


================================================
FILE: flow/aws-sdk.js
================================================
/* @flow */
declare module 'aws-sdk' {
  declare class DynamoDB {
    constructor(dynamoDBConfig: DynamoDBConfig): void;

    listTables(params: ?ListTablesRequest, callback: ?(err: ?Error,
      data: ListTablesResponse) => void): PromiseRequest<ListTablesResponse>;

    deleteTable(params: ?DeleteTableRequest,callback: ?(err: ?Error,
      data: DeleteTableResponse) => void): PromiseRequest<DeleteTableResponse>;

    createTable(params: ?CreateTableRequest, callback: ?(err: ?Error,
      data: CreateTableResponse) => void): PromiseRequest<CreateTableResponse>;

    describeTable(params: ?DescribeTableRequest, callback: ?(err: ?Error,
      data: DescribeTableResponse) => void): PromiseRequest<DescribeTableResponse>;

    updateTable(params: ?UpdateTableRequest, callback: ?(err: ?Error,
      data: UpdateTableResponse) => void): PromiseRequest<UpdateTableResponse>;

    scan(params: ?ScanRequest, callback: ?(err: ?Error,
      data: ScanQueryResponse) => void): PromiseRequest<ScanQueryResponse>;

    query(params: ?QueryRequest, callback: ?(err: ?Error,
      data: ScanQueryResponse) => void): PromiseRequest<ScanQueryResponse>;

    putItem(params: ?PutItemRequest, callback: ?(err: ?Error,
      data: PutItemResponse) => void): PromiseRequest<PutItemResponse>;

    getItem(params: ?GetItemRequest, callback: ?(err: ?Error,
      data: GetItemResponse) => void): PromiseRequest<GetItemResponse>;

    batchGetItem(params: ?BatchGetItemRequest, callback: ?(err: ?Error,
      data: BatchGetItemResponse) => void): PromiseRequest<BatchGetItemResponse>;

    batchWriteItem(params: ?BatchWriteItemRequest, callback: ?(err: ?Error,
      data: BatchWriteItemResponse) => void): PromiseRequest<BatchWriteItemResponse>;

    deleteItem(params: ?DeleteItemRequest, callback: ?(err: ?Error,
      data: DeleteItemResponse) => void): PromiseRequest<DeleteItemResponse>;

    updateItem(params: ?UpdateItemRequest, callback: ?(err: ?Error,
      data: UpdateItemResponse) => void): PromiseRequest<UpdateItemResponse>;
  }

  declare class CloudWatch {
    getMetricStatistics(params: ?GetMetricStatisticsRequest, callback: ?(err: ?Error,
      data: GetMetricStatisticsResponse) => void): PromiseRequest<GetMetricStatisticsResponse>;
  }

  declare class PromiseRequest<T> {
    promise(): Promise<T>;
  }

  declare type ListTablesRequest = {
     ExclusiveStartTableName?: string,
     Limit?: number
  };

  declare type ListTablesResponse = {
     LastEvaluatedTableName?: string,
     TableNames: string[]
  };

  declare type DeleteTableRequest = {
     TableName: string,
  };

  declare type DeleteTableResponse = {
    TableDescription: TableDescription,
  };

  declare type CreateTableRequest = {
    AttributeDefinitions: AttributeDefinition[],
    KeySchema: KeyDefinition[],
    ProvisionedThroughput: ProvisionedThroughput,
    TableName: string,
    GlobalSecondaryIndexes: GlobalSecondaryIndex[],
    LocalSecondaryIndexes: LocalSecondaryIndex[],
    StreamSpecification: StreamSpecification,
  };

  declare type CreateTableResponse = {
    TableDescription: TableDescription,
  };

  declare type ScanRequest = {
    AttributesToGet: string[],
    ConditionalOperator: string,
    ConsistentRead: boolean,
    ExclusiveStartKey: any,
    ExpressionAttributeNames: any,
    ExpressionAttributeValues: any,
    FilterExpression: string,
    IndexName: string,
    Limit: number,
    ProjectionExpression: string,
    ReturnConsumedCapacity: string,
    ScanFilter: any,
    Segment: number,
    Select: string,
    TableName: string,
    TotalSegments: number
  };

  declare type TableConsumedCapacity = {
     CapacityUnits: number,
  };

  declare type ConsumedCapacity = {
    CapacityUnits: number,
    GlobalSecondaryIndexes: any,
    LocalSecondaryIndexes: any,
    Table: TableConsumedCapacity,
    TableName: string
  };

  declare type ScanQueryResponse = {
    ConsumedCapacity: ConsumedCapacity,
    Count: number,
    Items: any[],
    LastEvaluatedKey: any,
    ScannedCount: number,
  };

  declare type QueryRequest = {
    AttributesToGet: string[],
    ConditionalOperator: string,
    ConsistentRead: boolean,
    ExclusiveStartKey: any,
    ExpressionAttributeNames: any,
    ExpressionAttributeValues: any,
    FilterExpression: string,
    IndexName: string,
    KeyConditionExpression: string,
    KeyConditions: any,
    Limit: number,
    ProjectionExpression: string,
    QueryFilter: any,
    ReturnConsumedCapacity: string,
    ScanIndexForward: boolean,
    Select: string,
    TableName: string,
  };

  declare type PutItemRequest = {
    ConditionalOperator: string,
    ConditionExpression: string,
    Expected: any,
    ExpressionAttributeNames: string,
    ExpressionAttributeValues: any,
    Item: any,
    ReturnConsumedCapacity: string,
    ReturnItemCollectionMetrics: string,
    ReturnValues: string,
    TableName: string,
  };

  declare type PutItemResponse = {
    Attributes: any,
    ConsumedCapacity: ConsumedCapacity,
    ItemCollectionMetrics: any,
  };

  declare type GetItemRequest = {
    AttributesToGet: string[],
    ConsistentRead: boolean,
    ExpressionAttributeNames: any,
    Key: any,
    ProjectionExpression: string,
    ReturnConsumedCapacity: string,
    TableName: string,
  };

  declare type GetItemResponse = {
    ConsumedCapacity: ConsumedCapacity,
    Item: any,
  };

  declare type BatchGetItemRequest = {
    RequestItems: any,
    ReturnConsumedCapacity: string,
  };

  declare type BatchGetItemResponse = {
    ConsumedCapacity: ConsumedCapacity[],
    Responses: any,
    UnprocessedKeys: any,
  };

  declare type BatchWriteItemRequest = {
    RequestItems: any,
    ReturnConsumedCapacity: string,
    ReturnItemCollectionMetrics: string,
  };

  declare type BatchWriteItemResponse = {
    ConsumedCapacity: ConsumedCapacity[],
    ItemCollectionMetrics: ItemCollectionMetrics,
    UnprocessedKeys: any,
  };

  declare type DeleteItemRequest = {
    ConditionalOperator: string,
    ConditionExpression: string,
    Expected: any,
    ExpressionAttributeNames: any,
    ExpressionAttributeValues: any,
    Key: any,
    ReturnConsumedCapacity: string,
    ReturnItemCollectionMetrics: string,
    ReturnValues: string,
    TableName: string,
  };

  declare type DeleteItemResponse = {
    Attributes: any,
    ConsumedCapacity: ConsumedCapacity,
    ItemCollectionMetrics: ItemCollectionMetrics,
  };

  declare type UpdateItemRequest = {
    AttributeUpdates: any,
    ConditionalOperator: string,
    ConditionExpression: string,
    Expected: any,
    ExpressionAttributeNames: any,
    ExpressionAttributeValues: any,
    ReturnConsumedCapacity: string,
    ReturnItemCollectionMetrics: string,
    ReturnValues: string,
    TableName: string,
    UpdateExpression: string
  };

  declare type UpdateItemResponse = {
    Attributes: any,
    ConsumedCapacity: ConsumedCapacity,
    ItemCollectionMetrics: ItemCollectionMetrics,
  };

  declare type ItemCollectionMetrics = {
    ItemCollectionKey: any,
    SizeEstimateRangeGB: number[],
  };

  declare type DynamoDBConfig = {
    apiVersion: string,
    region: string,
    dynamoDbCrc32: boolean
  };

  declare type DynamoDBAttributeDefinition = {
    AttributeName: string,
    AttributeType: string
  };

  declare type DynamoDBKeySchema = {
    AttributeName: string,
    KeyType: string
  };

  declare type DynamoDBTable = {
    TableName: string,
    AttributeDefinitions: DynamoDBAttributeDefinition[],
    KeySchema: DynamoDBKeySchema[],
    GlobalSecondaryIndexes?: DynamoDBGlobalSecondaryIndex[],
    LocalSecondaryIndexes?: DynamoDBLocalSecondaryIndex[],
    ProvisionedThroughput: DynamoDBProvisionedThroughput,
    StreamSpecification?: DynamoDBStreamSpecification
  };

  declare type DynamoDBGlobalSecondaryIndex = {
    IndexName: string,
    KeySchema: DynamoDBKeySchema [],
    Projection: DynamoDBProjection,
    ProvisionedThroughput: DynamoDBProvisionedThroughput
  };

  declare type DynamoDBLocalSecondaryIndex = {
    IndexName: string,
    KeySchema: DynamoDBKeySchema [],
    Projection: DynamoDBProjection,
  };

  declare type DynamoDBProjection = {
     NonKeyAttributes?: string[],
     ProjectionType: string
  };

  declare type DynamoDBProvisionedThroughput = {
    ReadCapacityUnits: number,
    WriteCapacityUnits: number
  };

  declare type DynamoDBStreamSpecification = {
    StreamEnabled: boolean,
    StreamViewType: string
  };

  declare type DynamoDBSchema = {
    tables: DynamoDBTable[]
  };

  // DynamoDB
  declare type DynamoDBOptions = {
    apiVersion: string,
    region: string,
    dynamoDbCrc32: boolean,
    httpOptions: HTTPOptions,
  };

  declare type HTTPOptions = {
    timeout: number,
  };

  declare type AttributeDefinition = {
    AttributeName: string,
    AttributeType: string,
  };

  declare type KeyDefinition = {
    AttributeName: string,
    KeyType: string,
  };

  declare type Projection = {
    NonKeyAttributes: string[],
    ProjectionType: string,
  };

  declare type ProvisionedThroughput = {
     LastDecreaseDateTime: string,
     LastIncreaseDateTime: string,
     NumberOfDecreasesToday: number,
     ReadCapacityUnits: number,
     WriteCapacityUnits: number,
  };

  declare type Throughput = {
    ReadCapacityUnits: number,
    WriteCapacityUnits: number,
  };

  declare type GlobalSecondaryIndex = {
    Backfilling: boolean,
    IndexArn: string,
    IndexName: string,
    IndexSizeBytes: number,
    IndexStatus: string,
    ItemCount: number,
    KeySchema: KeyDefinition[],
    Projection: Projection,
    ProvisionedThroughput: ProvisionedThroughput,
  };

  declare type LocalSecondaryIndex = {
     IndexArn: string,
     IndexName: string,
     IndexSizeBytes: number,
     ItemCount: number,
     KeySchema: KeyDefinition[],
     Projection: Projection,
  };

  declare type StreamSpecification = {
     StreamEnabled: boolean,
     StreamViewType: string,
  };

  declare type TableDescription = {
    AttributeDefinitions: AttributeDefinition[],
    CreationDateTime: number,
    GlobalSecondaryIndexes: GlobalSecondaryIndex[],
    ItemCount: number,
    KeySchema: KeyDefinition[],
    LatestStreamArn: string,
    LatestStreamLabel: string,
    LocalSecondaryIndexes: LocalSecondaryIndex[],
    ProvisionedThroughput: ProvisionedThroughput,
    StreamSpecification: StreamSpecification,
    TableArn: string,
    TableName: string,
    TableSizeBytes: number,
    TableStatus: string
  };

  declare type DescribeTableRequest = {
     TableName: string,
  };

  declare type DescribeTableResponse = {
    Table: TableDescription,
  };

  declare type GlobalSecondaryIndexUpdateCreate = {
    IndexName: string,
    KeySchema: KeyDefinition[],
    Projection: Projection,
    ProvisionedThroughput: Throughput,
  };

  declare type GlobalSecondaryIndexUpdateDelete = {
    IndexName: string,
  };

  declare type GlobalSecondaryIndexUpdateUpdate = {
    IndexName: string,
    ProvisionedThroughput: Throughput,
  };

  declare type GlobalSecondaryIndexUpdate = {
     Create?: GlobalSecondaryIndexUpdateCreate,
     Delete?: GlobalSecondaryIndexUpdateDelete,
     Update?: GlobalSecondaryIndexUpdateUpdate,
  };

  declare type UpdateTableRequest = {
     AttributeDefinitions?: AttributeDefinition[],
     GlobalSecondaryIndexUpdates? : GlobalSecondaryIndexUpdate[],
     ProvisionedThroughput?: Throughput,
     StreamSpecification?: StreamSpecification,
     TableName: string
  };

  declare type UpdateTableResponse = {
    TableDescription: TableDescription,
  };

  // CloudWatch
  declare type CloudWatchOptions = {
    apiVersion: string,
    region: string,
    httpOptions: HTTPOptions,
  };

  declare type GetMetricStatisticsResponse = {
    ResponseMetadata: ResponseMetadata,
    Label: string,
    Datapoints: Datapoint[],
  };

  declare type Dimension = {
    Name: string,
    Value: string,
  };

  declare type GetMetricStatisticsRequest = {
    Namespace: string,
    MetricName: string,
    Dimensions: Dimension[],
    StartTime: Date,
    EndTime: Date,
    Period: number,
    Statistics: string[],
    Unit: string,
  };

  declare type ResponseMetadata = {
    RequestId: string,
  };

  declare type Datapoint = {
    Timestamp: string,
    Average: number,
    Sum: number,
    Unit: string,
  };
}


================================================
FILE: flow/invariant.js
================================================
/* @flow */
declare module 'invariant' {
  declare class Invariant {
    (condition: boolean, message: string): any;
  }
  declare var exports: Invariant;
}


================================================
FILE: flow/measured.js
================================================
/* @flow */
declare module 'measured' {
  declare class MeasuredCollection {
    _metrics: any;

    timer(name: string): MeasuredTimer;

    counter(name: string): MeasuredCounter;

    toJSON(): any;
  }

  declare class MeasuredTimer {
    start(): Stopwatch;
  }

  declare class MeasuredCounter {
    inc(value: number): void;
  }

  declare class Stopwatch {
    end(): void;
  }

  declare function createCollection(): MeasuredCollection;
}


================================================
FILE: flow/warning.js
================================================
/* @flow */
declare module 'warning' {
  declare var exports: (shouldBeTrue: bool, warning: string) => void;
}


================================================
FILE: gulpfile.js
================================================
var gulp = require("gulp");
var del = require('del');
var rename = require('gulp-rename');
var install = require('gulp-install');
var zip = require('gulp-zip');
var uglify = require('gulp-uglify');
var AWS = require('aws-sdk');
var fs = require('fs');
var runSequence = require('run-sequence');
var webpack = require('webpack-stream');

// First we need to clean out the dist folder and remove the compiled zip file.
gulp.task('clean', function(cb) {
  del('./dist');
  cb();
});

gulp.task("webpack", function () {
  return gulp.src('src/Index.js')
  .pipe(webpack( require('./webpack-dev.config.js') ))
  .pipe(gulp.dest('dist/'));
});

// The js task could be replaced with gulp-coffee as desired.
gulp.task("js", function () {
  return gulp
    .src("dist/index.js")
    .pipe(gulp.dest("dist/"));
});

// Here we want to install npm packages to dist, ignoring devDependencies.
gulp.task('npm', function() {
  return gulp
    .src('./package.json')
    .pipe(gulp.dest('./dist/'))
    .pipe(install({production: true}));
});

// Next copy over environment variables managed outside of source control.
gulp.task('env', function() {
  return gulp
    .src('./config.env.production')
    .pipe(rename('config.env'))
    .pipe(gulp.dest('./dist'));
});

// Now the dist directory is ready to go. Zip it.
gulp.task('zip', function() {
  return gulp
    .src(['dist/**/*', '!dist/package.json', 'dist/.*'])
    .pipe(zip('dist.zip'))
    .pipe(gulp.dest('./'));
});

// Per the gulp guidelines, we do not need a plugin for something that can be
// done easily with an existing node module. #CodeOverConfig
//
// Note: This presumes that AWS.config already has credentials. This will be
// the case if you have installed and configured the AWS CLI.
//
// See http://aws.amazon.com/sdk-for-node-js/
gulp.task('upload', function() {

  // TODO: This should probably pull from package.json
  AWS.config.region = 'us-east-1';
  var lambda = new AWS.Lambda();
  var functionName = 'video-events';

  lambda.getFunction({FunctionName: functionName}, function(err, data) {
    if (err) {
      if (err.statusCode === 404) {
        var warning = 'Unable to find lambda function ' + deploy_function + '. '
        warning += 'Verify the lambda function name and AWS region are correct.'
        gutil.log(warning);
      } else {
        var warning = 'AWS API request failed. '
        warning += 'Check your AWS credentials and permissions.'
        gutil.log(warning);
      }
    }

    // This is a bit silly, simply because these five parameters are required.
    var current = data.Configuration;
    var params = {
      FunctionName: functionName,
      Handler: current.Handler,
      Mode: current.Mode,
      Role: current.Role,
      Runtime: current.Runtime
    };

    fs.readFile('./dist.zip', function(err, data) {
      params['FunctionZip'] = data;
      lambda.uploadFunction(params, function(err, data) {
        if (err) {
          var warning = 'Package upload failed. '
          warning += 'Check your iam:PassRole permissions.'
          gutil.log(warning);
        }
      });
    });
  });
});

// The key to deploying as a single command is to manage the sequence of events.
gulp.task('dist', function(cb) {
  return runSequence(
    ['clean'],
    ['webpack'],
    ['js', 'npm', 'env'],
    ['zip'],
//    ['upload'],
    function (err) {
      //if any error happened in the previous tasks, exit with a code > 0
      if (err) {
        cb(err);
        var exitCode = 2;
        console.log('[ERROR] gulp build task failed', err);
        console.log('[FAIL] gulp build task failed - exiting with code ' + exitCode);
        return process.exit(exitCode);
      }
      else {
        return cb();
      }
    }
  );
});


================================================
FILE: make-webpack-config.js
================================================
var path = require('path');
var webpack = require('webpack');
var StatsPlugin = require('stats-webpack-plugin');
var ContextReplacementPlugin = require('webpack/lib/ContextReplacementPlugin');

module.exports = function (options) {

  var entry = { index: './src/Index.js' };
  var loaders = [
    { test: /\.jsx$/, loader: options.hotComponents ?
			[ 'react-hot-loader', 'babel-loader?stage=0' ] : 'babel-loader?stage=0'},
    { test: /\.js$/, loader: 'babel-loader', include: path.join(__dirname, 'src')},
    { test: /\.json$/, loader: 'json-loader' },
    { test: /\.md|markdown$/, loader: 'markdown-loader'}
  ];

  var alias = {};
  var aliasLoader = {};
  var externals = [
		{ 'aws-sdk': 'commonjs aws-sdk' }   // This is already available on the lambda server
  ];
  var modulesDirectories = [ 'node_modules', 'web_modules' ];
  var extensions = [ '', '.web.js', '.js', '.jsx', '.json' ];
  var root = __dirname;

  var publicPath = options.devServer ? 'http://localhost:2992/assets/' : '/assets/';

  var output = {
    path: path.join(__dirname, 'dist'),
    publicPath,
    filename: '[name].js',
    chunkFilename: options.devServer ? '[id].js' : '[name].js',
    sourceMapFilename: 'debugging/[file].map',
    pathinfo: options.debug,
    libraryTarget: 'umd'
  };

  var excludeFromStats = [
    /node_modules[\\\/]react(-router)?[\\\/]/,
    /node_modules[\\\/]items-store[\\\/]/
  ];

  var plugins = [
    new StatsPlugin(path.join(__dirname, 'build', 'stats.json'), {
      chunkModules: true,
      exclude: excludeFromStats
    }),
    new ContextReplacementPlugin(/moment\.js[\/\\]lang$/, /^\.\/(de|pl)$/)
  ];

  if(options.minimize) {
    plugins.push(
      new webpack.optimize.UglifyJsPlugin({
        compressor: {
          warnings: false
        }
      }),
    new webpack.optimize.DedupePlugin()
    );
    plugins.push(
			new webpack.DefinePlugin({'process.env': { NODE_ENV: JSON.stringify('production')}}),
			new webpack.NoErrorsPlugin()
		);
  }

  return {
    entry,
    output,
    target: 'node',
    module: { loaders },
    devtool: options.devtool,
    debug: options.debug,
    resolveLoader: {
      root: path.join(__dirname, 'node_modules'),
      alias: aliasLoader
    },
    externals,
    resolve: {
      root,
      modulesDirectories,
      extensions,
      alias
    },
    plugins,
    devServer: {
      stats: {
        cached: false,
        exclude: excludeFromStats
      }
    }
  };
};


================================================
FILE: package.json
================================================
{
  "name": "dynamodb-lambda-autoscale",
  "version": "0.3.0",
  "description": "Autoscale DynamoDB provisioned capacity using AWS Lambda",
  "contributors": [
    "Thomas Mitchell <channlappio@gmail.com>"
  ],
  "license": "MIT",
  "repository": {
    "type": "git",
    "url": "http://github.com/channl/dynamodb-lambda-autoscale.git"
  },
  "main": "dist/index.js",
  "options": {
    "mocha": "--require resources/mocha-bootload src/**/__tests__/**/*.js"
  },
  "scripts": {
    "localtest": "babel-node ./scripts/localtest.js",
    "test": "npm run lint && npm run check",
    "lint": "eslint src",
    "check": "flow check",
    "build": "gulp dist",
    "start": "node ./scripts/start.js",
    "debug": "node --inspect --debug-brk ./scripts/start.js"
  },
  "dependencies": {},
  "devDependencies": {
    "async": "^2.0.0",
    "aws-sdk": "2.6.9",
    "babel": "^6.5.2",
    "babel-cli": "^6.7.7",
    "babel-eslint": "^6.0.4",
    "babel-loader": "^6.2.4",
    "babel-polyfill": "^6.7.4",
    "babel-preset-react-native": "^1.5.7",
    "del": "^2.2.0",
    "dotenv": "^2.0.0",
    "eslint": "^2.9.0",
    "eslint-plugin-babel": "^3.2.0",
    "gulp": "^3.9.1",
    "gulp-install": "^0.6.0",
    "gulp-rename": "^1.2.2",
    "gulp-uglify": "^1.5.3",
    "gulp-zip": "^3.2.0",
    "invariant": "^2.2.1",
    "json-loader": "^0.5.4",
    "measured": "^1.1.0",
    "run-sequence": "^1.1.5",
    "stats-webpack-plugin": "^0.3.1",
    "warning": "^2.1.0",
    "webpack": "^1.13.0",
    "webpack-stream": "^3.2.0"
  }
}


================================================
FILE: scripts/start.js
================================================
/* eslint-disable no-console */

try {
  var lambda = require('../dist/index.js');

  process.chdir('./dist');

  var context = {
    succeed: data => {
      try {
        if (data) {
          console.log(JSON.stringify(data));
        }
      } catch (e) {
        console.error(e.stack);
      }
    },
    fail: e => {
      console.error(e.stack);
    }
  };

  var event = {
    json: { padding: 2 }
  };

  lambda.handler(event, context);

} catch (e) {
  console.log(e.stack);
  console.error(e);
}


================================================
FILE: src/App.js
================================================
/* @flow */
import Provisioner from './Provisioner';
import Stats from './utils/Stats';
import CostEstimation from './utils/CostEstimation';
import Throughput from './utils/Throughput';
import CapacityCalculator from './CapacityCalculator';
import { json, stats, log, invariant } from './Global';
import type { UpdateTableRequest } from 'aws-sdk';

export default class App {
  _provisioner: Provisioner;
  _capacityCalculator: CapacityCalculator;

  constructor() {
    this._provisioner = new Provisioner();
    this._capacityCalculator = new CapacityCalculator();
  }

  async runAsync(event: any, context: any): Promise<void> {
    invariant(event != null, 'The argument \'event\' was null');
    invariant(context != null, 'The argument \'context\' was null');

    let sw = stats.timer('Index.handler').start();

    // In local mode the json padding can be overridden
    if (event.json && event.json.padding) {
      json.padding = event.json.padding;
    }

    log('Getting table names');
    let tableNames = await this._provisioner.getTableNamesAsync();

    log('Getting table details');
    let tableDetails = await this._getTableDetailsAsync(tableNames);

    log('Getting required table update requests');
    let tableUpdateRequests = this._getTableUpdateRequests(tableDetails);

    if (tableUpdateRequests.length > 0) {
      log('Updating tables');
      await this._updateTablesAsync(tableUpdateRequests);
      log('Updated tables');
    } else {
      log('No table updates required');
    }

    sw.end();
    this._logMetrics(tableDetails);

    // Return an empty response
    if (context) {
      context.succeed(null);
    }
  }

  async _getTableDetailsAsync(tableNames: string[]): Promise<Object[]> {
    invariant(tableNames instanceof Array, 'The argument \'tableNames\' was not an array');

    let tasks = tableNames.map(name => this._getTableDetailAsync(name));
    return await Promise.all(tasks);
  }

  async _getTableDetailAsync(tableName: string): Promise<Object> {
    invariant(typeof tableName === 'string', 'The argument \'tableName\' was not a string');

    log('Getting table description', tableName);
    let describeTableResponse = await this._provisioner.db
      .describeTableAsync({TableName: tableName});

    let tableDescription = describeTableResponse.Table;

    log('Getting table consumed capacity description', tableName);
    let consumedCapacityTableDescription = await this._capacityCalculator
      .describeTableConsumedCapacityAsync(tableDescription);

    log('Getting table update request', tableName);
    let tableUpdateRequest = await this._provisioner.getTableUpdateAsync(tableDescription,
      consumedCapacityTableDescription);

    // Log the monthlyEstimatedCost
    let totalTableProvisionedThroughput = Throughput
      .getTotalTableProvisionedThroughput(tableDescription);

    let monthlyEstimatedCost = CostEstimation
      .getMonthlyEstimatedTableCost(totalTableProvisionedThroughput);

    stats
      .counter('DynamoDB.monthlyEstimatedCost')
      .inc(monthlyEstimatedCost);

    let result = {
      tableName,
      tableDescription,
      consumedCapacityTableDescription,
      tableUpdateRequest,
      totalTableProvisionedThroughput,
      monthlyEstimatedCost,
    };

    return result;
  }

  async _updateTablesAsync(tableUpdateRequests: UpdateTableRequest[]): Promise<void> {
    invariant(tableUpdateRequests instanceof Array,
      'The argument \'tableUpdateRequests\' was not an array');

    // If we are updating more than 10 tables in a single run
    // then we must wait until each one has been completed to
    // ensure we do not hit the AWS limit of 10 concurrent updates
    let isRateLimitedUpdatingRequired = tableUpdateRequests.length > 10;
    await Promise.all(tableUpdateRequests.map(
      async req => this._updateTableAsync(req, isRateLimitedUpdatingRequired)
    ));
  }

  async _updateTableAsync(tableUpdateRequest: UpdateTableRequest,
    isRateLimitedUpdatingRequired: boolean): Promise<void> {
    invariant(tableUpdateRequest != null, 'The argument \'tableUpdateRequest\' was null');
    invariant(typeof isRateLimitedUpdatingRequired === 'boolean',
      'The argument \'isRateLimitedUpdatingRequired\' was not a boolean');

    log('Updating table', tableUpdateRequest.TableName);
    await this._provisioner.db
      .updateTableWithRateLimitAsync(tableUpdateRequest, isRateLimitedUpdatingRequired);

    log('Updated table', tableUpdateRequest.TableName);
  }

  _getTableUpdateRequests(tableDetails: Object[]): UpdateTableRequest[] {
    invariant(tableDetails instanceof Array,
      'The argument \'tableDetails\' was not an array');

    return tableDetails
      .filter(({tableUpdateRequest}) => { return tableUpdateRequest != null; })
      .map(({tableUpdateRequest}) => tableUpdateRequest);
  }

  _logMetrics(tableDetails: Object[]) {
    invariant(tableDetails instanceof Array,
      'The argument \'tableDetails\' was not an array');

    // Log stats
    let st = new Stats(stats);
    let stJSON = st.toJSON();
    st.reset();

    // Log readable info
    let updateRequests = tableDetails.map(i => i.tableUpdateRequest).filter(i => i !== null);
    let totalMonthlyEstimatedCost = tableDetails
      .reduce((prev, curr) => prev + curr.monthlyEstimatedCost, 0);
    let totalProvisionedThroughput = tableDetails.reduce((prev, curr) => {
      return {
        ReadCapacityUnits: prev.ReadCapacityUnits +
          curr.totalTableProvisionedThroughput.ReadCapacityUnits,
        WriteCapacityUnits: prev.WriteCapacityUnits +
          curr.totalTableProvisionedThroughput.WriteCapacityUnits,
      };
    }, {ReadCapacityUnits: 0, WriteCapacityUnits: 0});

    let indexHandler = stJSON['Index.handler'] != null ? {
      mean: stJSON['Index.handler'].histogram.mean
    } : undefined;

    let dynamoDBListTablesAsync = stJSON['DynamoDB.listTablesAsync'] != null ? {
      mean: stJSON['DynamoDB.listTablesAsync'].histogram.mean,
    } : undefined;

    let dynamoDBDescribeTableAsync = stJSON['DynamoDB.describeTableAsync'] != null ? {
      mean: stJSON['DynamoDB.describeTableAsync'].histogram.mean,
    } : undefined;

    let dynamoDBDescribeTableConsumedCapacityAsync =
      stJSON['DynamoDB.describeTableConsumedCapacityAsync'] != null ?
        { mean: stJSON['DynamoDB.describeTableConsumedCapacityAsync'].histogram.mean } :
        undefined;

    let cloudWatchGetMetricStatisticsAsync =
      stJSON['CloudWatch.getMetricStatisticsAsync'] != null ?
        { mean: stJSON['CloudWatch.getMetricStatisticsAsync'].histogram.mean } :
        undefined;

    let tableUpdates = updateRequests != null ? { count: updateRequests.length } :
      undefined;

    log(JSON.stringify({
      'Index.handler': indexHandler,
      'DynamoDB.listTablesAsync': dynamoDBListTablesAsync,
      'DynamoDB.describeTableAsync': dynamoDBDescribeTableAsync,
      'DynamoDB.describeTableConsumedCapacityAsync': dynamoDBDescribeTableConsumedCapacityAsync,
      'CloudWatch.getMetricStatisticsAsync': cloudWatchGetMetricStatisticsAsync,
      TableUpdates: tableUpdates,
      TotalProvisionedThroughput: totalProvisionedThroughput,
      TotalMonthlyEstimatedCost: totalMonthlyEstimatedCost,
    }, null, json.padding));
  }
}


================================================
FILE: src/CapacityCalculator.js
================================================
/* @flow */
import { invariant } from './Global';
import { Region } from './configuration/Region';
import CapacityCalculatorBase from './capacity/CapacityCalculatorBase';
import type { GetMetricStatisticsResponse } from 'aws-sdk';
import type { StatisticSettings } from './flow/FlowTypes';

export default class CapacityCalculator extends CapacityCalculatorBase {

  // Get the region
  getCloudWatchRegion() {
    return Region;
  }

  getStatisticSettings(): StatisticSettings {
    return {
      count: 5,
      spanMinutes: 1,
      type: 'Sum',
    };
  }

  getThrottledEventStatisticSettings(): StatisticSettings {
    return {
      count: 1,
      spanMinutes: 1,
      type: 'Sum',
    };
  }

  // Gets the projected capacity value based on the cloudwatch datapoints
  getProjectedValue(settings: StatisticSettings, data: GetMetricStatisticsResponse) {
    invariant(data != null, 'Parameter \'data\' is not set');

    if (data.Datapoints.length === 0) {
      return 0;
    }

    // Default algorithm for projecting a good value for the current ConsumedThroughput is:
    // 1. Query 5 average readings each spanning a minute
    // 2. Select the Max value from those 5 readings
    let spanSeconds = settings.spanMinutes * 60;
    let averages = data.Datapoints.map(dp => dp.Sum / spanSeconds);
    let projectedValue = Math.max(...averages);
    return projectedValue;
  }
}


================================================
FILE: src/Global.js
================================================
/* @flow */
import measured from 'measured';
import _warning from 'warning';
import _invariant from 'invariant';

export const json = { padding: 0 };

export const stats = measured.createCollection();

export const log = (...params: any[]) => {
  // eslint-disable-next-line no-console
  console.log(...params);
};

export const warning = (predicateOrValue: any, value: ?any) => {
  if (value == null) {
    _warning(false, predicateOrValue);
  } else {
    _warning(predicateOrValue, value);
  }
};

export const invariant = (predicateOrValue: any, value: ?any) => {
  if (value == null) {
    _invariant(false, predicateOrValue);
  } else {
    _invariant(predicateOrValue, value);
  }
};


================================================
FILE: src/Index.js
================================================
/* @flow */
/* eslint-disable */
// $FlowIgnore
import babelPolyfill from 'babel-polyfill';
/* eslint-enable */
// $FlowIgnore
import dotenv from 'dotenv';
import App from './App';
import { log } from './Global';

log('*** LAMBDA INIT ***');
export let handler = async (event: any, context: any) => {
  try {
    dotenv.config({path: 'config.env'});

    let app = new App();
    log('*** LAMBDA START ***');
    await app.runAsync(event, context);
  } catch (e) {
    log('*** LAMBDA ERROR ***');
    if (context) {
      context.fail(e);
    } else {
      throw e;
    }
  } finally {
    log('*** LAMBDA FINISH ***');
  }
};


================================================
FILE: src/Provisioner.js
================================================
/* @flow */
/* eslint-disable max-len */
import ProvisionerConfigurableBase from './provisioning/ProvisionerConfigurableBase';
import RateLimitedDecrement from './utils/RateLimitedDecrement';
import Throughput from './utils/Throughput';
import ProvisionerLogging from './provisioning/ProvisionerLogging';
import { Region } from './configuration/Region';
import DefaultProvisioner from './configuration/DefaultProvisioner';
import { invariant } from './Global';
import type { TableProvisionedAndConsumedThroughput, ProvisionerConfig, AdjustmentContext } from './flow/FlowTypes';

export default class Provisioner extends ProvisionerConfigurableBase {

  // Get the region
  getDynamoDBRegion(): string {
    return Region;
  }

  // Gets the list of tables which we want to autoscale
  async getTableNamesAsync(): Promise<string[]> {

    // Option 1 - All tables (Default)
    return await this.db.listAllTableNamesAsync();

    // Option 2 - Hardcoded list of tables
    // return ['Table1', 'Table2', 'Table3'];

    // Option 3 - DynamoDB / S3 configured list of tables
    // return await ...;
  }

  // Gets the json settings which control how the specifed table will be autoscaled
  // eslint-disable-next-line no-unused-vars
  getTableConfig(data: TableProvisionedAndConsumedThroughput): ProvisionerConfig {

    // Option 1 - Default settings for all tables
    return DefaultProvisioner;

    // Option 2 - Bespoke table specific settings
    // return data.TableName === 'Table1' ? Climbing : Default;

    // Option 3 - DynamoDB / S3 sourced table specific settings
    // return await ...;
  }

  isReadCapacityIncrementRequired(data: TableProvisionedAndConsumedThroughput): boolean {
    invariant(data != null, 'Parameter \'data\' is not set');

    let config = this.getTableConfig(data);
    let adjustmentContext = this.getReadCapacityIncrementAdjustmentContext(data, config);
    return this.isCapacityAdjustmentRequired(data, adjustmentContext);
  }

  calculateIncrementedReadCapacityValue(data: TableProvisionedAndConsumedThroughput): number {
    invariant(data != null, 'Parameter \'data\' is not set');

    let config = this.getTableConfig(data);
    let adjustmentContext = this.getReadCapacityIncrementAdjustmentContext(data, config);
    return Throughput.getAdjustedCapacityUnits(adjustmentContext);
  }

  isReadCapacityDecrementRequired(data: TableProvisionedAndConsumedThroughput): boolean {
    invariant(data != null, 'Parameter \'data\' is not set');

    let config = this.getTableConfig(data);
    let adjustmentContext = this.getReadCapacityDecrementAdjustmentContext(data, config);
    return this.isCapacityAdjustmentRequired(data, adjustmentContext);
  }

  calculateDecrementedReadCapacityValue(data: TableProvisionedAndConsumedThroughput): number {
    invariant(data != null, 'Parameter \'data\' is not set');

    let config = this.getTableConfig(data);
    let adjustmentContext = this.getReadCapacityDecrementAdjustmentContext(data, config);
    return Throughput.getAdjustedCapacityUnits(adjustmentContext);
  }

  isWriteCapacityIncrementRequired(data: TableProvisionedAndConsumedThroughput): boolean {
    invariant(data != null, 'Parameter \'data\' is not set');

    let config = this.getTableConfig(data);
    let adjustmentContext = this.getWriteCapacityIncrementAdjustmentContext(data, config);
    return this.isCapacityAdjustmentRequired(data, adjustmentContext);
  }

  calculateIncrementedWriteCapacityValue(data: TableProvisionedAndConsumedThroughput): number {
    invariant(data != null, 'Parameter \'data\' is not set');

    let config = this.getTableConfig(data);
    let adjustmentContext = this.getWriteCapacityIncrementAdjustmentContext(data, config);
    return Throughput.getAdjustedCapacityUnits(adjustmentContext);
  }

  isWriteCapacityDecrementRequired(data: TableProvisionedAndConsumedThroughput): boolean {
    invariant(data != null, 'Parameter \'data\' is not set');

    let config = this.getTableConfig(data);
    let adjustmentContext = this.getWriteCapacityDecrementAdjustmentContext(data, config);
    return this.isCapacityAdjustmentRequired(data, adjustmentContext);
  }

  calculateDecrementedWriteCapacityValue(data: TableProvisionedAndConsumedThroughput): number {
    invariant(data != null, 'Parameter \'data\' is not set');

    let config = this.getTableConfig(data);
    let adjustmentContext = this.getWriteCapacityDecrementAdjustmentContext(data, config);
    return Throughput.getAdjustedCapacityUnits(adjustmentContext);
  }

  getReadCapacityIncrementAdjustmentContext(data: TableProvisionedAndConsumedThroughput, config: ProvisionerConfig): AdjustmentContext {
    invariant(data != null, 'Argument \'data\' cannot be null');
    invariant(config != null, 'Argument \'config\' cannot be null');

    let context = {
      TableName: data.TableName,
      IndexName: data.IndexName,
      CapacityType: 'read',
      AdjustmentType: 'increment',
      ProvisionedValue: data.ProvisionedThroughput.ReadCapacityUnits,
      ConsumedValue: data.ConsumedThroughput.ReadCapacityUnits,
      ThrottledEvents: data.ThrottledEvents.ThrottledReadEvents,
      UtilisationPercent: (data.ConsumedThroughput.ReadCapacityUnits / data.ProvisionedThroughput.ReadCapacityUnits) * 100,
      CapacityConfig: config.ReadCapacity,
    };

    if (config.ReadCapacity.Increment != null) {
      // $FlowIgnore
      context.CapacityAdjustmentConfig = config.ReadCapacity.Increment;
    }

    return context;
  }

  getReadCapacityDecrementAdjustmentContext(data: TableProvisionedAndConsumedThroughput, config: ProvisionerConfig): AdjustmentContext {
    invariant(data != null, 'Argument \'data\' cannot be null');
    invariant(config != null, 'Argument \'config\' cannot be null');

    let context = {
      TableName: data.TableName,
      IndexName: data.IndexName,
      CapacityType: 'read',
      AdjustmentType: 'decrement',
      ProvisionedValue: data.ProvisionedThroughput.ReadCapacityUnits,
      ConsumedValue: data.ConsumedThroughput.ReadCapacityUnits,
      ThrottledEvents: data.ThrottledEvents.ThrottledReadEvents,
      UtilisationPercent: (data.ConsumedThroughput.ReadCapacityUnits / data.ProvisionedThroughput.ReadCapacityUnits) * 100,
      CapacityConfig: config.ReadCapacity,
    };

    if (config.ReadCapacity.Decrement != null) {
      // $FlowIgnore
      context.CapacityAdjustmentConfig = config.ReadCapacity.Decrement;
    }

    return context;
  }

  getWriteCapacityIncrementAdjustmentContext(data: TableProvisionedAndConsumedThroughput, config: ProvisionerConfig): AdjustmentContext {
    invariant(data != null, 'Argument \'data\' cannot be null');
    invariant(config != null, 'Argument \'config\' cannot be null');

    let context = {
      TableName: data.TableName,
      IndexName: data.IndexName,
      CapacityType: 'write',
      AdjustmentType: 'increment',
      ProvisionedValue: data.ProvisionedThroughput.WriteCapacityUnits,
      ConsumedValue: data.ConsumedThroughput.WriteCapacityUnits,
      ThrottledEvents: data.ThrottledEvents.ThrottledWriteEvents,
      UtilisationPercent: (data.ConsumedThroughput.WriteCapacityUnits / data.ProvisionedThroughput.WriteCapacityUnits) * 100,
      CapacityConfig: config.WriteCapacity,
    };

    if (config.WriteCapacity.Increment != null) {
      // $FlowIgnore
      context.CapacityAdjustmentConfig = config.WriteCapacity.Increment;
    }

    return context;
  }

  getWriteCapacityDecrementAdjustmentContext(data: TableProvisionedAndConsumedThroughput, config: ProvisionerConfig): AdjustmentContext {
    invariant(data != null, 'Argument \'data\' cannot be null');
    invariant(config != null, 'Argument \'config\' cannot be null');

    let context = {
      TableName: data.TableName,
      IndexName: data.IndexName,
      CapacityType: 'write',
      AdjustmentType: 'decrement',
      ProvisionedValue: data.ProvisionedThroughput.WriteCapacityUnits,
      ConsumedValue: data.ConsumedThroughput.WriteCapacityUnits,
      ThrottledEvents: data.ThrottledEvents.ThrottledWriteEvents,
      UtilisationPercent: (data.ConsumedThroughput.WriteCapacityUnits / data.ProvisionedThroughput.WriteCapacityUnits) * 100,
      CapacityConfig: config.WriteCapacity,
    };

    if (config.WriteCapacity.Decrement != null) {
      // $FlowIgnore
      context.CapacityAdjustmentConfig = config.WriteCapacity.Decrement;
    }

    return context;
  }

  isCapacityAdjustmentRequired(data: TableProvisionedAndConsumedThroughput, adjustmentContext: AdjustmentContext): boolean {

    // Determine if an adjustment is wanted
    let isProvAboveMax = adjustmentContext.CapacityConfig.Max == null ? false : adjustmentContext.ProvisionedValue > adjustmentContext.CapacityConfig.Max;
    let isProvBelowMax = adjustmentContext.CapacityConfig.Max == null ? true : adjustmentContext.ProvisionedValue < adjustmentContext.CapacityConfig.Max;
    let isProvBelowMin = adjustmentContext.CapacityConfig.Min == null ? adjustmentContext.ProvisionedValue < 1 : adjustmentContext.ProvisionedValue < adjustmentContext.CapacityConfig.Min;
    let isProvAboveMin = adjustmentContext.CapacityConfig.Min == null ? adjustmentContext.ProvisionedValue > 1 : adjustmentContext.ProvisionedValue > adjustmentContext.CapacityConfig.Min;
    let isUtilAboveThreshold = this.isAboveThreshold(adjustmentContext);
    let isUtilBelowThreshold = this.isBelowThreshold(adjustmentContext);
    let isThrottledEventsAboveThreshold = this.isThrottledEventsAboveThreshold(adjustmentContext);
    let isAdjustmentWanted = adjustmentContext.AdjustmentType === 'increment' ?
      (isProvBelowMin || isUtilAboveThreshold || isUtilBelowThreshold || isThrottledEventsAboveThreshold) && isProvBelowMax :
      (isProvAboveMax || isUtilAboveThreshold || isUtilBelowThreshold) && isProvAboveMin;

    // Determine if an adjustment is allowed under the rate limiting rules
    let isAfterLastDecreaseGracePeriod = adjustmentContext.CapacityAdjustmentConfig == null ||
      this.isAfterLastAdjustmentGracePeriod(data.ProvisionedThroughput.LastDecreaseDateTime,
        adjustmentContext.CapacityAdjustmentConfig.When.AfterLastDecrementMinutes);
    let isAfterLastIncreaseGracePeriod = adjustmentContext.CapacityAdjustmentConfig == null ||
      this.isAfterLastAdjustmentGracePeriod(data.ProvisionedThroughput.LastIncreaseDateTime,
        adjustmentContext.CapacityAdjustmentConfig.When.AfterLastIncrementMinutes);

    let isReadDecrementAllowed = adjustmentContext.AdjustmentType === 'decrement' ?
      RateLimitedDecrement.isDecrementAllowed(data, adjustmentContext, d => this.calculateDecrementedReadCapacityValue(d)) :
      true;

    let isAdjustmentAllowed = isAfterLastDecreaseGracePeriod && isAfterLastIncreaseGracePeriod && isReadDecrementAllowed;

    // Package up the configuration and the results so that we can produce
    // some effective logs
    let adjustmentData = {
      isAboveMax: isProvAboveMax,
      isBelowMin: isProvBelowMin,
      isAboveThreshold: isUtilAboveThreshold,
      isBelowThreshold: isUtilBelowThreshold,
      isAboveThrottledEventThreshold: isThrottledEventsAboveThreshold,
      isAfterLastDecreaseGracePeriod,
      isAfterLastIncreaseGracePeriod,
      isAdjustmentWanted,
      isAdjustmentAllowed
    };

    // Log and return result
    ProvisionerLogging.isAdjustmentRequiredLog(adjustmentContext, adjustmentData);
    return isAdjustmentWanted && isAdjustmentAllowed;
  }

  isThrottledEventsAboveThreshold(context: AdjustmentContext): boolean {
    invariant(context != null, 'Parameter \'context\' is not set');

    if (context.CapacityAdjustmentConfig == null ||
      context.CapacityAdjustmentConfig.When.ThrottledEventsPerMinuteIsAbove == null ||
      context.AdjustmentType === 'decrement') {
      return false;
    }

    return context.ThrottledEvents >
      context.CapacityAdjustmentConfig.When.ThrottledEventsPerMinuteIsAbove;
  }

  isAboveThreshold(context: AdjustmentContext): boolean {
    invariant(context != null, 'Parameter \'context\' is not set');

    if (context.CapacityAdjustmentConfig == null ||
      context.CapacityAdjustmentConfig.When.UtilisationIsAbovePercent == null) {
      return false;
    }

    let utilisationPercent = (context.ConsumedValue / context.ProvisionedValue) * 100;
    return utilisationPercent > context.CapacityAdjustmentConfig.When.UtilisationIsAbovePercent;
  }

  isBelowThreshold(context: AdjustmentContext): boolean {
    invariant(context != null, 'Parameter \'context\' is not set');

    if (context.CapacityAdjustmentConfig == null ||
      context.CapacityAdjustmentConfig.When.UtilisationIsBelowPercent == null) {
      return false;
    }

    let utilisationPercent = (context.ConsumedValue / context.ProvisionedValue) * 100;
    return utilisationPercent < context.CapacityAdjustmentConfig.When.UtilisationIsBelowPercent;
  }

  isAfterLastAdjustmentGracePeriod(lastAdjustmentDateTime: string, afterLastAdjustmentMinutes?: number): boolean {
    if (lastAdjustmentDateTime == null || afterLastAdjustmentMinutes == null) {
      return true;
    }

    let lastDecreaseDateTime = new Date(Date.parse(lastAdjustmentDateTime));
    let thresholdDateTime = new Date(Date.now());
    thresholdDateTime.setMinutes(thresholdDateTime.getMinutes() - (afterLastAdjustmentMinutes));
    return lastDecreaseDateTime < thresholdDateTime;
  }
}


================================================
FILE: src/aws/CloudWatch.js
================================================
/* @flow */
import AWS from 'aws-sdk';
import { json, stats, warning, invariant } from '../Global';
import type {
  CloudWatchOptions,
  GetMetricStatisticsRequest,
  GetMetricStatisticsResponse,
} from 'aws-sdk';

export default class CloudWatch {
  _cw: AWS.CloudWatch;

  constructor(cloudWatchOptions: CloudWatchOptions) {
    invariant(cloudWatchOptions != null, 'Parameter \'cloudWatchOptions\' is not set');
    this._cw = new AWS.CloudWatch(cloudWatchOptions);
  }

  static create(region: string): CloudWatch {
    var options = {
      region,
      apiVersion: '2010-08-01',
      httpOptions: { timeout: 5000 }
    };

    return new CloudWatch(options);
  }

  async getMetricStatisticsAsync(params: GetMetricStatisticsRequest)
    : Promise<GetMetricStatisticsResponse> {
    let sw = stats.timer('CloudWatch.getMetricStatisticsAsync').start();
    try {
      invariant(params != null, 'Parameter \'params\' is not set');
      return await this._cw.getMetricStatistics(params).promise();
    } catch (ex) {
      warning(JSON.stringify({
        class: 'CloudWatch',
        function: 'getMetricStatisticsAsync',
        params
      }, null, json.padding));
      throw ex;
    } finally {
      sw.end();
    }
  }
}


================================================
FILE: src/aws/DynamoDB.js
================================================
/* @flow */
import AWS from 'aws-sdk';
import { json, stats, warning, invariant } from '../Global';
import Delay from '../utils/Delay';
import Async from 'async';
import type {
  DynamoDBOptions,
  DescribeTableRequest,
  DescribeTableResponse,
  UpdateTableRequest,
  UpdateTableResponse,
  ListTablesRequest,
  ListTablesResponse,
} from 'aws-sdk';

export default class DynamoDB {
  _db: AWS.DynamoDB;
  _updatePool: Object;

  constructor(dynamoOptions: DynamoDBOptions) {
    invariant(dynamoOptions != null, 'Parameter \'dynamoOptions\' is not set');
    this._db = new AWS.DynamoDB(dynamoOptions);
    this._updatePool = Async.queue(async (params, callback) => {
      let result = await this.updateTableAndWaitAsync(params, true);
      callback(result);
    }, 10);
  }

  static create(region: string): DynamoDB {
    var options = {
      region,
      apiVersion: '2012-08-10',
      dynamoDbCrc32: false,
      httpOptions: { timeout: 5000 }
    };

    return new DynamoDB(options);
  }

  async listTablesAsync(params: ?ListTablesRequest): Promise<ListTablesResponse> {
    let sw = stats.timer('DynamoDB.listTablesAsync').start();
    try {
      return await this._db.listTables(params).promise();
    } catch (ex) {
      warning(JSON.stringify({
        class: 'DynamoDB',
        function: 'listTablesAsync'
      }, null, json.padding));
      throw ex;
    } finally {
      sw.end();
    }
  }

  async listAllTableNamesAsync(): Promise<string[]> {
    let tableNames = [];
    let lastTable;
    do {
      let listTablesResponse = await this.listTablesAsync({ ExclusiveStartTableName: lastTable });
      tableNames = tableNames.concat(listTablesResponse.TableNames);
      lastTable = listTablesResponse.LastEvaluatedTableName;
    } while (lastTable);
    return tableNames;
  }

  async describeTableAsync(params: DescribeTableRequest): Promise<DescribeTableResponse> {
    let sw = stats.timer('DynamoDB.describeTableAsync').start();
    try {
      invariant(params != null, 'Parameter \'params\' is not set');
      return await this._db.describeTable(params).promise();
    } catch (ex) {
      warning(JSON.stringify({
        class: 'DynamoDB',
        function: 'describeTableAsync',
        params
      }, null, json.padding));
      throw ex;
    } finally {
      sw.end();
    }
  }

  async delayUntilTableIsActiveAsync(tableName: string): Promise<void> {
    let isActive = false;
    let attempt = 0;
    do {
      let result = await this.describeTableAsync({ TableName: tableName });
      isActive = result.Table.TableStatus === 'ACTIVE';
      if (!isActive) {
        await Delay.delayAsync(1000);
        attempt++;
      }
    } while (!isActive && attempt < 10);
  }

  updateTableWithRateLimitAsync(params: UpdateTableRequest,
    isRateLimited: boolean): Promise<UpdateTableResponse> {

    if (!isRateLimited) {
      return this.updateTableAndWaitAsync(params, isRateLimited);
    }

    return new Promise((resolve, reject) => {
      let sw = stats.timer('DynamoDB.updateTableAsync').start();
      try {
        invariant(params != null, 'Parameter \'params\' is not set');
        this._updatePool.push(params, resolve);
      } catch (ex) {
        warning(JSON.stringify({
          class: 'DynamoDB',
          function: 'updateTableAsync',
          params
        }, null, json.padding));
        reject(ex);
      } finally {
        sw.end();
      }
    });
  }

  async updateTableAndWaitAsync(params: UpdateTableRequest,
    isRateLimited: boolean): Promise<UpdateTableResponse> {

    let response = await this._db.updateTable(params).promise();
    if (isRateLimited) {
      await this.delayUntilTableIsActiveAsync(params.TableName);
    }

    return response;
  }

  async updateTableAsync(params: UpdateTableRequest): Promise<UpdateTableResponse> {
    let sw = stats.timer('DynamoDB.updateTableAsync').start();
    try {
      invariant(params != null, 'Parameter \'params\' is not set');
      return await this._db.updateTable(params).promise();
    } catch (ex) {
      warning(JSON.stringify({
        class: 'DynamoDB',
        function: 'updateTableAsync',
        params
      }, null, json.padding));
      throw ex;
    } finally {
      sw.end();
    }
  }
}


================================================
FILE: src/capacity/CapacityCalculatorBase.js
================================================
/* @flow */
import { json, stats, warning, invariant } from '../Global';
import CloudWatch from '../aws/CloudWatch';
import type {
  TableConsumedCapacityDescription,
  StatisticSettings,
  ConsumedCapacityDesc,
} from '../flow/FlowTypes';
import type {
  TableDescription,
  GetMetricStatisticsResponse,
  Dimension,
} from 'aws-sdk';

export default class CapacityCalculatorBase {
  cw: CloudWatch;

  constructor() {
    this.cw = CloudWatch.create(this.getCloudWatchRegion());
  }

  // Get the region
  getCloudWatchRegion(): string {
    invariant(false, 'The method \'getCloudWatchRegion\' was not implemented');
  }

  // Gets the settings used to fetch the consumed throughput statistics
  getStatisticSettings(): StatisticSettings {
    invariant(false, 'The method \'getStatisticSettings\' was not implemented');
  }

  // Gets the settings used to fetch the throttled events statistics
  getThrottledEventStatisticSettings(): StatisticSettings {
    invariant(false, 'The method \'getThrottledEventStatisticSettings\' was not implemented');
  }

  // Gets the projected capacity value based on the cloudwatch datapoints
  // eslint-disable-next-line no-unused-vars
  getProjectedValue(settings: StatisticSettings, data: GetMetricStatisticsResponse): number {
    invariant(false, 'The method \'getProjectedValue\' was not implemented');
  }

  async describeTableConsumedCapacityAsync(params: TableDescription)
    : Promise<TableConsumedCapacityDescription> {
    let sw = stats
      .timer('DynamoDB.describeTableConsumedCapacityAsync')
      .start();

    try {
      invariant(params != null, 'Parameter \'params\' is not set');

      // Make all the requests concurrently
      let tableRead = this.getConsumedCapacityAsync(true, params.TableName, null);
      let tableWrite = this.getConsumedCapacityAsync(false, params.TableName, null);

      let gsiReads = (params.GlobalSecondaryIndexes || [])
        .map(gsi => this.getConsumedCapacityAsync(true, params.TableName, gsi.IndexName));

      let gsiWrites = (params.GlobalSecondaryIndexes || [])
        .map(gsi => this.getConsumedCapacityAsync(false, params.TableName, gsi.IndexName));

      let tableTRead = this.getThrottledEventsAsync(true, params.TableName, null);
      let tableTWrites = this.getThrottledEventsAsync(false, params.TableName, null);

      let gsiTReads = (params.GlobalSecondaryIndexes || [])
        .map(gsi => this.getThrottledEventsAsync(true, params.TableName, gsi.IndexName));

      let gsiTWrites = (params.GlobalSecondaryIndexes || [])
        .map(gsi => this.getThrottledEventsAsync(false, params.TableName, gsi.IndexName));

      // Await on the results
      let tableConsumedRead = await tableRead;
      let tableConsumedWrite = await tableWrite;
      let gsiConsumedReads = await Promise.all(gsiReads);
      let gsiConsumedWrites = await Promise.all(gsiWrites);

      // Await on throttled info
      let tableThrottledRead = await tableTRead;
      let tableThrottledWrite = await tableTWrites;
      let gsiThrottledReads = await Promise.all(gsiTReads);
      let gsiThrottledWrites = await Promise.all(gsiTWrites);

      // Format results
      let gsis = gsiConsumedReads.map((read, i) => {
        let write = gsiConsumedWrites[i];
        let throttledWrite = gsiThrottledWrites[i];
        let throttledRead = gsiThrottledReads[i];
        let gsiIndexName = read.globalSecondaryIndexName;
        invariant(gsiIndexName != null, '\'gsiIndexName\' was null');
        return {
          IndexName: gsiIndexName,
          ConsumedThroughput: {
            ReadCapacityUnits: read.value,
            WriteCapacityUnits: write.value
          },
          ThrottledEvents: {
            ThrottledReadEvents: throttledRead,
            ThrottledWriteEvents: throttledWrite
          }
        };
      });

      return {
        TableName: params.TableName,
        ConsumedThroughput: {
          ReadCapacityUnits: tableConsumedRead.value,
          WriteCapacityUnits: tableConsumedWrite.value
        },
        ThrottledEvents: {
          ThrottledReadEvents: tableThrottledRead,
          ThrottledWriteEvents: tableThrottledWrite
        },
        GlobalSecondaryIndexes: gsis
      };
    } catch (ex) {
      warning(JSON.stringify({
        class: 'CapacityCalculator',
        function: 'describeTableConsumedCapacityAsync',
        params,
      }, null, json.padding));
      throw ex;
    } finally {
      sw.end();
    }
  }

  async getConsumedCapacityAsync(
    isRead: boolean, tableName: string, globalSecondaryIndexName: ?string):
    Promise<ConsumedCapacityDesc> {
    try {
      invariant(isRead != null, 'Parameter \'isRead\' is not set');
      invariant(tableName != null, 'Parameter \'tableName\' is not set');

      let settings = this.getStatisticSettings();

      let EndTime = new Date();
      let StartTime = new Date();
      StartTime.setTime(EndTime - (60000 * settings.spanMinutes * settings.count));
      let MetricName = isRead ? 'ConsumedReadCapacityUnits' : 'ConsumedWriteCapacityUnits';
      let Dimensions = this.getDimensions(tableName, globalSecondaryIndexName);
      let period = (settings.spanMinutes * 60);
      let params = {
        Namespace: 'AWS/DynamoDB',
        MetricName,
        Dimensions,
        StartTime,
        EndTime,
        Period: period,
        Statistics: [ settings.type ],
        Unit: 'Count'
      };

      let statistics = await this.cw.getMetricStatisticsAsync(params);
      let value = this.getProjectedValue(settings, statistics);
      let result: ConsumedCapacityDesc = {
        tableName,
        globalSecondaryIndexName,
        value
      };

/*
      log(JSON.stringify({
        ...result,
        statistics: statistics.Datapoints.map(dp => dp.Sum / (settings.spanMinutes * 60)),
      }));
*/

      return result;
    } catch (ex) {
      warning(JSON.stringify({
        class: 'CapacityCalculator',
        function: 'getConsumedCapacityAsync',
        isRead, tableName, globalSecondaryIndexName,
      }, null, json.padding));
      throw ex;
    }
  }

  async getThrottledEventsAsync(
    isRead: boolean, tableName: string, globalSecondaryIndexName: ?string):
    Promise<number> {
    try {
      invariant(isRead != null, 'Parameter \'isRead\' is not set');
      invariant(tableName != null, 'Parameter \'tableName\' is not set');

      let settings = this.getThrottledEventStatisticSettings();

      let EndTime = new Date();
      let StartTime = new Date();
      StartTime.setTime(EndTime - (60000 * settings.spanMinutes * settings.count));
      let MetricName = isRead ? 'ReadThrottleEvents' : 'WriteThrottleEvents';
      let Dimensions = this.getDimensions(tableName, globalSecondaryIndexName);
      let period = (settings.spanMinutes * 60);
      let params = {
        Namespace: 'AWS/DynamoDB',
        MetricName,
        Dimensions,
        StartTime,
        EndTime,
        Period: period,
        Statistics: [ settings.type ],
        Unit: 'Count'
      };

      let statistics = await this.cw.getMetricStatisticsAsync(params);
      let value = this.getProjectedValue(settings, statistics);

      return value;
    } catch (ex) {
      warning(JSON.stringify({
        class: 'CapacityCalculator',
        function: 'getThrottledEventsAsync',
        isRead, tableName, globalSecondaryIndexName,
      }, null, json.padding));
      throw ex;
    }
  }

  getDimensions(tableName: string, globalSecondaryIndexName: ?string): Dimension[] {
    if (globalSecondaryIndexName) {
      return [
        { Name: 'TableName', Value: tableName},
        { Name: 'GlobalSecondaryIndexName', Value: globalSecondaryIndexName}
      ];
    }

    return [ { Name: 'TableName', Value: tableName} ];
  }
}


================================================
FILE: src/configuration/ClimbingProvisioner.json
================================================
{
  "ReadCapacity": {
    "Min": 1,
    "Max": 10,
    "Increment": {
      "When": {
        "UtilisationIsAbovePercent": 80
      },
      "By": {
        "ConsumedPercent": 30,
        "Units": 3
      }
    },
    "Decrement": {
      "When": {
        "UtilisationIsBelowPercent": 30,
        "AfterLastIncrementMinutes": 60,
        "AfterLastDecrementMinutes": 60,
        "UnitAdjustmentGreaterThan": 5
      },
      "To": {
        "ConsumedPercent": 100
      }
    }
  },
  "WriteCapacity": {
    "Min": 1,
    "Max": 10,
    "Increment": {
      "When": {
        "UtilisationIsAbovePercent": 80
      },
      "By": {
        "ProvisionedPercent": 30,
        "Units": 3
      }
    },
    "Decrement": {
      "When": {
        "UtilisationIsBelowPercent": 30,
        "AfterLastIncrementMinutes": 60,
        "AfterLastDecrementMinutes": 60,
        "UnitAdjustmentGreaterThan": 5
      },
      "To": {
        "ConsumedPercent": 100
      }
    }
  }
}


================================================
FILE: src/configuration/DefaultProvisioner.json
================================================
{
  "ReadCapacity": {
    "Min": 1,
    "Max": 100,
    "Increment": {
      "When": {
        "UtilisationIsAbovePercent": 75,
        "ThrottledEventsPerMinuteIsAbove": 25
      },
      "By": {
        "Units": 3,
        "ProvisionedPercent": 30,
        "ThrottledEventsWithMultiplier": 0.7
      },
      "To": {
        "ConsumedPercent": 130
      }
    },
    "Decrement": {
      "When": {
        "UtilisationIsBelowPercent": 30,
        "AfterLastIncrementMinutes": 60,
        "AfterLastDecrementMinutes": 60,
        "UnitAdjustmentGreaterThan": 5
      },
      "To": {
        "ConsumedPercent": 100
      }
    }
  },
  "WriteCapacity": {
    "Min": 1,
    "Max": 100,
    "Increment": {
      "When": {
        "UtilisationIsAbovePercent": 75,
        "ThrottledEventsPerMinuteIsAbove": 25
      },
      "By": {
        "Units": 3,
        "ProvisionedPercent": 30,
        "ThrottledEventsWithMultiplier": 0.7
      },
      "To": {
        "ConsumedPercent": 130
      }
    },
    "Decrement": {
      "When": {
        "UtilisationIsBelowPercent": 30,
        "AfterLastIncrementMinutes": 60,
        "AfterLastDecrementMinutes": 60,
        "UnitAdjustmentGreaterThan": 5
      },
      "To": {
        "ConsumedPercent": 100
      }
    }
  }
}


================================================
FILE: src/configuration/FixedProvisioner.json
================================================
{
  "ReadCapacity": {
    "Min": 1,
    "Max": 1
  },
  "WriteCapacity": {
    "Min": 1,
    "Max": 1
  }
}


================================================
FILE: src/configuration/Region.json
================================================
{
  "Region": "us-east-1"
}


================================================
FILE: src/flow/FlowTypes.js
================================================
/* @flow */
import type { ProvisionedThroughput, Throughput } from 'aws-sdk';

export type ThrottledEventsDescription = {
  ThrottledReadEvents: number,
  ThrottledWriteEvents: number
}

export type TableProvisionedAndConsumedThroughput = {
  TableName: string,
  IndexName?: string,
  ProvisionedThroughput: ProvisionedThroughput,
  ConsumedThroughput: Throughput,
  ThrottledEvents: ThrottledEventsDescription
};

export type GlobalSecondaryIndexConsumedThroughput = {
  IndexName: string,
  ConsumedThroughput: Throughput,
  ThrottledEvents: ThrottledEventsDescription,
};

export type TableConsumedCapacityDescription = {
  TableName: string,
  ConsumedThroughput: Throughput,
  GlobalSecondaryIndexes: GlobalSecondaryIndexConsumedThroughput[],
  ThrottledEvents: ThrottledEventsDescription,
};

export type ConsumedCapacityDesc = {
  tableName: string,
  globalSecondaryIndexName: ?string,
  value: number,
};

export type ProvisionerConfig = {
  ReadCapacity: CapacityConfig,
  WriteCapacity: CapacityConfig,
};

export type CapacityConfig = {
  Min?: number,
  Max?: number,
  Increment?: CapacityAdjustmentConfig,
  Decrement?: CapacityAdjustmentConfig,
};

export type CapacityAdjustmentConfig = {
  When: WhenConfig,
  By?: ByToConfig,
  To?: ByToConfig,
};

export type WhenConfig = {
  UtilisationIsAbovePercent?: number,
  UtilisationIsBelowPercent?: number,
  ThrottledEventsPerMinuteIsAbove?: number,
  AfterLastIncrementMinutes?: number,
  AfterLastDecrementMinutes?: number,
  UnitAdjustmentGreaterThan?: number,
};

export type ByToConfig = {
  ConsumedPercent?: number,
  ProvisionedPercent?: number,
  Units?: number,
  ThrottledEventsWithMultiplier?: number,
};

export type StatisticSettings = {
  count: number,
  spanMinutes: number,
  type: 'Average' | 'Sum',
};

export type AdjustmentContext = {
  TableName: string,
  IndexName?: string,
  CapacityType: 'read' | 'write',
  AdjustmentType: 'increment' | 'decrement',
  ProvisionedValue: number,
  ConsumedValue: number,
  ThrottledEvents: number,
  UtilisationPercent: number,
  CapacityConfig: CapacityConfig,
  CapacityAdjustmentConfig?: CapacityAdjustmentConfig,
};

export type AdjustmentData = {
  isAboveMax: boolean,
  isBelowMin: boolean,
  isAboveThreshold: boolean,
  isBelowThreshold: boolean,
  isAboveThrottledEventThreshold: boolean,
  isAfterLastDecreaseGracePeriod: boolean,
  isAfterLastIncreaseGracePeriod: boolean,
  isAdjustmentWanted: boolean,
  isAdjustmentAllowed: boolean
};


================================================
FILE: src/provisioning/ProvisionerBase.js
================================================
/* @flow */
/* eslint-disable no-unused-vars */
import { invariant } from '../Global';
import type { TableDescription, UpdateTableRequest } from 'aws-sdk';
import type { TableConsumedCapacityDescription } from '../flow/FlowTypes';
import DynamoDB from '../aws/DynamoDB';
import CloudWatch from '../aws/CloudWatch';

export default class ProvisionerBase {
  db: DynamoDB;

  constructor() {
    this.db = DynamoDB.create(this.getDynamoDBRegion());
  }

  getDynamoDBRegion(): string {
    invariant(false, 'The method \'getDynamoDBRegion\' was not implemented');
  }

  async getTableNamesAsync(): Promise<string[]> {
    invariant(false, 'The method \'getTableNamesAsync\' was not implemented');
  }

  async getTableUpdateAsync(
    tableDescription: TableDescription,
    tableConsumedCapacityDescription: TableConsumedCapacityDescription):
    Promise<?UpdateTableRequest> {
    invariant(false, 'The method \'getTableUpdateAsync\' was not implemented');
  }
}


================================================
FILE: src/provisioning/ProvisionerConfigurableBase.js
================================================
/* @flow */
import { json, warning, invariant } from '../Global';
import ProvisionerBase from '../provisioning/ProvisionerBase';
import type {
  TableDescription,
  GlobalSecondaryIndex,
  UpdateTableRequest,
  GlobalSecondaryIndexUpdate,
  Throughput,
} from 'aws-sdk';
import type {
  TableProvisionedAndConsumedThroughput,
  TableConsumedCapacityDescription,
} from '../flow/FlowTypes';

export default class ProvisionerConfigurableBase extends ProvisionerBase {

  // eslint-disable-next-line no-unused-vars
  isReadCapacityIncrementRequired(data: TableProvisionedAndConsumedThroughput): boolean {
    invariant(false, 'The method \'isReadCapacityIncrementRequired\' was not implemented');
  }

  // eslint-disable-next-line no-unused-vars
  calculateIncrementedReadCapacityValue(data: TableProvisionedAndConsumedThroughput): number {
    invariant(false, 'The method \'calculateIncrementedReadCapacityValue\' was not implemented');
  }

  // eslint-disable-next-line no-unused-vars
  isReadCapacityDecrementRequired(data: TableProvisionedAndConsumedThroughput): boolean {
    invariant(false, 'The method \'isReadCapacityDecrementRequired\' was not implemented');
  }

  // eslint-disable-next-line no-unused-vars
  calculateDecrementedReadCapacityValue(data: TableProvisionedAndConsumedThroughput): number {
    invariant(false, 'The method \'calculateDecrementedReadCapacityValue\' was not implemented');
  }

  // eslint-disable-next-line no-unused-vars
  isWriteCapacityIncrementRequired(data: TableProvisionedAndConsumedThroughput): boolean {
    invariant(false, 'The method \'isWriteCapacityIncrementRequired\' was not implemented');
  }

  // eslint-disable-next-line no-unused-vars
  calculateIncrementedWriteCapacityValue(data: TableProvisionedAndConsumedThroughput): number {
    invariant(false, 'The method \'calculateIncrementedWriteCapacityValue\' was not implemented');
  }

  // eslint-disable-next-line no-unused-vars
  isWriteCapacityDecrementRequired(data: TableProvisionedAndConsumedThroughput): boolean {
    invariant(false, 'The method \'isWriteCapacityDecrementRequired\' was not implemented');
  }

  // eslint-disable-next-line no-unused-vars
  calculateDecrementedWriteCapacityValue(data: TableProvisionedAndConsumedThroughput): number {
    invariant(false, 'The method \'calculateDecrementedWriteCapacityValue\' was not implemented');
  }

  async getTableNamesAsync(): Promise<string[]> {
    invariant(false, 'The method \'getTableNamesAsync\' was not implemented');
  }

  async getTableUpdateAsync(tableDescription: TableDescription,
    tableConsumedCapacityDescription: TableConsumedCapacityDescription) :
    Promise<?UpdateTableRequest> {
    try {
      invariant(tableDescription != null, 'Parameter \'tableDescription\' is not set');
      invariant(tableConsumedCapacityDescription != null,
        'Parameter \'tableConsumedCapacityDescription\' is not set');

      let tableData = {
        TableName: tableDescription.TableName,
        ProvisionedThroughput: tableDescription.ProvisionedThroughput,
        ConsumedThroughput: tableConsumedCapacityDescription.ConsumedThroughput,
        ThrottledEvents: tableConsumedCapacityDescription.ThrottledEvents
      };

      let provisionedThroughput = this.getUpdatedProvisionedThroughput(tableData);

      let gsis = tableDescription.GlobalSecondaryIndexes || [];
      let globalSecondaryIndexUpdates = gsis
        // $FlowIgnore
        .map(gsi => this.getGlobalSecondaryIndexUpdate(
          tableDescription, tableConsumedCapacityDescription, gsi))
        .filter(i => i !== null);

      // eslint-disable-next-line eqeqeq
      if (!provisionedThroughput && (globalSecondaryIndexUpdates == null ||
        globalSecondaryIndexUpdates.length === 0)) {
        return null;
      }

      let result: UpdateTableRequest = {
        TableName: tableDescription.TableName
      };

      if (provisionedThroughput) {
        result.ProvisionedThroughput = provisionedThroughput;
      }

      if (globalSecondaryIndexUpdates && globalSecondaryIndexUpdates.length > 0) {
        result.GlobalSecondaryIndexUpdates = globalSecondaryIndexUpdates;
      }

      return result;
    } catch (e) {
      warning(JSON.stringify({
        class: 'ConfigurableProvisioner',
        function: 'getTableUpdate',
        tableDescription,
        tableConsumedCapacityDescription
      }, null, json.padding));
      throw e;
    }
  }

  getUpdatedProvisionedThroughput(params: TableProvisionedAndConsumedThroughput)
    : ?Throughput {
    try {
      invariant(params != null, 'Parameter \'params\' is not set');

      let newProvisionedThroughput = {
        ReadCapacityUnits: params.ProvisionedThroughput.ReadCapacityUnits,
        WriteCapacityUnits: params.ProvisionedThroughput.WriteCapacityUnits
      };

      // Adjust read capacity
      if (this.isReadCapacityIncrementRequired(params)) {
        newProvisionedThroughput.ReadCapacityUnits = this
          .calculateIncrementedReadCapacityValue(params);

      } else if (this.isReadCapacityDecrementRequired(params)) {
        newProvisionedThroughput.ReadCapacityUnits = this
          .calculateDecrementedReadCapacityValue(params);
      }

      // Adjust write capacity
      if (this.isWriteCapacityIncrementRequired(params)) {
        newProvisionedThroughput.WriteCapacityUnits = this
          .calculateIncrementedWriteCapacityValue(params);

      } else if (this.isWriteCapacityDecrementRequired(params)) {
        newProvisionedThroughput.WriteCapacityUnits = this
          .calculateDecrementedWriteCapacityValue(params);
      }

      if (newProvisionedThroughput.ReadCapacityUnits ===
        params.ProvisionedThroughput.ReadCapacityUnits &&
        newProvisionedThroughput.WriteCapacityUnits ===
        params.ProvisionedThroughput.WriteCapacityUnits) {
        return null;
      }

      return newProvisionedThroughput;
    } catch (e) {
      warning(JSON.stringify({
        class: 'ConfigurableProvisioner',
        function: 'getUpdatedProvisionedThroughput', params
      }, null, json.padding));
      throw e;
    }
  }

  getGlobalSecondaryIndexUpdate(
    tableDescription: TableDescription,
    tableConsumedCapacityDescription: TableConsumedCapacityDescription,
    gsi: GlobalSecondaryIndex): ?GlobalSecondaryIndexUpdate {
    try {
      invariant(tableDescription != null, 'Parameter \'tableDescription\' is not set');
      invariant(tableConsumedCapacityDescription != null,
        'Parameter \'tableConsumedCapacityDescription\' is not set');
      invariant(gsi != null, 'Parameter \'gsi\' is not set');

      let gsicc = tableConsumedCapacityDescription
        .GlobalSecondaryIndexes
        .find(i => i.IndexName === gsi.IndexName);

      invariant(gsicc != null, 'Specified GSI could not be found');
      let provisionedThroughput = this.getUpdatedProvisionedThroughput({
        TableName: tableDescription.TableName,
        IndexName: gsicc.IndexName,
        ProvisionedThroughput: gsi.ProvisionedThroughput,
        ConsumedThroughput: gsicc.ConsumedThroughput,
        ThrottledEvents: gsicc.ThrottledEvents
      });

      // eslint-disable-next-line eqeqeq
      if (provisionedThroughput == null) {
        return null;
      }

      return {
        Update: {
          IndexName: gsi.IndexName,
          ProvisionedThroughput: provisionedThroughput
        }
      };
    } catch (e) {
      warning(JSON.stringify({
        class: 'ConfigurableProvisioner',
        function: 'getGlobalSecondaryIndexUpdate',
        tableDescription,
        tableConsumedCapacityDescription,
        gsi
      }, null, json.padding));
      throw e;
    }
  }
}


================================================
FILE: src/provisioning/ProvisionerLogging.js
================================================
/* @flow */
import { log } from '../Global';
import type {
  AdjustmentContext,
  AdjustmentData,
} from '../flow/FlowTypes';

export default class ConfigLogging {
  static isAdjustmentRequiredLog(
    adjustmentContext: AdjustmentContext,
    adjustmentData: AdjustmentData,
  ) {

    let logMessage = typeof adjustmentContext.IndexName === 'undefined' ?
      adjustmentContext.TableName : adjustmentContext.TableName + '.' + adjustmentContext.IndexName;
    logMessage += ' is consuming ' + adjustmentContext.ConsumedValue + ' of ' +
      adjustmentContext.ProvisionedValue + ' (' + adjustmentContext.UtilisationPercent +
      '%) ' + adjustmentContext.CapacityType + ' capacity units';

    if (adjustmentContext.CapacityConfig.Max != null && adjustmentData.isAboveMax) {
      logMessage += ' and is above max allowed ' + adjustmentContext.CapacityConfig.Max + ' units';
    }

    if (adjustmentContext.CapacityAdjustmentConfig != null &&
      adjustmentContext.CapacityAdjustmentConfig.When.UtilisationIsAbovePercent != null &&
      adjustmentData.isAboveThreshold && !adjustmentData.isAboveMax) {
      logMessage += ' and is above maximum threshold of ' +
        adjustmentContext.CapacityAdjustmentConfig.When.UtilisationIsAbovePercent + '%';
    }

    if (adjustmentContext.CapacityConfig.Min != null && adjustmentData.isBelowMin) {
      logMessage += ' and is below the min allowed ' + adjustmentContext.CapacityConfig.Min +
        ' units';
    }

    if (adjustmentContext.CapacityAdjustmentConfig != null &&
      adjustmentContext.CapacityAdjustmentConfig.When.UtilisationIsBelowPercent != null &&
      adjustmentData.isBelowThreshold && !adjustmentData.isBelowMin) {
      logMessage += ' and is below minimum threshold of ' +
        adjustmentContext.CapacityAdjustmentConfig.When.UtilisationIsBelowPercent + '%';
    }

    if (adjustmentContext.CapacityAdjustmentConfig != null &&
      adjustmentContext.CapacityAdjustmentConfig.When.ThrottledEventsPerMinuteIsAbove != null &&
      adjustmentData.isAboveThrottledEventThreshold) {
      logMessage += ' and throttled events per minute is above ' +
        adjustmentContext.CapacityAdjustmentConfig.When.ThrottledEventsPerMinuteIsAbove + ' events';
    }

    if (adjustmentData.isAdjustmentWanted) {
      logMessage += adjustmentContext.AdjustmentType === 'increment' ?
        ' so an increment is WANTED' : ' so a decrement is WANTED';
      if (adjustmentData.isAdjustmentAllowed) {
        logMessage += ' and is ALLOWED';
      } else if (!adjustmentData.isAfterLastDecreaseGracePeriod) {
        logMessage += ' but is DISALLOWED due to \'AfterLastDecrementMinutes\' grace period';
      } else if (!adjustmentData.isAfterLastIncreaseGracePeriod) {
        logMessage += ' but is DISALLOWED due to \'AfterLastIncreaseMinutes\' grace period';
      } else {
        logMessage += ' but is DISALLOWED';
      }
    } else {
      logMessage += adjustmentContext.AdjustmentType === 'increment' ?
        ' so an increment is not required' : ' so a decrement is not required';
    }

    log(logMessage);
  }
}


================================================
FILE: src/utils/CostEstimation.js
================================================
/* @flow */
import { json, warning, invariant } from '../Global';
import type {
  Throughput,
} from 'aws-sdk';

export default class CostEstimation {

  static getMonthlyEstimatedTableCost(provisionedThroughput: Throughput) {
    try {
      invariant(provisionedThroughput != null, 'Parameter \'provisionedThroughput\' is not set');

      const averageHoursPerMonth = 720;
      const readCostPerHour = 0.0065;
      const readCostUnits = 50;
      const writeCostPerHour = 0.0065;
      const writeCostUnits = 10;

      let readCost = provisionedThroughput.ReadCapacityUnits /
        readCostUnits * readCostPerHour * averageHoursPerMonth;

      let writeCost = provisionedThroughput.WriteCapacityUnits /
        writeCostUnits * writeCostPerHour * averageHoursPerMonth;

      return readCost + writeCost;
    } catch (ex) {
      warning(JSON.stringify({
        class: 'CostEstimation',
        function: 'getMonthlyEstimatedTableCost',
        provisionedThroughput
      }, null, json.padding));
      throw ex;
    }
  }
}


================================================
FILE: src/utils/Delay.js
================================================
/* @flow */
import { invariant } from '../Global';

export default class Delay {

  static delayAsync(ms: number) {
    invariant(typeof ms === 'number', 'Argument \'ms\' is not a number');

    return new Promise(resolve => {
      setTimeout(resolve, ms);
    });
  }
}


================================================
FILE: src/utils/RateLimitedDecrement.js
================================================
/* @flow */
import { invariant } from '../Global';
import type {
  TableProvisionedAndConsumedThroughput,
  AdjustmentContext
} from '../flow/FlowTypes';

export default class RateLimitedDecrement {

  static isDecrementAllowed(
    data: TableProvisionedAndConsumedThroughput,
    adjustmentContext: AdjustmentContext,
    calcNewValueFunc: (data: TableProvisionedAndConsumedThroughput) => number) {

    invariant(data != null, 'Parameter \'data\' is not set');
    invariant(adjustmentContext != null, 'Parameter \'adjustmentContext\' is not set');
    invariant(calcNewValueFunc != null, 'Parameter \'calcNewValueFunc\' is not set');

    if (this.getNextAllowedDecrementDate(data, adjustmentContext) > this.getNowDate()) {
      // Disallow if we havent crossed one of four time barriers
      return false;
    }

    let adjustment = Math.abs(adjustmentContext.ProvisionedValue) -
      Math.abs(calcNewValueFunc(data));

    if (adjustmentContext.CapacityAdjustmentConfig != null &&
      adjustmentContext.CapacityAdjustmentConfig.When.UnitAdjustmentGreaterThan != null &&
      adjustment <= adjustmentContext.CapacityAdjustmentConfig.When.UnitAdjustmentGreaterThan &&
      this.getNowDate().valueOf() <
      this.getLastAllowedDecrementDate().valueOf()) {
      // Disallow if the adjustment is very small.
      // However, if we have crossed the last time
      // barrier of the day then we might as well allow it.
      return false;
    }

    return true;
  }

  static getNextAllowedDecrementDate(
    data: TableProvisionedAndConsumedThroughput,
    adjustmentContext: AdjustmentContext) {

    // Check if we have already had all the decreases we are allowed today
    if (data.ProvisionedThroughput.NumberOfDecreasesToday >= 4) {
      return this.getTomorrowDate();
    }

    // Get the last decrease or start of day
    let lastDecrease = this.parseDate(data.ProvisionedThroughput.LastDecreaseDateTime);
    let lastDecrementDate = this.getLastDecrementDate(lastDecrease);

    // Get the next allowed decrement
    let lastAllowedDecrementDate = this.getLastAllowedDecrementDate();
    let periodMs = lastAllowedDecrementDate.valueOf() - lastDecrementDate.valueOf();
    let periodMs2 = periodMs / (5 - data.ProvisionedThroughput.NumberOfDecreasesToday);
    let nextDecrementDate = this.getLastDecrementDate(lastDecrease);
    nextDecrementDate.setMilliseconds(nextDecrementDate.getMilliseconds() + periodMs2);

    // Handle grace periods
    let withIncrementGracePeriod = this.parseDate(data.ProvisionedThroughput.LastIncreaseDateTime);

    if (adjustmentContext.CapacityAdjustmentConfig != null &&
      adjustmentContext.CapacityAdjustmentConfig.When.AfterLastIncrementMinutes != null) {
      let incMins = adjustmentContext.CapacityAdjustmentConfig.When.AfterLastIncrementMinutes;
      withIncrementGracePeriod.setMinutes(withIncrementGracePeriod.getMinutes() + incMins);
    }

    let withDecrementGracePeriod = this.parseDate(data.ProvisionedThroughput.LastDecreaseDateTime);

    if (adjustmentContext.CapacityAdjustmentConfig != null &&
      adjustmentContext.CapacityAdjustmentConfig.When.AfterLastDecrementMinutes != null) {
      let decMins = adjustmentContext.CapacityAdjustmentConfig.When.AfterLastDecrementMinutes;
      withDecrementGracePeriod.setMinutes(withDecrementGracePeriod.getMinutes() + decMins);
    }

    let result = new Date(Math.max(
      nextDecrementDate, withIncrementGracePeriod, withDecrementGracePeriod));

    return result;
  }

  static getNowDate() {
    return new Date(Date.now());
  }

  static getTodayDate() {
    let value = this.getNowDate();
    value.setHours(0, 0, 0, 0);
    return value;
  }

  static getTomorrowDate() {
    let value = this.getTodayDate();
    value.setDate(value.getDate() + 1);
    return value;
  }

  static getLastAllowedDecrementDate() {
    let value = this.getTodayDate();
    value.setHours(23, 30, 0, 0);
    return value;
  }

  static getLastDecrementDate(lastDecrease) {
    let today = this.getTodayDate();
    return lastDecrease < today ? today : new Date(lastDecrease.valueOf());
  }

  static parseDate(value) {
    // eslint-disable-next-line eqeqeq
    if (typeof value === 'undefined' || value == null) {
      return new Date(-8640000000000000);
    }

    return new Date(Date.parse(value));
  }
}


================================================
FILE: src/utils/Stats.js
================================================
/* @flow */
import measured from 'measured';

export default class Stats {
  _stats: measured.MeasuredCollection;

  constructor(stats: measured.MeasuredCollection) {
    this._stats = stats;
  }

  reset() {
    for(let name in this._stats._metrics) {
      if ({}.hasOwnProperty.call(this._stats._metrics, name)) {
        let metric = this._stats._metrics[name];
        if (metric.unref) {
          metric.unref();
        }
      }
    }

    this._stats._metrics = {};
  }

  toJSON(): any {
    return this._stats.toJSON();
  }

  getSummaries() {
    let statsData = this._stats.toJSON();
    let statsSummary = Object
    .keys(statsData)
    .map(name => {
      let mean = this.to2Dec(statsData[name].histogram.mean);
      let count = statsData[name].meter.count;
      return {name, mean, count};
    });

    statsSummary.sort((a, b) => {
      if (a.mean < b.mean) {
        return 1;
      }
      if (a.mean > b.mean) {
        return -1;
      }
      return 0;
    });

    let nameLen = Math.max.apply(Math, statsSummary.map(i => i.name.length));
    let statsAsStrings = statsSummary.map(s =>
      this.padRight(s.name, nameLen + 2) +
      this.padRight(s.mean + 'ms', 10) +
      ' ' +
      s.count);

    return statsAsStrings;
  }

  padRight(value: string, length: number) {
    return value + Array(length - value.length).join(' ');
  }

  padLeft(value: string, paddingValue: string) {
    return String(paddingValue + value).slice(-paddingValue.length);
  }

  to2Dec(value: number) {
    return parseFloat(parseFloat(Math.round(value * 100) / 100).toFixed(2));
  }
}


================================================
FILE: src/utils/Throughput.js
================================================
/* @flow */
/* eslint-disable max-len */
import { json, warning, invariant } from '../Global';
import type { TableDescription, DynamoDBProvisionedThroughput } from 'aws-sdk';
import type { TableProvisionedAndConsumedThroughput, AdjustmentContext } from '../flow/FlowTypes';

export default class Throughput {

  static getReadCapacityUtilisationPercent(data: TableProvisionedAndConsumedThroughput) {
    invariant(data != null, 'Parameter \'data\' is not set');

    return (
      data.ConsumedThroughput.ReadCapacityUnits /
      data.ProvisionedThroughput.ReadCapacityUnits) * 100;
  }

  static getWriteCapacityUtilisationPercent(data: TableProvisionedAndConsumedThroughput) {
    invariant(data != null, 'Parameter \'data\' is not set');

    return (
      data.ConsumedThroughput.WriteCapacityUnits /
      data.ProvisionedThroughput.WriteCapacityUnits) * 100;
  }

  static getAdjustedCapacityUnits(adjustmentContext: AdjustmentContext): number {
    invariant(adjustmentContext != null, 'Parameter \'adjustmentContext\' is not set');

    // If the provisioned units is less than minimum then simply return the minimum allowed
    if (adjustmentContext.CapacityConfig.Min != null &&
      adjustmentContext.ProvisionedValue < adjustmentContext.CapacityConfig.Min) {
      return adjustmentContext.CapacityConfig.Min;
    }

    // If the provisioned units is greater than maximum then simply return the maximum allowed
    if (adjustmentContext.CapacityConfig.Max != null &&
      adjustmentContext.ProvisionedValue > adjustmentContext.CapacityConfig.Max) {
      return adjustmentContext.CapacityConfig.Max;
    }

    let direction = adjustmentContext.AdjustmentType === 'increment' ? 1 : -1;

    // Increment 'by' throttled events and configured mutliplier, increments only!
    let byTE = (direction === 1 && adjustmentContext.CapacityAdjustmentConfig != null && adjustmentContext.CapacityAdjustmentConfig.By != null && adjustmentContext.CapacityAdjustmentConfig.By.ThrottledEventsWithMultiplier != null) ?
      (adjustmentContext.ThrottledEvents * adjustmentContext.CapacityAdjustmentConfig.By.ThrottledEventsWithMultiplier) :
      0;
    let byTEVal = adjustmentContext.ProvisionedValue + byTE;

    // Increment 'by' percentage of provisioned
    let byP = (adjustmentContext.CapacityAdjustmentConfig != null && adjustmentContext.CapacityAdjustmentConfig.By != null && adjustmentContext.CapacityAdjustmentConfig.By.ProvisionedPercent != null) ?
      (((adjustmentContext.ProvisionedValue / 100) * adjustmentContext.CapacityAdjustmentConfig.By.ProvisionedPercent) * direction) :
      0;
    let byPVal = adjustmentContext.ProvisionedValue + byP + byTE;

    // Increment 'by' percentage of consumed
    let byC = (adjustmentContext.CapacityAdjustmentConfig != null && adjustmentContext.CapacityAdjustmentConfig.By != null && adjustmentContext.CapacityAdjustmentConfig.By.ConsumedPercent != null) ?
      (((adjustmentContext.ConsumedValue / 100) * adjustmentContext.CapacityAdjustmentConfig.By.ConsumedPercent) * direction) :
      0;
    let byCVal = adjustmentContext.ProvisionedValue + byC + byTE;

    // Increment 'by' unit value
    let byU = (adjustmentContext.CapacityAdjustmentConfig != null && adjustmentContext.CapacityAdjustmentConfig.By != null && adjustmentContext.CapacityAdjustmentConfig.By.Units != null) ?
      (adjustmentContext.CapacityAdjustmentConfig.By.Units * direction) :
      0;
    let byUVal = adjustmentContext.ProvisionedValue + byU + byTE;

    // Increment 'to' percentage of provisioned
    let toP = (adjustmentContext.CapacityAdjustmentConfig != null && adjustmentContext.CapacityAdjustmentConfig.To != null && adjustmentContext.CapacityAdjustmentConfig.To.ProvisionedPercent != null) ?
      (adjustmentContext.ProvisionedValue / 100) * adjustmentContext.CapacityAdjustmentConfig.To.ProvisionedPercent :
      adjustmentContext.ProvisionedValue;

    // Increment 'to' percentage of consumed
    let toC = (adjustmentContext.CapacityAdjustmentConfig != null && adjustmentContext.CapacityAdjustmentConfig.To != null && adjustmentContext.CapacityAdjustmentConfig.To.ConsumedPercent != null) ?
      (adjustmentContext.ConsumedValue / 100) * adjustmentContext.CapacityAdjustmentConfig.To.ConsumedPercent :
      adjustmentContext.ProvisionedValue;

    // Increment 'to' unit value
    let toU = (adjustmentContext.CapacityAdjustmentConfig != null && adjustmentContext.CapacityAdjustmentConfig.To != null && adjustmentContext.CapacityAdjustmentConfig.To.Units != null) ?
      adjustmentContext.CapacityAdjustmentConfig.To.Units :
      adjustmentContext.ProvisionedValue;

    // Select the greatest calculated increment
    let newValue = adjustmentContext.AdjustmentType === 'increment' ?
      Math.max(byPVal, byCVal, byUVal, byTEVal, toP, toC, toU) :
      Math.min(byPVal, byCVal, byUVal, byTEVal, toP, toC, toU);

    // Limit to 'max' if it is specified
    if (adjustmentContext.CapacityConfig.Max != null) {
      newValue = Math.min(newValue, adjustmentContext.CapacityConfig.Max);
    }

    // Limit to 'min' if it is specified
    if (adjustmentContext.CapacityConfig.Min != null) {
      newValue = Math.max(newValue, adjustmentContext.CapacityConfig.Min, 1);
    }

    // Ensure we return a whole number
    return Math.round(newValue);
  }

  static getTotalTableProvisionedThroughput(params: TableDescription)
    : DynamoDBProvisionedThroughput {
    try {
      invariant(typeof params !== 'undefined', 'Parameter \'params\' is not set');

      let ReadCapacityUnits = params.ProvisionedThroughput.ReadCapacityUnits;
      let WriteCapacityUnits = params.ProvisionedThroughput.WriteCapacityUnits;

      if (params.GlobalSecondaryIndexes) {
        ReadCapacityUnits += params.GlobalSecondaryIndexes
          .reduce((prev, curr) =>
            prev + curr.ProvisionedThroughput.ReadCapacityUnits, 0);

        WriteCapacityUnits += params.GlobalSecondaryIndexes
          .reduce((prev, curr) =>
            prev + curr.ProvisionedThroughput.WriteCapacityUnits, 0);
      }

      return {
        ReadCapacityUnits,
        WriteCapacityUnits
      };
    } catch (ex) {
      warning(JSON.stringify({
        class: 'Throughput',
        function: 'getTotalTableProvisionedThroughput',
        params
      }, null, json.padding));
      throw ex;
    }
  }
}


================================================
FILE: webpack-dev.config.js
================================================
module.exports = require("./make-webpack-config")({
	devtool: "source-map",
	debug: true
});


================================================
FILE: webpack-prod.config.js
================================================
module.exports = require("./make-webpack-config")({
	minimize: true
});


================================================
FILE: webpack.config.js
================================================
module.exports = require("./make-webpack-config")({

});
Download .txt
gitextract_feix3qd4/

├── .babelrc
├── .eslintrc
├── .flowconfig
├── .gitignore
├── LICENSE
├── README.md
├── flow/
│   ├── aws-sdk.js
│   ├── invariant.js
│   ├── measured.js
│   └── warning.js
├── gulpfile.js
├── make-webpack-config.js
├── package.json
├── scripts/
│   └── start.js
├── src/
│   ├── App.js
│   ├── CapacityCalculator.js
│   ├── Global.js
│   ├── Index.js
│   ├── Provisioner.js
│   ├── aws/
│   │   ├── CloudWatch.js
│   │   └── DynamoDB.js
│   ├── capacity/
│   │   └── CapacityCalculatorBase.js
│   ├── configuration/
│   │   ├── ClimbingProvisioner.json
│   │   ├── DefaultProvisioner.json
│   │   ├── FixedProvisioner.json
│   │   └── Region.json
│   ├── flow/
│   │   └── FlowTypes.js
│   ├── provisioning/
│   │   ├── ProvisionerBase.js
│   │   ├── ProvisionerConfigurableBase.js
│   │   └── ProvisionerLogging.js
│   └── utils/
│       ├── CostEstimation.js
│       ├── Delay.js
│       ├── RateLimitedDecrement.js
│       ├── Stats.js
│       └── Throughput.js
├── webpack-dev.config.js
├── webpack-prod.config.js
└── webpack.config.js
Download .txt
SYMBOL INDEX (80 symbols across 13 files)

FILE: flow/measured.js
  class MeasuredCollection (line 3) | class MeasuredCollection {
  class MeasuredTimer (line 13) | class MeasuredTimer {
  class MeasuredCounter (line 17) | class MeasuredCounter {
  class Stopwatch (line 21) | class Stopwatch {

FILE: src/App.js
  class App (line 10) | class App {
    method constructor (line 14) | constructor() {
    method runAsync (line 19) | async runAsync(event: any, context: any): Promise<void> {
    method _getTableDetailsAsync (line 56) | async _getTableDetailsAsync(tableNames: string[]): Promise<Object[]> {
    method _getTableDetailAsync (line 63) | async _getTableDetailAsync(tableName: string): Promise<Object> {
    method _updateTablesAsync (line 103) | async _updateTablesAsync(tableUpdateRequests: UpdateTableRequest[]): P...
    method _updateTableAsync (line 116) | async _updateTableAsync(tableUpdateRequest: UpdateTableRequest,
    method _getTableUpdateRequests (line 129) | _getTableUpdateRequests(tableDetails: Object[]): UpdateTableRequest[] {
    method _logMetrics (line 138) | _logMetrics(tableDetails: Object[]) {

FILE: src/CapacityCalculator.js
  class CapacityCalculator (line 8) | class CapacityCalculator extends CapacityCalculatorBase {
    method getCloudWatchRegion (line 11) | getCloudWatchRegion() {
    method getStatisticSettings (line 15) | getStatisticSettings(): StatisticSettings {
    method getThrottledEventStatisticSettings (line 23) | getThrottledEventStatisticSettings(): StatisticSettings {
    method getProjectedValue (line 32) | getProjectedValue(settings: StatisticSettings, data: GetMetricStatisti...

FILE: src/Provisioner.js
  class Provisioner (line 12) | class Provisioner extends ProvisionerConfigurableBase {
    method getDynamoDBRegion (line 15) | getDynamoDBRegion(): string {
    method getTableNamesAsync (line 20) | async getTableNamesAsync(): Promise<string[]> {
    method getTableConfig (line 34) | getTableConfig(data: TableProvisionedAndConsumedThroughput): Provision...
    method isReadCapacityIncrementRequired (line 46) | isReadCapacityIncrementRequired(data: TableProvisionedAndConsumedThrou...
    method calculateIncrementedReadCapacityValue (line 54) | calculateIncrementedReadCapacityValue(data: TableProvisionedAndConsume...
    method isReadCapacityDecrementRequired (line 62) | isReadCapacityDecrementRequired(data: TableProvisionedAndConsumedThrou...
    method calculateDecrementedReadCapacityValue (line 70) | calculateDecrementedReadCapacityValue(data: TableProvisionedAndConsume...
    method isWriteCapacityIncrementRequired (line 78) | isWriteCapacityIncrementRequired(data: TableProvisionedAndConsumedThro...
    method calculateIncrementedWriteCapacityValue (line 86) | calculateIncrementedWriteCapacityValue(data: TableProvisionedAndConsum...
    method isWriteCapacityDecrementRequired (line 94) | isWriteCapacityDecrementRequired(data: TableProvisionedAndConsumedThro...
    method calculateDecrementedWriteCapacityValue (line 102) | calculateDecrementedWriteCapacityValue(data: TableProvisionedAndConsum...
    method getReadCapacityIncrementAdjustmentContext (line 110) | getReadCapacityIncrementAdjustmentContext(data: TableProvisionedAndCon...
    method getReadCapacityDecrementAdjustmentContext (line 134) | getReadCapacityDecrementAdjustmentContext(data: TableProvisionedAndCon...
    method getWriteCapacityIncrementAdjustmentContext (line 158) | getWriteCapacityIncrementAdjustmentContext(data: TableProvisionedAndCo...
    method getWriteCapacityDecrementAdjustmentContext (line 182) | getWriteCapacityDecrementAdjustmentContext(data: TableProvisionedAndCo...
    method isCapacityAdjustmentRequired (line 206) | isCapacityAdjustmentRequired(data: TableProvisionedAndConsumedThroughp...
    method isThrottledEventsAboveThreshold (line 253) | isThrottledEventsAboveThreshold(context: AdjustmentContext): boolean {
    method isAboveThreshold (line 266) | isAboveThreshold(context: AdjustmentContext): boolean {
    method isBelowThreshold (line 278) | isBelowThreshold(context: AdjustmentContext): boolean {
    method if (line 291) | if (lastAdjustmentDateTime == null || afterLastAdjustmentMinutes == nu...

FILE: src/aws/DynamoDB.js
  method if (line 99) | if (!isRateLimited) {

FILE: src/capacity/CapacityCalculatorBase.js
  method constructor (line 18) | constructor() {
  method getCloudWatchRegion (line 23) | getCloudWatchRegion(): string {
  method getStatisticSettings (line 28) | getStatisticSettings(): StatisticSettings {
  method getThrottledEventStatisticSettings (line 33) | getThrottledEventStatisticSettings(): StatisticSettings {
  method getProjectedValue (line 39) | getProjectedValue(settings: StatisticSettings, data: GetMetricStatistics...
  method describeTableConsumedCapacityAsync (line 43) | async describeTableConsumedCapacityAsync(params: TableDescription)
  method if (line 220) | if (globalSecondaryIndexName) {

FILE: src/provisioning/ProvisionerBase.js
  class ProvisionerBase (line 9) | class ProvisionerBase {
    method constructor (line 12) | constructor() {
    method getDynamoDBRegion (line 16) | getDynamoDBRegion(): string {
    method getTableNamesAsync (line 20) | async getTableNamesAsync(): Promise<string[]> {

FILE: src/provisioning/ProvisionerConfigurableBase.js
  method isReadCapacityIncrementRequired (line 19) | isReadCapacityIncrementRequired(data: TableProvisionedAndConsumedThrough...
  method calculateIncrementedReadCapacityValue (line 24) | calculateIncrementedReadCapacityValue(data: TableProvisionedAndConsumedT...
  method isReadCapacityDecrementRequired (line 29) | isReadCapacityDecrementRequired(data: TableProvisionedAndConsumedThrough...
  method calculateDecrementedReadCapacityValue (line 34) | calculateDecrementedReadCapacityValue(data: TableProvisionedAndConsumedT...
  method isWriteCapacityIncrementRequired (line 39) | isWriteCapacityIncrementRequired(data: TableProvisionedAndConsumedThroug...
  method calculateIncrementedWriteCapacityValue (line 44) | calculateIncrementedWriteCapacityValue(data: TableProvisionedAndConsumed...
  method isWriteCapacityDecrementRequired (line 49) | isWriteCapacityDecrementRequired(data: TableProvisionedAndConsumedThroug...
  method calculateDecrementedWriteCapacityValue (line 54) | calculateDecrementedWriteCapacityValue(data: TableProvisionedAndConsumed...
  method getTableNamesAsync (line 58) | async getTableNamesAsync(): Promise<string[]> {

FILE: src/provisioning/ProvisionerLogging.js
  class ConfigLogging (line 8) | class ConfigLogging {
    method isAdjustmentRequiredLog (line 9) | static isAdjustmentRequiredLog(

FILE: src/utils/CostEstimation.js
  class CostEstimation (line 7) | class CostEstimation {
    method getMonthlyEstimatedTableCost (line 9) | static getMonthlyEstimatedTableCost(provisionedThroughput: Throughput) {

FILE: src/utils/Delay.js
  class Delay (line 4) | class Delay {
    method delayAsync (line 6) | static delayAsync(ms: number) {

FILE: src/utils/RateLimitedDecrement.js
  class RateLimitedDecrement (line 8) | class RateLimitedDecrement {
    method isDecrementAllowed (line 10) | static isDecrementAllowed(
    method getNextAllowedDecrementDate (line 41) | static getNextAllowedDecrementDate(
    method getNowDate (line 84) | static getNowDate() {
    method getTodayDate (line 88) | static getTodayDate() {
    method getTomorrowDate (line 94) | static getTomorrowDate() {
    method getLastAllowedDecrementDate (line 100) | static getLastAllowedDecrementDate() {
    method getLastDecrementDate (line 106) | static getLastDecrementDate(lastDecrease) {
    method parseDate (line 111) | static parseDate(value) {

FILE: src/utils/Throughput.js
  class Throughput (line 7) | class Throughput {
    method getReadCapacityUtilisationPercent (line 9) | static getReadCapacityUtilisationPercent(data: TableProvisionedAndCons...
    method getWriteCapacityUtilisationPercent (line 17) | static getWriteCapacityUtilisationPercent(data: TableProvisionedAndCon...
    method getAdjustedCapacityUnits (line 25) | static getAdjustedCapacityUnits(adjustmentContext: AdjustmentContext):...
    method getTotalTableProvisionedThroughput (line 100) | static getTotalTableProvisionedThroughput(params: TableDescription)
Condensed preview — 38 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (116K chars).
[
  {
    "path": ".babelrc",
    "chars": 34,
    "preview": "{\n  \"presets\": [\"react-native\"]\n}\n"
  },
  {
    "path": ".eslintrc",
    "chars": 5792,
    "preview": "{\n  \"parser\": \"babel-eslint\",\n\n  \"plugins\": [\n    \"babel\"\n  ],\n\n  \"env\": {\n    \"es6\": true,\n    \"node\": true\n  },\n\n  \"ec"
  },
  {
    "path": ".flowconfig",
    "chars": 244,
    "preview": "[ignore]\n.*/lib/.*\n.*/dist/.*\n.*/coverage/.*\n.*/resources/.*\n\n[include]\n\n[libs]\n./flow\n\n[options]\nesproposal.class_stati"
  },
  {
    "path": ".gitignore",
    "chars": 574,
    "preview": "# Logs\nlogs\n*.log\nnpm-debug.log*\n\n# Runtime data\npids\n*.pid\n*.seed\n\n# Directory for instrumented libs generated by jscov"
  },
  {
    "path": "LICENSE",
    "chars": 1096,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2016 for dynamodb-lambda-autoscale\n\nPermission is hereby granted, free of charge, t"
  },
  {
    "path": "README.md",
    "chars": 13197,
    "preview": "# dynamodb-lambda-autoscale\n**Autoscale AWS DynamoDB using an AWS Lambda function**\n\n+ 5 minute setup process\n+ Serverle"
  },
  {
    "path": "flow/aws-sdk.js",
    "chars": 12310,
    "preview": "/* @flow */\ndeclare module 'aws-sdk' {\n  declare class DynamoDB {\n    constructor(dynamoDBConfig: DynamoDBConfig): void;"
  },
  {
    "path": "flow/invariant.js",
    "chars": 157,
    "preview": "/* @flow */\ndeclare module 'invariant' {\n  declare class Invariant {\n    (condition: boolean, message: string): any;\n  }"
  },
  {
    "path": "flow/measured.js",
    "chars": 448,
    "preview": "/* @flow */\ndeclare module 'measured' {\n  declare class MeasuredCollection {\n    _metrics: any;\n\n    timer(name: string)"
  },
  {
    "path": "flow/warning.js",
    "chars": 111,
    "preview": "/* @flow */\ndeclare module 'warning' {\n  declare var exports: (shouldBeTrue: bool, warning: string) => void;\n}\n"
  },
  {
    "path": "gulpfile.js",
    "chars": 3743,
    "preview": "var gulp = require(\"gulp\");\nvar del = require('del');\nvar rename = require('gulp-rename');\nvar install = require('gulp-i"
  },
  {
    "path": "make-webpack-config.js",
    "chars": 2453,
    "preview": "var path = require('path');\nvar webpack = require('webpack');\nvar StatsPlugin = require('stats-webpack-plugin');\nvar Con"
  },
  {
    "path": "package.json",
    "chars": 1519,
    "preview": "{\n  \"name\": \"dynamodb-lambda-autoscale\",\n  \"version\": \"0.3.0\",\n  \"description\": \"Autoscale DynamoDB provisioned capacity"
  },
  {
    "path": "scripts/start.js",
    "chars": 508,
    "preview": "/* eslint-disable no-console */\n\ntry {\n  var lambda = require('../dist/index.js');\n\n  process.chdir('./dist');\n\n  var co"
  },
  {
    "path": "src/App.js",
    "chars": 7271,
    "preview": "/* @flow */\nimport Provisioner from './Provisioner';\nimport Stats from './utils/Stats';\nimport CostEstimation from './ut"
  },
  {
    "path": "src/CapacityCalculator.js",
    "chars": 1392,
    "preview": "/* @flow */\nimport { invariant } from './Global';\nimport { Region } from './configuration/Region';\nimport CapacityCalcul"
  },
  {
    "path": "src/Global.js",
    "chars": 691,
    "preview": "/* @flow */\nimport measured from 'measured';\nimport _warning from 'warning';\nimport _invariant from 'invariant';\n\nexport"
  },
  {
    "path": "src/Index.js",
    "chars": 629,
    "preview": "/* @flow */\n/* eslint-disable */\n// $FlowIgnore\nimport babelPolyfill from 'babel-polyfill';\n/* eslint-enable */\n// $Flow"
  },
  {
    "path": "src/Provisioner.js",
    "chars": 13379,
    "preview": "/* @flow */\n/* eslint-disable max-len */\nimport ProvisionerConfigurableBase from './provisioning/ProvisionerConfigurable"
  },
  {
    "path": "src/aws/CloudWatch.js",
    "chars": 1235,
    "preview": "/* @flow */\nimport AWS from 'aws-sdk';\nimport { json, stats, warning, invariant } from '../Global';\nimport type {\n  Clou"
  },
  {
    "path": "src/aws/DynamoDB.js",
    "chars": 4245,
    "preview": "/* @flow */\nimport AWS from 'aws-sdk';\nimport { json, stats, warning, invariant } from '../Global';\nimport Delay from '."
  },
  {
    "path": "src/capacity/CapacityCalculatorBase.js",
    "chars": 7763,
    "preview": "/* @flow */\nimport { json, stats, warning, invariant } from '../Global';\nimport CloudWatch from '../aws/CloudWatch';\nimp"
  },
  {
    "path": "src/configuration/ClimbingProvisioner.json",
    "chars": 971,
    "preview": "{\n  \"ReadCapacity\": {\n    \"Min\": 1,\n    \"Max\": 10,\n    \"Increment\": {\n      \"When\": {\n        \"UtilisationIsAbovePercent"
  },
  {
    "path": "src/configuration/DefaultProvisioner.json",
    "chars": 1270,
    "preview": "{\n  \"ReadCapacity\": {\n    \"Min\": 1,\n    \"Max\": 100,\n    \"Increment\": {\n      \"When\": {\n        \"UtilisationIsAbovePercen"
  },
  {
    "path": "src/configuration/FixedProvisioner.json",
    "chars": 108,
    "preview": "{\n  \"ReadCapacity\": {\n    \"Min\": 1,\n    \"Max\": 1\n  },\n  \"WriteCapacity\": {\n    \"Min\": 1,\n    \"Max\": 1\n  }\n}\n"
  },
  {
    "path": "src/configuration/Region.json",
    "chars": 28,
    "preview": "{\n  \"Region\": \"us-east-1\"\n}\n"
  },
  {
    "path": "src/flow/FlowTypes.js",
    "chars": 2477,
    "preview": "/* @flow */\nimport type { ProvisionedThroughput, Throughput } from 'aws-sdk';\n\nexport type ThrottledEventsDescription = "
  },
  {
    "path": "src/provisioning/ProvisionerBase.js",
    "chars": 964,
    "preview": "/* @flow */\n/* eslint-disable no-unused-vars */\nimport { invariant } from '../Global';\nimport type { TableDescription, U"
  },
  {
    "path": "src/provisioning/ProvisionerConfigurableBase.js",
    "chars": 7678,
    "preview": "/* @flow */\nimport { json, warning, invariant } from '../Global';\nimport ProvisionerBase from '../provisioning/Provision"
  },
  {
    "path": "src/provisioning/ProvisionerLogging.js",
    "chars": 3098,
    "preview": "/* @flow */\nimport { log } from '../Global';\nimport type {\n  AdjustmentContext,\n  AdjustmentData,\n} from '../flow/FlowTy"
  },
  {
    "path": "src/utils/CostEstimation.js",
    "chars": 1036,
    "preview": "/* @flow */\nimport { json, warning, invariant } from '../Global';\nimport type {\n  Throughput,\n} from 'aws-sdk';\n\nexport "
  },
  {
    "path": "src/utils/Delay.js",
    "chars": 272,
    "preview": "/* @flow */\nimport { invariant } from '../Global';\n\nexport default class Delay {\n\n  static delayAsync(ms: number) {\n    "
  },
  {
    "path": "src/utils/RateLimitedDecrement.js",
    "chars": 4329,
    "preview": "/* @flow */\nimport { invariant } from '../Global';\nimport type {\n  TableProvisionedAndConsumedThroughput,\n  AdjustmentCo"
  },
  {
    "path": "src/utils/Stats.js",
    "chars": 1600,
    "preview": "/* @flow */\nimport measured from 'measured';\n\nexport default class Stats {\n  _stats: measured.MeasuredCollection;\n\n  con"
  },
  {
    "path": "src/utils/Throughput.js",
    "chars": 6342,
    "preview": "/* @flow */\n/* eslint-disable max-len */\nimport { json, warning, invariant } from '../Global';\nimport type { TableDescri"
  },
  {
    "path": "webpack-dev.config.js",
    "chars": 93,
    "preview": "module.exports = require(\"./make-webpack-config\")({\n\tdevtool: \"source-map\",\n\tdebug: true\n});\n"
  },
  {
    "path": "webpack-prod.config.js",
    "chars": 72,
    "preview": "module.exports = require(\"./make-webpack-config\")({\n\tminimize: true\n});\n"
  },
  {
    "path": "webpack.config.js",
    "chars": 57,
    "preview": "module.exports = require(\"./make-webpack-config\")({\n\n});\n"
  }
]

About this extraction

This page contains the full source code of the channl/dynamodb-lambda-autoscale GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 38 files (106.6 KB), approximately 26.8k tokens, and a symbol index with 80 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!