diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a676215 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea +bin diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3baac0a --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 maintell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index f59ed02..ff29fbb 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,42 @@ # webBenchmark -a simple tool of website benchmark. +http benchmark tool to ran out your server bandwidth. +- random User-Agent on every Request +- customizable Referer Url, +- concurrent routines as you wish, depends on you server performance. +- add http post mode +- specify target ip, or resolved by system dns. +- randomly X-Forwarded-For and X-Real-IP (default on). + +# Todo +- automatically tune concurrent routines to gain maximum performance. +- randomly target ip. +- support NOT standard port in address with specify target ip. + +# Usage: + webBenchmark -c [COUNT] -s [URL] -r [REFERER] + -c int + concurrent routines for download (default 16) + -r string + referer url + -s string + target url (default "https://baidu.com") + -i string + customize ip + -f string + randomized X-Forwarded-For and X-Real-IP address + -p string + post content + + +# LINUX: + wget https://github.com/maintell/webBenchmark/releases/download/0.3/webBenchmark_linux_x64 + chmod +x webBenchmark_linux_x64 + ./webBenchmark_linux_x64 -c 32 -s https://target.url + +## advanced example + # send request to 1.1.1.1 for https://target.url with 32 concurrent threads + # and refer is https://refer.url + ./webBenchmark_linux_x64 -c 32 -s https://target.url -r https://refer.url -i 1.1.1.1 + + -# Usage: webBenchmark -c [COUNT] -s [URL] -r [Refer] diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..13595a3 --- /dev/null +++ b/build.bat @@ -0,0 +1,14 @@ +@echo off +SET CGO_ENABLED=0 +SET GOOS=linux +SET GOARCH=amd64 +go build -o webBenchmark_linux_x64 +SET GOOS=linux +SET GOARCH=arm +go build -o webBenchmark_linux_arm +SET GOOS=windows +SET GOARCH=amd64 +go build -o webBenchmark_x64.exe +SET GOOS=windows +SET GOARCH=arm +go build -o webBenchmark_x64_arm.exe \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..55c1393 --- /dev/null +++ b/go.mod @@ -0,0 +1,17 @@ +module github.com/maintell/webBenchmark + +go 1.16 + +require ( + github.com/EDDYCJY/fake-useragent v0.2.0 + github.com/PuerkitoBio/goquery v1.6.1 // indirect + github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 // indirect + github.com/apoorvam/goterminal v0.0.0-20180523175556-614d345c47e5 + github.com/go-ole/go-ole v1.2.5 // indirect + github.com/mattn/go-isatty v0.0.12 // indirect + github.com/miekg/dns v1.1.43 + github.com/shirou/gopsutil v3.21.4+incompatible + github.com/stretchr/testify v1.7.0 // indirect + github.com/tklauser/go-sysconf v0.3.6 // indirect + golang.org/x/sys v0.0.0-20210514084401-e8d321eab015 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..f92b5b1 --- /dev/null +++ b/go.sum @@ -0,0 +1,51 @@ +github.com/EDDYCJY/fake-useragent v0.2.0 h1:Jcnkk2bgXmDpX0z+ELlUErTkoLb/mxFBNd2YdcpvJBs= +github.com/EDDYCJY/fake-useragent v0.2.0/go.mod h1:5wn3zzlDxhKW6NYknushqinPcAqZcAPHy8lLczCdJdc= +github.com/PuerkitoBio/goquery v1.6.1 h1:FgjbQZKl5HTmcn4sKBgvx8vv63nhyhIpv7lJpFGCWpk= +github.com/PuerkitoBio/goquery v1.6.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= +github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 h1:5sXbqlSomvdjlRbWyNqkPsJ3Fg+tQZCbgeX1VGljbQY= +github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo= +github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= +github.com/apoorvam/goterminal v0.0.0-20180523175556-614d345c47e5 h1:VYqcjykqpcq262cDxBAkAelSdg6HETkxgwzQRTS40Aw= +github.com/apoorvam/goterminal v0.0.0-20180523175556-614d345c47e5/go.mod h1:E7x8aDc3AQzDKjEoIZCt+XYheHk2OkP+p2UgeNjecH8= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY= +github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg= +github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/shirou/gopsutil v3.21.4+incompatible h1:fuHcTm5mX+wzo542cmYcV9RTGQLbnHLI5SyQ5ryTVck= +github.com/shirou/gopsutil v3.21.4+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tklauser/go-sysconf v0.3.6 h1:oc1sJWvKkmvIxhDHeKWvZS4f6AW+YcoguSfRF2/Hmo4= +github.com/tklauser/go-sysconf v0.3.6/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= +github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefldA= +github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015 h1:hZR0X1kPW+nwyJ9xRxqZk1vx5RUObAPBdKVvXPDUH/E= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go new file mode 100644 index 0000000..65cf2e8 --- /dev/null +++ b/main.go @@ -0,0 +1,290 @@ +package main + +import ( + "container/list" + "context" + "flag" + "fmt" + "github.com/EDDYCJY/fake-useragent" + "io" + "io/ioutil" + "math" + "math/rand" + "net/http" + "os" + "strings" + "sync" + "time" + + "github.com/miekg/dns" + "net" + + URL "net/url" + + "github.com/apoorvam/goterminal" + "github.com/shirou/gopsutil/cpu" + "github.com/shirou/gopsutil/load" + "github.com/shirou/gopsutil/mem" + netstat "github.com/shirou/gopsutil/net" +) + +const ( + letterIdxBits = 6 + letterIdxMask = 1<= 0; { + if remain == 0 { + cache, remain = rand.Int63(), letterIdxMax + } + if idx := int(cache & letterIdxMask); idx < len(letterBytes) { + b[i] = letterBytes[idx] + i-- + } + cache >>= letterIdxBits + remain-- + } + return string(b) +} + +func generateRandomIPAddress() string { + rand.Seed(time.Now().Unix()) + ip := fmt.Sprintf("%d.%d.%d.%d", rand.Intn(255), rand.Intn(255), rand.Intn(255), rand.Intn(255)) + return ip +} + +func LeastSquares(x []float64, y []float64) (a float64, b float64) { + xi := float64(0) + x2 := float64(0) + yi := float64(0) + xy := float64(0) + if len(x) != len(y) { + a = 0 + b = 0 + return + } else { + length := float64(len(x)) + for i := 0; i < len(x); i++ { + xi += x[i] + x2 += x[i] * x[i] + yi += y[i] + xy += x[i] * y[i] + } + a = (yi*xi - xy*length) / (xi*xi - x2*length) + b = (yi*x2 - xy*xi) / (x2*length - xi*xi) + } + return +} + +func showStat() { + + initialNetCounter, _ := netstat.IOCounters(true) + iplist := "" + if customIP !=nil && len(customIP)>0{ + iplist = customIP.String() + }else{ + u, _ := URL.Parse(*url) + iplist = strings.Join(nslookup(u.Hostname(),"8.8.8.8"),",") + } + + for true { + percent, _ := cpu.Percent(time.Second, false) + memStat, _ := mem.VirtualMemory() + netCounter, _ := netstat.IOCounters(true) + loadStat, _ := load.Avg() + + fmt.Fprintf(TerminalWriter, "target URL:%s\n", *url) + fmt.Fprintf(TerminalWriter, "target IP:%s\n", iplist) + + fmt.Fprintf(TerminalWriter, "cpu percent:%.3f%% \n", percent) + fmt.Fprintf(TerminalWriter, "mem percent:%.3f%% \n", memStat.UsedPercent) + fmt.Fprintf(TerminalWriter, "load info:%.3f %.3f %.3f\n", loadStat.Load1, loadStat.Load5, loadStat.Load15) + for i := 0; i < len(netCounter); i++ { + if netCounter[i].BytesRecv == 0 && netCounter[i].BytesSent == 0 { + continue + } + RecvBytes := float64(netCounter[i].BytesRecv - initialNetCounter[i].BytesRecv) + SendBytes := float64(netCounter[i].BytesSent - initialNetCounter[i].BytesSent) + //if RecvBytes > 1000 { + // SpeedIndex++ + // pair := speedPair{ + // index: SpeedIndex, + // speed: RecvBytes, + // } + // SpeedQueue.PushBack(pair) + // if SpeedQueue.Len() > 60 { + // SpeedQueue.Remove(SpeedQueue.Front()) + // } + // var x []float64 + // var y []float64 + // x = make([]float64, 60) + // y = make([]float64, 60) + // var point = 0 + // for item := SpeedQueue.Front(); item != nil; item = item.Next() { + // spdPair := item.Value.(speedPair) + // x[point] = float64(spdPair.index) + // y[point] = spdPair.speed + // point++ + // } + // _, b := LeastSquares(x, y) + // log.Printf("Speed Vertical:%.3f\n", b) + //} + fmt.Fprintf(TerminalWriter, "Nic:%v,Recv %s/s,Send %s/s\n", netCounter[i].Name, + readableBytes(RecvBytes), + readableBytes(SendBytes)) + } + initialNetCounter = netCounter + TerminalWriter.Clear() + TerminalWriter.Print() + time.Sleep(1 * time.Millisecond) + } +} + +func readableBytes(bytes float64) (expression string) { + if bytes == 0 { + return "0B" + } + var i = math.Floor(math.Log(bytes) / math.Log(1024)) + var sizes = []string{"B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"} + return fmt.Sprintf("%.3f%s", bytes/math.Pow(1024, i), sizes[int(i)]) +} + +func nslookup(targetAddress, server string) (res []string) { + if server == "" { + server = "8.8.8.8" + } + c := dns.Client{} + m := dns.Msg{} + m.SetQuestion(targetAddress+".", dns.TypeA) + + ns := server + ":53" + r, t, err := c.Exchange(&m, ns) + if err != nil { + fmt.Printf("nameserver %s error: %v\n", ns, err) + return res + } + fmt.Printf("nameserver %s took %v", ns, t) + if len(r.Answer) == 0 { + return res + } + for _, ans := range r.Answer { + Arecord := ans.(*dns.A) + res = append(res, fmt.Sprintf("%s", Arecord)) + } + return +} + +func goFun(Url string, postContent string, Referer string, XforwardFor bool, customIP ipArray, wg *sync.WaitGroup) { + defer func() { + if r := recover(); r != nil { + go goFun(Url, postContent, Referer, XforwardFor, customIP, wg) + } + }() + for true { + if customIP != nil && len(customIP) > 0 { + dialer := &net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + } + http.DefaultTransport.(*http.Transport).DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { + rand.Seed(time.Now().Unix()) + ip := customIP[rand.Intn(len(customIP))] + if strings.HasPrefix(addr,"https"){ + addr = ip + ":443" + } else if strings.HasPrefix(addr,"http") { + addr = ip + ":80" + } else { + addr = ip + ":80" + } + return dialer.DialContext(ctx, network, addr) + } + } + + var request *http.Request + var err1 error = nil + client := &http.Client{} + if len(postContent) > 0 { + request, err1 = http.NewRequest("POST", Url, strings.NewReader(postContent)) + } else { + request, err1 = http.NewRequest("GET", Url, nil) + } + if err1 != nil { + continue + } + if len(Referer) == 0 { + Referer = Url + } + request.Header.Add("Cookie", RandStringBytesMaskImpr(12)) + request.Header.Add("User-Agent", browser.Random()) + request.Header.Add("Referer", Referer) + if XforwardFor { + randomip := generateRandomIPAddress() + request.Header.Add("X-Forwarded-For", randomip) + request.Header.Add("X-Real-IP", randomip) + } + + resp, err2 := client.Do(request) + if err2 != nil { + continue + } + _, err3 := io.Copy(ioutil.Discard, resp.Body) + if err3 != nil { + continue + } + } + wg.Done() +} + +var count = flag.Int("c", 16, "concurrent thread for download,default 8") +var url = flag.String("s", "https://baidu.com", "target url") +var postContent = flag.String("p", "", "post content") +var referer = flag.String("r", "", "referer url") +var xforwardfor = flag.Bool("f", true, "randomized X-Forwarded-For and X-Real-IP address") +var TerminalWriter = goterminal.New(os.Stdout) +var customIP ipArray + +func main() { + flag.Var(&customIP, "i", "custom ip address for that domain, multiple addresses automatically will be assigned randomly") + flag.Parse() + routines := *count + + if customIP != nil && len(customIP) > 0 && routines < len(customIP){ + routines = len(customIP) + } + + go showStat() + var waitgroup sync.WaitGroup + if routines <= 0 { + routines = 16 + } + for i := 0; i < routines; i++ { + waitgroup.Add(1) + go goFun(*url, *postContent, *referer, *xforwardfor, customIP, &waitgroup) + } + waitgroup.Wait() + TerminalWriter.Reset() +}