OverviewFilesCommitsIssuesDocsReleasesPeers

knowledge-ratchet / atomspace / test / bench.js

165 lines · javascript ref:
#!/usr/bin/env node

/**
 * bench.js — Benchmarks for the AtomSpace knowledge layer
 *
 * Measures:
 *   1. CogServer connection latency
 *   2. Single eval round-trip
 *   3. Ingest throughput (atoms/sec)
 *   4. Query latency (blastRadius, similarLoops, riskAssessment)
 *   5. MeetLink vs incoming-set traversal
 *   6. Graph size and composition
 *   7. Preflight hook end-to-end latency
 *
 * Usage: node atomspace/test/bench.js
 * Requires: CogServer running on port 17001
 */

const { isCogServerUp, evalScheme } = require('../cogserver')
const { blastRadius, similarLoops, riskAssessment } = require('../query')
const { ingestCodetree, treeToScheme, scanCodetree } = require('../ingest-codetree')
const { ingestRatchet } = require('../ingest-ratchet')
const { execSync } = require('child_process')
const path = require('path')

async function timeIt (name, fn, iterations = 1) {
  const times = []
  for (let i = 0; i < iterations; i++) {
    const start = Date.now()
    await fn()
    times.push(Date.now() - start)
  }
  times.sort((a, b) => a - b)
  const avg = times.reduce((a, b) => a + b, 0) / times.length
  const min = times[0]
  const max = times[times.length - 1]
  const median = times[Math.floor(times.length / 2)]
  const rate = iterations > 1 ? `  (${(1000 / avg).toFixed(0)} ops/sec)` : ''
  console.log(`  ${name.padEnd(40)} avg=${avg.toFixed(0)}ms  min=${min}ms  max=${max}ms  median=${median}ms${rate}`)
  return { avg, min, max, median }
}

async function main () {
  console.log('AtomSpace Knowledge Layer — Benchmarks')
  console.log('=' .repeat(60))

  const up = await isCogServerUp()
  if (!up) {
    console.log('\nCogServer not running. Start it first: cogserver &')
    process.exit(1)
  }

  // ── 1. Connection ──
  console.log('\n── Connection ──')
  await timeIt('isCogServerUp() probe', () => isCogServerUp(), 20)
  await timeIt('evalScheme("(+ 1 1)") round-trip', () => evalScheme('(+ 1 1)'), 20)

  // ── 2. Graph size ──
  console.log('\n── Graph Size ──')
  const counts = await evalScheme('(cog-report-counts)')
  console.log('  ' + counts)

  const edgeCount = await evalScheme('(length (cog-get-atoms (quote EdgeLink)))')
  const conceptCount = await evalScheme('(length (cog-get-atoms (quote ConceptNode)))')
  console.log(`  ConceptNodes: ${conceptCount}  EdgeLinks: ${edgeCount}`)

  const bonds = ['contains', 'imports', 'depends-on', 'exports', 'loop-in-file', 'rule-for-file']
  for (const b of bonds) {
    const c = await evalScheme(`(length (cog-incoming-by-type (BondNode "${b}") (quote EdgeLink)))`)
    console.log(`    ${b}: ${c} edges`)
  }

  // ── 3. Query latency ──
  console.log('\n── Query Latency ──')

  const testFiles = [
    'hyper-overlay/index.js',
    'hypernote/lib/auth.js',
    'pear-git-v3/lib/pear-git.js',
    'pear-git/pear-git-server.js',
    'codetree/lib/parser.js'
  ]

  for (const f of testFiles) {
    await timeIt(`blastRadius(${f.split('/').pop()})`,
      () => blastRadius(f), 5)
  }

  await timeIt('similarLoops(3 files)',
    () => similarLoops(['hyper-overlay/index.js', 'hypernote/lib/auth.js', 'pear-git/pear-git-server.js']), 5)

  await timeIt('riskAssessment(3 files)',
    () => riskAssessment(['hyper-overlay/index.js', 'pear-git-v3/lib/pear-git.js', 'hypernote/lib/auth.js']), 5)

  // ── 4. Raw MeetLink latency ──
  console.log('\n── Raw MeetLink vs Incoming-Set ──')

  await timeIt('MeetLink: children of mod:hypernote',
    () => evalScheme('(cog-execute! (Meet (TypedVariable (Variable "$f") (Type (quote ConceptNode))) (Present (EdgeLink (BondNode "contains") (ListLink (ConceptNode "mod:hypernote") (Variable "$f"))))))'), 10)

  await timeIt('incoming-set: edges of BondNode',
    () => evalScheme('(length (cog-incoming-by-type (BondNode "contains") (quote EdgeLink)))'), 10)

  await timeIt('incoming-set: ListLinks for a file',
    () => evalScheme('(length (cog-incoming-by-type (ConceptNode "file:hyper-overlay/index.js") (quote ListLink)))'), 10)

  // ── 5. Ingest throughput ──
  console.log('\n── Ingest Throughput ──')

  // Codetree scan (no ingest, just measure scan speed)
  const scanResult = await timeIt('codetree scan (JSON generation)',
    () => { scanCodetree(); return true }, 1)

  const tree = scanCodetree()
  const exprs = treeToScheme(tree)
  console.log(`  Codetree: ${exprs.length} Scheme expressions from ${Object.keys(tree.modules || {}).length} modules`)

  // Measure batch ingest speed
  const batchSizes = [10, 50, 100]
  for (const size of batchSizes) {
    const batch = exprs.slice(0, size)
    await timeIt(`ingest batch of ${size} expressions`,
      () => evalScheme('(begin ' + batch.join(' ') + ')'), 3)
  }

  // Full ingest
  const fullStart = Date.now()
  await ingestRatchet()
  const ratchetMs = Date.now() - fullStart
  console.log(`  Full ratchet ingest: ${ratchetMs}ms`)

  // ── 6. Preflight hook end-to-end ──
  console.log('\n── Preflight Hook End-to-End ──')

  const hookTestFiles = [
    '/storage/projects/code/hypercore-kb/hyper-fleeky/hyper-overlay/index.js',
    '/storage/projects/code/hypercore-kb/hyper-fleeky/hypernote/lib/auth.js',
    '/storage/projects/code/hypercore-kb/hyper-fleeky/codetree/lib/parser.js'
  ]

  for (const f of hookTestFiles) {
    // Clear state so it doesn't skip
    try { require('fs').unlinkSync('/tmp/claude-preflight-state.json') } catch {}

    const label = f.split('/').pop()
    await timeIt(`preflight hook (${label})`, async () => {
      const input = JSON.stringify({ tool_name: 'Edit', tool_input: { file_path: f, old_string: 'x', new_string: 'y' } })
      try {
        execSync(`echo '${input}' | node ${path.resolve(__dirname, '../../preflight-review.js')}`, {
          timeout: 5000, stdio: 'pipe'
        })
      } catch {}
    }, 3)
  }

  // ── Summary ──
  console.log('\n' + '=' .repeat(60))
  console.log('Benchmark complete.')
}

main().catch(err => {
  console.error('Bench failed:', err.message)
  process.exit(1)
})