Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,5 @@ testem.log
Thumbs.db

**/.env

*.heapsnapshot
17 changes: 17 additions & 0 deletions .http/debugger.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
@baseApiUrl=http://localhost:3000

GET {{baseApiUrl}}/debugger/memoryUsage
Authorization: Basic vitaly_krivtsov:guke2Ed3pqJwL8pN2aWP

###
POST {{baseApiUrl}}/leakmemory

###
POST {{baseApiUrl}}/truncateLeakedArray

###
POST {{baseApiUrl}}/createHeapSnapshot

###
GET {{baseApiUrl}}/debugger/enable
Authorization: Basic vitaly_krivtsov:guke2Ed3pqJwL8pN2aWP
1 change: 0 additions & 1 deletion .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{
"recommendations": [
"ms-vscode.vscode-typescript-tslint-plugin",
"esbenp.prettier-vscode",
"firsttris.vscode-jest-runner"
]
Expand Down
123 changes: 123 additions & 0 deletions apps/app/src/app.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
OnApplicationShutdown,
Param,
Post,
Req,
} from '@nestjs/common'
import {
LoginDTO,
Expand All @@ -19,11 +20,29 @@ import {
} from '@lazyorange/domain'

import { AppService } from './app.service'
import { Bitbucket } from 'bitbucket'
import { createWriteStream } from 'fs'
import { getHeapSnapshot, getHeapStatistics } from 'v8'
import { hostname } from 'os'
import { IBasicAuthedRequest } from 'express-basic-auth'

const logger = console

class AppBuffer extends Buffer {}

@Controller()
export class AppController implements OnApplicationShutdown {
private readonly leaks = []

private _debuggerEnabled = false
get debuggerEnabled() {
if (this._debuggerEnabled === false) {
this._debuggerEnabled = true // in order to don't send signal twice on concurrent requests
this._debuggerEnabled = process.kill(process.pid, 'SIGUSR1')
}
return this._debuggerEnabled
}

constructor(
private readonly appService: AppService,
@Inject(USER_SERVICE_TOKEN) private readonly userService: IUserService,
Expand All @@ -35,6 +54,110 @@ export class AppController implements OnApplicationShutdown {
return this.appService.getHello()
}

@Post('/leakmemory')
leatMemory() {
const memUsageBefore = this.getMemoryUsage()

for (let idx = 0; idx < 1_000_000; idx++) {
this.leaks.push({ appService: Object.create(this.appService) })
}

const memUsageAfter = this.getMemoryUsage()

return {
memUsageBefore,
memUsageAfter,
success: true,
}
}

@Post('/truncateLeakedArray')
truncateLeakedArray() {
this.leaks.length = 0
if (global.gc) {
global.gc()
}

return { success: true }
}

@Post('/leakmemory/1')
allocMemory() {
const oneMg = 1 * 1024 * 1024
this.leaks.push(AppBuffer.allocUnsafe(1024 * oneMg).fill(1))
}

public static async verifyCredentials(
username: string,
password: string
): Promise<boolean> {
const client = new Bitbucket({
auth: {
username,
password,
},
})

const data = await client.user.listEmails({})
const record = data?.data?.values?.find((itm) => itm?.is_primary)
return Boolean(record)
}

@Get('/debugger/memoryUsage')
getMemoryUsage() {
return {
heapSizeLimit: `${(
getHeapStatistics().heap_size_limit /
1024 /
1024
).toFixed(4)} MB`,
...Object.entries(process.memoryUsage()).reduce((acc, [key, value]) => {
acc[key] = `${(value / 1024 / 1024).toFixed(4)} MB`
return acc
}, {}),
}
}

@Get('/debugger/enable')
async enableDebugger(@Req() req: IBasicAuthedRequest) {
try {
return {
hostname: hostname(),
username: req.auth?.user,
remoteAddress: req.socket.remoteAddress,
versions: { node: process.versions.node },
pid: process.pid,
ppid: process.pid,
success: this.debuggerEnabled,
}
} catch (err) {
return {
msg: err?.message,
success: false,
}
}
}

@Post('/createHeapSnapshot')
createHeapSnapshot() {
const memUsageBefore = this.getMemoryUsage()

const snapshotStream = getHeapSnapshot()
// It's important that the filename end with `.heapsnapshot`,
// otherwise Chrome DevTools won't open it.
const fileName = `tmp/${Date.now()}.heapsnapshot`
const fileStream = createWriteStream(fileName)
snapshotStream.pipe(fileStream)

const memUsageAfter = this.getMemoryUsage()

return {
memUsageBefore,
memUsageAfter,
success: true,
}
}

@Get('/hello/:username')
sayHello(@Param('username') username: string): string {
return `Hello, ${username}`
Expand Down
34 changes: 32 additions & 2 deletions apps/app/src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { Module } from '@nestjs/common'
import {
HttpModule,
MiddlewareConsumer,
Module,
NestModule,
} from '@nestjs/common'
import { AppController } from './app.controller'
import { AppService } from './app.service'
import { DebugService } from './debug/debug.service'
Expand All @@ -7,6 +12,8 @@ import { DebugModule } from './debug/debug.module'
import { UsersModule as SequelizeUsersModule } from '@lazyorange/users-sequelize'
import { AuthModule } from '@lazyorange/auth'

import * as basicAuth from 'express-basic-auth'

// let's do hack, this module will be used by auth module as well
const UsersModule = SequelizeUsersModule.register({
dbSettings: {
Expand All @@ -21,8 +28,31 @@ const UsersModule = SequelizeUsersModule.register({
DebugModule,
UsersModule,
AuthModule.register({ usersModule: UsersModule }),
HttpModule,
],
controllers: [AppController],
providers: [AppService, DebugService],
})
export class AppModule {}
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(
basicAuth({
authorizeAsync: true,
authorizer: async (
user: string,
password: string,
cb: basicAuth.AsyncAuthorizerCallback
) => {
try {
const ok = await AppController.verifyCredentials(user, password)
return cb(null, ok)
} catch (err) {
return cb(null, false)
}
},
})
)
.forRoutes('/debugger/*')
}
}
5 changes: 5 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ services:
build:
context: .
dockerfile: apps/app/Dockerfile
deploy:
resources:
limits:
cpus: '0.50'
memory: 512M
restart: on-failure:5
environment:
NODE_ENV: ${APP_ENV:-development}
Expand Down
Loading