Skip to content

Commit

Permalink
fix(core): added memory cache limits
Browse files Browse the repository at this point in the history
  • Loading branch information
slvnperron committed Jun 16, 2019
1 parent 66566f2 commit 4732f3f
Show file tree
Hide file tree
Showing 7 changed files with 53 additions and 9 deletions.
10 changes: 10 additions & 0 deletions src/bp/core/misc/utils.test.ts
@@ -0,0 +1,10 @@
import { asBytes } from './utils'

test('asBytes', () => {
expect(asBytes('5mb')).toEqual(5 * 1024 * 1024)
expect(asBytes('5 mb')).toEqual(5 * 1024 * 1024)
expect(asBytes('5.0 MB')).toEqual(5 * 1024 * 1024)
expect(asBytes('1234')).toEqual(1234)
expect(asBytes('1234b')).toEqual(1234)
expect(asBytes('')).toEqual(0)
})
19 changes: 19 additions & 0 deletions src/bp/core/misc/utils.ts
Expand Up @@ -59,3 +59,22 @@ export const stringify = content => JSON.stringify(content, undefined, 2)
export const forceForwardSlashes = path => path.replace(/\\/g, '/')

export const getCacheKeyInMinutes = (minutes: number = 1) => Math.round(new Date().getTime() / 1000 / 60 / minutes)

export const asBytes = (size: string) => {
const matches = (size || '')
.replace(',', '.')
.toLowerCase()
.match(/(\d+\.?\d{0,})\s{0,}(mb|gb|pt|kb|b)?/i)

if (!matches || !matches.length) {
return 0
}

/**/ if (matches[2] === 'b') return Number(matches[1]) * Math.pow(1024, 0)
else if (matches[2] === 'kb') return Number(matches[1]) * Math.pow(1024, 1)
else if (matches[2] === 'mb') return Number(matches[1]) * Math.pow(1024, 2)
else if (matches[2] === 'gb') return Number(matches[1]) * Math.pow(1024, 3)
else if (matches[2] === 'tb') return Number(matches[1]) * Math.pow(1024, 4)

return Number(matches[1])
}
5 changes: 3 additions & 2 deletions src/bp/core/routers/bots/index.ts
Expand Up @@ -8,6 +8,7 @@ import { Serialize } from 'cerialize'
import { gaId, machineUUID } from 'common/stats'
import { BotpressConfig } from 'core/config/botpress.config'
import { ConfigProvider } from 'core/config/config-loader'
import { asBytes } from 'core/misc/utils'
import { GhostService } from 'core/services'
import ActionService from 'core/services/action/action-service'
import AuthService, { TOKEN_AUDIENCE } from 'core/services/auth/auth-service'
Expand All @@ -32,7 +33,7 @@ import { CustomRouter } from '../customRouter'
import { checkTokenHeader, needPermissions } from '../util'

const debugMedia = DEBUG('audit:action:media-upload')
const DEFAULT_MAX_SIZE = 10 // mb
const DEFAULT_MAX_SIZE = '10mb'

export class BotsRouter extends CustomRouter {
private actionService: ActionService
Expand Down Expand Up @@ -302,7 +303,7 @@ export class BotsRouter extends CustomRouter {
cb(new Error(`Invalid mime type (${file.mimetype})`), false)
},
limits: {
fileSize: _.get(this.botpressConfig, 'fileUpload.maxFileSize', DEFAULT_MAX_SIZE) * 1000 * 1024
fileSize: asBytes(_.get(this.botpressConfig, 'fileUpload.maxFileSize', DEFAULT_MAX_SIZE))
}
}).single('file')

Expand Down
4 changes: 2 additions & 2 deletions src/bp/core/services/ghost/db-driver.test.ts
Expand Up @@ -3,7 +3,7 @@ import path from 'path'

import Database from '../../database'
import { createDatabaseSuite } from '../../database/index.tests'
import { expectAsync } from '../../misc/utils'
import { asBytes, expectAsync } from '../../misc/utils'

import DBStorageDriver from './db-driver'

Expand All @@ -22,7 +22,7 @@ createDatabaseSuite('GhostDB Driver', function(database: Database) {
})

it('writing large blob', async () => {
const size = 1024 * 1024 * 1 // 1mb
const size = asBytes('1mb')
await driver.upsertFile(F_A_PATH, Buffer.alloc(size))
const buffer = await driver.readFile(F_A_PATH)
expect(buffer.length).toBe(size)
Expand Down
14 changes: 11 additions & 3 deletions src/bp/core/services/ghost/memory-cache.ts
@@ -1,4 +1,5 @@
import { ObjectCache } from 'common/object-cache'
import { asBytes } from 'core/misc/utils'
import { EventEmitter } from 'events'
import { inject, injectable } from 'inversify'
import LRU from 'lru-cache'
Expand All @@ -14,9 +15,16 @@ export default class MemoryObjectCache implements ObjectCache {

constructor(@inject(TYPES.FileCacheInvalidator) private cacheInvalidator: CacheInvalidators.FileChangedInvalidator) {
this.cache = LRU({
// For now we cache up to 5000 elements, whatever the size
// We will probably want to assign different length to various element types in the future
max: 5000
max: asBytes(process.core_env.BP_MAX_MEMORY_CACHE_SIZE || '1gb'),
length: (n, key) => {
if (key.startsWith('buffer::')) {
return n.length
} else if (key.startsWith('string::')) {
return n.length * 2 // each char is 2 bytes in ECMAScript
}

return 500 // Assuming 500 bytes per objects, this is kind of random
}
})

this.cacheInvalidator.install(this)
Expand Down
4 changes: 2 additions & 2 deletions src/bp/core/services/ghost/service.ts
@@ -1,7 +1,7 @@
import { ListenHandle, Logger } from 'botpress/sdk'
import { ObjectCache } from 'common/object-cache'
import { isValidBotId } from 'common/validation'
import { forceForwardSlashes } from 'core/misc/utils'
import { asBytes, forceForwardSlashes } from 'core/misc/utils'
import { EventEmitter2 } from 'eventemitter2'
import fse from 'fs-extra'
import { inject, injectable, tagged } from 'inversify'
Expand All @@ -19,7 +19,7 @@ import { PendingRevisions, ServerWidePendingRevisions, StorageDriver } from '.'
import DBStorageDriver from './db-driver'
import DiskStorageDriver from './disk-driver'

const MAX_GHOST_FILE_SIZE = 20 * 1024 * 1024 // 20 Mb
const MAX_GHOST_FILE_SIZE = asBytes('20mb')

@injectable()
export class GhostService {
Expand Down
6 changes: 6 additions & 0 deletions src/typings/global.d.ts
Expand Up @@ -68,6 +68,12 @@ declare type BotpressEnvironementVariables = {
* Truthy if running the official Botpress docker image
*/
readonly BP_IS_DOCKER?: boolean

/**
* The max size of the in-memory, in-process cache.
* Defaults to '1gb'
*/
readonly BP_MAX_MEMORY_CACHE_SIZE?: string
}

interface IDebug {
Expand Down

0 comments on commit 4732f3f

Please sign in to comment.