| // +build go1.1 |
| |
| package pq |
| |
| import ( |
| "bufio" |
| "bytes" |
| "database/sql" |
| "database/sql/driver" |
| "io" |
| "math/rand" |
| "net" |
| "runtime" |
| "strconv" |
| "strings" |
| "sync" |
| "testing" |
| "time" |
| |
| "github.com/lib/pq/oid" |
| ) |
| |
| var ( |
| selectStringQuery = "SELECT '" + strings.Repeat("0123456789", 10) + "'" |
| selectSeriesQuery = "SELECT generate_series(1, 100)" |
| ) |
| |
| func BenchmarkSelectString(b *testing.B) { |
| var result string |
| benchQuery(b, selectStringQuery, &result) |
| } |
| |
| func BenchmarkSelectSeries(b *testing.B) { |
| var result int |
| benchQuery(b, selectSeriesQuery, &result) |
| } |
| |
| func benchQuery(b *testing.B, query string, result interface{}) { |
| b.StopTimer() |
| db := openTestConn(b) |
| defer db.Close() |
| b.StartTimer() |
| |
| for i := 0; i < b.N; i++ { |
| benchQueryLoop(b, db, query, result) |
| } |
| } |
| |
| func benchQueryLoop(b *testing.B, db *sql.DB, query string, result interface{}) { |
| rows, err := db.Query(query) |
| if err != nil { |
| b.Fatal(err) |
| } |
| defer rows.Close() |
| for rows.Next() { |
| err = rows.Scan(result) |
| if err != nil { |
| b.Fatal("failed to scan", err) |
| } |
| } |
| } |
| |
| // reading from circularConn yields content[:prefixLen] once, followed by |
| // content[prefixLen:] over and over again. It never returns EOF. |
| type circularConn struct { |
| content string |
| prefixLen int |
| pos int |
| net.Conn // for all other net.Conn methods that will never be called |
| } |
| |
| func (r *circularConn) Read(b []byte) (n int, err error) { |
| n = copy(b, r.content[r.pos:]) |
| r.pos += n |
| if r.pos >= len(r.content) { |
| r.pos = r.prefixLen |
| } |
| return |
| } |
| |
| func (r *circularConn) Write(b []byte) (n int, err error) { return len(b), nil } |
| |
| func (r *circularConn) Close() error { return nil } |
| |
| func fakeConn(content string, prefixLen int) *conn { |
| c := &circularConn{content: content, prefixLen: prefixLen} |
| return &conn{buf: bufio.NewReader(c), c: c} |
| } |
| |
| // This benchmark is meant to be the same as BenchmarkSelectString, but takes |
| // out some of the factors this package can't control. The numbers are less noisy, |
| // but also the costs of network communication aren't accurately represented. |
| func BenchmarkMockSelectString(b *testing.B) { |
| b.StopTimer() |
| // taken from a recorded run of BenchmarkSelectString |
| // See: http://www.postgresql.org/docs/current/static/protocol-message-formats.html |
| const response = "1\x00\x00\x00\x04" + |
| "t\x00\x00\x00\x06\x00\x00" + |
| "T\x00\x00\x00!\x00\x01?column?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xc1\xff\xfe\xff\xff\xff\xff\x00\x00" + |
| "Z\x00\x00\x00\x05I" + |
| "2\x00\x00\x00\x04" + |
| "D\x00\x00\x00n\x00\x01\x00\x00\x00d0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" + |
| "C\x00\x00\x00\rSELECT 1\x00" + |
| "Z\x00\x00\x00\x05I" + |
| "3\x00\x00\x00\x04" + |
| "Z\x00\x00\x00\x05I" |
| c := fakeConn(response, 0) |
| b.StartTimer() |
| |
| for i := 0; i < b.N; i++ { |
| benchMockQuery(b, c, selectStringQuery) |
| } |
| } |
| |
| var seriesRowData = func() string { |
| var buf bytes.Buffer |
| for i := 1; i <= 100; i++ { |
| digits := byte(2) |
| if i >= 100 { |
| digits = 3 |
| } else if i < 10 { |
| digits = 1 |
| } |
| buf.WriteString("D\x00\x00\x00") |
| buf.WriteByte(10 + digits) |
| buf.WriteString("\x00\x01\x00\x00\x00") |
| buf.WriteByte(digits) |
| buf.WriteString(strconv.Itoa(i)) |
| } |
| return buf.String() |
| }() |
| |
| func BenchmarkMockSelectSeries(b *testing.B) { |
| b.StopTimer() |
| var response = "1\x00\x00\x00\x04" + |
| "t\x00\x00\x00\x06\x00\x00" + |
| "T\x00\x00\x00!\x00\x01?column?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xc1\xff\xfe\xff\xff\xff\xff\x00\x00" + |
| "Z\x00\x00\x00\x05I" + |
| "2\x00\x00\x00\x04" + |
| seriesRowData + |
| "C\x00\x00\x00\x0fSELECT 100\x00" + |
| "Z\x00\x00\x00\x05I" + |
| "3\x00\x00\x00\x04" + |
| "Z\x00\x00\x00\x05I" |
| c := fakeConn(response, 0) |
| b.StartTimer() |
| |
| for i := 0; i < b.N; i++ { |
| benchMockQuery(b, c, selectSeriesQuery) |
| } |
| } |
| |
| func benchMockQuery(b *testing.B, c *conn, query string) { |
| stmt, err := c.Prepare(query) |
| if err != nil { |
| b.Fatal(err) |
| } |
| defer stmt.Close() |
| rows, err := stmt.Query(nil) |
| if err != nil { |
| b.Fatal(err) |
| } |
| defer rows.Close() |
| var dest [1]driver.Value |
| for { |
| if err := rows.Next(dest[:]); err != nil { |
| if err == io.EOF { |
| break |
| } |
| b.Fatal(err) |
| } |
| } |
| } |
| |
| func BenchmarkPreparedSelectString(b *testing.B) { |
| var result string |
| benchPreparedQuery(b, selectStringQuery, &result) |
| } |
| |
| func BenchmarkPreparedSelectSeries(b *testing.B) { |
| var result int |
| benchPreparedQuery(b, selectSeriesQuery, &result) |
| } |
| |
| func benchPreparedQuery(b *testing.B, query string, result interface{}) { |
| b.StopTimer() |
| db := openTestConn(b) |
| defer db.Close() |
| stmt, err := db.Prepare(query) |
| if err != nil { |
| b.Fatal(err) |
| } |
| defer stmt.Close() |
| b.StartTimer() |
| |
| for i := 0; i < b.N; i++ { |
| benchPreparedQueryLoop(b, db, stmt, result) |
| } |
| } |
| |
| func benchPreparedQueryLoop(b *testing.B, db *sql.DB, stmt *sql.Stmt, result interface{}) { |
| rows, err := stmt.Query() |
| if err != nil { |
| b.Fatal(err) |
| } |
| if !rows.Next() { |
| rows.Close() |
| b.Fatal("no rows") |
| } |
| defer rows.Close() |
| for rows.Next() { |
| err = rows.Scan(&result) |
| if err != nil { |
| b.Fatal("failed to scan") |
| } |
| } |
| } |
| |
| // See the comment for BenchmarkMockSelectString. |
| func BenchmarkMockPreparedSelectString(b *testing.B) { |
| b.StopTimer() |
| const parseResponse = "1\x00\x00\x00\x04" + |
| "t\x00\x00\x00\x06\x00\x00" + |
| "T\x00\x00\x00!\x00\x01?column?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xc1\xff\xfe\xff\xff\xff\xff\x00\x00" + |
| "Z\x00\x00\x00\x05I" |
| const responses = parseResponse + |
| "2\x00\x00\x00\x04" + |
| "D\x00\x00\x00n\x00\x01\x00\x00\x00d0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" + |
| "C\x00\x00\x00\rSELECT 1\x00" + |
| "Z\x00\x00\x00\x05I" |
| c := fakeConn(responses, len(parseResponse)) |
| |
| stmt, err := c.Prepare(selectStringQuery) |
| if err != nil { |
| b.Fatal(err) |
| } |
| b.StartTimer() |
| |
| for i := 0; i < b.N; i++ { |
| benchPreparedMockQuery(b, c, stmt) |
| } |
| } |
| |
| func BenchmarkMockPreparedSelectSeries(b *testing.B) { |
| b.StopTimer() |
| const parseResponse = "1\x00\x00\x00\x04" + |
| "t\x00\x00\x00\x06\x00\x00" + |
| "T\x00\x00\x00!\x00\x01?column?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xc1\xff\xfe\xff\xff\xff\xff\x00\x00" + |
| "Z\x00\x00\x00\x05I" |
| var responses = parseResponse + |
| "2\x00\x00\x00\x04" + |
| seriesRowData + |
| "C\x00\x00\x00\x0fSELECT 100\x00" + |
| "Z\x00\x00\x00\x05I" |
| c := fakeConn(responses, len(parseResponse)) |
| |
| stmt, err := c.Prepare(selectSeriesQuery) |
| if err != nil { |
| b.Fatal(err) |
| } |
| b.StartTimer() |
| |
| for i := 0; i < b.N; i++ { |
| benchPreparedMockQuery(b, c, stmt) |
| } |
| } |
| |
| func benchPreparedMockQuery(b *testing.B, c *conn, stmt driver.Stmt) { |
| rows, err := stmt.Query(nil) |
| if err != nil { |
| b.Fatal(err) |
| } |
| defer rows.Close() |
| var dest [1]driver.Value |
| for { |
| if err := rows.Next(dest[:]); err != nil { |
| if err == io.EOF { |
| break |
| } |
| b.Fatal(err) |
| } |
| } |
| } |
| |
| func BenchmarkEncodeInt64(b *testing.B) { |
| for i := 0; i < b.N; i++ { |
| encode(¶meterStatus{}, int64(1234), oid.T_int8) |
| } |
| } |
| |
| func BenchmarkEncodeFloat64(b *testing.B) { |
| for i := 0; i < b.N; i++ { |
| encode(¶meterStatus{}, 3.14159, oid.T_float8) |
| } |
| } |
| |
| var testByteString = []byte("abcdefghijklmnopqrstuvwxyz") |
| |
| func BenchmarkEncodeByteaHex(b *testing.B) { |
| for i := 0; i < b.N; i++ { |
| encode(¶meterStatus{serverVersion: 90000}, testByteString, oid.T_bytea) |
| } |
| } |
| func BenchmarkEncodeByteaEscape(b *testing.B) { |
| for i := 0; i < b.N; i++ { |
| encode(¶meterStatus{serverVersion: 84000}, testByteString, oid.T_bytea) |
| } |
| } |
| |
| func BenchmarkEncodeBool(b *testing.B) { |
| for i := 0; i < b.N; i++ { |
| encode(¶meterStatus{}, true, oid.T_bool) |
| } |
| } |
| |
| var testTimestamptz = time.Date(2001, time.January, 1, 0, 0, 0, 0, time.Local) |
| |
| func BenchmarkEncodeTimestamptz(b *testing.B) { |
| for i := 0; i < b.N; i++ { |
| encode(¶meterStatus{}, testTimestamptz, oid.T_timestamptz) |
| } |
| } |
| |
| var testIntBytes = []byte("1234") |
| |
| func BenchmarkDecodeInt64(b *testing.B) { |
| for i := 0; i < b.N; i++ { |
| decode(¶meterStatus{}, testIntBytes, oid.T_int8, formatText) |
| } |
| } |
| |
| var testFloatBytes = []byte("3.14159") |
| |
| func BenchmarkDecodeFloat64(b *testing.B) { |
| for i := 0; i < b.N; i++ { |
| decode(¶meterStatus{}, testFloatBytes, oid.T_float8, formatText) |
| } |
| } |
| |
| var testBoolBytes = []byte{'t'} |
| |
| func BenchmarkDecodeBool(b *testing.B) { |
| for i := 0; i < b.N; i++ { |
| decode(¶meterStatus{}, testBoolBytes, oid.T_bool, formatText) |
| } |
| } |
| |
| func TestDecodeBool(t *testing.T) { |
| db := openTestConn(t) |
| rows, err := db.Query("select true") |
| if err != nil { |
| t.Fatal(err) |
| } |
| rows.Close() |
| } |
| |
| var testTimestamptzBytes = []byte("2013-09-17 22:15:32.360754-07") |
| |
| func BenchmarkDecodeTimestamptz(b *testing.B) { |
| for i := 0; i < b.N; i++ { |
| decode(¶meterStatus{}, testTimestamptzBytes, oid.T_timestamptz, formatText) |
| } |
| } |
| |
| func BenchmarkDecodeTimestamptzMultiThread(b *testing.B) { |
| oldProcs := runtime.GOMAXPROCS(0) |
| defer runtime.GOMAXPROCS(oldProcs) |
| runtime.GOMAXPROCS(runtime.NumCPU()) |
| globalLocationCache = newLocationCache() |
| |
| f := func(wg *sync.WaitGroup, loops int) { |
| defer wg.Done() |
| for i := 0; i < loops; i++ { |
| decode(¶meterStatus{}, testTimestamptzBytes, oid.T_timestamptz, formatText) |
| } |
| } |
| |
| wg := &sync.WaitGroup{} |
| b.ResetTimer() |
| for j := 0; j < 10; j++ { |
| wg.Add(1) |
| go f(wg, b.N/10) |
| } |
| wg.Wait() |
| } |
| |
| func BenchmarkLocationCache(b *testing.B) { |
| globalLocationCache = newLocationCache() |
| for i := 0; i < b.N; i++ { |
| globalLocationCache.getLocation(rand.Intn(10000)) |
| } |
| } |
| |
| func BenchmarkLocationCacheMultiThread(b *testing.B) { |
| oldProcs := runtime.GOMAXPROCS(0) |
| defer runtime.GOMAXPROCS(oldProcs) |
| runtime.GOMAXPROCS(runtime.NumCPU()) |
| globalLocationCache = newLocationCache() |
| |
| f := func(wg *sync.WaitGroup, loops int) { |
| defer wg.Done() |
| for i := 0; i < loops; i++ { |
| globalLocationCache.getLocation(rand.Intn(10000)) |
| } |
| } |
| |
| wg := &sync.WaitGroup{} |
| b.ResetTimer() |
| for j := 0; j < 10; j++ { |
| wg.Add(1) |
| go f(wg, b.N/10) |
| } |
| wg.Wait() |
| } |
| |
| // Stress test the performance of parsing results from the wire. |
| func BenchmarkResultParsing(b *testing.B) { |
| b.StopTimer() |
| |
| db := openTestConn(b) |
| defer db.Close() |
| _, err := db.Exec("BEGIN") |
| if err != nil { |
| b.Fatal(err) |
| } |
| |
| b.StartTimer() |
| for i := 0; i < b.N; i++ { |
| res, err := db.Query("SELECT generate_series(1, 50000)") |
| if err != nil { |
| b.Fatal(err) |
| } |
| res.Close() |
| } |
| } |