webrtc_m130/call/adaptation/resource_adaptation_processor_unittest.cc
Henrik Boström b04b2a1719 Initial version of ResourceAdaptationProcessor and friends.
This CL adds Resource, ResourceConsumer, ResourceConsumerConfiguration
and ResourceAdaptationProcessor and implements the algorithm outlined
in
https://docs.google.com/presentation/d/13jyqCWNpIa873iKT6yDuB5Q5ma-c0CvxBpX--0tCclY/edit?usp=sharing.

Simply put, if any resource (such as "CPU") is overusing, the most
expensive consumer (e.g. encoded stream) is adapted one step down.
If all resources are underusing, the least expensive consumer is
adapted one step up.

The current resources, consumers and configurations are all fakes;
this CL has no effect on the current adaptation algorithms used in
practise, but it lays down the foundation for future work in this
area.

Bug: webrtc:11167, webrtc:11168, webrtc:11169
Change-Id: I4054ec7728a52a49e137eee6fa67fa27debd9254
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/161237
Commit-Queue: Henrik Boström <hbos@webrtc.org>
Reviewed-by: Evan Shrubsole <eshr@google.com>
Reviewed-by: Stefan Holmer <stefan@webrtc.org>
Reviewed-by: Erik Språng <sprang@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#30053}
2019-12-10 15:31:43 +00:00

262 lines
11 KiB
C++

/*
* Copyright 2019 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "call/adaptation/resource_adaptation_processor.h"
#include "absl/types/optional.h"
#include "call/adaptation/resource.h"
#include "call/adaptation/test/fake_resource.h"
#include "call/adaptation/test/fake_resource_consumer_configuration.h"
#include "test/gmock.h"
#include "test/gtest.h"
namespace webrtc {
// The indices of different resolutions returned by
// AddStandardResolutionConfigurations().
static size_t k1080pIndex = 0;
static size_t k720pIndex = 1;
static size_t k360pIndex = 2;
static size_t k180pIndex = 3;
void ConnectNeighbors(ResourceConsumerConfiguration* upper,
ResourceConsumerConfiguration* lower) {
upper->AddLowerNeighbor(lower);
lower->AddUpperNeighbor(upper);
}
std::vector<FakeResourceConsumerConfiguration*>
AddStandardResolutionConfigurations(ResourceAdaptationProcessor* processor) {
std::vector<FakeResourceConsumerConfiguration*> configs;
configs.push_back(processor->AddConfiguration(
std::make_unique<FakeResourceConsumerConfiguration>(1920, 1080, 30.0,
1.0)));
configs.push_back(processor->AddConfiguration(
std::make_unique<FakeResourceConsumerConfiguration>(1280, 720, 30.0,
1.0)));
configs.push_back(processor->AddConfiguration(
std::make_unique<FakeResourceConsumerConfiguration>(640, 360, 30.0,
1.0)));
configs.push_back(processor->AddConfiguration(
std::make_unique<FakeResourceConsumerConfiguration>(320, 180, 30.0,
1.0)));
for (size_t i = 1; i < configs.size(); ++i) {
ConnectNeighbors(configs[i - 1], configs[i]);
}
return configs;
}
TEST(ResourceAdaptationProcessorTest,
SingleStreamAndResourceDontAdaptDownWhenStable) {
ResourceAdaptationProcessor processor;
processor.AddResource(
std::make_unique<FakeResource>(ResourceUsageState::kStable));
auto resolution_configs = AddStandardResolutionConfigurations(&processor);
processor.AddConsumer(std::make_unique<ResourceConsumer>(
"OnlyStream", resolution_configs[k1080pIndex]));
EXPECT_EQ(absl::nullopt, processor.FindNextConfiguration());
}
TEST(ResourceAdaptationProcessorTest,
SingleStreamAndResourceAdaptDownOnOveruse) {
ResourceAdaptationProcessor processor;
processor.AddResource(
std::make_unique<FakeResource>(ResourceUsageState::kOveruse));
auto resolution_configs = AddStandardResolutionConfigurations(&processor);
auto* consumer = processor.AddConsumer(std::make_unique<ResourceConsumer>(
"OnlyStream", resolution_configs[k1080pIndex]));
auto next_config = processor.FindNextConfiguration();
EXPECT_TRUE(next_config.has_value());
EXPECT_EQ(consumer, next_config->consumer);
EXPECT_EQ(resolution_configs[k720pIndex], next_config->configuration);
}
TEST(ResourceAdaptationProcessorTest,
SingleStreamAndResourceDontAdaptOnOveruseIfMinResolution) {
ResourceAdaptationProcessor processor;
processor.AddResource(
std::make_unique<FakeResource>(ResourceUsageState::kOveruse));
auto resolution_configs = AddStandardResolutionConfigurations(&processor);
processor.AddConsumer(std::make_unique<ResourceConsumer>(
"OnlyStream", resolution_configs.back()));
EXPECT_EQ(absl::nullopt, processor.FindNextConfiguration());
}
TEST(ResourceAdaptationProcessorTest,
SingleStreamAndResourceAdaptUpOnUnderuse) {
ResourceAdaptationProcessor processor;
processor.AddResource(
std::make_unique<FakeResource>(ResourceUsageState::kUnderuse));
auto resolution_configs = AddStandardResolutionConfigurations(&processor);
auto* consumer = processor.AddConsumer(std::make_unique<ResourceConsumer>(
"OnlyStream", resolution_configs[k720pIndex]));
auto next_config = processor.FindNextConfiguration();
EXPECT_TRUE(next_config.has_value());
EXPECT_EQ(consumer, next_config->consumer);
EXPECT_EQ(resolution_configs[k1080pIndex], next_config->configuration);
}
TEST(ResourceAdaptationProcessorTest,
SingleStreamAndResourceDontAdaptOnUnderuseIfMaxResolution) {
ResourceAdaptationProcessor processor;
processor.AddResource(
std::make_unique<FakeResource>(ResourceUsageState::kUnderuse));
auto resolution_configs = AddStandardResolutionConfigurations(&processor);
processor.AddConsumer(std::make_unique<ResourceConsumer>(
"OnlyStream", resolution_configs[k1080pIndex]));
EXPECT_EQ(absl::nullopt, processor.FindNextConfiguration());
}
TEST(ResourceAdaptationProcessorTest,
MultipleStreamsLargestStreamGetsAdaptedDownOnOveruse) {
ResourceAdaptationProcessor processor;
processor.AddResource(
std::make_unique<FakeResource>(ResourceUsageState::kOveruse));
auto resolution_configs = AddStandardResolutionConfigurations(&processor);
auto* first_stream = processor.AddConsumer(std::make_unique<ResourceConsumer>(
"FirstStream", resolution_configs[k1080pIndex]));
auto* second_stream =
processor.AddConsumer(std::make_unique<ResourceConsumer>(
"SecondStream", resolution_configs[k720pIndex]));
// When the first stream is larger.
auto next_config = processor.FindNextConfiguration();
EXPECT_TRUE(next_config.has_value());
EXPECT_EQ(first_stream, next_config->consumer);
// When the second stream is larger.
first_stream->SetConfiguration(resolution_configs[k720pIndex]);
second_stream->SetConfiguration(resolution_configs[k1080pIndex]);
next_config = processor.FindNextConfiguration();
EXPECT_TRUE(next_config.has_value());
EXPECT_EQ(second_stream, next_config->consumer);
}
TEST(ResourceAdaptationProcessorTest,
MultipleStreamsSmallestStreamGetsAdaptedUpOnUnderuse) {
ResourceAdaptationProcessor processor;
processor.AddResource(
std::make_unique<FakeResource>(ResourceUsageState::kUnderuse));
auto resolution_configs = AddStandardResolutionConfigurations(&processor);
auto* first_stream = processor.AddConsumer(std::make_unique<ResourceConsumer>(
"FirstStream", resolution_configs[k360pIndex]));
auto* second_stream =
processor.AddConsumer(std::make_unique<ResourceConsumer>(
"SecondStream", resolution_configs[k180pIndex]));
// When the first stream is larger.
auto next_config = processor.FindNextConfiguration();
EXPECT_TRUE(next_config.has_value());
EXPECT_EQ(second_stream, next_config->consumer);
// When the second stream is larger.
first_stream->SetConfiguration(resolution_configs[k180pIndex]);
second_stream->SetConfiguration(resolution_configs[k360pIndex]);
next_config = processor.FindNextConfiguration();
EXPECT_TRUE(next_config.has_value());
EXPECT_EQ(first_stream, next_config->consumer);
}
// If both streams are equally valid to adapt down, the first one is preferred.
TEST(ResourceAdaptationProcessorTest,
MultipleStreamsAdaptFirstStreamWhenBothStreamsHaveSameCost) {
ResourceAdaptationProcessor processor;
processor.AddResource(
std::make_unique<FakeResource>(ResourceUsageState::kOveruse));
auto resolution_configs = AddStandardResolutionConfigurations(&processor);
auto* first_stream = processor.AddConsumer(std::make_unique<ResourceConsumer>(
"FirstStream", resolution_configs[k720pIndex]));
processor.AddConsumer(std::make_unique<ResourceConsumer>(
"SecondStream", resolution_configs[k720pIndex]));
auto next_config = processor.FindNextConfiguration();
EXPECT_TRUE(next_config.has_value());
EXPECT_EQ(first_stream, next_config->consumer);
}
TEST(ResourceAdaptationProcessorTest,
MultipleResourcesAdaptDownIfAnyIsOverused) {
ResourceAdaptationProcessor processor;
auto* first_resource = processor.AddResource(
std::make_unique<FakeResource>(ResourceUsageState::kOveruse));
auto* second_resource = processor.AddResource(
std::make_unique<FakeResource>(ResourceUsageState::kStable));
auto resolution_configs = AddStandardResolutionConfigurations(&processor);
processor.AddConsumer(std::make_unique<ResourceConsumer>(
"OnlyStream", resolution_configs[k1080pIndex]));
// When the first resource is overused.
EXPECT_TRUE(processor.FindNextConfiguration().has_value());
// When the second resource is overused.
first_resource->set_usage(ResourceUsageState::kStable);
second_resource->set_usage(ResourceUsageState::kOveruse);
EXPECT_TRUE(processor.FindNextConfiguration().has_value());
}
TEST(ResourceAdaptationProcessorTest,
MultipleResourcesAdaptUpIfAllAreUnderused) {
ResourceAdaptationProcessor processor;
processor.AddResource(
std::make_unique<FakeResource>(ResourceUsageState::kUnderuse));
auto* second_resource = processor.AddResource(
std::make_unique<FakeResource>(ResourceUsageState::kStable));
auto resolution_configs = AddStandardResolutionConfigurations(&processor);
processor.AddConsumer(std::make_unique<ResourceConsumer>(
"OnlyStream", resolution_configs[k720pIndex]));
// When only the first resource is underused.
EXPECT_EQ(absl::nullopt, processor.FindNextConfiguration());
// When all resources are underused.
second_resource->set_usage(ResourceUsageState::kUnderuse);
EXPECT_TRUE(processor.FindNextConfiguration().has_value());
}
TEST(ResourceAdaptationProcessorTest,
HighestPreferredNeighborIsPickedWhenAdapting) {
ResourceAdaptationProcessor processor;
// Set up the following graph, where (#) is the preference.
//
// Downward arrows Upward arrows
//
// a(1) -----> b(2) a(1) <----- b(2)
// | ^ | ^ / ^
// | / | | / |
// v / v | v |
// c(1.5) ---> d(2) c(1.5) <--- d(2)
//
auto* a = processor.AddConfiguration(
std::make_unique<FakeResourceConsumerConfiguration>(1, 1, 1, 1.0));
auto* b = processor.AddConfiguration(
std::make_unique<FakeResourceConsumerConfiguration>(1, 1, 1, 2.0));
auto* c = processor.AddConfiguration(
std::make_unique<FakeResourceConsumerConfiguration>(1, 1, 1, 1.5));
auto* d = processor.AddConfiguration(
std::make_unique<FakeResourceConsumerConfiguration>(1, 1, 1, 2.0));
ConnectNeighbors(a, b);
ConnectNeighbors(a, c);
ConnectNeighbors(b, d);
ConnectNeighbors(c, b);
ConnectNeighbors(c, d);
auto* resource = processor.AddResource(
std::make_unique<FakeResource>(ResourceUsageState::kOveruse));
auto* consumer = processor.AddConsumer(
std::make_unique<ResourceConsumer>("OnlyStream", a));
// We should expect adapting down: a -> b -> d
EXPECT_EQ(b, processor.FindNextConfiguration()->configuration);
consumer->SetConfiguration(b);
EXPECT_EQ(d, processor.FindNextConfiguration()->configuration);
consumer->SetConfiguration(d);
// We should expect to adapt up: d -> b -> c -> a
resource->set_usage(ResourceUsageState::kUnderuse);
EXPECT_EQ(b, processor.FindNextConfiguration()->configuration);
consumer->SetConfiguration(b);
EXPECT_EQ(c, processor.FindNextConfiguration()->configuration);
consumer->SetConfiguration(c);
EXPECT_EQ(a, processor.FindNextConfiguration()->configuration);
}
} // namespace webrtc