/** * Creates the {@link Values} attached to the outgoing tuple. * * @param route The route the message must take. * @return */ private Values createValues(MessageRoute route) { // the order here must match `declareOutputFields` return new Values(route.getMessage(), route.getTimestamp(), route.getEntity(), route.getProfileDefinition()); }
@Override public String call(MessageRoute route) { ProfilePeriod period = ProfilePeriod.fromTimestamp(route.getTimestamp(), periodDuration, periodDurationUnits); return route.getProfileDefinition().getProfile() + "-" + route.getEntity() + "-" + period.getPeriod(); } }
/** * Distribute a message along a MessageRoute. * * @param route The message route. * @param context The Stellar execution context. */ @Override public void distribute(MessageRoute route, Context context) { try { ProfileBuilder builder = getBuilder(route, context); builder.apply(route.getMessage(), route.getTimestamp()); } catch(ExecutionException e) { LOG.error("Unexpected error", e); throw new RuntimeException(e); } }
/** * Retrieves the cached ProfileBuilder that is used to build and maintain the Profile. If none exists, * one will be created and returned. * * @param route The message route. * @param context The Stellar execution context. */ public ProfileBuilder getBuilder(MessageRoute route, Context context) throws ExecutionException { ProfileConfig profile = route.getProfileDefinition(); String entity = route.getEntity(); Function<Integer, ProfileBuilder> profileCreator = (k) -> new DefaultProfileBuilder.Builder() .withDefinition(profile) .withEntity(entity) .withPeriodDurationMillis(periodDurationMillis) .withContext(context) .build(); return activeCache.get(cacheKey(profile, entity), profileCreator); }
.sorted(comparing(rt -> rt.getTimestamp())) .collect(Collectors.toList()); LOG.debug("Building a profile for group '{}' from {} message(s)", group, routes.size());
/** * Creates a route if a message is needed by a profile. * @param message The message that needs routed. * @param profile The profile that may need the message. * @return A MessageRoute if the message is needed by the profile. */ private Optional<MessageRoute> routeToProfile(JSONObject message, ProfileConfig profile, Clock clock) { Optional<MessageRoute> route = Optional.empty(); // allow the profile to access the fields defined within the message @SuppressWarnings("unchecked") final Map<String, Object> state = (Map<String, Object>) message; try { // is this message needed by this profile? if (executor.execute(profile.getOnlyif(), state, Boolean.class)) { // what time is is? could be either system or event time Optional<Long> timestamp = clock.currentTimeMillis(message); if(timestamp.isPresent()) { // what is the name of the entity in this message? String entity = executor.execute(profile.getForeach(), state, String.class); route = Optional.of(new MessageRoute(profile, entity, message, timestamp.get())); } } } catch(Throwable e) { // log an error and move on. ignore bad profiles. String msg = format("error while executing profile; profile='%s', error='%s'", profile.getProfile(), e.getMessage()); LOG.error(msg, e); } return route; }
/** * Route a message based on the Profiler configuration. * @param input The input tuple on which to anchor. * @param message The telemetry message. * @param config The Profiler configuration. */ private void routeMessage(Tuple input, JSONObject message, ProfilerConfig config) { // emit a tuple for each 'route' List<MessageRoute> routes = router.route(message, config, getStellarContext()); for (MessageRoute route : routes) { Values values = createValues(route); collector.emit(input, values); LOG.debug("Found route for message; profile={}, entity={}, timestamp={}", route.getProfileDefinition().getProfile(), route.getEntity(), route.getTimestamp()); } LOG.debug("Found {} route(s) for message", routes.size()); }
/** * Handles the processing of a single tuple. * * @param input The tuple containing a telemetry message. */ private void handleMessage(Tuple input) { // crack open the tuple JSONObject message = getField(MESSAGE_TUPLE_FIELD, input, JSONObject.class); ProfileConfig definition = getField(PROFILE_TUPLE_FIELD, input, ProfileConfig.class); String entity = getField(ENTITY_TUPLE_FIELD, input, String.class); Long timestamp = getField(TIMESTAMP_TUPLE_FIELD, input, Long.class); // keep track of time activeFlushSignal.update(timestamp); // distribute the message MessageRoute route = new MessageRoute(definition, entity, message, timestamp); synchronized (messageDistributor) { messageDistributor.distribute(route, getStellarContext()); } LOG.debug("Message distributed: profile={}, entity={}, timestamp={}", definition.getProfile(), entity, timestamp); }