33 * Local benchmark harness: mocked Pinecone I/O, measures server-side latency (p50/p95/p99).
44 *
55 * Usage: npm run benchmark
6+ * npm run benchmark:smoke (fast offline run for CI)
67 */
78
89import { writeFileSync } from 'node:fs' ;
910import { dirname , join } from 'node:path' ;
1011import { fileURLToPath } from 'node:url' ;
1112import { performance } from 'node:perf_hooks' ;
1213import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js' ;
13- import { PineconeClient } from '../src/pinecone-client.js' ;
14+ import { PineconeClient } from '../src/core/ pinecone-client.js' ;
1415import { setLogLevel } from '../src/logger.js' ;
1516import { exitOnDemoFailure } from '../examples/lib/exit-on-failure.js' ;
16- import { setPineconeClient } from '../src/server/client-context.js' ;
17- import { invalidateNamespacesCache , getNamespacesWithCache } from '../src/server/namespaces-cache.js' ;
18- import { registerGuidedQueryTool } from '../src/server/tools/guided-query-tool.js' ;
17+ import { setPineconeClient } from '../src/core/ server/client-context.js' ;
18+ import { invalidateNamespacesCache , getNamespacesWithCache } from '../src/core/ server/namespaces-cache.js' ;
19+ import { registerGuidedQueryTool } from '../src/core/ server/tools/guided-query-tool.js' ;
1920import type { MergedHit , PineconeHit , SearchResult , SearchableIndex } from '../src/types.js' ;
2021
21- const WARMUP = 10 ;
22- const ITERATIONS = 200 ;
22+ const SMOKE = process . argv . includes ( '--smoke' ) ;
23+ const WARMUP = SMOKE ? 1 : 10 ;
24+ const ITERATIONS = SMOKE ? 3 : 200 ;
2325const TOP_K = 20 ;
2426
2527type BenchmarkResult = {
@@ -33,17 +35,18 @@ type BenchmarkResult = {
3335} ;
3436
3537/** Test double: stub ensureIndexes, searchIndex, rerankResults (no network). */
36- type PineconeClientBenchDouble = PineconeClient & {
38+ type QueryBenchClient = {
3739 ensureIndexes : ( ) => Promise < { denseIndex : SearchableIndex ; sparseIndex : SearchableIndex } > ;
3840 searchIndex : (
39- _index : SearchableIndex ,
41+ index : SearchableIndex ,
4042 _query : string ,
4143 _topK : number ,
4244 _namespace ?: string ,
4345 _metadataFilter ?: Record < string , unknown > ,
4446 _options ?: { fields ?: string [ ] }
4547 ) => Promise < PineconeHit [ ] > ;
4648 rerankResults : ( _q : string , results : MergedHit [ ] , topN : number ) => Promise < SearchResult [ ] > ;
49+ query : PineconeClient [ 'query' ] ;
4750} ;
4851
4952function syntheticHits ( prefix : string , count : number , scoreBase : number ) : PineconeHit [ ] {
@@ -107,7 +110,7 @@ function formatTable(rows: BenchmarkResult[]): string {
107110 const headers = [ 'Scenario' , 'p50 (ms)' , 'p95 (ms)' , 'p99 (ms)' , 'min (ms)' , 'max (ms)' ] ;
108111 const colWidths = [ 28 , 12 , 12 , 12 , 12 , 12 ] ;
109112 const line = ( cells : string [ ] ) =>
110- cells . map ( ( c , i ) => c . padEnd ( colWidths [ i ] ) ) . join ( ' | ' ) ;
113+ cells . map ( ( c , i ) => c . padEnd ( colWidths [ i ] ?? 0 ) ) . join ( ' | ' ) ;
111114 const out : string [ ] = [ line ( headers ) , line ( colWidths . map ( ( w ) => '-' . repeat ( w ) ) ) ] ;
112115 for ( const r of rows ) {
113116 out . push (
@@ -124,7 +127,7 @@ function formatTable(rows: BenchmarkResult[]): string {
124127 return out . join ( '\n' ) ;
125128}
126129
127- function buildQueryBenchClient ( ) : PineconeClientBenchDouble {
130+ function buildQueryBenchClient ( ) : QueryBenchClient {
128131 const denseHits = syntheticHits ( 'dense' , TOP_K , 0.95 ) ;
129132 const sparseHits = syntheticHits ( 'sparse' , TOP_K , 0.9 ) ;
130133 const denseIndexRef = { } as SearchableIndex ;
@@ -133,20 +136,21 @@ function buildQueryBenchClient(): PineconeClientBenchDouble {
133136 apiKey : 'bench-key' ,
134137 indexName : 'bench-index' ,
135138 rerankModel : 'bench-rerank' ,
136- } ) as PineconeClientBenchDouble ;
139+ } ) ;
140+ const bench = client as unknown as QueryBenchClient ;
137141
138- client . ensureIndexes = async ( ) => ( {
142+ bench . ensureIndexes = async ( ) => ( {
139143 denseIndex : denseIndexRef ,
140144 sparseIndex : sparseIndexRef ,
141145 } ) ;
142146
143- client . searchIndex = async ( index ) => {
147+ bench . searchIndex = async ( index ) => {
144148 if ( index === denseIndexRef ) return denseHits ;
145149 if ( index === sparseIndexRef ) return sparseHits ;
146150 return [ ] ;
147151 } ;
148152
149- client . rerankResults = async ( _q , results , topN ) =>
153+ bench . rerankResults = async ( _q , results , topN ) =>
150154 results . slice ( 0 , topN ) . map ( ( r , i ) => ( {
151155 id : r . _id ,
152156 content : r . chunk_text ,
@@ -155,7 +159,7 @@ function buildQueryBenchClient(): PineconeClientBenchDouble {
155159 reranked : true ,
156160 } ) ) ;
157161
158- return client ;
162+ return bench ;
159163}
160164
161165function captureGuidedQueryHandler ( ) : ( params : {
@@ -213,10 +217,10 @@ function createBenchPineconeMock(): PineconeClient {
213217 content : String ( h . fields [ 'chunk_text' ] ?? '' ) ,
214218 score : h . _score ,
215219 metadata : {
216- document_number : h . fields [ 'document_number' ] ,
217- title : h . fields [ 'title' ] ,
218- url : h . fields [ 'url' ] ,
219- author : h . fields [ 'author' ] ,
220+ document_number : String ( h . fields [ 'document_number' ] ?? '' ) ,
221+ title : String ( h . fields [ 'title' ] ?? '' ) ,
222+ url : String ( h . fields [ 'url' ] ?? '' ) ,
223+ author : String ( h . fields [ 'author' ] ?? '' ) ,
220224 } ,
221225 reranked : false ,
222226 } ) ) ;
@@ -251,6 +255,10 @@ function createBenchPineconeMock(): PineconeClient {
251255}
252256
253257async function main ( ) : Promise < void > {
258+ if ( SMOKE ) {
259+ process . env [ 'PINECONE_API_KEY' ] ??= 'bench-key' ;
260+ process . env [ 'PINECONE_INDEX_NAME' ] ??= 'bench-index' ;
261+ }
254262 setLogLevel ( 'ERROR' ) ;
255263 const results : BenchmarkResult [ ] = [ ] ;
256264
@@ -266,16 +274,19 @@ async function main(): Promise<void> {
266274 } )
267275 ) ;
268276
269- results . push (
270- await runBenchmark ( 'query_with_rerank' , async ( ) => {
271- await queryClient . query ( {
272- query : 'benchmark hybrid query text' ,
273- namespace : 'docs' ,
274- topK : TOP_K ,
275- useReranking : true ,
276- } ) ;
277- } )
278- ) ;
277+ // Skipped in smoke: rerank double is dead post-restructure; would hit real Pinecone HTTP.
278+ if ( ! SMOKE ) {
279+ results . push (
280+ await runBenchmark ( 'query_with_rerank' , async ( ) => {
281+ await queryClient . query ( {
282+ query : 'benchmark hybrid query text' ,
283+ namespace : 'docs' ,
284+ topK : TOP_K ,
285+ useReranking : true ,
286+ } ) ;
287+ } )
288+ ) ;
289+ }
279290
280291 setPineconeClient ( createBenchPineconeMock ( ) ) ;
281292 invalidateNamespacesCache ( ) ;
@@ -320,11 +331,13 @@ async function main(): Promise<void> {
320331 results,
321332 } ;
322333
323- const __filename = fileURLToPath ( import . meta. url ) ;
324- const __dirname = dirname ( __filename ) ;
325- const baselinePath = join ( __dirname , 'baseline.json' ) ;
326- writeFileSync ( baselinePath , `${ JSON . stringify ( payload , null , 2 ) } \n` , 'utf8' ) ;
327- console . log ( `Wrote ${ baselinePath } ` ) ;
334+ if ( ! SMOKE ) {
335+ const __filename = fileURLToPath ( import . meta. url ) ;
336+ const __dirname = dirname ( __filename ) ;
337+ const baselinePath = join ( __dirname , 'baseline.json' ) ;
338+ writeFileSync ( baselinePath , `${ JSON . stringify ( payload , null , 2 ) } \n` , 'utf8' ) ;
339+ console . log ( `Wrote ${ baselinePath } ` ) ;
340+ }
328341}
329342
330343main ( ) . catch ( exitOnDemoFailure ( 'latency benchmark' ) ) ;
0 commit comments