com.cloudera.gertrude.space.LayerBuilder.java Source code

Java tutorial

Introduction

Here is the source code for com.cloudera.gertrude.space.LayerBuilder.java

Source

/**
 * Copyright (c) 2013, Cloudera, Inc. All Rights Reserved.
 *
 * Cloudera, Inc. 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
 *
 * This software 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 com.cloudera.gertrude.space;

import com.cloudera.gertrude.DiversionCriterion;
import com.cloudera.gertrude.Layer;
import com.cloudera.gertrude.Segment;
import com.cloudera.gertrude.calculate.FlagValueCalculatorImpl;
import com.cloudera.gertrude.calculate.FlagValueOverride;
import com.google.common.base.Function;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;
import com.google.common.hash.Hashing;

import java.util.Map;
import java.util.NavigableMap;
import java.util.Set;
import java.util.SortedSet;

final class LayerBuilder {

    private final LayerInfo info;
    private final SetMultimap<String, Integer> flagOverridesByExperiment = HashMultimap.create();
    private final Map<Integer, ExperimentInfo> experiments = Maps.newHashMap();
    private final Map<Integer, SegmentInfo> domains = Maps.newHashMap();
    private final Map<Integer, Map<Integer, Integer>> allocatedBucketsByDiversion = Maps.newHashMap();
    private final ExperimentSpaceBuilder parent;

    LayerBuilder(ExperimentSpaceBuilder parent, LayerInfo info) {
        this.parent = parent;
        this.info = info;
    }

    boolean isLaunchLayer() {
        return info.isLaunchLayer();
    }

    int getDomainId() {
        return info.getDomainId();
    }

    void addDomain(SegmentInfo domain) {
        this.domains.put(domain.getId(), domain);
    }

    void addExperiment(ExperimentInfo info) {
        this.experiments.put(info.getId(), info);
    }

    Layer build(Map<Integer, Segment> finalSegments) {
        ImmutableMap.Builder<Integer, NavigableMap<Integer, Set<Segment>>> b = ImmutableMap.builder();
        Set<Integer> allSegmentIds = Sets.newHashSet();
        for (Map.Entry<Integer, Map<Integer, Integer>> e : allocatedBucketsByDiversion.entrySet()) {
            int diversionId = e.getKey();
            NavigableMap<Integer, Set<Segment>> bucketToSegment = Maps.newTreeMap();
            for (Map.Entry<Integer, Integer> ee : e.getValue().entrySet()) {
                int bucketId = ee.getKey();
                int segmentId = ee.getValue();
                Segment segment = experiments.get(segmentId);
                if (segment == null) {
                    segment = parent.getDomain(domains.get(segmentId), finalSegments);
                } else {
                    allSegmentIds.add(segmentId);
                }
                Set<Segment> segments = bucketToSegment.get(bucketId);
                if (segments == null) {
                    segments = Sets.newHashSet();
                    bucketToSegment.put(bucketId, segments);
                }
                segments.add(segment);
                finalSegments.put(segmentId, segment);
            }
            b.put(diversionId, bucketToSegment);
        }
        // TODO: check the collision properties of faster impls (e.g., MurmurHash3)
        return new LayerImpl(info, Hashing.md5(), allSegmentIds, b.build(), parent.getRandom());
    }

    void allocateBuckets(int segmentId, DiversionCriterion criteria, SortedSet<Integer> buckets)
            throws ValidationException {
        if (buckets.first() < 0) {
            throw new ValidationException("Negative buckets in segment: " + segmentId);
        }
        if (buckets.last() >= criteria.getNumBuckets()) {
            throw new ValidationException("Buckets in segment " + segmentId + " exceeds max buckets for criteria");
        }
        Map<Integer, Integer> allocatedBuckets = allocatedBucketsByDiversion.get(criteria.getId());
        if (allocatedBuckets == null) {
            allocatedBuckets = Maps.newHashMap();
            allocatedBucketsByDiversion.put(criteria.getId(), allocatedBuckets);
        }
        Set<Integer> conflict = Sets.intersection(buckets, allocatedBuckets.keySet());
        if (!conflict.isEmpty()) {
            StringBuilder sb = new StringBuilder("Overlapping buckets for segment ").append(segmentId)
                    .append(" and segment(s) ");
            Set<Integer> conflictSegments = Sets.newHashSet();
            for (Integer bucket : conflict) {
                conflictSegments.add(allocatedBuckets.get(bucket));
            }
            sb.append(conflictSegments).append(" (Buckets: ").append(conflict).append(')');
            throw new ValidationException(sb.toString());
        }
        for (Integer bucket : buckets) {
            allocatedBuckets.put(bucket, segmentId);
        }
    }

    private Iterable<ExperimentInfo> findExperimentsThatOverride(String name) {
        return Iterables.transform(flagOverridesByExperiment.get(name), new Function<Integer, ExperimentInfo>() {
            @Override
            public ExperimentInfo apply(Integer experimentId) {
                return experiments.get(experimentId);
            }
        });
    }

    FlagValueData checkOverrides(int experimentId, Map<String, FlagValueOverride<Object>> overrides)
            throws ValidationException {
        ImmutableMap.Builder<String, FlagValueCalculatorImpl<Object>> bb = ImmutableMap.builder();
        Map<Integer, Map<String, FlagValueCalculatorImpl<Object>>> lb = Maps.newHashMap();
        for (Map.Entry<String, FlagValueOverride<Object>> e : overrides.entrySet()) {
            String name = e.getKey();
            FlagValueOverride<Object> valueOverride = e.getValue();
            FlagValueCalculatorImpl<Object> base = parent.getFlagDefinition(name);
            if (base == null) {
                throw new ValidationException(
                        "Experiment " + experimentId + " overrides non-existent flag " + name);
            }
            bb.put(name, valueOverride.apply(base));
            flagOverridesByExperiment.put(name, experimentId);

            //TODO: experiments can share experiment flags if they are in separate domains within a single layer
            Set<Integer> layerIds = parent.getFlagLayerIds(name);
            if (!layerIds.contains(info.getLayerId())) {
                if (!layerIds.isEmpty()) {
                    if (isLaunchLayer()) {
                        throw new ValidationException(String.format(
                                "Experiments in multiple launch layers override flag %s: layer %d (experiment %d) and layer(s) %s",
                                name, info.getLayerId(), experimentId, layerIds.toString()));
                    } else {
                        for (Integer otherLayerId : layerIds) {
                            LayerBuilder other = parent.getLayer(otherLayerId);
                            if (other.isLaunchLayer()) {
                                for (ExperimentInfo info : other.findExperimentsThatOverride(name)) {
                                    Map<String, FlagValueCalculatorImpl<Object>> llOverride = lb.get(info.getId());
                                    if (llOverride == null) {
                                        llOverride = Maps.newHashMap();
                                        lb.put(info.getId(), llOverride);
                                    }
                                    llOverride.put(name,
                                            valueOverride.apply(parent.getFlagFromExperiment(info.getId(), name)));
                                }
                            } else if (parent.areLayersOverlapping(info.getLayerId(), otherLayerId)) {
                                throw new ValidationException(String.format(
                                        "Experiments across overlapping layers override flag %s: layer %d (experiment %d) and layer(s) %s",
                                        name, info.getLayerId(), experimentId, layerIds.toString()));
                            }
                        }
                    }
                }
                parent.addFlagLayerAssignment(name, info.getLayerId());
            }

        }
        return new FlagValueData(bb.build(), lb);
    }
}