mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-07-07 18:01:42 +00:00
162 lines
4.8 KiB
JavaScript
162 lines
4.8 KiB
JavaScript
const chai = require('chai')
|
|
const expect = chai.expect
|
|
const sinon = require('sinon')
|
|
|
|
const Logger = require('../../../server/Logger')
|
|
const {
|
|
createBrowseRequestProfile,
|
|
createBrowsePhaseTiming,
|
|
finishBrowseRequestProfile
|
|
} = require('../../../server/utils/queries/libraryBrowseInstrumentation')
|
|
const { profile } = require('../../../server/utils/profiler')
|
|
|
|
describe('libraryBrowseInstrumentation', () => {
|
|
afterEach(() => {
|
|
sinon.restore()
|
|
})
|
|
|
|
it('records named browse phases and returns a slow-summary payload', () => {
|
|
const timestamps = [
|
|
100,
|
|
110,
|
|
160,
|
|
170,
|
|
200,
|
|
210,
|
|
235,
|
|
240,
|
|
280,
|
|
320
|
|
]
|
|
|
|
const profile = createBrowseRequestProfile({
|
|
route: 'GET /api/libraries/:id/items',
|
|
libraryId: 'lib_1',
|
|
now: () => timestamps.shift()
|
|
})
|
|
|
|
profile.mark('rows:start')
|
|
profile.mark('rows:end')
|
|
profile.mark('count:start')
|
|
profile.mark('count:end')
|
|
profile.mark('derived:start')
|
|
profile.mark('derived:end')
|
|
profile.mark('postprocess:start')
|
|
profile.mark('postprocess:end')
|
|
|
|
const result = finishBrowseRequestProfile(profile, { slowMs: 150 })
|
|
|
|
expect(result).to.deep.equal({
|
|
route: 'GET /api/libraries/:id/items',
|
|
libraryId: 'lib_1',
|
|
totalMs: 220,
|
|
phases: {
|
|
rows: 50,
|
|
count: 30,
|
|
derived: 25,
|
|
postprocess: 40
|
|
},
|
|
isSlow: true
|
|
})
|
|
})
|
|
|
|
it('accumulates repeated timings for the same phase', () => {
|
|
const timestamps = [100, 110, 140, 150, 190, 210]
|
|
const profile = createBrowseRequestProfile({
|
|
route: 'GET /api/libraries/:id/items',
|
|
libraryId: 'lib_1',
|
|
now: () => timestamps.shift()
|
|
})
|
|
|
|
profile.mark('rows:start')
|
|
profile.mark('rows:end')
|
|
profile.mark('rows:start')
|
|
profile.mark('rows:end')
|
|
|
|
const result = finishBrowseRequestProfile(profile, { slowMs: 500 })
|
|
|
|
expect(result.phases).to.deep.equal({ rows: 70 })
|
|
expect(result.isSlow).to.equal(false)
|
|
})
|
|
|
|
it('handles null and partial profiles safely', () => {
|
|
expect(finishBrowseRequestProfile(null, { slowMs: 0 })).to.deep.equal({
|
|
route: null,
|
|
libraryId: null,
|
|
totalMs: 0,
|
|
phases: {},
|
|
isSlow: false
|
|
})
|
|
|
|
expect(finishBrowseRequestProfile({ route: 'GET /items', marks: [{ name: 'rows:start', at: 10 }] })).to.deep.equal({
|
|
route: 'GET /items',
|
|
libraryId: null,
|
|
totalMs: 0,
|
|
phases: {},
|
|
isSlow: false
|
|
})
|
|
})
|
|
|
|
it('composes caller logging with request-scoped browse timing hooks', async () => {
|
|
const timestamps = [100, 110, 150, 180]
|
|
const browseProfile = createBrowseRequestProfile({
|
|
route: 'GET /api/libraries/:id/items',
|
|
libraryId: 'lib_1',
|
|
now: () => timestamps.shift()
|
|
})
|
|
const callerLogging = sinon.spy()
|
|
const originalFindOptions = {
|
|
where: { id: 'item_1' },
|
|
logging: callerLogging,
|
|
requestTiming: createBrowsePhaseTiming(browseProfile, 'rows')
|
|
}
|
|
|
|
const wrappedQuery = profile(async (findOptions) => {
|
|
findOptions.logging('SELECT 1', 12)
|
|
return findOptions
|
|
}, true, 'browseRows')
|
|
|
|
const wrappedFindOptions = await wrappedQuery(originalFindOptions)
|
|
const result = finishBrowseRequestProfile(browseProfile, { slowMs: 1000 })
|
|
|
|
expect(wrappedFindOptions).to.not.equal(originalFindOptions)
|
|
expect(originalFindOptions).to.not.have.property('benchmark')
|
|
expect(callerLogging.calledOnceWithExactly('SELECT 1', 12)).to.equal(true)
|
|
expect(result.phases).to.deep.equal({ rows: 40 })
|
|
})
|
|
|
|
it('bounds retained histogram samples and logs a compact summary instead of full arrays', async () => {
|
|
const loggerInfoStub = sinon.stub(Logger, 'info')
|
|
const wrappedQuery = profile(async () => 'ok', false, 'boundedProfilerQuery')
|
|
|
|
for (let i = 0; i < 150; i++) {
|
|
await wrappedQuery()
|
|
}
|
|
|
|
const summaryCalls = loggerInfoStub.getCalls().filter((call) => call.args[0] === '[boundedProfilerQuery] histogram summary:')
|
|
const lastSummary = summaryCalls[summaryCalls.length - 1].args[1]
|
|
|
|
expect(summaryCalls.length).to.equal(150)
|
|
expect(lastSummary.recentValues).to.have.length.at.most(100)
|
|
expect(lastSummary).to.not.have.property('values')
|
|
})
|
|
|
|
it('limits serialized findOptions logging for profiled queries', async () => {
|
|
const loggerInfoStub = sinon.stub(Logger, 'info')
|
|
const wrappedQuery = profile(async (findOptions) => {
|
|
findOptions.logging('SELECT 1', 5)
|
|
return []
|
|
}, true, 'loggedFindOptionsQuery')
|
|
|
|
await wrappedQuery({
|
|
where: {
|
|
ids: Array.from({ length: 500 }, (_, index) => `id-${index}`)
|
|
}
|
|
})
|
|
|
|
const findOptionsCall = loggerInfoStub.getCalls().find((call) => call.args[0] === '[loggedFindOptionsQuery] findOptions:')
|
|
|
|
expect(findOptionsCall.args[1]).to.be.a('string')
|
|
expect(findOptionsCall.args[1].length).to.be.lessThan(2000)
|
|
})
|
|
})
|