Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
zero734kr committed Oct 22, 2022
0 parents commit 39e060e
Show file tree
Hide file tree
Showing 9 changed files with 3,598 additions and 0 deletions.
20 changes: 20 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"root": true,
"extends": [
"neon/common",
"neon/node",
"neon/typescript"
],
"parserOptions": {
"project": "./tsconfig.json"
},
"ignorePatterns": [
"**/dist/*"
],
"rules": {
"@typescript-eslint/indent": [
"error",
4
]
}
}
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* text=auto eol=lf
42 changes: 42 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Packages
node_modules/

# Log files
logs/
*.log
npm-debug.log*

# Runtime data
pids
*.pid
*.seed

# Env
.env

# Dist
dist/

# Miscellaneous
.tmp/
.vscode/*
!.vscode/extensions.json
!.vscode/settings.json
.idea/
.DS_Store
.turbo
tsconfig.tsbuildinfo
coverage/

# yarn
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions

# Cache
.prettiercache
.eslintcache
61 changes: 61 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Asynchronous Operations Debugger

<div align="center">
<br />
<p>
<a href="https://www.npmjs.com/package/async-debug"><img src="https://img.shields.io/npm/v/async-debug.svg?maxAge=3600" alt="npm version" /></a>
<a href="https://www.npmjs.com/package/async-debug"><img src="https://img.shields.io/npm/dt/async-debug.svg?maxAge=3600" alt="npm downloads" /></a>
</p>
</div>

## About

``async-debug`` is a Node.js module that debugs your asynchronous operations, inspired by [asyncio debug mode](https://docs.python.org/3/library/asyncio-dev.html#debug-mode) of python.

Currently, the only feature of this module is to listen for your asynchronous operations and log them to the console if they take too long to complete (considered "slow").

## Installation

```sh-session
npm install async-debug
yarn add async-debug
pnpm add async-debug
```

## Usage

```js
import { configure } from "async-debug";

const asyncDebugger = configure({
longTaskThreshold: 250, // 0.25 seconds, default is 300 (0.3 seconds)
})

if (process.env.NODE_ENV === 'development') {
asyncDebugger.enable();
}

// Your code here

// Output:
// Executing asynchronous operation 15 using HTTPCLIENTREQUEST took 313.654224999249ms to complete.
```

And that's it! You can now debug your asynchronous operations.

If you want to setup different thresholds for different operations, you can pass `object` to the ``longTaskThreshold`` parameter like below.

```js
configure({
longTaskThreshold: {
HTTPCLIENTREQUEST: 300,
TCPCONNECTWRAP: 100,
FSREQCALLBACK: 150,
// ...
}
})
```

### Configuration

All options are described as JSDoc comments, but if I think it's necessary, I'll add them here later.
50 changes: 50 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"name": "async-debug",
"version": "1.0.0",
"description": "Asynchronous operation debugger built on top of async_hooks.",
"scripts": {
"build": "tsup",
"lint": "eslint src",
"fmt": "eslint src --fix",
"prepack": "yarn lint && yarn build"
},
"author": "zero734kr <zero734kr@gmail.com>",
"license": "Apache-2.0",
"private": false,
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"typings": "./dist/index.d.ts",
"exports": {
"import": "./dist/index.mjs",
"require": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"files": [
"dist"
],
"keywords": [
"async",
"debug",
"async_hooks",
"performance",
"tracing"
],
"repository": {
"type": "git",
"url": "git+https://github.com/zero734kr/async-debug.git"
},
"bugs": {
"url": "https://github.com/zero734kr/async-debug/issues"
},
"devDependencies": {
"@swc/core": "^1.3.10",
"@types/node": "^18.11.3",
"eslint": "^8.26.0",
"eslint-config-neon": "^0.1.39",
"tsup": "^6.3.0",
"typescript": "^4.8.4"
},
"engines": {
"node": ">=8.5.0"
}
}
95 changes: 95 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import type { AsyncHook } from "node:async_hooks";
import { createHook } from "node:async_hooks";
import { writeSync } from "node:fs";
import { performance } from "node:perf_hooks";
import process from "node:process";

/**
* Options to configure the async hook.
*/
export type DebugOptions = {
/**
* The threshold to determine if an asynchronous task is taking too long in milliseconds.
* If an object is passed as a parameter, each key of object is the type of the asynchronous
* resource and the value will be used for each of them,
* and if no corresponding key is found, the default below will be used.
* If threshold is not specified, the default value is 300ms.
* If a number is passed as a parameter, it will be used for all async resources.
*/
longTaskThreshold?: Record<string, number> | number;
};

export enum DebugEventType {
LISTENING = "LISTENING",
}

export type DebugEvent = {
asyncId: number;
resourceType: string;
startTime?: number;
type: DebugEventType;
};

/**
* Storing information about the asynchronous execution context.
*/
export const asyncTaskContextStore = new Map();

/**
* According to node.js async_hooks documentation,
* printing to the console is an asynchronous operation.
* So `console.log` will cause an infinite loop as the hook callbacks are called recursively.
* we need to print to the console synchronously.
*
* @see https://nodejs.org/api/async_hooks.html#printing-in-asynchook-callbacks
*/
function debugLogSync(...args: any[]) {
if (process.env.AD_DEBUGGING !== "1") {
return;
}

return writeSync(process.stdout.fd, `${args.join(" ")}\n`);
}

/**
* Creates an async hook to debug asynchronous tasks.
*/
export function configure({ longTaskThreshold = 300 }: DebugOptions = {}): AsyncHook {
// I haven't used before/after hook because it not worked correctly.

return createHook({
init(asyncId, type, eid) {
debugLogSync(`INIT: ${asyncId}(${type}) - ${eid}`);

if (asyncTaskContextStore.has(asyncId)) {
return;
}

asyncTaskContextStore.set(asyncId, {
asyncId,
type: DebugEventType.LISTENING,
startTime: performance.now(),
resourceType: type,
});
},
destroy(asyncId) {
const event = asyncTaskContextStore.get(asyncId);

if (event && event.type === DebugEventType.LISTENING) {
const duration = performance.now() - event.startTime!;

debugLogSync(`DESTROY: ${asyncId} - ${duration}ms`);

const threshold = typeof longTaskThreshold === "number" ?
longTaskThreshold :
longTaskThreshold[event.resourceType] ?? 300;

if (duration > threshold) {
console.log(`Executing asynchronous task ${asyncId} using ${event.resourceType} took ${duration}ms to complete.`);
}
}

asyncTaskContextStore.delete(asyncId);
},
});
}
47 changes: 47 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
// Mapped from https://www.typescriptlang.org/tsconfig
"compilerOptions": {
// Type Checking
"allowUnreachableCode": false,
"allowUnusedLabels": false,
"exactOptionalPropertyTypes": true,
"noFallthroughCasesInSwitch": true,
"noImplicitOverride": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"strict": true,
"useUnknownInCatchVariables": true,
"noUncheckedIndexedAccess": true,
// Modules
"module": "ESNext",
"moduleResolution": "node",
"resolveJsonModule": true,
// Emit
"declaration": true,
"declarationMap": true,
"importHelpers": true,
"importsNotUsedAsValues": "error",
"inlineSources": true,
"newLine": "lf",
"noEmitHelpers": true,
"outDir": "dist",
"preserveConstEnums": true,
"removeComments": false,
"sourceMap": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
// Language and Environment
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": [
"ESNext"
],
"target": "ES2021",
"useDefineForClassFields": true
},
"include": [
"src/**/*.ts",
"**/*.config.js"
]
}
13 changes: 13 additions & 0 deletions tsup.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { defineConfig } from "tsup";

export default defineConfig({
entry: ["src/index.ts"],
platform: "node",
format: ["esm", "cjs"],
target: "es2020",
clean: true,
dts: true,
keepNames: true,
shims: true,
skipNodeModulesBundle: true,
});
Loading

0 comments on commit 39e060e

Please sign in to comment.