/* * Strongback * Copyright 2015, Strongback and individual contributors by the @authors tag. * See the COPYRIGHT.txt in the distribution for a full listing of individual * contributors. * * Licensed under the MIT License; you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://opensource.org/licenses/MIT * 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.strongback; import java.util.Queue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicInteger; import org.strongback.annotation.ThreadSafe; import org.strongback.components.Clock; /** * A thread-safe and lock-free {@link EventRecorder} implementation that records all events to a thread-safe queue and then when * {@link #execute(long) executed} writes all enqueued events in the same order as received. The {@link AsyncEventRecorder} is * {@link Executable}, and is designed to be {@link Executor#register(Executable) registered} with an {@link Executor} to * automatically and periodically write the enqueued events to the given {@link EventWriter}. * * @author Randall Hauch */ @ThreadSafe final class AsyncEventRecorder implements EventRecorder { private static final AtomicInteger TYPE_NUMBER_GENERATOR = new AtomicInteger(0); private static final EventType NEW_EVENT_TYPE = new EventType("NewEventType"); protected static final class EventType { private final int typeNumber; private final String typeName; protected EventType(String typeName) { this.typeName = typeName; this.typeNumber = TYPE_NUMBER_GENERATOR.incrementAndGet(); } public String typeName() { return typeName; } public int typeNumber() { return typeNumber; } } protected static interface EventWriter extends AutoCloseable { /** * Record a new type of event. * * @param timeInMillis the time (in milliseconds) of the event * @param newType the event information; never null */ public void recordEventType(long timeInMillis, EventType newType); /** * Record an event with the specified {@link EventType#typeNumber() event type number} and String value. Before this * method is called, {@link #recordEventType(long, EventType)} will have been called for the given type of event * * @param timeInMillis the time (in milliseconds) of the event * @param eventType the type of event * @param value the event value */ public void recordEvent(long timeInMillis, int eventType, String value); /** * Record an event with the specified {@link EventType#typeNumber() event type number} and integer value. Before this * method is called, {@link #recordEventType(long, EventType)} will have been called for the given type of event * * @param timeInMillis the time (in milliseconds) of the event * @param eventType the type of event * @param value the event value */ public void recordEvent(long timeInMillis, int eventType, int value); @Override public void close(); } private final ConcurrentMap<String, EventType> eventTypes = new ConcurrentHashMap<>(); private final QueuedWriter queuedWriter; private final Clock clock; protected AsyncEventRecorder(EventWriter writer, Clock clock) { this.eventTypes.put(NEW_EVENT_TYPE.typeName(), NEW_EVENT_TYPE); this.clock = clock; this.queuedWriter = new QueuedWriter(writer); } protected int typeNumber(String eventType) { EventType info = eventTypes.get(eventType); if (info == null) { info = new EventType(eventType); EventType prev = eventTypes.putIfAbsent(eventType, info); if (prev == null) { // We added a new type, so record it ... queuedWriter.recordEventType(clock.currentTimeInMillis(), info); } // Otherwise, somebody already beat us to it so we don't have to } return info.typeNumber(); } @Override public void record(String eventType, String value) { queuedWriter.recordEvent(clock.currentTimeInMillis(), typeNumber(eventType), value); } @Override public void record(String eventType, int value) { queuedWriter.recordEvent(clock.currentTimeInMillis(), typeNumber(eventType), value); } @Override public void execute(long timeInMillis) { queuedWriter.execute(timeInMillis); } @FunctionalInterface protected static interface Event { public void write(EventWriter writer); } protected static final class QueuedWriter implements EventWriter, Executable { private final EventWriter writer; private final Queue<Event> queue = new ConcurrentLinkedQueue<>(); private Event event = null; public QueuedWriter(EventWriter writer) { this.writer = writer; } @Override public void recordEvent(long timeInMillis, int eventType, int value) { queue.offer(writer -> writer.recordEvent(timeInMillis, eventType, value)); } @Override public void recordEvent(long timeInMillis, int eventType, String value) { queue.offer(writer -> writer.recordEvent(timeInMillis, eventType, value)); } @Override public void recordEventType(long timeInMillis, EventType newType) { queue.offer(writer -> writer.recordEventType(timeInMillis, newType)); } @Override public void close() { queue.offer(writer -> writer.close()); } @Override public void execute(long timeInMillis) { while ((event = queue.poll()) != null) { event.write(writer); } } } }