2020-07-04 23:00:11 +02:00
|
|
|
package progress
|
|
|
|
|
|
|
|
|
|
import (
|
2020-12-17 20:33:42 +05:30
|
|
|
"context"
|
2020-07-04 23:00:11 +02:00
|
|
|
"fmt"
|
2021-07-25 04:27:25 +05:30
|
|
|
"os"
|
2020-07-04 23:00:11 +02:00
|
|
|
"strings"
|
2020-07-31 23:07:33 +02:00
|
|
|
"time"
|
2020-08-25 23:24:31 +02:00
|
|
|
|
2020-11-01 19:42:25 +05:30
|
|
|
"github.com/projectdiscovery/clistats"
|
2020-08-25 23:24:31 +02:00
|
|
|
"github.com/projectdiscovery/gologger"
|
2025-02-11 04:31:37 +07:00
|
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
|
2020-07-04 23:00:11 +02:00
|
|
|
)
|
|
|
|
|
|
2021-03-09 17:19:03 +05:30
|
|
|
// Progress is an interface implemented by nuclei progress display
|
|
|
|
|
// driver.
|
|
|
|
|
type Progress interface {
|
|
|
|
|
// Stop stops the progress recorder.
|
|
|
|
|
Stop()
|
|
|
|
|
// Init inits the progress bar with initial details for scan
|
|
|
|
|
Init(hostCount int64, rulesCount int, requestCount int64)
|
|
|
|
|
// AddToTotal adds a value to the total request count
|
|
|
|
|
AddToTotal(delta int64)
|
|
|
|
|
// IncrementRequests increments the requests counter by 1.
|
|
|
|
|
IncrementRequests()
|
2023-01-13 13:41:05 +05:30
|
|
|
// SetRequests sets the counter by incrementing it with a delta
|
|
|
|
|
SetRequests(count uint64)
|
2021-03-09 17:19:03 +05:30
|
|
|
// IncrementMatched increments the matched counter by 1.
|
|
|
|
|
IncrementMatched()
|
|
|
|
|
// IncrementErrorsBy increments the error counter by count.
|
|
|
|
|
IncrementErrorsBy(count int64)
|
|
|
|
|
// IncrementFailedRequestsBy increments the number of requests counter by count
|
|
|
|
|
// along with errors.
|
|
|
|
|
IncrementFailedRequestsBy(count int64)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var _ Progress = &StatsTicker{}
|
|
|
|
|
|
|
|
|
|
// StatsTicker is a progress instance for showing program stats
|
|
|
|
|
type StatsTicker struct {
|
2023-01-13 13:41:05 +05:30
|
|
|
cloud bool
|
2020-11-16 00:40:32 +01:00
|
|
|
active bool
|
2021-06-22 16:02:37 -07:00
|
|
|
outputJSON bool
|
2021-07-07 12:22:37 -07:00
|
|
|
stats clistats.StatisticsClient
|
|
|
|
|
tickDuration time.Duration
|
2020-07-04 23:00:11 +02:00
|
|
|
}
|
|
|
|
|
|
2021-03-09 17:19:03 +05:30
|
|
|
// NewStatsTicker creates and returns a new progress tracking object.
|
2023-09-02 14:34:05 +05:30
|
|
|
func NewStatsTicker(duration int, active, outputJSON, cloud bool, port int) (Progress, error) {
|
2020-11-01 19:42:25 +05:30
|
|
|
var tickDuration time.Duration
|
2023-01-13 13:41:05 +05:30
|
|
|
if active && duration != -1 {
|
2021-02-23 18:05:20 +05:30
|
|
|
tickDuration = time.Duration(duration) * time.Second
|
2020-11-01 19:42:25 +05:30
|
|
|
} else {
|
|
|
|
|
tickDuration = -1
|
2020-07-29 23:11:17 +02:00
|
|
|
}
|
|
|
|
|
|
2021-03-09 17:19:03 +05:30
|
|
|
progress := &StatsTicker{}
|
2020-12-17 20:33:42 +05:30
|
|
|
|
2023-09-02 14:34:05 +05:30
|
|
|
statsOpts := &clistats.DefaultOptions
|
|
|
|
|
statsOpts.ListenPort = port
|
|
|
|
|
// metrics port is enabled by default and is not configurable with new version of clistats
|
|
|
|
|
// by default 63636 is used and than can be modified with -mp flag
|
|
|
|
|
|
|
|
|
|
stats, err := clistats.NewWithOptions(context.TODO(), statsOpts)
|
2020-12-17 20:33:42 +05:30
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2023-10-26 19:07:04 +05:30
|
|
|
// only print in verbose mode
|
|
|
|
|
gologger.Verbose().Msgf("Started metrics server at localhost:%v", stats.Options.ListenPort)
|
2023-01-13 13:41:05 +05:30
|
|
|
progress.cloud = cloud
|
2020-12-17 20:33:42 +05:30
|
|
|
progress.active = active
|
|
|
|
|
progress.stats = stats
|
|
|
|
|
progress.tickDuration = tickDuration
|
2021-06-22 16:02:37 -07:00
|
|
|
progress.outputJSON = outputJSON
|
2020-12-17 20:33:42 +05:30
|
|
|
|
|
|
|
|
return progress, nil
|
2020-07-04 23:00:11 +02:00
|
|
|
}
|
|
|
|
|
|
2020-11-01 19:42:25 +05:30
|
|
|
// Init initializes the progress display mechanism by setting counters, etc.
|
2021-03-09 17:19:03 +05:30
|
|
|
func (p *StatsTicker) Init(hostCount int64, rulesCount int, requestCount int64) {
|
2020-12-17 20:33:42 +05:30
|
|
|
p.stats.AddStatic("templates", rulesCount)
|
|
|
|
|
p.stats.AddStatic("hosts", hostCount)
|
|
|
|
|
p.stats.AddStatic("startedAt", time.Now())
|
|
|
|
|
p.stats.AddCounter("requests", uint64(0))
|
|
|
|
|
p.stats.AddCounter("errors", uint64(0))
|
2021-01-16 12:26:38 +05:30
|
|
|
p.stats.AddCounter("matched", uint64(0))
|
2020-12-17 20:33:42 +05:30
|
|
|
p.stats.AddCounter("total", uint64(requestCount))
|
|
|
|
|
|
2020-11-16 00:40:32 +01:00
|
|
|
if p.active {
|
2023-06-22 19:18:59 +05:30
|
|
|
var printCallbackFunc clistats.DynamicCallback
|
2021-06-22 16:02:37 -07:00
|
|
|
if p.outputJSON {
|
|
|
|
|
printCallbackFunc = printCallbackJSON
|
|
|
|
|
} else {
|
2023-01-13 13:41:05 +05:30
|
|
|
printCallbackFunc = p.makePrintCallback()
|
2021-06-22 16:02:37 -07:00
|
|
|
}
|
2023-06-22 19:18:59 +05:30
|
|
|
p.stats.AddDynamic("summary", printCallbackFunc)
|
|
|
|
|
if err := p.stats.Start(); err != nil {
|
2020-12-25 20:33:52 +05:30
|
|
|
gologger.Warning().Msgf("Couldn't start statistics: %s", err)
|
2020-11-16 00:40:32 +01:00
|
|
|
}
|
2023-06-22 19:18:59 +05:30
|
|
|
|
2023-09-02 14:34:05 +05:30
|
|
|
// Note: this is needed and is responsible for the tick event
|
2023-06-22 19:18:59 +05:30
|
|
|
p.stats.GetStatResponse(p.tickDuration, func(s string, err error) error {
|
|
|
|
|
if err != nil {
|
|
|
|
|
gologger.Warning().Msgf("Could not read statistics: %s\n", err)
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
})
|
2020-11-16 00:40:32 +01:00
|
|
|
}
|
2020-07-25 21:03:18 +02:00
|
|
|
}
|
|
|
|
|
|
2020-11-01 19:42:25 +05:30
|
|
|
// AddToTotal adds a value to the total request count
|
2021-03-09 17:19:03 +05:30
|
|
|
func (p *StatsTicker) AddToTotal(delta int64) {
|
2020-12-17 20:33:42 +05:30
|
|
|
p.stats.IncrementCounter("total", int(delta))
|
2020-07-27 00:00:06 +02:00
|
|
|
}
|
|
|
|
|
|
2020-12-25 20:33:52 +05:30
|
|
|
// IncrementRequests increments the requests counter by 1.
|
2021-03-09 17:19:03 +05:30
|
|
|
func (p *StatsTicker) IncrementRequests() {
|
2020-12-17 20:33:42 +05:30
|
|
|
p.stats.IncrementCounter("requests", 1)
|
2020-07-04 23:00:11 +02:00
|
|
|
}
|
|
|
|
|
|
2023-01-13 13:41:05 +05:30
|
|
|
// SetRequests sets the counter by incrementing it with a delta
|
|
|
|
|
func (p *StatsTicker) SetRequests(count uint64) {
|
2025-05-07 17:22:15 +05:30
|
|
|
p.stats.IncrementCounter("requests", int(count))
|
2023-01-13 13:41:05 +05:30
|
|
|
}
|
|
|
|
|
|
2021-01-16 12:26:38 +05:30
|
|
|
// IncrementMatched increments the matched counter by 1.
|
2021-03-09 17:19:03 +05:30
|
|
|
func (p *StatsTicker) IncrementMatched() {
|
2021-01-16 12:26:38 +05:30
|
|
|
p.stats.IncrementCounter("matched", 1)
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-02 02:22:15 +01:00
|
|
|
// IncrementErrorsBy increments the error counter by count.
|
2021-03-09 17:19:03 +05:30
|
|
|
func (p *StatsTicker) IncrementErrorsBy(count int64) {
|
2021-03-03 19:55:34 +01:00
|
|
|
p.stats.IncrementCounter("errors", int(count))
|
2021-03-02 02:22:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// IncrementFailedRequestsBy increments the number of requests counter by count along with errors.
|
2021-03-09 17:19:03 +05:30
|
|
|
func (p *StatsTicker) IncrementFailedRequestsBy(count int64) {
|
2020-12-17 20:33:42 +05:30
|
|
|
// mimic dropping by incrementing the completed requests
|
2020-12-25 20:33:52 +05:30
|
|
|
p.stats.IncrementCounter("requests", int(count))
|
2020-12-17 20:33:42 +05:30
|
|
|
p.stats.IncrementCounter("errors", int(count))
|
2020-07-06 00:09:58 +02:00
|
|
|
}
|
|
|
|
|
|
2023-06-22 19:18:59 +05:30
|
|
|
func (p *StatsTicker) makePrintCallback() func(stats clistats.StatisticsClient) interface{} {
|
|
|
|
|
return func(stats clistats.StatisticsClient) interface{} {
|
2023-01-13 13:41:05 +05:30
|
|
|
builder := &strings.Builder{}
|
2022-03-23 14:13:30 +01:00
|
|
|
|
2023-01-13 13:41:05 +05:30
|
|
|
var duration time.Duration
|
|
|
|
|
if startedAt, ok := stats.GetStatic("startedAt"); ok {
|
|
|
|
|
if startedAtTime, ok := startedAt.(time.Time); ok {
|
|
|
|
|
duration = time.Since(startedAtTime)
|
2025-07-01 00:40:44 +07:00
|
|
|
_, _ = fmt.Fprintf(builder, "[%s]", fmtDuration(duration))
|
2023-01-13 13:41:05 +05:30
|
|
|
}
|
2022-03-23 14:13:30 +01:00
|
|
|
}
|
|
|
|
|
|
2023-01-13 13:41:05 +05:30
|
|
|
if templates, ok := stats.GetStatic("templates"); ok {
|
|
|
|
|
builder.WriteString(" | Templates: ")
|
|
|
|
|
builder.WriteString(clistats.String(templates))
|
|
|
|
|
}
|
2022-03-23 14:13:30 +01:00
|
|
|
|
2023-01-13 13:41:05 +05:30
|
|
|
if hosts, ok := stats.GetStatic("hosts"); ok {
|
|
|
|
|
builder.WriteString(" | Hosts: ")
|
|
|
|
|
builder.WriteString(clistats.String(hosts))
|
|
|
|
|
}
|
2022-03-23 14:13:30 +01:00
|
|
|
|
2023-01-13 13:41:05 +05:30
|
|
|
requests, okRequests := stats.GetCounter("requests")
|
|
|
|
|
total, okTotal := stats.GetCounter("total")
|
2022-03-23 14:13:30 +01:00
|
|
|
|
2023-04-26 12:35:07 +05:30
|
|
|
// If input is not given, total is 0 which cause percentage overflow
|
|
|
|
|
if total == 0 {
|
|
|
|
|
total = requests
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-13 13:41:05 +05:30
|
|
|
if okRequests && okTotal && duration > 0 && !p.cloud {
|
|
|
|
|
builder.WriteString(" | RPS: ")
|
|
|
|
|
builder.WriteString(clistats.String(uint64(float64(requests) / duration.Seconds())))
|
|
|
|
|
}
|
2022-03-23 14:13:30 +01:00
|
|
|
|
2023-01-13 13:41:05 +05:30
|
|
|
if matched, ok := stats.GetCounter("matched"); ok {
|
|
|
|
|
builder.WriteString(" | Matched: ")
|
|
|
|
|
builder.WriteString(clistats.String(matched))
|
|
|
|
|
}
|
2022-03-23 14:13:30 +01:00
|
|
|
|
2023-01-13 13:41:05 +05:30
|
|
|
if errors, ok := stats.GetCounter("errors"); ok && !p.cloud {
|
|
|
|
|
builder.WriteString(" | Errors: ")
|
|
|
|
|
builder.WriteString(clistats.String(errors))
|
|
|
|
|
}
|
2022-03-23 14:13:30 +01:00
|
|
|
|
2023-01-13 13:41:05 +05:30
|
|
|
if okRequests && okTotal {
|
|
|
|
|
if p.cloud {
|
|
|
|
|
builder.WriteString(" | Task: ")
|
|
|
|
|
} else {
|
|
|
|
|
builder.WriteString(" | Requests: ")
|
|
|
|
|
}
|
|
|
|
|
builder.WriteString(clistats.String(requests))
|
|
|
|
|
builder.WriteRune('/')
|
|
|
|
|
builder.WriteString(clistats.String(total))
|
|
|
|
|
builder.WriteRune(' ')
|
|
|
|
|
builder.WriteRune('(')
|
|
|
|
|
//nolint:gomnd // this is not a magic number
|
|
|
|
|
builder.WriteString(clistats.String(uint64(float64(requests) / float64(total) * 100.0)))
|
|
|
|
|
builder.WriteRune('%')
|
|
|
|
|
builder.WriteRune(')')
|
|
|
|
|
builder.WriteRune('\n')
|
|
|
|
|
}
|
2021-03-03 19:55:34 +01:00
|
|
|
|
2025-07-01 00:40:44 +07:00
|
|
|
_, _ = fmt.Fprintf(os.Stderr, "%s", builder.String())
|
2023-06-22 19:18:59 +05:30
|
|
|
return builder.String()
|
2023-01-13 13:41:05 +05:30
|
|
|
}
|
2020-08-01 21:44:14 +02:00
|
|
|
}
|
2020-08-01 15:07:04 +02:00
|
|
|
|
2023-06-22 19:18:59 +05:30
|
|
|
func printCallbackJSON(stats clistats.StatisticsClient) interface{} {
|
2021-06-22 16:02:37 -07:00
|
|
|
builder := &strings.Builder{}
|
2022-04-15 18:40:51 +02:00
|
|
|
if err := json.NewEncoder(builder).Encode(metricsMap(stats)); err == nil {
|
2025-07-01 00:40:44 +07:00
|
|
|
_, _ = fmt.Fprintf(os.Stderr, "%s", builder.String())
|
2022-04-15 18:40:51 +02:00
|
|
|
}
|
2023-06-22 19:18:59 +05:30
|
|
|
return builder.String()
|
2021-06-22 16:02:37 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func metricsMap(stats clistats.StatisticsClient) map[string]interface{} {
|
2020-12-17 20:33:42 +05:30
|
|
|
results := make(map[string]interface{})
|
|
|
|
|
|
2022-04-15 18:40:51 +02:00
|
|
|
var (
|
|
|
|
|
startedAt time.Time
|
|
|
|
|
duration time.Duration
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if stAt, ok := stats.GetStatic("startedAt"); ok {
|
|
|
|
|
startedAt = stAt.(time.Time)
|
|
|
|
|
duration = time.Since(startedAt)
|
|
|
|
|
}
|
2020-12-17 20:33:42 +05:30
|
|
|
|
2022-04-15 18:40:51 +02:00
|
|
|
results["startedAt"] = startedAt
|
2020-12-17 20:33:42 +05:30
|
|
|
results["duration"] = fmtDuration(duration)
|
2021-06-22 16:02:37 -07:00
|
|
|
templates, _ := stats.GetStatic("templates")
|
2020-12-17 20:33:42 +05:30
|
|
|
results["templates"] = clistats.String(templates)
|
2021-06-22 16:02:37 -07:00
|
|
|
hosts, _ := stats.GetStatic("hosts")
|
2020-12-17 20:33:42 +05:30
|
|
|
results["hosts"] = clistats.String(hosts)
|
2021-06-22 16:02:37 -07:00
|
|
|
matched, _ := stats.GetCounter("matched")
|
2021-01-16 12:26:38 +05:30
|
|
|
results["matched"] = clistats.String(matched)
|
2021-06-22 16:02:37 -07:00
|
|
|
requests, _ := stats.GetCounter("requests")
|
2020-12-17 20:33:42 +05:30
|
|
|
results["requests"] = clistats.String(requests)
|
2021-06-22 16:02:37 -07:00
|
|
|
total, _ := stats.GetCounter("total")
|
2020-12-17 20:33:42 +05:30
|
|
|
results["total"] = clistats.String(total)
|
|
|
|
|
results["rps"] = clistats.String(uint64(float64(requests) / duration.Seconds()))
|
2021-06-22 16:02:37 -07:00
|
|
|
errors, _ := stats.GetCounter("errors")
|
2020-12-17 20:33:42 +05:30
|
|
|
results["errors"] = clistats.String(errors)
|
|
|
|
|
|
2022-04-15 18:40:51 +02:00
|
|
|
// nolint:gomnd // this is not a magic number
|
2020-12-17 20:33:42 +05:30
|
|
|
percentData := (float64(requests) * float64(100)) / float64(total)
|
|
|
|
|
percent := clistats.String(uint64(percentData))
|
|
|
|
|
results["percent"] = percent
|
|
|
|
|
return results
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-01 19:42:25 +05:30
|
|
|
// fmtDuration formats the duration for the time elapsed
|
|
|
|
|
func fmtDuration(d time.Duration) string {
|
|
|
|
|
d = d.Round(time.Second)
|
|
|
|
|
h := d / time.Hour
|
|
|
|
|
d -= h * time.Hour
|
|
|
|
|
m := d / time.Minute
|
|
|
|
|
d -= m * time.Minute
|
|
|
|
|
s := d / time.Second
|
|
|
|
|
return fmt.Sprintf("%d:%02d:%02d", h, m, s)
|
2020-07-31 23:07:33 +02:00
|
|
|
}
|
|
|
|
|
|
2020-11-01 19:42:25 +05:30
|
|
|
// Stop stops the progress bar execution
|
2021-03-09 17:19:03 +05:30
|
|
|
func (p *StatsTicker) Stop() {
|
2020-11-16 00:40:32 +01:00
|
|
|
if p.active {
|
2021-03-03 19:55:34 +01:00
|
|
|
// Print one final summary
|
2021-06-22 16:02:37 -07:00
|
|
|
if p.outputJSON {
|
|
|
|
|
printCallbackJSON(p.stats)
|
|
|
|
|
} else {
|
2023-01-13 13:41:05 +05:30
|
|
|
p.makePrintCallback()(p.stats)
|
2021-06-22 16:02:37 -07:00
|
|
|
}
|
2020-11-16 00:40:32 +01:00
|
|
|
if err := p.stats.Stop(); err != nil {
|
2020-12-25 20:33:52 +05:30
|
|
|
gologger.Warning().Msgf("Couldn't stop statistics: %s", err)
|
2020-11-16 00:40:32 +01:00
|
|
|
}
|
|
|
|
|
}
|
2020-08-01 15:07:04 +02:00
|
|
|
}
|