VirtualBox

source: vbox/trunk/src/VBox/Runtime/tools/RTTraceLogTool.cpp@ 104920

Last change on this file since 104920 was 104920, checked in by vboxsync, 8 months ago

Runtime/tools/RTTraceLogTool,Devices/VBoxTraceLogDecoders.cpp: Allow attaching a state for a decoder to enable analysis spanning multiple events, bugref:10701

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 22.7 KB
Line 
1/* $Id: RTTraceLogTool.cpp 104920 2024-06-14 12:02:03Z vboxsync $ */
2/** @file
3 * IPRT - Utility for reading/receiving and dissecting trace logs.
4 */
5
6/*
7 * Copyright (C) 2018-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * The contents of this file may alternatively be used under the terms
26 * of the Common Development and Distribution License Version 1.0
27 * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
28 * in the VirtualBox distribution, in which case the provisions of the
29 * CDDL are applicable instead of those of the GPL.
30 *
31 * You may elect to license modified versions of this file under the
32 * terms and conditions of either the GPL or the CDDL or both.
33 *
34 * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
35 */
36
37
38/*********************************************************************************************************************************
39* Header Files *
40*********************************************************************************************************************************/
41#include <iprt/tracelog.h>
42#include <iprt/tracelog-decoder-plugin.h>
43
44#include <iprt/assert.h>
45#include <iprt/errcore.h>
46#include <iprt/getopt.h>
47#include <iprt/initterm.h>
48#include <iprt/ldr.h>
49#include <iprt/message.h>
50#include <iprt/mem.h>
51#include <iprt/path.h>
52#include <iprt/stream.h>
53#include <iprt/string.h>
54#include <iprt/tcp.h>
55
56
57typedef struct RTTRACELOGDECODER
58{
59 /** The tracelog decoder registration structure. */
60 PCRTTRACELOGDECODERREG pReg;
61 /** The helper structure for this decoder. */
62 RTTRACELOGDECODERHLP Hlp;
63 /** The decoder state if created. */
64 void *pvDecoderState;
65 /** The free callback of any attached decoder state. */
66 PFNTRACELOGDECODERSTATEFREE pfnDecoderStateFree;
67} RTTRACELOGDECODER;
68typedef RTTRACELOGDECODER *PRTTRACELOGDECODER;
69typedef const RTTRACELOGDECODER *PCRTTRACELOGDECODER;
70
71
72/**
73 * Loaded tracelog decoders.
74 */
75typedef struct RTTRACELOGDECODERSTATE
76{
77 /** Pointer to the array of registered decoders. */
78 PRTTRACELOGDECODER paDecoders;
79 /** Number of entries in the decoder array. */
80 uint32_t cDecoders;
81 /** Allocation size of the decoder array. */
82 uint32_t cDecodersAlloc;
83} RTTRACELOGDECODERSTATE;
84typedef RTTRACELOGDECODERSTATE *PRTTRACELOGDECODERSTATE;
85
86
87/**
88 * The tracelog tool TCP server/client state.
89 */
90typedef struct RTTRACELOGTOOLTCP
91{
92 /** Flag whether this is a server. */
93 bool fIsServer;
94 /** The TCP socket handle for the connection. */
95 RTSOCKET hSock;
96 /** The TCP server. */
97 PRTTCPSERVER pTcpSrv;
98} RTTRACELOGTOOLTCP;
99/** Pointer to the TCP server/client state. */
100typedef RTTRACELOGTOOLTCP *PRTTRACELOGTOOLTCP;
101
102
103static void rtTraceLogTcpDestroy(PRTTRACELOGTOOLTCP pTrcLogTcp)
104{
105 if (pTrcLogTcp->fIsServer)
106 RTTcpServerDestroy(pTrcLogTcp->pTcpSrv);
107 if (pTrcLogTcp->hSock != NIL_RTSOCKET)
108 {
109 if (pTrcLogTcp->fIsServer)
110 RTTcpServerDisconnectClient2(pTrcLogTcp->hSock);
111 else
112 RTTcpClientClose(pTrcLogTcp->hSock);
113 }
114 RTMemFree(pTrcLogTcp);
115}
116
117
118static DECLCALLBACK(int) rtTraceLogToolTcpInput(void *pvUser, void *pvBuf, size_t cbBuf, size_t *pcbRead,
119 RTMSINTERVAL cMsTimeout)
120{
121 PRTTRACELOGTOOLTCP pTrcLogTcp = (PRTTRACELOGTOOLTCP)pvUser;
122 if ( pTrcLogTcp->fIsServer
123 && pTrcLogTcp->hSock == NIL_RTSOCKET)
124 {
125 int rc = RTTcpServerListen2(pTrcLogTcp->pTcpSrv, &pTrcLogTcp->hSock);
126 if (RT_FAILURE(rc))
127 return rc;
128 }
129
130 int rc = RTTcpSelectOne(pTrcLogTcp->hSock, cMsTimeout);
131 if (RT_SUCCESS(rc))
132 rc = RTTcpReadNB(pTrcLogTcp->hSock, pvBuf, cbBuf, pcbRead);
133
134 return rc;
135}
136
137
138static DECLCALLBACK(int) rtTraceLogToolTcpClose(void *pvUser)
139{
140 PRTTRACELOGTOOLTCP pTrcLogTcp = (PRTTRACELOGTOOLTCP)pvUser;
141 rtTraceLogTcpDestroy(pTrcLogTcp);
142 return VINF_SUCCESS;
143}
144
145
146/**
147 * Tries to create a new trace log reader using the given input.
148 *
149 * @returns IPRT status code.
150 * @param phTraceLogRdr Where to store the handle to the trace log reader instance on success.
151 * @param pszInput The input path.
152 * @param pszSave The optional path to save
153 */
154static int rtTraceLogToolReaderCreate(PRTTRACELOGRDR phTraceLogRdr, const char *pszInput, const char *pszSave)
155{
156 RT_NOREF(pszSave);
157
158 /* Try treating the input as a file first. */
159 int rc = RTTraceLogRdrCreateFromFile(phTraceLogRdr, pszInput);
160 if (RT_FAILURE(rc))
161 {
162 /*
163 * Check whether the input looks like a port number or an address:port pair.
164 * The former will create a server listening on the port while the latter tries
165 * to connect to the given address:port combination.
166 */
167 uint32_t uPort = 0;
168 bool fIsServer = false;
169 PRTTCPSERVER pTcpSrv = NULL;
170 RTSOCKET hSock = NIL_RTSOCKET;
171 rc = RTStrToUInt32Full(pszInput, 10, &uPort);
172 if (rc == VINF_SUCCESS)
173 {
174 fIsServer = true;
175 rc = RTTcpServerCreateEx(NULL, uPort, &pTcpSrv);
176 }
177 else
178 {
179 /* Try treating the input as an address:port pair. */
180 }
181
182 if (RT_SUCCESS(rc))
183 {
184 /* Initialize structure and reader. */
185 PRTTRACELOGTOOLTCP pTrcLogTcp = (PRTTRACELOGTOOLTCP)RTMemAllocZ(sizeof(*pTrcLogTcp));
186 if (pTrcLogTcp)
187 {
188 pTrcLogTcp->fIsServer = fIsServer;
189 pTrcLogTcp->hSock = hSock;
190 pTrcLogTcp->pTcpSrv = pTcpSrv;
191 rc = RTTraceLogRdrCreate(phTraceLogRdr, rtTraceLogToolTcpInput, rtTraceLogToolTcpClose, pTrcLogTcp);
192 if (RT_FAILURE(rc))
193 rtTraceLogTcpDestroy(pTrcLogTcp);
194 }
195 else
196 {
197 if (fIsServer)
198 RTTcpServerDestroy(pTcpSrv);
199 else
200 RTSocketClose(hSock);
201 }
202 }
203 }
204 return rc;
205}
206
207
208static DECLCALLBACK(int) rtTraceLogToolDecoderHlpPrintf(PRTTRACELOGDECODERHLP pHlp, const char *pszFormat, ...)
209 RT_IPRT_FORMAT_ATTR(3, 4)
210{
211 RT_NOREF(pHlp);
212 va_list Args;
213 va_start(Args, pszFormat);
214 int rc = RTMsgInfoV(pszFormat, Args);
215 va_end(Args);
216 return rc;
217}
218
219
220static DECLCALLBACK(int) rtTraceLogToolDecoderHlpErrorMsg(PRTTRACELOGDECODERHLP pHlp, const char *pszFormat, ...)
221 RT_IPRT_FORMAT_ATTR(3, 4)
222{
223 RT_NOREF(pHlp);
224 va_list Args;
225 va_start(Args, pszFormat);
226 int rc = RTMsgErrorV(pszFormat, Args);
227 va_end(Args);
228 return rc;
229}
230
231
232static DECLCALLBACK(int) rtTraceLogToolDecoderHlpStateCreate(PRTTRACELOGDECODERHLP pHlp, size_t cbState, PFNTRACELOGDECODERSTATEFREE pfnFree,
233 void **ppvState)
234{
235 PRTTRACELOGDECODER pDecoder = RT_FROM_MEMBER(pHlp, RTTRACELOGDECODER, Hlp);
236
237 if (pDecoder->pvDecoderState)
238 {
239 if (pDecoder->pfnDecoderStateFree)
240 pDecoder->pfnDecoderStateFree(pHlp, pDecoder->pvDecoderState);
241 RTMemFree(pDecoder->pvDecoderState);
242 pDecoder->pvDecoderState = NULL;
243 pDecoder->pfnDecoderStateFree = NULL;
244 }
245
246 pDecoder->pvDecoderState = RTMemAllocZ(cbState);
247 if (pDecoder->pvDecoderState)
248 {
249 pDecoder->pfnDecoderStateFree = pfnFree;
250 *ppvState = pDecoder->pvDecoderState;
251 return VINF_SUCCESS;
252 }
253
254 return VERR_NO_MEMORY;
255}
256
257
258static DECLCALLBACK(void) rtTraceLogToolDecoderHlpStateDestroy(PRTTRACELOGDECODERHLP pHlp)
259{
260 PRTTRACELOGDECODER pDecoder = RT_FROM_MEMBER(pHlp, RTTRACELOGDECODER, Hlp);
261
262 if (pDecoder->pvDecoderState)
263 {
264 if (pDecoder->pfnDecoderStateFree)
265 pDecoder->pfnDecoderStateFree(pHlp, pDecoder->pvDecoderState);
266 RTMemFree(pDecoder->pvDecoderState);
267 pDecoder->pvDecoderState = NULL;
268 pDecoder->pfnDecoderStateFree = NULL;
269 }
270}
271
272
273static DECLCALLBACK(void*) rtTraceLogToolDecoderHlpStateGet(PRTTRACELOGDECODERHLP pHlp)
274{
275 PRTTRACELOGDECODER pDecoder = RT_FROM_MEMBER(pHlp, RTTRACELOGDECODER, Hlp);
276
277 return pDecoder->pvDecoderState;
278}
279
280
281static DECLCALLBACK(int) rtTraceLogToolRegisterDecoders(void *pvUser, PCRTTRACELOGDECODERREG paDecoders, uint32_t cDecoders)
282{
283 PRTTRACELOGDECODERSTATE pDecoderState = (PRTTRACELOGDECODERSTATE)pvUser;
284
285 if (pDecoderState->cDecodersAlloc - pDecoderState->cDecoders <= cDecoders)
286 {
287 PRTTRACELOGDECODER paNew = (PRTTRACELOGDECODER)RTMemRealloc(pDecoderState->paDecoders,
288 (pDecoderState->cDecodersAlloc + cDecoders) * sizeof(*paNew));
289 if (!paNew)
290 return VERR_NO_MEMORY;
291
292 pDecoderState->paDecoders = paNew;
293 pDecoderState->cDecodersAlloc += cDecoders;
294 }
295
296 for (uint32_t i = 0; i < cDecoders; i++)
297 {
298 PRTTRACELOGDECODER pDecoder = &pDecoderState->paDecoders[i];
299
300 pDecoder->pReg = &paDecoders[i];
301 pDecoder->pvDecoderState = NULL;
302 pDecoder->pfnDecoderStateFree = NULL;
303 pDecoder->Hlp.pfnPrintf = rtTraceLogToolDecoderHlpPrintf;
304 pDecoder->Hlp.pfnErrorMsg = rtTraceLogToolDecoderHlpErrorMsg;
305 pDecoder->Hlp.pfnDecoderStateCreate = rtTraceLogToolDecoderHlpStateCreate;
306 pDecoder->Hlp.pfnDecoderStateDestroy = rtTraceLogToolDecoderHlpStateDestroy;
307 pDecoder->Hlp.pfnDecoderStateGet = rtTraceLogToolDecoderHlpStateGet;
308 }
309
310 pDecoderState->cDecoders += cDecoders;
311 return VINF_SUCCESS;
312}
313
314
315int main(int argc, char **argv)
316{
317 int rc = RTR3InitExe(argc, &argv, 0);
318 if (RT_FAILURE(rc))
319 return RTMsgInitFailure(rc);
320
321 /*
322 * Parse arguments.
323 */
324 static const RTGETOPTDEF s_aOptions[] =
325 {
326 { "--input", 'i', RTGETOPT_REQ_STRING },
327 { "--save", 's', RTGETOPT_REQ_STRING },
328 { "--load-decoder", 'l', RTGETOPT_REQ_STRING },
329 { "--help", 'h', RTGETOPT_REQ_NOTHING },
330 { "--version", 'V', RTGETOPT_REQ_NOTHING },
331 };
332
333 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
334 const char *pszInput = NULL;
335 const char *pszSave = NULL;
336 RTTRACELOGDECODERSTATE DecoderState; RT_ZERO(DecoderState);
337
338 RTGETOPTUNION ValueUnion;
339 RTGETOPTSTATE GetState;
340 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
341 while ((rc = RTGetOpt(&GetState, &ValueUnion)))
342 {
343 switch (rc)
344 {
345 case 'h':
346 RTPrintf("Usage: %s [options]\n"
347 "\n"
348 "Options:\n"
349 " -i,--input=<file|port|address:port>\n"
350 " Input path, can be a file a port to start listening on for incoming connections or an address:port to connect to\n"
351 " -s,--save=file\n"
352 " Save the input to a file for later use\n"
353 " -l,--load-decoder=<plugin path>\n"
354 " Loads the given decoder library used for decoding events\n"
355 " -h, -?, --help\n"
356 " Display this help text and exit successfully.\n"
357 " -V, --version\n"
358 " Display the revision and exit successfully.\n"
359 , RTPathFilename(argv[0]));
360 return RTEXITCODE_SUCCESS;
361 case 'V':
362 RTPrintf("$Revision: 104920 $\n");
363 return RTEXITCODE_SUCCESS;
364
365 case 'i':
366 pszInput = ValueUnion.psz;
367 break;
368 case 's':
369 pszSave = ValueUnion.psz;
370 break;
371 case 'l':
372 {
373 RTLDRMOD hLdrMod;
374 rc = RTLdrLoadEx(ValueUnion.psz, &hLdrMod, RTLDRLOAD_FLAGS_NO_UNLOAD, NULL);
375 if (RT_SUCCESS(rc))
376 {
377 PFNTRACELOGDECODERPLUGINLOAD pfnLoad = NULL;
378 rc = RTLdrGetSymbol(hLdrMod, RT_TRACELOG_DECODER_PLUGIN_LOAD, (void **)&pfnLoad);
379 if (RT_SUCCESS(rc))
380 {
381 RTTRACELOGDECODERREGISTER RegCb;
382
383 RegCb.u32Version = RT_TRACELOG_DECODERREG_CB_VERSION;
384 RegCb.pfnRegisterDecoders = rtTraceLogToolRegisterDecoders;
385
386 rc = pfnLoad(&DecoderState, &RegCb);
387 if (RT_FAILURE(rc))
388 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to register decoders %Rrc\n", rc);
389 }
390 else
391 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to lretrieve entry point '%s' %Rrc\n",
392 RT_TRACELOG_DECODER_PLUGIN_LOAD, rc);
393
394 RTLdrClose(hLdrMod);
395 }
396 else
397 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to load decoder library %Rrc\n", rc);
398 break;
399 }
400 default:
401 return RTGetOptPrintError(rc, &ValueUnion);
402 }
403 }
404
405 if (!pszInput)
406 {
407 RTPrintf("An input path must be given\n");
408 return RTEXITCODE_FAILURE;
409 }
410
411 /*
412 * Create trace log reader instance.
413 */
414 RTTRACELOGRDR hTraceLogRdr = NIL_RTTRACELOGRDR;
415 rc = rtTraceLogToolReaderCreate(&hTraceLogRdr, pszInput, pszSave);
416 if (RT_SUCCESS(rc))
417 {
418 do
419 {
420 RTTRACELOGRDRPOLLEVT enmEvt = RTTRACELOGRDRPOLLEVT_INVALID;
421 rc = RTTraceLogRdrEvtPoll(hTraceLogRdr, &enmEvt, RT_INDEFINITE_WAIT);
422 if (RT_SUCCESS(rc))
423 {
424 switch (enmEvt)
425 {
426 case RTTRACELOGRDRPOLLEVT_HDR_RECVD:
427 RTMsgInfo("A valid header was received\n");
428 break;
429 case RTTRACELOGRDRPOLLEVT_TRACE_EVENT_RECVD:
430 {
431 RTTRACELOGRDREVT hTraceLogEvt;
432 rc = RTTraceLogRdrQueryLastEvt(hTraceLogRdr, &hTraceLogEvt);
433 if (RT_SUCCESS(rc))
434 {
435 PCRTTRACELOGEVTDESC pEvtDesc = RTTraceLogRdrEvtGetDesc(hTraceLogEvt);
436 RTMsgInfo("%llu %llu %s\n",
437 RTTraceLogRdrEvtGetSeqNo(hTraceLogEvt),
438 RTTraceLogRdrEvtGetTs(hTraceLogEvt),
439 pEvtDesc->pszId);
440
441 /*
442 * Look through our registered decoders and pass the decoding on to it.
443 * If there is no decoder registered just dump the raw values.
444 */
445 PRTTRACELOGDECODER pDecoder = NULL;
446 PCRTTRACELOGDECODEEVT pDecodeEvt = NULL;
447 for (uint32_t i = 0; (i < DecoderState.cDecoders) && !pDecoder; i++)
448 {
449 PCRTTRACELOGDECODEEVT pTmp = DecoderState.paDecoders[i].pReg->paEvtIds;
450 while (pTmp->pszEvtId)
451 {
452 if (!strcmp(pTmp->pszEvtId, pEvtDesc->pszId))
453 {
454 pDecoder = &DecoderState.paDecoders[i];
455 pDecodeEvt = pTmp;
456 break;
457 }
458 pTmp++;
459 }
460 }
461
462 if (pDecoder)
463 {
464 Assert(pDecodeEvt);
465
466 /** @todo Dynamic value allocation (too lazy right now). */
467 RTTRACELOGEVTVAL aVals[32];
468 uint32_t cVals = 0;
469 rc = RTTraceLogRdrEvtFillVals(hTraceLogEvt, 0, &aVals[0], RT_ELEMENTS(aVals),
470 &cVals);
471 if ( RT_SUCCESS(rc)
472 || cVals != pEvtDesc->cEvtItems)
473 {
474 rc = pDecoder->pReg->pfnDecode(&pDecoder->Hlp, pDecodeEvt->idDecodeEvt, hTraceLogEvt,
475 pEvtDesc, &aVals[0], cVals);
476 if (RT_FAILURE(rc))
477 RTMsgError("Failed to decode event with ID '%s' -> %Rrc\n", pEvtDesc->pszId, rc);
478 }
479 else
480 RTMsgError("Failed to fill values for event with ID '%s' -> %Rrc (cVals=%u vs. cEvtItems=%u)\n",
481 pEvtDesc->pszId, rc, cVals, pEvtDesc->cEvtItems);
482 }
483 else
484 for (unsigned i = 0; i < pEvtDesc->cEvtItems; i++)
485 {
486 RTTRACELOGEVTVAL Val;
487 unsigned cVals = 0;
488 rc = RTTraceLogRdrEvtFillVals(hTraceLogEvt, i, &Val, 1, &cVals);
489 if (RT_SUCCESS(rc))
490 {
491 switch (Val.pItemDesc->enmType)
492 {
493 case RTTRACELOGTYPE_BOOL:
494 RTMsgInfo(" %s: %s\n", Val.pItemDesc->pszName, Val.u.f ? "true" : "false");
495 break;
496 case RTTRACELOGTYPE_UINT8:
497 RTMsgInfo(" %s: %u\n", Val.pItemDesc->pszName, Val.u.u8);
498 break;
499 case RTTRACELOGTYPE_INT8:
500 RTMsgInfo(" %s: %d\n", Val.pItemDesc->pszName, Val.u.i8);
501 break;
502 case RTTRACELOGTYPE_UINT16:
503 RTMsgInfo(" %s: %u\n", Val.pItemDesc->pszName, Val.u.u16);
504 break;
505 case RTTRACELOGTYPE_INT16:
506 RTMsgInfo(" %s: %d\n", Val.pItemDesc->pszName, Val.u.i16);
507 break;
508 case RTTRACELOGTYPE_UINT32:
509 RTMsgInfo(" %s: %u\n", Val.pItemDesc->pszName, Val.u.u32);
510 break;
511 case RTTRACELOGTYPE_INT32:
512 RTMsgInfo(" %s: %d\n", Val.pItemDesc->pszName, Val.u.i32);
513 break;
514 case RTTRACELOGTYPE_UINT64:
515 RTMsgInfo(" %s: %llu\n", Val.pItemDesc->pszName, Val.u.u64);
516 break;
517 case RTTRACELOGTYPE_INT64:
518 RTMsgInfo(" %s: %lld\n", Val.pItemDesc->pszName, Val.u.i64);
519 break;
520 case RTTRACELOGTYPE_RAWDATA:
521 RTMsgInfo(" %s:\n"
522 "%.*Rhxd\n", Val.pItemDesc->pszName, Val.u.RawData.cb, Val.u.RawData.pb);
523 break;
524 case RTTRACELOGTYPE_FLOAT32:
525 case RTTRACELOGTYPE_FLOAT64:
526 RTMsgInfo(" %s: Float32 and Float64 data not supported yet\n", Val.pItemDesc->pszName);
527 break;
528 case RTTRACELOGTYPE_POINTER:
529 RTMsgInfo(" %s: %#llx\n", Val.pItemDesc->pszName, Val.u.uPtr);
530 break;
531 case RTTRACELOGTYPE_SIZE:
532 RTMsgInfo(" %s: %llu\n", Val.pItemDesc->pszName, Val.u.sz);
533 break;
534 default:
535 RTMsgError(" %s: Invalid type given %d\n", Val.pItemDesc->pszName, Val.pItemDesc->enmType);
536 }
537 }
538 else
539 RTMsgInfo(" Failed to retrieve event data with %Rrc\n", rc);
540 }
541 }
542 break;
543 }
544 default:
545 RTMsgInfo("Invalid event received: %d\n", enmEvt);
546 }
547 }
548 else
549 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Polling for an event failed with %Rrc\n", rc);
550 } while (RT_SUCCESS(rc));
551
552 RTTraceLogRdrDestroy(hTraceLogRdr);
553 }
554 else
555 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to create trace log reader with %Rrc\n", rc);
556
557 return rcExit;
558}
559
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette