package org.yamcs.web.rest.archive; import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStreamWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.yamcs.api.MediaType; import org.yamcs.parameter.ParameterValueWithId; import org.yamcs.protobuf.Pvalue.ParameterData; import org.yamcs.protobuf.Pvalue.ParameterValue; import org.yamcs.protobuf.Pvalue.TimeSeries; import org.yamcs.protobuf.SchemaPvalue; import org.yamcs.protobuf.Yamcs.EndAction; import org.yamcs.protobuf.Yamcs.NamedObjectId; import org.yamcs.protobuf.Yamcs.ParameterReplayRequest; import org.yamcs.protobuf.Yamcs.ReplayRequest; import org.yamcs.protobuf.Yamcs.ReplaySpeed; import org.yamcs.protobuf.Yamcs.ReplaySpeed.ReplaySpeedType; import org.yamcs.utils.ParameterFormatter; import org.yamcs.utils.TimeEncoding; import org.yamcs.web.BadRequestException; import org.yamcs.web.HttpException; import org.yamcs.web.InternalServerErrorException; import org.yamcs.web.rest.RestHandler; import org.yamcs.web.rest.RestParameterReplayListener; import org.yamcs.web.rest.RestReplayListener; import org.yamcs.web.rest.RestRequest; import org.yamcs.web.rest.archive.RestDownsampler.Sample; import org.yamcs.xtce.FloatParameterType; import org.yamcs.xtce.IntegerParameterType; import org.yamcs.xtce.Parameter; import org.yamcs.xtce.ParameterType; import org.yamcs.xtce.XtceDb; import org.yamcs.xtceproc.XtceDbFactory; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufOutputStream; /** * provides parameters by doing replays. In general, if possible, avoid replays and use the parameter archive (much faster) * @author nm * */ public class ArchiveParameterReplayRestHandler extends RestHandler { private static final Logger log = LoggerFactory.getLogger(ArchiveParameterReplayRestHandler.class); /** * A series is a list of samples that are determined in one-pass while processing a stream result. * Final API unstable. * <p> * If no query parameters are defined, the series covers *all* data. * @param req * rest request * @throws HttpException */ public void getParameterSamples(RestRequest req) throws HttpException { String instance = verifyInstance(req, req.getRouteParam("instance")); XtceDb mdb = XtceDbFactory.getInstance(instance); Parameter p = verifyParameter(req, mdb, req.getRouteParam("name")); ParameterType ptype = p.getParameterType(); if ((ptype != null) && (!(ptype instanceof FloatParameterType) && !(ptype instanceof IntegerParameterType))) { throw new BadRequestException("Only integer or float parameters can be sampled. Got " + ptype.getTypeAsString()); } ReplayRequest.Builder rr = ReplayRequest.newBuilder().setEndAction(EndAction.QUIT); rr.setSpeed(ReplaySpeed.newBuilder().setType(ReplaySpeedType.AFAP)); NamedObjectId id = NamedObjectId.newBuilder().setName(p.getQualifiedName()).build(); rr.setParameterRequest(ParameterReplayRequest.newBuilder().addNameFilter(id)); if (req.hasQueryParameter("start")) { rr.setStart(req.getQueryParameterAsDate("start")); } rr.setStop(req.getQueryParameterAsDate("stop", TimeEncoding.getWallclockTime())); RestDownsampler sampler = new RestDownsampler(rr.getStop()); RestReplays.replay(instance, req.getAuthToken(), rr.build(), new RestReplayListener() { @Override public void onParameterData(List<ParameterValueWithId> params) { for (ParameterValueWithId pvalid : params) { sampler.process(pvalid.getParameterValue()); } } @Override public void replayFinished() { TimeSeries.Builder series = TimeSeries.newBuilder(); for (Sample s : sampler.collect()) { series.addSample(ArchiveHelper.toGPBSample(s)); } completeOK(req, series.build(), SchemaPvalue.TimeSeries.WRITE); } @Override public void replayFailed(Throwable t) { completeWithError(req, new InternalServerErrorException(t)); } }); } public void listParameterHistory(RestRequest req) throws HttpException { String instance = verifyInstance(req, req.getRouteParam("instance")); XtceDb mdb = XtceDbFactory.getInstance(instance); String pathName = req.getRouteParam("name"); NameDescriptionWithId<Parameter> p = verifyParameterWithId(req, mdb, pathName); long pos = req.getQueryParameterAsLong("pos", 0); int limit = req.getQueryParameterAsInt("limit", 100); boolean noRepeat = req.getQueryParameterAsBoolean("norepeat", false); ReplayRequest rr = ArchiveHelper.toParameterReplayRequest(req, p.getItem(), true); if (req.asksFor(MediaType.CSV)) { ByteBuf buf = req.getChannelHandlerContext().alloc().buffer(); try (BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new ByteBufOutputStream(buf)))) { List<NamedObjectId> idList = Arrays.asList(p.getRequestedId()); ParameterFormatter csvFormatter = new ParameterFormatter(bw, idList); limit++; // Allow one extra line for the CSV header RestParameterReplayListener replayListener = new RestParameterReplayListener(pos, limit, req) { @Override public void onParameterData(List<ParameterValueWithId> params) { try { List<ParameterValue> pvlist = new ArrayList<>(); for(ParameterValueWithId pvalid: params) { pvlist.add(pvalid.toGbpParameterValue()); } csvFormatter.writeParameters(pvlist); } catch (IOException e) { log.error("Error while writing parameter line", e); completeWithError(req, new InternalServerErrorException(e)); } } public void replayFinished() { completeOK(req, MediaType.CSV, buf); } }; replayListener.setNoRepeat(noRepeat); RestReplays.replay(instance, req.getAuthToken(), rr, replayListener); } catch (IOException e) { throw new InternalServerErrorException(e); } } else { ParameterData.Builder resultb = ParameterData.newBuilder(); RestParameterReplayListener replayListener = new RestParameterReplayListener(pos, limit, req) { @Override public void onParameterData(List<ParameterValueWithId> params) { for(ParameterValueWithId pvalid: params) { resultb.addParameter(pvalid.toGbpParameterValue()); } } @Override public void replayFinished() { completeOK(req, resultb.build(), SchemaPvalue.ParameterData.WRITE); } }; replayListener.setNoRepeat(noRepeat); RestReplays.replay(instance, req.getAuthToken(), rr, replayListener); } } }