blob: f8fe12d1973b1a2f4afce93cd314bbbff45275d8 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <string>
#include <vector>
#include <mesos/values.hpp>
#include <stout/check.hpp>
#include <stout/error.hpp>
#include <stout/foreach.hpp>
#include <stout/numify.hpp>
#include "common/resource_quantities.hpp"
using std::pair;
using std::string;
using std::vector;
namespace mesos {
namespace internal {
// This function tries to be consistent with `Resources::fromSimpleString()`.
// We trim the whitespace around the pair and in the number but whitespace in
// "c p us:10" are preserved and will be parsed to {"c p us", 10}.
Try<ResourceQuantities> ResourceQuantities::fromString(const string& text)
{
ResourceQuantities result;
foreach (const string& token, strings::tokenize(text, ";")) {
vector<string> pair = strings::tokenize(token, ":");
if (pair.size() != 2) {
return Error("Failed to parse '" + token + "': missing or extra ':'");
}
Try<Value> value = values::parse(pair[1]);
if (value.isError()) {
return Error(
"Failed to parse '" + pair[1] + "' to quantity: " + value.error());
}
if (value->type() != Value::SCALAR) {
return Error(
"Failed to parse '" + pair[1] + "' to quantity:"
" only scalar values are allowed");
}
if (value->scalar().value() < 0) {
return Error(
"Failed to parse '" + pair[1] + "' to quantity:"
" negative values are not allowed");
} else if (value->scalar().value() > 0) {
result.add(strings::trim(pair[0]), value->scalar());
}
// Zero value is silently dropped.
}
return result;
}
ResourceQuantities ResourceQuantities::fromScalarResources(
const Resources& resources)
{
ResourceQuantities result;
foreach (const Resource& resource, resources) {
CHECK_EQ(Value::SCALAR, resource.type()) << " Resources: " << resources;
result.add(resource.name(), resource.scalar());
}
return result;
}
ResourceQuantities ResourceQuantities::fromResources(const Resources& resources)
{
ResourceQuantities result;
foreach (const Resource& resource, resources) {
switch (resource.type()) {
case Value::SCALAR: {
result.add(resource.name(), resource.scalar());
break;
}
case Value::SET: {
result.add(resource.name(), resource.set().item_size());
break;
}
case Value::RANGES: {
foreach (const Value::Range& range, resource.ranges().range()) {
result.add(resource.name(), range.end() - range.begin() + 1);
}
break;
}
case Value::TEXT: {
LOG(FATAL) << "TEXT type resources are not valid";
}
}
}
return result;
}
ResourceQuantities::ResourceQuantities()
{
// Pre-reserve space for first-class resources.
// [cpus, disk, gpus, mem, ports]
quantities.reserve(5u);
}
ResourceQuantities::const_iterator ResourceQuantities::begin()
{
return static_cast<const std::vector<std::pair<std::string, Value::Scalar>>&>(
quantities)
.begin();
}
ResourceQuantities::const_iterator ResourceQuantities::end()
{
return static_cast<const std::vector<std::pair<std::string, Value::Scalar>>&>(
quantities)
.end();
}
Value::Scalar ResourceQuantities::get(const string& name) const
{
// Don't bother binary searching since
// we don't expect a large number of elements.
foreach (auto& quantity, quantities) {
if (quantity.first == name) {
return quantity.second;
} else if (quantity.first > name) {
// We can return early since we keep names in alphabetical order.
break;
}
}
return Value::Scalar();
}
bool ResourceQuantities::operator==(const ResourceQuantities& that) const
{
return quantities == that.quantities;
}
bool ResourceQuantities::operator!=(const ResourceQuantities& that) const
{
return quantities != that.quantities;
}
ResourceQuantities& ResourceQuantities::operator+=(
const ResourceQuantities& right)
{
size_t leftIndex = 0u;
size_t rightIndex = 0u;
// Since quantities are sorted in alphabetical order, we can walk them
// at the same time.
while (leftIndex < size() && rightIndex < right.size()) {
pair<string, Value::Scalar>& left_ = quantities.at(leftIndex);
const pair<string, Value::Scalar>& right_ = right.quantities.at(rightIndex);
if (left_.first < right_.first) {
// Item exists in the left but not in the right.
++leftIndex;
} else if (left_.first > right_.first) {
// Item exists in the right but not in the left.
// Insert absent entries in the alphabetical order.
quantities.insert(quantities.begin() + leftIndex, right_);
++leftIndex;
++rightIndex;
} else {
// Item exists in both left and right.
left_.second += right_.second;
++leftIndex;
++rightIndex;
}
}
// Copy the remaining items in `right`.
while (rightIndex < right.size()) {
quantities.push_back(right.quantities.at(rightIndex));
++rightIndex;
}
return *this;
}
ResourceQuantities& ResourceQuantities::operator-=(
const ResourceQuantities& right)
{
size_t leftIndex = 0u;
size_t rightIndex = 0u;
// Since quantities are sorted in alphabetical order, we can walk them
// at the same time.
while (leftIndex < size() && rightIndex < right.size()) {
pair<string, Value::Scalar>& left_ = quantities.at(leftIndex);
const pair<string, Value::Scalar>& right_ = right.quantities.at(rightIndex);
if (left_.first < right_.first) {
// Item exists in the left but not in the right.
++leftIndex;
} else if (left_.first > right_.first) {
// Item exists in the right but not in the left (i.e. 0), this
// would result in a negative entry, so skip it.
++rightIndex;
} else {
// Item exists in both left and right.
if (left_.second <= right_.second) {
// Drop negative and zero entries.
quantities.erase(quantities.begin() + leftIndex);
++rightIndex;
} else {
left_.second -= right_.second;
++leftIndex;
++rightIndex;
}
}
}
return *this;
}
void ResourceQuantities::add(const string& name, const Value::Scalar& scalar)
{
CHECK_GE(scalar, Value::Scalar());
// Ignore adding zero.
if (scalar == Value::Scalar()) {
return;
}
// Find the location to insert while maintaining
// alphabetical ordering. Don't bother binary searching
// since we don't expect a large number of quantities.
auto it = quantities.begin();
for (; it != quantities.end(); ++it) {
if (it->first == name) {
it->second += scalar;
return;
}
if (it->first > name) {
break;
}
}
it = quantities.insert(it, std::make_pair(name, scalar));
}
void ResourceQuantities::add(const string& name, const double value)
{
Value::Scalar scalar;
scalar.set_value(value);
add(name, std::move(scalar));
}
} // namespace internal {
} // namespace mesos {