Skip to content

Feat[Storagetool]: cluster state ledger (CSL) file search support #558

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 85 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 79 commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
4438797
Add seqnum and offset input args, add composite sequence number support
alexander-e1off Nov 1, 2024
465076c
Debug binary search for all range types
alexander-e1off Nov 7, 2024
32f36b3
Reemove debug
alexander-e1off Nov 7, 2024
e0cf5a1
Simplify getValue template
alexander-e1off Nov 8, 2024
bd9001d
Add search result decorators for seq.number and offset, add UTs
alexander-e1off Nov 8, 2024
cff0b26
Fix hasCache() call
alexander-e1off Nov 11, 2024
1fd8eac
Fix code style
alexander-e1off Nov 11, 2024
81ba90f
Fix code style
alexander-e1off Nov 11, 2024
8a5d825
Add search for specific sequence numbers and offsets
alexander-e1off Nov 12, 2024
fd3f96b
Cleanup
alexander-e1off Nov 12, 2024
42e6b9a
Fix README.md
alexander-e1off Nov 13, 2024
2343748
Fix typo in README.md
alexander-e1off Nov 13, 2024
5189f9c
Update header caption
alexander-e1off Nov 14, 2024
80c58ce
Fix review comments
alexander-e1off Nov 19, 2024
9175303
Fix code style
alexander-e1off Nov 19, 2024
ec47845
Add support of queueOp and journalOp records
alexander-e1off Nov 21, 2024
8a46f56
Add UTs, update footer printing
alexander-e1off Nov 22, 2024
d270645
Move printRecord methods to RecordPrinter namespace
alexander-e1off Nov 22, 2024
98655ac
Add printRecord method for queueOp record
alexander-e1off Nov 25, 2024
3e9f3f4
Add flag to stop search when reached higher bound
alexander-e1off Nov 26, 2024
9be7be8
Update README.md
alexander-e1off Nov 26, 2024
13522a6
Revert "Add flag to stop search when reached higher bound"
alexander-e1off Nov 27, 2024
096a3fa
Add flag to stop search when reached higher bound
alexander-e1off Nov 26, 2024
77a57a5
Update README.md, fix code style
alexander-e1off Nov 27, 2024
062979d
Fix typo
alexander-e1off Nov 27, 2024
05192f0
Cosmetic change: move validators to CommandLineArguments
alexander-e1off Nov 28, 2024
a82b95f
Cleanup
alexander-e1off Nov 28, 2024
d69dbf6
Update README.md
alexander-e1off Nov 28, 2024
15cc481
Fix comment
alexander-e1off Nov 29, 2024
042b1ff
Fix typo
alexander-e1off Nov 29, 2024
06aebf7
Added base structures
alexander-e1off Dec 4, 2024
817ec28
Refactor FileManager class
alexander-e1off Dec 5, 2024
eb3740b
Update csl file processor and file manager
alexander-e1off Dec 6, 2024
a1d976b
Update CslSearchResult classes
alexander-e1off Dec 17, 2024
0dd4c44
Add csl processor UTs, update filters
alexander-e1off Dec 23, 2024
f62b30a
Add tests to m_bmqstoragetool_cslfileprocessor.t
alexander-e1off Dec 24, 2024
15c024f
Add seqNum/offset decorators
alexander-e1off Dec 27, 2024
42cb59e
Add summary support
alexander-e1off Dec 31, 2024
3d1fb70
Add summary UT
alexander-e1off Jan 2, 2025
75770c2
Update README.md
alexander-e1off Jan 2, 2025
ccd7137
Cleanup code
alexander-e1off Jan 3, 2025
6b08426
Fix offset option validation
alexander-e1off Jan 3, 2025
8c97579
Fix QueueMap::queueInfos, fix error message
alexander-e1off Jan 7, 2025
d7c8afd
Small improvements
alexander-e1off Jan 14, 2025
3d9276b
Handle CSL iteration error
alexander-e1off Jan 14, 2025
db75178
Update error message
alexander-e1off Jan 14, 2025
fd39205
Fix compile error
alexander-e1off Jan 14, 2025
6f6f4b5
Update error handling
alexander-e1off Jan 14, 2025
bd008d5
Output result in case of error
alexander-e1off Jan 14, 2025
d89518b
Merge from main, fix merge conflicts
alexander-e1off Jan 15, 2025
74a0952
Add missed license
alexander-e1off Jan 15, 2025
8df6bd1
Merge from main, fix merge conflicts
alexander-e1off Jan 20, 2025
6a4507c
Fix formatting
alexander-e1off Jan 20, 2025
c8aa29f
Add summary queues limit option
alexander-e1off Jan 24, 2025
26f3e37
Merge from main, fix merge conflicts
alexander-e1off Feb 17, 2025
8f9ad5e
Implement human printers for short and detail results
alexander-e1off Feb 20, 2025
f078ed5
Add humanreadable printer methods for all SearchResult classes
alexander-e1off Feb 21, 2025
d76df1c
Fix cslfileprocessor UTs
alexander-e1off Feb 25, 2025
a29b8a6
Fix code style
alexander-e1off Feb 25, 2025
7078547
Merge remote-tracking branch 'upstream/main' into csl-file-support
alexander-e1off Feb 25, 2025
666837a
Add cslprinter UTs, fix failed UT
alexander-e1off Feb 25, 2025
a0ac39e
Fix failed test
alexander-e1off Feb 25, 2025
a654560
Fix malformed json (missed coma)
alexander-e1off Feb 26, 2025
901e924
Add JsonCslPrinter methods
alexander-e1off Feb 28, 2025
66efe9b
Add UTs for pretty printer
alexander-e1off Mar 4, 2025
5346292
Add json line printer UTs, fix code style
alexander-e1off Mar 5, 2025
afc64f3
Merge remote-tracking branch 'upstream/main' into csl-file-support
alexander-e1off Mar 5, 2025
d99958c
Cleanup code, fix formatting
alexander-e1off Mar 5, 2025
b7b366f
fix formatting
alexander-e1off Mar 5, 2025
fa4a54c
Fix failed test
alexander-e1off Mar 6, 2025
aaad48f
Merge remote-tracking branch 'upstream/main' into csl-file-support
alexander-e1off Mar 6, 2025
ab2fbba
Fix delimiter
alexander-e1off Mar 6, 2025
99288c3
Update readme.md with missed json format
alexander-e1off Mar 6, 2025
45001cd
Fix validation
alexander-e1off Mar 6, 2025
82fc964
Fix review comments
alexander-e1off Mar 7, 2025
b508729
Merge remote-tracking branch 'upstream/main' into csl-file-support
alexander-e1off Mar 10, 2025
d951bc2
Merge from main, fix merge conflicts
alexander-e1off Mar 14, 2025
fe0bced
Merge from main, fix conflicts
alexander-e1off Mar 14, 2025
c79785b
Merge from main, fix conflicts
alexander-e1off Mar 26, 2025
4153fd5
Fix review comments
alexander-e1off Mar 27, 2025
c883ff4
Fix code formatting
alexander-e1off Mar 27, 2025
0cd1dd4
Merge remote-tracking branch 'upstream/main' into csl-file-support
alexander-e1off Mar 28, 2025
bccbdf3
Merge remote-tracking branch 'upstream/main' into csl-file-support
alexander-e1off Apr 2, 2025
6cb9945
Merge remote-tracking branch 'upstream/main' into csl-file-support
alexander-e1off Apr 9, 2025
bcf4415
Merge branch 'main' into csl-file-support
alexander-e1off Apr 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 121 additions & 12 deletions src/applications/bmqstoragetool/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,30 @@ BMQStorageTool
==============

BMQStorageTool is a command-line tool for analyzing of BlazingMQ Broker storage
files. It allows to search records in `journal` file with set of different
filters and output found results in the short or detail form.
As input, a `journal` file (*.bmq_journal) is *always* required. To dump
payload, `data` file (*.bmq_data) is required. To filter by queue Uri, cluster
state ledger (CSL) file (*.bmq_csl) is required.
files. It allows to search records in:
- `journal` file (.bmq_journal) with the set of different filters and output found results in
the short or detail form;
- `cluster state ledger` (CSL) file (*.bmq_csl) with the set of different filters and output
found results in the short or detail form;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the current form of explanation is a bit too heavy and should be simplified and disambiguated:

Suggested change
files. It allows to search records in:
- `journal` file (.bmq_journal) with the set of different filters and output found results in
the short or detail form;
- `cluster state ledger` (CSL) file (*.bmq_csl) with the set of different filters and output
found results in the short or detail form;
files. Using a set of different filters, it allows to search records in:
- `journal` file (.bmq_journal).
- `cluster state ledger` (CSL) file (*.bmq_csl).
The output results can be returned in either short or detailed form.

NOTE: For this mode, `journal` file (.bmq_journal) **must not** be passed.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is a must, it is better to enforce it in the storage tool itself. Also this note can be merged with the next paragraph


As an input, either a `journal` file (*.bmq_journal) or `cluster state ledger`
(CSL) file (*.bmq_csl) is **always** required. To dump payload, `data` file (*.bmq_data)
is required. To filter by queue Uri, cluster state ledger (CSL) file (*.bmq_csl) is required.

The tool can be found under your `CMAKE` build directory after making
the project. From the command-line, there are a few options you can use when
invoking the tool.

```bash
Usage: bmqstoragetool [-r|record-type <record type>]*
[--csl-record-type <csl record type>]*
[--journal-path <journal path>]
[--journal-file <journal file>]
[--data-file <data file>]
[--csl-file <csl file>]
[--csl-from-begin]
[--print-mode <print mode>]
[--guid <guid>]*
[--seqnum <seqnum>]*
[--offset <offset>]*
Expand All @@ -35,12 +43,15 @@ Usage: bmqstoragetool [-r|record-type <record type>]*
[--details]
[--dump-payload]
[--dump-limit <dump limit>]
[--min-records-per-queue <threshold>]
[--summary]
[--min-records-per-queue <threshold>]
[--summary-queues-limit <queues limit>]
[-h|help]
Where:
-r | --record-type <record type>
record type to search {message|queue-op|journal-op} (default: message)
record type to search {<message>|queue-op|journal-op} (default: message)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need angle brackets for message?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just follow the style of bmqtool to highlight default value in case when it is not explicitly defined in balcl::OptionInfo.

--csl-record-type <csl record type>
CSL record type to search {<snapshot>|update|commit|ack} (default: all record types)
--journal-path <pattern>
'*'-ended file path pattern, where the tool will try to find journal
and data files
Expand All @@ -50,6 +61,10 @@ Where:
path to a .bmq_data file
--csl-file <csl file>
path to a .bmq_csl file
--csl-from-begin
force to iterate CSL file from the beginning. By default: iterate from the latest snapshot
--print-mode <print mode>
can be one of the following {<human>|json-prety|json-line} (default: human)
--guid <guid>
message guid
--seqnum <seqnum>
Expand Down Expand Up @@ -89,15 +104,17 @@ Where:
--summary
summary of all matching messages (number of outstanding messages and
other statistics)
--summary-queues-limit <queues limit>
limit of queues to display in CSL file summary (default: 50)
-h | --help
print usage
```

Scenarios of BMQStorageTool usage
=================================
Scenarios of BMQStorageTool usage for journal file
==================================================

Output summary for journal file
----------------------------------------
-------------------------------
Example:
```bash
./bmqstoragetool.tsk --journal-file=<path> --summary
Expand All @@ -111,7 +128,7 @@ Example:
```

Search and otput all queueOp/journalOp records or all records in journal file
--------------------------------------------------
-----------------------------------------------------------------------------
Example:
```bash
./bmqstoragetool.tsk --journal-file=<path> --record-type=queue-op
Expand All @@ -136,14 +153,48 @@ Example:
```

Search all message GUIDs with payload dump in journal file
----------------------------------------------------------------------
----------------------------------------------------------
Example:
```bash
./bmqstoragetool.tsk --journal-file=<journal-path> --data-file=<data-path> --dump-payload
./bmqstoragetool.tsk --journal-path=<path.*> --dump-payload
./bmqstoragetool.tsk --journal-path=<path.*> --dump-payload --dump-limit=64
```

Scenarios of BMQStorageTool usage for CSL file
==============================================

Output summary for CSL file
---------------------------
Example:
```bash
./bmqstoragetool.tsk --csl-file=<path> --summary
./bmqstoragetool.tsk --csl-file=<path> --summary --summary-queues-limit=100
```

Output records from the beginning of CSL file
---------------------------------------------
Example:
```bash
./bmqstoragetool.tsk --csl-file=<path> --csl-from-begin
```
NOTE: by default search is done from the latest snapshot.

Search and otput only desired record types in CSL file
------------------------------------------------------
Example:
```bash
./bmqstoragetool.tsk --csl-file=<path> --csl-record-type=snapshot --csl-record-type=update
```
NOTE: `snapshot`, `update`, `commit` and `ack ` are supported. Without this option all record types are selected.

Search and otput records details in CSL file
--------------------------------------------
Example:
```bash
./bmqstoragetool.tsk --csl-file=<path> --details
```

Applying search filters to above scenarios
==========================================

Expand All @@ -153,6 +204,24 @@ Example:
```bash
./bmqstoragetool.tsk --journal-file=<path> --guid=<guid_1> --guid=<guid_N>
```
NOTE: no other filters are allowed with this one. Not suitable for CSL file search.

Filter messages with corresponding composite sequence numbers (defined in form `primaryLeaseId-sequenceNumber` for journal file or `electorTerm-sequenceNumber` for CSL file)
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can make this header shorter by moving the note

Suggested change
Filter messages with corresponding composite sequence numbers (defined in form `primaryLeaseId-sequenceNumber` for journal file or `electorTerm-sequenceNumber` for CSL file)
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Filter messages with corresponding composite sequence numbers
-------------------------------------------------------------
Composite sequence numbers are defined in form `primaryLeaseId-sequenceNumber` for journal file or `electorTerm-sequenceNumber` for CSL file.

Example:
```bash
./bmqstoragetool.tsk --journal-file=<path> --seqnum=<leaseId1-sequenceNumber_1> --seqnum=<leaseId_N-sequenceNumber_N>
./bmqstoragetool.tsk --csl-file=<path> --seqnum=<electorTerm1-sequenceNumber_1> --seqnum=<electorTerm_N-sequenceNumber_N>
```
NOTE: no other filters are allowed with this one

Filter messages with corresponding record offsets
-------------------------------------------------
Example:
```bash
./bmqstoragetool.tsk --journal-file=<path> --offset=<offset_1> --offset=<offset_N>
./bmqstoragetool.tsk --csl-file=<path> --offset=<offset_1> --offset=<offset_N>
```
NOTE: no other filters are allowed with this one

Filter messages with corresponding composite sequence numbers (defined in form <primaryLeaseId-sequenceNumber>)
Expand All @@ -178,6 +247,27 @@ Example:
./bmqstoragetool.tsk --journal-file=<path> --timestamp-lt=<stamp>
./bmqstoragetool.tsk --journal-file=<path> --timestamp-gt=<stamp>
./bmqstoragetool.tsk --journal-file=<path> --timestamp-lt=<stamp1> --timestamp-gt=<stamp2>
./bmqstoragetool.tsk --csl-file=<path> --timestamp-lt=<stamp1> --timestamp-gt=<stamp2>
```

Filter messages within composite sequence numbers (primaryLeaseId/electorTerm, sequenceNumber) range
----------------------------------------------------------------------------------------------------
Example:
```bash
./bmqstoragetool.tsk --journal-file=<path> --seqnum-lt=<leaseId-sequenceNumber>
./bmqstoragetool.tsk --journal-file=<path> --seqnum-gt=<leaseId-sequenceNumber>
./bmqstoragetool.tsk --journal-file=<path> --seqnum-lt=<leaseId1-sequenceNumber1> --seqnum-gt=<leaseId2-sequenceNumber2>
./bmqstoragetool.tsk --csl-file=<path> --seqnum-lt=<electorTerm1-sequenceNumber1> --seqnum-gt=<electorTerm2-sequenceNumber2>
```

Filter messages within record offsets range
-------------------------------------------
Example:
```bash
./bmqstoragetool.tsk --journal-file=<path> --offset-lt=<offset>
./bmqstoragetool.tsk --journal-file=<path> --offset-gt=<offset>
./bmqstoragetool.tsk --journal-file=<path> --offset-lt=<offset1> --offset-gt=<offset2>
./bmqstoragetool.tsk --csl-file=<path> --offset-lt=<offset1> --offset-gt=<offset2>
```

Filter messages within composite sequence numbers (primaryLeaseId, sequenceNumber) range
Expand All @@ -203,16 +293,35 @@ Filter messages by queue key
Example:
```bash
./bmqstoragetool.tsk --journal-file=<path> --queue-key=<key_1> --queue-key=<key_N>
./bmqstoragetool.tsk --csl-file=<path> --queue-key=<key_1> --queue-key=<key_N>
```

Filter messages by queue Uri
----------------------------
Example:
```bash
./bmqstoragetool.tsk --journal-file=<journal_path> --csl-file=<csl_path> --queue-name=<queue_uri_1> --queue-name=<queue_uri_N>
./bmqstoragetool.tsk --csl-file=<csl_path> --queue-name=<queue_uri_1> --queue-name=<queue_uri_N>
```
NOTE: CSL file is required

Output search results in machine readable (JSON) format for all above scenarios
===============================================================================

Output in JSON pretty format
----------------------------
Example:
```bash
./bmqstoragetool.tsk --print-mode=json-pretty
```

Output in JSON line format
--------------------------
Example:
```bash
./bmqstoragetool.tsk --print-mode=json-line
```

Display number of records per type (e.g. Message, Confirm, Delete, etc.) per queue.
The number of Confirm records are displayed per AppId if there are more than 1 AppId.
The information is displayed for the queues with a total number of records greater or
Expand Down
29 changes: 22 additions & 7 deletions src/applications/bmqstoragetool/bmqstoragetool.m.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,19 @@ static bool parseArgs(CommandLineArguments& arguments,
{
bool showHelp = false;

bsl::vector<bsl::string> defaultRecordType(allocator);
defaultRecordType.push_back(CommandLineArguments::k_MESSAGE_TYPE);

balcl::OptionInfo specTable[] = {
{"r|record-type",
"record type",
"record type to search {message|queue-op|journal-op}",
"record type to search {<message>|queue-op|journal-op}",
balcl::TypeInfo(&arguments.d_recordType,
CommandLineArguments::isValidRecordType),
balcl::OccurrenceInfo(defaultRecordType)},
balcl::OccurrenceInfo::e_OPTIONAL},
{"csl-record-type",
"csl record type",
"CSL record type to search {<snapshot>|update|commit|ack}",
balcl::TypeInfo(&arguments.d_cslRecordType,
CommandLineArguments::isValidCslRecordType),
balcl::OccurrenceInfo::e_OPTIONAL},
{"journal-path",
"journal path",
"'*'-ended file path pattern, where the tool will try to find "
Expand All @@ -72,6 +75,12 @@ static bool parseArgs(CommandLineArguments& arguments,
balcl::TypeInfo(&arguments.d_cslFile,
CommandLineArguments::isValidFileName),
balcl::OccurrenceInfo::e_OPTIONAL},
{"csl-from-begin",
"start from begin",
"force to iterate CSL file from the beginning. By default: iterate "
"from the latest snapshot",
balcl::TypeInfo(&arguments.d_cslFromBegin),
balcl::OccurrenceInfo::e_OPTIONAL},
{"print-mode",
"print mode",
"can be one of the following {human|json-pretty|json-line}",
Expand Down Expand Up @@ -175,6 +184,11 @@ static bool parseArgs(CommandLineArguments& arguments,
"min number of records per queue for detailed info to be displayed",
balcl::TypeInfo(&arguments.d_minRecordsPerQueue),
balcl::OccurrenceInfo(0LL)},
{"summary-queues-limit",
"queues limit",
"limit of queues to display in CSL file summary",
balcl::TypeInfo(&arguments.d_cslSummaryQueuesLimit),
balcl::OccurrenceInfo(50)},
{"h|help",
"help",
"print usage)",
Expand Down Expand Up @@ -227,10 +241,11 @@ int main(int argc, const char* argv[])
fileManager.load(new (*allocator)
FileManagerImpl(arguments.d_journalFile,
arguments.d_dataFile,
arguments.d_cslFile,
arguments.d_cslFromBegin,
allocator));
if (!arguments.d_cslFile.empty()) {
parameters.d_queueMap =
FileManagerImpl::buildQueueMap(arguments.d_cslFile, allocator);
fileManager->fillQueueMapFromCslFile(&parameters.d_queueMap);
parameters.validateQueueNames();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

// bmqstoragetool
#include <m_bmqstoragetool_commandprocessorfactory.h>
#include <m_bmqstoragetool_cslfileprocessor.h>
#include <m_bmqstoragetool_journalfileprocessor.h>
#include <m_bmqstoragetool_searchresultfactory.h>

namespace BloombergLP {
Expand All @@ -36,38 +38,58 @@ CommandProcessorFactory::createCommandProcessor(

bslma::Allocator* alloc = bslma::Default::allocator(allocator);

// Create printer
bsl::shared_ptr<Printer> printer = createPrinter(params->d_printMode,
ostream,
allocator);
if (params->d_cslMode) {
// Create CSL printer
bsl::shared_ptr<CslPrinter> printer =
createCslPrinter(params->d_printMode, ostream, allocator);

// Create payload dumper
bslma::ManagedPtr<PayloadDumper> payloadDumper;
if (params->d_dumpPayload) {
payloadDumper.load(new (*alloc)
PayloadDumper(ostream,
fileManager->dataFileIterator(),
params->d_dumpLimit,
alloc),
alloc);
}
// Create CslSearchResult for given 'params'.
bsl::shared_ptr<CslSearchResult> cslSearchResult =
SearchResultFactory::createCslSearchResult(params, printer, alloc);

// Create searchResult for given 'params'.
bsl::shared_ptr<SearchResult> searchResult =
SearchResultFactory::createSearchResult(params,
fileManager,
printer,
payloadDumper,
alloc);
// Create commandProcessor.
bslma::ManagedPtr<CommandProcessor> commandProcessor(
new (*alloc) JournalFileProcessor(params,
// Create CslFileProcessor
return bslma::ManagedPtr<CommandProcessor>(
new (*alloc) CslFileProcessor(params,
fileManager,
searchResult,
cslSearchResult,
ostream,
alloc),
alloc);
return commandProcessor;
alloc); // RETURN
}
else {
// Create printer
bsl::shared_ptr<Printer> printer = createPrinter(params->d_printMode,
ostream,
allocator);

// Create payload dumper
bslma::ManagedPtr<PayloadDumper> payloadDumper;
if (params->d_dumpPayload) {
payloadDumper.load(
new (*alloc) PayloadDumper(ostream,
fileManager->dataFileIterator(),
params->d_dumpLimit,
alloc),
alloc);
}

// Create searchResult for given 'params'.
bsl::shared_ptr<SearchResult> searchResult =
SearchResultFactory::createSearchResult(params,
fileManager,
printer,
payloadDumper,
alloc);
// Create commandProcessor.
bslma::ManagedPtr<CommandProcessor> commandProcessor(
new (*alloc) JournalFileProcessor(params,
fileManager,
searchResult,
ostream,
alloc),
alloc);
return commandProcessor;
}
}

} // close package namespace
Expand Down
Loading
Loading