Skip to content

Commit 3a81c59

Browse files
authored
Merge pull request #8 from xaionaro-go/drafts
[youtube] Investigating quota consumption mismatch
2 parents 6692e72 + 92d42b3 commit 3a81c59

5 files changed

Lines changed: 41 additions & 30 deletions

File tree

pkg/streamcontrol/youtube/chat_listener.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,8 +211,11 @@ func (l *ChatListener) streamMessages(
211211
}
212212
logger.Debugf(ctx, "gRPC stream opened successfully for liveChatId=%q", l.liveChatID)
213213

214+
// StreamList quota cost is not documented by Google (Live Streaming API
215+
// operations are absent from the quota calculator). Set to 5 as a
216+
// provisional estimate; needs further empirical calibration.
214217
if l.quotaTracker != nil {
215-
l.quotaTracker.ReportQuotaConsumption(ctx, "StreamList", 1)
218+
l.quotaTracker.ReportQuotaConsumption(ctx, "StreamList", 5)
216219
}
217220

218221
streamStartedAt := time.Now()

pkg/streamcontrol/youtube/chat_listener_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -612,8 +612,8 @@ func TestChatListenerQuotaTracking(t *testing.T) {
612612
err = listener.streamMessages(ctx, client, &pageToken)
613613
require.NoError(t, err)
614614

615-
assert.Equal(t, uint64(1), qt.UsedQuotaPoints(),
616-
"each StreamList RPC should report 1 quota point (matching api/request_count)")
615+
assert.Equal(t, uint64(5), qt.UsedQuotaPoints(),
616+
"StreamList quota is charged per gRPC stream open (provisional estimate: 5 units)")
617617
assert.Equal(t, fmt.Sprintf("token-%d", responseCount-1), pageToken)
618618
assert.Equal(t, responseCount, len(listener.messagesOutChan),
619619
"all messages should be delivered to the output channel")

pkg/streamcontrol/youtube/types/config.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ type AccountConfig struct {
2626
ClientSecret secret.String
2727
Token *OAuth2Token
2828
GCPProjectID string `yaml:"GCPProjectID,omitempty"`
29-
QuotaUsedPoints uint64 `yaml:"quota_used_points,omitempty"`
3029
QuotaUsedByOp map[string]uint64 `yaml:"quota_used_by_op,omitempty"`
3130
QuotaUsedDate string `yaml:"quota_used_date,omitempty"`
3231
CustomOAuthHandler OAuthHandler `yaml:"-"`

pkg/streamcontrol/youtube/youtube.go

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -133,13 +133,15 @@ func New(
133133
return nil, fmt.Errorf("initialization failed: %w", err)
134134
}
135135

136-
if cfg.QuotaUsedPoints > 0 && cfg.QuotaUsedDate == getQuotaCutoffDate(time.Now()) {
137-
yt.YouTubeClient.UsedPoints.Store(cfg.QuotaUsedPoints)
136+
if len(cfg.QuotaUsedByOp) > 0 && cfg.QuotaUsedDate == getQuotaCutoffDate(time.Now()) {
137+
var total uint64
138138
for op, pts := range cfg.QuotaUsedByOp {
139139
yt.YouTubeClient.UsedPointsByOp.Store(op, pts)
140+
total += pts
140141
}
142+
yt.YouTubeClient.UsedPoints.Store(total)
141143
yt.YouTubeClient.PreviousCheckAt = time.Now()
142-
logger.Infof(ctx, "loaded persisted quota: %d points for %s", cfg.QuotaUsedPoints, cfg.QuotaUsedDate)
144+
logger.Infof(ctx, "loaded persisted quota: %d points for %s", total, cfg.QuotaUsedDate)
143145
}
144146

145147
err = yt.YouTubeClient.Ping(ctx)
@@ -212,20 +214,20 @@ func (yt *YouTube) persistQuota(ctx context.Context) {
212214
return
213215
}
214216

215-
points := yt.YouTubeClient.UsedPoints.Load()
216217
date := getQuotaCutoffDate(time.Now())
217218

218219
byOp := map[string]uint64{}
220+
var total uint64
219221
yt.YouTubeClient.UsedPointsByOp.Range(func(key string, value uint64) bool {
220222
byOp[key] = value
223+
total += value
221224
return true
222225
})
223226

224-
if yt.Config.QuotaUsedPoints == points && yt.Config.QuotaUsedDate == date {
227+
if sumQuotaByOp(yt.Config.QuotaUsedByOp) == total && yt.Config.QuotaUsedDate == date {
225228
return
226229
}
227230

228-
yt.Config.QuotaUsedPoints = points
229231
yt.Config.QuotaUsedByOp = byOp
230232
yt.Config.QuotaUsedDate = date
231233

@@ -234,6 +236,14 @@ func (yt *YouTube) persistQuota(ctx context.Context) {
234236
}
235237
}
236238

239+
func sumQuotaByOp(m map[string]uint64) uint64 {
240+
var total uint64
241+
for _, v := range m {
242+
total += v
243+
}
244+
return total
245+
}
246+
237247
func (yt *YouTube) getNewToken(ctx context.Context) (_ret *oauth2.Token, _err error) {
238248
logger.Debugf(ctx, "YouTube.getNewToken")
239249
defer func() { logger.Debugf(ctx, "/YouTube.getNewToken: %v", _err) }()
@@ -1454,7 +1464,7 @@ func (yt *YouTube) GetStreamStatus(
14541464
return nil, fmt.Errorf("unable to get active broadcasts info: %w", err)
14551465
}
14561466
if len(requestStatsVideoIDs) > 0 {
1457-
videos, err := yt.YouTubeClient.Client.GetVideos(ctx, requestStatsVideoIDs, videoParts)
1467+
videos, err := yt.YouTubeClient.GetVideos(ctx, requestStatsVideoIDs, videoParts)
14581468
if err != nil {
14591469
logger.Errorf(ctx, "unable to get info for videos %v: %v", requestStatsVideoIDs, err)
14601470
} else {

pkg/streamcontrol/youtube/youtube_test.go

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,8 @@ func TestGetInfoReturnsQuotaAndListeners(t *testing.T) {
159159
assert.Empty(t, info.ChatListeners)
160160
assert.Empty(t, info.ActiveBroadcasts)
161161

162-
// Simulate some quota usage.
162+
// Simulate some quota usage via per-op tracking.
163+
yt.YouTubeClient.UsedPointsByOp.Store("GetBroadcasts", 42)
163164
yt.YouTubeClient.UsedPoints.Store(42)
164165
info = yt.GetInfo(ctx)
165166
assert.Equal(t, uint64(42), info.QuotaUsage.UsedPoints.Load())
@@ -215,18 +216,20 @@ func TestTemplateBroadcastIDSet(t *testing.T) {
215216

216217
func TestQuotaPersistenceLoad(t *testing.T) {
217218
cfg := newTestAccountConfig()
218-
cfg.QuotaUsedPoints = 500
219+
cfg.QuotaUsedByOp = map[string]uint64{
220+
"GetBroadcasts": 200,
221+
"Search": 300,
222+
}
219223
cfg.QuotaUsedDate = getQuotaCutoffDate(time.Now())
220224

221225
yt := newTestYouTubeWithConfig(t, cfg, func(AccountConfig) error { return nil })
222226

223-
// 500 loaded + 1 from Ping during New()
227+
// 500 loaded from per-op map + 1 from Ping during New()
224228
assert.Equal(t, uint64(501), yt.YouTubeClient.UsedPoints.Load())
225229
}
226230

227231
func TestQuotaPersistenceLoadPerOp(t *testing.T) {
228232
cfg := newTestAccountConfig()
229-
cfg.QuotaUsedPoints = 200
230233
cfg.QuotaUsedDate = getQuotaCutoffDate(time.Now())
231234
cfg.QuotaUsedByOp = map[string]uint64{
232235
"GetBroadcasts": 3,
@@ -248,11 +251,17 @@ func TestQuotaPersistenceLoadPerOp(t *testing.T) {
248251
updVid, ok := yt.YouTubeClient.UsedPointsByOp.Load("UpdateVideo")
249252
assert.True(t, ok)
250253
assert.Equal(t, uint64(97), updVid)
254+
255+
// Total should be derived from per-op sum + 1 from Ping during New()
256+
assert.Equal(t, uint64(201), yt.YouTubeClient.UsedPoints.Load())
251257
}
252258

253259
func TestQuotaPersistenceLoadStaleDate(t *testing.T) {
254260
cfg := newTestAccountConfig()
255-
cfg.QuotaUsedPoints = 500
261+
cfg.QuotaUsedByOp = map[string]uint64{
262+
"GetBroadcasts": 200,
263+
"Search": 300,
264+
}
256265
cfg.QuotaUsedDate = "2020-01-01"
257266

258267
yt := newTestYouTubeWithConfig(t, cfg, func(AccountConfig) error { return nil })
@@ -276,32 +285,22 @@ func TestPersistQuotaSavesOnChange(t *testing.T) {
276285
yt := newTestYouTubeWithConfig(t, cfg, saveFn)
277286
ctx := context.Background()
278287

279-
// Simulate some quota usage.
280-
yt.YouTubeClient.UsedPoints.Store(42)
281-
282-
// First persist should save.
288+
// After New(), Ping added 1 point. First explicit persist should save.
283289
saveCount = 0
284290
yt.persistQuota(ctx)
285291
assert.Equal(t, 1, saveCount)
286-
assert.Equal(t, uint64(42), savedCfg.QuotaUsedPoints)
292+
assert.Equal(t, map[string]uint64{"Ping": 1}, savedCfg.QuotaUsedByOp)
287293
assert.Equal(t, getQuotaCutoffDate(time.Now()), savedCfg.QuotaUsedDate)
288294

289295
// Second persist with same value should not save again.
290296
yt.persistQuota(ctx)
291297
assert.Equal(t, 1, saveCount, "persistQuota should skip save when values unchanged")
292298

293-
// Change the value; persist should save again.
294-
yt.YouTubeClient.UsedPoints.Store(100)
295-
yt.persistQuota(ctx)
296-
assert.Equal(t, 2, saveCount)
297-
assert.Equal(t, uint64(100), savedCfg.QuotaUsedPoints)
298-
299-
// Verify per-op breakdown is persisted.
299+
// Add more per-op usage; persist should save again.
300300
yt.YouTubeClient.UsedPointsByOp.Store("GetBroadcasts", 5)
301301
yt.YouTubeClient.UsedPointsByOp.Store("Search", 95)
302-
yt.YouTubeClient.UsedPoints.Store(200)
303302
yt.persistQuota(ctx)
304-
assert.Equal(t, 3, saveCount)
303+
assert.Equal(t, 2, saveCount)
305304
assert.Equal(t, map[string]uint64{
306305
"GetBroadcasts": 5,
307306
"Ping": 1,

0 commit comments

Comments
 (0)