1 package pq
2
3 import (
4 "bufio"
5 "bytes"
6 "context"
7 "database/sql"
8 "database/sql/driver"
9 "io"
10 "math/rand"
11 "net"
12 "runtime"
13 "strconv"
14 "strings"
15 "sync"
16 "testing"
17 "time"
18
19 "github.com/lib/pq/oid"
20 )
21
22 var (
23 selectStringQuery = "SELECT '" + strings.Repeat("0123456789", 10) + "'"
24 selectSeriesQuery = "SELECT generate_series(1, 100)"
25 )
26
27 func BenchmarkSelectString(b *testing.B) {
28 var result string
29 benchQuery(b, selectStringQuery, &result)
30 }
31
32 func BenchmarkSelectSeries(b *testing.B) {
33 var result int
34 benchQuery(b, selectSeriesQuery, &result)
35 }
36
37 func benchQuery(b *testing.B, query string, result interface{}) {
38 b.StopTimer()
39 db := openTestConn(b)
40 defer db.Close()
41 b.StartTimer()
42
43 for i := 0; i < b.N; i++ {
44 benchQueryLoop(b, db, query, result)
45 }
46 }
47
48 func benchQueryLoop(b *testing.B, db *sql.DB, query string, result interface{}) {
49 rows, err := db.Query(query)
50 if err != nil {
51 b.Fatal(err)
52 }
53 defer rows.Close()
54 for rows.Next() {
55 err = rows.Scan(result)
56 if err != nil {
57 b.Fatal("failed to scan", err)
58 }
59 }
60 }
61
62
63
64 type circularConn struct {
65 content string
66 prefixLen int
67 pos int
68 net.Conn
69 }
70
71 func (r *circularConn) Read(b []byte) (n int, err error) {
72 n = copy(b, r.content[r.pos:])
73 r.pos += n
74 if r.pos >= len(r.content) {
75 r.pos = r.prefixLen
76 }
77 return
78 }
79
80 func (r *circularConn) Write(b []byte) (n int, err error) { return len(b), nil }
81
82 func (r *circularConn) Close() error { return nil }
83
84 func fakeConn(content string, prefixLen int) *conn {
85 c := &circularConn{content: content, prefixLen: prefixLen}
86 return &conn{buf: bufio.NewReader(c), c: c}
87 }
88
89
90
91
92 func BenchmarkMockSelectString(b *testing.B) {
93 b.StopTimer()
94
95
96 const response = "1\x00\x00\x00\x04" +
97 "t\x00\x00\x00\x06\x00\x00" +
98 "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" +
99 "Z\x00\x00\x00\x05I" +
100 "2\x00\x00\x00\x04" +
101 "D\x00\x00\x00n\x00\x01\x00\x00\x00d0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" +
102 "C\x00\x00\x00\rSELECT 1\x00" +
103 "Z\x00\x00\x00\x05I" +
104 "3\x00\x00\x00\x04" +
105 "Z\x00\x00\x00\x05I"
106 c := fakeConn(response, 0)
107 b.StartTimer()
108
109 for i := 0; i < b.N; i++ {
110 benchMockQuery(b, c, selectStringQuery)
111 }
112 }
113
114 var seriesRowData = func() string {
115 var buf bytes.Buffer
116 for i := 1; i <= 100; i++ {
117 digits := byte(2)
118 if i >= 100 {
119 digits = 3
120 } else if i < 10 {
121 digits = 1
122 }
123 buf.WriteString("D\x00\x00\x00")
124 buf.WriteByte(10 + digits)
125 buf.WriteString("\x00\x01\x00\x00\x00")
126 buf.WriteByte(digits)
127 buf.WriteString(strconv.Itoa(i))
128 }
129 return buf.String()
130 }()
131
132 func BenchmarkMockSelectSeries(b *testing.B) {
133 b.StopTimer()
134 var response = "1\x00\x00\x00\x04" +
135 "t\x00\x00\x00\x06\x00\x00" +
136 "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" +
137 "Z\x00\x00\x00\x05I" +
138 "2\x00\x00\x00\x04" +
139 seriesRowData +
140 "C\x00\x00\x00\x0fSELECT 100\x00" +
141 "Z\x00\x00\x00\x05I" +
142 "3\x00\x00\x00\x04" +
143 "Z\x00\x00\x00\x05I"
144 c := fakeConn(response, 0)
145 b.StartTimer()
146
147 for i := 0; i < b.N; i++ {
148 benchMockQuery(b, c, selectSeriesQuery)
149 }
150 }
151
152 func benchMockQuery(b *testing.B, c *conn, query string) {
153 stmt, err := c.Prepare(query)
154 if err != nil {
155 b.Fatal(err)
156 }
157 defer stmt.Close()
158 rows, err := stmt.(driver.StmtQueryContext).QueryContext(context.Background(), nil)
159 if err != nil {
160 b.Fatal(err)
161 }
162 defer rows.Close()
163 var dest [1]driver.Value
164 for {
165 if err := rows.Next(dest[:]); err != nil {
166 if err == io.EOF {
167 break
168 }
169 b.Fatal(err)
170 }
171 }
172 }
173
174 func BenchmarkPreparedSelectString(b *testing.B) {
175 var result string
176 benchPreparedQuery(b, selectStringQuery, &result)
177 }
178
179 func BenchmarkPreparedSelectSeries(b *testing.B) {
180 var result int
181 benchPreparedQuery(b, selectSeriesQuery, &result)
182 }
183
184 func benchPreparedQuery(b *testing.B, query string, result interface{}) {
185 b.StopTimer()
186 db := openTestConn(b)
187 defer db.Close()
188 stmt, err := db.Prepare(query)
189 if err != nil {
190 b.Fatal(err)
191 }
192 defer stmt.Close()
193 b.StartTimer()
194
195 for i := 0; i < b.N; i++ {
196 benchPreparedQueryLoop(b, db, stmt, result)
197 }
198 }
199
200 func benchPreparedQueryLoop(b *testing.B, db *sql.DB, stmt *sql.Stmt, result interface{}) {
201 rows, err := stmt.Query()
202 if err != nil {
203 b.Fatal(err)
204 }
205 if !rows.Next() {
206 rows.Close()
207 b.Fatal("no rows")
208 }
209 defer rows.Close()
210 for rows.Next() {
211 err = rows.Scan(&result)
212 if err != nil {
213 b.Fatal("failed to scan")
214 }
215 }
216 }
217
218
219 func BenchmarkMockPreparedSelectString(b *testing.B) {
220 b.StopTimer()
221 const parseResponse = "1\x00\x00\x00\x04" +
222 "t\x00\x00\x00\x06\x00\x00" +
223 "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" +
224 "Z\x00\x00\x00\x05I"
225 const responses = parseResponse +
226 "2\x00\x00\x00\x04" +
227 "D\x00\x00\x00n\x00\x01\x00\x00\x00d0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" +
228 "C\x00\x00\x00\rSELECT 1\x00" +
229 "Z\x00\x00\x00\x05I"
230 c := fakeConn(responses, len(parseResponse))
231
232 stmt, err := c.Prepare(selectStringQuery)
233 if err != nil {
234 b.Fatal(err)
235 }
236 b.StartTimer()
237
238 for i := 0; i < b.N; i++ {
239 benchPreparedMockQuery(b, c, stmt)
240 }
241 }
242
243 func BenchmarkMockPreparedSelectSeries(b *testing.B) {
244 b.StopTimer()
245 const parseResponse = "1\x00\x00\x00\x04" +
246 "t\x00\x00\x00\x06\x00\x00" +
247 "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" +
248 "Z\x00\x00\x00\x05I"
249 var responses = parseResponse +
250 "2\x00\x00\x00\x04" +
251 seriesRowData +
252 "C\x00\x00\x00\x0fSELECT 100\x00" +
253 "Z\x00\x00\x00\x05I"
254 c := fakeConn(responses, len(parseResponse))
255
256 stmt, err := c.Prepare(selectSeriesQuery)
257 if err != nil {
258 b.Fatal(err)
259 }
260 b.StartTimer()
261
262 for i := 0; i < b.N; i++ {
263 benchPreparedMockQuery(b, c, stmt)
264 }
265 }
266
267 func benchPreparedMockQuery(b *testing.B, c *conn, stmt driver.Stmt) {
268 rows, err := stmt.(driver.StmtQueryContext).QueryContext(context.Background(), nil)
269 if err != nil {
270 b.Fatal(err)
271 }
272 defer rows.Close()
273 var dest [1]driver.Value
274 for {
275 if err := rows.Next(dest[:]); err != nil {
276 if err == io.EOF {
277 break
278 }
279 b.Fatal(err)
280 }
281 }
282 }
283
284 func BenchmarkEncodeInt64(b *testing.B) {
285 for i := 0; i < b.N; i++ {
286 encode(¶meterStatus{}, int64(1234), oid.T_int8)
287 }
288 }
289
290 func BenchmarkEncodeFloat64(b *testing.B) {
291 for i := 0; i < b.N; i++ {
292 encode(¶meterStatus{}, 3.14159, oid.T_float8)
293 }
294 }
295
296 var testByteString = []byte("abcdefghijklmnopqrstuvwxyz")
297
298 func BenchmarkEncodeByteaHex(b *testing.B) {
299 for i := 0; i < b.N; i++ {
300 encode(¶meterStatus{serverVersion: 90000}, testByteString, oid.T_bytea)
301 }
302 }
303 func BenchmarkEncodeByteaEscape(b *testing.B) {
304 for i := 0; i < b.N; i++ {
305 encode(¶meterStatus{serverVersion: 84000}, testByteString, oid.T_bytea)
306 }
307 }
308
309 func BenchmarkEncodeBool(b *testing.B) {
310 for i := 0; i < b.N; i++ {
311 encode(¶meterStatus{}, true, oid.T_bool)
312 }
313 }
314
315 var testTimestamptz = time.Date(2001, time.January, 1, 0, 0, 0, 0, time.Local)
316
317 func BenchmarkEncodeTimestamptz(b *testing.B) {
318 for i := 0; i < b.N; i++ {
319 encode(¶meterStatus{}, testTimestamptz, oid.T_timestamptz)
320 }
321 }
322
323 var testIntBytes = []byte("1234")
324
325 func BenchmarkDecodeInt64(b *testing.B) {
326 for i := 0; i < b.N; i++ {
327 decode(¶meterStatus{}, testIntBytes, oid.T_int8, formatText)
328 }
329 }
330
331 var testFloatBytes = []byte("3.14159")
332
333 func BenchmarkDecodeFloat64(b *testing.B) {
334 for i := 0; i < b.N; i++ {
335 decode(¶meterStatus{}, testFloatBytes, oid.T_float8, formatText)
336 }
337 }
338
339 var testBoolBytes = []byte{'t'}
340
341 func BenchmarkDecodeBool(b *testing.B) {
342 for i := 0; i < b.N; i++ {
343 decode(¶meterStatus{}, testBoolBytes, oid.T_bool, formatText)
344 }
345 }
346
347 func TestDecodeBool(t *testing.T) {
348 db := openTestConn(t)
349 rows, err := db.Query("select true")
350 if err != nil {
351 t.Fatal(err)
352 }
353 rows.Close()
354 }
355
356 var testTimestamptzBytes = []byte("2013-09-17 22:15:32.360754-07")
357
358 func BenchmarkDecodeTimestamptz(b *testing.B) {
359 for i := 0; i < b.N; i++ {
360 decode(¶meterStatus{}, testTimestamptzBytes, oid.T_timestamptz, formatText)
361 }
362 }
363
364 func BenchmarkDecodeTimestamptzMultiThread(b *testing.B) {
365 oldProcs := runtime.GOMAXPROCS(0)
366 defer runtime.GOMAXPROCS(oldProcs)
367 runtime.GOMAXPROCS(runtime.NumCPU())
368 globalLocationCache = newLocationCache()
369
370 f := func(wg *sync.WaitGroup, loops int) {
371 defer wg.Done()
372 for i := 0; i < loops; i++ {
373 decode(¶meterStatus{}, testTimestamptzBytes, oid.T_timestamptz, formatText)
374 }
375 }
376
377 wg := &sync.WaitGroup{}
378 b.ResetTimer()
379 for j := 0; j < 10; j++ {
380 wg.Add(1)
381 go f(wg, b.N/10)
382 }
383 wg.Wait()
384 }
385
386 func BenchmarkLocationCache(b *testing.B) {
387 globalLocationCache = newLocationCache()
388 for i := 0; i < b.N; i++ {
389 globalLocationCache.getLocation(rand.Intn(10000))
390 }
391 }
392
393 func BenchmarkLocationCacheMultiThread(b *testing.B) {
394 oldProcs := runtime.GOMAXPROCS(0)
395 defer runtime.GOMAXPROCS(oldProcs)
396 runtime.GOMAXPROCS(runtime.NumCPU())
397 globalLocationCache = newLocationCache()
398
399 f := func(wg *sync.WaitGroup, loops int) {
400 defer wg.Done()
401 for i := 0; i < loops; i++ {
402 globalLocationCache.getLocation(rand.Intn(10000))
403 }
404 }
405
406 wg := &sync.WaitGroup{}
407 b.ResetTimer()
408 for j := 0; j < 10; j++ {
409 wg.Add(1)
410 go f(wg, b.N/10)
411 }
412 wg.Wait()
413 }
414
415
416 func BenchmarkResultParsing(b *testing.B) {
417 b.StopTimer()
418
419 db := openTestConn(b)
420 defer db.Close()
421 _, err := db.Exec("BEGIN")
422 if err != nil {
423 b.Fatal(err)
424 }
425
426 b.StartTimer()
427 for i := 0; i < b.N; i++ {
428 res, err := db.Query("SELECT generate_series(1, 50000)")
429 if err != nil {
430 b.Fatal(err)
431 }
432 res.Close()
433 }
434 }
435
View as plain text