org.apache.metron.profiler.ProfileBuilder.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.metron.profiler.ProfileBuilder.java

Source

/*
 *
 *  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.
 *
 */

package org.apache.metron.profiler;

import com.google.common.collect.ImmutableMap;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.curator.framework.CuratorFramework;
import org.apache.metron.common.configuration.profiler.ProfileConfig;
import org.apache.metron.common.configuration.profiler.ProfileResult;
import org.apache.metron.common.dsl.Context;
import org.apache.metron.common.dsl.ParseException;
import org.apache.metron.common.dsl.StellarFunctions;
import org.apache.metron.profiler.clock.Clock;
import org.apache.metron.profiler.clock.WallClock;
import org.apache.metron.profiler.stellar.DefaultStellarExecutor;
import org.apache.metron.profiler.stellar.StellarExecutor;
import org.json.simple.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static java.lang.String.format;

/**
 * Responsible for building and maintaining a Profile.
 *
 * One or more messages are applied to the Profile with `apply` and a profile measurement is
 * produced by calling `flush`.
 *
 * Any one instance is responsible only for building the profile for a specific [profile, entity]
 * pairing.  There will exist many instances, one for each [profile, entity] pair that exists
 * within the incoming telemetry data applied to the profile.
 */
public class ProfileBuilder implements Serializable {

    protected static final Logger LOG = LoggerFactory.getLogger(ProfileBuilder.class);

    /**
     * The name of the profile.
     */
    private String profileName;

    /**
     * The name of the entity.
     */
    private String entity;

    /**
     * The definition of the Profile that the bolt is building.
     */
    private ProfileConfig definition;

    /**
     * Executes Stellar code and maintains state across multiple invocations.
     */
    private StellarExecutor executor;

    /**
     * Has the profile been initialized?
     */
    private boolean isInitialized;

    /**
     * The duration of each period in milliseconds.
     */
    private long periodDurationMillis;

    /**
     * A clock is used to tell time; imagine that.
     */
    private Clock clock;

    /**
     * Use the ProfileBuilder.Builder to create a new ProfileBuilder.
     */
    private ProfileBuilder(ProfileConfig definition, String entity, Clock clock, long periodDurationMillis,
            CuratorFramework client, Map<String, Object> global) {

        this.isInitialized = false;
        this.definition = definition;
        this.profileName = definition.getProfile();
        this.entity = entity;
        this.clock = clock;
        this.periodDurationMillis = periodDurationMillis;
        this.executor = new DefaultStellarExecutor();
        Context context = new Context.Builder().with(Context.Capabilities.ZOOKEEPER_CLIENT, () -> client)
                .with(Context.Capabilities.GLOBAL_CONFIG, () -> global)
                .with(Context.Capabilities.STELLAR_CONFIG, () -> global).build();
        StellarFunctions.initialize(context);
        this.executor.setContext(context);
    }

    /**
     * Apply a message to the profile.
     * @param message The message to apply.
     */
    @SuppressWarnings("unchecked")
    public void apply(JSONObject message) {

        if (!isInitialized()) {
            assign(definition.getInit(), message, "init");
            isInitialized = true;
        }

        assign(definition.getUpdate(), message, "update");
    }

    /**
     * Flush the Profile.
     *
     * Completes and emits the ProfileMeasurement.  Clears all state in preparation for
     * the next window period.
     *
     * @return Returns the completed profile measurement.
     */
    public ProfileMeasurement flush() {
        LOG.debug("Flushing profile: profile={}, entity={}", profileName, entity);

        // execute the 'profile' expression(s)
        @SuppressWarnings("unchecked")
        Object profileValue = execute(definition.getResult().getProfileExpressions().getExpression(),
                "result/profile");

        // execute the 'triage' expression(s)
        Map<String, Object> triageValues = definition.getResult().getTriageExpressions().getExpressions().entrySet()
                .stream().collect(Collectors.toMap(e -> e.getKey(), e -> execute(e.getValue(), "result/triage")));

        // execute the 'groupBy' expression(s) - can refer to value of 'result' expression
        List<Object> groups = execute(definition.getGroupBy(), ImmutableMap.of("result", profileValue), "groupBy");

        isInitialized = false;
        return new ProfileMeasurement().withProfileName(profileName).withEntity(entity).withGroups(groups)
                .withPeriod(clock.currentTimeMillis(), periodDurationMillis, TimeUnit.MILLISECONDS)
                .withProfileValue(profileValue).withTriageValues(triageValues).withDefinition(definition);
    }

    /**
     * Executes an expression contained within the profile definition.
     * @param expression The expression to execute.
     * @param transientState Additional transient state provided to the expression.
     * @param expressionType The type of expression; init, update, result.  Provides additional context if expression execution fails.
     * @return The result of executing the expression.
     */
    private Object execute(String expression, Map<String, Object> transientState, String expressionType) {
        Object result = null;

        List<Object> allResults = execute(Collections.singletonList(expression), transientState, expressionType);
        if (allResults.size() > 0) {
            result = allResults.get(0);
        }

        return result;
    }

    /**
     * Executes an expression contained within the profile definition.
     * @param expression The expression to execute.
     * @param expressionType The type of expression; init, update, result.  Provides additional context if expression execution fails.
     * @return The result of executing the expression.
     */
    private Object execute(String expression, String expressionType) {
        return execute(expression, Collections.emptyMap(), expressionType);
    }

    /**
     * Executes a set of expressions whose results need to be assigned to a variable.
     * @param expressions Maps the name of a variable to the expression whose result should be assigned to it.
     * @param transientState Additional transient state provided to the expression.
     * @param expressionType The type of expression; init, update, result.  Provides additional context if expression execution fails.
     */
    private void assign(Map<String, String> expressions, Map<String, Object> transientState,
            String expressionType) {
        try {

            // execute each of the 'update' expressions
            MapUtils.emptyIfNull(expressions).forEach((var, expr) -> executor.assign(var, expr, transientState));

        } catch (ParseException e) {

            // make it brilliantly clear that one of the 'update' expressions is bad
            String msg = format("Bad '%s' expression: %s, profile=%s, entity=%s", expressionType, e.getMessage(),
                    profileName, entity);
            throw new ParseException(msg, e);
        }
    }

    /**
     * Executes the expressions contained within the profile definition.
     * @param expressions A list of expressions to execute.
     * @param transientState Additional transient state provided to the expressions.
     * @param expressionType The type of expression; init, update, result.  Provides additional context if expression execution fails.
     * @return The result of executing each expression.
     */
    private List<Object> execute(List<String> expressions, Map<String, Object> transientState,
            String expressionType) {
        List<Object> results = new ArrayList<>();

        try {
            ListUtils.emptyIfNull(expressions)
                    .forEach((expr) -> results.add(executor.execute(expr, transientState, Object.class)));

        } catch (Throwable e) {
            String msg = format("Bad '%s' expression: %s, profile=%s, entity=%s", expressionType, e.getMessage(),
                    profileName, entity);
            throw new ParseException(msg, e);
        }

        return results;
    }

    /**
     * Returns the current value of a variable.
     * @param variable The name of the variable.
     */
    public Object valueOf(String variable) {
        return executor.getState().get(variable);
    }

    public boolean isInitialized() {
        return isInitialized;
    }

    public ProfileConfig getDefinition() {
        return definition;
    }

    /**
     * A builder used to construct a new ProfileBuilder.
     */
    public static class Builder {

        private ProfileConfig definition;
        private String entity;
        private long periodDurationMillis;
        private CuratorFramework zookeeperClient;
        private Map<String, Object> global;
        private Clock clock = new WallClock();

        public Builder withClock(Clock clock) {
            this.clock = clock;
            return this;
        }

        /**
         * @param definition The profiler definition.
         */
        public Builder withDefinition(ProfileConfig definition) {
            this.definition = definition;
            return this;
        }

        /**
         * @param entity The name of the entity
         */
        public Builder withEntity(String entity) {
            this.entity = entity;
            return this;
        }

        /**
         * @param duration The duration of each profile period.
         * @param units The units used to specify the duration of the profile period.
         */
        public Builder withPeriodDuration(long duration, TimeUnit units) {
            this.periodDurationMillis = units.toMillis(duration);
            return this;
        }

        /**
         * @param millis The duration of each profile period in milliseconds.
         */
        public Builder withPeriodDurationMillis(long millis) {
            this.periodDurationMillis = millis;
            return this;
        }

        /**
         * @param zookeeperClient The zookeeper client.
         */
        public Builder withZookeeperClient(CuratorFramework zookeeperClient) {
            this.zookeeperClient = zookeeperClient;
            return this;
        }

        /**
         * @param global The global configuration.
         */
        public Builder withGlobalConfiguration(Map<String, Object> global) {
            // TODO how does the profile builder ever seen a global that has been update in zookeeper?
            this.global = global;
            return this;
        }

        /**
         * Construct a ProfileBuilder.
         */
        public ProfileBuilder build() {

            if (definition == null) {
                throw new IllegalArgumentException("missing profiler definition; got null");
            }
            if (StringUtils.isEmpty(entity)) {
                throw new IllegalArgumentException(format("missing entity name; got '%s'", entity));
            }

            return new ProfileBuilder(definition, entity, clock, periodDurationMillis, zookeeperClient, global);
        }
    }
}