@kysera/debug 0.7.3 → 0.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +177 -173
- package/dist/index.d.ts +99 -21
- package/dist/index.js +6 -6
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/circular-buffer.ts +122 -0
- package/src/format.ts +49 -28
- package/src/index.ts +6 -17
- package/src/plugin.ts +62 -77
- package/src/profiler.ts +42 -33
package/README.md
CHANGED
|
@@ -24,26 +24,28 @@ bun add @kysera/debug
|
|
|
24
24
|
- **Performance Metrics** - Collect and analyze query performance data
|
|
25
25
|
- **Slow Query Detection** - Identify and alert on slow database queries
|
|
26
26
|
- **SQL Formatting** - Format and highlight SQL for better readability
|
|
27
|
-
- **Query Profiling** - Detailed performance analysis with statistics
|
|
28
|
-
- **Circular Buffer** -
|
|
27
|
+
- **Query Profiling** - Detailed performance analysis with statistics using O(1) index-based metrics
|
|
28
|
+
- **Circular Buffer** - O(1) memory-efficient metrics storage with automatic cleanup (ring buffer implementation)
|
|
29
29
|
- **Zero Dependencies** - Only depends on `@kysera/core` and peer-depends on `kysely`
|
|
30
30
|
|
|
31
31
|
## Quick Start
|
|
32
32
|
|
|
33
33
|
```typescript
|
|
34
|
-
import { Kysely } from 'kysely'
|
|
35
|
-
import { withDebug } from '@kysera/debug'
|
|
34
|
+
import { Kysely } from 'kysely'
|
|
35
|
+
import { withDebug } from '@kysera/debug'
|
|
36
36
|
|
|
37
37
|
// Wrap your database with debug capabilities
|
|
38
|
-
const db = new Kysely<Database>({
|
|
39
|
-
|
|
38
|
+
const db = new Kysely<Database>({
|
|
39
|
+
/* config */
|
|
40
|
+
})
|
|
41
|
+
const debugDb = withDebug(db)
|
|
40
42
|
|
|
41
43
|
// Execute queries - they're automatically logged and timed
|
|
42
|
-
await debugDb.selectFrom('users').selectAll().execute()
|
|
44
|
+
await debugDb.selectFrom('users').selectAll().execute()
|
|
43
45
|
|
|
44
46
|
// Get collected metrics
|
|
45
|
-
const metrics = debugDb.getMetrics()
|
|
46
|
-
console.log(`Executed ${metrics.length} queries`)
|
|
47
|
+
const metrics = debugDb.getMetrics()
|
|
48
|
+
console.log(`Executed ${metrics.length} queries`)
|
|
47
49
|
```
|
|
48
50
|
|
|
49
51
|
## API Documentation
|
|
@@ -53,10 +55,7 @@ console.log(`Executed ${metrics.length} queries`);
|
|
|
53
55
|
Wraps a Kysely database instance with debug capabilities, adding query logging, metrics collection, and slow query detection.
|
|
54
56
|
|
|
55
57
|
```typescript
|
|
56
|
-
function withDebug<DB>(
|
|
57
|
-
db: Kysely<DB>,
|
|
58
|
-
options?: DebugOptions
|
|
59
|
-
): DebugDatabase<DB>
|
|
58
|
+
function withDebug<DB>(db: Kysely<DB>, options?: DebugOptions): DebugDatabase<DB>
|
|
60
59
|
```
|
|
61
60
|
|
|
62
61
|
#### Parameters
|
|
@@ -71,7 +70,7 @@ function withDebug<DB>(
|
|
|
71
70
|
#### Example
|
|
72
71
|
|
|
73
72
|
```typescript
|
|
74
|
-
import { withDebug } from '@kysera/debug'
|
|
73
|
+
import { withDebug } from '@kysera/debug'
|
|
75
74
|
|
|
76
75
|
const debugDb = withDebug(db, {
|
|
77
76
|
logQuery: true,
|
|
@@ -79,9 +78,9 @@ const debugDb = withDebug(db, {
|
|
|
79
78
|
slowQueryThreshold: 100,
|
|
80
79
|
maxMetrics: 1000,
|
|
81
80
|
onSlowQuery: (sql, duration) => {
|
|
82
|
-
console.warn(`Slow query detected: ${duration}ms`)
|
|
83
|
-
}
|
|
84
|
-
})
|
|
81
|
+
console.warn(`Slow query detected: ${duration}ms`)
|
|
82
|
+
}
|
|
83
|
+
})
|
|
85
84
|
```
|
|
86
85
|
|
|
87
86
|
### DebugOptions
|
|
@@ -94,37 +93,37 @@ interface DebugOptions {
|
|
|
94
93
|
* Log query SQL.
|
|
95
94
|
* @default true
|
|
96
95
|
*/
|
|
97
|
-
logQuery?: boolean
|
|
96
|
+
logQuery?: boolean
|
|
98
97
|
|
|
99
98
|
/**
|
|
100
99
|
* Log query parameters.
|
|
101
100
|
* @default false
|
|
102
101
|
*/
|
|
103
|
-
logParams?: boolean
|
|
102
|
+
logParams?: boolean
|
|
104
103
|
|
|
105
104
|
/**
|
|
106
105
|
* Duration threshold (ms) to consider a query slow.
|
|
107
106
|
* @default 100
|
|
108
107
|
*/
|
|
109
|
-
slowQueryThreshold?: number
|
|
108
|
+
slowQueryThreshold?: number
|
|
110
109
|
|
|
111
110
|
/**
|
|
112
111
|
* Callback for slow queries.
|
|
113
112
|
*/
|
|
114
|
-
onSlowQuery?: (sql: string, duration: number) => void
|
|
113
|
+
onSlowQuery?: (sql: string, duration: number) => void
|
|
115
114
|
|
|
116
115
|
/**
|
|
117
116
|
* Logger for debug messages.
|
|
118
117
|
* @default consoleLogger
|
|
119
118
|
*/
|
|
120
|
-
logger?: KyseraLogger
|
|
119
|
+
logger?: KyseraLogger
|
|
121
120
|
|
|
122
121
|
/**
|
|
123
122
|
* Maximum number of metrics to keep in memory.
|
|
124
123
|
* When limit is reached, oldest metrics are removed (circular buffer).
|
|
125
124
|
* @default 1000
|
|
126
125
|
*/
|
|
127
|
-
maxMetrics?: number
|
|
126
|
+
maxMetrics?: number
|
|
128
127
|
}
|
|
129
128
|
```
|
|
130
129
|
|
|
@@ -135,10 +134,10 @@ Extended database interface with debug capabilities.
|
|
|
135
134
|
```typescript
|
|
136
135
|
interface DebugDatabase<DB> extends Kysely<DB> {
|
|
137
136
|
/** Get all collected query metrics */
|
|
138
|
-
getMetrics(): QueryMetrics[]
|
|
137
|
+
getMetrics(): QueryMetrics[]
|
|
139
138
|
|
|
140
139
|
/** Clear all collected metrics */
|
|
141
|
-
clearMetrics(): void
|
|
140
|
+
clearMetrics(): void
|
|
142
141
|
}
|
|
143
142
|
```
|
|
144
143
|
|
|
@@ -149,9 +148,11 @@ interface DebugDatabase<DB> extends Kysely<DB> {
|
|
|
149
148
|
Returns all collected query metrics.
|
|
150
149
|
|
|
151
150
|
```typescript
|
|
152
|
-
const metrics = debugDb.getMetrics()
|
|
153
|
-
console.log(`Total queries: ${metrics.length}`)
|
|
154
|
-
console.log(
|
|
151
|
+
const metrics = debugDb.getMetrics()
|
|
152
|
+
console.log(`Total queries: ${metrics.length}`)
|
|
153
|
+
console.log(
|
|
154
|
+
`Average duration: ${metrics.reduce((sum, m) => sum + m.duration, 0) / metrics.length}ms`
|
|
155
|
+
)
|
|
155
156
|
```
|
|
156
157
|
|
|
157
158
|
##### clearMetrics()
|
|
@@ -159,7 +160,7 @@ console.log(`Average duration: ${metrics.reduce((sum, m) => sum + m.duration, 0)
|
|
|
159
160
|
Clears all collected metrics from memory.
|
|
160
161
|
|
|
161
162
|
```typescript
|
|
162
|
-
debugDb.clearMetrics()
|
|
163
|
+
debugDb.clearMetrics()
|
|
163
164
|
```
|
|
164
165
|
|
|
165
166
|
### QueryMetrics
|
|
@@ -169,16 +170,16 @@ Query performance metrics data.
|
|
|
169
170
|
```typescript
|
|
170
171
|
interface QueryMetrics {
|
|
171
172
|
/** SQL query string */
|
|
172
|
-
sql: string
|
|
173
|
+
sql: string
|
|
173
174
|
|
|
174
175
|
/** Query parameters */
|
|
175
|
-
params?: unknown[]
|
|
176
|
+
params?: unknown[]
|
|
176
177
|
|
|
177
178
|
/** Query execution duration in milliseconds */
|
|
178
|
-
duration: number
|
|
179
|
+
duration: number
|
|
179
180
|
|
|
180
181
|
/** Timestamp when query was executed */
|
|
181
|
-
timestamp: number
|
|
182
|
+
timestamp: number
|
|
182
183
|
}
|
|
183
184
|
```
|
|
184
185
|
|
|
@@ -186,57 +187,59 @@ interface QueryMetrics {
|
|
|
186
187
|
|
|
187
188
|
Advanced query profiler for collecting and analyzing query performance with detailed statistics.
|
|
188
189
|
|
|
190
|
+
**Implementation:** Uses O(1) index-based circular buffer for efficient metrics tracking with minimal memory overhead.
|
|
191
|
+
|
|
189
192
|
```typescript
|
|
190
193
|
class QueryProfiler {
|
|
191
|
-
constructor(options?: ProfilerOptions)
|
|
194
|
+
constructor(options?: ProfilerOptions)
|
|
192
195
|
|
|
193
196
|
/** Record a query metric */
|
|
194
|
-
record(metric: QueryMetrics): void
|
|
197
|
+
record(metric: QueryMetrics): void
|
|
195
198
|
|
|
196
|
-
/** Get profiling summary */
|
|
197
|
-
getSummary(): ProfilerSummary
|
|
199
|
+
/** Get profiling summary (O(1) using index-based metrics) */
|
|
200
|
+
getSummary(): ProfilerSummary
|
|
198
201
|
|
|
199
202
|
/** Get the slowest N queries */
|
|
200
|
-
getSlowestQueries(count: number): QueryMetrics[]
|
|
203
|
+
getSlowestQueries(count: number): QueryMetrics[]
|
|
201
204
|
|
|
202
205
|
/** Get queries slower than a threshold */
|
|
203
|
-
getSlowQueries(thresholdMs: number): QueryMetrics[]
|
|
206
|
+
getSlowQueries(thresholdMs: number): QueryMetrics[]
|
|
204
207
|
|
|
205
208
|
/** Clear all recorded queries */
|
|
206
|
-
clear(): void
|
|
209
|
+
clear(): void
|
|
207
210
|
|
|
208
211
|
/** Get the number of recorded queries */
|
|
209
|
-
get count(): number
|
|
212
|
+
get count(): number
|
|
210
213
|
}
|
|
211
214
|
```
|
|
212
215
|
|
|
213
216
|
#### Example
|
|
214
217
|
|
|
215
218
|
```typescript
|
|
216
|
-
import { QueryProfiler } from '@kysera/debug'
|
|
219
|
+
import { QueryProfiler } from '@kysera/debug'
|
|
217
220
|
|
|
218
|
-
const profiler = new QueryProfiler({ maxQueries: 500 })
|
|
221
|
+
const profiler = new QueryProfiler({ maxQueries: 500 })
|
|
219
222
|
|
|
220
223
|
// Record queries manually
|
|
221
224
|
profiler.record({
|
|
222
225
|
sql: 'SELECT * FROM users WHERE id = $1',
|
|
223
226
|
params: [123],
|
|
224
227
|
duration: 10.5,
|
|
225
|
-
timestamp: Date.now()
|
|
226
|
-
})
|
|
228
|
+
timestamp: Date.now()
|
|
229
|
+
})
|
|
227
230
|
|
|
228
231
|
// Get summary
|
|
229
|
-
const summary = profiler.getSummary()
|
|
230
|
-
console.log(`Total queries: ${summary.totalQueries}`)
|
|
231
|
-
console.log(`Average duration: ${summary.averageDuration.toFixed(2)}ms`)
|
|
232
|
-
console.log(`Slowest query: ${summary.slowestQuery?.duration.toFixed(2)}ms`)
|
|
232
|
+
const summary = profiler.getSummary()
|
|
233
|
+
console.log(`Total queries: ${summary.totalQueries}`)
|
|
234
|
+
console.log(`Average duration: ${summary.averageDuration.toFixed(2)}ms`)
|
|
235
|
+
console.log(`Slowest query: ${summary.slowestQuery?.duration.toFixed(2)}ms`)
|
|
233
236
|
|
|
234
237
|
// Get slow queries
|
|
235
|
-
const slowQueries = profiler.getSlowQueries(50)
|
|
236
|
-
console.log(`Queries slower than 50ms: ${slowQueries.length}`)
|
|
238
|
+
const slowQueries = profiler.getSlowQueries(50)
|
|
239
|
+
console.log(`Queries slower than 50ms: ${slowQueries.length}`)
|
|
237
240
|
|
|
238
241
|
// Get top 10 slowest
|
|
239
|
-
const top10 = profiler.getSlowestQueries(10)
|
|
242
|
+
const top10 = profiler.getSlowestQueries(10)
|
|
240
243
|
```
|
|
241
244
|
|
|
242
245
|
### ProfilerOptions
|
|
@@ -249,7 +252,7 @@ interface ProfilerOptions {
|
|
|
249
252
|
* Maximum number of queries to keep in memory.
|
|
250
253
|
* @default 1000
|
|
251
254
|
*/
|
|
252
|
-
maxQueries?: number
|
|
255
|
+
maxQueries?: number
|
|
253
256
|
}
|
|
254
257
|
```
|
|
255
258
|
|
|
@@ -260,22 +263,22 @@ Summary statistics from the query profiler.
|
|
|
260
263
|
```typescript
|
|
261
264
|
interface ProfilerSummary {
|
|
262
265
|
/** Total number of recorded queries */
|
|
263
|
-
totalQueries: number
|
|
266
|
+
totalQueries: number
|
|
264
267
|
|
|
265
268
|
/** Sum of all query durations */
|
|
266
|
-
totalDuration: number
|
|
269
|
+
totalDuration: number
|
|
267
270
|
|
|
268
271
|
/** Average query duration */
|
|
269
|
-
averageDuration: number
|
|
272
|
+
averageDuration: number
|
|
270
273
|
|
|
271
274
|
/** Slowest recorded query */
|
|
272
|
-
slowestQuery: QueryMetrics | null
|
|
275
|
+
slowestQuery: QueryMetrics | null
|
|
273
276
|
|
|
274
277
|
/** Fastest recorded query */
|
|
275
|
-
fastestQuery: QueryMetrics | null
|
|
278
|
+
fastestQuery: QueryMetrics | null
|
|
276
279
|
|
|
277
280
|
/** All recorded queries */
|
|
278
|
-
queries: QueryMetrics[]
|
|
281
|
+
queries: QueryMetrics[]
|
|
279
282
|
}
|
|
280
283
|
```
|
|
281
284
|
|
|
@@ -292,10 +295,10 @@ function formatSQL(sql: string): string
|
|
|
292
295
|
**Example:**
|
|
293
296
|
|
|
294
297
|
```typescript
|
|
295
|
-
import { formatSQL } from '@kysera/debug'
|
|
298
|
+
import { formatSQL } from '@kysera/debug'
|
|
296
299
|
|
|
297
|
-
const sql = 'SELECT id, name FROM users WHERE active = true ORDER BY name'
|
|
298
|
-
console.log(formatSQL(sql))
|
|
300
|
+
const sql = 'SELECT id, name FROM users WHERE active = true ORDER BY name'
|
|
301
|
+
console.log(formatSQL(sql))
|
|
299
302
|
// Output:
|
|
300
303
|
// SELECT id, name
|
|
301
304
|
// FROM users
|
|
@@ -312,16 +315,17 @@ function formatSQLPretty(sql: string, indentSize?: number): string
|
|
|
312
315
|
```
|
|
313
316
|
|
|
314
317
|
**Parameters:**
|
|
318
|
+
|
|
315
319
|
- `sql` - SQL string to format
|
|
316
320
|
- `indentSize` - Number of spaces for indentation (default: 2)
|
|
317
321
|
|
|
318
322
|
**Example:**
|
|
319
323
|
|
|
320
324
|
```typescript
|
|
321
|
-
import { formatSQLPretty } from '@kysera/debug'
|
|
325
|
+
import { formatSQLPretty } from '@kysera/debug'
|
|
322
326
|
|
|
323
|
-
const sql = 'SELECT * FROM users WHERE id IN (SELECT user_id FROM orders WHERE total > 100)'
|
|
324
|
-
console.log(formatSQLPretty(sql))
|
|
327
|
+
const sql = 'SELECT * FROM users WHERE id IN (SELECT user_id FROM orders WHERE total > 100)'
|
|
328
|
+
console.log(formatSQLPretty(sql))
|
|
325
329
|
// Output with proper indentation for subqueries
|
|
326
330
|
```
|
|
327
331
|
|
|
@@ -336,14 +340,14 @@ function minifySQL(sql: string): string
|
|
|
336
340
|
**Example:**
|
|
337
341
|
|
|
338
342
|
```typescript
|
|
339
|
-
import { minifySQL } from '@kysera/debug'
|
|
343
|
+
import { minifySQL } from '@kysera/debug'
|
|
340
344
|
|
|
341
345
|
const sql = `
|
|
342
346
|
SELECT id, name
|
|
343
347
|
FROM users
|
|
344
348
|
WHERE active = true
|
|
345
|
-
|
|
346
|
-
console.log(minifySQL(sql))
|
|
349
|
+
`
|
|
350
|
+
console.log(minifySQL(sql))
|
|
347
351
|
// Output: SELECT id, name FROM users WHERE active = true
|
|
348
352
|
```
|
|
349
353
|
|
|
@@ -358,9 +362,9 @@ function highlightSQL(sql: string): string
|
|
|
358
362
|
**Example:**
|
|
359
363
|
|
|
360
364
|
```typescript
|
|
361
|
-
import { highlightSQL } from '@kysera/debug'
|
|
365
|
+
import { highlightSQL } from '@kysera/debug'
|
|
362
366
|
|
|
363
|
-
console.log(highlightSQL('SELECT * FROM users WHERE active = true'))
|
|
367
|
+
console.log(highlightSQL('SELECT * FROM users WHERE active = true'))
|
|
364
368
|
// Keywords will be highlighted in blue in terminal
|
|
365
369
|
```
|
|
366
370
|
|
|
@@ -369,21 +373,19 @@ console.log(highlightSQL('SELECT * FROM users WHERE active = true'));
|
|
|
369
373
|
### Basic Query Logging
|
|
370
374
|
|
|
371
375
|
```typescript
|
|
372
|
-
import { Kysely } from 'kysely'
|
|
373
|
-
import { withDebug } from '@kysera/debug'
|
|
376
|
+
import { Kysely } from 'kysely'
|
|
377
|
+
import { withDebug } from '@kysera/debug'
|
|
374
378
|
|
|
375
|
-
const db = new Kysely<Database>({
|
|
379
|
+
const db = new Kysely<Database>({
|
|
380
|
+
/* config */
|
|
381
|
+
})
|
|
376
382
|
const debugDb = withDebug(db, {
|
|
377
383
|
logQuery: true,
|
|
378
|
-
logParams: false
|
|
379
|
-
})
|
|
384
|
+
logParams: false
|
|
385
|
+
})
|
|
380
386
|
|
|
381
387
|
// Queries are automatically logged
|
|
382
|
-
await debugDb
|
|
383
|
-
.selectFrom('users')
|
|
384
|
-
.where('active', '=', true)
|
|
385
|
-
.selectAll()
|
|
386
|
-
.execute();
|
|
388
|
+
await debugDb.selectFrom('users').where('active', '=', true).selectAll().execute()
|
|
387
389
|
// Console output:
|
|
388
390
|
// [SQL] SELECT * FROM "users" WHERE "active" = $1
|
|
389
391
|
// [Duration] 12.34ms
|
|
@@ -392,129 +394,127 @@ await debugDb
|
|
|
392
394
|
### Detecting Slow Queries
|
|
393
395
|
|
|
394
396
|
```typescript
|
|
395
|
-
import { withDebug } from '@kysera/debug'
|
|
397
|
+
import { withDebug } from '@kysera/debug'
|
|
396
398
|
|
|
397
399
|
const debugDb = withDebug(db, {
|
|
398
400
|
slowQueryThreshold: 50, // 50ms threshold
|
|
399
401
|
onSlowQuery: (sql, duration) => {
|
|
400
402
|
// Send to monitoring service
|
|
401
|
-
monitoring.recordSlowQuery({ sql, duration })
|
|
403
|
+
monitoring.recordSlowQuery({ sql, duration })
|
|
402
404
|
|
|
403
405
|
// Log to error tracking
|
|
404
|
-
logger.warn(`Slow query detected: ${duration.toFixed(2)}ms`, { sql })
|
|
405
|
-
}
|
|
406
|
-
})
|
|
406
|
+
logger.warn(`Slow query detected: ${duration.toFixed(2)}ms`, { sql })
|
|
407
|
+
}
|
|
408
|
+
})
|
|
407
409
|
|
|
408
410
|
// If query takes > 50ms, callback is triggered
|
|
409
|
-
await debugDb.selectFrom('users').selectAll().execute()
|
|
411
|
+
await debugDb.selectFrom('users').selectAll().execute()
|
|
410
412
|
```
|
|
411
413
|
|
|
412
414
|
### Collecting and Analyzing Metrics
|
|
413
415
|
|
|
414
416
|
```typescript
|
|
415
|
-
import { withDebug, formatSQL } from '@kysera/debug'
|
|
417
|
+
import { withDebug, formatSQL } from '@kysera/debug'
|
|
416
418
|
|
|
417
419
|
const debugDb = withDebug(db, {
|
|
418
|
-
maxMetrics: 500
|
|
419
|
-
})
|
|
420
|
+
maxMetrics: 500 // Keep last 500 queries
|
|
421
|
+
})
|
|
420
422
|
|
|
421
423
|
// Execute some queries
|
|
422
|
-
await debugDb.selectFrom('users').selectAll().execute()
|
|
423
|
-
await debugDb.selectFrom('posts').selectAll().execute()
|
|
424
|
+
await debugDb.selectFrom('users').selectAll().execute()
|
|
425
|
+
await debugDb.selectFrom('posts').selectAll().execute()
|
|
424
426
|
|
|
425
427
|
// Analyze metrics
|
|
426
|
-
const metrics = debugDb.getMetrics()
|
|
427
|
-
const totalDuration = metrics.reduce((sum, m) => sum + m.duration, 0)
|
|
428
|
-
const avgDuration = totalDuration / metrics.length
|
|
428
|
+
const metrics = debugDb.getMetrics()
|
|
429
|
+
const totalDuration = metrics.reduce((sum, m) => sum + m.duration, 0)
|
|
430
|
+
const avgDuration = totalDuration / metrics.length
|
|
429
431
|
|
|
430
|
-
console.log(`Total queries: ${metrics.length}`)
|
|
431
|
-
console.log(`Average duration: ${avgDuration.toFixed(2)}ms`)
|
|
432
|
+
console.log(`Total queries: ${metrics.length}`)
|
|
433
|
+
console.log(`Average duration: ${avgDuration.toFixed(2)}ms`)
|
|
432
434
|
|
|
433
435
|
// Find slowest query
|
|
434
|
-
const slowest = metrics.reduce((max, m) =>
|
|
435
|
-
|
|
436
|
-
)
|
|
437
|
-
console.log(
|
|
438
|
-
console.log(formatSQL(slowest.sql));
|
|
439
|
-
console.log(`Duration: ${slowest.duration.toFixed(2)}ms`);
|
|
436
|
+
const slowest = metrics.reduce((max, m) => (m.duration > max.duration ? m : max))
|
|
437
|
+
console.log('Slowest query:')
|
|
438
|
+
console.log(formatSQL(slowest.sql))
|
|
439
|
+
console.log(`Duration: ${slowest.duration.toFixed(2)}ms`)
|
|
440
440
|
```
|
|
441
441
|
|
|
442
442
|
### Advanced Profiling
|
|
443
443
|
|
|
444
444
|
```typescript
|
|
445
|
-
import { QueryProfiler } from '@kysera/debug'
|
|
445
|
+
import { QueryProfiler } from '@kysera/debug'
|
|
446
446
|
|
|
447
|
-
const profiler = new QueryProfiler({ maxQueries: 1000 })
|
|
447
|
+
const profiler = new QueryProfiler({ maxQueries: 1000 })
|
|
448
448
|
|
|
449
449
|
// Record queries from debug database
|
|
450
|
-
const debugDb = withDebug(db)
|
|
450
|
+
const debugDb = withDebug(db)
|
|
451
451
|
// ... execute queries ...
|
|
452
452
|
|
|
453
|
-
const metrics = debugDb.getMetrics()
|
|
454
|
-
metrics.forEach(m => profiler.record(m))
|
|
453
|
+
const metrics = debugDb.getMetrics()
|
|
454
|
+
metrics.forEach(m => profiler.record(m))
|
|
455
455
|
|
|
456
456
|
// Get comprehensive summary
|
|
457
|
-
const summary = profiler.getSummary()
|
|
458
|
-
console.log('Query Performance Summary:')
|
|
459
|
-
console.log(` Total Queries: ${summary.totalQueries}`)
|
|
460
|
-
console.log(` Total Time: ${summary.totalDuration.toFixed(2)}ms`)
|
|
461
|
-
console.log(` Average: ${summary.averageDuration.toFixed(2)}ms`)
|
|
462
|
-
console.log(` Slowest: ${summary.slowestQuery?.duration.toFixed(2)}ms`)
|
|
463
|
-
console.log(` Fastest: ${summary.fastestQuery?.duration.toFixed(2)}ms`)
|
|
457
|
+
const summary = profiler.getSummary()
|
|
458
|
+
console.log('Query Performance Summary:')
|
|
459
|
+
console.log(` Total Queries: ${summary.totalQueries}`)
|
|
460
|
+
console.log(` Total Time: ${summary.totalDuration.toFixed(2)}ms`)
|
|
461
|
+
console.log(` Average: ${summary.averageDuration.toFixed(2)}ms`)
|
|
462
|
+
console.log(` Slowest: ${summary.slowestQuery?.duration.toFixed(2)}ms`)
|
|
463
|
+
console.log(` Fastest: ${summary.fastestQuery?.duration.toFixed(2)}ms`)
|
|
464
464
|
|
|
465
465
|
// Analyze slow queries
|
|
466
|
-
const slowQueries = profiler.getSlowQueries(100)
|
|
466
|
+
const slowQueries = profiler.getSlowQueries(100)
|
|
467
467
|
if (slowQueries.length > 0) {
|
|
468
|
-
console.log(`\nFound ${slowQueries.length} queries slower than 100ms:`)
|
|
468
|
+
console.log(`\nFound ${slowQueries.length} queries slower than 100ms:`)
|
|
469
469
|
slowQueries.forEach(q => {
|
|
470
|
-
console.log(` ${q.duration.toFixed(2)}ms: ${q.sql.substring(0, 80)}...`)
|
|
471
|
-
})
|
|
470
|
+
console.log(` ${q.duration.toFixed(2)}ms: ${q.sql.substring(0, 80)}...`)
|
|
471
|
+
})
|
|
472
472
|
}
|
|
473
473
|
|
|
474
474
|
// Get top 5 slowest
|
|
475
|
-
const top5 = profiler.getSlowestQueries(5)
|
|
476
|
-
console.log('\nTop 5 Slowest Queries:')
|
|
475
|
+
const top5 = profiler.getSlowestQueries(5)
|
|
476
|
+
console.log('\nTop 5 Slowest Queries:')
|
|
477
477
|
top5.forEach((q, i) => {
|
|
478
|
-
console.log(`${i + 1}. ${q.duration.toFixed(2)}ms`)
|
|
479
|
-
console.log(formatSQL(q.sql))
|
|
480
|
-
console.log('')
|
|
481
|
-
})
|
|
478
|
+
console.log(`${i + 1}. ${q.duration.toFixed(2)}ms`)
|
|
479
|
+
console.log(formatSQL(q.sql))
|
|
480
|
+
console.log('')
|
|
481
|
+
})
|
|
482
482
|
```
|
|
483
483
|
|
|
484
484
|
### Custom Logger Integration
|
|
485
485
|
|
|
486
486
|
```typescript
|
|
487
|
-
import { withDebug } from '@kysera/debug'
|
|
488
|
-
import type { KyseraLogger } from '@kysera/core'
|
|
487
|
+
import { withDebug } from '@kysera/debug'
|
|
488
|
+
import type { KyseraLogger } from '@kysera/core'
|
|
489
489
|
|
|
490
490
|
// Custom logger implementation
|
|
491
491
|
const customLogger: KyseraLogger = {
|
|
492
492
|
debug: (message: string) => {
|
|
493
493
|
// Send to logging service
|
|
494
|
-
loggingService.debug('db-query', message)
|
|
494
|
+
loggingService.debug('db-query', message)
|
|
495
495
|
},
|
|
496
496
|
info: (message: string) => {
|
|
497
|
-
loggingService.info('db-query', message)
|
|
497
|
+
loggingService.info('db-query', message)
|
|
498
498
|
},
|
|
499
499
|
warn: (message: string) => {
|
|
500
|
-
loggingService.warn('db-query', message)
|
|
500
|
+
loggingService.warn('db-query', message)
|
|
501
501
|
},
|
|
502
502
|
error: (message: string) => {
|
|
503
|
-
loggingService.error('db-query', message)
|
|
504
|
-
}
|
|
505
|
-
}
|
|
503
|
+
loggingService.error('db-query', message)
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
506
|
|
|
507
507
|
const debugDb = withDebug(db, {
|
|
508
508
|
logger: customLogger,
|
|
509
509
|
logQuery: true,
|
|
510
|
-
logParams: true
|
|
511
|
-
})
|
|
510
|
+
logParams: true
|
|
511
|
+
})
|
|
512
512
|
```
|
|
513
513
|
|
|
514
514
|
### Production Monitoring
|
|
515
515
|
|
|
516
516
|
```typescript
|
|
517
|
-
import { withDebug } from '@kysera/debug'
|
|
517
|
+
import { withDebug } from '@kysera/debug'
|
|
518
518
|
|
|
519
519
|
const debugDb = withDebug(db, {
|
|
520
520
|
logQuery: false, // Don't log in production
|
|
@@ -525,51 +525,52 @@ const debugDb = withDebug(db, {
|
|
|
525
525
|
apm.recordTransaction({
|
|
526
526
|
type: 'db.query',
|
|
527
527
|
duration,
|
|
528
|
-
metadata: { sql }
|
|
529
|
-
})
|
|
528
|
+
metadata: { sql }
|
|
529
|
+
})
|
|
530
530
|
|
|
531
531
|
// Alert if extremely slow
|
|
532
532
|
if (duration > 1000) {
|
|
533
533
|
alerting.critical('Database query exceeded 1 second', {
|
|
534
534
|
sql,
|
|
535
|
-
duration
|
|
536
|
-
})
|
|
535
|
+
duration
|
|
536
|
+
})
|
|
537
537
|
}
|
|
538
|
-
}
|
|
539
|
-
})
|
|
538
|
+
}
|
|
539
|
+
})
|
|
540
540
|
|
|
541
541
|
// Periodic metrics reporting
|
|
542
542
|
setInterval(() => {
|
|
543
|
-
const metrics = debugDb.getMetrics()
|
|
543
|
+
const metrics = debugDb.getMetrics()
|
|
544
544
|
if (metrics.length > 0) {
|
|
545
|
-
const avgDuration = metrics.reduce((sum, m) => sum + m.duration, 0) / metrics.length
|
|
545
|
+
const avgDuration = metrics.reduce((sum, m) => sum + m.duration, 0) / metrics.length
|
|
546
546
|
|
|
547
|
-
monitoring.gauge('db.query.avg_duration', avgDuration)
|
|
548
|
-
monitoring.gauge('db.query.count', metrics.length)
|
|
547
|
+
monitoring.gauge('db.query.avg_duration', avgDuration)
|
|
548
|
+
monitoring.gauge('db.query.count', metrics.length)
|
|
549
549
|
|
|
550
|
-
debugDb.clearMetrics()
|
|
550
|
+
debugDb.clearMetrics() // Reset for next interval
|
|
551
551
|
}
|
|
552
|
-
}, 60000)
|
|
552
|
+
}, 60000) // Every minute
|
|
553
553
|
```
|
|
554
554
|
|
|
555
555
|
### Formatting SQL for Display
|
|
556
556
|
|
|
557
557
|
```typescript
|
|
558
|
-
import { formatSQL, formatSQLPretty, highlightSQL } from '@kysera/debug'
|
|
558
|
+
import { formatSQL, formatSQLPretty, highlightSQL } from '@kysera/debug'
|
|
559
559
|
|
|
560
|
-
const sql =
|
|
560
|
+
const sql =
|
|
561
|
+
'SELECT u.id, u.name, COUNT(p.id) as post_count FROM users u LEFT JOIN posts p ON u.id = p.user_id WHERE u.active = true GROUP BY u.id, u.name ORDER BY post_count DESC'
|
|
561
562
|
|
|
562
563
|
// Basic formatting
|
|
563
|
-
console.log('Basic Format:')
|
|
564
|
-
console.log(formatSQL(sql))
|
|
564
|
+
console.log('Basic Format:')
|
|
565
|
+
console.log(formatSQL(sql))
|
|
565
566
|
|
|
566
567
|
// Pretty formatting with indentation
|
|
567
|
-
console.log('\nPretty Format:')
|
|
568
|
-
console.log(formatSQLPretty(sql))
|
|
568
|
+
console.log('\nPretty Format:')
|
|
569
|
+
console.log(formatSQLPretty(sql))
|
|
569
570
|
|
|
570
571
|
// Highlighted for terminal
|
|
571
|
-
console.log('\nHighlighted:')
|
|
572
|
-
console.log(highlightSQL(formatSQL(sql)))
|
|
572
|
+
console.log('\nHighlighted:')
|
|
573
|
+
console.log(highlightSQL(formatSQL(sql)))
|
|
573
574
|
```
|
|
574
575
|
|
|
575
576
|
## TypeScript Support
|
|
@@ -582,40 +583,41 @@ import type {
|
|
|
582
583
|
DebugOptions,
|
|
583
584
|
DebugDatabase,
|
|
584
585
|
ProfilerSummary,
|
|
585
|
-
ProfilerOptions
|
|
586
|
-
} from '@kysera/debug'
|
|
586
|
+
ProfilerOptions
|
|
587
|
+
} from '@kysera/debug'
|
|
587
588
|
|
|
588
589
|
// Type-safe debug options
|
|
589
590
|
const options: DebugOptions = {
|
|
590
591
|
logQuery: true,
|
|
591
592
|
slowQueryThreshold: 100,
|
|
592
593
|
onSlowQuery: (sql: string, duration: number) => {
|
|
593
|
-
console.warn(`Slow: ${sql} (${duration}ms)`)
|
|
594
|
-
}
|
|
595
|
-
}
|
|
594
|
+
console.warn(`Slow: ${sql} (${duration}ms)`)
|
|
595
|
+
}
|
|
596
|
+
}
|
|
596
597
|
|
|
597
598
|
// Type-safe database
|
|
598
599
|
interface Database {
|
|
599
|
-
users: { id: number; name: string }
|
|
600
|
+
users: { id: number; name: string }
|
|
600
601
|
}
|
|
601
602
|
|
|
602
|
-
const debugDb: DebugDatabase<Database> = withDebug(db, options)
|
|
603
|
+
const debugDb: DebugDatabase<Database> = withDebug(db, options)
|
|
603
604
|
```
|
|
604
605
|
|
|
605
606
|
## Performance Considerations
|
|
606
607
|
|
|
607
608
|
### Memory Management
|
|
608
609
|
|
|
609
|
-
The debug plugin uses
|
|
610
|
+
The debug plugin uses an O(1) circular buffer (ring buffer) to manage memory efficiently:
|
|
610
611
|
|
|
611
612
|
- Default limit: 1000 metrics
|
|
612
|
-
- Oldest metrics automatically removed when limit reached
|
|
613
|
+
- Oldest metrics automatically removed when limit reached (O(1) operation)
|
|
613
614
|
- Configure via `maxMetrics` option
|
|
615
|
+
- Index-based metrics tracking for constant-time summary calculations
|
|
614
616
|
|
|
615
617
|
```typescript
|
|
616
618
|
const debugDb = withDebug(db, {
|
|
617
|
-
maxMetrics: 500
|
|
618
|
-
})
|
|
619
|
+
maxMetrics: 500 // Keep only last 500 queries
|
|
620
|
+
})
|
|
619
621
|
```
|
|
620
622
|
|
|
621
623
|
### Production Usage
|
|
@@ -623,29 +625,31 @@ const debugDb = withDebug(db, {
|
|
|
623
625
|
For production environments:
|
|
624
626
|
|
|
625
627
|
1. **Disable verbose logging:**
|
|
628
|
+
|
|
626
629
|
```typescript
|
|
627
630
|
const debugDb = withDebug(db, {
|
|
628
631
|
logQuery: false,
|
|
629
|
-
logParams: false
|
|
630
|
-
})
|
|
632
|
+
logParams: false
|
|
633
|
+
})
|
|
631
634
|
```
|
|
632
635
|
|
|
633
636
|
2. **Use slow query detection only:**
|
|
637
|
+
|
|
634
638
|
```typescript
|
|
635
639
|
const debugDb = withDebug(db, {
|
|
636
640
|
logQuery: false,
|
|
637
641
|
slowQueryThreshold: 500,
|
|
638
642
|
onSlowQuery: (sql, duration) => {
|
|
639
|
-
monitoring.recordSlowQuery({ sql, duration })
|
|
640
|
-
}
|
|
641
|
-
})
|
|
643
|
+
monitoring.recordSlowQuery({ sql, duration })
|
|
644
|
+
}
|
|
645
|
+
})
|
|
642
646
|
```
|
|
643
647
|
|
|
644
648
|
3. **Limit metrics collection:**
|
|
645
649
|
```typescript
|
|
646
650
|
const debugDb = withDebug(db, {
|
|
647
|
-
maxMetrics: 100
|
|
648
|
-
})
|
|
651
|
+
maxMetrics: 100 // Smaller buffer for production
|
|
652
|
+
})
|
|
649
653
|
```
|
|
650
654
|
|
|
651
655
|
## Runtime Compatibility
|