/* * Copyright 2016 Cel Skeggs * * This file is part of the CCRE, the Common Chicken Runtime Engine. * * The CCRE is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) any * later version. * * The CCRE is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public License * along with the CCRE. If not, see <http://www.gnu.org/licenses/>. */ package ccre.recording; import java.io.IOException; import java.io.OutputStream; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.PriorityBlockingQueue; import java.util.concurrent.TimeUnit; import ccre.concurrency.ReporterThread; import ccre.verifier.FlowPhase; import ccre.verifier.SetupPhase; class RecorderThread { private static final RecordSnapshot SENTINEL = new RecordSnapshot(); static { SENTINEL.timestamp = Long.MAX_VALUE; // always ordered LAST } private final ReporterThread thread = new ReporterThread("Recorder") { @Override protected void threadBody() throws Throwable { try { while (true) { // flush once per second RecordSnapshot rs = queue.poll(1, TimeUnit.SECONDS); if (rs == null) { enc.flush(); rs = queue.take(); } if (rs == SENTINEL) { enc.close(); queue.clear(); return; } else { enc.encode(rs); pool.offer(rs); // and if not, just drop it } } } finally { terminated.countDown(); } } }; private final StreamEncoder enc; public RecorderThread(OutputStream output) throws IOException { enc = new StreamEncoder(output); thread.setDaemon(true); } private final CountDownLatch terminated = new CountDownLatch(1); private final ArrayBlockingQueue<RecordSnapshot> pool = new ArrayBlockingQueue<>(64); private final PriorityBlockingQueue<RecordSnapshot> queue = new PriorityBlockingQueue<>(); @FlowPhase private RecordSnapshot getOrAllocateSnapshot() { RecordSnapshot rs = pool.poll(); return rs == null ? new RecordSnapshot() : rs; } @SetupPhase public void close() throws InterruptedException { queue.add(SENTINEL); terminated.await(); } @FlowPhase public void record(long timestamp, int channel, byte type, long value) { RecordSnapshot rs = getOrAllocateSnapshot(); rs.timestamp = timestamp; rs.channel = channel; rs.type = type; rs.value = value; rs.data = null; queue.add(rs); } @FlowPhase public void record(long timestamp, int channel, byte[] data) { RecordSnapshot rs = getOrAllocateSnapshot(); rs.timestamp = timestamp; rs.channel = channel; rs.type = RecordSnapshot.T_BYTES; rs.data = data; queue.add(rs); } @SetupPhase public void start() { thread.start(); } }