VirtualBox

source: vbox/trunk/src/VBox/Frontends/VBoxManage/VBoxManageMetrics.cpp@ 26163

Last change on this file since 26163 was 26163, checked in by vboxsync, 14 years ago

PDM: s/pUsbReg/pReg/g (2nd try, backed out r57176)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 20.0 KB
RevLine 
[12599]1/* $Id: VBoxManageMetrics.cpp 26163 2010-02-02 18:58:33Z vboxsync $ */
[1]2/** @file
[14736]3 * VBoxManage - The 'metrics' command.
[1]4 */
5
6/*
[20928]7 * Copyright (C) 2006-2009 Sun Microsystems, Inc.
[1]8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
[5999]12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
[8155]16 *
17 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
18 * Clara, CA 95054 USA or visit http://www.sun.com if you need
19 * additional information or have any questions.
[1]20 */
21
[14646]22#ifndef VBOX_ONLY_DOCS
[1]23
24/*******************************************************************************
25* Header Files *
26*******************************************************************************/
[14735]27#include <VBox/com/com.h>
[7379]28#include <VBox/com/array.h>
[1]29#include <VBox/com/ErrorInfo.h>
[20928]30#include <VBox/com/errorprint.h>
[1]31#include <VBox/com/VirtualBox.h>
32
[14734]33#include <iprt/asm.h>
[1]34#include <iprt/stream.h>
35#include <iprt/string.h>
[14731]36#include <iprt/time.h>
[1]37#include <iprt/thread.h>
[14731]38#include <VBox/log.h>
[1]39
40#include "VBoxManage.h"
41using namespace com;
42
43
44// funcs
45///////////////////////////////////////////////////////////////////////////////
46
[3077]47
[12024]48static char *toBaseMetricNames(const char *metricList)
49{
50 char *newList = (char*)RTMemAlloc(strlen(metricList) + 1);
51 int cSlashes = 0;
52 bool fSkip = false;
[12025]53 const char *src = metricList;
54 char c, *dst = newList;
[12024]55 while ((c = *src++))
56 if (c == ':')
57 fSkip = true;
58 else if (c == '/' && ++cSlashes == 2)
59 fSkip = true;
60 else if (c == ',')
61 {
62 fSkip = false;
63 cSlashes = 0;
64 *dst++ = c;
65 }
66 else
67 if (!fSkip)
68 *dst++ = c;
69 *dst = 0;
70 return newList;
71}
72
[11384]73static int parseFilterParameters(int argc, char *argv[],
74 ComPtr<IVirtualBox> aVirtualBox,
75 ComSafeArrayOut(BSTR, outMetrics),
[12024]76 ComSafeArrayOut(BSTR, outBaseMetrics),
[11384]77 ComSafeArrayOut(IUnknown *, outObjects))
78{
79 HRESULT rc = S_OK;
80 com::SafeArray<BSTR> retMetrics(1);
[12024]81 com::SafeArray<BSTR> retBaseMetrics(1);
[11384]82 com::SafeIfaceArray <IUnknown> retObjects;
83
[12024]84 Bstr metricNames, baseNames;
[11384]85
86 /* Metric list */
87 if (argc > 1)
[12024]88 {
[11384]89 metricNames = argv[1];
[12024]90 char *tmp = toBaseMetricNames(argv[1]);
91 if (!tmp)
92 return VERR_NO_MEMORY;
93 baseNames = tmp;
94 RTMemFree(tmp);
95 }
[11384]96 else
[12024]97 {
[11384]98 metricNames = L"*";
[12024]99 baseNames = L"*";
100 }
[11384]101 metricNames.cloneTo(&retMetrics[0]);
[12024]102 baseNames.cloneTo(&retBaseMetrics[0]);
[11384]103
104 /* Object name */
105 if (argc > 0 && strcmp(argv[0], "*"))
106 {
107 if (!strcmp(argv[0], "host"))
108 {
109 ComPtr<IHost> host;
110 CHECK_ERROR(aVirtualBox, COMGETTER(Host)(host.asOutParam()));
111 retObjects.reset(1);
112 host.queryInterfaceTo(&retObjects[0]);
113 }
114 else
115 {
116 ComPtr <IMachine> machine;
117 rc = aVirtualBox->FindMachine(Bstr(argv[0]), machine.asOutParam());
118 if (SUCCEEDED (rc))
119 {
120 retObjects.reset(1);
121 machine.queryInterfaceTo(&retObjects[0]);
122 }
123 else
124 {
125 errorArgument("Invalid machine name: '%s'", argv[0]);
126 return rc;
127 }
128 }
129
130 }
131
132 retMetrics.detachTo(ComSafeArrayOutArg(outMetrics));
[12024]133 retBaseMetrics.detachTo(ComSafeArrayOutArg(outBaseMetrics));
[11384]134 retObjects.detachTo(ComSafeArrayOutArg(outObjects));
135
136 return rc;
137}
138
139static Bstr getObjectName(ComPtr<IVirtualBox> aVirtualBox,
140 ComPtr<IUnknown> aObject)
141{
142 HRESULT rc;
143
144 ComPtr<IHost> host = aObject;
145 if (!host.isNull())
146 return Bstr("host");
147
148 ComPtr<IMachine> machine = aObject;
149 if (!machine.isNull())
150 {
151 Bstr name;
152 CHECK_ERROR(machine, COMGETTER(Name)(name.asOutParam()));
153 if (SUCCEEDED(rc))
154 return name;
155 }
156 return Bstr("unknown");
157}
158
[12668]159static void listAffectedMetrics(ComPtr<IVirtualBox> aVirtualBox,
160 ComSafeArrayIn(IPerformanceMetric*, aMetrics))
161{
162 HRESULT rc;
163 com::SafeIfaceArray<IPerformanceMetric> metrics(ComSafeArrayInArg(aMetrics));
164 if (metrics.size())
165 {
166 ComPtr<IUnknown> object;
167 Bstr metricName;
168 RTPrintf("The following metrics were modified:\n\n"
169 "Object Metric\n"
170 "---------- --------------------\n");
171 for (size_t i = 0; i < metrics.size(); i++)
172 {
173 CHECK_ERROR(metrics[i], COMGETTER(Object)(object.asOutParam()));
174 CHECK_ERROR(metrics[i], COMGETTER(MetricName)(metricName.asOutParam()));
175 RTPrintf("%-10ls %-20ls\n",
176 getObjectName(aVirtualBox, object).raw(), metricName.raw());
177 }
178 RTPrintf("\n");
179 }
180 else
181 {
182 RTPrintf("No metrics match the specified filter!\n");
183 }
184}
185
[12599]186/**
187 * list *
188 */
[12024]189static int handleMetricsList(int argc, char *argv[],
190 ComPtr<IVirtualBox> aVirtualBox,
191 ComPtr<IPerformanceCollector> performanceCollector)
[11384]192{
193 HRESULT rc;
194 com::SafeArray<BSTR> metrics;
[12024]195 com::SafeArray<BSTR> baseMetrics;
[11384]196 com::SafeIfaceArray<IUnknown> objects;
197
[12024]198 rc = parseFilterParameters(argc - 1, &argv[1], aVirtualBox,
199 ComSafeArrayAsOutParam(metrics),
200 ComSafeArrayAsOutParam(baseMetrics),
201 ComSafeArrayAsOutParam(objects));
202 if (FAILED(rc))
203 return 1;
[11384]204
[12024]205 com::SafeIfaceArray<IPerformanceMetric> metricInfo;
[11384]206
[12024]207 CHECK_ERROR(performanceCollector,
208 GetMetrics(ComSafeArrayAsInParam(metrics),
209 ComSafeArrayAsInParam(objects),
210 ComSafeArrayAsOutParam(metricInfo)));
211
212 ComPtr<IUnknown> object;
213 Bstr metricName, unit, description;
214 ULONG period, count;
215 LONG minimum, maximum;
216 RTPrintf(
217"Object Metric Unit Minimum Maximum Period Count Description\n"
218"---------- -------------------- ---- ---------- ---------- ---------- ---------- -----------\n");
219 for (size_t i = 0; i < metricInfo.size(); i++)
[11384]220 {
[12024]221 CHECK_ERROR(metricInfo[i], COMGETTER(Object)(object.asOutParam()));
222 CHECK_ERROR(metricInfo[i], COMGETTER(MetricName)(metricName.asOutParam()));
223 CHECK_ERROR(metricInfo[i], COMGETTER(Period)(&period));
224 CHECK_ERROR(metricInfo[i], COMGETTER(Count)(&count));
225 CHECK_ERROR(metricInfo[i], COMGETTER(MinimumValue)(&minimum));
226 CHECK_ERROR(metricInfo[i], COMGETTER(MaximumValue)(&maximum));
227 CHECK_ERROR(metricInfo[i], COMGETTER(Unit)(unit.asOutParam()));
228 CHECK_ERROR(metricInfo[i], COMGETTER(Description)(description.asOutParam()));
229 RTPrintf("%-10ls %-20ls %-4ls %10d %10d %10u %10u %ls\n",
230 getObjectName(aVirtualBox, object).raw(), metricName.raw(), unit.raw(),
231 minimum, maximum, period, count, description.raw());
232 }
[12448]233
[12024]234 return 0;
235}
[11384]236
[12599]237/**
238 * Metics setup
239 */
[12024]240static int handleMetricsSetup(int argc, char *argv[],
241 ComPtr<IVirtualBox> aVirtualBox,
242 ComPtr<IPerformanceCollector> performanceCollector)
243{
244 HRESULT rc;
245 com::SafeArray<BSTR> metrics;
246 com::SafeArray<BSTR> baseMetrics;
247 com::SafeIfaceArray<IUnknown> objects;
[14740]248 uint32_t period = 1, samples = 1;
[12668]249 bool listMatches = false;
[12051]250 int i;
[11384]251
[12051]252 for (i = 1; i < argc; i++)
253 {
[18776]254 if ( !strcmp(argv[i], "--period")
255 || !strcmp(argv[i], "-period"))
[12051]256 {
257 if (argc <= i + 1)
258 return errorArgument("Missing argument to '%s'", argv[i]);
[14740]259 if ( VINF_SUCCESS != RTStrToUInt32Full(argv[++i], 10, &period)
260 || !period)
[12051]261 return errorArgument("Invalid value for 'period' parameter: '%s'", argv[i]);
262 }
[18776]263 else if ( !strcmp(argv[i], "--samples")
264 || !strcmp(argv[i], "-samples"))
[12051]265 {
266 if (argc <= i + 1)
267 return errorArgument("Missing argument to '%s'", argv[i]);
[14740]268 if ( VINF_SUCCESS != RTStrToUInt32Full(argv[++i], 10, &samples)
269 || !samples)
[12051]270 return errorArgument("Invalid value for 'samples' parameter: '%s'", argv[i]);
271 }
[18776]272 else if ( !strcmp(argv[i], "--list")
273 || !strcmp(argv[i], "-list"))
[12668]274 listMatches = true;
[12051]275 else
276 break; /* The rest of params should define the filter */
277 }
[12448]278
[12051]279 rc = parseFilterParameters(argc - i, &argv[i], aVirtualBox,
[12024]280 ComSafeArrayAsOutParam(metrics),
281 ComSafeArrayAsOutParam(baseMetrics),
282 ComSafeArrayAsOutParam(objects));
283 if (FAILED(rc))
284 return 1;
285
[13082]286 com::SafeIfaceArray<IPerformanceMetric> affectedMetrics;
287 CHECK_ERROR(performanceCollector,
288 SetupMetrics(ComSafeArrayAsInParam(metrics),
289 ComSafeArrayAsInParam(objects), period, samples,
290 ComSafeArrayAsOutParam(affectedMetrics)));
[12668]291 if (listMatches)
292 listAffectedMetrics(aVirtualBox,
293 ComSafeArrayAsInParam(affectedMetrics));
[13550]294
[12024]295 return 0;
296}
297
[12599]298/**
299 * metrics query
300 */
[12024]301static int handleMetricsQuery(int argc, char *argv[],
302 ComPtr<IVirtualBox> aVirtualBox,
303 ComPtr<IPerformanceCollector> performanceCollector)
304{
305 HRESULT rc;
306 com::SafeArray<BSTR> metrics;
307 com::SafeArray<BSTR> baseMetrics;
308 com::SafeIfaceArray<IUnknown> objects;
309
310 rc = parseFilterParameters(argc - 1, &argv[1], aVirtualBox,
311 ComSafeArrayAsOutParam(metrics),
312 ComSafeArrayAsOutParam(baseMetrics),
313 ComSafeArrayAsOutParam(objects));
314 if (FAILED(rc))
315 return 1;
316
317 com::SafeArray<BSTR> retNames;
318 com::SafeIfaceArray<IUnknown> retObjects;
[13082]319 com::SafeArray<BSTR> retUnits;
320 com::SafeArray<ULONG> retScales;
321 com::SafeArray<ULONG> retSequenceNumbers;
[12024]322 com::SafeArray<ULONG> retIndices;
323 com::SafeArray<ULONG> retLengths;
324 com::SafeArray<LONG> retData;
325 CHECK_ERROR (performanceCollector, QueryMetricsData(ComSafeArrayAsInParam(metrics),
326 ComSafeArrayAsInParam(objects),
327 ComSafeArrayAsOutParam(retNames),
328 ComSafeArrayAsOutParam(retObjects),
[13082]329 ComSafeArrayAsOutParam(retUnits),
330 ComSafeArrayAsOutParam(retScales),
331 ComSafeArrayAsOutParam(retSequenceNumbers),
[12024]332 ComSafeArrayAsOutParam(retIndices),
333 ComSafeArrayAsOutParam(retLengths),
334 ComSafeArrayAsOutParam(retData)) );
335
336 RTPrintf("Object Metric Values\n"
337 "---------- -------------------- --------------------------------------------\n");
338 for (unsigned i = 0; i < retNames.size(); i++)
339 {
[13082]340 Bstr metricUnit(retUnits[i]);
[12024]341 Bstr metricName(retNames[i]);
342 RTPrintf("%-10ls %-20ls ", getObjectName(aVirtualBox, retObjects[i]).raw(), metricName.raw());
343 const char *separator = "";
344 for (unsigned j = 0; j < retLengths[i]; j++)
[11384]345 {
[13082]346 if (retScales[i] == 1)
[12024]347 RTPrintf("%s%d %ls", separator, retData[retIndices[i] + j], metricUnit.raw());
348 else
[13082]349 RTPrintf("%s%d.%02d%ls", separator, retData[retIndices[i] + j] / retScales[i],
350 (retData[retIndices[i] + j] * 100 / retScales[i]) % 100, metricUnit.raw());
[12024]351 separator = ", ";
[11384]352 }
[12024]353 RTPrintf("\n");
[11384]354 }
[12448]355
[12024]356 return 0;
357}
[11384]358
[12024]359static void getTimestamp(char *pts, size_t tsSize)
360{
361 *pts = 0;
362 AssertReturnVoid(tsSize >= 13); /* 3+3+3+3+1 */
363 RTTIMESPEC TimeSpec;
364 RTTIME Time;
365 RTTimeExplode(&Time, RTTimeNow(&TimeSpec));
366 pts += RTStrFormatNumber(pts, Time.u8Hour, 10, 2, 0, RTSTR_F_ZEROPAD);
367 *pts++ = ':';
368 pts += RTStrFormatNumber(pts, Time.u8Minute, 10, 2, 0, RTSTR_F_ZEROPAD);
369 *pts++ = ':';
370 pts += RTStrFormatNumber(pts, Time.u8Second, 10, 2, 0, RTSTR_F_ZEROPAD);
371 *pts++ = '.';
372 pts += RTStrFormatNumber(pts, Time.u32Nanosecond / 1000000, 10, 3, 0, RTSTR_F_ZEROPAD);
373 *pts = 0;
[12448]374}
[11384]375
[12599]376/** Used by the handleMetricsCollect loop. */
377static bool volatile g_fKeepGoing = true;
[12594]378
[12596]379#ifdef RT_OS_WINDOWS
[12599]380/**
381 * Handler routine for catching Ctrl-C, Ctrl-Break and closing of
382 * the console.
383 *
384 * @returns true if handled, false if not handled.
385 * @param dwCtrlType The type of control signal.
386 *
387 * @remarks This is called on a new thread.
388 */
389static BOOL WINAPI ctrlHandler(DWORD dwCtrlType)
390{
391 switch (dwCtrlType)
392 {
[12594]393 /* Ctrl-C or Ctrl-Break or Close */
394 case CTRL_C_EVENT:
395 case CTRL_BREAK_EVENT:
[12599]396 case CTRL_CLOSE_EVENT:
[12594]397 /* Let's shut down gracefully. */
[12607]398 ASMAtomicWriteBool(&g_fKeepGoing, false);
[12605]399 return TRUE;
[12594]400 }
401 /* Don't care about the rest -- let it die a horrible death. */
[12605]402 return FALSE;
[12599]403}
[12596]404#endif /* RT_OS_WINDOWS */
[12594]405
[12599]406/**
407 * collect
408 */
[12024]409static int handleMetricsCollect(int argc, char *argv[],
410 ComPtr<IVirtualBox> aVirtualBox,
411 ComPtr<IPerformanceCollector> performanceCollector)
412{
413 HRESULT rc;
414 com::SafeArray<BSTR> metrics;
415 com::SafeArray<BSTR> baseMetrics;
416 com::SafeIfaceArray<IUnknown> objects;
[14740]417 uint32_t period = 1, samples = 1;
[12024]418 bool isDetached = false, listMatches = false;
419 int i;
420 for (i = 1; i < argc; i++)
421 {
[18776]422 if ( !strcmp(argv[i], "--period")
423 || !strcmp(argv[i], "-period"))
[12024]424 {
425 if (argc <= i + 1)
426 return errorArgument("Missing argument to '%s'", argv[i]);
[14740]427 if ( VINF_SUCCESS != RTStrToUInt32Full(argv[++i], 10, &period)
428 || !period)
[12024]429 return errorArgument("Invalid value for 'period' parameter: '%s'", argv[i]);
430 }
[18776]431 else if ( !strcmp(argv[i], "--samples")
432 || !strcmp(argv[i], "-samples"))
[12024]433 {
434 if (argc <= i + 1)
435 return errorArgument("Missing argument to '%s'", argv[i]);
[14740]436 if ( VINF_SUCCESS != RTStrToUInt32Full(argv[++i], 10, &samples)
437 || !samples)
[12051]438 return errorArgument("Invalid value for 'samples' parameter: '%s'", argv[i]);
[12024]439 }
[18776]440 else if ( !strcmp(argv[i], "--list")
441 || !strcmp(argv[i], "-list"))
[12024]442 listMatches = true;
[18776]443 else if ( !strcmp(argv[i], "--detach")
444 || !strcmp(argv[i], "-detach"))
[12024]445 isDetached = true;
446 else
447 break; /* The rest of params should define the filter */
448 }
[12448]449
[12024]450 rc = parseFilterParameters(argc - i, &argv[i], aVirtualBox,
451 ComSafeArrayAsOutParam(metrics),
452 ComSafeArrayAsOutParam(baseMetrics),
453 ComSafeArrayAsOutParam(objects));
454 if (FAILED(rc))
455 return 1;
[11384]456
457
[13082]458 com::SafeIfaceArray<IPerformanceMetric> affectedMetrics;
[12024]459 CHECK_ERROR(performanceCollector,
460 SetupMetrics(ComSafeArrayAsInParam(baseMetrics),
[13082]461 ComSafeArrayAsInParam(objects), period, samples,
462 ComSafeArrayAsOutParam(affectedMetrics)));
463 if (listMatches)
464 listAffectedMetrics(aVirtualBox,
465 ComSafeArrayAsInParam(affectedMetrics));
466 if (!affectedMetrics.size())
467 return 1;
[11384]468
[12024]469 if (isDetached)
470 {
471 RTPrintf("Warning! The background process holding collected metrics will shutdown\n"
472 "in few seconds, discarding all collected data and parameters.\n");
473 return 0;
474 }
[12448]475
[12594]476#ifdef RT_OS_WINDOWS
[12599]477 SetConsoleCtrlHandler(ctrlHandler, true);
[12594]478#endif /* RT_OS_WINDOWS */
479
[12024]480 RTPrintf("Time stamp Object Metric Value\n");
[12448]481
[12599]482 while (g_fKeepGoing)
[12024]483 {
484 RTPrintf("------------ ---------- -------------------- --------------------\n");
485 RTThreadSleep(period * 1000); // Sleep for 'period' seconds
486 char ts[15];
[12448]487
[12024]488 getTimestamp(ts, sizeof(ts));
[11384]489 com::SafeArray<BSTR> retNames;
490 com::SafeIfaceArray<IUnknown> retObjects;
[13082]491 com::SafeArray<BSTR> retUnits;
492 com::SafeArray<ULONG> retScales;
493 com::SafeArray<ULONG> retSequenceNumbers;
[11384]494 com::SafeArray<ULONG> retIndices;
495 com::SafeArray<ULONG> retLengths;
496 com::SafeArray<LONG> retData;
497 CHECK_ERROR (performanceCollector, QueryMetricsData(ComSafeArrayAsInParam(metrics),
498 ComSafeArrayAsInParam(objects),
499 ComSafeArrayAsOutParam(retNames),
500 ComSafeArrayAsOutParam(retObjects),
[13082]501 ComSafeArrayAsOutParam(retUnits),
502 ComSafeArrayAsOutParam(retScales),
503 ComSafeArrayAsOutParam(retSequenceNumbers),
[11384]504 ComSafeArrayAsOutParam(retIndices),
505 ComSafeArrayAsOutParam(retLengths),
506 ComSafeArrayAsOutParam(retData)) );
[25019]507 for (unsigned j = 0; j < retNames.size(); j++)
[11384]508 {
[25019]509 Bstr metricUnit(retUnits[j]);
510 Bstr metricName(retNames[j]);
511 RTPrintf("%-12s %-10ls %-20ls ", ts, getObjectName(aVirtualBox, retObjects[j]).raw(), metricName.raw());
[11384]512 const char *separator = "";
[25019]513 for (unsigned k = 0; k < retLengths[j]; k++)
[11384]514 {
[25019]515 if (retScales[j] == 1)
516 RTPrintf("%s%d %ls", separator, retData[retIndices[j] + k], metricUnit.raw());
[11384]517 else
[25019]518 RTPrintf("%s%d.%02d%ls", separator, retData[retIndices[j] + k] / retScales[j],
519 (retData[retIndices[j] + k] * 100 / retScales[j]) % 100, metricUnit.raw());
[11384]520 separator = ", ";
521 }
522 RTPrintf("\n");
523 }
[19653]524 RTStrmFlush(g_pStdOut);
[11384]525 }
[12448]526
[12594]527#ifdef RT_OS_WINDOWS
[12599]528 SetConsoleCtrlHandler(ctrlHandler, false);
[12594]529#endif /* RT_OS_WINDOWS */
530
[12024]531 return 0;
532}
533
[16052]534int handleMetrics(HandlerArg *a)
[12024]535{
536 int rc;
537
[12055]538 /* at least one option: subcommand name */
[16052]539 if (a->argc < 1)
[12055]540 return errorSyntax(USAGE_METRICS, "Subcommand missing");
[12024]541
542 ComPtr<IPerformanceCollector> performanceCollector;
[16052]543 CHECK_ERROR(a->virtualBox, COMGETTER(PerformanceCollector)(performanceCollector.asOutParam()));
[12024]544
[16052]545 if (!strcmp(a->argv[0], "list"))
546 rc = handleMetricsList(a->argc, a->argv, a->virtualBox, performanceCollector);
547 else if (!strcmp(a->argv[0], "setup"))
548 rc = handleMetricsSetup(a->argc, a->argv, a->virtualBox, performanceCollector);
549 else if (!strcmp(a->argv[0], "query"))
550 rc = handleMetricsQuery(a->argc, a->argv, a->virtualBox, performanceCollector);
551 else if (!strcmp(a->argv[0], "collect"))
552 rc = handleMetricsCollect(a->argc, a->argv, a->virtualBox, performanceCollector);
[11384]553 else
[16052]554 return errorSyntax(USAGE_METRICS, "Invalid subcommand '%s'", a->argv[0]);
[11384]555
[12024]556 return rc;
[11384]557}
558
[14646]559#endif /* VBOX_ONLY_DOCS */
[1]560
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use