Unverified Commit 522ec861 authored by Kay Kasemir's avatar Kay Kasemir Committed by GitHub
Browse files

Merge pull request #2367 from ControlSystemStudio/arch_prefix

Archive: Treat ca://, pva:// as equivalent
parents b724ec3e c75cd418
Pipeline #119512 passed with stage
in 14 minutes and 49 seconds
......@@ -7,7 +7,7 @@
******************************************************************************/
package org.csstudio.archive.ts.reader;
import static org.csstudio.archive.ts.reader.TSArchiveReaderFactory.logger;
import static org.phoebus.archive.reader.ArchiveReaders.logger;
import java.sql.Connection;
import java.sql.PreparedStatement;
......
......@@ -7,7 +7,7 @@
******************************************************************************/
package org.csstudio.archive.ts.reader;
import static org.csstudio.archive.ts.reader.TSArchiveReaderFactory.logger;
import static org.phoebus.archive.reader.ArchiveReaders.logger;
import java.sql.Connection;
import java.sql.PreparedStatement;
......@@ -37,6 +37,7 @@ import org.phoebus.archive.reader.ArchiveReader;
import org.phoebus.archive.reader.UnknownChannelException;
import org.phoebus.archive.reader.ValueIterator;
import org.phoebus.framework.rdb.RDBConnectionPool;
import org.phoebus.pv.PVPool;
import org.phoebus.util.time.TimestampFormats;
/** Archive reader for TimestampDB
......@@ -368,7 +369,11 @@ public class TSArchiveReader implements ArchiveReader
return new ArrayValueIterator(values);
}
/** @param name Channel name
* @return Numeric channel ID
* @throws UnknownChannelException when channel not known
* @throws Exception on error
*/
int getChannelID(final String name) throws UnknownChannelException, Exception
{
final Connection connection = pool.getConnection();
......@@ -379,14 +384,22 @@ public class TSArchiveReader implements ArchiveReader
{
if (Preferences.timeout_secs > 0)
statement.setQueryTimeout(Preferences.timeout_secs);
statement.setString(1, name);
try (final ResultSet result = statement.executeQuery())
// Loop over variants
for (String variant : PVPool.getNameVariants(name, org.csstudio.trends.databrowser3.preferences.Preferences.equivalent_pv_prefixes))
{
if (!result.next())
throw new UnknownChannelException(name);
final int channel_id = result.getInt(1);
return channel_id;
statement.setString(1, variant);
try (final ResultSet result = statement.executeQuery())
{
if (result.next())
{
final int channel_id = result.getInt(1);
logger.log(Level.FINE, () -> "Found '" + name + "' as '" + variant + "' (" + channel_id + ")");
return channel_id;
}
}
}
// Nothing found
throw new UnknownChannelException(name);
}
finally
{
......
......@@ -7,8 +7,6 @@
******************************************************************************/
package org.csstudio.archive.ts.reader;
import java.util.logging.Logger;
import org.phoebus.archive.reader.ArchiveReader;
import org.phoebus.archive.reader.spi.ArchiveReaderFactory;
......@@ -26,9 +24,6 @@ import org.phoebus.archive.reader.spi.ArchiveReaderFactory;
@SuppressWarnings("nls")
public class TSArchiveReaderFactory implements ArchiveReaderFactory
{
/** Common logger */
public static final Logger logger = Logger.getLogger(TSArchiveReaderFactory.class.getPackageName());
/** Data source prefix */
public static final String PREFIX = "ts:";
......
......@@ -7,7 +7,7 @@
******************************************************************************/
package org.csstudio.archive.ts.reader;
import static org.csstudio.archive.ts.reader.TSArchiveReaderFactory.logger;
import static org.phoebus.archive.reader.ArchiveReaders.logger;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
......
/*******************************************************************************
* Copyright (c) 2010-2020 Oak Ridge National Laboratory.
* Copyright (c) 2010-2022 Oak Ridge National Laboratory.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
......@@ -88,6 +88,8 @@ public class Preferences
/** Setting */
@Preference public static boolean drop_failed_archives;
/** Setting */
@Preference public static String[] equivalent_pv_prefixes;
/** Setting */
@Preference public static boolean use_trace_names;
/** Setting */
@Preference public static boolean prompt_for_raw_data_request;
......
/*******************************************************************************
* Copyright (c) 2017 Oak Ridge National Laboratory.
* Copyright (c) 2017-2022 Oak Ridge National Laboratory.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
......
/*******************************************************************************
* Copyright (c) 2017-2020 Oak Ridge National Laboratory.
* Copyright (c) 2017-2022 Oak Ridge National Laboratory.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
......@@ -28,6 +28,7 @@ import org.phoebus.archive.reader.AveragedValueIterator;
import org.phoebus.archive.reader.UnknownChannelException;
import org.phoebus.archive.reader.ValueIterator;
import org.phoebus.framework.rdb.RDBConnectionPool;
import org.phoebus.pv.PVPool;
import org.phoebus.util.time.TimeDuration;
/** {@link ArchiveReader} for RDB
......@@ -310,11 +311,15 @@ public class RDBArchiveReader implements ArchiveReader
final ValueIterator raw_data = getRawValues(channel_id, start, end);
// If there weren't that many, that's it
final int actual = counted;
if (counted < count)
{
logger.log(Level.FINER, () -> name + " has only " + actual + " samples, using raw data");
return raw_data;
}
// Else: Perform averaging to reduce sample count
final double seconds = TimeDuration.toSecondsDouble(Duration.between(start, end)) / count;
logger.log(Level.FINER, () -> name + " has " + actual + " samples, averaging into " + count + " bins");
return new AveragedValueIterator(raw_data, seconds);
}
......@@ -327,26 +332,33 @@ public class RDBArchiveReader implements ArchiveReader
int getChannelID(final String name) throws UnknownChannelException, Exception
{
final Connection connection = pool.getConnection();
try
try (final PreparedStatement statement = connection.prepareStatement(sql.channel_sel_by_name))
{
final PreparedStatement statement = connection.prepareStatement(sql.channel_sel_by_name);
addForCancellation(statement);
try
{
if (RDBPreferences.timeout_secs > 0)
statement.setQueryTimeout(RDBPreferences.timeout_secs);
statement.setString(1, name);
final ResultSet result = statement.executeQuery();
if (!result.next())
throw new UnknownChannelException(name);
final int channel_id = result.getInt(1);
result.close();
return channel_id;
// Loop over variants
for (String variant : PVPool.getNameVariants(name, org.csstudio.trends.databrowser3.preferences.Preferences.equivalent_pv_prefixes))
{
statement.setString(1, variant);
try (final ResultSet result = statement.executeQuery())
{
if (result.next())
{
final int channel_id = result.getInt(1);
logger.log(Level.FINE, () -> "Found '" + name + "' as '" + variant + "' (" + channel_id + ")");
return channel_id;
}
}
}
// Nothing found
throw new UnknownChannelException(name);
}
finally
{
removeFromCancellation(statement);
statement.close();
}
}
finally
......
......@@ -92,6 +92,27 @@ use_default_archives=false
# return an error (cannot connect, ...) should be queried again for the given channel.
drop_failed_archives=true
# With EPICS IOCs from release 7 on, the PVs
# "xxx", "ca://xxx" and "pva://xxx" all refer
# to the same record "xxx" on the IOC.
#
# When the plot requests "pva://xxx", the archive might still
# trace that channel as "ca://xxx" or "xxx".
# Alternatively, the archive might already track the channel
# as "pva://xxx" while data browser plots still use "ca://xxx"
# or just "xxx".
# This preference setting instructs the data browser
# to try all equivalent variants. If any types are listed,
# just "xxx" without any prefix will also be checked in addition
# to the listed types.
#
# The default of setting of "ca, pva" supports the seamless
# transition between the key protocols.
#
# When `equivalent_pv_prefixes` is empty,
# the PV name is used as is without looking for any equivalent names.
equivalent_pv_prefixes=ca, pva
# Re-scale behavior when archived data arrives: NONE, STAGGER
archive_rescale=STAGGER
......
/*******************************************************************************
* Copyright (c) 2017-2020 Oak Ridge National Laboratory.
* Copyright (c) 2017-2022 Oak Ridge National Laboratory.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
......@@ -11,8 +11,10 @@ import static org.phoebus.pv.PV.logger;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.logging.Level;
import org.phoebus.framework.preferences.AnnotatedPreferences;
......@@ -61,7 +63,7 @@ public class PVPool
final private static Map<String, PVFactory> factories = new HashMap<>();
/** Default PV name type prefix */
@Preference(name="default") private static String default_type;
@Preference(name="default") public static String default_type;
static
{
......@@ -85,6 +87,91 @@ public class PVPool
}
}
/** Combination of type and name, <code>type://name</name> */
public static class TypedName
{
/** PV Type */
public final String type;
/** Name */
public final String name;
/** Analyze PV name
* @param type_name PV Name, "name..." or "type://name..."
* @return {@link TypedName}
*/
public static TypedName analyze(final String type_name)
{
final String type, name;
if (type_name.startsWith("="))
{ // Special handling of equations, treating "=...." as "eq://...."
type = FormulaPVFactory.TYPE;
name = type_name.substring(1);
}
else
{
final int sep = type_name.indexOf(SEPARATOR);
if (sep > 0)
{
type = type_name.substring(0, sep);
name = type_name.substring(sep+SEPARATOR.length());
}
else
{
type = default_type;
name = type_name;
}
}
return new TypedName(type, name);
}
/** @param type "type"
* @param name "name"
* @return "type://name"
*/
public static String format(final String type, final String name)
{
return type + SEPARATOR + name;
}
private TypedName(final String type, final String name)
{
this.type = type;
this.name = name;
}
@Override
public String toString()
{
return format(type, name);
}
}
/** @param name PV Name, may be "xxx" or "type://xxx"
* @param equivalent_pv_prefixes List of equivalent PV prefixes (types), e.g. "ca", "pva"
* @return Set of equivalent names, e.g. "xxx", "ca://xxx", "pva://xxx"
*/
public static Set<String> getNameVariants(final String name, final String [] equivalent_pv_prefixes)
{
// First, look for name as given
final Set<String> variants = new LinkedHashSet<>();
variants.add(name);
if (equivalent_pv_prefixes != null && equivalent_pv_prefixes.length > 0)
{ // Optionally, if the original name is one of the equivalent types ...
final TypedName typed = TypedName.analyze(name);
for (String type : equivalent_pv_prefixes)
if (type.equals(typed.type))
{
// .. add equivalent prefixes, starting with base name
variants.add(typed.name);
for (String variant : equivalent_pv_prefixes)
variants.add(TypedName.format(variant, typed.name));
break;
}
}
return variants;
}
/** PV Pool
* SYNC on 'pool':
* Otherwise, two threads concurrently looking for a new PV would both add it.
......@@ -116,13 +203,13 @@ public class PVPool
{
if (name.isBlank())
throw new Exception("Empty PV name");
final String[] prefix_base = analyzeName(name);
final PVFactory factory = factories.get(prefix_base[0]);
final TypedName type_name = TypedName.analyze(name);
final PVFactory factory = factories.get(type_name.type);
if (factory == null)
throw new Exception(name + " has unknown PV type '" + prefix_base[0] + "'");
throw new Exception(name + " has unknown PV type '" + type_name.type + "'");
final String core_name = factory.getCoreName(name);
final ReferencedEntry<PV> ref = pool.createOrGet(core_name, () -> createPV(factory, name, prefix_base[1]));
final ReferencedEntry<PV> ref = pool.createOrGet(core_name, () -> createPV(factory, name, type_name.name));
logger.log(Level.CONFIG, () -> "PV '" + ref.getEntry().getName() + "' references: " + ref.getReferences());
return ref.getEntry();
}
......@@ -140,36 +227,6 @@ public class PVPool
return null;
}
/** Analyze PV name
* @param name PV Name, "base..." or "prefix://base..."
* @return Array with type (or default) and base name
*/
private static String[] analyzeName(final String name)
{
final String type, base;
if (name.startsWith("="))
{ // Special handling of equations, treating "=...." as "eq://...."
type = FormulaPVFactory.TYPE;
base = name.substring(1);
}
else
{
final int sep = name.indexOf(SEPARATOR);
if (sep > 0)
{
type = name.substring(0, sep);
base = name.substring(sep+SEPARATOR.length());
}
else
{
type = default_type;
base = name;
}
}
return new String[] { type, base };
}
/** @param pv PV to be released */
public static void releasePV(final PV pv)
{
......
/*******************************************************************************
* Copyright (c) 2014-2020 Oak Ridge National Laboratory.
* Copyright (c) 2014-2022 Oak Ridge National Laboratory.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
......@@ -61,7 +61,7 @@ public class LocalPVFactory implements PVFactory
final String[] ntv = ValueHelper.parseName(base_name);
// Actual name: loc://the_pv without <type> or (initial value)
final String actual_name = LocalPVFactory.TYPE + PVPool.SEPARATOR + ntv[0];
final String actual_name = PVPool.TypedName.format(LocalPVFactory.TYPE, ntv[0]);
// Info for initial value, null if nothing provided
final List<String> initial_value = ValueHelper.splitInitialItems(ntv[2]);
......
/*******************************************************************************
* Copyright (c) 2017 Oak Ridge National Laboratory.
* Copyright (c) 2017-2022 Oak Ridge National Laboratory.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
......@@ -7,13 +7,16 @@
******************************************************************************/
package org.phoebus.pv;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.hasItem;
import static org.junit.Assert.assertThat;
import static org.hamcrest.MatcherAssert.assertThat;
import java.util.Collection;
import java.util.Set;
import java.util.prefs.Preferences;
import org.junit.Test;
import org.phoebus.pv.PVPool.TypedName;
import org.phoebus.pv.ca.JCA_Preferences;
/** @author Kay Kasemir */
......@@ -29,6 +32,42 @@ public class PVPoolTest
assertThat(prefs, hasItem("sim"));
}
@Test
public void analyzePVs()
{
TypedName type_name = TypedName.analyze("pva://ramp");
assertThat(type_name.type, equalTo("pva"));
assertThat(type_name.name, equalTo("ramp"));
assertThat(type_name.toString(), equalTo("pva://ramp"));
type_name = TypedName.analyze("ramp");
assertThat(type_name.type, equalTo(PVPool.default_type));
assertThat(type_name.name, equalTo("ramp"));
assertThat(type_name.toString(), equalTo(PVPool.default_type + "://ramp"));
}
@Test
public void equivalentPVs()
{
// Given "ramp" or "ca://ramp", all the other variants should be considered
final String[] equivalent_pv_prefixes = new String[] { "ca", "pva" };
Set<String> pvs = PVPool.getNameVariants("pva://ramp", equivalent_pv_prefixes);
assertThat(pvs.size(), equalTo(3));
assertThat(pvs, hasItem("ramp"));
assertThat(pvs, hasItem("ca://ramp"));
assertThat(pvs, hasItem("pva://ramp"));
// For loc or sim which are not in the equivalent list, pass name through
pvs = PVPool.getNameVariants("loc://ramp", equivalent_pv_prefixes);
assertThat(pvs.size(), equalTo(1));
assertThat(pvs, hasItem("loc://ramp"));
pvs = PVPool.getNameVariants("sim://ramp", equivalent_pv_prefixes);
assertThat(pvs.size(), equalTo(1));
assertThat(pvs, hasItem("sim://ramp"));
}
@Test
public void dumpPreferences() throws Exception
{
......
......@@ -91,6 +91,30 @@ in this example replacing the original one::
archive-engine.sh -engine Demo -import Demo.xml -port 4812 -replace_engine
PV Name Details
---------------
The archive engine uses CS-Studio PV names.
"ca://xxxx" will force a Channel Access connection,
"pva://xxxx" will force a PV Access connection,
and just "xxxx" will use the default PV type
configurable via
org.phoebus.pv/default=ca
Since EPICS 7, IOCs can support both protocols.
"xxxx", "ca://xxxx" and "pva://xxxx" will thus
refer to the same record on the IOC.
The preference setting
org.csstudio.archive/equivalent_pv_prefixes=ca, pva
causes the archive engine to treat them equivalent as well.
For details, refer to the description of the
`equivalent_pv_prefixes` preference setting.
Run the Archive Engine
----------------------
......
/*******************************************************************************
* Copyright (c) 2018-2021 Oak Ridge National Laboratory.
* Copyright (c) 2018-2022 Oak Ridge National Laboratory.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
......@@ -10,6 +10,7 @@ package org.csstudio.archive;
import java.io.File;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.TimeUnit;
......@@ -315,6 +316,7 @@ public class Engine
logger.log(Level.INFO, "URL : " + url);
logger.log(Level.INFO, "Replace engine : " + replace_engine);
logger.log(Level.INFO, "Duplicate channels : " + duplicates);
logger.log(Level.INFO, "Equiv. PV prefixes : " + Arrays.toString(Preferences.equivalent_pv_prefixes));
try
(
......
/*******************************************************************************
* Copyright (c) 2018-2020 Oak Ridge National Laboratory.
* Copyright (c) 2018-2022 Oak Ridge National Laboratory.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
......@@ -25,6 +25,7 @@ public class Preferences
@Preference public static String write_sample_table;
@Preference public static int max_text_sample_length;
@Preference public static boolean use_postgres_copy;
@Preference public static String[] equivalent_pv_prefixes;
@Preference public static int log_trouble_samples;
@Preference public static int log_overrun;
@Preference public static int write_period;
......
/*******************************************************************************
* Copyright (c) 2018-2021 Oak Ridge National Laboratory.
* Copyright (c) 2018-2022 Oak Ridge National Laboratory.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
......@@ -17,6 +17,7 @@ import java.sql.Statement;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import org.csstudio.archive.Preferences;
......@@ -26,6 +27,7 @@ import org.csstudio.archive.engine.model.EngineModel;
import org.csstudio.archive.engine.model.SampleMode;
import org.csstudio.archive.writer.rdb.TimestampHelper;
import org.phoebus.framework.rdb.RDBInfo;
import org.phoebus.pv.PVPool;
@SuppressWarnings("nls")
public class RDBConfig implements AutoCloseable
......@@ -195,28 +197,35 @@ public class RDBConfig implements AutoCloseable
/** @param group_id Group where to add channel
* @param duplicates How to handle duplicate channels
* @param name Name of channel
* @param monitor
* @param period
* @param delta
* @param enable