tests.ts•25.4 kB
import { Providers } from '../../_utils/providers'
import { NewPrismaClient } from '../../_utils/types'
import testMatrix from './_matrix'
// @ts-ignore
import type { PrismaClient } from './generated/prisma/client'
declare let prisma: PrismaClient
declare const newPrismaClient: NewPrismaClient<PrismaClient, typeof PrismaClient>
const executeOneQuery = async () => {
const email = 'user@example.com'
await prisma.user.create({
data: {
email,
},
})
await prisma.user.findFirst({ where: { email } })
}
testMatrix.setupTestSuite(
({ provider, driverAdapter }) => {
const usesDriverAdapter = driverAdapter !== undefined
describe('empty', () => {
test('$metrics.prometheus() does not crash before client is connected', async () => {
await expect(prisma.$metrics.prometheus()).resolves.not.toThrow()
})
test('$metrics.json() does not crash before client is connected', async () => {
await expect(prisma.$metrics.json()).resolves.not.toThrow()
})
})
describe('before a query', () => {
test('MongoDB: should have the same keys, before and after a query', async () => {
if (provider !== Providers.MONGODB) {
return
}
const metricBefore = await prisma.$metrics.json()
// console.log(JSON.stringify(metricBefore, null, 2))
const { counters: countersBefore, gauges: gaugesBefore, histograms: histogramsBefore } = metricBefore
expect(countersBefore.sort((a, b) => a.key.localeCompare(b.key))).toMatchObject([
{
key: 'prisma_client_queries_total',
labels: {},
value: 0,
description: 'The total number of Prisma Client queries executed',
},
{
key: 'prisma_datasource_queries_total',
labels: {},
value: 0,
description: 'The total number of datasource queries executed',
},
{
key: 'prisma_pool_connections_closed_total',
labels: {},
value: 0,
description: 'The total number of pool connections closed',
},
{
key: 'prisma_pool_connections_opened_total',
labels: {},
value: 0, // different from SQL providers
description: 'The total number of pool connections opened',
},
])
expect(gaugesBefore.sort((a, b) => a.key.localeCompare(b.key))).toMatchObject([
{
key: 'prisma_client_queries_active',
labels: {},
value: 0,
description: 'The number of currently active Prisma Client queries',
},
{
key: 'prisma_client_queries_wait',
labels: {},
value: 0,
description: 'The number of datasource queries currently waiting for a free connection',
},
{
key: 'prisma_pool_connections_busy',
labels: {},
value: 0,
description: 'The number of pool connections currently executing datasource queries',
},
{
key: 'prisma_pool_connections_idle',
labels: {},
value: 0, // different from SQL providers
description: 'The number of pool connections that are not busy running a query',
},
{
key: 'prisma_pool_connections_open',
labels: {},
value: 0, // different from SQL providers
description: 'The number of pool connections currently open',
},
])
expect(histogramsBefore).toEqual([])
// Send 1 query
await executeOneQuery()
const metricAfter = await prisma.$metrics.json()
// console.log(JSON.stringify(metricAfter, null, 2))
const { counters: countersAfter, gauges: gaugesAfter, histograms: histogramsAfter } = metricAfter
expect(countersAfter.sort((a, b) => a.key.localeCompare(b.key))).toMatchObject([
{
key: 'prisma_client_queries_total',
labels: {},
value: 2, // different from before
description: 'The total number of Prisma Client queries executed',
},
{
key: 'prisma_datasource_queries_total',
labels: {},
value: expect.any(Number), // different from before
description: 'The total number of datasource queries executed',
},
{
key: 'prisma_pool_connections_closed_total',
labels: {},
value: 0,
description: 'The total number of pool connections closed',
},
{
key: 'prisma_pool_connections_opened_total',
labels: {},
value: 0,
description: 'The total number of pool connections opened',
},
])
expect(gaugesAfter.sort((a, b) => a.key.localeCompare(b.key))).toMatchObject([
{
key: 'prisma_client_queries_active',
labels: {},
value: 0,
description: 'The number of currently active Prisma Client queries',
},
{
key: 'prisma_client_queries_wait',
labels: {},
value: 0,
description: 'The number of datasource queries currently waiting for a free connection',
},
{
key: 'prisma_pool_connections_busy',
labels: {},
value: 0,
description: 'The number of pool connections currently executing datasource queries',
},
{
key: 'prisma_pool_connections_idle',
labels: {},
value: 0, // different from SQL providers
description: 'The number of pool connections that are not busy running a query',
},
{
key: 'prisma_pool_connections_open',
labels: {},
value: 0, // different from SQL providers
description: 'The number of pool connections currently open',
},
])
expect(histogramsAfter.sort((a, b) => a.key.localeCompare(b.key))).toMatchObject([
{
key: 'prisma_client_queries_duration_histogram_ms',
labels: {},
value: {
buckets: [
[0, 0],
[1, expect.any(Number)],
[5, expect.any(Number)],
[10, expect.any(Number)],
[50, expect.any(Number)],
[100, expect.any(Number)],
[500, expect.any(Number)],
[1000, expect.any(Number)],
[5000, expect.any(Number)],
[50000, expect.any(Number)],
],
sum: expect.any(Number),
count: expect.any(Number),
},
description: 'The distribution of the time Prisma Client queries took to run end to end',
},
{
key: 'prisma_datasource_queries_duration_histogram_ms',
labels: {},
value: {
buckets: [
[0, expect.any(Number)],
[1, expect.any(Number)],
[5, expect.any(Number)],
[10, expect.any(Number)],
[50, expect.any(Number)],
[100, expect.any(Number)],
[500, expect.any(Number)],
[1000, expect.any(Number)],
[5000, expect.any(Number)],
[50000, expect.any(Number)],
],
sum: expect.any(Number),
count: expect.any(Number),
},
description: 'The distribution of the time datasource queries took to run',
},
])
expect(countersBefore.length).toEqual(countersAfter.length)
expect(gaugesBefore.length).toEqual(gaugesAfter.length)
// TODO: this is currently failing
// See https://github.com/prisma/prisma/issues/21070
// expect(histogramsBefore.length).toEqual(histogramsAfter.length)
// So we test the current behavior
expect(histogramsBefore.length).toBeLessThan(histogramsAfter.length)
})
testIf(provider !== Providers.MONGODB)(
'SQL Providers: should have the same keys, before and after a query',
async () => {
const metricBefore = await prisma.$metrics.json()
// console.log(JSON.stringify(metricBefore, null, 2))
const { counters: countersBefore, gauges: gaugesBefore, histograms: histogramsBefore } = metricBefore
expect(countersBefore.sort((a, b) => a.key.localeCompare(b.key))).toMatchObject([
{
key: 'prisma_client_queries_total',
labels: {},
value: 0,
description: 'The total number of Prisma Client queries executed',
},
{
key: 'prisma_datasource_queries_total',
labels: {},
value: 0,
description: 'The total number of datasource queries executed',
},
{
key: 'prisma_pool_connections_closed_total',
labels: {},
value: 0,
description: 'The total number of pool connections closed',
},
{
key: 'prisma_pool_connections_opened_total',
labels: {},
value: usesDriverAdapter ? 0 : 1,
description: 'The total number of pool connections opened',
},
])
expect(gaugesBefore.sort((a, b) => a.key.localeCompare(b.key))).toMatchObject([
{
key: 'prisma_client_queries_active',
labels: {},
value: 0,
description: 'The number of currently active Prisma Client queries',
},
{
key: 'prisma_client_queries_wait',
labels: {},
// Our test suite shows that the value can be one too few (=> -1) sometimes
// Last seen in `Tests / Client func&legacy-notypes (4/5, library, 20, relationJoins)` run for SQLite, but also happens for other providers.
// Tracking issue: https://github.com/prisma/team-orm/issues/1024
value: expect.toBeOneOf([-1, 0]),
description: 'The number of datasource queries currently waiting for a free connection',
},
{
key: 'prisma_pool_connections_busy',
labels: {},
value: 0,
description: 'The number of pool connections currently executing datasource queries',
},
{
key: 'prisma_pool_connections_idle',
labels: {},
// This can sometimes be reported as 0, 1 or 2,
// as metrics are reported asynchronously.
// Note: We want to investigate why these different values are reported in our test setup
// https://github.com/prisma/team-orm/issues/587
value: expect.toBeOneOf([0, 1, 2]),
description: 'The number of pool connections that are not busy running a query',
},
{
key: 'prisma_pool_connections_open',
labels: {},
value: expect.any(Number), // usually the value is 1 but sometimes 0 on Windows CI
description: 'The number of pool connections currently open',
},
])
if (!usesDriverAdapter) {
expect(histogramsBefore.sort((a, b) => a.key.localeCompare(b.key))).toMatchObject([
{
key: 'prisma_client_queries_wait_histogram_ms',
labels: {},
value: {
buckets: [
[0, 0],
[1, 1],
[5, 0],
[10, 0],
[50, 0],
[100, 0],
[500, 0],
[1000, 0],
[5000, 0],
[50000, 0],
],
sum: expect.any(Number),
count: 1,
},
description: 'The distribution of the time all datasource queries spent waiting for a free connection',
},
])
}
// Send 1 query
await executeOneQuery()
const metricAfter = await prisma.$metrics.json()
const { counters: countersAfter, gauges: gaugesAfter, histograms: histogramsAfter } = metricAfter
expect(countersAfter.sort((a, b) => a.key.localeCompare(b.key))).toMatchObject([
{
key: 'prisma_client_queries_total',
labels: {},
value: 2, // different from before
description: 'The total number of Prisma Client queries executed',
},
{
key: 'prisma_datasource_queries_total',
labels: {},
value: expect.any(Number), // different from before
description: 'The total number of datasource queries executed',
},
{
key: 'prisma_pool_connections_closed_total',
labels: {},
value: 0,
description: 'The total number of pool connections closed',
},
{
key: 'prisma_pool_connections_opened_total',
labels: {},
value: expect.any(Number),
description: 'The total number of pool connections opened',
},
])
expect(gaugesAfter.sort((a, b) => a.key.localeCompare(b.key))).toMatchObject([
{
key: 'prisma_client_queries_active',
labels: {},
value: 0,
description: 'The number of currently active Prisma Client queries',
},
{
key: 'prisma_client_queries_wait',
labels: {},
value: 0,
description: 'The number of datasource queries currently waiting for a free connection',
},
{
key: 'prisma_pool_connections_busy',
labels: {},
// This is either 0 or 1 at this point. We might want to use `waitFor` to wait for a stable
// final state if we know the total number of connections.
value: expect.toBeOneOf([0, 1]),
description: 'The number of pool connections currently executing datasource queries',
},
{
key: 'prisma_pool_connections_idle',
labels: {},
// This can sometimes be reported as 0, 1 or 2,
// as metrics are reported asynchronously.
// Note: We want to investigate why these different values are reported in our test setup
// https://github.com/prisma/team-orm/issues/587
value: expect.toBeOneOf([0, 1, 2]),
description: 'The number of pool connections that are not busy running a query',
},
{
key: 'prisma_pool_connections_open',
labels: {},
value: expect.any(Number),
description: 'The number of pool connections currently open',
},
])
const expectedHistograms = [
{
key: 'prisma_client_queries_duration_histogram_ms',
labels: {},
value: {
buckets: [
[0, 0],
[1, expect.any(Number)],
[5, expect.any(Number)],
[10, expect.any(Number)],
[50, expect.any(Number)],
[100, expect.any(Number)],
[500, expect.any(Number)],
[1000, expect.any(Number)],
[5000, expect.any(Number)],
[50000, expect.any(Number)],
],
sum: expect.any(Number),
count: expect.any(Number),
},
description: 'The distribution of the time Prisma Client queries took to run end to end',
},
]
if (!usesDriverAdapter) {
expectedHistograms.push({
key: 'prisma_client_queries_wait_histogram_ms',
labels: {},
value: {
buckets: [
[0, 0],
[1, expect.any(Number)],
[5, expect.any(Number)],
[10, expect.any(Number)],
[50, expect.any(Number)],
[100, expect.any(Number)],
[500, expect.any(Number)],
[1000, expect.any(Number)],
[5000, expect.any(Number)],
[50000, expect.any(Number)],
],
sum: expect.any(Number),
count: expect.any(Number),
},
description: 'The distribution of the time all datasource queries spent waiting for a free connection',
})
}
expectedHistograms.push({
key: 'prisma_datasource_queries_duration_histogram_ms',
labels: {},
value: {
buckets: [
[0, 0],
[1, expect.any(Number)],
[5, expect.any(Number)],
[10, expect.any(Number)],
[50, expect.any(Number)],
[100, expect.any(Number)],
[500, expect.any(Number)],
[1000, expect.any(Number)],
[5000, expect.any(Number)],
[50000, expect.any(Number)],
],
sum: expect.any(Number),
count: expect.any(Number),
},
description: 'The distribution of the time datasource queries took to run',
})
expect(histogramsAfter.sort((a, b) => a.key.localeCompare(b.key))).toMatchObject(expectedHistograms)
expect(countersBefore.length).toEqual(countersAfter.length)
expect(gaugesBefore.length).toEqual(gaugesAfter.length)
// TODO: this is currently failing
// See https://github.com/prisma/prisma/issues/21070
// expect(histogramsBefore.length).toEqual(histogramsAfter.length)
// So we test the current behavior
expect(histogramsBefore.length).toBeLessThan(histogramsAfter.length)
},
)
})
describe('after a query', () => {
beforeAll(async () => {
await executeOneQuery()
})
// TODO test fails with Expected `11` but got `0` for key "/prisma_client_queries_wait_histogram_ms_bucket/g" See https://github.com/prisma/team-orm/issues/372
test('returns metrics in prometheus format', async () => {
const metrics = await prisma.$metrics.prometheus()
expect((metrics.match(/prisma_client_queries_total \d/g) || []).length).toBe(1)
expect((metrics.match(/prisma_client_queries_active \d/g) || []).length).toBe(1)
// Our test suite shows that the value can be one too few (=> 0) sometimes
// Last seen in `Tests / Client func&legacy-notypes (4/5, library, 20, relationJoins)` run for SQLite, but also happens for other providers.
// Tracking issue: https://github.com/prisma/team-orm/issues/1024
const prisma_client_queries_wait_length = (metrics.match(/prisma_client_queries_wait \d/g) || []).length
expect(prisma_client_queries_wait_length === 0 || prisma_client_queries_wait_length === 1).toBe(true)
expect((metrics.match(/prisma_client_queries_duration_histogram_ms_bucket/g) || []).length).toBe(11)
expect((metrics.match(/prisma_client_queries_duration_histogram_ms_sum .*/g) || []).length).toBe(1)
expect((metrics.match(/prisma_client_queries_duration_histogram_ms_count \d/g) || []).length).toBe(1)
expect((metrics.match(/prisma_datasource_queries_total \d/g) || []).length).toBe(1)
expect((metrics.match(/prisma_datasource_queries_duration_histogram_ms_bucket/g) || []).length).toBe(11)
expect((metrics.match(/prisma_datasource_queries_duration_histogram_ms_sum .*/g) || []).length).toBe(1)
expect((metrics.match(/prisma_datasource_queries_duration_histogram_ms_count \d/g) || []).length).toBe(1)
expect((metrics.match(/prisma_pool_connections_busy \d/g) || []).length).toBe(1)
expect((metrics.match(/prisma_pool_connections_open \d/g) || []).length).toBe(1)
expect((metrics.match(/prisma_pool_connections_idle \d/g) || []).length).toBe(1)
expect((metrics.match(/prisma_pool_connections_closed_total \d/g) || []).length).toBe(1)
expect((metrics.match(/prisma_pool_connections_opened_total \d/g) || []).length).toBe(1)
if (provider === Providers.MONGODB) {
expect((metrics.match(/prisma_client_queries_wait_histogram_ms_bucket/g) || []).length).toBe(0)
expect((metrics.match(/prisma_client_queries_wait_histogram_ms_sum .*/g) || []).length).toBe(0)
expect((metrics.match(/prisma_client_queries_wait_histogram_ms_count \d/g) || []).length).toBe(0)
} else if (!usesDriverAdapter) {
// JS providers have no connection pool metrics
expect((metrics.match(/prisma_client_queries_wait_histogram_ms_bucket/g) || []).length).toBe(11)
expect((metrics.match(/prisma_client_queries_wait_histogram_ms_sum .*/g) || []).length).toBe(1)
expect((metrics.match(/prisma_client_queries_wait_histogram_ms_count \d/g) || []).length).toBe(1)
}
})
test('includes global labels in prometheus format', async () => {
const metrics = await prisma.$metrics.prometheus({ globalLabels: { label1: 'value1', label2: 'value2' } })
expect(metrics).toContain('{label1="value1",label2="value2"}')
})
test('returns metrics in json format', async () => {
const { counters, gauges, histograms } = await prisma.$metrics.json()
expect(counters.length).toBeGreaterThan(0)
expect(counters[0].value).toBeGreaterThan(0)
const counterKeys = counters.map((c) => c.key)
expect(counterKeys).toEqual([
'prisma_client_queries_total',
'prisma_datasource_queries_total',
'prisma_pool_connections_closed_total',
'prisma_pool_connections_opened_total',
])
expect(gauges.length).toBeGreaterThan(0)
expect(gauges[0].value).toBeGreaterThanOrEqual(0)
const gaugesKeys = gauges.map((c) => c.key)
expect(gaugesKeys).toEqual([
'prisma_client_queries_active',
'prisma_client_queries_wait',
'prisma_pool_connections_busy',
'prisma_pool_connections_idle',
'prisma_pool_connections_open',
])
expect(histograms.length).toBeGreaterThan(0)
expect(histograms[0].value.buckets.length).toBeGreaterThan(0)
expect(histograms[0].value.count).toBeGreaterThan(0)
expect(histograms[0].value.sum).toBeGreaterThan(0)
const histogramsKeys = histograms.map((c) => c.key)
if (provider === Providers.MONGODB || usesDriverAdapter) {
// mongo and driver adapter don't use connection pool
expect(histogramsKeys).toEqual([
'prisma_client_queries_duration_histogram_ms',
'prisma_datasource_queries_duration_histogram_ms',
])
} else {
expect(histogramsKeys).toEqual([
'prisma_client_queries_duration_histogram_ms',
'prisma_client_queries_wait_histogram_ms',
'prisma_datasource_queries_duration_histogram_ms',
])
}
for (const [max, count] of histograms[0].value.buckets) {
expect(max).toBeGreaterThanOrEqual(0)
expect(count).toBeGreaterThanOrEqual(0)
}
})
test('includes global labels in json format', async () => {
const metrics = await prisma.$metrics.json({ globalLabels: { label1: 'value1', label2: 'value2' } })
for (const counter of metrics.counters) {
expect(counter.labels).toEqual({ label1: 'value1', label2: 'value2' })
}
for (const gauge of metrics.gauges) {
expect(gauge.labels).toEqual({ label1: 'value1', label2: 'value2' })
}
for (const histogram of metrics.histograms) {
expect(histogram.labels).toEqual({ label1: 'value1', label2: 'value2' })
}
})
})
describe('multiple instances', () => {
test('does not share metrics between 2 different instances of client', async () => {
const secondClient = newPrismaClient()
await secondClient.user.create({
data: {
email: 'second-user@example.com',
},
})
const metrics1 = await prisma.$metrics.json()
const metrics2 = await secondClient.$metrics.json()
expect(metrics1).not.toEqual(metrics2)
})
})
},
{
skipDataProxy: {
runtimes: ['node', 'edge'],
reason: 'Metrics are not supported with Data Proxy yet',
},
skip(when, { clientRuntime }) {
when(clientRuntime === 'wasm-engine-edge', 'Metrics are not supported with WASM engine')
when(clientRuntime === 'client', 'Metrics are not implemented for the client runtime')
},
},
)