/** * Licensed 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. */ package org.apache.aurora.scheduler.stats; import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; import javax.inject.Inject; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.Ordering; import org.apache.aurora.common.inject.TimedInterceptor.Timed; import org.apache.aurora.scheduler.resources.ResourceBag; import static java.util.Objects.requireNonNull; import static org.apache.aurora.scheduler.resources.ResourceBag.LARGE; import static org.apache.aurora.scheduler.resources.ResourceBag.MEDIUM; import static org.apache.aurora.scheduler.resources.ResourceBag.SMALL; import static org.apache.aurora.scheduler.resources.ResourceBag.XLARGE; /** * A stat computer that aggregates the number of 'slots' available at different pre-determined * slot sizes, broken down by dedicated and non-dedicated hosts. */ class SlotSizeCounter implements Runnable { private static final Map<String, ResourceBag> SLOT_SIZES = ImmutableMap.of( "small", SMALL, "medium", MEDIUM, "large", LARGE, "xlarge", XLARGE); // Ensures all counters are always initialized regardless of the Resource availability. private static final Iterable<String> SLOT_GROUPS = ImmutableList.of( getPrefix(false, false), getPrefix(false, true), getPrefix(true, false), getPrefix(true, true) ); private final Map<String, ResourceBag> slotSizes; private final MachineResourceProvider machineResourceProvider; private final CachedCounters cachedCounters; @VisibleForTesting SlotSizeCounter( final Map<String, ResourceBag> slotSizes, MachineResourceProvider machineResourceProvider, CachedCounters cachedCounters) { this.slotSizes = requireNonNull(slotSizes); this.machineResourceProvider = requireNonNull(machineResourceProvider); this.cachedCounters = requireNonNull(cachedCounters); } static class MachineResource { private final ResourceBag size; private final boolean dedicated; private final boolean revocable; MachineResource(ResourceBag size, boolean dedicated, boolean revocable) { this.size = requireNonNull(size); this.dedicated = dedicated; this.revocable = revocable; } public ResourceBag getSize() { return size; } public boolean isDedicated() { return dedicated; } public boolean isRevocable() { return revocable; } @Override public int hashCode() { return Objects.hash(size, dedicated, revocable); } @Override public boolean equals(Object obj) { if (!(obj instanceof MachineResource)) { return false; } MachineResource other = (MachineResource) obj; return Objects.equals(size, other.size) && Objects.equals(dedicated, other.dedicated) && Objects.equals(revocable, other.revocable); } } interface MachineResourceProvider { Iterable<MachineResource> get(); } @Inject SlotSizeCounter(MachineResourceProvider machineResourceProvider, CachedCounters cachedCounters) { this(SLOT_SIZES, machineResourceProvider, cachedCounters); } private static String getPrefix(boolean dedicated, boolean revocable) { String dedicatedSuffix = dedicated ? "dedicated_" : ""; String revocableSuffix = revocable ? "revocable_" : ""; return "empty_slots_" + dedicatedSuffix + revocableSuffix; } @VisibleForTesting static String getStatName(String slotName, boolean dedicated, boolean revocable) { return getPrefix(dedicated, revocable) + slotName; } private int countSlots(Iterable<ResourceBag> slots, final ResourceBag slotSize) { Function<ResourceBag, Integer> counter = machineSlack -> Ordering.natural().min( machineSlack.divide(slotSize).streamResourceVectors() .map(entry -> entry.getValue()) .collect(Collectors.toSet())) .intValue(); int sum = 0; for (int slotCount : FluentIterable.from(slots).transform(counter)) { sum += slotCount; } return sum; } private void updateStats( String name, Iterable<MachineResource> slots, ResourceBag slotSize) { ImmutableMultimap.Builder<String, ResourceBag> builder = ImmutableMultimap.builder(); for (MachineResource slot : slots) { builder.put(getStatName(name, slot.isDedicated(), slot.isRevocable()), slot.getSize()); } ImmutableMultimap<String, ResourceBag> sizes = builder.build(); for (String slotGroup : SLOT_GROUPS) { String statName = slotGroup + name; cachedCounters.get(statName).set(countSlots(sizes.get(statName), slotSize)); } } @Timed("slot_size_counter_run") @Override public void run() { Iterable<MachineResource> slots = machineResourceProvider.get(); slotSizes.entrySet().stream().forEach(e -> updateStats(e.getKey(), slots, e.getValue())); } }