erigon-pulse/cmd/observer/reports/clients_estimate_report.go
Håvard Anda Estensen 099fb7c627
Grow string builders (#6192)
Strings builders have a buffer (slice) that can be pre-allocated
2022-12-04 10:51:20 +07:00

98 lines
2.3 KiB
Go

package reports
import (
"context"
"fmt"
"math"
"strings"
"github.com/ledgerwatch/erigon/cmd/observer/database"
)
type ClientsEstimateReportEntry struct {
Name string
CountLow uint
CountHigh uint
}
type ClientsEstimateReport struct {
Clients []ClientsEstimateReportEntry
}
func CreateClientsEstimateReport(
ctx context.Context,
db database.DB,
limit uint,
maxPingTries uint,
networkID uint,
) (*ClientsEstimateReport, error) {
clientsReport, err := CreateClientsReport(ctx, db, limit, maxPingTries, networkID)
if err != nil {
return nil, err
}
report := ClientsEstimateReport{}
for i, topClient := range clientsReport.Clients {
if uint(i) >= limit {
break
}
clientName := topClient.Name
sameNetworkCount, err := db.CountClients(ctx, clientName+"/", maxPingTries, networkID)
if err != nil {
return nil, err
}
if sameNetworkCount == 0 {
continue
}
knownNetworkCount, err := db.CountClientsWithNetworkID(ctx, clientName+"/", maxPingTries)
if err != nil {
return nil, err
}
if knownNetworkCount == 0 {
continue
}
// 1 - (1 - p)/2 percentile for 95% confidence
const z = 1.96
intervalLow, intervalHigh := waldInterval(knownNetworkCount, sameNetworkCount, z)
transientErrCount, err := db.CountClientsWithHandshakeTransientError(ctx, clientName+"/", maxPingTries)
if err != nil {
return nil, err
}
countLow := sameNetworkCount + uint(math.Round(float64(transientErrCount)*intervalLow))
countHigh := sameNetworkCount + uint(math.Round(float64(transientErrCount)*intervalHigh))
client := ClientsEstimateReportEntry{
clientName,
countLow,
countHigh,
}
report.Clients = append(report.Clients, client)
}
return &report, nil
}
// https://en.wikipedia.org/wiki/Binomial_proportion_confidence_interval#Normal_approximation_interval_or_Wald_interval
func waldInterval(n uint, ns uint, z float64) (float64, float64) {
nf := n - ns
p := float64(ns) / float64(n)
interval := z * math.Sqrt(float64(ns*nf)) / (float64(n) * math.Sqrt(float64(n)))
return p - interval, p + interval
}
func (report *ClientsEstimateReport) String() string {
var builder strings.Builder
builder.Grow(2 * len(report.Clients))
for _, client := range report.Clients {
builder.WriteString(fmt.Sprintf("%6d - %-6d %s", client.CountLow, client.CountHigh, client.Name))
builder.WriteRune('\n')
}
return builder.String()
}