Skip to content

Commit 5fb6387

Browse files
committed
UPDATE 2025.02.05
1 parent 0910f75 commit 5fb6387

File tree

15 files changed

+303
-122
lines changed

15 files changed

+303
-122
lines changed

.github/workflows/test.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
on: [push, pull_request]
2+
name: Test
3+
jobs:
4+
test:
5+
runs-on: ubuntu-latest
6+
steps:
7+
- name: Install Go
8+
uses: actions/setup-go@v4
9+
with:
10+
go-version: '^1.21.0'
11+
- name: Checkout code
12+
uses: actions/checkout@v3
13+
- uses: actions/cache@v3
14+
with:
15+
path: ~/go/pkg/mod
16+
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
17+
restore-keys: |
18+
${{ runner.os }}-go-
19+
- name: Test
20+
run: go test -coverprofile=coverage.txt -covermode=atomic -race ./...

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
.PHONY: build
22
build:
3-
go build -trimpath -o go-sysbench cmd/main.go
3+
go build -trimpath -o go-sysbench cmd/go-sysbench/main.go
44

55
.PHONY: lint
66
lint:
@@ -9,4 +9,4 @@ lint:
99

1010
.PHONY: test
1111
test:
12-
go test -v ./...
12+
go test -coverprofile=coverage.txt -covermode=atomic -v -race ./...

README.md

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,65 @@
1-
Sysbench golang version
1+
# go-sysbench
22

3-
# Usage
3+
[![Go Report Card](https://goreportcard.com/badge/github.com/samitani/go-sysbench)](https://goreportcard.com/report/github.com/samitani/go-sysbench)
4+
[![License](https://img.shields.io/badge/license-GPLv2-blue.svg)](LICENSE)
5+
6+
Yet another sysbench written in Golang
7+
8+
`sysbench` is a very simple, lightweight, and easy-to-customize benchmarking tool. I've been using `sysbench` for many years. In most cases, it met my requirements and worked well.
9+
But I needed to do more complex and fundamental customization, such as adding drivers. So I decided to make a `sysbench` clone in Golang.
10+
11+
`go-sysbench` runs the same SQL as sysbench and reports the results in the same format as `sysbench`.
12+
`go-sysbench` cannot do as many things as sysbench, but it offers more customizability for those who are familiar with Golang.
13+
14+
15+
I would like to thank Peter Zaitsev, Alexy Kopytov and contributors for inventing great tool [sysbench](https://github.com/akopytov/sysbench).
416

517
## How to install
618

7-
# Incompatibility with sysbench
19+
```
20+
go install github.com/samitani/go-sysbench/cmd/go-sysbench@main
21+
```
22+
23+
## Incompatibility with sysbench
824

9-
## Few options
25+
* `go-sysbench` supports only `oltp_read_only` and `oltp_read_write` database benchmarks. Linux benchmarks such as `fileio`, `cpu`, `memory`, etc. are not supported.
26+
* Some options are not implemented. `go-sysbench oltp_read_write run --help` shows available options.
27+
```
28+
$ go-sysbench oltp_read_write run --help
29+
2025/02/05 22:55:07 Usage:
30+
go-sysbench [OPTIONS] oltp_read_write run [run-OPTIONS]
1031
11-
## Number of reconnects is not supported
32+
Help Options:
33+
-h, --help Show this help message
1234
13-
## Lua script is not supported
35+
[run command options]
36+
--threads= number of threads to use (default: 1)
37+
--events= limit for total number of events (default: 0)
38+
--time= limit for total execution time in seconds (default: 10)
39+
--report-interval= periodically report intermediate statistics with a specified interval in seconds. 0 disables intermediate reports (default: 0)
40+
--histogram=[on|off] print latency histogram in report (default: off)
41+
--percentile= percentile to calculate in latency statistics (1-100) (default: 95)
42+
--tables= number of tables (default: 1)
43+
--table_size= number of rows per table (default: 10000)
44+
--db-driver=[mysql|pgsql|spanner] specifies database driver to use (default: mysql)
45+
--mysql-host= MySQL server host (default: localhost)
46+
--mysql-port= MySQL server port (default: 3306)
47+
--mysql-user= MySQL user (default: sbtest)
48+
--mysql-password= MySQL password [$MYSQL_PWD]
49+
--mysql-db= MySQL database name (default: sbtest)
50+
--mysql-ssl=[on|off] use SSL connections (default: off)
51+
--pgsql-host= PostgreSQL server host (default: localhost)
52+
--pgsql-port= PostgreSQL server port (default: 5432)
53+
--pgsql-user= PostgreSQL user (default: sbtest)
54+
--pgsql-password= PostgreSQL password [$PGPASSWORD]
55+
--pgsql-db= PostgreSQL database name (default: sbtest)
56+
--spanner-project= Spanner Google Cloud project name
57+
--spanner-instance= Spanner instance id
58+
--spanner-db= Spanner database name (default: sbtest)
59+
```
60+
* Number of reconnects is not reported.
61+
* Lua scripts is not supported. To customize the benchmark scenario, you have to edit the code directly.
1462

15-
# Additional feature
63+
## Additional feature
1664

17-
## Spanner driver
65+
### Google Cloud Spanner

benchmark/benchmark.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
11
package benchmark
22

3+
import "context"
4+
5+
const (
6+
DBDriverMySQL = "mysql"
7+
DBDriverPgSQL = "pgsql"
8+
DBDriverSpanner = "spanner"
9+
)
10+
311
type (
412
Benchmark interface {
5-
Init() error
13+
Init(context.Context) error
614
Done() error
7-
Prepare() error
8-
Event() (uint64, uint64, uint64, uint64, error)
15+
Prepare(context.Context) error
16+
Event(context.Context) (uint64, uint64, uint64, uint64, error)
917
}
1018
CommonOpts struct {
1119
Tables int `long:"tables" description:"number of tables" default:"1"`
@@ -16,14 +24,15 @@ type (
1624
BenchmarkOpts struct {
1725
CommonOpts
1826
MySQLOpts
27+
PgSQLOpts
1928
SpannerOpts
2029
}
2130
)
2231

2332
func BenchmarkFactory(opt *BenchmarkOpts) Benchmark {
24-
if opt.DBDriver == "spanner" {
33+
if opt.DBDriver == DBDriverSpanner {
2534
return newSpannerOLTP(opt)
2635
} else {
27-
return newMySQLOLTP(opt)
36+
return newOLTPBench(opt)
2837
}
2938
}
Lines changed: 63 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
package benchmark
22

33
import (
4+
"context"
45
"database/sql"
56
"fmt"
67
"math/rand"
78
"strings"
89

910
_ "github.com/go-sql-driver/mysql"
11+
_ "github.com/lib/pq"
12+
_ "github.com/samitani/go-sysbench/driver"
1013
)
1114

1215
const (
@@ -61,21 +64,46 @@ type (
6164
MySQLUser string `long:"mysql-user" description:"MySQL user" default:"sbtest"`
6265
MySQLPassword string `long:"mysql-password" env:"MYSQL_PWD" description:"MySQL password" default:""`
6366
MySQLDB string `long:"mysql-db" description:"MySQL database name" default:"sbtest"`
67+
MySQLSSL string `long:"mysql-ssl" choice:"on" choice:"off" description:"use SSL connections" default:"off"` //nolint:staticcheck
6468
}
6569

66-
MySQLOLTP struct {
70+
PgSQLOpts struct {
71+
PgSQLHost string `long:"pgsql-host" description:"PostgreSQL server host" default:"localhost"`
72+
PgSQLPort int `long:"pgsql-port" description:"PostgreSQL server port" default:"5432"`
73+
PgSQLUser string `long:"pgsql-user" description:"PostgreSQL user" default:"sbtest"`
74+
PgSQLPassword string `long:"pgsql-password" env:"PGPASSWORD" description:"PostgreSQL password" default:""`
75+
PgSQLDB string `long:"pgsql-db" description:"PostgreSQL database name" default:"sbtest"`
76+
}
77+
78+
OLTPBench struct {
6779
opts *BenchmarkOpts
6880

6981
db *sql.DB
7082
}
7183
)
7284

73-
func newMySQLOLTP(option *BenchmarkOpts) *MySQLOLTP {
74-
return &MySQLOLTP{opts: option}
85+
func newOLTPBench(option *BenchmarkOpts) *OLTPBench {
86+
return &OLTPBench{opts: option}
7587
}
7688

77-
func (o *MySQLOLTP) Init() error {
78-
db, err := sql.Open("mysql", o.dsn())
89+
func (o *OLTPBench) Init(ctx context.Context) error {
90+
var drvName string
91+
var dsn string
92+
93+
if o.opts.DBDriver == DBDriverMySQL {
94+
drvName = "mysql"
95+
dsn = o.dsnMySQL()
96+
} else if o.opts.DBDriver == DBDriverPgSQL {
97+
drvName = "postgres"
98+
dsn = o.dsnPgSQL()
99+
}
100+
101+
db, err := sql.Open(drvName, dsn)
102+
if err != nil {
103+
return err
104+
}
105+
106+
err = db.Ping()
79107
if err != nil {
80108
return err
81109
}
@@ -85,15 +113,15 @@ func (o *MySQLOLTP) Init() error {
85113
return nil
86114
}
87115

88-
func (o *MySQLOLTP) Prepare() error {
116+
func (o *OLTPBench) Prepare(ctx context.Context) error {
89117
err := o.createTable()
90118
if err != nil {
91119
return err
92120
}
93121
return nil
94122
}
95123

96-
func (o *MySQLOLTP) Event() (reads uint64, writes uint64, others uint64, errors uint64, e error) {
124+
func (o *OLTPBench) Event(ctx context.Context) (reads uint64, writes uint64, others uint64, errors uint64, e error) {
97125
var numReads, numWrites, numOthers uint64
98126
var tableNum = o.getRandTableNum()
99127
var numRowReturn = 0
@@ -106,7 +134,7 @@ func (o *MySQLOLTP) Event() (reads uint64, writes uint64, others uint64, errors
106134
numOthers += 1
107135

108136
for i := 0; i < numPointSelects; i++ {
109-
rows, err := tx.Query(fmt.Sprintf(stmtPointSelects, tableNum, sbRand(0, o.opts.TableSize)))
137+
rows, err := tx.QueryContext(ctx, fmt.Sprintf(stmtPointSelects, tableNum, sbRand(0, o.opts.TableSize)))
110138
if err != nil {
111139
tx.Rollback()
112140
return numReads, numWrites, numOthers, 1, err
@@ -119,7 +147,7 @@ func (o *MySQLOLTP) Event() (reads uint64, writes uint64, others uint64, errors
119147

120148
for i := 0; i < numSimpleRanges; i++ {
121149
begin := sbRand(0, o.opts.TableSize)
122-
rows, err := tx.Query(fmt.Sprintf(stmtSimpleRanges, tableNum, begin, begin+rangeSize-1))
150+
rows, err := tx.QueryContext(ctx, fmt.Sprintf(stmtSimpleRanges, tableNum, begin, begin+rangeSize-1))
123151
if err != nil {
124152
tx.Rollback()
125153
return numReads, numWrites, numOthers, 1, err
@@ -132,7 +160,7 @@ func (o *MySQLOLTP) Event() (reads uint64, writes uint64, others uint64, errors
132160

133161
for i := 0; i < numSumRanges; i++ {
134162
begin := sbRand(0, o.opts.TableSize)
135-
rows, err := tx.Query(fmt.Sprintf(stmtSumRanges, tableNum, begin, begin+rangeSize-1))
163+
rows, err := tx.QueryContext(ctx, fmt.Sprintf(stmtSumRanges, tableNum, begin, begin+rangeSize-1))
136164
if err != nil {
137165
tx.Rollback()
138166
return numReads, numWrites, numOthers, 1, err
@@ -145,7 +173,7 @@ func (o *MySQLOLTP) Event() (reads uint64, writes uint64, others uint64, errors
145173

146174
for i := 0; i < numOrderRanges; i++ {
147175
begin := sbRand(0, o.opts.TableSize)
148-
rows, err := tx.Query(fmt.Sprintf(stmtOrderRanges, tableNum, begin, begin+rangeSize-1))
176+
rows, err := tx.QueryContext(ctx, fmt.Sprintf(stmtOrderRanges, tableNum, begin, begin+rangeSize-1))
149177
if err != nil {
150178
tx.Rollback()
151179
return numReads, numWrites, numOthers, 1, err
@@ -158,7 +186,7 @@ func (o *MySQLOLTP) Event() (reads uint64, writes uint64, others uint64, errors
158186

159187
for i := 0; i < numDistinctRanges; i++ {
160188
begin := sbRand(0, o.opts.TableSize)
161-
rows, err := tx.Query(fmt.Sprintf(stmtDistinctRanges, tableNum, begin, begin+rangeSize-1))
189+
rows, err := tx.QueryContext(ctx, fmt.Sprintf(stmtDistinctRanges, tableNum, begin, begin+rangeSize-1))
162190
if err != nil {
163191
tx.Rollback()
164192
return numReads, numWrites, numOthers, 1, err
@@ -171,15 +199,15 @@ func (o *MySQLOLTP) Event() (reads uint64, writes uint64, others uint64, errors
171199

172200
if o.opts.ReadWrite {
173201
for i := 0; i < numIndexUpdates; i++ {
174-
_, err := tx.Exec(fmt.Sprintf(stmtIndexUpdates, tableNum, sbRand(0, o.opts.TableSize)))
202+
_, err := tx.ExecContext(ctx, fmt.Sprintf(stmtIndexUpdates, tableNum, sbRand(0, o.opts.TableSize)))
175203
if err != nil {
176204
tx.Rollback()
177205
return numReads, numWrites, numOthers, 1, err
178206
}
179207
numWrites += 1
180208
}
181209
for i := 0; i < numNonIndexUpdates; i++ {
182-
_, err := tx.Exec(fmt.Sprintf(stmtNonIndex_updates, tableNum, getCValue(), sbRand(0, o.opts.TableSize)))
210+
_, err := tx.ExecContext(ctx, fmt.Sprintf(stmtNonIndex_updates, tableNum, getCValue(), sbRand(0, o.opts.TableSize)))
183211
if err != nil {
184212
tx.Rollback()
185213
return numReads, numWrites, numOthers, 1, err
@@ -189,14 +217,14 @@ func (o *MySQLOLTP) Event() (reads uint64, writes uint64, others uint64, errors
189217
for i := 0; i < numDeleteInserts; i++ {
190218
id := sbRand(0, o.opts.TableSize)
191219

192-
_, err := tx.Exec(fmt.Sprintf(stmtDeletes, tableNum, id))
220+
_, err := tx.ExecContext(ctx, fmt.Sprintf(stmtDeletes, tableNum, id))
193221
if err != nil {
194222
tx.Rollback()
195223
return numReads, numWrites, numOthers, 1, err
196224
}
197225
numWrites += 1
198226

199-
_, err = tx.Exec(fmt.Sprintf(stmtInserts, tableNum, id, sbRand(0, o.opts.TableSize), getCValue(), getPadValue()))
227+
_, err = tx.ExecContext(ctx, fmt.Sprintf(stmtInserts, tableNum, id, sbRand(0, o.opts.TableSize), getCValue(), getPadValue()))
200228
if err != nil {
201229
tx.Rollback()
202230
return numReads, numWrites, numOthers, 1, err
@@ -215,16 +243,20 @@ func (o *MySQLOLTP) Event() (reads uint64, writes uint64, others uint64, errors
215243
return numReads, numWrites, numOthers, 0, nil
216244
}
217245

218-
func (o *MySQLOLTP) Done() error {
246+
func (o *OLTPBench) Done() error {
219247
o.db.Close()
220248
return nil
221249
}
222250

223-
func (o *MySQLOLTP) dsn() string {
251+
func (o *OLTPBench) dsnMySQL() string {
224252
return fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", o.opts.MySQLUser, o.opts.MySQLPassword, o.opts.MySQLHost, o.opts.MySQLPort, o.opts.MySQLDB)
225253
}
226254

227-
func (o *MySQLOLTP) getRandTableNum() int {
255+
func (o *OLTPBench) dsnPgSQL() string {
256+
return fmt.Sprintf("postgres://%s:%s@%s:%d/%s?sslmode=disable", o.opts.PgSQLUser, o.opts.PgSQLPassword, o.opts.PgSQLHost, o.opts.PgSQLPort, o.opts.PgSQLDB)
257+
}
258+
259+
func (o *OLTPBench) getRandTableNum() int {
228260
return sbRand(1, o.opts.Tables)
229261
}
230262

@@ -256,8 +288,15 @@ func sbRandStr(format string) string {
256288
return string(buf)
257289
}
258290

259-
func (o *MySQLOLTP) createTable() error {
260-
idDef := "INT NOT NULL AUTO_INCREMENT"
291+
func (o *OLTPBench) createTable() error {
292+
var idDef string
293+
294+
if o.opts.DBDriver == DBDriverPgSQL {
295+
idDef = "INT NOT NULL"
296+
} else {
297+
idDef = "INT NOT NULL AUTO_INCREMENT"
298+
}
299+
261300
idIndexDef := "PRIMARY KEY"
262301
engineDef := ""
263302
extraTableOptions := ""
@@ -278,10 +317,10 @@ func (o *MySQLOLTP) createTable() error {
278317

279318
fmt.Printf("Inserting %d records into 'sbtest%d'\n", o.opts.TableSize, tableNum)
280319
insertValues := []string{}
281-
for i := 0; i < o.opts.TableSize; i++ {
282-
insertValues = append(insertValues, fmt.Sprintf(`(%d, "%s", "%s") `, sbRand(0, o.opts.TableSize), getCValue(), getPadValue()))
320+
for i := 1; i <= o.opts.TableSize; i++ {
321+
insertValues = append(insertValues, fmt.Sprintf(`(%d, %d, '%s', '%s') `, i, sbRand(0, o.opts.TableSize), getCValue(), getPadValue()))
283322
}
284-
query = fmt.Sprintf("INSERT INTO sbtest%d (k, c, pad) VALUES", tableNum) + strings.Join(insertValues, ",")
323+
query = fmt.Sprintf("INSERT INTO sbtest%d (id, k, c, pad) VALUES", tableNum) + strings.Join(insertValues, ",")
285324
_, err = o.db.Exec(query)
286325
if err != nil {
287326
return err

0 commit comments

Comments
 (0)