mirror of
https://github.com/weaveworks/scope.git
synced 2026-07-28 17:51:21 +00:00
Add multitenant-live collector
For when we want to collect reports in memory, but not save them to store. Extract this functionality out of awsCollector to create new liveCollector object.
This commit is contained in:
@@ -15,7 +15,6 @@ import (
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/dynamodb"
|
||||
"github.com/bluele/gcache"
|
||||
"github.com/nats-io/nats"
|
||||
opentracing "github.com/opentracing/opentracing-go"
|
||||
otlog "github.com/opentracing/opentracing-go/log"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
@@ -110,11 +109,6 @@ func registerAWSCollectorMetrics() {
|
||||
prometheus.MustRegister(dynamoValueSize)
|
||||
prometheus.MustRegister(inProcessCacheRequests)
|
||||
prometheus.MustRegister(inProcessCacheHits)
|
||||
prometheus.MustRegister(reportSizeHistogram)
|
||||
prometheus.MustRegister(reportsPerUser)
|
||||
prometheus.MustRegister(reportSizePerUser)
|
||||
prometheus.MustRegister(topologiesDropped)
|
||||
prometheus.MustRegister(natsRequests)
|
||||
flushDuration.Register()
|
||||
}
|
||||
|
||||
@@ -133,32 +127,16 @@ type ReportStore interface {
|
||||
|
||||
// AWSCollectorConfig has everything we need to make an AWS collector.
|
||||
type AWSCollectorConfig struct {
|
||||
UserIDer UserIDer
|
||||
DynamoDBConfig *aws.Config
|
||||
DynamoTable string
|
||||
S3Store *S3Store
|
||||
StoreInterval time.Duration
|
||||
NatsHost string
|
||||
MemcacheClient *MemcacheClient
|
||||
Window time.Duration
|
||||
MaxTopNodes int
|
||||
CollectorAddr string
|
||||
}
|
||||
|
||||
type awsCollector struct {
|
||||
cfg AWSCollectorConfig
|
||||
liveCollector
|
||||
awsCfg AWSCollectorConfig
|
||||
db *dynamodb.DynamoDB
|
||||
merger app.Merger
|
||||
inProcess inProcessStore
|
||||
pending sync.Map
|
||||
ticker *time.Ticker
|
||||
|
||||
nats *nats.Conn
|
||||
waitersLock sync.Mutex
|
||||
waiters map[watchKey]*nats.Subscription
|
||||
|
||||
collectors []string
|
||||
lastResolved time.Time
|
||||
}
|
||||
|
||||
// Shortcut reports:
|
||||
@@ -177,45 +155,25 @@ type watchKey struct {
|
||||
|
||||
// NewAWSCollector the elastic reaper of souls
|
||||
// https://github.com/aws/aws-sdk-go/wiki/common-examples
|
||||
func NewAWSCollector(config AWSCollectorConfig) (AWSCollector, error) {
|
||||
func NewAWSCollector(liveConfig LiveCollectorConfig, config AWSCollectorConfig) (AWSCollector, error) {
|
||||
registerAWSCollectorMetricsOnce.Do(registerAWSCollectorMetrics)
|
||||
var nc *nats.Conn
|
||||
if config.NatsHost != "" {
|
||||
if config.MemcacheClient == nil {
|
||||
return nil, fmt.Errorf("Must supply memcache client when using nats")
|
||||
}
|
||||
var err error
|
||||
nc, err = nats.Connect(config.NatsHost)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// (window * report rate) * number of hosts per user * number of users
|
||||
reportCacheSize := (int(config.Window.Seconds()) / 3) * 10 * 5
|
||||
reportCacheSize := (int(liveConfig.Window.Seconds()) / 3) * 10 * 5
|
||||
c := &awsCollector{
|
||||
cfg: config,
|
||||
db: dynamodb.New(session.New(config.DynamoDBConfig)),
|
||||
merger: app.NewFastMerger(),
|
||||
inProcess: newInProcessStore(reportCacheSize, config.Window+reportQuantisationInterval),
|
||||
nats: nc,
|
||||
waiters: map[watchKey]*nats.Subscription{},
|
||||
liveCollector: liveCollector{cfg: liveConfig},
|
||||
awsCfg: config,
|
||||
db: dynamodb.New(session.New(config.DynamoDBConfig)),
|
||||
inProcess: newInProcessStore(reportCacheSize, liveConfig.Window+reportQuantisationInterval),
|
||||
}
|
||||
|
||||
// If given a StoreInterval we will be storing periodically; if not we only answer queries
|
||||
if c.isCollector() {
|
||||
c.ticker = time.NewTicker(config.StoreInterval)
|
||||
go c.flushLoop()
|
||||
err := c.liveCollector.init()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.tickCallbacks = append(c.tickCallbacks, c.flushPending)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *awsCollector) flushLoop() {
|
||||
for range c.ticker.C {
|
||||
c.flushPending(context.Background())
|
||||
}
|
||||
}
|
||||
|
||||
// Range over all users (instances) that have pending reports and send to store
|
||||
func (c *awsCollector) flushPending(ctx context.Context) {
|
||||
instrument.CollectedRequest(ctx, "FlushPending", flushDuration, nil, func(ctx context.Context) error {
|
||||
@@ -245,14 +203,7 @@ func (c *awsCollector) flushPending(ctx context.Context) {
|
||||
entry := value.(*pendingEntry)
|
||||
|
||||
entry.Lock()
|
||||
rpt := entry.report
|
||||
entry.report = nil
|
||||
if entry.older == nil {
|
||||
entry.older = make([]*report.Report, c.cfg.Window/c.cfg.StoreInterval)
|
||||
} else {
|
||||
copy(entry.older[1:], entry.older) // move everything down one
|
||||
}
|
||||
entry.older[0] = rpt
|
||||
rpt := entry.older[0]
|
||||
entry.Unlock()
|
||||
|
||||
if rpt != nil {
|
||||
@@ -274,7 +225,7 @@ func (c *awsCollector) flushPending(ctx context.Context) {
|
||||
|
||||
// Close will flush pending data
|
||||
func (c *awsCollector) Close() {
|
||||
c.ticker.Stop() // note this doesn't close the chan; goroutine keeps running
|
||||
c.liveCollector.Close()
|
||||
c.flushPending(context.Background())
|
||||
}
|
||||
|
||||
@@ -288,13 +239,13 @@ func (c *awsCollector) CreateTables() error {
|
||||
return err
|
||||
}
|
||||
for _, s := range resp.TableNames {
|
||||
if *s == c.cfg.DynamoTable {
|
||||
if *s == c.awsCfg.DynamoTable {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
params := &dynamodb.CreateTableInput{
|
||||
TableName: aws.String(c.cfg.DynamoTable),
|
||||
TableName: aws.String(c.awsCfg.DynamoTable),
|
||||
AttributeDefinitions: []*dynamodb.AttributeDefinition{
|
||||
{
|
||||
AttributeName: aws.String(hourField),
|
||||
@@ -325,7 +276,7 @@ func (c *awsCollector) CreateTables() error {
|
||||
WriteCapacityUnits: aws.Int64(5),
|
||||
},
|
||||
}
|
||||
log.Infof("Creating table %s", c.cfg.DynamoTable)
|
||||
log.Infof("Creating table %s", c.awsCfg.DynamoTable)
|
||||
_, err = c.db.CreateTable(params)
|
||||
return err
|
||||
}
|
||||
@@ -342,7 +293,7 @@ func (c *awsCollector) reportKeysInRange(ctx context.Context, userid string, row
|
||||
err := instrument.TimeRequestHistogram(ctx, "DynamoDB.Query", dynamoRequestDuration, func(_ context.Context) error {
|
||||
var err error
|
||||
resp, err = c.db.Query(&dynamodb.QueryInput{
|
||||
TableName: aws.String(c.cfg.DynamoTable),
|
||||
TableName: aws.String(c.awsCfg.DynamoTable),
|
||||
KeyConditions: map[string]*dynamodb.Condition{
|
||||
hourField: {
|
||||
AttributeValueList: []*dynamodb.AttributeValue{
|
||||
@@ -423,7 +374,7 @@ func (c *awsCollector) getReports(ctx context.Context, userid string, reportKeys
|
||||
if c.cfg.MemcacheClient != nil {
|
||||
stores = append(stores, c.cfg.MemcacheClient)
|
||||
}
|
||||
stores = append(stores, c.cfg.S3Store)
|
||||
stores = append(stores, c.awsCfg.S3Store)
|
||||
|
||||
var reports []report.Report
|
||||
for _, store := range stores {
|
||||
@@ -451,23 +402,6 @@ func (c *awsCollector) getReports(ctx context.Context, userid string, reportKeys
|
||||
return reports, nil
|
||||
}
|
||||
|
||||
// process a report from a probe which may be at an older version or overloaded
|
||||
func (c *awsCollector) massageReport(userid string, report report.Report) report.Report {
|
||||
if c.cfg.MaxTopNodes > 0 {
|
||||
max := c.cfg.MaxTopNodes
|
||||
if len(report.Host.Nodes) > 1 {
|
||||
max = max * len(report.Host.Nodes) // higher limit for merged reports
|
||||
}
|
||||
var dropped []string
|
||||
report, dropped = report.DropTopologiesOver(max)
|
||||
for _, name := range dropped {
|
||||
topologiesDropped.WithLabelValues(userid, name).Inc()
|
||||
}
|
||||
}
|
||||
report = report.Upgrade()
|
||||
return report
|
||||
}
|
||||
|
||||
// If we are running as a Query service, fetch data and merge into a report
|
||||
// If we are running as a Collector and the request is for live data, merge in-memory data and return
|
||||
func (c *awsCollector) Report(ctx context.Context, timestamp time.Time) (report.Report, error) {
|
||||
@@ -583,7 +517,7 @@ func (c *awsCollector) HasHistoricReports() bool {
|
||||
// AdminSummary returns a string with some internal information about
|
||||
// the report, which may be useful to troubleshoot.
|
||||
func (c *awsCollector) AdminSummary(ctx context.Context, timestamp time.Time) (string, error) {
|
||||
span, ctx := opentracing.StartSpanFromContext(ctx, "awsCollector.Report")
|
||||
span, ctx := opentracing.StartSpanFromContext(ctx, "awsCollector.AdminSummary")
|
||||
defer span.Finish()
|
||||
userid, err := c.cfg.UserIDer(ctx)
|
||||
if err != nil {
|
||||
@@ -625,7 +559,7 @@ func calculateReportKeys(userid string, now time.Time) (string, string, string)
|
||||
|
||||
func (c *awsCollector) persistReport(ctx context.Context, userid, rowKey, colKey, reportKey string, buf []byte) error {
|
||||
// Put in S3 and cache before index, so it is fetchable before it is discoverable
|
||||
reportSize, err := c.cfg.S3Store.StoreReportBytes(ctx, reportKey, buf)
|
||||
reportSize, err := c.awsCfg.S3Store.StoreReportBytes(ctx, reportKey, buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -674,7 +608,7 @@ func (c *awsCollector) putItemInDynamo(rowKey, colKey, reportKey string) (*dynam
|
||||
)
|
||||
for {
|
||||
resp, err = c.db.PutItem(&dynamodb.PutItemInput{
|
||||
TableName: aws.String(c.cfg.DynamoTable),
|
||||
TableName: aws.String(c.awsCfg.DynamoTable),
|
||||
Item: map[string]*dynamodb.AttributeValue{
|
||||
hourField: {
|
||||
S: aws.String(rowKey),
|
||||
@@ -701,106 +635,6 @@ func (c *awsCollector) putItemInDynamo(rowKey, colKey, reportKey string) (*dynam
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (c *awsCollector) Add(ctx context.Context, rep report.Report, buf []byte) error {
|
||||
userid, err := c.cfg.UserIDer(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c.cfg.StoreInterval == 0 {
|
||||
return fmt.Errorf("--app.collector.store-interval must be non-zero")
|
||||
}
|
||||
|
||||
// Shortcut reports are published to nats but not persisted -
|
||||
// we'll get a full report from the same probe in a few seconds
|
||||
if rep.Shortcut {
|
||||
if c.nats != nil {
|
||||
_, _, reportKey := calculateReportKeys(userid, time.Now())
|
||||
_, err = c.cfg.MemcacheClient.StoreReportBytes(ctx, reportKey, buf)
|
||||
if err != nil {
|
||||
log.Warningf("Could not store shortcut %v in memcache: %v", reportKey, err)
|
||||
// No point publishing on nats if cache store failed
|
||||
return nil
|
||||
}
|
||||
err := c.nats.Publish(userid, []byte(reportKey))
|
||||
natsRequests.WithLabelValues("Publish", instrument.ErrorCode(err)).Add(1)
|
||||
if err != nil {
|
||||
log.Errorf("Error sending shortcut report: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
rep = c.massageReport(userid, rep)
|
||||
c.addToLive(ctx, userid, rep)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *awsCollector) WaitOn(ctx context.Context, waiter chan struct{}) {
|
||||
userid, err := c.cfg.UserIDer(ctx)
|
||||
if err != nil {
|
||||
log.Errorf("Error getting user id in WaitOn: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if c.nats == nil {
|
||||
return
|
||||
}
|
||||
|
||||
sub, err := c.nats.SubscribeSync(userid)
|
||||
natsRequests.WithLabelValues("SubscribeSync", instrument.ErrorCode(err)).Add(1)
|
||||
if err != nil {
|
||||
log.Errorf("Error subscribing for shortcuts: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.waitersLock.Lock()
|
||||
c.waiters[watchKey{userid, waiter}] = sub
|
||||
c.waitersLock.Unlock()
|
||||
|
||||
go func() {
|
||||
for {
|
||||
_, err := sub.NextMsg(natsTimeout)
|
||||
if err == nats.ErrTimeout {
|
||||
continue
|
||||
}
|
||||
natsRequests.WithLabelValues("NextMsg", instrument.ErrorCode(err)).Add(1)
|
||||
if err != nil {
|
||||
log.Debugf("NextMsg error: %v", err)
|
||||
return
|
||||
}
|
||||
select {
|
||||
case waiter <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (c *awsCollector) UnWait(ctx context.Context, waiter chan struct{}) {
|
||||
userid, err := c.cfg.UserIDer(ctx)
|
||||
if err != nil {
|
||||
log.Errorf("Error getting user id in WaitOn: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if c.nats == nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.waitersLock.Lock()
|
||||
key := watchKey{userid, waiter}
|
||||
sub := c.waiters[key]
|
||||
delete(c.waiters, key)
|
||||
c.waitersLock.Unlock()
|
||||
|
||||
err = sub.Unsubscribe()
|
||||
natsRequests.WithLabelValues("Unsubscribe", instrument.ErrorCode(err)).Add(1)
|
||||
if err != nil {
|
||||
log.Errorf("Error on unsubscribe: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
type inProcessStore struct {
|
||||
cache gcache.Cache
|
||||
}
|
||||
|
||||
@@ -14,14 +14,55 @@ import (
|
||||
|
||||
"context"
|
||||
|
||||
"github.com/nats-io/nats"
|
||||
"github.com/opentracing-contrib/go-stdlib/nethttp"
|
||||
opentracing "github.com/opentracing/opentracing-go"
|
||||
otlog "github.com/opentracing/opentracing-go/log"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/weaveworks/common/instrument"
|
||||
"github.com/weaveworks/common/user"
|
||||
"github.com/weaveworks/scope/app"
|
||||
"github.com/weaveworks/scope/report"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
func registerLiveCollectorMetrics() {
|
||||
prometheus.MustRegister(reportSizeHistogram)
|
||||
prometheus.MustRegister(reportsPerUser)
|
||||
prometheus.MustRegister(reportSizePerUser)
|
||||
prometheus.MustRegister(topologiesDropped)
|
||||
prometheus.MustRegister(natsRequests)
|
||||
}
|
||||
|
||||
var registerLiveCollectorMetricsOnce sync.Once
|
||||
|
||||
// LiveCollectorConfig has everything we need to make a collector for live multitenant data.
|
||||
type LiveCollectorConfig struct {
|
||||
UserIDer UserIDer
|
||||
NatsHost string
|
||||
MemcacheClient *MemcacheClient
|
||||
Window time.Duration
|
||||
TickInterval time.Duration
|
||||
MaxTopNodes int
|
||||
CollectorAddr string
|
||||
}
|
||||
|
||||
type liveCollector struct {
|
||||
cfg LiveCollectorConfig
|
||||
merger app.Merger
|
||||
pending sync.Map
|
||||
ticker *time.Ticker
|
||||
tickCallbacks []func(context.Context)
|
||||
|
||||
nats *nats.Conn
|
||||
waitersLock sync.Mutex
|
||||
waiters map[watchKey]*nats.Subscription
|
||||
|
||||
collectors []string
|
||||
lastResolved time.Time
|
||||
}
|
||||
|
||||
// if StoreInterval is set, reports are merged into here and held until flushed to store
|
||||
type pendingEntry struct {
|
||||
sync.Mutex
|
||||
@@ -29,9 +70,140 @@ type pendingEntry struct {
|
||||
older []*report.Report
|
||||
}
|
||||
|
||||
// We are building up a report in memory; merge into that and it will be saved shortly
|
||||
func NewLiveCollector(config LiveCollectorConfig) (app.Collector, error) {
|
||||
registerLiveCollectorMetricsOnce.Do(registerLiveCollectorMetrics)
|
||||
c := &liveCollector{
|
||||
cfg: config,
|
||||
}
|
||||
return c, c.init()
|
||||
}
|
||||
|
||||
func (c *liveCollector) init() error {
|
||||
var nc *nats.Conn
|
||||
if c.cfg.NatsHost != "" {
|
||||
if c.cfg.MemcacheClient == nil {
|
||||
return fmt.Errorf("Must supply memcache client when using nats")
|
||||
}
|
||||
var err error
|
||||
nc, err = nats.Connect(c.cfg.NatsHost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
c.nats = nc
|
||||
c.merger = app.NewFastMerger()
|
||||
c.waiters = make(map[watchKey]*nats.Subscription)
|
||||
if c.isCollector() {
|
||||
if c.cfg.TickInterval == 0 {
|
||||
return fmt.Errorf("--app.collector.tick-interval or --app.collector.store-interval must be non-zero for a collector")
|
||||
}
|
||||
c.ticker = time.NewTicker(c.cfg.TickInterval)
|
||||
go c.tickLoop()
|
||||
}
|
||||
c.tickCallbacks = append(c.tickCallbacks, c.bumpPending)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *liveCollector) tickLoop() {
|
||||
for range c.ticker.C {
|
||||
for _, f := range c.tickCallbacks {
|
||||
f(context.Background())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Close will close things down
|
||||
func (c *liveCollector) Close() {
|
||||
c.ticker.Stop() // note this doesn't close the chan; goroutine keeps running
|
||||
}
|
||||
|
||||
// Range over all users (instances) that have pending reports and shift the data back in the array
|
||||
func (c *liveCollector) bumpPending(ctx context.Context) {
|
||||
c.pending.Range(func(key, value interface{}) bool {
|
||||
entry := value.(*pendingEntry)
|
||||
|
||||
entry.Lock()
|
||||
rpt := entry.report
|
||||
entry.report = nil
|
||||
if entry.older == nil {
|
||||
entry.older = make([]*report.Report, c.cfg.Window/c.cfg.TickInterval)
|
||||
} else {
|
||||
copy(entry.older[1:], entry.older) // move everything down one
|
||||
}
|
||||
entry.older[0] = rpt
|
||||
entry.Unlock()
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func (c *liveCollector) HasHistoricReports() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *liveCollector) HasReports(ctx context.Context, timestamp time.Time) (bool, error) {
|
||||
userid, err := c.cfg.UserIDer(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if time.Since(timestamp) < c.cfg.Window {
|
||||
has, err := c.hasReportsFromLive(ctx, userid)
|
||||
return has, err
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (c *liveCollector) Add(ctx context.Context, rep report.Report, buf []byte) error {
|
||||
userid, err := c.cfg.UserIDer(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Shortcut reports are published to nats but not persisted -
|
||||
// we'll get a full report from the same probe in a few seconds
|
||||
if rep.Shortcut {
|
||||
if c.nats != nil {
|
||||
_, _, reportKey := calculateReportKeys(userid, time.Now())
|
||||
_, err = c.cfg.MemcacheClient.StoreReportBytes(ctx, reportKey, buf)
|
||||
if err != nil {
|
||||
log.Warningf("Could not store shortcut %v in memcache: %v", reportKey, err)
|
||||
// No point publishing on nats if cache store failed
|
||||
return nil
|
||||
}
|
||||
err := c.nats.Publish(userid, []byte(reportKey))
|
||||
natsRequests.WithLabelValues("Publish", instrument.ErrorCode(err)).Add(1)
|
||||
if err != nil {
|
||||
log.Errorf("Error sending shortcut report: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
rep = c.massageReport(userid, rep)
|
||||
c.addToLive(ctx, userid, rep)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// process a report from a probe which may be at an older version or overloaded
|
||||
func (c *liveCollector) massageReport(userid string, report report.Report) report.Report {
|
||||
if c.cfg.MaxTopNodes > 0 {
|
||||
max := c.cfg.MaxTopNodes
|
||||
if len(report.Host.Nodes) > 1 {
|
||||
max = max * len(report.Host.Nodes) // higher limit for merged reports
|
||||
}
|
||||
var dropped []string
|
||||
report, dropped = report.DropTopologiesOver(max)
|
||||
for _, name := range dropped {
|
||||
topologiesDropped.WithLabelValues(userid, name).Inc()
|
||||
}
|
||||
}
|
||||
report = report.Upgrade()
|
||||
return report
|
||||
}
|
||||
|
||||
// We are building up a report in memory; merge into that (for awsCollector it will be saved shortly)
|
||||
// NOTE: may retain a reference to rep; must not be used by caller after this.
|
||||
func (c *awsCollector) addToLive(ctx context.Context, userid string, rep report.Report) {
|
||||
func (c *liveCollector) addToLive(ctx context.Context, userid string, rep report.Report) {
|
||||
entry := &pendingEntry{}
|
||||
if e, found := c.pending.LoadOrStore(userid, entry); found {
|
||||
entry = e.(*pendingEntry)
|
||||
@@ -45,11 +217,11 @@ func (c *awsCollector) addToLive(ctx context.Context, userid string, rep report.
|
||||
entry.Unlock()
|
||||
}
|
||||
|
||||
func (c *awsCollector) isCollector() bool {
|
||||
return c.cfg.StoreInterval != 0
|
||||
func (c *liveCollector) isCollector() bool {
|
||||
return c.cfg.CollectorAddr == ""
|
||||
}
|
||||
|
||||
func (c *awsCollector) hasReportsFromLive(ctx context.Context, userid string) (bool, error) {
|
||||
func (c *liveCollector) hasReportsFromLive(ctx context.Context, userid string) (bool, error) {
|
||||
span, ctx := opentracing.StartSpanFromContext(ctx, "hasReportsFromLive")
|
||||
defer span.Finish()
|
||||
if c.isCollector() {
|
||||
@@ -91,7 +263,26 @@ func (c *awsCollector) hasReportsFromLive(ctx context.Context, userid string) (b
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (c *awsCollector) reportsFromLive(ctx context.Context, userid string) ([]report.Report, error) {
|
||||
func (c *liveCollector) Report(ctx context.Context, timestamp time.Time) (report.Report, error) {
|
||||
span, ctx := opentracing.StartSpanFromContext(ctx, "liveCollector.Report")
|
||||
defer span.Finish()
|
||||
userid, err := c.cfg.UserIDer(ctx)
|
||||
if err != nil {
|
||||
return report.MakeReport(), err
|
||||
}
|
||||
span.SetTag("userid", userid)
|
||||
var reports []report.Report
|
||||
if time.Since(timestamp) < c.cfg.Window {
|
||||
reports, err = c.reportsFromLive(ctx, userid)
|
||||
}
|
||||
if err != nil {
|
||||
return report.MakeReport(), err
|
||||
}
|
||||
span.LogFields(otlog.Int("merging", len(reports)))
|
||||
return c.merger.Merge(reports), nil
|
||||
}
|
||||
|
||||
func (c *liveCollector) reportsFromLive(ctx context.Context, userid string) ([]report.Report, error) {
|
||||
span, ctx := opentracing.StartSpanFromContext(ctx, "reportsFromLive")
|
||||
defer span.Finish()
|
||||
if c.isCollector() {
|
||||
@@ -196,3 +387,82 @@ func oneCall(ctx context.Context, endpoint, path, userid string) (io.ReadCloser,
|
||||
|
||||
return res.Body, nil
|
||||
}
|
||||
|
||||
func (c *liveCollector) WaitOn(ctx context.Context, waiter chan struct{}) {
|
||||
userid, err := c.cfg.UserIDer(ctx)
|
||||
if err != nil {
|
||||
log.Errorf("Error getting user id in WaitOn: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if c.nats == nil {
|
||||
return
|
||||
}
|
||||
|
||||
sub, err := c.nats.SubscribeSync(userid)
|
||||
natsRequests.WithLabelValues("SubscribeSync", instrument.ErrorCode(err)).Add(1)
|
||||
if err != nil {
|
||||
log.Errorf("Error subscribing for shortcuts: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.waitersLock.Lock()
|
||||
c.waiters[watchKey{userid, waiter}] = sub
|
||||
c.waitersLock.Unlock()
|
||||
|
||||
go func() {
|
||||
for {
|
||||
_, err := sub.NextMsg(natsTimeout)
|
||||
if err == nats.ErrTimeout {
|
||||
continue
|
||||
}
|
||||
natsRequests.WithLabelValues("NextMsg", instrument.ErrorCode(err)).Add(1)
|
||||
if err != nil {
|
||||
log.Debugf("NextMsg error: %v", err)
|
||||
return
|
||||
}
|
||||
select {
|
||||
case waiter <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (c *liveCollector) UnWait(ctx context.Context, waiter chan struct{}) {
|
||||
userid, err := c.cfg.UserIDer(ctx)
|
||||
if err != nil {
|
||||
log.Errorf("Error getting user id in WaitOn: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if c.nats == nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.waitersLock.Lock()
|
||||
key := watchKey{userid, waiter}
|
||||
sub := c.waiters[key]
|
||||
delete(c.waiters, key)
|
||||
c.waitersLock.Unlock()
|
||||
|
||||
err = sub.Unsubscribe()
|
||||
natsRequests.WithLabelValues("Unsubscribe", instrument.ErrorCode(err)).Add(1)
|
||||
if err != nil {
|
||||
log.Errorf("Error on unsubscribe: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// AdminSummary returns a string with some internal information about
|
||||
// the report, which may be useful to troubleshoot.
|
||||
func (c *liveCollector) AdminSummary(ctx context.Context, timestamp time.Time) (string, error) {
|
||||
span, ctx := opentracing.StartSpanFromContext(ctx, "liveCollector.AdminSummary")
|
||||
defer span.Finish()
|
||||
userid, err := c.cfg.UserIDer(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
_ = userid
|
||||
// TODO: finish implementation
|
||||
return "TODO", nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user