Skip to content

Commit

Permalink
chore: add transfer benchmark
Browse files Browse the repository at this point in the history
Adds a benchmark suite for doing various size data transfers between
helia, kubo and js-ipfs.

Closes #88
  • Loading branch information
achingbrain committed Apr 13, 2023
1 parent 3446638 commit 06c53b9
Show file tree
Hide file tree
Showing 12 changed files with 386 additions and 23 deletions.
6 changes: 3 additions & 3 deletions benchmarks/gc/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@
"helia": "^1.0.0",
"ipfs-core": "^0.18.0",
"ipfsd-ctl": "^13.0.0",
"it-all": "^2.0.0",
"it-drain": "^2.0.0",
"it-map": "^2.0.1",
"it-all": "^3.0.1",
"it-drain": "^3.0.1",
"it-map": "^3.0.2",
"kubo-rpc-client": "^3.0.1",
"libp2p": "^0.43.0",
"multiformats": "^11.0.1",
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/gc/src/graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const ITERATIONS = 2
const INCREMENT = 1000
const MAX = 10000

for (let i = 1; i <= MAX / INCREMENT; i ++) {
for (let i = 1; i <= MAX / INCREMENT; i++) {
await execa('node', ['dist/src/index.js'], {
env: {
...process.env,
Expand Down
8 changes: 4 additions & 4 deletions benchmarks/gc/src/helia.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,11 @@ export async function createHeliaBenchmark (): Promise<GcBenchmark> {

return pins.length
},
isPinned: (cid) => {
return helia.pins.isPinned(cid)
isPinned: async (cid) => {
return await helia.pins.isPinned(cid)
},
hasBlock: (cid) => {
return helia.blockstore.has(cid)
hasBlock: async (cid) => {
return await helia.blockstore.has(cid)
}
}
}
18 changes: 10 additions & 8 deletions benchmarks/gc/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* eslint-disable no-console,no-loop-func */

import { Bench } from 'tinybench'
import { CID } from 'multiformats/cid'
import { createHeliaBenchmark } from './helia.js'
Expand All @@ -15,7 +17,7 @@ const RESULT_PRECISION = 2
export interface GcBenchmark {
gc: () => Promise<void>
teardown: () => Promise<void>
pin: (cid: CID ) => Promise<void>
pin: (cid: CID) => Promise<void>
putBlocks: (blocks: Array<{ key: CID, value: Uint8Array }>) => Promise<void>
clearPins: () => Promise<number>
isPinned: (cid: CID) => Promise<boolean>
Expand Down Expand Up @@ -90,9 +92,9 @@ async function pinBlocks (benchmark: GcBenchmark): Promise<void> {
}
}

const impls: Array<{ name: string, create: () => Promise<GcBenchmark>, results: { gc: number[], clearedPins: number[], addedBlocks: number[], pinnedBlocks: number[] }}> = [{
const impls: Array<{ name: string, create: () => Promise<GcBenchmark>, results: { gc: number[], clearedPins: number[], addedBlocks: number[], pinnedBlocks: number[] } }> = [{
name: 'helia',
create: () => createHeliaBenchmark(),
create: async () => await createHeliaBenchmark(),
results: {
gc: [],
clearedPins: [],
Expand All @@ -101,7 +103,7 @@ const impls: Array<{ name: string, create: () => Promise<GcBenchmark>, results:
}
}, {
name: 'ipfs',
create: () => createIpfsBenchmark(),
create: async () => await createIpfsBenchmark(),
results: {
gc: [],
clearedPins: [],
Expand All @@ -110,7 +112,7 @@ const impls: Array<{ name: string, create: () => Promise<GcBenchmark>, results:
}
}, {
name: 'kubo',
create: () => createKuboBenchmark(),
create: async () => await createKuboBenchmark(),
results: {
gc: [],
clearedPins: [],
Expand Down Expand Up @@ -174,15 +176,15 @@ async function main (): Promise<void> {
`${(impl.results.clearedPins.reduce((acc, curr) => acc + curr, 0) / impl.results.clearedPins.length).toFixed(RESULT_PRECISION)},`,
`${(impl.results.addedBlocks.reduce((acc, curr) => acc + curr, 0) / impl.results.addedBlocks.length).toFixed(RESULT_PRECISION)},`,
`${(impl.results.pinnedBlocks.reduce((acc, curr) => acc + curr, 0) / impl.results.pinnedBlocks.length).toFixed(RESULT_PRECISION)},`,
`${(impl.results.gc.reduce((acc, curr) => acc + curr, 0) / impl.results.gc.length).toFixed(RESULT_PRECISION)}`,
`${(impl.results.gc.reduce((acc, curr) => acc + curr, 0) / impl.results.gc.length).toFixed(RESULT_PRECISION)}`
)
}
} else {
console.table(suite.tasks.map(({ name, result }) => ({
'Implementation': name,
Implementation: name,
'ops/s': result?.hz.toFixed(RESULT_PRECISION),
'ms/op': result?.period.toFixed(RESULT_PRECISION),
'runs': result?.samples.length
runs: result?.samples.length
})))
}
}
Expand Down
8 changes: 1 addition & 7 deletions benchmarks/gc/src/kubo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,7 @@ export async function createKuboBenchmark (): Promise<GcBenchmark> {
paths: cid
}))

const isPinned = result[0].type.includes('direct') || result[0].type.includes('indirect') || result[0].type.includes('recursive')

if (!isPinned) {
console.info(result)
}

return isPinned
return result[0].type.includes('direct') || result[0].type.includes('indirect') || result[0].type.includes('recursive')
},
hasBlock: async (cid) => {
try {
Expand Down
41 changes: 41 additions & 0 deletions benchmarks/transfer/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"name": "benchmarks-transfer",
"version": "1.0.0",
"main": "index.js",
"private": true,
"type": "module",
"scripts": {
"clean": "aegir clean",
"build": "aegir build --bundle false",
"lint": "aegir lint",
"dep-check": "aegir dep-check",
"start": "npm run build && node dist/src/index.js"
},
"devDependencies": {
"@chainsafe/libp2p-noise": "^11.0.0",
"@chainsafe/libp2p-yamux": "^3.0.5",
"@helia/unixfs": "^1.2.1",
"@ipld/dag-pb": "^4.0.2",
"@libp2p/websockets": "^5.0.3",
"aegir": "^38.1.5",
"blockstore-fs": "^1.0.1",
"datastore-level": "^10.0.1",
"execa": "^7.0.0",
"go-ipfs": "^0.19.0",
"helia": "^1.0.0",
"ipfs-core": "^0.18.0",
"ipfs-unixfs-importer": "^15.1.1",
"ipfsd-ctl": "^13.0.0",
"it-all": "^3.0.1",
"it-buffer-stream": "^3.0.2",
"it-drain": "^3.0.1",
"it-map": "^3.0.2",
"kubo-rpc-client": "^3.0.1",
"libp2p": "^0.43.0",
"multiformats": "^11.0.1",
"tinybench": "^2.4.0"
},
"dependencies": {
"pretty-bytes": "^6.1.0"
}
}
30 changes: 30 additions & 0 deletions benchmarks/transfer/src/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Transfer Benchmark

Benchmarks Helia transfer performance against js-ipfs and Kubo

To run:

1. Add `benchmarks/*` to the `workspaces` entry in the root `package.json` of this repo
2. Run
```console
$ npm run reset
$ npm i
$ npm run build
$ cd benchmarks/transfer
$ npm start

> benchmarks-gc@1.0.0 start
> npm run build && node dist/src/index.js


> benchmarks-transfer@1.0.0 build
> aegir build --bundle false

[14:51:28] tsc [started]
[14:51:33] tsc [completed]
generating Ed25519 keypair...
┌─────────┬────────────────┬─────────┬───────────┬──────┐
│ (index) │ Implementation │ ops/s │ ms/op │ runs │
├─────────┼────────────────┼─────────┼───────────┼──────┤
//... results here
```
63 changes: 63 additions & 0 deletions benchmarks/transfer/src/helia.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { createHelia } from 'helia'
import { createLibp2p } from 'libp2p'
import { tcp } from '@libp2p/tcp'
import { noise } from '@chainsafe/libp2p-noise'
// import { yamux } from '@chainsafe/libp2p-yamux'
import { mplex } from '@libp2p/mplex'
import type { TransferBenchmark } from './index.js'
import os from 'node:os'
import path from 'node:path'
import { LevelDatastore } from 'datastore-level'
import { FsBlockstore } from 'blockstore-fs'
import drain from 'it-drain'
import { unixfs } from '@helia/unixfs'
// import { fixedSize } from 'ipfs-unixfs-importer/chunker'
// import { balanced } from 'ipfs-unixfs-importer/layout'

export async function createHeliaBenchmark (): Promise<TransferBenchmark> {
const repoPath = path.join(os.tmpdir(), `helia-${Math.random()}`)

const helia = await createHelia({
blockstore: new FsBlockstore(`${repoPath}/blocks`),
datastore: new LevelDatastore(`${repoPath}/data`),
libp2p: await createLibp2p({
addresses: {
listen: [
'/ip4/127.0.0.1/tcp/0'
]
},
transports: [
tcp()
],
connectionEncryption: [
noise()
],
streamMuxers: [
mplex()
// yamux()
]
})
})

return {
async teardown () {
await helia.stop()
},
async addr () {
return helia.libp2p.getMultiaddrs()[0]
},
async dial (ma) {
await helia.libp2p.dial(ma)
},
async add (content) {
const fs = unixfs(helia)

return await fs.addByteStream(content)
},
async get (cid) {
const fs = unixfs(helia)

await drain(fs.cat(cid))
}
}
}
119 changes: 119 additions & 0 deletions benchmarks/transfer/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/* eslint-disable no-console */

import type { CID } from 'multiformats/cid'
import { createHeliaBenchmark } from './helia.js'
import { createIpfsBenchmark } from './ipfs.js'
import { createKuboBenchmark } from './kubo.js'
import bufferStream from 'it-buffer-stream'
import type { Multiaddr } from '@multiformats/multiaddr'
import prettyBytes from 'pretty-bytes'

const ONE_MEG = 1024 * 1024

export interface TransferBenchmark {
teardown: () => Promise<void>
addr: () => Promise<Multiaddr>
dial: (multiaddr: Multiaddr) => Promise<void>
add: (content: AsyncIterable<Uint8Array>, options: ImportOptions) => Promise<CID>
get: (cid: CID) => Promise<void>
}

interface ImportOptions {
cidVersion?: 0 | 1
rawLeaves?: boolean
chunkSize?: number
maxChildrenPerNode?: number
}

interface File {
name: string
options: ImportOptions
size: number
}

const opts: Record<string, ImportOptions> = {
defaults: {},
'kubo defaults': {
chunkSize: 256 * 1024,
rawLeaves: false,
cidVersion: 0,
maxChildrenPerNode: 174
},
'256KiB block size': {
chunkSize: 256 * 1024
},
'1MB block size': {
chunkSize: 1024 * 1024
},
'3MB block size': {
chunkSize: (1024 * 1024) * 10
},
'4MB block size': {
chunkSize: (1024 * 1024) * 10
},
'10MB block size': {
chunkSize: (1024 * 1024) * 10
}
}

const tests: Record<string, File[]> = {}

for (const [name, options] of Object.entries(opts)) {
tests[name] = []

for (let i = 100; i < 1100; i += 100) {
tests[name].push({
name: `${i}`,
options,
size: ONE_MEG * i
})

console.info(prettyBytes(ONE_MEG * i))
}
}

const impls: Array<{ name: string, create: () => Promise<TransferBenchmark> }> = [{
name: 'helia',
create: async () => await createHeliaBenchmark()
}, {
name: 'ipfs',
create: async () => await createIpfsBenchmark()
}, {
name: 'kubo',
create: async () => await createKuboBenchmark()
}]

async function main (): Promise<void> {
for (const [name, files] of Object.entries(tests)) {
for (const implA of impls) {
for (const implB of impls) {
console.info(`${implA.name} -> ${implB.name} ${name}`)

for (const file of files) {
const subjectA = await implA.create()
const subjectB = await implB.create()

const addr = await subjectB.addr()
await subjectA.dial(addr)

const cid = await subjectA.add(bufferStream(file.size), file.options)

const start = Date.now()

// b pulls from a
await subjectB.get(cid)

console.info(`${Date.now() - start}`)

await subjectA.teardown()
await subjectB.teardown()
}
}
}
}
}

main().catch(err => {
console.error(err) // eslint-disable-line no-console
process.exit(1)
})
Loading

0 comments on commit 06c53b9

Please sign in to comment.