From 14d6bcaa1309e309f9643aec2e810022f63fe7d7 Mon Sep 17 00:00:00 2001 From: Fernando Santoro Date: Fri, 26 Mar 2021 17:58:00 -0400 Subject: [PATCH 01/12] Feature: New TDI python fun to read data from Influxdb from an MDSplus tree --- tdi/python/influxSignal.py | 52 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 tdi/python/influxSignal.py diff --git a/tdi/python/influxSignal.py b/tdi/python/influxSignal.py new file mode 100644 index 0000000000..15d3c2dfc7 --- /dev/null +++ b/tdi/python/influxSignal.py @@ -0,0 +1,52 @@ +import MDSplus + +try: + from influxdb import InfluxDBClient +except: + print( + "You must install the `influxdb` python package.") + exit(1) + +def influxSignal(dbname, measurement, field_value): + """Instantiate a connection to the InfluxDB.""" + host = 'localhost' + port = 8086 + user = 'admin' + password = 'password' + + dbname = dbname.data() + measurement = measurement.data() + field_value = field_value.data() + + MDSplus.Data.execute('TreeOpen("influx", 0)') + start_end_times = MDSplus.Tree.getTimeContext() + print('Getting time context from the tree: {} '.format(start_end_times)) + start_time = str(int(start_end_times[0])) + end_time = str(int(start_end_times[1])) + + client = InfluxDBClient(host, port, user, password, dbname) + # example influxDB query: + # dbname = 'NOAA_water_database' + # measurement = h2o_feet + # field_value = water_level + # 'SELECT "water_level" FROM "h2o_feet" WHERE time >= 1568745000000000000 AND time <= 1568750760000000000;' + query = 'SELECT "%s" FROM "%s" WHERE time >= %s AND time <= %s;' % (field_value, measurement, start_time, end_time) + print('Query: %s' % query) + + result = client.query(query, params={'epoch': 'ms'}) + + data = list(result.get_points()) + + valueData = [None] * len(data) + timeData = [None] * len(data) + + i = 0 + for row in data: + valueData[i] = float(row['water_level']) + timeData[i] = row['time'] + i += 1 + + values = MDSplus.Float32Array(valueData) + times = MDSplus.Uint64Array(timeData) + + return MDSplus.Signal(values, None, times) \ No newline at end of file From 8ac1227b9069b7b71eecf8406b2a45775cc47915 Mon Sep 17 00:00:00 2001 From: Fernando Santoro Date: Tue, 30 Mar 2021 10:43:13 -0400 Subject: [PATCH 02/12] Only use getTimeContext. Query was improved to be more general --- tdi/python/influxSignal.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tdi/python/influxSignal.py b/tdi/python/influxSignal.py index 15d3c2dfc7..d840ad458a 100644 --- a/tdi/python/influxSignal.py +++ b/tdi/python/influxSignal.py @@ -18,8 +18,8 @@ def influxSignal(dbname, measurement, field_value): measurement = measurement.data() field_value = field_value.data() - MDSplus.Data.execute('TreeOpen("influx", 0)') start_end_times = MDSplus.Tree.getTimeContext() + print('Getting time context from the tree: {} '.format(start_end_times)) start_time = str(int(start_end_times[0])) end_time = str(int(start_end_times[1])) @@ -30,7 +30,7 @@ def influxSignal(dbname, measurement, field_value): # measurement = h2o_feet # field_value = water_level # 'SELECT "water_level" FROM "h2o_feet" WHERE time >= 1568745000000000000 AND time <= 1568750760000000000;' - query = 'SELECT "%s" FROM "%s" WHERE time >= %s AND time <= %s;' % (field_value, measurement, start_time, end_time) + query = 'SELECT "%s" AS value FROM "%s" WHERE time >= %s AND time <= %s;' % (field_value, measurement, start_time, end_time) print('Query: %s' % query) result = client.query(query, params={'epoch': 'ms'}) @@ -42,7 +42,7 @@ def influxSignal(dbname, measurement, field_value): i = 0 for row in data: - valueData[i] = float(row['water_level']) + valueData[i] = float(row['value']) timeData[i] = row['time'] i += 1 From 2e93f5e51c5dcc09a4156dd46174d23f6e5e258c Mon Sep 17 00:00:00 2001 From: Fernando Santoro Date: Mon, 5 Apr 2021 18:26:20 -0400 Subject: [PATCH 03/12] Add small comment --- tdi/python/influxSignal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tdi/python/influxSignal.py b/tdi/python/influxSignal.py index d840ad458a..4207c465b0 100644 --- a/tdi/python/influxSignal.py +++ b/tdi/python/influxSignal.py @@ -27,7 +27,7 @@ def influxSignal(dbname, measurement, field_value): client = InfluxDBClient(host, port, user, password, dbname) # example influxDB query: # dbname = 'NOAA_water_database' - # measurement = h2o_feet + # measurement = h2o_feet == Table # field_value = water_level # 'SELECT "water_level" FROM "h2o_feet" WHERE time >= 1568745000000000000 AND time <= 1568750760000000000;' query = 'SELECT "%s" AS value FROM "%s" WHERE time >= %s AND time <= %s;' % (field_value, measurement, start_time, end_time) From 3ac32e89e2ee4d362adcc5d79bbd7d28276b3812 Mon Sep 17 00:00:00 2001 From: Fernando Santoro Date: Tue, 22 Jun 2021 02:04:32 -0400 Subject: [PATCH 04/12] Read credentials from file --- tdi/python/influxSignal.py | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/tdi/python/influxSignal.py b/tdi/python/influxSignal.py index 4207c465b0..e1492f9e9a 100644 --- a/tdi/python/influxSignal.py +++ b/tdi/python/influxSignal.py @@ -7,16 +7,29 @@ "You must install the `influxdb` python package.") exit(1) -def influxSignal(dbname, measurement, field_value): +def influxSignal(dbname, measurement, field_value, address, credentials): """Instantiate a connection to the InfluxDB.""" - host = 'localhost' + host = address.data() port = 8086 - user = 'admin' - password = 'password' + + username = '' + password = '' + try: + with open(credentials.data()) as cred_file: + lines = cred_file.readlines() + + if len(lines) < 2: + print("Failed to read credentials from file %s" %(credentials,)) + + username = lines[0].strip('\n') + password = lines[1].strip('\n') + + except IOError as e: + print("Failed to open credentials file %s" %(credentials,)) dbname = dbname.data() - measurement = measurement.data() - field_value = field_value.data() + #measurement = measurement.data() + #field_value = field_value.data() start_end_times = MDSplus.Tree.getTimeContext() @@ -24,7 +37,8 @@ def influxSignal(dbname, measurement, field_value): start_time = str(int(start_end_times[0])) end_time = str(int(start_end_times[1])) - client = InfluxDBClient(host, port, user, password, dbname) + client = InfluxDBClient(host, port, username, password, dbname) + # example influxDB query: # dbname = 'NOAA_water_database' # measurement = h2o_feet == Table @@ -49,4 +63,5 @@ def influxSignal(dbname, measurement, field_value): values = MDSplus.Float32Array(valueData) times = MDSplus.Uint64Array(timeData) - return MDSplus.Signal(values, None, times) \ No newline at end of file + return MDSplus.Signal(values, None, times) + From fc80ada3f738fb11efdd09da494d62fe3a638ab2 Mon Sep 17 00:00:00 2001 From: Fernando Santoro Date: Tue, 22 Jun 2021 17:03:05 -0400 Subject: [PATCH 05/12] Change input order. Add address and credentials --- tdi/python/influxSignal.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tdi/python/influxSignal.py b/tdi/python/influxSignal.py index e1492f9e9a..6261f5605a 100644 --- a/tdi/python/influxSignal.py +++ b/tdi/python/influxSignal.py @@ -19,13 +19,13 @@ def influxSignal(dbname, measurement, field_value, address, credentials): lines = cred_file.readlines() if len(lines) < 2: - print("Failed to read credentials from file %s" %(credentials,)) + print("Failed to read credentials from file %s" %(credentials.data(),)) username = lines[0].strip('\n') password = lines[1].strip('\n') except IOError as e: - print("Failed to open credentials file %s" %(credentials,)) + print("Failed to open credentials file %s" %(credentials.data(),)) dbname = dbname.data() #measurement = measurement.data() From 609ba06e41842033a20087293868d54be6637cea Mon Sep 17 00:00:00 2001 From: Fernando Santoro Date: Thu, 24 Jun 2021 10:14:12 -0400 Subject: [PATCH 06/12] Address and credentials input as MDSplus tree nodes --- tdi/python/influxSignal.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tdi/python/influxSignal.py b/tdi/python/influxSignal.py index 6261f5605a..b8f5539010 100644 --- a/tdi/python/influxSignal.py +++ b/tdi/python/influxSignal.py @@ -28,8 +28,8 @@ def influxSignal(dbname, measurement, field_value, address, credentials): print("Failed to open credentials file %s" %(credentials.data(),)) dbname = dbname.data() - #measurement = measurement.data() - #field_value = field_value.data() + measurement = measurement.data() + field_value = field_value.data() start_end_times = MDSplus.Tree.getTimeContext() From 745cc8b1bd50c71b8e6de14019b0c38ff0a4c844 Mon Sep 17 00:00:00 2001 From: Fernando Santoro Date: Thu, 24 Jun 2021 14:53:52 -0400 Subject: [PATCH 07/12] Add small comment --- tdi/python/influxSignal.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tdi/python/influxSignal.py b/tdi/python/influxSignal.py index b8f5539010..6507a36ff5 100644 --- a/tdi/python/influxSignal.py +++ b/tdi/python/influxSignal.py @@ -1,3 +1,28 @@ +# +# Copyright (c) 2021, Massachusetts Institute of Technology All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# Redistributions in binary form must reproduce the above copyright notice, this +# list of conditions and the following disclaimer in the documentation and/or +# other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + import MDSplus try: @@ -31,6 +56,7 @@ def influxSignal(dbname, measurement, field_value, address, credentials): measurement = measurement.data() field_value = field_value.data() + # We get the start_time and the end_time of the query from the defined MDSplus tree time context start_end_times = MDSplus.Tree.getTimeContext() print('Getting time context from the tree: {} '.format(start_end_times)) From c484c6c00ff476030b269500e2a7ef52f6592164 Mon Sep 17 00:00:00 2001 From: Fernando Santoro Date: Mon, 28 Jun 2021 13:04:13 -0400 Subject: [PATCH 08/12] Consolidate WHERE and time start and end tiime --- tdi/python/influxSignal.py | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/tdi/python/influxSignal.py b/tdi/python/influxSignal.py index 6507a36ff5..5691b15c46 100644 --- a/tdi/python/influxSignal.py +++ b/tdi/python/influxSignal.py @@ -32,7 +32,7 @@ "You must install the `influxdb` python package.") exit(1) -def influxSignal(dbname, measurement, field_value, address, credentials): +def influxSignal(dbname, measurement, field_value, where, address, credentials): """Instantiate a connection to the InfluxDB.""" host = address.data() port = 8086 @@ -56,13 +56,37 @@ def influxSignal(dbname, measurement, field_value, address, credentials): measurement = measurement.data() field_value = field_value.data() - # We get the start_time and the end_time of the query from the defined MDSplus tree time context + if where == '': + return + + whereList = [where.data()] + + # We get the start_time and the end_time of the query from the defined MDSplus tree time context: start_end_times = MDSplus.Tree.getTimeContext() - print('Getting time context from the tree: {} '.format(start_end_times)) - start_time = str(int(start_end_times[0])) - end_time = str(int(start_end_times[1])) - + print('Getting time context from the tree: %s '%(start_end_times,)) + start = int(start_end_times[0]) + end = int(start_end_times[1]) + + startTimeQuery = '' + endTimeQuery = '' + + # Convert to nanosecond UNIX timestamp + startTimeQuery = 'time > %d' % (start * 1000000,) + + # Convert to nanosecond UNIX timestamp + endTimeQuery = 'time < %d' % (end * 1000000,) + + if startTimeQuery != '': + whereList.append(startTimeQuery) + + if endTimeQuery != '': + whereList.append(endTimeQuery) + + where = '' + if len(whereList) > 0: + where = 'WHERE %s' % (' AND '.join(whereList),) + client = InfluxDBClient(host, port, username, password, dbname) # example influxDB query: @@ -70,7 +94,7 @@ def influxSignal(dbname, measurement, field_value, address, credentials): # measurement = h2o_feet == Table # field_value = water_level # 'SELECT "water_level" FROM "h2o_feet" WHERE time >= 1568745000000000000 AND time <= 1568750760000000000;' - query = 'SELECT "%s" AS value FROM "%s" WHERE time >= %s AND time <= %s;' % (field_value, measurement, start_time, end_time) + query = 'SELECT "%s" AS value FROM "%s" %s;' % (field_value, measurement, where) print('Query: %s' % query) result = client.query(query, params={'epoch': 'ms'}) From 5f80cf4f9019b2b283825d64230fb4f50473bf82 Mon Sep 17 00:00:00 2001 From: Fernando Santoro Date: Fri, 9 Jul 2021 16:59:52 -0400 Subject: [PATCH 09/12] Refactor code --- tdi/python/influxSignal.py | 96 ++++++++++++++++++++++++++------------ 1 file changed, 65 insertions(+), 31 deletions(-) diff --git a/tdi/python/influxSignal.py b/tdi/python/influxSignal.py index 5691b15c46..9f0b991592 100644 --- a/tdi/python/influxSignal.py +++ b/tdi/python/influxSignal.py @@ -32,50 +32,89 @@ "You must install the `influxdb` python package.") exit(1) -def influxSignal(dbname, measurement, field_value, where, address, credentials): - """Instantiate a connection to the InfluxDB.""" - host = address.data() - port = 8086 +def influxSignal(fieldKey, where, series=None, database=None, config=None, shotStartTime=None, shotEndTime=None): + tree = MDSplus.Tree() + + if series is None: + series = tree.getNode('\INFLUX_SERIES') + + if database is None: + database = tree.getNode('\INFLUX_DATABASE') + + if config is None: + config = tree.getNode('\INFLUX_CONFIG') + + if shotStartTime is None: + shotStartTime = tree.getNode('\INFLUX_START_TIME') + + if shotEndTime is None: + shotEndTime = tree.getNode('\INFLUX_END_TIME') + """Instantiate a connection to the InfluxDB.""" username = '' password = '' try: - with open(credentials.data()) as cred_file: - lines = cred_file.readlines() + with open(config.data()) as file: + lines = file.readlines() if len(lines) < 2: - print("Failed to read credentials from file %s" %(credentials.data(),)) - - username = lines[0].strip('\n') - password = lines[1].strip('\n') + print("Failed to read influx config from file %s" %(config.data(),)) + + host = lines[0].strip('\n') + username = lines[1].strip('\n') + password = lines[2].strip('\n') except IOError as e: - print("Failed to open credentials file %s" %(credentials.data(),)) + print("Failed to open credentials file %s" %(config.data(),)) + + port = 8086 + if ':' in host: + parts = host.split(':') + host = parts[0] + port = int(parts[1]) - dbname = dbname.data() - measurement = measurement.data() - field_value = field_value.data() + database = database.data() + series = series.data() + fieldKey = fieldKey.data() + shotStartTime = shotStartTime.data() + shotEndTime = shotEndTime.data() if where == '': return whereList = [where.data()] - # We get the start_time and the end_time of the query from the defined MDSplus tree time context: - start_end_times = MDSplus.Tree.getTimeContext() + timeContext = MDSplus.Tree.getTimeContext() + + startTime = shotStartTime + endTime = shotEndTime + + #print('Getting time context from the tree: %s '%(timeContext,)) + + # The time context is in seconds relative to the start of the shot. + if timeContext[0] is not None: + startTime = shotStartTime + (int(timeContext[0]) * 1000) + if timeContext[1] is not None: + endTime = shotStartTime + (int(timeContext[1]) * 1000) + + # TODO: timeContext[2] is the interval which influx supports, therefore we should support it too. - print('Getting time context from the tree: %s '%(start_end_times,)) - start = int(start_end_times[0]) - end = int(start_end_times[1]) + # Clamp the computed start/end time within the time bounds of the shot + if startTime < shotStartTime: + startTime = shotStartTime + if endTime > shotEndTime: + endTime = shotEndTime startTimeQuery = '' endTimeQuery = '' # Convert to nanosecond UNIX timestamp - startTimeQuery = 'time > %d' % (start * 1000000,) + if startTime is not None: + startTimeQuery = 'time > %d' % (startTime * 1000000,) # Convert to nanosecond UNIX timestamp - endTimeQuery = 'time < %d' % (end * 1000000,) + if endTime is not None: + endTimeQuery = 'time < %d' % (endTime * 1000000,) if startTimeQuery != '': whereList.append(startTimeQuery) @@ -87,15 +126,10 @@ def influxSignal(dbname, measurement, field_value, where, address, credentials): if len(whereList) > 0: where = 'WHERE %s' % (' AND '.join(whereList),) - client = InfluxDBClient(host, port, username, password, dbname) + client = InfluxDBClient(host, port, username, password, database) - # example influxDB query: - # dbname = 'NOAA_water_database' - # measurement = h2o_feet == Table - # field_value = water_level - # 'SELECT "water_level" FROM "h2o_feet" WHERE time >= 1568745000000000000 AND time <= 1568750760000000000;' - query = 'SELECT "%s" AS value FROM "%s" %s;' % (field_value, measurement, where) - print('Query: %s' % query) + query = 'SELECT "%s" AS value FROM "%s" %s;' % (fieldKey, series, where) + #print('Query: %s' % query) result = client.query(query, params={'epoch': 'ms'}) @@ -107,11 +141,11 @@ def influxSignal(dbname, measurement, field_value, where, address, credentials): i = 0 for row in data: valueData[i] = float(row['value']) - timeData[i] = row['time'] + timeData[i] = (row['time'] - shotStartTime) / 1000 i += 1 values = MDSplus.Float32Array(valueData) - times = MDSplus.Uint64Array(timeData) + times = MDSplus.Float32Array(timeData) return MDSplus.Signal(values, None, times) From 001a63f7e32276d53d23800dc0555d5d6377a0bf Mon Sep 17 00:00:00 2001 From: Fernando Santoro Date: Mon, 12 Jul 2021 10:39:38 -0400 Subject: [PATCH 10/12] Several changes to input parameters and variable names --- tdi/python/influxSignal.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tdi/python/influxSignal.py b/tdi/python/influxSignal.py index 9f0b991592..71c802e9b6 100644 --- a/tdi/python/influxSignal.py +++ b/tdi/python/influxSignal.py @@ -77,7 +77,7 @@ def influxSignal(fieldKey, where, series=None, database=None, config=None, shotS series = series.data() fieldKey = fieldKey.data() shotStartTime = shotStartTime.data() - shotEndTime = shotEndTime.data() + shotEndTime = shotEndTime.getDataNoRaise() if where == '': return @@ -102,15 +102,14 @@ def influxSignal(fieldKey, where, series=None, database=None, config=None, shotS # Clamp the computed start/end time within the time bounds of the shot if startTime < shotStartTime: startTime = shotStartTime - if endTime > shotEndTime: + if endTime is not None and endTime > shotEndTime: endTime = shotEndTime startTimeQuery = '' endTimeQuery = '' # Convert to nanosecond UNIX timestamp - if startTime is not None: - startTimeQuery = 'time > %d' % (startTime * 1000000,) + startTimeQuery = 'time > %d' % (startTime * 1000000,) # Convert to nanosecond UNIX timestamp if endTime is not None: From 617e2fdb6c5a8693447c0f285c5939007cc2dc17 Mon Sep 17 00:00:00 2001 From: Fernando Santoro Date: Mon, 12 Jul 2021 10:49:19 -0400 Subject: [PATCH 11/12] Fix to the 435st class so that the trig_time node is correctly populated with the star time of the shot --- pydevices/HtsDevices/acq2106_435st.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pydevices/HtsDevices/acq2106_435st.py b/pydevices/HtsDevices/acq2106_435st.py index 9e089d6d43..7ef382f63f 100755 --- a/pydevices/HtsDevices/acq2106_435st.py +++ b/pydevices/HtsDevices/acq2106_435st.py @@ -273,6 +273,10 @@ def lcma(arr): except Empty: continue + if self.dev.trig_time.getDataNoRaise() is None: + self.dev.trig_time.record = self.device_thread.trig_time - \ + ((self.device_thread.io_buffer_size / np.int32(0).nbytes) * dt) + buffer = np.right_shift(np.frombuffer(buf, dtype='int32'), 8) i = 0 for c in self.chans: From 0aa3db3ec5d1eab3738bbedd47823e5f5a53cac0 Mon Sep 17 00:00:00 2001 From: Fernando Santoro Date: Mon, 12 Jul 2021 20:33:25 -0400 Subject: [PATCH 12/12] Add file in kernel packaging list --- deploy/packaging/debian/kernel.noarch | 1 + deploy/packaging/redhat/kernel.noarch | 1 + 2 files changed, 2 insertions(+) diff --git a/deploy/packaging/debian/kernel.noarch b/deploy/packaging/debian/kernel.noarch index a972662fd8..6e5af350c3 100644 --- a/deploy/packaging/debian/kernel.noarch +++ b/deploy/packaging/debian/kernel.noarch @@ -156,6 +156,7 @@ ./usr/local/mdsplus/tdi/python/intersect1d.py ./usr/local/mdsplus/tdi/python/pyfun.py ./usr/local/mdsplus/tdi/python/setdiff1d.py +./usr/local/mdsplus/tdi/python/influxSignal.py ./usr/local/mdsplus/tdi/remote/GetManyExecute.fun ./usr/local/mdsplus/tdi/remote/ListConnections.fun ./usr/local/mdsplus/tdi/remote/MdsConnect.fun diff --git a/deploy/packaging/redhat/kernel.noarch b/deploy/packaging/redhat/kernel.noarch index 5c37684743..8d1aad760f 100644 --- a/deploy/packaging/redhat/kernel.noarch +++ b/deploy/packaging/redhat/kernel.noarch @@ -182,6 +182,7 @@ ./usr/local/mdsplus/tdi/python/intersect1d.py ./usr/local/mdsplus/tdi/python/pyfun.py ./usr/local/mdsplus/tdi/python/setdiff1d.py +./usr/local/mdsplus/tdi/python/influxSignal.py ./usr/local/mdsplus/tdi/remote ./usr/local/mdsplus/tdi/remote/GetManyExecute.fun ./usr/local/mdsplus/tdi/remote/ListConnections.fun