Skip to content

Commit

Permalink
init base file
Browse files Browse the repository at this point in the history
  • Loading branch information
cnlh committed Mar 31, 2020
1 parent 45f8494 commit 2824a6e
Show file tree
Hide file tree
Showing 6 changed files with 376 additions and 138 deletions.
63 changes: 63 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# perfor
A simple performance testing tool implemented in golang, the basic functions refer to wrk and ab
## Why use perfor?
- http and socks5 support
- a good performance as wrk(some implements in golang not work well)
## Building

```
git clone git://github.com/cnlh/perfor.git
cd perfor
go build
```
## usage

basic usage is quite simple:
```
perfor [flags] url
```

with the flags being
```
-b string
the body of request
-c int
the number of connection (default 1000)
-cpu int
the number of cpu used
-h string
request header, split by \r\n
-host string
the host of request
-m string
request method (default "GET")
-n int
the number of request (default 100000)
-t int
request/socket timeout in ms (default 3000)
-proxy string
proxy of request
```
for example
```
perfor -c 1100 -n 1000000 http://127.0.0.1/
```

## Example
```shell script
Running 1000000 test @ 127.0.0.1:80 by 1100 connections
1000000 requests in 5.73s, 4.01GB read, 33.42MB write
Requests/sec: 174420.54
Transfer/sec: 721.21MB
Error : 0
Percentage of the requests served within a certain time (ms)
50% 5
65% 6
75% 7
80% 7
90% 9
95% 13
98% 19
99% 23
100% 107
```
176 changes: 176 additions & 0 deletions connection.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
package main

import (
"bufio"
"bytes"
"crypto/tls"
"errors"
"golang.org/x/net/proxy"
"io"
"net"
"net/http"
"net/url"
"strconv"
"sync/atomic"
"time"
"unsafe"
)

var (
rawBytes = []byte("\r\n\r\n")
rowBytes = []byte("\r\n")
lenBytes = []byte("Content-Length: ")
l = len(lenBytes)
)

type ReqConn struct {
ErrorTimes int
Count int64
NowNum *int64
timeout int
writeBytes []byte
writeLen int
readLen int
reqTimes []int
conn net.Conn
remoteAddr string
schema string
buf []byte
proxy string
}

func (rc *ReqConn) dial() error {
if rc.conn != nil {
rc.conn.Close()
}
var err error
var conn net.Conn
if rc.proxy != "" {
var u *url.URL
var d proxy.Dialer
u, err = url.Parse(rc.proxy)
if err != nil {
return err
}
switch u.Scheme {
case "socks5":
d, err = proxy.FromURL(u, nil)
if err != nil {
return err
}
conn, err = d.Dial("tcp", rc.remoteAddr)
default:
conn, err = NewHttpProxyConn(u, rc.remoteAddr)
}

} else {
conn, err = net.DialTimeout("tcp", rc.remoteAddr, time.Millisecond*time.Duration(rc.timeout))
}
if err != nil {
return err
}
rc.conn = conn
if rc.schema == "https" {
conf := &tls.Config{
InsecureSkipVerify: true,
}
rc.conn = tls.Client(rc.conn, conf)
}
return nil
}

func (rc *ReqConn) Start() (err error) {
var contentLen string
var bodyHasRead int
var headerHasRead int
var n int
var reqTime time.Time
re:
if err != nil && err != io.EOF {
rc.ErrorTimes += 1
}
if err = rc.dial(); err != nil {
return
}
for {
bodyHasRead = 0
headerHasRead = 0
reqTime = time.Now()
n, err = rc.conn.Write(rc.writeBytes)
if err != nil {
goto re
}
rc.writeLen += n
readHeader:
rc.conn.SetReadDeadline(time.Now().Add(time.Millisecond * time.Duration(rc.timeout)))
n, err = rc.conn.Read(rc.buf[headerHasRead:])
if err != nil {
goto re
}
headerHasRead += n
rc.readLen += n
var bbArr [2][]byte
bodyPos := bytes.Index(rc.buf[:headerHasRead], rawBytes)
if bodyPos > -1 {
bbArr[0] = rc.buf[:bodyPos]
bbArr[1] = rc.buf[bodyPos+len(rawBytes):]
} else {
goto readHeader
}
n := bytes.Index(bbArr[0], lenBytes)
start := n + l
end := bytes.Index(bbArr[0][start:], rowBytes)
if end == -1 {
contentLen = Bytes2str(bbArr[0][start:])
} else {
contentLen = Bytes2str(bbArr[0][start : start+end])
}
contentLenI, _ := strconv.Atoi(contentLen)
bodyHasRead += len(bbArr[1])
for {
if bodyHasRead >= contentLenI {
break
}
n, err = rc.conn.Read(rc.buf)
if err != nil {
goto re
}
rc.readLen += n
bodyHasRead += n
}
rc.reqTimes = append(rc.reqTimes, int(time.Now().Sub(reqTime).Milliseconds()))
if atomic.AddInt64(rc.NowNum, 1) >= rc.Count {
return
}
}
}

func Bytes2str(b []byte) string {
return *(*string)(unsafe.Pointer(&b))
}

func NewHttpProxyConn(url *url.URL, remoteAddr string) (net.Conn, error) {
req, err := http.NewRequest("CONNECT", "http://"+remoteAddr, nil)
if err != nil {
return nil, err
}
password, _ := url.User.Password()
req.SetBasicAuth(url.User.Username(), password)
// we make a http proxy request
proxyConn, err := net.Dial("tcp", url.Host)
if err != nil {
return nil, err
}
if err := req.Write(proxyConn); err != nil {
return nil, err
}
res, err := http.ReadResponse(bufio.NewReader(proxyConn), req)
if err != nil {
return nil, err
}
_ = res.Body.Close()
if res.StatusCode != 200 {
return nil, errors.New("Proxy error " + res.Status)
}
return proxyConn, nil
}
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/cnlh/perfor

go 1.13

require golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
Loading

0 comments on commit 2824a6e

Please sign in to comment.