@@ -196,6 +196,30 @@ func (s *usageStore) TenantActiveStreams(tenant string) iter.Seq2[string, stream
196196 }
197197}
198198
199+ func (s * usageStore ) Get (tenant string , streamHash uint64 ) (streamUsage , bool ) {
200+ var (
201+ now = s .clock .Now ()
202+ withinActiveWindow = s .newActiveWindowFunc (now )
203+ withinRateWindow = s .newRateWindowFunc (now )
204+ stream streamUsage
205+ ok bool
206+ )
207+ partition := s .getPartitionForHash (streamHash )
208+ s .withRLock (tenant , func (i int ) {
209+ stream , ok = s .get (i , tenant , partition , streamHash )
210+ if ok {
211+ ok = withinActiveWindow (stream .lastSeenAt )
212+ if ok {
213+ stream .rateBuckets = getActiveRateBuckets (
214+ stream .rateBuckets ,
215+ withinRateWindow ,
216+ )
217+ }
218+ }
219+ })
220+ return stream , ok
221+ }
222+
199223func (s * usageStore ) Update (tenant string , metadata * proto.StreamMetadata , seenAt time.Time ) error {
200224 if ! s .withinActiveWindow (seenAt .UnixNano ()) {
201225 return errOutsideActiveWindow
@@ -269,6 +293,40 @@ func (s *usageStore) UpdateCond(tenant string, metadata []*proto.StreamMetadata,
269293 return toProduce , accepted , rejected , nil
270294}
271295
296+ func (s * usageStore ) UpdateRates (tenant string , metadata []* proto.StreamMetadata , seenAt time.Time ) ([]streamUsage , error ) {
297+ if ! s .withinActiveWindow (seenAt .UnixNano ()) {
298+ return nil , errOutsideActiveWindow
299+ }
300+ var (
301+ updated = make ([]streamUsage , 0 , len (metadata ))
302+ cutoff = seenAt .Add (- s .activeWindow ).UnixNano ()
303+ )
304+ s .withLock (tenant , func (i int ) {
305+ for _ , m := range metadata {
306+ partition := s .getPartitionForHash (m .StreamHash )
307+ s .checkInitMap (i , tenant , partition , noPolicy )
308+ streams := s.stripes [i ][tenant ][partition ][noPolicy ]
309+ stream , ok := streams [m .StreamHash ]
310+
311+ // If the stream does not exist, or exists but has expired,
312+ // we need to check if accepting it would exceed the maximum
313+ // stream limit.
314+ if ! ok || stream .lastSeenAt < cutoff {
315+ if ok {
316+ // The stream has expired, delete it so it doesn't count
317+ // towards the active streams.
318+ delete (streams , m .StreamHash )
319+ }
320+ }
321+ s .updateWithBuckets (i , tenant , partition , noPolicy , m , seenAt )
322+ got , _ := s .get (i , tenant , partition , m .StreamHash )
323+ got .rateBuckets = getActiveRateBuckets (got .rateBuckets , s .withinRateWindow )
324+ updated = append (updated , got )
325+ }
326+ })
327+ return updated , nil
328+ }
329+
272330// Evict evicts all streams that have not been seen within the window.
273331func (s * usageStore ) Evict () map [string ]int {
274332 cutoff := s .clock .Now ().Add (- s .activeWindow ).UnixNano ()
@@ -362,30 +420,50 @@ func (s *usageStore) update(i int, tenant string, partition int32, policyBucket
362420 stream .hash = streamHash
363421 stream .totalSize = 0
364422 stream .policy = policyBucket
365- // stream.rateBuckets = make([]rateBucket, s.numBuckets)
366423 }
367424 seenAtUnixNano := seenAt .UnixNano ()
368425 if stream .lastSeenAt <= seenAtUnixNano {
369426 stream .lastSeenAt = seenAtUnixNano
370427 }
371- // TODO(grobinson): As mentioned above, we will come back and implement
372- // rate limits at a later date in the future.
373- // stream.totalSize += totalSize
428+ s.stripes [i ][tenant ][partition ][policyBucket ][streamHash ] = stream
429+ }
430+
431+ // Duplicate of update but also updates the rate buckets. This allows us to
432+ // isolate the changes needed to support UpdateRates RPC without affecting
433+ // the ExceedsLimits RPCs.
434+ func (s * usageStore ) updateWithBuckets (i int , tenant string , partition int32 , policyBucket string , metadata * proto.StreamMetadata , seenAt time.Time ) {
435+ s .checkInitMap (i , tenant , partition , policyBucket )
436+ streamHash := metadata .StreamHash
437+ // Get the stats for the stream.
438+ stream , ok := s.stripes [i ][tenant ][partition ][policyBucket ][streamHash ]
439+ cutoff := seenAt .Add (- s .activeWindow ).UnixNano ()
440+ // If the stream does not exist, or it has expired, reset it.
441+ if ! ok || stream .lastSeenAt < cutoff {
442+ stream .hash = streamHash
443+ stream .totalSize = 0
444+ stream .policy = policyBucket
445+ stream .rateBuckets = make ([]rateBucket , s .numBuckets )
446+ }
447+ seenAtUnixNano := seenAt .UnixNano ()
448+ if stream .lastSeenAt <= seenAtUnixNano {
449+ stream .lastSeenAt = seenAtUnixNano
450+ }
451+ stream .totalSize += metadata .TotalSize
374452 // rate buckets are implemented as a circular list. To update a rate
375453 // bucket we must first calculate the bucket index.
376- // bucketNum := seenAtUnixNano / int64(s.bucketSize)
377- // bucketIdx := int(bucketNum % int64(s.numBuckets))
378- // bucket := stream.rateBuckets[bucketIdx]
454+ bucketNum := seenAtUnixNano / int64 (s .bucketSize )
455+ bucketIdx := int (bucketNum % int64 (s .numBuckets ))
456+ bucket := stream .rateBuckets [bucketIdx ]
379457 // Once we have found the bucket, we then need to check if it is an old
380458 // bucket outside the rate window. If it is, we must reset it before we
381459 // can re-use it.
382- // bucketStart := seenAt.Truncate(s.bucketSize).UnixNano()
383- // if bucket.timestamp < bucketStart {
384- // bucket.timestamp = bucketStart
385- // bucket.size = 0
386- // }
387- // bucket.size += totalSize
388- // stream.rateBuckets[bucketIdx] = bucket
460+ bucketStart := seenAt .Truncate (s .bucketSize ).UnixNano ()
461+ if bucket .timestamp < bucketStart {
462+ bucket .timestamp = bucketStart
463+ bucket .size = 0
464+ }
465+ bucket .size += metadata . TotalSize
466+ stream .rateBuckets [bucketIdx ] = bucket
389467 s.stripes [i ][tenant ][partition ][policyBucket ][streamHash ] = stream
390468}
391469
@@ -503,3 +581,31 @@ func getActiveRateBuckets(buckets []rateBucket, withinRateWindow func(int64) boo
503581 }
504582 return result
505583}
584+
585+ // Used in tests. Is not goroutine-safe.
586+ func (s * usageStore ) getForTests (tenant string , streamHash uint64 ) (streamUsage , bool ) {
587+ partition := s .getPartitionForHash (streamHash )
588+ var result streamUsage
589+ var ok bool
590+ s .withRLock (tenant , func (i int ) {
591+ result , ok = s .get (i , tenant , partition , streamHash )
592+ })
593+ return result , ok
594+ }
595+
596+ func (s * usageStore ) get (i int , tenant string , partition int32 , streamHash uint64 ) (stream streamUsage , ok bool ) {
597+ partitions , ok := s.stripes [i ][tenant ]
598+ if ! ok {
599+ return
600+ }
601+ policies , ok := partitions [partition ]
602+ if ! ok {
603+ return
604+ }
605+ streams , ok := policies [noPolicy ]
606+ if ! ok {
607+ return
608+ }
609+ stream , ok = streams [streamHash ]
610+ return
611+ }
0 commit comments