Skip to content

Commit f340371

Browse files
committed
chore: add transfer benchmark
Adds a benchmark suite for doing various size data transfers between helia, kubo and js-ipfs. Closes #88
1 parent 97fb1a7 commit f340371

File tree

8 files changed

+368
-7
lines changed

8 files changed

+368
-7
lines changed

benchmarks/gc/src/kubo.ts

+1-7
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,7 @@ export async function createKuboBenchmark (): Promise<GcBenchmark> {
5151
paths: cid
5252
}))
5353

54-
const isPinned = result[0].type.includes('direct') || result[0].type.includes('indirect') || result[0].type.includes('recursive')
55-
56-
if (!isPinned) {
57-
console.info(result)
58-
}
59-
60-
return isPinned
54+
return result[0].type.includes('direct') || result[0].type.includes('indirect') || result[0].type.includes('recursive')
6155
},
6256
hasBlock: async (cid) => {
6357
try {

benchmarks/transfer/package.json

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"name": "benchmarks-transfer",
3+
"version": "1.0.0",
4+
"main": "index.js",
5+
"private": true,
6+
"type": "module",
7+
"scripts": {
8+
"clean": "aegir clean",
9+
"build": "aegir build --bundle false",
10+
"lint": "aegir lint",
11+
"dep-check": "aegir dep-check",
12+
"start": "npm run build && node dist/src/index.js"
13+
},
14+
"devDependencies": {
15+
"@chainsafe/libp2p-noise": "^11.0.0",
16+
"@chainsafe/libp2p-yamux": "^3.0.5",
17+
"@helia/unixfs": "^1.2.1",
18+
"@ipld/dag-pb": "^4.0.2",
19+
"@libp2p/websockets": "^5.0.3",
20+
"aegir": "^38.1.5",
21+
"blockstore-fs": "^1.0.1",
22+
"datastore-level": "^10.0.1",
23+
"execa": "^7.0.0",
24+
"go-ipfs": "^0.19.0",
25+
"helia": "^1.0.0",
26+
"ipfs-core": "^0.18.0",
27+
"ipfs-unixfs-importer": "^15.1.1",
28+
"ipfsd-ctl": "^13.0.0",
29+
"it-all": "^3.0.1",
30+
"it-buffer-stream": "^3.0.2",
31+
"it-drain": "^3.0.1",
32+
"it-map": "^3.0.2",
33+
"kubo-rpc-client": "^3.0.1",
34+
"libp2p": "^0.43.0",
35+
"multiformats": "^11.0.1",
36+
"tinybench": "^2.4.0"
37+
},
38+
"dependencies": {
39+
"pretty-bytes": "^6.1.0"
40+
}
41+
}

benchmarks/transfer/src/README.md

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Transfer Benchmark
2+
3+
Benchmarks Helia transfer performance against js-ipfs and Kubo
4+
5+
To run:
6+
7+
1. Add `benchmarks/*` to the `workspaces` entry in the root `package.json` of this repo
8+
2. Run
9+
```console
10+
$ npm run reset
11+
$ npm i
12+
$ npm run build
13+
$ cd benchmarks/transfer
14+
$ npm start
15+
16+
17+
> npm run build && node dist/src/index.js
18+
19+
20+
21+
> aegir build --bundle false
22+
23+
[14:51:28] tsc [started]
24+
[14:51:33] tsc [completed]
25+
generating Ed25519 keypair...
26+
┌─────────┬────────────────┬─────────┬───────────┬──────┐
27+
│ (index) │ Implementation │ ops/s │ ms/op │ runs │
28+
├─────────┼────────────────┼─────────┼───────────┼──────┤
29+
//... results here
30+
```

benchmarks/transfer/src/helia.ts

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { createHelia } from 'helia'
2+
import { createLibp2p } from 'libp2p'
3+
import { tcp } from '@libp2p/tcp'
4+
import { noise } from '@chainsafe/libp2p-noise'
5+
// import { yamux } from '@chainsafe/libp2p-yamux'
6+
import { mplex } from '@libp2p/mplex'
7+
import type { TransferBenchmark } from './index.js'
8+
import os from 'node:os'
9+
import path from 'node:path'
10+
import { LevelDatastore } from 'datastore-level'
11+
import { FsBlockstore } from 'blockstore-fs'
12+
import drain from 'it-drain'
13+
import { unixfs } from '@helia/unixfs'
14+
// import { fixedSize } from 'ipfs-unixfs-importer/chunker'
15+
// import { balanced } from 'ipfs-unixfs-importer/layout'
16+
17+
export async function createHeliaBenchmark (): Promise<TransferBenchmark> {
18+
const repoPath = path.join(os.tmpdir(), `helia-${Math.random()}`)
19+
20+
const helia = await createHelia({
21+
blockstore: new FsBlockstore(`${repoPath}/blocks`),
22+
datastore: new LevelDatastore(`${repoPath}/data`),
23+
libp2p: await createLibp2p({
24+
addresses: {
25+
listen: [
26+
'/ip4/127.0.0.1/tcp/0'
27+
]
28+
},
29+
transports: [
30+
tcp()
31+
],
32+
connectionEncryption: [
33+
noise()
34+
],
35+
streamMuxers: [
36+
mplex()
37+
// yamux()
38+
]
39+
})
40+
})
41+
42+
return {
43+
async teardown () {
44+
await helia.stop()
45+
},
46+
async addr () {
47+
return helia.libp2p.getMultiaddrs()[0]
48+
},
49+
async dial (ma) {
50+
await helia.libp2p.dial(ma)
51+
},
52+
async add (content) {
53+
const fs = unixfs(helia)
54+
55+
return await fs.addByteStream(content)
56+
},
57+
async get (cid) {
58+
const fs = unixfs(helia)
59+
60+
await drain(fs.cat(cid))
61+
}
62+
}
63+
}

benchmarks/transfer/src/index.ts

+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/* eslint-disable no-console */
2+
3+
import type { CID } from 'multiformats/cid'
4+
import { createHeliaBenchmark } from './helia.js'
5+
import { createIpfsBenchmark } from './ipfs.js'
6+
import { createKuboBenchmark } from './kubo.js'
7+
import bufferStream from 'it-buffer-stream'
8+
import type { Multiaddr } from '@multiformats/multiaddr'
9+
import prettyBytes from 'pretty-bytes'
10+
11+
const ONE_MEG = 1024 * 1024
12+
13+
export interface TransferBenchmark {
14+
teardown: () => Promise<void>
15+
addr: () => Promise<Multiaddr>
16+
dial: (multiaddr: Multiaddr) => Promise<void>
17+
add: (content: AsyncIterable<Uint8Array>, options: ImportOptions) => Promise<CID>
18+
get: (cid: CID) => Promise<void>
19+
}
20+
21+
interface ImportOptions {
22+
cidVersion?: 0 | 1
23+
rawLeaves?: boolean
24+
chunkSize?: number
25+
maxChildrenPerNode?: number
26+
}
27+
28+
interface File {
29+
name: string
30+
options: ImportOptions
31+
size: number
32+
}
33+
34+
const opts: Record<string, ImportOptions> = {
35+
defaults: {},
36+
'kubo defaults': {
37+
chunkSize: 256 * 1024,
38+
rawLeaves: false,
39+
cidVersion: 0,
40+
maxChildrenPerNode: 174
41+
},
42+
'256KiB block size': {
43+
chunkSize: 256 * 1024
44+
},
45+
'1MB block size': {
46+
chunkSize: 1024 * 1024
47+
},
48+
'3MB block size': {
49+
chunkSize: (1024 * 1024) * 10
50+
},
51+
'4MB block size': {
52+
chunkSize: (1024 * 1024) * 10
53+
},
54+
'10MB block size': {
55+
chunkSize: (1024 * 1024) * 10
56+
}
57+
}
58+
59+
const tests: Record<string, File[]> = {}
60+
61+
for (const [name, options] of Object.entries(opts)) {
62+
tests[name] = []
63+
64+
for (let i = 100; i < 1100; i += 100) {
65+
tests[name].push({
66+
name: `${i}`,
67+
options,
68+
size: ONE_MEG * i
69+
})
70+
71+
console.info(prettyBytes(ONE_MEG * i))
72+
}
73+
}
74+
75+
const impls: Array<{ name: string, create: () => Promise<TransferBenchmark> }> = [{
76+
name: 'helia',
77+
create: async () => await createHeliaBenchmark()
78+
}, {
79+
name: 'ipfs',
80+
create: async () => await createIpfsBenchmark()
81+
}, {
82+
name: 'kubo',
83+
create: async () => await createKuboBenchmark()
84+
}]
85+
86+
async function main (): Promise<void> {
87+
for (const [name, files] of Object.entries(tests)) {
88+
for (const implA of impls) {
89+
for (const implB of impls) {
90+
console.info(`${implA.name} -> ${implB.name} ${name}`)
91+
92+
for (const file of files) {
93+
const subjectA = await implA.create()
94+
const subjectB = await implB.create()
95+
96+
const addr = await subjectB.addr()
97+
await subjectA.dial(addr)
98+
99+
const cid = await subjectA.add(bufferStream(file.size), file.options)
100+
101+
const start = Date.now()
102+
103+
// b pulls from a
104+
await subjectB.get(cid)
105+
106+
console.info(`${Date.now() - start}`)
107+
108+
await subjectA.teardown()
109+
await subjectB.teardown()
110+
}
111+
}
112+
}
113+
}
114+
}
115+
116+
main().catch(err => {
117+
console.error(err) // eslint-disable-line no-console
118+
process.exit(1)
119+
})

benchmarks/transfer/src/ipfs.ts

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { create } from 'ipfs-core'
2+
import drain from 'it-drain'
3+
import type { TransferBenchmark } from './index.js'
4+
import os from 'node:os'
5+
import path from 'node:path'
6+
7+
export async function createIpfsBenchmark (): Promise<TransferBenchmark> {
8+
const repoPath = path.join(os.tmpdir(), `ipfs-${Math.random()}`)
9+
10+
const ipfs = await create({
11+
config: {
12+
Addresses: {
13+
Swarm: [
14+
'/ip4/127.0.0.1/tcp/0'
15+
]
16+
}
17+
},
18+
repo: repoPath,
19+
init: {
20+
emptyRepo: true
21+
},
22+
silent: true
23+
})
24+
25+
return {
26+
async teardown () {
27+
await ipfs.stop()
28+
},
29+
async addr () {
30+
const id = await ipfs.id()
31+
32+
return id.addresses[0]
33+
},
34+
async dial (ma) {
35+
await ipfs.swarm.connect(ma)
36+
},
37+
async add (content, options: any) {
38+
const { cid } = await ipfs.add(content)
39+
40+
return cid
41+
},
42+
async get (cid) {
43+
await drain(ipfs.cat(cid))
44+
}
45+
}
46+
}

benchmarks/transfer/src/kubo.ts

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import drain from 'it-drain'
2+
import type { TransferBenchmark } from './index.js'
3+
// @ts-expect-error no types
4+
import * as goIpfs from 'go-ipfs'
5+
import * as goRpcClient from 'kubo-rpc-client'
6+
import { createController } from 'ipfsd-ctl'
7+
8+
export async function createKuboBenchmark (): Promise<TransferBenchmark> {
9+
const controller = await createController({
10+
type: 'go',
11+
test: true,
12+
ipfsBin: goIpfs.path(),
13+
ipfsHttpModule: goRpcClient,
14+
ipfsOptions: {
15+
init: {
16+
emptyRepo: true
17+
},
18+
config: {
19+
Addresses: {
20+
Swarm: [
21+
'/ip4/127.0.0.1/tcp/0'
22+
]
23+
}
24+
},
25+
silent: true
26+
}
27+
})
28+
29+
return {
30+
async teardown () {
31+
await controller.stop()
32+
},
33+
async addr () {
34+
const id = await controller.api.id()
35+
36+
return id.addresses[0]
37+
},
38+
async dial (ma) {
39+
await controller.api.swarm.connect(ma)
40+
},
41+
async add (content, options: any) {
42+
const { cid } = await controller.api.add(content)
43+
44+
return cid
45+
},
46+
async get (cid) {
47+
await drain(controller.api.cat(cid))
48+
}
49+
}
50+
}

benchmarks/transfer/tsconfig.json

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"extends": "aegir/src/config/tsconfig.aegir.json",
3+
"compilerOptions": {
4+
"outDir": "dist",
5+
"target": "ES2022",
6+
"module": "ES2022",
7+
"lib": ["ES2022", "DOM", "DOM.Iterable"]
8+
},
9+
"include": [
10+
"src",
11+
"test"
12+
],
13+
"references": [
14+
{
15+
"path": "../../packages/helia"
16+
}
17+
]
18+
}

0 commit comments

Comments
 (0)