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

org.yamcs.parameter.ParameterRetrievalService Maven / Gradle / Ivy

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

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;

import org.rocksdb.RocksDBException;
import org.yamcs.AbstractYamcsService;
import org.yamcs.InitException;
import org.yamcs.Processor;
import org.yamcs.ProcessorFactory;
import org.yamcs.YConfiguration;
import org.yamcs.YamcsServer;
import org.yamcs.archive.ReplayOptions;
import org.yamcs.parameterarchive.ConsumerAbortException;
import org.yamcs.parameterarchive.MultiParameterRetrieval;
import org.yamcs.parameterarchive.MultipleParameterRequest;
import org.yamcs.parameterarchive.ParameterArchive;
import org.yamcs.parameterarchive.ParameterId;
import org.yamcs.parameterarchive.ParameterIdDb;
import org.yamcs.parameterarchive.ParameterValueArray;
import org.yamcs.parameterarchive.SingleParameterRetrieval;
import org.yamcs.protobuf.Yamcs.ParameterReplayRequest;
import org.yamcs.time.Instant;
import org.yamcs.utils.AggregateUtil;
import org.yamcs.utils.IntArray;
import org.yamcs.utils.TimeEncoding;
import org.yamcs.xtce.Parameter;
import org.yamcs.xtce.PathElement;

import com.google.common.collect.Lists;

/**
 * Combines retrieval from different sources:
 * 
    *
  • Parameter Archive
  • *
  • Replays
  • *
  • Parameter cache
  • *
  • Realtime Parameter Archive filler
  • *
* */ public class ParameterRetrievalService extends AbstractYamcsService { private static final String DEFAULT_PROCESSOR = "realtime"; String procName = DEFAULT_PROCESSOR; ArrayParameterCache pcache; ParameterArchive parchive; ParameterCacheConfig cacheConfig; ExecutorService executor; static AtomicInteger count = new AtomicInteger(); // if this is true, then we stick to the cacheConfig discovered during init // if this is false (meaning no explicit cache has been configured) then we set a cache if the realtime archive // filler is not enabled boolean pcacheConfigured; public void init(String yamcsInstance, String serviceName, YConfiguration config) throws InitException { super.init(yamcsInstance, serviceName, config); this.procName = config.getString("processor", DEFAULT_PROCESSOR); int parallelRetrievals = config.getInt("parallelRetrievals", 4); this.executor = createExecutor(parallelRetrievals); if (config.containsKey("parameterCache")) { pcacheConfigured = true; YConfiguration pcacheConfig = config.getConfig("parameterCache"); if (pcacheConfig.getBoolean("enabled", true)) { this.cacheConfig = new ParameterCacheConfig(pcacheConfig, log); } } else { pcacheConfigured = false; } } @Override protected void doStart() { var ysi = YamcsServer.getServer().getInstance(yamcsInstance); var l = ysi.getServices(ParameterArchive.class); if (!l.isEmpty()) { parchive = l.get(0); } else { log.info("The parameter archive service has not been found"); } if ((parchive == null || parchive.getRealtimeFiller() == null) && !pcacheConfigured) { cacheConfig = new ParameterCacheConfig(YConfiguration.emptyConfig(), log); } if (cacheConfig != null) { pcache = new ArrayParameterCache(yamcsInstance, cacheConfig); var proc = ysi.getProcessor(procName); proc.getParameterRequestManager() .subscribeAll((id, items) -> pcache.update(items)); } notifyStarted(); } @Override protected void doStop() { executor.shutdown(); notifyStopped(); } /** * Retrieves a single scalar parameter or aggregate/array member. */ public CompletableFuture retrieveScalar(ParameterWithId pid, ParameterRetrievalOptions opts, Consumer consumer) { log.debug("retrieveScalar pid: {}, opts: {} ", pid, opts); var cf = new CompletableFuture(); executor.submit(() -> { try { if (parchive == null || opts.noparchive()) { retrieveScalarReplayOrCache(pid, opts, consumer); } else if (parchive.getRealtimeFiller() != null) { retrieveScalarParameterArchive(pid, opts, consumer); } else { long coverageEnd = parchive.coverageEnd(); if (opts.ascending()) { // ascending case -> retrieve max possible from the parameter archive var tc = retrieveScalarParameterArchive(pid, opts, consumer); // then from cache or via replay if (tc.isValid()) { if (opts.stop() > tc.time && opts.stop() > coverageEnd) { var opts1 = opts.withUpdatedStart(tc.time + 1); retrieveScalarReplayOrCache(pid, opts1, consumer); } } else {// no data retrieved from the parameter archive retrieveScalarReplayOrCache(pid, opts, consumer); } } else { // descending case // if the request is beyond parameter archive coverage, retrieve first by cache or replay if (opts.stop() > coverageEnd) { if (opts.start() >= coverageEnd) { // request does not overlap at all with the parameter archive coverage retrieveScalarReplayOrCache(pid, opts, consumer); } else { // request overlaps with the parameter archive coverage var req1 = opts.withUpdatedStart(coverageEnd); retrieveScalarReplayOrCache(pid, req1, consumer); var req2 = opts.withUpdatedStop(coverageEnd); retrieveScalarParameterArchive(pid, req2, consumer); } } else { // request can be satisfied only by parameter archive retrieveScalarParameterArchive(pid, opts, consumer); } } } cf.complete(null); } catch (Exception e) { cf.completeExceptionally(e); } }); return cf; } public CompletableFuture retrieveSingle(ParameterWithId pid, ParameterRetrievalOptions opts, Consumer consumer) { log.debug("retrieveSingle requestedParamWithId: {}, opts: {}", pid, opts); var cf = new CompletableFuture(); executor.submit(() -> { try { if (parchive == null || opts.noparchive()) { retrieveSingleReplayOrCache(pid, opts, consumer); } else if (parchive.getRealtimeFiller() != null) { retrieveSingleParameterArchive(pid, opts, consumer); } else { long coverageEnd = parchive.coverageEnd(); if (opts.ascending()) { // ascending case -> retrieve max possible from the parameter archive var tc = retrieveSingleParameterArchive(pid, opts, consumer); // then from cache or via replay if (tc.isValid()) { if (opts.stop() > tc.time && opts.stop() > coverageEnd) { var req1 = opts.withUpdatedStart(tc.time + 1); retrieveSingleReplayOrCache(pid, req1, consumer); } } else { retrieveSingleReplayOrCache(pid, opts, consumer); } } else { // descending case // if the request is beyond parameter archive coverage, retrieve first by cache or replay if (opts.stop() > coverageEnd) { if (opts.start() >= coverageEnd) { // request does not overlap at all with the parameter archive coverage retrieveSingleReplayOrCache(pid, opts, consumer); } else { // request overlaps with the parameter archive coverage var req1 = opts.withUpdatedStart(coverageEnd); retrieveSingleReplayOrCache(pid, req1, consumer); var req2 = opts.withUpdatedStop(coverageEnd); retrieveSingleParameterArchive(pid, req2, consumer); } } else { // request can be satisfied only by parameter archive retrieveSingleParameterArchive(pid, opts, consumer); } } } cf.complete(null); } catch (ConsumerAbortException e) { cf.complete(null); } catch (Exception e) { cf.completeExceptionally(e); } }); return cf; }; public CompletableFuture retrieveMulti(List pids, ParameterRetrievalOptions opts, Consumer> consumer) { log.debug("retrieveMulti pids: {}, opts: {}", pids, opts); var cf = new CompletableFuture(); executor.submit(() -> { try { if (parchive == null || opts.noparchive()) { retrieveMultiReplayOrCache(pids, opts, consumer); } else if (parchive.getRealtimeFiller() != null) { retrieveMultiParameterArchive(pids, opts, consumer); } else { long coverageEnd = parchive.coverageEnd(); if (opts.ascending()) { // ascending case -> retrieve max possible from the parameter archive var tc = retrieveMultiParameterArchive(pids, opts, consumer); // then from cache or via replay if (tc.isValid()) { if (opts.stop() > tc.time && opts.stop() > coverageEnd) { var req1 = opts.withUpdatedStart(tc.time + 1); retrieveMultiReplayOrCache(pids, req1, consumer); } } else { retrieveMultiReplayOrCache(pids, opts, consumer); } } else { // descending case // if the request is beyond parameter archive coverage, retrieve first by cache or replay if (opts.stop() > coverageEnd) { if (opts.start() >= coverageEnd) { // request does not overlap at all with the parameter archive coverage retrieveMultiReplayOrCache(pids, opts, consumer); } else { // request overlaps with the parameter archive coverage var req1 = opts.withUpdatedStart(coverageEnd); retrieveMultiReplayOrCache(pids, req1, consumer); var req2 = opts.withUpdatedStop(coverageEnd); retrieveMultiParameterArchive(pids, req2, consumer); } } else { // request can be satisfied only by parameter archive retrieveMultiParameterArchive(pids, opts, consumer); } } } cf.complete(null); } catch (ConsumerAbortException e) { cf.complete(null); } catch (Exception e) { cf.completeExceptionally(e); } }); return cf; } public ParameterCache getParameterCache() { return pcache; } private TimeAndCount retrieveScalarParameterArchive(ParameterWithId pid, ParameterRetrievalOptions request, Consumer consumer) throws IOException { log.debug("retrieveScalarParameterArchive pid: {}, request: {}", pid, request); SingleParameterRetrieval spar = new SingleParameterRetrieval(parchive, pid.getQualifiedName(), request); TimeAndCount tc = new TimeAndCount(TimeEncoding.INVALID_INSTANT, 0); try { spar.retrieve(pva -> { long[] timestamps = pva.getTimestamps(); tc.time = timestamps[timestamps.length - 1]; tc.count += timestamps.length; consumer.accept(pva); }); } catch (RocksDBException e) { throw new IOException(e); } return tc; } TimeAndCount retrieveScalarReplayOrCache(ParameterWithId pid, ParameterRetrievalOptions opts, Consumer consumer) throws Exception { log.debug("retrieveScalarReplayOrCache pid: {}, opts: {} pcache present: {}", pid, opts, pcache != null); if (pcache != null && !opts.norealtime()) { long start = opts.start(); long stop = opts.stop(); // parameter cache returns value in reverse order in (start, stop] // if ascending is required we have to retrieve [start, stop) if (opts.ascending()) { start--; stop--; } // TODO this could be optimised because the pcache stores already arrays of values var pvList = opts.noreplay() ? pcache.getAllValues(pid.getParameter(), start, stop) : pcache.getAllValuesIfCovered(pid.getParameter(), start, stop); log.debug("pcache returned {} results", pvList == null ? null : pvList.size()); if (pvList != null) { if (pid.getPath() != null) { pvList = extractMembers(pvList, pid.getPath()); } if (opts.ascending()) { pvList = Lists.reverse(pvList); } return splitAndSend(pvList, consumer); } // else it means the cache does not cover the requested interval, // send everything via replay if allowed } if (!opts.noreplay()) { return replay(Collections.singletonList(pid), opts, new Consumer>() { @Override public void accept(List pvList) { for (var pv : pvList) { consumer.accept(toScalarPva(pv.getParameterValue(), opts)); } } }); } else { // noreplay is specified and no data has been found in the cache return new TimeAndCount(TimeEncoding.INVALID_INSTANT, 0); } } private TimeAndCount retrieveSingleParameterArchive(ParameterWithId pid, ParameterRetrievalOptions opts, Consumer consumer) throws RocksDBException, IOException { log.debug("retrieveSingleParameterArchive pid: {}, opts: {}", pid, opts); MultipleParameterRequest mpvr; ParameterIdDb piddb = parchive.getParameterIdDb(); String qn = pid.getQualifiedName(); ParameterId[] pids = piddb.get(qn); if (pids != null) { TimeAndCount tc = new TimeAndCount(TimeEncoding.INVALID_INSTANT, 0); mpvr = new MultipleParameterRequest(opts.start(), opts.stop(), pids, opts.ascending()); MultiParameterRetrieval mpdr = new MultiParameterRetrieval(parchive, mpvr); mpdr.retrieve(pvList -> { tc.count += pvList.size(); tc.time = pvList.time(); for (var pv : pvList.getValues()) { consumer.accept(new ParameterValueWithId(pv, pid.id)); } }); return tc; } else { return new TimeAndCount(TimeEncoding.INVALID_INSTANT, 0); } } TimeAndCount retrieveSingleReplayOrCache(ParameterWithId pid, ParameterRetrievalOptions opts, Consumer consumer) throws Exception { log.debug("retrieveSingleReplayOrCache pid: {}, opts: {} pcache present: {}", pid, opts, pcache != null); if (pcache != null && !opts.norealtime()) { long start = opts.start(); long stop = opts.stop(); // parameter cache returns value in reverse order in (start, stop] // if ascending is required we have to retrieve [start, stop) if (opts.ascending()) { start--; stop--; } var pvList = opts.noreplay() ? pcache.getAllValues(pid.getParameter(), start, stop) : pcache.getAllValuesIfCovered(pid.getParameter(), start, stop); log.debug("pcache returned {} results", pvList == null ? null : pvList.size()); if (pvList != null) { if (pid.getPath() != null) { pvList = extractMembers(pvList, pid.getPath()); } if (opts.ascending()) { for (int i = pvList.size() - 1; i >= 0; i--) { var pv = pvList.get(i); consumer.accept(new ParameterValueWithId(pv, pid.id)); } return new TimeAndCount(pvList.get(0).getGenerationTime(), pvList.size()); } else { for (var pv : pvList) { consumer.accept(new ParameterValueWithId(pv, pid.id)); } return new TimeAndCount(pvList.get(pvList.size() - 1).getGenerationTime(), pvList.size()); } } // else it means the cache does not cover the requested interval, // send everything via replay if allowed } if (!opts.noreplay()) { return replay(Collections.singletonList(pid), opts, new Consumer>() { @Override public void accept(List pvList) { for (var pv : pvList) { consumer.accept(pv); } } }); } else { // noreplay is specified and no data has been found in the cache return new TimeAndCount(Instant.MIN_INSTANT, 0); } } private TimeAndCount retrieveMultiParameterArchive(List pidList, ParameterRetrievalOptions opts, Consumer> consumer) throws RocksDBException, IOException { log.debug("retrieveMultiParameterArchive pid: {}, opts: {}", pidList, opts); MultipleParameterRequest mpvr; ParameterIdDb piddb = parchive.getParameterIdDb(); List parameterIds = new ArrayList<>(); // map between the Map> pidMapping = new HashMap<>(); for (ParameterWithId pid : pidList) { String qn = pid.getQualifiedName(); ParameterId[] pids = piddb.get(qn); if (pids != null) { parameterIds.addAll(Arrays.asList(pids)); for (var paraid : pids) { pidMapping.computeIfAbsent(paraid.getPid(), k -> new ArrayList<>()).add(pid); } } } if (!parameterIds.isEmpty()) { TimeAndCount tc = new TimeAndCount(TimeEncoding.INVALID_INSTANT, 0); mpvr = new MultipleParameterRequest(opts.start(), opts.stop(), parameterIds.toArray(new ParameterId[0]), opts.ascending()); MultiParameterRetrieval mpdr = new MultiParameterRetrieval(parchive, mpvr); mpdr.retrieve(pvList -> { tc.count += pvList.size(); tc.time = pvList.time(); List pvl = new ArrayList<>(); IntArray parchiveIds = pvList.getPids(); var x = pvList.getValues(); for (int i = 0; i < parchiveIds.size(); i++) { var paraIdList = pidMapping.get(parchiveIds.get(i)); for (var paraId : paraIdList) { pvl.add(new ParameterValueWithId(x.get(i), paraId.id)); } } consumer.accept(pvl); }); return tc; } else { return new TimeAndCount(TimeEncoding.INVALID_INSTANT, 0); } } TimeAndCount retrieveMultiReplayOrCache(List pids, ParameterRetrievalOptions opts, Consumer> consumer) throws Exception { log.debug("retrieveSingleReplayOrCache pid: {}, opts: {} pcache present: {}", pids, opts, pcache != null); if (pcache != null && !opts.norealtime()) { long start = opts.start(); long stop = opts.stop(); // parameter cache returns value in reverse order in (start, stop] // if ascending is required we have to retrieve [start, stop) if (opts.ascending()) { start--; stop--; } Map> pidMapping = new HashMap<>(); for (var pid : pids) { Parameter parameter = pid.getParameter(); pidMapping.computeIfAbsent(parameter, k -> new ArrayList<>()).add(pid); } var parameters = new ArrayList<>(pidMapping.keySet()); var pvListList = opts.noreplay() ? pcache.getAllValues(parameters, start, stop) : pcache.getAllValuesIfCovered(parameters, start, stop); log.debug("pcache returned {} results", pvListList.size()); if (opts.ascending()) { pvListList = Lists.reverse(pvListList); } if (!pvListList.isEmpty()) { long t = 0; for (var pvList : pvListList) { var pvidList = new ArrayList(); for (var pv : pvList) { for (var pid : pidMapping.get(pv.getParameter())) { if (pid.getPath() != null) { pv = AggregateUtil.extractMember(pv, pid.getPath()); } pvidList.add(new ParameterValueWithId(pv, pid.getId())); } t = pv.getGenerationTime(); } consumer.accept(pvidList); } return new TimeAndCount(t, pvListList.size()); } // else it means the cache does not cover the requested interval, // send everything via replay if allowed } if (!opts.noreplay()) { return replay(pids, opts, consumer); } else { // noreplay is specified and no data has been found in the cache return new TimeAndCount(Instant.MIN_INSTANT, 0); } } // splits the list in arrays of parameters having the same type private TimeAndCount splitAndSend(List pvlist, Consumer consumer) { int n = 0; int m = pvlist.size(); ParameterValue pv0 = pvlist.get(n); for (int j = 1; j < m; j++) { ParameterValue pv = pvlist.get(j); if (differentType(pv0, pv)) { sendToConsumer(pvlist, n, j, consumer); pv0 = pv; n = j; } } sendToConsumer(pvlist, n, m, consumer); return new TimeAndCount(pvlist.get(0).getGenerationTime(), pvlist.size()); } private void sendToConsumer(List pvlist, int n, int m, Consumer consumer) { ParameterValue pv0 = pvlist.get(n); ValueArray rawValues = null; if (pv0.getRawValue() != null) { rawValues = new ValueArray(pv0.getRawValue().getType(), m - n); for (int i = n; i < m; i++) { rawValues.setValue(i - n, pvlist.get(i).getRawValue()); } } ValueArray engValues = null; if (pv0.getEngValue() != null) { engValues = new ValueArray(pv0.getEngValue().getType(), m - n); for (int i = n; i < m; i++) { engValues.setValue(i - n, pvlist.get(i).getEngValue()); } } long[] timestamps = new long[m - n]; var statuses = new org.yamcs.yarch.protobuf.Db.ParameterStatus[m - n]; for (int i = n; i < m; i++) { ParameterValue pv = pvlist.get(i); timestamps[i - n] = pv.getGenerationTime(); statuses[i - n] = pv.getStatus().toProtoBuf(false); } ParameterValueArray pva = new ParameterValueArray(timestamps, engValues, rawValues, statuses); consumer.accept(pva); } private boolean differentType(ParameterValue pv0, ParameterValue pv1) { return differentType(pv0.getRawValue(), pv1.getRawValue()) || differentType(pv0.getEngValue(), pv1.getEngValue()); } private boolean differentType(Value v1, Value v2) { if (v1 == null) { return v2 != null; } if (v2 == null) { return true; } return v1.getType() != v2.getType(); } private List extractMembers(List pvlist, PathElement[] path) { List l = new ArrayList(pvlist.size()); for (ParameterValue pv : pvlist) { ParameterValue pv1 = AggregateUtil.extractMember(pv, path); if (pv1 != null) { l.add(pv1); } } return l; } private TimeAndCount replay(List paramList, ParameterRetrievalOptions opts, Consumer> consumer) throws Exception { ReplayOptions replayOpts = ReplayOptions.getAfapReplay(opts.start(), opts.stop(), !opts.ascending()); if (opts.packetReplayRequest() != null) { replayOpts.setPacketRequest(opts.packetReplayRequest()); } var prrb = ParameterReplayRequest.newBuilder(); Map> params = new HashMap<>(); for (var pid : paramList) { prrb.addNameFilter(pid.id); params.computeIfAbsent(pid.getParameter(), k -> new ArrayList<>()).add(pid); } replayOpts.setParameterRequest(prrb.build()); Processor processor = ProcessorFactory.create(yamcsInstance, "api_replay" + count.incrementAndGet(), "ArchiveRetrieval", "internal", replayOpts); TimeAndCount tc = new TimeAndCount(Instant.MIN_INSTANT, 0); ParameterConsumer prmConsumer = new ParameterConsumer() { @Override public void updateItems(int subscriptionId, List pvalues) { List pvaluesWithIds = new ArrayList<>(params.size()); for (ParameterValue pv : pvalues) { var pids = params.get(pv.getParameter()); if (pids != null) { if (opts.ascending()) { tc.time = Math.max(pv.getGenerationTime(), tc.time); } else { tc.time = Math.min(pv.getGenerationTime(), tc.time); } for (var pid : pids) { if (pid.getPath() != null) { pv = AggregateUtil.extractMember(pv, pid.getPath()); } pvaluesWithIds.add(new ParameterValueWithId(pv, pid.getId())); } } } try { consumer.accept(pvaluesWithIds); } catch (ConsumerAbortException e) { processor.quit(); } ; } }; processor.getParameterRequestManager().addRequest(params.keySet(), prmConsumer); processor.startAsync(); processor.awaitRunning(); processor.awaitTerminated(); return tc; } private ParameterValueArray toScalarPva(ParameterValue pv, ParameterRetrievalOptions opts) { long[] timestamps = new long[] { pv.getGenerationTime() }; ValueArray engValues = null; if (opts.retrieveEngValues() && pv.getEngValue() != null) { engValues = new ValueArray(pv.getEngValue().getType(), 1); engValues.setValue(0, pv.getEngValue()); } ValueArray rawValues = null; if (opts.retrieveRawValues() && pv.getRawValue() != null) { rawValues = new ValueArray(pv.getRawValue().getType(), 1); rawValues.setValue(0, pv.getRawValue()); } org.yamcs.yarch.protobuf.Db.ParameterStatus[] paramStatus = null; if (opts.retrieveParameterStatus()) { paramStatus = new org.yamcs.yarch.protobuf.Db.ParameterStatus[] { pv.getStatus().toProtoBuf(false) }; } return new ParameterValueArray(timestamps, engValues, rawValues, paramStatus); } private ExecutorService createExecutor(int numThreads) { return Executors.newFixedThreadPool(numThreads, new ThreadFactory() { private int count = 1; @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r); thread.setName("ParameterRetrievalService-" + count++); return thread; } }); } static final class TimeAndCount { long time; int count; public TimeAndCount(long time, int count) { this.time = time; this.count = count; } public boolean isValid() { return this.time != TimeEncoding.INVALID_INSTANT; } @Override public String toString() { return "TimeAndCount [time=" + TimeEncoding.toString(time) + ", count=" + count + "]"; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy