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

org.yamcs.parameterarchive.BackFiller Maven / Gradle / Ivy

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

import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import org.yamcs.ConfigurationException;
import org.yamcs.Processor;
import org.yamcs.ProcessorFactory;
import org.yamcs.Spec;
import org.yamcs.Spec.OptionType;
import org.yamcs.StandardTupleDefinitions;
import org.yamcs.StreamConfig;
import org.yamcs.StreamConfig.StandardStreamType;
import org.yamcs.YConfiguration;
import org.yamcs.YamcsServer;
import org.yamcs.archive.ReplayOptions;
import org.yamcs.logging.Log;
import org.yamcs.time.TimeService;
import org.yamcs.utils.LongArray;
import org.yamcs.utils.TimeEncoding;
import org.yamcs.yarch.Stream;
import org.yamcs.yarch.StreamSubscriber;
import org.yamcs.yarch.Tuple;
import org.yamcs.yarch.YarchDatabase;
import org.yamcs.yarch.YarchDatabaseInstance;

import com.google.common.util.concurrent.ThreadFactoryBuilder;

/**
 * Back-fills the parameter archive by triggering replays: - either regularly scheduled replays - or monitor data
 * streams (tm, param) and keep track of which segments have to be rebuild
 * 
 */
public class BackFiller implements StreamSubscriber {
    List schedules;
    long t0;
    int runCount;

    final ParameterArchive parchive;

    long warmupTime;
    final TimeService timeService;
    static AtomicInteger count = new AtomicInteger();
    private final Log log;
    final ScheduledThreadPoolExecutor executor;

    // set of segments that have to be rebuilt following monitoring of streams
    private Map streamUpdates;
    // streams which are monitored
    private List subscribedStreams;

    // after how many backfilling tasks to trigger a parchive.compact()
    int compactFrequency = -1;

    int compactCount = 0;
    long quietPeriodThreshold;

    private List listeners = new CopyOnWriteArrayList<>();
    private List streamUpdatePolicy = new ArrayList<>();

    private boolean automaticBackfillingEnabled = true;
    private List> scheduledFutures = new ArrayList<>();

    /**
     * 
     * Constructs a new BackFiller
     * 

* The backfiller is used for manual requests and also by automatic backfilling. *

* defaultAutomaticBackfilling is used as default for whether to schedule or not backfillings. If the realtime * filler is enabled, the automatic backfilling is disabled by default. */ BackFiller(ParameterArchive parchive, YConfiguration config, boolean defaultAutomaticBackfilling) { this.parchive = parchive; this.log = new Log(BackFiller.class, parchive.getYamcsInstance()); parseConfig(config, defaultAutomaticBackfilling); timeService = YamcsServer.getTimeService(parchive.getYamcsInstance()); executor = new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder().setNameFormat("ParameterArchive-BackFiller-" + parchive.getYamcsInstance()) .build()); } public static Spec getSpec() { Spec spec = new Spec(); spec.addOption("warmupTime", OptionType.INTEGER).withDefault(60); spec.addOption("automaticBackfilling", OptionType.BOOLEAN).withAliases("enabled").withRequired(false); spec.addOption("monitorStreams", OptionType.LIST).withElementType(OptionType.STRING); spec.addOption("streamUpdateFillFrequency", OptionType.INTEGER) .withDeprecationMessage("Please use the streamUpdateFillPolicy").withDefault(3600); Spec policyEntry = new Spec(); policyEntry.addOption("dataAge", OptionType.FLOAT).withRequired(true); policyEntry.addOption("fillFrequency", OptionType.INTEGER).withDefault(3600); policyEntry.addOption("quietThreshold", OptionType.INTEGER).withDefault(60); spec.addOption("streamUpdateFillPolicy", OptionType.LIST).withElementType(OptionType.MAP).withSpec(policyEntry); Spec schedSpec = new Spec(); schedSpec.addOption("startInterval", OptionType.INTEGER); schedSpec.addOption("numIntervals", OptionType.INTEGER); spec.addOption("schedule", OptionType.MAP).withSpec(schedSpec); spec.addOption("compactFrequency", OptionType.INTEGER).withDefault(-1); return spec; } synchronized void scheduleAutoFillers() { if (!this.automaticBackfillingEnabled) { return; } if (schedules != null && !schedules.isEmpty()) { int c = 0; for (Schedule s : schedules) { if (s.frequency == -1) { c++; continue; } var f = executor.scheduleAtFixedRate(() -> { runSchedule(s); }, 0, s.frequency, TimeUnit.SECONDS); scheduledFutures.add(f); } if (c > 0) { long now = timeService.getMissionTime(); t0 = ParameterArchive.getIntervalStart(now); var f = executor.schedule(() -> { runSegmentSchedules(); }, t0 - now, TimeUnit.MILLISECONDS); scheduledFutures.add(f); } } if (subscribedStreams != null && !subscribedStreams.isEmpty()) { var f = executor.scheduleAtFixedRate(() -> { checkStreamUpdates(); }, 5, 5, TimeUnit.SECONDS); scheduledFutures.add(f); } } public synchronized void enableAutomaticBackfilling(boolean enable) { if (this.automaticBackfillingEnabled == enable) { log.debug("automatic backfilling is already {}", automaticBackfillingEnabled ? "enabled" : "disabled"); return; } for (var f : scheduledFutures) { f.cancel(true); } scheduledFutures.clear(); this.automaticBackfillingEnabled = enable; if (enable) { scheduleAutoFillers(); } log.debug("automatic backfilling has been {}", automaticBackfillingEnabled ? "enabled" : "disabled"); } private void parseConfig(YConfiguration config, boolean defaultAutomaticBackfilling) { this.warmupTime = 1000L * config.getInt("warmupTime", 60); this.compactFrequency = config.getInt("compactFrequency", -1); if (config.containsKey("schedule")) { List l = config.getConfigList("schedule"); schedules = new ArrayList<>(l.size()); for (YConfiguration sch : l) { int segstart = sch.getInt("startSegment"); int numseg = sch.getInt("numSegments"); long interval = sch.getInt("interval", -1); Schedule s = new Schedule(segstart, numseg, interval); schedules.add(s); } } List monitoredStreams; if (config.containsKey("monitorStreams")) { monitoredStreams = config.getList("monitorStreams"); } else { StreamConfig sc = StreamConfig.getInstance(parchive.getYamcsInstance()); monitoredStreams = new ArrayList<>(); sc.getEntries(StandardStreamType.TM).forEach(sce -> monitoredStreams.add(sce.getName())); sc.getEntries(StandardStreamType.PARAM).forEach(sce -> monitoredStreams.add(sce.getName())); } if (!monitoredStreams.isEmpty()) { if (config.containsKey("streamUpdateFillPolicy")) { if (monitoredStreams.isEmpty()) { log.warn("Monitored streams is empty, the streamUpdateFillPolicy will not be used"); } List l = config.getConfigList("streamUpdateFillPolicy"); for (YConfiguration sch : l) { long dataAge = (long) (sch.getDouble("dataAge") * 3600_000); long fillFrequency = sch.getLong("fillFrequency", 3600) * 1000; long quietThreshold = sch.getLong("quietThreshold") * 1000; streamUpdatePolicy.add(new StreamUpdatePolicyEnter(dataAge, fillFrequency, quietThreshold)); } streamUpdatePolicy.sort(Comparator.comparingLong(StreamUpdatePolicyEnter::dataAge)); } else if (config.containsKey("streamUpdateFillFrequency")) { var streamUpdateFillFrequency = 1000 * config.getLong("streamUpdateFillFrequency", 3600); streamUpdatePolicy.add(new StreamUpdatePolicyEnter(-1, streamUpdateFillFrequency, -1)); } else { streamUpdatePolicy.add(new StreamUpdatePolicyEnter(-3600_000, 600_000, 10_000)); streamUpdatePolicy.add(new StreamUpdatePolicyEnter(7200_000, -1, 60_000)); } streamUpdates = new HashMap<>(); subscribedStreams = new ArrayList<>(monitoredStreams.size()); YarchDatabaseInstance ydb = YarchDatabase.getInstance(parchive.getYamcsInstance()); for (String streamName : monitoredStreams) { Stream s = ydb.getStream(streamName); if (s == null) { throw new ConfigurationException( "Cannot find stream '" + s + "' required for the parameter archive backfiller"); } s.addSubscriber(this); subscribedStreams.add(s); } } } public Future scheduleFillingTask(long start, long stop) { return executor.schedule(() -> runTask(start, stop), 0, TimeUnit.SECONDS); } private void runTask(long start, long stop) { try { start = ParameterArchive.getIntervalStart(start); stop = ParameterArchive.getIntervalEnd(stop) + 1; BackFillerTask bft = new BackFillerTask(parchive); bft.setCollectionStart(start); String timePeriod = '[' + TimeEncoding.toString(start) + "-" + TimeEncoding.toString(stop) + ')'; log.debug("Starting parameter archive fillup for interval {}", timePeriod); long t0 = System.nanoTime(); ReplayOptions rrb = ReplayOptions.getAfapReplay(start - warmupTime, stop, false); Processor proc = ProcessorFactory.create(parchive.getYamcsInstance(), "ParameterArchive-backfilling_" + count.incrementAndGet(), "ParameterArchive", "internal", rrb); bft.setProcessor(proc); proc.getParameterRequestManager().subscribeAll(bft); proc.start(); proc.awaitTerminated(); if (bft.aborted) { log.warn("Parameter archive fillup for interval {} aborted", timePeriod); } else { bft.flush(); long t1 = System.nanoTime(); log.debug("Parameter archive fillup for interval {} finished, processed {} samples in {} millisec", timePeriod, bft.getNumProcessedParameters(), (t1 - t0) / 1_000_000); for (BackFillerListener listener : listeners) { listener.onBackfillFinished(start, stop, bft.getNumProcessedParameters()); } } if (compactFrequency != -1 && ++compactCount >= compactFrequency) { compactCount = 0; parchive.compact(); } } catch (Exception e) { log.error("Error when running the archive filler task", e); } } private void runSchedule(Schedule s) { if (!automaticBackfillingEnabled) { return; } long start, stop; long intervalDuration = ParameterArchive.getIntervalDuration(); if (s.frequency == -1) { start = t0 + (runCount - s.intervalStart) * intervalDuration; stop = start + s.numIntervals * intervalDuration - 1; } else { long now = timeService.getMissionTime(); start = now - s.intervalStart * intervalDuration; stop = start + s.numIntervals * intervalDuration - 1; } runTask(start, stop); } private void checkStreamUpdates() { if (!automaticBackfillingEnabled) { return; } LongArray rebuildIntervals; synchronized (streamUpdates) { if (streamUpdates.isEmpty()) { return; } // wall clock time is used to compare with the lastUpdate and lastRebuild since these are set by the // System.currentTime var nowWc = System.currentTimeMillis(); // mission time is used to compare with the interval time to get the data age var nowMt = timeService.getMissionTime(); rebuildIntervals = new LongArray(streamUpdates.size()); var it = streamUpdates.entrySet().iterator(); while (it.hasNext()) { var streamUpdateEntry = it.next(); long age = nowMt - streamUpdateEntry.getKey(); var applicablePolicyEntry = streamUpdatePolicy.get(0); for (int i = 1; i < streamUpdatePolicy.size(); i++) { var supe = streamUpdatePolicy.get(i); if (age < supe.dataAge) { break; } applicablePolicyEntry = supe; } var streamUpdate = streamUpdateEntry.getValue(); if (applicablePolicyEntry.fillFrequency > 0 && nowWc - streamUpdate.lastRebuild > applicablePolicyEntry.fillFrequency) { rebuildIntervals.add(streamUpdateEntry.getKey()); streamUpdate.lastRebuild = nowWc; it.remove(); } else if (applicablePolicyEntry.quietThreshold > 0 && nowWc - streamUpdate.lastUpdate > applicablePolicyEntry.quietThreshold) { rebuildIntervals.add(streamUpdateEntry.getKey()); it.remove(); } } } rebuildIntervals.sort(); for (int i = 0; i < rebuildIntervals.size(); i++) { int j; for (j = i; j < rebuildIntervals.size() - 1; j++) { if (ParameterArchive.getIntervalEnd(rebuildIntervals.get(j)) != rebuildIntervals.get(j + 1)) { break; } } runTask(rebuildIntervals.get(i), ParameterArchive.getIntervalEnd(rebuildIntervals.get(j))); i = j; } } // runs all schedules with interval -1 private void runSegmentSchedules() { if (!automaticBackfillingEnabled) { return; } for (Schedule s : schedules) { if (s.frequency == -1) { runSchedule(s); } } runCount++; } static class Schedule { public Schedule(int intervalStart, int numIntervals, long frequency) { this.intervalStart = intervalStart; this.numIntervals = numIntervals; this.frequency = frequency; } int intervalStart; int numIntervals; long frequency; } public void shutDown() throws InterruptedException { if (subscribedStreams != null) { for (Stream s : subscribedStreams) { s.removeSubscriber(this); } } executor.shutdown(); executor.awaitTermination(10, TimeUnit.SECONDS); } @Override public void onTuple(Stream stream, Tuple tuple) { long gentime = tuple.getTimestampColumn(StandardTupleDefinitions.GENTIME_COLUMN); if (gentime == TimeEncoding.INVALID_INSTANT) { log.warn("Ignorning tuple with invalid gentime {}", tuple); return; } long t0 = ParameterArchive.getIntervalStart(gentime); synchronized (streamUpdates) { long now = System.currentTimeMillis(); var streamUpdate = streamUpdates.computeIfAbsent(t0, t -> new StreamUpdate(now)); streamUpdate.lastUpdate = now; } } @Override public void streamClosed(Stream stream) { log.debug("Stream {} closed", stream.getName()); } public void addListener(BackFillerListener listener) { listeners.add(listener); } public void removeListener(BackFillerListener listener) { listeners.remove(listener); } static class StreamUpdate { long lastUpdate; long lastRebuild; StreamUpdate(long lastRebuild) { this.lastRebuild = lastRebuild; } } /** * all values are milliseconds */ static record StreamUpdatePolicyEnter(long dataAge, long fillFrequency, long quietThreshold) { @Override public String toString() { return String.format("StreamUpdatePolicyEnter{dataAge=%.2f h, fillFrequency=%.2f s, quietThreshold=%.2f s}", dataAge / 3600000.0, fillFrequency / 1000.0, quietThreshold / 1000.0); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy