All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.yamcs.cascading.YamcsTmArchiveLink Maven / Gradle / Ivy

There is a newer version: 5.10.9
Show newest version
package org.yamcs.cascading;

import java.util.ArrayDeque;
import java.util.Collections;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

import org.yamcs.TmPacket;
import org.yamcs.YConfiguration;
import org.yamcs.client.archive.ArchiveClient;
import org.yamcs.client.archive.ArchiveClient.StreamOptions;
import org.yamcs.client.archive.ArchiveClient.StreamOptions.StreamOption;
import org.yamcs.protobuf.TmPacketData;
import org.yamcs.tctm.AbstractTmDataLink;
import org.yamcs.tctm.AggregatedDataLink;
import org.yamcs.utils.TimeEncoding;

/**
 * 
 * Yamcs TM Archive link - fetches archive data
 *
 */
public class YamcsTmArchiveLink extends AbstractTmDataLink {
    YamcsLink parentLink;

    private List containers;

    int retrievalDays;
    int mergeTime;
    int gapFillingInterval;

    Queue queue = new ArrayDeque<>();
    List prevGaps;
    CompletableFuture runningTask;
    private long start;
    private long stop;

    public YamcsTmArchiveLink(YamcsLink parentLink) {
        this.parentLink = parentLink;
    }

    @Override
    public void init(String instance, String name, YConfiguration config) {
        config = YamcsTmLink.swapConfig(config, "tmArchiveStream", "tmStream", "tm_dump");
        super.init(instance, name, config);
        this.retrievalDays = config.getInt("retrievalDays", 120);
        // when retrieving archive indexes, merge all the gaps smaller than 300 seconds
        this.mergeTime = config.getInt("mergeTime", 300) * 1000;

        this.mergeTime = 1000;
        this.gapFillingInterval = config.getInt("gapFillingInterval", 300);

        log.debug("Archive retrieval for {} days", retrievalDays);
    }

    @Override
    protected void doStart() {
        if (!isEffectivelyDisabled()) {
            doEnable();
        }
        notifyStarted();
    }

    @Override
    public void doDisable() {
    }

    @Override
    public void doEnable() {
        if (containers != null) {
            scheduleDataRetrieval();
        }
    }

    @Override
    protected void doStop() {
        if (!isDisabled()) {
            doDisable();
        }
        notifyStopped();
    }

    @Override
    protected Status connectionStatus() {
        return parentLink.connectionStatus();
    }

    @Override
    public AggregatedDataLink getParent() {
        return parentLink;
    }

    void scheduleDataRetrieval() {
        parentLink.getExecutor().execute(this::retrieveGaps);
    }

    void retrieveGaps() {
        if (connectionStatus() != Status.OK || isEffectivelyDisabled()) {
            return;
        }

        if (runningTask != null && !runningTask.isDone()) {
            return;
        }

        if (queue.isEmpty()) {
            if (runningTask != null) {
                log.debug("Retrieval finished, looking for new gaps");
                runningTask = null;
            }
            if (prevGaps == null) {
                identifyGaps();
            } else {
                checkRemainingGaps();
            }
        }
        if (queue.isEmpty()) {
            return;
        } else {
            retrieveGap(queue.poll());
        }
    }

    private void processPacketData(TmPacketData data) {
        long rectime = timeService.getMissionTime();
        byte[] pktData = data.getPacket().toByteArray();

        TmPacket pkt = new TmPacket(rectime, pktData);
        if (data.hasGenerationTime()) {
            pkt.setGenerationTime(TimeEncoding.fromProtobufTimestamp(data.getGenerationTime()));
        }
        pkt.setSequenceCount(data.getSequenceNumber());

        packetCount.incrementAndGet();
        processPacket(pkt);
    }

    /**
     * retrieves the TM index from upstream and compares it with the local
     * 
     * @return
     */
    void identifyGaps() {
        start = timeService.getMissionTime() - 86400_000 * retrievalDays;
        stop = timeService.getMissionTime();

        TmGapFinder gapFinder = new TmGapFinder(yamcsInstance, parentLink, eventProducer, retrievalDays,
                p -> isPacketRequired(p));

        var gaps = gapFinder.identifyGaps(start, stop);

        if (gaps.size() == 0) {
            log.debug("No gap identified.");
            log.debug("Scheduling next gap filling in {} seconds", gapFillingInterval);
            parentLink.getExecutor().schedule(this::retrieveGaps, gapFillingInterval, TimeUnit.SECONDS);
            return;
        }

        Collections.sort(gaps);

        prevGaps = gaps;
        log.info("Identified {} gaps for the retrieval", gaps.size());
        queue.addAll(gaps);
    }

    void checkRemainingGaps() {
        TmGapFinder gapFinder = new TmGapFinder(yamcsInstance, parentLink, eventProducer, retrievalDays,
                p -> isPacketRequired(p));

        var gaps = gapFinder.identifyGaps(start, stop);

        for (Gap gap : gaps) {
            if (Collections.binarySearch(prevGaps, gap) >= 0) {
                if (gap.stop < stop) {
                    log.warn("Gap {} still remains after replay", gap);
                }
            }
        }

        prevGaps = null;
        log.debug("Scheduling next gap filling in {} seconds", gapFillingInterval);
        parentLink.getExecutor().schedule(this::retrieveGaps, gapFillingInterval, TimeUnit.SECONDS);
    }

    void retrieveGap(Gap g) {
        log.debug("Retrieving gap {}", g);
        ArchiveClient arcClient = parentLink.getClient().createArchiveClient(parentLink.getUpstreamInstance());
        java.time.Instant startj = java.time.Instant.ofEpochMilli(TimeEncoding.toUnixMillisec(g.start));

        // the retrieval is exclusive on the right, so we increase the stop by one millisecond
        java.time.Instant stopj = java.time.Instant.ofEpochMilli(TimeEncoding.toUnixMillisec(g.stop + 1));
        StreamOption opt = StreamOptions.packets(containers.toArray(new String[0]));
        runningTask = arcClient.streamPackets(pkt -> processPacketData(pkt), startj, stopj, opt);
        runningTask.whenComplete((v, t) -> {
            if (t != null) {
                log.warn("Error in gap retrieval", t);
            }
            scheduleDataRetrieval();
        });
    }

    private boolean isPacketRequired(String name) {
        return containers.contains(name);
    }

    public void setContainers(List containers) {
        this.containers = containers;
    }

    static class Gap implements Comparable {
        long start;
        long stop;

        public Gap(long start, long stop) {
            this.start = start;
            this.stop = stop;
        }

        @Override
        public int compareTo(Gap other) {
            return Long.compare(start, other.start);
        }

        @Override
        public String toString() {
            return "[" + TimeEncoding.toString(start) + " - " + TimeEncoding.toString(stop) + "]";
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy