2019-06-25 21:58:38 +01:00
|
|
|
// Copyright (C) 2019 Storj Labs, Inc.
|
|
|
|
// See LICENSE for copying information.
|
|
|
|
|
|
|
|
package reports
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"encoding/csv"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"os"
|
|
|
|
"strconv"
|
2022-04-12 19:01:14 +01:00
|
|
|
"strings"
|
2019-06-25 21:58:38 +01:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/zeebo/errs"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
|
2019-12-27 11:48:47 +00:00
|
|
|
"storj.io/common/memory"
|
2022-04-12 19:01:14 +01:00
|
|
|
"storj.io/common/useragent"
|
2019-06-25 21:58:38 +01:00
|
|
|
"storj.io/storj/satellite/attribution"
|
|
|
|
"storj.io/storj/satellite/satellitedb"
|
|
|
|
)
|
|
|
|
|
|
|
|
var headers = []string{
|
2022-01-20 21:15:13 +00:00
|
|
|
"userAgent",
|
2022-04-07 21:14:23 +01:00
|
|
|
"gbHours",
|
|
|
|
"segmentHours",
|
|
|
|
"objectHours",
|
|
|
|
"hours",
|
|
|
|
"gbEgress",
|
2019-06-25 21:58:38 +01:00
|
|
|
}
|
|
|
|
|
2022-04-25 21:56:11 +01:00
|
|
|
// AttributionTotals is a map of attribution totals per user agent.
|
|
|
|
type AttributionTotals map[string]Total
|
|
|
|
|
|
|
|
// Total is the total attributable usage for a user agent over a period of time.
|
|
|
|
type Total struct {
|
2022-04-12 19:01:14 +01:00
|
|
|
ByteHours float64
|
|
|
|
SegmentHours float64
|
|
|
|
ObjectHours float64
|
|
|
|
BucketHours float64
|
|
|
|
BytesEgress int64
|
|
|
|
}
|
|
|
|
|
2020-07-16 15:18:02 +01:00
|
|
|
// GenerateAttributionCSV creates a report with.
|
2022-01-20 21:15:13 +00:00
|
|
|
func GenerateAttributionCSV(ctx context.Context, database string, start time.Time, end time.Time, output io.Writer) error {
|
2022-04-12 19:01:14 +01:00
|
|
|
log := zap.L().Named("attribution-report")
|
|
|
|
db, err := satellitedb.Open(ctx, log.Named("db"), database, satellitedb.Options{ApplicationName: "satellite-attribution"})
|
2019-06-25 21:58:38 +01:00
|
|
|
if err != nil {
|
|
|
|
return errs.New("error connecting to master database on satellite: %+v", err)
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
err = errs.Combine(err, db.Close())
|
|
|
|
if err != nil {
|
2020-04-13 10:31:17 +01:00
|
|
|
log.Error("Error closing satellite DB connection after retrieving partner value attribution data.", zap.Error(err))
|
2019-06-25 21:58:38 +01:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2022-01-20 21:15:13 +00:00
|
|
|
rows, err := db.Attribution().QueryAllAttribution(ctx, start, end)
|
2019-06-25 21:58:38 +01:00
|
|
|
if err != nil {
|
|
|
|
return errs.Wrap(err)
|
|
|
|
}
|
|
|
|
|
2022-04-12 19:01:14 +01:00
|
|
|
partnerAttributionTotals := SumAttributionByUserAgent(rows, log)
|
|
|
|
|
2019-06-25 21:58:38 +01:00
|
|
|
w := csv.NewWriter(output)
|
|
|
|
defer func() {
|
|
|
|
w.Flush()
|
|
|
|
}()
|
|
|
|
|
|
|
|
if err := w.Write(headers); err != nil {
|
|
|
|
return errs.Wrap(err)
|
|
|
|
}
|
2022-04-12 19:01:14 +01:00
|
|
|
for userAgent, totals := range partnerAttributionTotals {
|
|
|
|
gbHours := memory.Size(totals.ByteHours).GB()
|
|
|
|
egressGBData := memory.Size(totals.BytesEgress).GB()
|
|
|
|
record := []string{
|
|
|
|
userAgent,
|
|
|
|
strconv.FormatFloat(gbHours, 'f', 4, 64),
|
|
|
|
strconv.FormatFloat(totals.SegmentHours, 'f', 4, 64),
|
|
|
|
strconv.FormatFloat(totals.ObjectHours, 'f', 4, 64),
|
|
|
|
strconv.FormatFloat(totals.BucketHours, 'f', 4, 64),
|
|
|
|
strconv.FormatFloat(egressGBData, 'f', 4, 64),
|
2019-06-25 21:58:38 +01:00
|
|
|
}
|
|
|
|
if err := w.Write(record); err != nil {
|
|
|
|
return errs.Wrap(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if err := w.Error(); err != nil {
|
|
|
|
return errs.Wrap(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if output != os.Stdout {
|
|
|
|
fmt.Println("Generated report for partner attribution")
|
|
|
|
}
|
|
|
|
return errs.Wrap(err)
|
|
|
|
}
|
|
|
|
|
2022-04-12 19:01:14 +01:00
|
|
|
// SumAttributionByUserAgent sums all bucket attribution by the first entry in the user agent.
|
2022-04-25 21:56:11 +01:00
|
|
|
func SumAttributionByUserAgent(rows []*attribution.BucketUsage, log *zap.Logger) AttributionTotals {
|
|
|
|
attributionTotals := make(map[string]Total)
|
2022-04-12 19:01:14 +01:00
|
|
|
userAgentParseFailures := make(map[string]bool)
|
|
|
|
|
|
|
|
for _, row := range rows {
|
|
|
|
userAgentEntries, err := useragent.ParseEntries(row.UserAgent)
|
|
|
|
// also check the length of user agent for sanity.
|
|
|
|
// If the length of user agent is zero the parse method will not return an error.
|
|
|
|
if err != nil || len(row.UserAgent) == 0 {
|
|
|
|
if _, ok := userAgentParseFailures[string(row.UserAgent)]; !ok {
|
|
|
|
userAgentParseFailures[string(row.UserAgent)] = true
|
|
|
|
log.Error("error while parsing user agent", zap.String("user agent", string(row.UserAgent)), zap.Error(err))
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
userAgent := strings.ToLower(userAgentEntries[0].Product)
|
|
|
|
|
2022-04-25 21:56:11 +01:00
|
|
|
if _, ok := attributionTotals[userAgent]; !ok {
|
|
|
|
attributionTotals[userAgent] = Total{
|
2022-04-12 19:01:14 +01:00
|
|
|
ByteHours: row.ByteHours,
|
|
|
|
SegmentHours: row.SegmentHours,
|
|
|
|
ObjectHours: row.ObjectHours,
|
|
|
|
BucketHours: float64(row.Hours),
|
|
|
|
BytesEgress: row.EgressData,
|
|
|
|
}
|
|
|
|
} else {
|
2022-04-25 21:56:11 +01:00
|
|
|
partnerTotal := attributionTotals[userAgent]
|
2022-04-12 19:01:14 +01:00
|
|
|
|
|
|
|
partnerTotal.ByteHours += row.ByteHours
|
|
|
|
partnerTotal.SegmentHours += row.SegmentHours
|
|
|
|
partnerTotal.ObjectHours += row.ObjectHours
|
|
|
|
partnerTotal.BucketHours += float64(row.Hours)
|
|
|
|
partnerTotal.BytesEgress += row.EgressData
|
|
|
|
|
2022-04-25 21:56:11 +01:00
|
|
|
attributionTotals[userAgent] = partnerTotal
|
2022-04-12 19:01:14 +01:00
|
|
|
}
|
2019-06-25 21:58:38 +01:00
|
|
|
}
|
2022-04-25 21:56:11 +01:00
|
|
|
return attributionTotals
|
2019-06-25 21:58:38 +01:00
|
|
|
}
|