openvibers/openvibe.media Commits
Emit placement provider events under the contracted media namespace (#19)
Changes
@@@ -161,14 +161,14 @@ request/playback → telemetry → Events → {Hotness, Health, Cost} engines(minutes–hours) ⊂ cost (hours–days). Each outer loop only constrains the inner (budgets bound placement; placementbounds routes); every loop uses hysteresis and rate limits.- **Events emitted:** `media.object.hot`, `media.replica.requested|ready|draining|evicted`, `media.variant.requested|ready`,-- `media.region.hot`, `media.delivery.surge|degraded`, `provider.health.degraded`, `provider.capacity.warning`,-- `provider.cost.threshold`.++ `media.region.hot`, `media.delivery.surge|degraded`, `media.provider.health_degraded`, `media.provider.capacity_warning`,++ `media.provider.cost_threshold`.## 9. Cost truth`media.cost_tiers` holds list prices; estimates drive decisions; **provider bills calibrate them**: R2 analytics(GraphQL), B2 usage reports, Bunny statistics API are imported daily, compared with the estimate, and the gap is a--metric. Budgets per class and provider with a forecast; `provider.cost.threshold` at 90 % shifts non-critical traffic.++metric. Budgets per class and provider with a forecast; `media.provider.cost_threshold` at 90 % shifts non-critical traffic.## 10. Build order@@@ -46,7 +46,7 @@ const TYPES = {const PLACEMENT_TYPES = new Set(['media.replica.requested', 'media.replica.ready', 'media.replica.draining', 'media.replica.evicted','media.object.hot', 'media.region.hot', 'media.delivery.surge', 'media.delivery.degraded',-- 'provider.health.degraded', 'provider.capacity.warning', 'provider.cost.threshold',++ 'media.provider.health_degraded', 'media.provider.capacity_warning', 'media.provider.cost_threshold',]);// media.job.<transition>: progress is low priority, the outcome (and a proposal waiting for its owner) important.@@@ -120,7 +120,7 @@ function setCapabilities(name, caps) {const prev = classesOf(old);const next = classesOf(caps);if (next.length < prev.length) {-- void announceProviderEvent('provider.capacity.warning', name, { classes: next, previous_classes: prev });++ void announceProviderEvent('media.provider.capacity_warning', name, { classes: next, previous_classes: prev });}}state.capabilities[name] = caps;@@@ -249,7 +249,7 @@ async function liveHealth(name) {const wasHealthy = state.lastHealthCheck[name] != null && state.lastHealthError[name] == null;state.lastHealthCheck[name] = Date.now();state.lastHealthError[name] = err.message || String(err);-- if (wasHealthy) void announceProviderEvent('provider.health.degraded', name, { healthy: false, last_error: state.lastHealthError[name] });++ if (wasHealthy) void announceProviderEvent('media.provider.health_degraded', name, { healthy: false, last_error: state.lastHealthError[name] });const ms = state.lastHealthCheck[name] - started;observe(name, 'head', false, ms);return { configured: true, healthy: false, lastCheck: state.lastHealthCheck[name], error: state.lastHealthError[name], latencyMs: ms };@@@ -146,15 +146,15 @@ const path = require('path');assert.strictEqual((await providers.liveHealth('r2')).healthy, true);healthy = false;assert.strictEqual((await providers.liveHealth('r2')).healthy, false);-- await waitFor(async () => (await rowsFor('r2')).some((e) => e.event_type === 'provider.health.degraded'));++ await waitFor(async () => (await rowsFor('r2')).some((e) => e.event_type === 'media.provider.health_degraded'));await providers.liveHealth('r2');-- assert.strictEqual((await rowsFor('r2')).filter((e) => e.event_type === 'provider.health.degraded').length, 1);++ assert.strictEqual((await rowsFor('r2')).filter((e) => e.event_type === 'media.provider.health_degraded').length, 1);providers._setCapabilities('r2', { passed: true, capabilities: { range: true, range_206: true } });providers._setCapabilities('r2', { passed: true, capabilities: { range: true, range_206: false } });-- await waitFor(async () => (await rowsFor('r2')).some((e) => e.event_type === 'provider.capacity.warning'));++ await waitFor(async () => (await rowsFor('r2')).some((e) => e.event_type === 'media.provider.capacity_warning'));providers._setCapabilities('r2', { passed: true, capabilities: { range: true, range_206: false } });rows = await rowsFor('r2');-- assert.deepStrictEqual(rows.map((e) => e.event_type), ['provider.health.degraded', 'provider.capacity.warning']);++ assert.deepStrictEqual(rows.map((e) => e.event_type), ['media.provider.health_degraded', 'media.provider.capacity_warning']);assert.deepStrictEqual(rows.map((e) => e.subject), [{ type: 'provider', id: 'r2' }, { type: 'provider', id: 'r2' }]);assert.deepStrictEqual(rows[1].payload, { app_id: null, provider: 'r2', classes: [], previous_classes: ['online-hot'] });console.log('✅ provider transitions: one health degradation and one class-loss warning');
@@ -161,14 +161,14 @@ request/playback → telemetry → Events → {Hotness, Health, Cost} engines
@@ -161,14 +161,14 @@ request/playback → telemetry → Events → {Hotness, Health, Cost} engines
(minutes–hours) ⊂ cost (hours–days). Each outer loop only constrains the inner (budgets bound placement; placement
(minutes–hours) ⊂ cost (hours–days). Each outer loop only constrains the inner (budgets bound placement; placement
bounds routes); every loop uses hysteresis and rate limits.
bounds routes); every loop uses hysteresis and rate limits.
- **Events emitted:** `media.object.hot`, `media.replica.requested|ready|draining|evicted`, `media.variant.requested|ready`,
- **Events emitted:** `media.object.hot`, `media.replica.requested|ready|draining|evicted`, `media.variant.requested|ready`,
- `media.region.hot`, `media.delivery.surge|degraded`, `provider.health.degraded`, `provider.capacity.warning`,
- `provider.cost.threshold`.
+ `media.region.hot`, `media.delivery.surge|degraded`, `media.provider.health_degraded`, `media.provider.capacity_warning`,
+ `media.provider.cost_threshold`.
## 9. Cost truth
## 9. Cost truth
`media.cost_tiers` holds list prices; estimates drive decisions; **provider bills calibrate them**: R2 analytics
`media.cost_tiers` holds list prices; estimates drive decisions; **provider bills calibrate them**: R2 analytics
(GraphQL), B2 usage reports, Bunny statistics API are imported daily, compared with the estimate, and the gap is a
(GraphQL), B2 usage reports, Bunny statistics API are imported daily, compared with the estimate, and the gap is a
-metric. Budgets per class and provider with a forecast; `provider.cost.threshold` at 90 % shifts non-critical traffic.
+metric. Budgets per class and provider with a forecast; `media.provider.cost_threshold` at 90 % shifts non-critical traffic.
## 10. Build order
## 10. Build order
@@ -46,7 +46,7 @@ const TYPES = {
@@ -46,7 +46,7 @@ const TYPES = {
const PLACEMENT_TYPES = new Set([
const PLACEMENT_TYPES = new Set([
'media.replica.requested', 'media.replica.ready', 'media.replica.draining', 'media.replica.evicted',
'media.replica.requested', 'media.replica.ready', 'media.replica.draining', 'media.replica.evicted',
'media.object.hot', 'media.region.hot', 'media.delivery.surge', 'media.delivery.degraded',
'media.object.hot', 'media.region.hot', 'media.delivery.surge', 'media.delivery.degraded',
- 'provider.health.degraded', 'provider.capacity.warning', 'provider.cost.threshold',
+ 'media.provider.health_degraded', 'media.provider.capacity_warning', 'media.provider.cost_threshold',
]);
]);
// media.job.<transition>: progress is low priority, the outcome (and a proposal waiting for its owner) important.
// media.job.<transition>: progress is low priority, the outcome (and a proposal waiting for its owner) important.
@@ -120,7 +120,7 @@ function setCapabilities(name, caps) {
@@ -120,7 +120,7 @@ function setCapabilities(name, caps) {
const prev = classesOf(old);
const prev = classesOf(old);
const next = classesOf(caps);
const next = classesOf(caps);
if (next.length < prev.length) {
if (next.length < prev.length) {
- void announceProviderEvent('provider.capacity.warning', name, { classes: next, previous_classes: prev });
+ void announceProviderEvent('media.provider.capacity_warning', name, { classes: next, previous_classes: prev });
}
}
}
}
state.capabilities[name] = caps;
state.capabilities[name] = caps;
@@ -249,7 +249,7 @@ async function liveHealth(name) {
@@ -249,7 +249,7 @@ async function liveHealth(name) {
const wasHealthy = state.lastHealthCheck[name] != null && state.lastHealthError[name] == null;
const wasHealthy = state.lastHealthCheck[name] != null && state.lastHealthError[name] == null;
state.lastHealthCheck[name] = Date.now();
state.lastHealthCheck[name] = Date.now();
state.lastHealthError[name] = err.message || String(err);
state.lastHealthError[name] = err.message || String(err);
- if (wasHealthy) void announceProviderEvent('provider.health.degraded', name, { healthy: false, last_error: state.lastHealthError[name] });
+ if (wasHealthy) void announceProviderEvent('media.provider.health_degraded', name, { healthy: false, last_error: state.lastHealthError[name] });
const ms = state.lastHealthCheck[name] - started;
const ms = state.lastHealthCheck[name] - started;
observe(name, 'head', false, ms);
observe(name, 'head', false, ms);
return { configured: true, healthy: false, lastCheck: state.lastHealthCheck[name], error: state.lastHealthError[name], latencyMs: ms };
return { configured: true, healthy: false, lastCheck: state.lastHealthCheck[name], error: state.lastHealthError[name], latencyMs: ms };
@@ -146,15 +146,15 @@ const path = require('path');
@@ -146,15 +146,15 @@ const path = require('path');
assert.strictEqual((await providers.liveHealth('r2')).healthy, true);
assert.strictEqual((await providers.liveHealth('r2')).healthy, true);
healthy = false;
healthy = false;
assert.strictEqual((await providers.liveHealth('r2')).healthy, false);
assert.strictEqual((await providers.liveHealth('r2')).healthy, false);
- await waitFor(async () => (await rowsFor('r2')).some((e) => e.event_type === 'provider.health.degraded'));
+ await waitFor(async () => (await rowsFor('r2')).some((e) => e.event_type === 'media.provider.health_degraded'));
await providers.liveHealth('r2');
await providers.liveHealth('r2');
- assert.strictEqual((await rowsFor('r2')).filter((e) => e.event_type === 'provider.health.degraded').length, 1);
+ assert.strictEqual((await rowsFor('r2')).filter((e) => e.event_type === 'media.provider.health_degraded').length, 1);
providers._setCapabilities('r2', { passed: true, capabilities: { range: true, range_206: true } });
providers._setCapabilities('r2', { passed: true, capabilities: { range: true, range_206: true } });
providers._setCapabilities('r2', { passed: true, capabilities: { range: true, range_206: false } });
providers._setCapabilities('r2', { passed: true, capabilities: { range: true, range_206: false } });
- await waitFor(async () => (await rowsFor('r2')).some((e) => e.event_type === 'provider.capacity.warning'));
+ await waitFor(async () => (await rowsFor('r2')).some((e) => e.event_type === 'media.provider.capacity_warning'));
providers._setCapabilities('r2', { passed: true, capabilities: { range: true, range_206: false } });
providers._setCapabilities('r2', { passed: true, capabilities: { range: true, range_206: false } });
rows = await rowsFor('r2');
rows = await rowsFor('r2');
- assert.deepStrictEqual(rows.map((e) => e.event_type), ['provider.health.degraded', 'provider.capacity.warning']);
+ assert.deepStrictEqual(rows.map((e) => e.event_type), ['media.provider.health_degraded', 'media.provider.capacity_warning']);
assert.deepStrictEqual(rows.map((e) => e.subject), [{ type: 'provider', id: 'r2' }, { type: 'provider', id: 'r2' }]);
assert.deepStrictEqual(rows.map((e) => e.subject), [{ type: 'provider', id: 'r2' }, { type: 'provider', id: 'r2' }]);
assert.deepStrictEqual(rows[1].payload, { app_id: null, provider: 'r2', classes: [], previous_classes: ['online-hot'] });
assert.deepStrictEqual(rows[1].payload, { app_id: null, provider: 'r2', classes: [], previous_classes: ['online-hot'] });
console.log('✅ provider transitions: one health degradation and one class-loss warning');
console.log('✅ provider transitions: one health degradation and one class-loss warning');