iotrace 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- iotrace/__cli__.py +75 -0
- iotrace/__init__.py +316 -0
- iotrace/logging.py +22 -0
- iotrace/py.typed +0 -0
- iotrace-0.1.0.dist-info/METADATA +274 -0
- iotrace-0.1.0.dist-info/RECORD +8 -0
- iotrace-0.1.0.dist-info/WHEEL +4 -0
- iotrace-0.1.0.dist-info/entry_points.txt +3 -0
iotrace/__cli__.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
import iotrace
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def main(argv: list[str] | None = None) -> None:
|
|
8
|
+
parser = argparse.ArgumentParser(
|
|
9
|
+
prog="iotrace",
|
|
10
|
+
description="Control NI IO Trace from the command line.",
|
|
11
|
+
)
|
|
12
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
13
|
+
|
|
14
|
+
# start
|
|
15
|
+
start_parser = subparsers.add_parser("start", help="Start tracing driver calls.")
|
|
16
|
+
start_parser.add_argument(
|
|
17
|
+
"--log-format",
|
|
18
|
+
choices=["none", "io-trace", "plain-text", "csv", "xml"],
|
|
19
|
+
default="none",
|
|
20
|
+
help="Log file format (default: none).",
|
|
21
|
+
)
|
|
22
|
+
start_parser.add_argument(
|
|
23
|
+
"--file",
|
|
24
|
+
default=None,
|
|
25
|
+
help="Path to the log file.",
|
|
26
|
+
)
|
|
27
|
+
start_parser.add_argument(
|
|
28
|
+
"--write-mode",
|
|
29
|
+
choices=["create", "append", "overwrite"],
|
|
30
|
+
default="create",
|
|
31
|
+
help="File write mode (default: create).",
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
# stop
|
|
35
|
+
stop_parser = subparsers.add_parser("stop", help="Stop tracing driver calls.")
|
|
36
|
+
stop_parser.add_argument(
|
|
37
|
+
"--close",
|
|
38
|
+
action="store_true",
|
|
39
|
+
help="Close NI IO Trace after stopping.",
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
args = parser.parse_args(argv)
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
if args.command == "start":
|
|
46
|
+
iotrace.launch_io_trace(window_state=iotrace.WindowState.MINIMIZED)
|
|
47
|
+
log_format_map = {
|
|
48
|
+
"none": iotrace.LogFileSetting.NO_FILE,
|
|
49
|
+
"io-trace": iotrace.LogFileSetting.IO_TRACE,
|
|
50
|
+
"plain-text": iotrace.LogFileSetting.PLAIN_TEXT,
|
|
51
|
+
"csv": iotrace.LogFileSetting.COMMA_SEPARATED,
|
|
52
|
+
"xml": iotrace.LogFileSetting.XML,
|
|
53
|
+
}
|
|
54
|
+
write_mode_map = {
|
|
55
|
+
"create": iotrace.FileWriteMode.CREATE_ONLY,
|
|
56
|
+
"append": iotrace.FileWriteMode.CREATE_OR_APPEND,
|
|
57
|
+
"overwrite": iotrace.FileWriteMode.CREATE_OR_OVERWRITE,
|
|
58
|
+
}
|
|
59
|
+
iotrace.start_tracing(
|
|
60
|
+
log_file_setting=log_format_map[args.log_format],
|
|
61
|
+
file_path=args.file,
|
|
62
|
+
file_write_mode=write_mode_map[args.write_mode],
|
|
63
|
+
)
|
|
64
|
+
print("Tracing started.")
|
|
65
|
+
|
|
66
|
+
elif args.command == "stop":
|
|
67
|
+
iotrace.stop_tracing()
|
|
68
|
+
print("Tracing stopped.")
|
|
69
|
+
if args.close:
|
|
70
|
+
iotrace.close_io_trace()
|
|
71
|
+
print("NI IO Trace closed.")
|
|
72
|
+
|
|
73
|
+
except iotrace.IoTraceError as exc:
|
|
74
|
+
print(f"Error: {exc}", file=sys.stderr)
|
|
75
|
+
sys.exit(1)
|
iotrace/__init__.py
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ctypes
|
|
4
|
+
import enum
|
|
5
|
+
import subprocess
|
|
6
|
+
import time
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"LogFileSetting",
|
|
11
|
+
"FileWriteMode",
|
|
12
|
+
"WindowState",
|
|
13
|
+
"StatusCode",
|
|
14
|
+
"IoTraceError",
|
|
15
|
+
"get_application_path",
|
|
16
|
+
"launch_io_trace",
|
|
17
|
+
"start_tracing",
|
|
18
|
+
"stop_tracing",
|
|
19
|
+
"log_message",
|
|
20
|
+
"close_io_trace",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class LogFileSetting(enum.IntEnum):
|
|
25
|
+
"""Controls the log file format used when tracing.
|
|
26
|
+
|
|
27
|
+
Members:
|
|
28
|
+
NO_FILE: Do not write a log file. Trace data is only visible in the
|
|
29
|
+
NI IO Trace GUI.
|
|
30
|
+
IO_TRACE: Write an NI IO Trace binary log file (``.nitrace``).
|
|
31
|
+
PLAIN_TEXT: Write a human-readable plain-text log file.
|
|
32
|
+
COMMA_SEPARATED: Write a comma-separated values (CSV) log file.
|
|
33
|
+
XML: Write an XML-formatted log file.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
NO_FILE = -1
|
|
37
|
+
IO_TRACE = 0
|
|
38
|
+
PLAIN_TEXT = 1
|
|
39
|
+
COMMA_SEPARATED = 2
|
|
40
|
+
XML = 3
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class FileWriteMode(enum.IntEnum):
|
|
44
|
+
"""Controls how the log file is created or opened.
|
|
45
|
+
|
|
46
|
+
Members:
|
|
47
|
+
CREATE_ONLY: Create a new file. Raises :class:`IoTraceError` if the
|
|
48
|
+
file already exists.
|
|
49
|
+
CREATE_OR_APPEND: Open an existing file and append to it, or create a
|
|
50
|
+
new file if it does not exist.
|
|
51
|
+
CREATE_OR_OVERWRITE: Overwrite an existing file, or create a new file
|
|
52
|
+
if it does not exist.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
CREATE_ONLY = 0
|
|
56
|
+
CREATE_OR_APPEND = 1
|
|
57
|
+
CREATE_OR_OVERWRITE = 2
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class WindowState(enum.IntEnum):
|
|
61
|
+
"""Controls the window state of the NI IO Trace application at launch.
|
|
62
|
+
|
|
63
|
+
Members:
|
|
64
|
+
HIDDEN: Launch the application with no visible window.
|
|
65
|
+
NORMAL: Launch the application in its default (restored) window state.
|
|
66
|
+
MAXIMIZED: Launch the application with the window maximized.
|
|
67
|
+
MINIMIZED: Launch the application with the window minimized.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
HIDDEN = 0
|
|
71
|
+
NORMAL = 1
|
|
72
|
+
MAXIMIZED = 2
|
|
73
|
+
MINIMIZED = 3
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class StatusCode(enum.IntEnum):
|
|
77
|
+
"""Status codes returned by NI IO Trace API calls.
|
|
78
|
+
|
|
79
|
+
``SUCCESS`` indicates the call completed without error. All other members
|
|
80
|
+
represent error conditions and are used to populate
|
|
81
|
+
:attr:`IoTraceError.status`.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
SUCCESS = 0
|
|
85
|
+
FAILED_NO_EXECUTE = -303200
|
|
86
|
+
FAILED_INCOMPATIBLE_STATE = -303201
|
|
87
|
+
FAILED_UNABLE_TO_OPEN_LOG_FILE = -303202
|
|
88
|
+
FAILED_GUI_CLOSED = -303203
|
|
89
|
+
FAILED_INVALID_SETTINGS = -303204
|
|
90
|
+
FAILED_BAD_PARAMETER = -303205
|
|
91
|
+
FAILED_INTERNAL_FAILURE = -303206
|
|
92
|
+
FAILED_INVALID_FILE_EXTENSION = -303207
|
|
93
|
+
FAILED_BUFFER_TOO_SMALL = -303208
|
|
94
|
+
FAILED_FILE_ALREADY_EXISTS = -303209
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class IoTraceError(Exception):
|
|
98
|
+
"""Raised when an NI IO Trace API call returns a non-success status.
|
|
99
|
+
|
|
100
|
+
Attributes:
|
|
101
|
+
status: The :class:`CommandStatus` that triggered the error.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
def __init__(self, status: StatusCode) -> None:
|
|
105
|
+
self.status = status
|
|
106
|
+
super().__init__(f"NI IO Trace API error: {status.name} ({status.value})")
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _check(status_code: int) -> None:
|
|
110
|
+
status = StatusCode(status_code)
|
|
111
|
+
if status != StatusCode.SUCCESS:
|
|
112
|
+
raise IoTraceError(status)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _load_dll() -> ctypes.WinDLL:
|
|
116
|
+
dll = ctypes.WinDLL("NiSpyLog")
|
|
117
|
+
|
|
118
|
+
# nispy_GetApplicationPath(char*, size_t) -> int
|
|
119
|
+
dll.nispy_GetApplicationPath.argtypes = [ctypes.c_char_p, ctypes.c_size_t]
|
|
120
|
+
dll.nispy_GetApplicationPath.restype = ctypes.c_int
|
|
121
|
+
|
|
122
|
+
# nispy_StartSpying(int, const char*, int) -> int
|
|
123
|
+
dll.nispy_StartSpying.argtypes = [ctypes.c_int, ctypes.c_char_p, ctypes.c_int]
|
|
124
|
+
dll.nispy_StartSpying.restype = ctypes.c_int
|
|
125
|
+
|
|
126
|
+
# nispy_StopSpying(void) -> int
|
|
127
|
+
dll.nispy_StopSpying.argtypes = []
|
|
128
|
+
dll.nispy_StopSpying.restype = ctypes.c_int
|
|
129
|
+
|
|
130
|
+
# nispy_WriteTextEntry(const char*) -> int
|
|
131
|
+
dll.nispy_WriteTextEntry.argtypes = [ctypes.c_char_p]
|
|
132
|
+
dll.nispy_WriteTextEntry.restype = ctypes.c_int
|
|
133
|
+
|
|
134
|
+
# nispy_CloseSpy(void) -> int
|
|
135
|
+
dll.nispy_CloseSpy.argtypes = []
|
|
136
|
+
dll.nispy_CloseSpy.restype = ctypes.c_int
|
|
137
|
+
|
|
138
|
+
return dll
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
_dll: ctypes.WinDLL | None = None
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _get_dll() -> ctypes.WinDLL:
|
|
145
|
+
global _dll
|
|
146
|
+
if _dll is None:
|
|
147
|
+
_dll = _load_dll()
|
|
148
|
+
return _dll
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def get_application_path() -> Path:
|
|
152
|
+
"""Return the filesystem path to the NI IO Trace executable.
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
A :class:`~pathlib.Path` pointing to the executable.
|
|
156
|
+
|
|
157
|
+
Raises:
|
|
158
|
+
IoTraceError: If NI IO Trace is not installed.
|
|
159
|
+
"""
|
|
160
|
+
buf_size = 1024
|
|
161
|
+
buf = ctypes.create_string_buffer(buf_size)
|
|
162
|
+
_check(_get_dll().nispy_GetApplicationPath(buf, buf_size))
|
|
163
|
+
return Path(buf.value.decode())
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
_WINDOW_STATE_ARGS: dict[WindowState, list[str]] = {
|
|
167
|
+
WindowState.HIDDEN: ["/hidden"],
|
|
168
|
+
WindowState.NORMAL: [],
|
|
169
|
+
WindowState.MAXIMIZED: ["/maximized"],
|
|
170
|
+
WindowState.MINIMIZED: ["/minimized"],
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def launch_io_trace(
|
|
175
|
+
window_state: WindowState = WindowState.MINIMIZED,
|
|
176
|
+
) -> subprocess.Popen:
|
|
177
|
+
"""Launch the NI IO Trace application and return the process handle.
|
|
178
|
+
|
|
179
|
+
The application must be running before calls to :func:`start_tracing`,
|
|
180
|
+
:func:`stop_tracing`, :func:`log_message`, or :func:`close_io_trace` can
|
|
181
|
+
succeed.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
window_state: The initial window state of the application. Defaults to
|
|
185
|
+
:attr:`WindowState.MINIMIZED`.
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
The :class:`~subprocess.Popen` instance for the launched process.
|
|
189
|
+
|
|
190
|
+
Raises:
|
|
191
|
+
RuntimeError: If the process exits immediately after being started.
|
|
192
|
+
IoTraceError: If the application path cannot be resolved.
|
|
193
|
+
"""
|
|
194
|
+
app_path = get_application_path()
|
|
195
|
+
cmd = [str(app_path), *_WINDOW_STATE_ARGS[window_state]]
|
|
196
|
+
process = subprocess.Popen(cmd)
|
|
197
|
+
|
|
198
|
+
if process.poll() is not None:
|
|
199
|
+
raise RuntimeError(f"NI IO Trace exited immediately with code {process.returncode}")
|
|
200
|
+
|
|
201
|
+
# Try to start a tracing session to verify that the application is ready to accept commands.
|
|
202
|
+
# If this fails, wait a moment and try again, as the application may still be finishing its launch process.
|
|
203
|
+
# This typically happens when the application is launched for the first time.
|
|
204
|
+
for _ in range(3):
|
|
205
|
+
try:
|
|
206
|
+
start_tracing() # Test that we can communicate with the application
|
|
207
|
+
stop_tracing() # Stop the test tracing session immediately
|
|
208
|
+
break # Success, exit the loop
|
|
209
|
+
except IoTraceError as _:
|
|
210
|
+
time.sleep(1) # Wait a moment for the application to finish launching
|
|
211
|
+
else:
|
|
212
|
+
raise RuntimeError("NI IO Trace failed to respond after multiple attempts")
|
|
213
|
+
|
|
214
|
+
return process
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def start_tracing(
|
|
218
|
+
log_file_setting: LogFileSetting = LogFileSetting.NO_FILE,
|
|
219
|
+
file_path: str | Path | None = None,
|
|
220
|
+
file_write_mode: FileWriteMode = FileWriteMode.CREATE_ONLY,
|
|
221
|
+
) -> None:
|
|
222
|
+
"""Start tracing NI driver calls.
|
|
223
|
+
|
|
224
|
+
NI IO Trace must already be running (see :func:`launch_io_trace`) before
|
|
225
|
+
calling this function.
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
log_file_setting: The format of the log file to write. Use
|
|
229
|
+
:attr:`LogFileSetting.NO_FILE` to trace without writing a file.
|
|
230
|
+
file_path: The path to the log file. Required when *log_file_setting*
|
|
231
|
+
is not :attr:`LogFileSetting.NO_FILE`. Can be a string or
|
|
232
|
+
:class:`~pathlib.Path`.
|
|
233
|
+
file_write_mode: How to handle an existing file at *file_path*.
|
|
234
|
+
|
|
235
|
+
Raises:
|
|
236
|
+
IoTraceError: If the call fails (e.g. IO Trace is not running,
|
|
237
|
+
the file already exists with :attr:`FileWriteMode.CREATE_ONLY`,
|
|
238
|
+
or the settings are invalid).
|
|
239
|
+
"""
|
|
240
|
+
path_bytes = str(file_path).encode() if file_path is not None else None
|
|
241
|
+
_check(_get_dll().nispy_StartSpying(int(log_file_setting), path_bytes, int(file_write_mode)))
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def stop_tracing() -> None:
|
|
245
|
+
"""Stop tracing NI driver calls.
|
|
246
|
+
|
|
247
|
+
Tracing must have been started with :func:`start_tracing` before calling
|
|
248
|
+
this function. The NI IO Trace application remains open and can be
|
|
249
|
+
restarted with another call to :func:`start_tracing`.
|
|
250
|
+
|
|
251
|
+
Raises:
|
|
252
|
+
IoTraceError: If tracing was not active.
|
|
253
|
+
"""
|
|
254
|
+
_check(_get_dll().nispy_StopSpying())
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def log_message(message: str) -> None:
|
|
258
|
+
"""Write a custom text entry into the active NI IO Trace log.
|
|
259
|
+
|
|
260
|
+
This is useful for inserting markers or annotations into a trace session
|
|
261
|
+
to correlate driver calls with application-level events.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
message: The text to write. Will be UTF-8 encoded.
|
|
265
|
+
|
|
266
|
+
Raises:
|
|
267
|
+
IoTraceError: If the IO Trace application has been closed.
|
|
268
|
+
"""
|
|
269
|
+
_check(_get_dll().nispy_WriteTextEntry(message.encode()))
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def _find_process_ids(exe_name: str) -> list[int]:
|
|
273
|
+
"""Return PIDs of all running processes matching *exe_name*."""
|
|
274
|
+
result = subprocess.run(
|
|
275
|
+
["tasklist", "/FI", f"IMAGENAME eq {exe_name}", "/FO", "CSV", "/NH"],
|
|
276
|
+
capture_output=True,
|
|
277
|
+
text=True,
|
|
278
|
+
)
|
|
279
|
+
pids: list[int] = []
|
|
280
|
+
for line in result.stdout.splitlines():
|
|
281
|
+
parts = line.strip().strip('"').split('","')
|
|
282
|
+
if len(parts) >= 2 and parts[0].lower() == exe_name.lower():
|
|
283
|
+
pids.append(int(parts[1]))
|
|
284
|
+
return pids
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def _wait_for_process_exit(exe_name: str, timeout: float) -> None:
|
|
288
|
+
"""Block until no processes named *exe_name* are running, or *timeout* expires."""
|
|
289
|
+
deadline = time.monotonic() + timeout
|
|
290
|
+
while time.monotonic() < deadline:
|
|
291
|
+
if not _find_process_ids(exe_name):
|
|
292
|
+
return
|
|
293
|
+
time.sleep(0.25)
|
|
294
|
+
raise RuntimeError(f"{exe_name} did not exit within {timeout} seconds")
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def close_io_trace(timeout: float = 10.0) -> None:
|
|
298
|
+
"""Close the NI IO Trace application and wait for the process to exit.
|
|
299
|
+
|
|
300
|
+
Sends the close command and then polls until the NI IO Trace process is
|
|
301
|
+
no longer running. The application does not need to have been launched by
|
|
302
|
+
:func:`launch_io_trace` — it may have been started manually.
|
|
303
|
+
|
|
304
|
+
After this call, the application must be relaunched before any further
|
|
305
|
+
tracing can occur.
|
|
306
|
+
|
|
307
|
+
Args:
|
|
308
|
+
timeout: Maximum number of seconds to wait for the process to exit.
|
|
309
|
+
|
|
310
|
+
Raises:
|
|
311
|
+
IoTraceError: If the close command fails.
|
|
312
|
+
RuntimeError: If the process does not exit within *timeout* seconds.
|
|
313
|
+
"""
|
|
314
|
+
exe_name = get_application_path().name
|
|
315
|
+
_check(_get_dll().nispy_CloseSpy())
|
|
316
|
+
_wait_for_process_exit(exe_name, timeout)
|
iotrace/logging.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Logging integration for NI IO Trace."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from iotrace import IoTraceError, log_message
|
|
6
|
+
|
|
7
|
+
__all__ = ["IOTraceHandler"]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class IOTraceHandler(logging.Handler):
|
|
11
|
+
"""A logging handler that writes log records into the NI IO Trace log.
|
|
12
|
+
|
|
13
|
+
Each log record is formatted and passed to :func:`iotrace.log_message`,
|
|
14
|
+
making Python log entries appear in the IO Trace log alongside NI driver
|
|
15
|
+
calls.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def emit(self, record: logging.LogRecord) -> None:
|
|
19
|
+
try:
|
|
20
|
+
log_message(self.format(record))
|
|
21
|
+
except IoTraceError:
|
|
22
|
+
self.handleError(record)
|
iotrace/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: iotrace
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python wrapper for NI IO Trace programmatic C API
|
|
5
|
+
Keywords: ni,io-trace,iotrace,nitrace,ni-io-trace,nimi-python,nidcpower,nidmm,niscope,niswitch,nidigital,nidaqmx,nifgen,nirfsa,nirfsg,nise,ni-visa,pyVISA
|
|
6
|
+
Author: Karsten van Zwol
|
|
7
|
+
Author-email: Karsten van Zwol <karsten.van.zwol@gmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering
|
|
20
|
+
Classifier: Topic :: Software Development :: Testing
|
|
21
|
+
Classifier: Topic :: System :: Logging
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Project-URL: Homepage, https://github.com/ktvanzwol/nitrace
|
|
25
|
+
Project-URL: Repository, https://github.com/ktvanzwol/nitrace
|
|
26
|
+
Project-URL: Issues, https://github.com/ktvanzwol/nitrace/issues
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# NI IO Trace Python API
|
|
30
|
+
|
|
31
|
+
A Python library for controlling [NI IO Trace](https://www.ni.com/docs/en-US/bundle/ni-io-trace/page/overview.html) programmatically. Launch the application, start and stop tracing, write log messages, and close IO Trace — all from Python.
|
|
32
|
+
|
|
33
|
+
## Requirements
|
|
34
|
+
|
|
35
|
+
- Windows
|
|
36
|
+
- Python 3.10+
|
|
37
|
+
- NI IO Trace installed (part of NI software distributions)
|
|
38
|
+
- [Where Can I Download NI I/O Trace?](https://knowledge.ni.com/KnowledgeArticleDetails?id=kA00Z000000kJcQSAU)
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
Install from PyPI:
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
pip install iotrace
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Or with [uv](https://docs.astral.sh/uv/):
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
uv add iotrace
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Or install directly from GitHub:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
pip install git+https://github.com/ktvanzwol/nitrace.git
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Quick Start
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from pathlib import Path
|
|
64
|
+
|
|
65
|
+
import iotrace
|
|
66
|
+
|
|
67
|
+
# Launch the application (minimized by default)
|
|
68
|
+
iotrace.launch_io_trace()
|
|
69
|
+
|
|
70
|
+
# Start tracing to a NI IO Trace log file
|
|
71
|
+
iotrace.start_tracing(
|
|
72
|
+
log_file_setting=iotrace.LogFileSetting.IO_TRACE,
|
|
73
|
+
file_path=Path.cwd() / "trace.nitrace",
|
|
74
|
+
file_write_mode=iotrace.FileWriteMode.CREATE_OR_OVERWRITE,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# Insert a marker into the trace log
|
|
78
|
+
iotrace.log_message("Test started")
|
|
79
|
+
|
|
80
|
+
# ... run your NI driver calls ...
|
|
81
|
+
|
|
82
|
+
# Stop tracing and leave the application running to inspect the log.
|
|
83
|
+
iotrace.stop_tracing()
|
|
84
|
+
|
|
85
|
+
print("Trace complete. Log file saved to:", Path.cwd() / "trace.nitrace")
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## CLI
|
|
89
|
+
|
|
90
|
+
A command-line interface is included:
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
# Launch IO Trace and start tracing to a CSV file
|
|
94
|
+
iotrace start --log-format csv --file trace.csv --write-mode overwrite
|
|
95
|
+
|
|
96
|
+
# Stop tracing and close the application
|
|
97
|
+
iotrace stop --close
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### `iotrace start`
|
|
101
|
+
|
|
102
|
+
| Option | Description |
|
|
103
|
+
|---|---|
|
|
104
|
+
| `--log-format` | Log file format: `none`, `io-trace`, `plain-text`, `csv`, `xml` (default: `none`). |
|
|
105
|
+
| `--file` | Path to the log file. |
|
|
106
|
+
| `--write-mode` | File write mode: `create`, `append`, `overwrite` (default: `create`). |
|
|
107
|
+
|
|
108
|
+
### `iotrace stop`
|
|
109
|
+
|
|
110
|
+
| Option | Description |
|
|
111
|
+
|---|---|
|
|
112
|
+
| `--close` | Close NI IO Trace after stopping. |
|
|
113
|
+
|
|
114
|
+
## API Reference
|
|
115
|
+
|
|
116
|
+
- **Functions:**
|
|
117
|
+
- [`get_application_path`](#get_application_path---path)
|
|
118
|
+
- [`launch_io_trace`](#launch_io_tracewindow_statewindowstateminimized---subprocesspopen)
|
|
119
|
+
- [`start_tracing`](#start_tracinglog_file_settinglogfilesettingno_file-file_pathnone-file_write_modefilewritemodecreate_only---none)
|
|
120
|
+
- [`stop_tracing`](#stop_tracing---none)
|
|
121
|
+
- [`log_message`](#log_messagemessage-str---none)
|
|
122
|
+
- [`close_io_trace`](#close_io_tracetimeout-float--100---none)
|
|
123
|
+
- **Logging:**
|
|
124
|
+
- [`IOTraceHandler`](#iotracehandler)
|
|
125
|
+
- **Enums:**
|
|
126
|
+
- [`LogFileSetting`](#logfilesetting)
|
|
127
|
+
- [`FileWriteMode`](#filewritemode)
|
|
128
|
+
- [`WindowState`](#windowstate)
|
|
129
|
+
- [`StatusCode`](#statuscode)
|
|
130
|
+
- **Exceptions:**
|
|
131
|
+
- [`IoTraceError`](#iotraceerror)
|
|
132
|
+
|
|
133
|
+
### Functions
|
|
134
|
+
|
|
135
|
+
#### `get_application_path() -> Path`
|
|
136
|
+
|
|
137
|
+
Return the filesystem path to the NI IO Trace executable.
|
|
138
|
+
|
|
139
|
+
**Raises:** `IoTraceError` if NI IO Trace is not installed.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
#### `launch_io_trace(window_state=WindowState.MINIMIZED) -> subprocess.Popen`
|
|
144
|
+
|
|
145
|
+
Launch the NI IO Trace application and return the process handle. The application must be running before calling `start_tracing`, `stop_tracing`, `log_message`, or `close_io_trace`.
|
|
146
|
+
|
|
147
|
+
| Parameter | Type | Default | Description |
|
|
148
|
+
|---|---|---|---|
|
|
149
|
+
| `window_state` | `WindowState` | `MINIMIZED` | Initial window state of the application. |
|
|
150
|
+
|
|
151
|
+
**Raises:** `RuntimeError` if the process exits immediately. `IoTraceError` if the application path cannot be resolved.
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
#### `start_tracing(log_file_setting=LogFileSetting.NO_FILE, file_path=None, file_write_mode=FileWriteMode.CREATE_ONLY) -> None`
|
|
156
|
+
|
|
157
|
+
Start tracing NI driver calls. NI IO Trace must already be running.
|
|
158
|
+
|
|
159
|
+
| Parameter | Type | Default | Description |
|
|
160
|
+
|---|---|---|---|
|
|
161
|
+
| `log_file_setting` | `LogFileSetting` | `NO_FILE` | Format of the log file. |
|
|
162
|
+
| `file_path` | `str \| Path \| None` | `None` | Path to the log file. Required when `log_file_setting` is not `NO_FILE`. |
|
|
163
|
+
| `file_write_mode` | `FileWriteMode` | `CREATE_ONLY` | How to handle an existing file. |
|
|
164
|
+
|
|
165
|
+
**Raises:** `IoTraceError` if IO Trace is not running, the file already exists with `CREATE_ONLY`, or the settings are invalid.
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
#### `stop_tracing() -> None`
|
|
170
|
+
|
|
171
|
+
Stop tracing NI driver calls. The application remains open and tracing can be restarted.
|
|
172
|
+
|
|
173
|
+
**Raises:** `IoTraceError` if tracing was not active.
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
#### `log_message(message: str) -> None`
|
|
178
|
+
|
|
179
|
+
Write a custom text entry into the active trace log. Useful for inserting markers to correlate driver calls with application events.
|
|
180
|
+
|
|
181
|
+
| Parameter | Type | Description |
|
|
182
|
+
|---|---|---|
|
|
183
|
+
| `message` | `str` | The text to write. |
|
|
184
|
+
|
|
185
|
+
**Raises:** `IoTraceError` if the IO Trace application has been closed.
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
#### `close_io_trace(timeout: float = 10.0) -> None`
|
|
190
|
+
|
|
191
|
+
Close the NI IO Trace application and wait for the process to exit. The application does not need to have been launched by `launch_io_trace` — it may have been started manually.
|
|
192
|
+
|
|
193
|
+
| Parameter | Type | Default | Description |
|
|
194
|
+
|---|---|---|---|
|
|
195
|
+
| `timeout` | `float` | `10.0` | Maximum seconds to wait for the process to exit. |
|
|
196
|
+
|
|
197
|
+
**Raises:** `IoTraceError` if the close command fails. `RuntimeError` if the process does not exit within the timeout.
|
|
198
|
+
|
|
199
|
+
### Enums
|
|
200
|
+
|
|
201
|
+
#### `LogFileSetting`
|
|
202
|
+
|
|
203
|
+
| Member | Value | Description |
|
|
204
|
+
|---|---|---|
|
|
205
|
+
| `NO_FILE` | -1 | No log file; trace data visible only in the GUI. |
|
|
206
|
+
| `IO_TRACE` | 0 | NI IO Trace binary format (`.iotrace`). |
|
|
207
|
+
| `PLAIN_TEXT` | 1 | Human-readable plain-text file. |
|
|
208
|
+
| `COMMA_SEPARATED` | 2 | Comma-separated values (CSV) file. |
|
|
209
|
+
| `XML` | 3 | XML-formatted file. |
|
|
210
|
+
|
|
211
|
+
#### `FileWriteMode`
|
|
212
|
+
|
|
213
|
+
| Member | Value | Description |
|
|
214
|
+
|---|---|---|
|
|
215
|
+
| `CREATE_ONLY` | 0 | Create a new file. Raises `IoTraceError` if the file exists. |
|
|
216
|
+
| `CREATE_OR_APPEND` | 1 | Append to an existing file or create a new one. |
|
|
217
|
+
| `CREATE_OR_OVERWRITE` | 2 | Overwrite an existing file or create a new one. |
|
|
218
|
+
|
|
219
|
+
#### `WindowState`
|
|
220
|
+
|
|
221
|
+
| Member | Value | Description |
|
|
222
|
+
|---|---|---|
|
|
223
|
+
| `HIDDEN` | 0 | No visible window. |
|
|
224
|
+
| `NORMAL` | 1 | Default (restored) window state. |
|
|
225
|
+
| `MAXIMIZED` | 2 | Window maximized. |
|
|
226
|
+
| `MINIMIZED` | 3 | Window minimized. |
|
|
227
|
+
|
|
228
|
+
#### `StatusCode`
|
|
229
|
+
|
|
230
|
+
| Member | Value | Description |
|
|
231
|
+
|---|---|---|
|
|
232
|
+
| `SUCCESS` | 0 | Call completed successfully. |
|
|
233
|
+
| `FAILED_NO_EXECUTE` | -303200 | Command could not be executed. |
|
|
234
|
+
| `FAILED_INCOMPATIBLE_STATE` | -303201 | Operation not valid in the current state. |
|
|
235
|
+
| `FAILED_UNABLE_TO_OPEN_LOG_FILE` | -303202 | Could not open the specified log file. |
|
|
236
|
+
| `FAILED_GUI_CLOSED` | -303203 | The IO Trace application is not running. |
|
|
237
|
+
| `FAILED_INVALID_SETTINGS` | -303204 | Invalid tracing settings. |
|
|
238
|
+
| `FAILED_BAD_PARAMETER` | -303205 | A parameter value is invalid. |
|
|
239
|
+
| `FAILED_INTERNAL_FAILURE` | -303206 | An internal error occurred. |
|
|
240
|
+
| `FAILED_INVALID_FILE_EXTENSION` | -303207 | The log file extension does not match the format. |
|
|
241
|
+
| `FAILED_BUFFER_TOO_SMALL` | -303208 | The provided buffer is too small. |
|
|
242
|
+
| `FAILED_FILE_ALREADY_EXISTS` | -303209 | The file already exists (with `CREATE_ONLY` mode). |
|
|
243
|
+
|
|
244
|
+
### Logging
|
|
245
|
+
|
|
246
|
+
#### `IOTraceHandler`
|
|
247
|
+
|
|
248
|
+
`iotrace.logging.IOTraceHandler` is a [`logging.Handler`](https://docs.python.org/3/library/logging.html#handler-objects) subclass that forwards Python log records into the NI IO Trace log via `log_message`. If the IO Trace application is not running, the error is passed to `handleError`.
|
|
249
|
+
|
|
250
|
+
```python
|
|
251
|
+
import logging
|
|
252
|
+
from iotrace.logging import IOTraceHandler
|
|
253
|
+
|
|
254
|
+
logger = logging.getLogger("my_app")
|
|
255
|
+
handler = IOTraceHandler()
|
|
256
|
+
handler.setFormatter(logging.Formatter("[%(levelname)s] %(name)s - %(message)s"))
|
|
257
|
+
logger.addHandler(handler)
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
See [`examples/logging_handler.py`](examples/logging_handler.py) for a complete runnable example.
|
|
261
|
+
|
|
262
|
+
### Exceptions
|
|
263
|
+
|
|
264
|
+
#### `IoTraceError`
|
|
265
|
+
|
|
266
|
+
Raised when an API call returns a non-success status.
|
|
267
|
+
|
|
268
|
+
| Attribute | Type | Description |
|
|
269
|
+
|---|---|---|
|
|
270
|
+
| `status` | `StatusCode` | The status code that triggered the error. |
|
|
271
|
+
|
|
272
|
+
## License
|
|
273
|
+
|
|
274
|
+
This project is licensed under the [MIT License](LICENSE).
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
iotrace/__cli__.py,sha256=ysQ-CqXUXLgIKRDh2tS5XgjGxX1-1PB838UHt7a62UA,2540
|
|
2
|
+
iotrace/__init__.py,sha256=cHwWrK-h_lyHT0qLMrLQ7ADArtzPTPK_3wu1Gvx1rEM,10551
|
|
3
|
+
iotrace/logging.py,sha256=8luACri3EKa9V4i-gfaU-KHonTxQVyu4WJg6mkyWFy8,627
|
|
4
|
+
iotrace/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
iotrace-0.1.0.dist-info/WHEEL,sha256=uOqnPWqgFlbov4NeTCercq7cBQ2UN7xh5fiW55lOnAg,81
|
|
6
|
+
iotrace-0.1.0.dist-info/entry_points.txt,sha256=SaDlzfj7t8NvEW-qknaGtkW4qOldlX6OhSpa6sPgm4I,50
|
|
7
|
+
iotrace-0.1.0.dist-info/METADATA,sha256=U_R7555_ixOmvkYLfk619w7W42mYCN0ljp1HuQWdEG8,9516
|
|
8
|
+
iotrace-0.1.0.dist-info/RECORD,,
|