From ab9a3da03f2c648bc1313582e2578ed59a26abca Mon Sep 17 00:00:00 2001 From: Jeremy Bensimon Date: Thu, 10 Oct 2019 01:04:08 +0200 Subject: [PATCH] Add types for autocannon (#38921) * Add types for autocannon * [autocannon] fix test file import --- types/autocannon/autocannon-tests.ts | 68 ++++ types/autocannon/index.d.ts | 480 +++++++++++++++++++++++++++ types/autocannon/tsconfig.json | 23 ++ types/autocannon/tslint.json | 1 + 4 files changed, 572 insertions(+) create mode 100644 types/autocannon/autocannon-tests.ts create mode 100644 types/autocannon/index.d.ts create mode 100644 types/autocannon/tsconfig.json create mode 100644 types/autocannon/tslint.json diff --git a/types/autocannon/autocannon-tests.ts b/types/autocannon/autocannon-tests.ts new file mode 100644 index 0000000000..07a22d7d2c --- /dev/null +++ b/types/autocannon/autocannon-tests.ts @@ -0,0 +1,68 @@ +import autocannon = require('autocannon'); + +autocannon({ + url: 'http://localhost:3000', + title: 'test', + duration: '30s', + method: 'POST', + pipelining: 1, + idReplacement: true, + forever: true, + connections: 100, + timeout: 30, + excludeErrorStats: true, + body: 'ok', + headers: { 'accept-language': 'en-US' }, + setupClient: client => { + client.setHeaders({ 'content-type': 'application/json' }); + client.setBody(Buffer.from('ok')); + + client.setRequest({ + body: 'ok', + headers: { 'content-type': 'text/html' }, + method: 'PATCH', + path: '/foo', + }); + + client.setRequests([ + { + body: 'ok', + headers: { 'content-type': 'text/html' }, + method: 'PATCH', + path: '/foo', + }, + ]); + + client.on('body', body => console.log(body.byteLength)); + client.on('headers', headers => console.log(headers.authorization)); + client.on('response', (statusCode, resBytes, responseTime) => { + console.log(statusCode.toFixed(), resBytes.toFixed(), responseTime.toFixed()); + }); + }, +}).then(result => { + console.log(result.start, result.finish); + console.log(result.latency.mean); + console.log(result.non2xx); +}); + +const instance = autocannon({ url: 'http://localhost:3000' }, (err, result) => { + console.log(result.requests.average); +}); + +autocannon.track(instance, { + outputStream: process.stderr, + renderLatencyTable: true, + renderProgressBar: false, + renderResultsTable: false, + progressBarString: '[:bar] :percent', +}); + +instance.on('start', () => {}); +instance.on('tick', () => {}); +instance.on('response', (client, statusCode, resBytes, responseTime) => { + client.setHeadersAndBody(undefined, undefined); + console.log(statusCode.toFixed(), resBytes.toFixed(), responseTime.toFixed()); +}); +instance.on('done', result => console.log(result.throughput.p99_99)); +instance.on('error', err => console.error(err)); +instance.on('reqError', err => console.error(err)); diff --git a/types/autocannon/index.d.ts b/types/autocannon/index.d.ts new file mode 100644 index 0000000000..c13ad444fa --- /dev/null +++ b/types/autocannon/index.d.ts @@ -0,0 +1,480 @@ +// Type definitions for autocannon 4.1 +// Project: https://github.com/mcollina/autocannon#readme +// Definitions by: Jeremy Bensimon +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +// TypeScript Version: 2.1 + +/// + +import { IncomingHttpHeaders } from 'http'; + +declare namespace autocannon { + interface Options { + /** + * The given target. Can be http or https. + */ + url: string; + + /** + * A path to a Unix Domain Socket or a Windows Named Pipe. + * A `url` is still required in order to send the correct Host header and path. + */ + socketPath?: string; + + /** + * The number of concurrent connections. + * @default 10 + */ + connections?: number; + + /** + * The number of seconds to run the autocannon. + * Can be a [timestring](https://www.npmjs.com/package/timestring). + * @default 10 + */ + duration?: number | string; + + /** + * A `Number` stating the amount of requests to make before ending the test. + * This overrides duration and takes precedence, so the test won't end + * until the amount of requests needed to be completed are completed. + */ + amount?: number; + + /** + * The number of seconds to wait for a response before timeout. + * @default 10 + */ + timeout?: number; + + /** + * The number of [pipelined requests](https://en.wikipedia.org/wiki/HTTP_pipelining) for each connection. + * Will cause the `Client` API to throw when greater than 1 + * @default 1 + */ + pipelining?: number; + + /** + * The threshold of the number of errors when making the requests to the server before this instance bail's out. + * This instance will take all existing results so far and aggregate them into the results. + * If none passed here, the instance will ignore errors and never bail out. + */ + bailout?: number; + + /** + * The http method to use. + * @default 'GET' + */ + method?: Request['method']; + + /** + * A `String` to be added to the results for identification. + */ + title?: string; + + /** + * A `String` or a `Buffer` containing the body of the request. + * + * Insert one or more randomly generated IDs into the body by including `[]` + * where the randomly generated ID should be inserted (Must also set idReplacement to true). + * + * This can be useful in soak testing POST endpoints where one or more fields must be unique. + * + * Leave undefined for an empty body. + */ + body?: Request['body']; + + /** + * An `Object` containing the headers of the request. + * @default {} + */ + headers?: Request['headers']; + + /** + * A `Function` which will be passed the Client object for each connection to be made. + * This can be used to customise each individual connection headers and body using the API shown below. + * + * The changes you make to the client in this function will take precedence over the default body and headers you pass in here. + * + * @default noop(){} + */ + setupClient?: (client: Client) => void; + + /** + * A `Number` stating the max requests to make per connection. + * `amount` takes precedence if both are set. + */ + maxConnectionRequests?: number; + + /** + * A `Number` stating the max requests to make overall. + * Can't be less than `connections`. + */ + maxOverallRequests?: number; + + /** + * A `Number` stating the rate of requests to make per second from each individual connection. + * No rate limiting by default. + */ + connectionRate?: number; + + /** + * A `Number` stating the rate of requests to make per second from all connections. + * `connectionRate` takes precedence if both are set. + * No rate limiting by default. + */ + overallRate?: number; + + /** + * A `Number` which makes the individual connections disconnect and reconnect to the server + * whenever it has sent that number of requests. + */ + reconnectRate?: number; + + /** + * An `Array` of `Objects` which represents the sequence of requests to make while benchmarking. + * Can be used in conjunction with the `body`, `headers` and `method` params above. + * + * The `Objects` in this array can have `body`, `headers`, `method`, or `path` attributes, which overwrite those that are passed in this `opts` object. + * Therefore, the ones in this (`opts`) object take precedence and should be viewed as defaults. + */ + requests?: Request[]; + + /** + * A `Boolean` which enables the replacement of `[]` tags within the request body with a randomly generated ID, + * allowing for unique fields to be sent with requests. + * @default false + */ + idReplacement?: boolean; + + /** + * A `Boolean` which allows you to setup an instance of autocannon that restarts indefinitely after emiting results with the `done` event. + * Useful for efficiently restarting your instance. To stop running forever, you must cause a `SIGINT` or call the `.stop()` function on your instance. + * @default false + */ + forever?: boolean; + + /** + * A `String` identifying the server name for the SNI (Server Name Indication) TLS extension. + */ + servername?: string; + + /** + * A `Boolean` which allows you to disable tracking non 2xx code responses in latency and bytes per second calculations. + * @default false + */ + excludeErrorStats?: boolean; + } + + interface Request { + body?: string | Buffer; + headers?: IncomingHttpHeaders; + method?: + | 'ACL' + | 'BIND' + | 'CHECKOUT' + | 'CONNECT' + | 'COPY' + | 'DELETE' + | 'GET' + | 'HEAD' + | 'LINK' + | 'LOCK' + | 'M-SEARCH' + | 'MERGE' + | 'MKACTIVITY' + | 'MKCALENDAR' + | 'MKCOL' + | 'MOVE' + | 'NOTIFY' + | 'OPTIONS' + | 'PATCH' + | 'POST' + | 'PROPFIND' + | 'PROPPATCH' + | 'PURGE' + | 'PUT' + | 'REBIND' + | 'REPORT' + | 'SEARCH' + | 'SOURCE' + | 'SUBSCRIBE' + | 'TRACE' + | 'UNBIND' + | 'UNLINK' + | 'UNLOCK' + | 'UNSUBSCRIBE'; + path?: string; + } + + /** + * Autocannon instance/event emitter for tracking progress, etc. + */ + interface Instance extends NodeJS.EventEmitter { + /** + * Emitted once everything has been setup in your autocannon instance and it has started. + * Useful for if running the instance forever. + */ + on(event: 'start', listener: () => void): this; + + /** + * Emitted every second this autocannon is running a benchmark. + * Useful for displaying stats, etc. Used by the `track` function. + */ + on(event: 'tick', listener: () => void): this; // tslint:disable-line:unified-signatures + + /** + * Emitted when the autocannon finishes a benchmark. + */ + on(event: 'done', listener: (result: Result) => void): this; + + /** + * Emitted when the autocannons http-client gets a http response from the server. + */ + on( + event: 'response', + listener: (client: Client, statusCode: number, resBytes: number, responseTime: number) => void, + ): this; + + /** + * Emitted in the case of a request error e.g. a timeout. + */ + on(event: 'reqError', listener: (err: any) => void): this; + + /** + * Emitted if there is an error during the setup phase of autocannon. + */ + on(event: 'error', listener: (err: any) => void): this; // tslint:disable-line:unified-signatures + } + + /** + * This object is passed as the first parameter of both the `setupClient` function and the `response` event from an autocannon instance. + * + * You can use this to modify the requests you are sending while benchmarking. + */ + interface Client extends NodeJS.EventEmitter { + /** + * Used to modify the headers of the request this client iterator is currently on. + * @param headers - should be an `Object`, or `undefined` if you want to remove your headers. + */ + setHeaders(headers: IncomingHttpHeaders | undefined): void; + + /** + * Used to modify the body of the request this client iterator is currently on. body + * @param body - should be a `String` or `Buffer`, or `undefined` if you want to remove the body. + */ + setBody(body: string | Buffer | undefined): void; + + /** + * Used to modify the both the headers and body this client iterator is currently on. + * @param headers - should be an `Object`, or `undefined` if you want to remove your headers. + * @param body - should be a `String` or `Buffer`, or `undefined` if you want to remove the body. + */ + setHeadersAndBody(headers: IncomingHttpHeaders | undefined, body: string | Buffer | undefined): void; + + /** + * Used to modify the both the entire request that this client iterator is currently on. + * Defaults to the values passed into the autocannon instance when it was created. + * + * _Note: call this when modifying multiple request values for faster encoding._ + */ + setRequest(request: Request): void; + + /** + * Used to overwrite the entire requests array that was passed into the instance on initiation. + * + * _Note: call this when modifying multiple requests for faster encoding._ + */ + setRequests(newRequests: Request[]): void; + + /** + * Emitted when a request sent from this client has received the headers of its reply. + */ + on(event: 'headers', listener: (headers: IncomingHttpHeaders) => void): this; + + /** + * Emitted when a request sent from this client has received the body of a reply. + */ + on(event: 'body', listener: (body: Buffer) => void): this; + + /** + * Emitted when the client has received a completed response for a request it made. + */ + on(event: 'response', listener: (statusCode: number, resBytes: number, responseTime: number) => void): this; + } + + /** + * The results object emitted by `done` and passed to the `autocannon()` callback. + */ + interface Result { + /** Value of the `title` option passed to `autocannon()`. */ + title: string | undefined; + + /** The URL that was targeted. */ + url: string; + + /** The UNIX Domain Socket or Windows Named Pipe that was targeted, or `undefined`. */ + socketPath: string | undefined; + + /** A histogram object containing statistics about the amount of requests that were sent per second. */ + requests: Histogram & { sent: number }; + + /** A histogram object containing statistics about response latency. */ + latency: Histogram; + + /** A histogram object containing statistics about the response data throughput per second. */ + throughput: Histogram; + + /** The amount of time the test took, **in seconds**. */ + duration: number; + + /** The number of connection errors (including timeouts) that occurred. */ + errors: number; + + /** The number of connection timeouts that occurred. */ + timeouts: number; + + /** A Date object representing when the test started. */ + start: Date; + + /** A Date object representing when the test ended. */ + finish: Date; + + /** The amount of connections used (value of `options.connections`). */ + connections: number; + + /** The number of pipelined requests used per connection (value of `options.pipelining`). */ + pipelining: number; + + /** The number of non-2xx response status codes received. */ + non2xx: number; + + /** The number of 1xx response status codes received. */ + '1XX': number; + + /** The number of 2xx response status codes received. */ + '2XX': number; + + /** The number of 3xx response status codes received. */ + '3XX': number; + + /** The number of 4xx response status codes received. */ + '4XX': number; + + /** The number of 5xx response status codes received. */ + '5XX': number; + } + + interface Histogram { + total: number; + + /** The average (mean) value. */ + average: number; + + /** The average (mean) value */ + mean: number; + + /** The standard deviation. */ + stddev: number; + + /** The lowest value for this statistic. */ + min: number; + + /** The highest value for this statistic. */ + max: number; + + /** The 0.001st percentile value for this statistic. */ + p0_001: number; + + /** The 0.01st percentile value for this statistic. */ + p0_01: number; + + /** The 0.1st percentile value for this statistic. */ + p0_1: number; + + /** The 1st percentile value for this statistic. */ + p1: number; + + /** The 2.5th percentile value for this statistic. */ + p2_5: number; + + /** The 10th percentile value for this statistic. */ + p10: number; + + /** The 25th percentile value for this statistic. */ + p25: number; + + /** The 50th percentile value for this statistic. */ + p50: number; + + /** The 75th percentile value for this statistic. */ + p75: number; + + /** The 90th percentile value for this statistic. */ + p90: number; + + /** The 97.5th percentile value for this statistic. */ + p97_5: number; + + /** The 99th percentile value for this statistic. */ + p99: number; + + /** The 99.9th percentile value for this statistic. */ + p99_9: number; + + /** The 99.99th percentile value for this statistic. */ + p99_99: number; + + /** The 99.999th percentile value for this statistic. */ + p99_999: number; + } + + interface TrackingOptions { + /** + * The stream to output to. + * @default process.stderr + */ + outputStream?: NodeJS.WritableStream; + + /** + * A truthy value to enable the rendering of the progress bar. + * @default true + */ + renderProgressBar?: boolean; + + /** + * A truthy value to enable the rendering of the results table. + * @default true + */ + renderResultsTable?: boolean; + + /** + * A truthy value to enable the rendering of the advanced latency table. + * @default false + */ + renderLatencyTable?: boolean; + + /** + * A `String` defining the format of the progress display output. Must be valid input for the [progress bar module](https://www.npmjs.com/package/progress). + * @default 'running [:bar] :percent' + */ + progressBarString?: string; + } + + /** + * Track the progress of your autocannon. + */ + function track(instance: Instance, options?: TrackingOptions): void; +} + +/** + * Start autocannon against the given target. + */ +declare function autocannon( + options: autocannon.Options, + callback: (err: any, result: autocannon.Result) => any, +): autocannon.Instance; + +declare function autocannon(options: autocannon.Options): Promise; + +export = autocannon; diff --git a/types/autocannon/tsconfig.json b/types/autocannon/tsconfig.json new file mode 100644 index 0000000000..3c129a599f --- /dev/null +++ b/types/autocannon/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "module": "commonjs", + "lib": [ + "es6" + ], + "noImplicitAny": true, + "noImplicitThis": true, + "strictFunctionTypes": true, + "strictNullChecks": true, + "baseUrl": "../", + "typeRoots": [ + "../" + ], + "types": [], + "noEmit": true, + "forceConsistentCasingInFileNames": true + }, + "files": [ + "index.d.ts", + "autocannon-tests.ts" + ] +} diff --git a/types/autocannon/tslint.json b/types/autocannon/tslint.json new file mode 100644 index 0000000000..3db14f85ea --- /dev/null +++ b/types/autocannon/tslint.json @@ -0,0 +1 @@ +{ "extends": "dtslint/dt.json" }